Factor common chart configuration

its_happening
Tyler Goodlet 2020-06-14 11:48:48 -04:00
parent 6bac250db5
commit 856c6f2fd8
1 changed files with 62 additions and 41 deletions

View File

@ -1,7 +1,7 @@
""" """
Real-time quotes charting components Real-time quotes charting components
""" """
from typing import Callable from typing import Callable, List, Tuple
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
@ -279,7 +279,9 @@ class ScrollFromRightView(pg.ViewBox):
# ) # )
furthest_right_coord = self.boundingRect().topRight() furthest_right_coord = self.boundingRect().topRight()
center = pg.Point( center = pg.Point(
fn.invertQTransform(self.childGroup.transform()).map(furthest_right_coord) fn.invertQTransform(
self.childGroup.transform()
).map(furthest_right_coord)
) )
self._resetTarget() self._resetTarget()
@ -288,13 +290,15 @@ class ScrollFromRightView(pg.ViewBox):
self.sigRangeChangedManually.emit(mask) self.sigRangeChangedManually.emit(mask)
# TODO: convert this to a ``ViewBox`` type giving us # TODO: This is a sub-class of ``GracphicView`` which can
# control over mouse scrolling and a context menu
# This is a sub-class of ``GracphicView`` which can
# take a ``background`` color setting. # take a ``background`` color setting.
class CustomPlotWidget(pg.PlotWidget): class ChartPlotWidget(pg.PlotWidget):
"""``GraphicsView`` subtype containing a single ``PlotItem``. """``GraphicsView`` subtype containing a single ``PlotItem``.
Overrides a ``pyqtgraph.PlotWidget`` (a ``GraphicsView`` containing
a single ``PlotItem``) to intercept and and re-emit mouse enter/exit
events.
(Could be replaced with a ``pg.GraphicsLayoutWidget`` if we (Could be replaced with a ``pg.GraphicsLayoutWidget`` if we
eventually want multiple plots managed together). eventually want multiple plots managed together).
""" """
@ -360,6 +364,7 @@ class CrossHairItem(pg.GraphicsObject):
slot=lambda: self.mouseAction('Leave', False), slot=lambda: self.mouseAction('Leave', False),
) )
# determine where to place x-axis label
if _xaxis_at == 'bottom': if _xaxis_at == 'bottom':
# place below is last indicator subplot # place below is last indicator subplot
self.xaxis_label = XAxisLabel( self.xaxis_label = XAxisLabel(
@ -407,7 +412,8 @@ class CrossHairItem(pg.GraphicsObject):
else: else:
self.yaxis_label.show() self.yaxis_label.show()
self.hline.show() self.hline.show()
else: # Leave # Leave
else:
# hide horiz line and y-label # hide horiz line and y-label
if ind: if ind:
self.indicators[ind]['hl'].hide() self.indicators[ind]['hl'].hide()
@ -424,7 +430,7 @@ class CrossHairItem(pg.GraphicsObject):
pos = evt[0] pos = evt[0]
# if the mouse is within the parent ``CustomPlotWidget`` # if the mouse is within the parent ``ChartPlotWidget``
if self.parent.sceneBoundingRect().contains(pos): if self.parent.sceneBoundingRect().contains(pos):
# mouse_point = self.vb.mapSceneToView(pos) # mouse_point = self.vb.mapSceneToView(pos)
mouse_point = self.parent.mapToView(pos) mouse_point = self.parent.mapToView(pos)
@ -551,22 +557,16 @@ class CandlestickItem(BarItem):
p.drawRects(*rects[Quotes.close < Quotes.open]) p.drawRects(*rects[Quotes.close < Quotes.open])
def _configure_quotes_chart( def _configure_chart(
chart: CustomPlotWidget, chart: ChartPlotWidget,
style: ChartType,
update_yrange_limits: Callable,
) -> None: ) -> None:
"""Update and format a chart with quotes data. """Configure a chart with common settings.
""" """
# show only right side axes
chart.hideAxis('left') chart.hideAxis('left')
chart.showAxis('right') chart.showAxis('right')
# adds all bar/candle graphics objects for each
# data point in the np array buffer to
# be drawn on next render cycle
chart.addItem(_get_chart_points(style))
# set panning limits # set panning limits
chart.setLimits( chart.setLimits(
xMin=Quotes[0].id, xMin=Quotes[0].id,
@ -575,16 +575,53 @@ def _configure_quotes_chart(
yMin=Quotes.low.min() * 0.98, yMin=Quotes.low.min() * 0.98,
yMax=Quotes.high.max() * 1.02, yMax=Quotes.high.max() * 1.02,
) )
# show background grid
chart.showGrid(x=True, y=True, alpha=0.4) chart.showGrid(x=True, y=True, alpha=0.4)
# use cross-hair for cursor
chart.setCursor(QtCore.Qt.CrossCursor) chart.setCursor(QtCore.Qt.CrossCursor)
def _configure_quotes_chart(
chart: ChartPlotWidget,
style: ChartType,
update_yrange_limits: Callable,
) -> None:
"""Update and format a chart with quotes data.
"""
_configure_chart(chart)
# adds all bar/candle graphics objects for each
# data point in the np array buffer to
# be drawn on next render cycle
chart.addItem(_get_chart_points(style))
# assign callback for rescaling y-axis automatically # assign callback for rescaling y-axis automatically
# based on y-range contents # based on y-range contents
# TODO: this can likely be ported to built-in: .enableAutoRange() # TODO: this can likely be ported to built-in: .enableAutoRange()
# but needs testing # but needs testing
chart.sigXRangeChanged.connect(update_yrange_limits) chart.sigXRangeChanged.connect(update_yrange_limits)
def _configure_ind_charts(
indicators: List[Tuple[ChartPlotWidget, np.ndarray]],
xlink_to_chart: ChartPlotWidget,
) -> None:
for ind_chart, d in indicators:
# default config
_configure_chart(ind_chart)
curve = pg.PlotDataItem(d, pen='b', antialias=True)
ind_chart.addItem(curve)
# XXX: never do this lol
# ind.setAspectLocked(1)
# link chart x-axis to main quotes chart
ind_chart.setXLink(xlink_to_chart)
class QuotesChart(QtGui.QWidget): class QuotesChart(QtGui.QWidget):
long_pen = pg.mkPen('#006000') long_pen = pg.mkPen('#006000')
@ -639,25 +676,6 @@ class QuotesChart(QtGui.QWidget):
del self.signals_group_text del self.signals_group_text
self.signals_visible = False self.signals_visible = False
def _update_ind_charts(self):
for ind, d in self.indicators:
curve = pg.PlotDataItem(d, pen='b', antialias=True)
ind.addItem(curve)
ind.hideAxis('left')
ind.showAxis('right')
# XXX: never do this lol
# ind.setAspectLocked(1)
ind.setXLink(self.chart)
ind.setLimits(
xMin=Quotes[0].id,
xMax=Quotes[-1].id,
minXRange=60,
yMin=Quotes.open.min() * 0.98,
yMax=Quotes.open.max() * 1.02,
)
ind.showGrid(x=True, y=True, alpha=0.4)
ind.setCursor(QtCore.Qt.CrossCursor)
def _update_sizes(self): def _update_sizes(self):
min_h_ind = int(self.height() * 0.3 / len(self.indicators)) min_h_ind = int(self.height() * 0.3 / len(self.indicators))
sizes = [int(self.height() * 0.7)] sizes = [int(self.height() * 0.7)]
@ -691,7 +709,7 @@ class QuotesChart(QtGui.QWidget):
def plot(self, symbol): def plot(self, symbol):
self.digits = symbol.digits self.digits = symbol.digits
self.chart = CustomPlotWidget( self.chart = ChartPlotWidget(
parent=self.splitter, parent=self.splitter,
axisItems={'bottom': self.xaxis, 'right': PriceAxis()}, axisItems={'bottom': self.xaxis, 'right': PriceAxis()},
viewBox=ScrollFromRightView, viewBox=ScrollFromRightView,
@ -704,7 +722,7 @@ class QuotesChart(QtGui.QWidget):
inds = [Quotes.open] inds = [Quotes.open]
for d in inds: for d in inds:
ind = CustomPlotWidget( ind = ChartPlotWidget(
parent=self.splitter, parent=self.splitter,
axisItems={'bottom': self.xaxis_ind, 'right': PriceAxis()}, axisItems={'bottom': self.xaxis_ind, 'right': PriceAxis()},
# axisItems={'top': self.xaxis_ind, 'right': PriceAxis()}, # axisItems={'top': self.xaxis_ind, 'right': PriceAxis()},
@ -720,7 +738,10 @@ class QuotesChart(QtGui.QWidget):
self.style, self.style,
self._update_yrange_limits self._update_yrange_limits
) )
self._update_ind_charts() _configure_ind_charts(
self.indicators,
xlink_to_chart=self.chart
)
self._update_sizes() self._update_sizes()
ch = CrossHairItem( ch = CrossHairItem(
@ -783,7 +804,7 @@ class QuotesChart(QtGui.QWidget):
self.signals_visible = True self.signals_visible = True
# this function is borderline rediculous. # this function is borderline ridiculous.
# The creation of these chart types mutates all the input data # The creation of these chart types mutates all the input data
# inside each type's constructor (mind blown) # inside each type's constructor (mind blown)
def _get_chart_points(style): def _get_chart_points(style):