Factor common chart configuration
parent
6bac250db5
commit
856c6f2fd8
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Real-time quotes charting components
|
||||
"""
|
||||
from typing import Callable
|
||||
from typing import Callable, List, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
|
@ -279,7 +279,9 @@ class ScrollFromRightView(pg.ViewBox):
|
|||
# )
|
||||
furthest_right_coord = self.boundingRect().topRight()
|
||||
center = pg.Point(
|
||||
fn.invertQTransform(self.childGroup.transform()).map(furthest_right_coord)
|
||||
fn.invertQTransform(
|
||||
self.childGroup.transform()
|
||||
).map(furthest_right_coord)
|
||||
)
|
||||
|
||||
self._resetTarget()
|
||||
|
@ -288,13 +290,15 @@ class ScrollFromRightView(pg.ViewBox):
|
|||
self.sigRangeChangedManually.emit(mask)
|
||||
|
||||
|
||||
# TODO: convert this to a ``ViewBox`` type giving us
|
||||
# control over mouse scrolling and a context menu
|
||||
# This is a sub-class of ``GracphicView`` which can
|
||||
# TODO: This is a sub-class of ``GracphicView`` which can
|
||||
# take a ``background`` color setting.
|
||||
class CustomPlotWidget(pg.PlotWidget):
|
||||
class ChartPlotWidget(pg.PlotWidget):
|
||||
"""``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
|
||||
eventually want multiple plots managed together).
|
||||
"""
|
||||
|
@ -360,6 +364,7 @@ class CrossHairItem(pg.GraphicsObject):
|
|||
slot=lambda: self.mouseAction('Leave', False),
|
||||
)
|
||||
|
||||
# determine where to place x-axis label
|
||||
if _xaxis_at == 'bottom':
|
||||
# place below is last indicator subplot
|
||||
self.xaxis_label = XAxisLabel(
|
||||
|
@ -407,7 +412,8 @@ class CrossHairItem(pg.GraphicsObject):
|
|||
else:
|
||||
self.yaxis_label.show()
|
||||
self.hline.show()
|
||||
else: # Leave
|
||||
# Leave
|
||||
else:
|
||||
# hide horiz line and y-label
|
||||
if ind:
|
||||
self.indicators[ind]['hl'].hide()
|
||||
|
@ -424,7 +430,7 @@ class CrossHairItem(pg.GraphicsObject):
|
|||
|
||||
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):
|
||||
# mouse_point = self.vb.mapSceneToView(pos)
|
||||
mouse_point = self.parent.mapToView(pos)
|
||||
|
@ -551,22 +557,16 @@ class CandlestickItem(BarItem):
|
|||
p.drawRects(*rects[Quotes.close < Quotes.open])
|
||||
|
||||
|
||||
def _configure_quotes_chart(
|
||||
chart: CustomPlotWidget,
|
||||
style: ChartType,
|
||||
update_yrange_limits: Callable,
|
||||
def _configure_chart(
|
||||
chart: ChartPlotWidget,
|
||||
) -> None:
|
||||
"""Update and format a chart with quotes data.
|
||||
"""Configure a chart with common settings.
|
||||
"""
|
||||
|
||||
# show only right side axes
|
||||
chart.hideAxis('left')
|
||||
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
|
||||
chart.setLimits(
|
||||
xMin=Quotes[0].id,
|
||||
|
@ -575,16 +575,53 @@ def _configure_quotes_chart(
|
|||
yMin=Quotes.low.min() * 0.98,
|
||||
yMax=Quotes.high.max() * 1.02,
|
||||
)
|
||||
# show background grid
|
||||
chart.showGrid(x=True, y=True, alpha=0.4)
|
||||
|
||||
# use cross-hair for cursor
|
||||
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
|
||||
# based on y-range contents
|
||||
|
||||
# TODO: this can likely be ported to built-in: .enableAutoRange()
|
||||
# but needs testing
|
||||
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):
|
||||
|
||||
long_pen = pg.mkPen('#006000')
|
||||
|
@ -639,25 +676,6 @@ class QuotesChart(QtGui.QWidget):
|
|||
del self.signals_group_text
|
||||
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):
|
||||
min_h_ind = int(self.height() * 0.3 / len(self.indicators))
|
||||
sizes = [int(self.height() * 0.7)]
|
||||
|
@ -691,7 +709,7 @@ class QuotesChart(QtGui.QWidget):
|
|||
|
||||
def plot(self, symbol):
|
||||
self.digits = symbol.digits
|
||||
self.chart = CustomPlotWidget(
|
||||
self.chart = ChartPlotWidget(
|
||||
parent=self.splitter,
|
||||
axisItems={'bottom': self.xaxis, 'right': PriceAxis()},
|
||||
viewBox=ScrollFromRightView,
|
||||
|
@ -704,7 +722,7 @@ class QuotesChart(QtGui.QWidget):
|
|||
inds = [Quotes.open]
|
||||
|
||||
for d in inds:
|
||||
ind = CustomPlotWidget(
|
||||
ind = ChartPlotWidget(
|
||||
parent=self.splitter,
|
||||
axisItems={'bottom': self.xaxis_ind, 'right': PriceAxis()},
|
||||
# axisItems={'top': self.xaxis_ind, 'right': PriceAxis()},
|
||||
|
@ -720,7 +738,10 @@ class QuotesChart(QtGui.QWidget):
|
|||
self.style,
|
||||
self._update_yrange_limits
|
||||
)
|
||||
self._update_ind_charts()
|
||||
_configure_ind_charts(
|
||||
self.indicators,
|
||||
xlink_to_chart=self.chart
|
||||
)
|
||||
self._update_sizes()
|
||||
|
||||
ch = CrossHairItem(
|
||||
|
@ -783,7 +804,7 @@ class QuotesChart(QtGui.QWidget):
|
|||
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
|
||||
# inside each type's constructor (mind blown)
|
||||
def _get_chart_points(style):
|
||||
|
|
Loading…
Reference in New Issue