Massively simplify the cross-hair monstrosity
Stop with all this "main chart" special treatment. Manage all lines in the same way across all referenced plots. Add `CrossHair.add_plot()` for adding new plots dynamically. Just, smh.bar_select
parent
c56aee6347
commit
b4f1ec7960
|
@ -23,152 +23,116 @@ from ._axes import YAxisLabel, XAxisLabel
|
||||||
_mouse_rate_limit = 50
|
_mouse_rate_limit = 50
|
||||||
|
|
||||||
|
|
||||||
class CrossHairItem(pg.GraphicsObject):
|
class CrossHair(pg.GraphicsObject):
|
||||||
|
|
||||||
def __init__(self, parent, indicators=None, digits=0):
|
def __init__(self, parent, digits: int = 0):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
# self.pen = pg.mkPen('#000000')
|
# self.pen = pg.mkPen('#000000')
|
||||||
self.pen = pg.mkPen('#a9a9a9')
|
self.pen = pg.mkPen('#a9a9a9')
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.indicators = {}
|
self.graphics = {}
|
||||||
self.activeIndicator = None
|
self.plots = []
|
||||||
self.xaxis = self.parent.getAxis('bottom')
|
self.active_plot = None
|
||||||
self.yaxis = self.parent.getAxis('right')
|
self.digits = digits
|
||||||
|
|
||||||
self.vline = self.parent.addLine(x=0, pen=self.pen, movable=False)
|
def add_plot(
|
||||||
self.hline = self.parent.addLine(y=0, pen=self.pen, movable=False)
|
self,
|
||||||
|
plot: 'ChartPlotWidget', # noqa
|
||||||
|
digits: int = 0,
|
||||||
|
) -> None:
|
||||||
|
# add ``pg.graphicsItems.InfiniteLine``s
|
||||||
|
# vertical and horizonal lines and a y-axis label
|
||||||
|
vl = plot.addLine(x=0, pen=self.pen, movable=False)
|
||||||
|
hl = plot.addLine(y=0, pen=self.pen, movable=False)
|
||||||
|
yl = YAxisLabel(
|
||||||
|
parent=plot.getAxis('right'),
|
||||||
|
digits=digits or self.digits,
|
||||||
|
opacity=1
|
||||||
|
)
|
||||||
|
|
||||||
self.proxy_moved = pg.SignalProxy(
|
# TODO: checkout what ``.sigDelayed`` can be used for
|
||||||
self.parent.scene().sigMouseMoved,
|
# (emitted once a sufficient delay occurs in mouse movement)
|
||||||
|
px_moved = pg.SignalProxy(
|
||||||
|
plot.scene().sigMouseMoved,
|
||||||
rateLimit=_mouse_rate_limit,
|
rateLimit=_mouse_rate_limit,
|
||||||
slot=self.mouseMoved,
|
slot=self.mouseMoved
|
||||||
)
|
)
|
||||||
|
px_enter = pg.SignalProxy(
|
||||||
self.yaxis_label = YAxisLabel(
|
plot.sig_mouse_enter,
|
||||||
parent=self.yaxis, digits=digits, opacity=1
|
rateLimit=_mouse_rate_limit,
|
||||||
|
slot=lambda: self.mouseAction('Enter', plot),
|
||||||
)
|
)
|
||||||
|
px_leave = pg.SignalProxy(
|
||||||
indicators = indicators or []
|
plot.sig_mouse_leave,
|
||||||
|
rateLimit=_mouse_rate_limit,
|
||||||
if indicators:
|
slot=lambda: self.mouseAction('Leave', plot),
|
||||||
# when there are indicators present in sub-plot rows
|
)
|
||||||
# take the last one (nearest to the bottom) and place the
|
self.graphics[plot] = {
|
||||||
# crosshair label on it's x-axis.
|
'vl': vl,
|
||||||
last_ind = indicators[-1]
|
'hl': hl,
|
||||||
|
'yl': yl,
|
||||||
self.proxy_enter = pg.SignalProxy(
|
'px': (px_moved, px_enter, px_leave),
|
||||||
self.parent.sig_mouse_enter,
|
}
|
||||||
rateLimit=_mouse_rate_limit,
|
self.plots.append(plot)
|
||||||
slot=lambda: self.mouseAction('Enter', False),
|
|
||||||
)
|
|
||||||
self.proxy_leave = pg.SignalProxy(
|
|
||||||
self.parent.sig_mouse_leave,
|
|
||||||
rateLimit=_mouse_rate_limit,
|
|
||||||
slot=lambda: self.mouseAction('Leave', False),
|
|
||||||
)
|
|
||||||
|
|
||||||
# determine where to place x-axis label
|
# determine where to place x-axis label
|
||||||
if _xaxis_at == 'bottom':
|
if _xaxis_at == 'bottom':
|
||||||
# place below is last indicator subplot
|
# place below the last plot
|
||||||
self.xaxis_label = XAxisLabel(
|
self.xaxis_label = XAxisLabel(
|
||||||
parent=last_ind.getAxis('bottom'), opacity=1
|
parent=self.plots[-1].getAxis('bottom'),
|
||||||
|
opacity=1
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# keep x-axis right below main chart
|
# keep x-axis right below main chart
|
||||||
self.xaxis_label = XAxisLabel(parent=self.xaxis, opacity=1)
|
first = self.plots[0]
|
||||||
|
xaxis = first.getAxis('bottom')
|
||||||
|
self.xaxis_label = XAxisLabel(parent=xaxis, opacity=1)
|
||||||
|
|
||||||
for i in indicators:
|
def mouseAction(self, action, plot): # noqa
|
||||||
# add vertial and horizonal lines and a y-axis label
|
# TODO: why do we no handle all plots the same?
|
||||||
vl = i.addLine(x=0, pen=self.pen, movable=False)
|
# -> main plot has special path? would simplify code.
|
||||||
hl = i.addLine(y=0, pen=self.pen, movable=False)
|
|
||||||
yl = YAxisLabel(parent=i.getAxis('right'), opacity=1)
|
|
||||||
|
|
||||||
px_moved = pg.SignalProxy(
|
|
||||||
i.scene().sigMouseMoved,
|
|
||||||
rateLimit=_mouse_rate_limit,
|
|
||||||
slot=self.mouseMoved
|
|
||||||
)
|
|
||||||
px_enter = pg.SignalProxy(
|
|
||||||
i.sig_mouse_enter,
|
|
||||||
rateLimit=_mouse_rate_limit,
|
|
||||||
slot=lambda: self.mouseAction('Enter', i),
|
|
||||||
)
|
|
||||||
px_leave = pg.SignalProxy(
|
|
||||||
i.sig_mouse_leave,
|
|
||||||
rateLimit=_mouse_rate_limit,
|
|
||||||
slot=lambda: self.mouseAction('Leave', i),
|
|
||||||
)
|
|
||||||
self.indicators[i] = {
|
|
||||||
'vl': vl,
|
|
||||||
'hl': hl,
|
|
||||||
'yl': yl,
|
|
||||||
'px': (px_moved, px_enter, px_leave),
|
|
||||||
}
|
|
||||||
|
|
||||||
def mouseAction(self, action, ind=False): # noqa
|
|
||||||
if action == 'Enter':
|
if action == 'Enter':
|
||||||
# show horiz line and y-label
|
# show horiz line and y-label
|
||||||
if ind:
|
self.graphics[plot]['hl'].show()
|
||||||
self.indicators[ind]['hl'].show()
|
self.graphics[plot]['yl'].show()
|
||||||
self.indicators[ind]['yl'].show()
|
self.active_plot = plot
|
||||||
self.activeIndicator = ind
|
else: # Leave
|
||||||
else:
|
|
||||||
self.yaxis_label.show()
|
|
||||||
self.hline.show()
|
|
||||||
# Leave
|
|
||||||
else:
|
|
||||||
# hide horiz line and y-label
|
# hide horiz line and y-label
|
||||||
if ind:
|
self.graphics[plot]['hl'].hide()
|
||||||
self.indicators[ind]['hl'].hide()
|
self.graphics[plot]['yl'].hide()
|
||||||
self.indicators[ind]['yl'].hide()
|
self.active_plot = None
|
||||||
self.activeIndicator = None
|
|
||||||
else:
|
|
||||||
self.yaxis_label.hide()
|
|
||||||
self.hline.hide()
|
|
||||||
|
|
||||||
def mouseMoved(self, evt): # noqa
|
def mouseMoved(self, evt): # noqa
|
||||||
"""Update horizonal and vertical lines when mouse moves inside
|
"""Update horizonal and vertical lines when mouse moves inside
|
||||||
either the main chart or any indicator subplot.
|
either the main chart or any indicator subplot.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pos = evt[0]
|
pos = evt[0]
|
||||||
|
|
||||||
# if the mouse is within the parent ``ChartPlotWidget``
|
# find position in main chart
|
||||||
if self.parent.sceneBoundingRect().contains(pos):
|
mouse_point = self.plots[0].mapToView(pos)
|
||||||
# mouse_point = self.vb.mapSceneToView(pos)
|
|
||||||
mouse_point = self.parent.mapToView(pos)
|
|
||||||
|
|
||||||
# move the vertial line to the current x coordinate
|
# move the vertical line to the current x coordinate in all charts
|
||||||
self.vline.setX(mouse_point.x())
|
for opts in self.graphics.values():
|
||||||
|
opts['vl'].setX(mouse_point.x())
|
||||||
|
|
||||||
# update the label on the bottom of the crosshair
|
# update the label on the bottom of the crosshair
|
||||||
self.xaxis_label.update_label(evt_post=pos, point_view=mouse_point)
|
self.xaxis_label.update_label(evt_post=pos, point_view=mouse_point)
|
||||||
|
|
||||||
# update the vertical line in any indicators subplots
|
# vertical position of the mouse is inside an indicator
|
||||||
for opts in self.indicators.values():
|
mouse_point_ind = self.active_plot.mapToView(pos)
|
||||||
opts['vl'].setX(mouse_point.x())
|
|
||||||
|
|
||||||
if self.activeIndicator:
|
self.graphics[self.active_plot]['hl'].setY(
|
||||||
# vertial position of the mouse is inside an indicator
|
mouse_point_ind.y()
|
||||||
mouse_point_ind = self.activeIndicator.mapToView(pos)
|
)
|
||||||
self.indicators[self.activeIndicator]['hl'].setY(
|
self.graphics[self.active_plot]['yl'].update_label(
|
||||||
mouse_point_ind.y()
|
evt_post=pos, point_view=mouse_point_ind
|
||||||
)
|
)
|
||||||
self.indicators[self.activeIndicator]['yl'].update_label(
|
|
||||||
evt_post=pos, point_view=mouse_point_ind
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# vertial position of the mouse is inside the main chart
|
|
||||||
self.hline.setY(mouse_point.y())
|
|
||||||
self.yaxis_label.update_label(
|
|
||||||
evt_post=pos, point_view=mouse_point
|
|
||||||
)
|
|
||||||
|
|
||||||
def paint(self, p, *args):
|
# def paint(self, p, *args):
|
||||||
pass
|
# pass
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return self.parent.boundingRect()
|
return self.plots[0].boundingRect()
|
||||||
|
|
||||||
|
|
||||||
def _mk_lines_array(data: List, size: int) -> np.ndarray:
|
def _mk_lines_array(data: List, size: int) -> np.ndarray:
|
||||||
|
@ -198,7 +162,8 @@ def bars_from_ohlc(
|
||||||
lines = _mk_lines_array(data, data.shape[0])
|
lines = _mk_lines_array(data, data.shape[0])
|
||||||
|
|
||||||
for i, q in enumerate(data[start:], start=start):
|
for i, q in enumerate(data[start:], start=start):
|
||||||
open, high, low, close, index = q[['open', 'high', 'low', 'close', 'index']]
|
open, high, low, close, index = q[
|
||||||
|
['open', 'high', 'low', 'close', 'index']]
|
||||||
|
|
||||||
# high - low line
|
# high - low line
|
||||||
if low != high:
|
if low != high:
|
||||||
|
|
Loading…
Reference in New Issue