Specialize `LevelLabel` for orientation-around-axis gymnastics

bar_select
Tyler Goodlet 2020-11-05 12:08:02 -05:00
parent 9c3850874d
commit db075b81ac
2 changed files with 96 additions and 37 deletions

View File

@ -113,7 +113,7 @@ class AxisLabel(pg.GraphicsObject):
digits: int = 2, digits: int = 2,
bg_color: str = 'bracket', bg_color: str = 'bracket',
fg_color: str = 'black', fg_color: str = 'black',
opacity: int = 1, opacity: int = 0,
font_size: Optional[int] = None, font_size: Optional[int] = None,
): ):
super().__init__(parent) super().__init__(parent)
@ -162,6 +162,7 @@ class AxisLabel(pg.GraphicsObject):
) -> None: ) -> None:
# this adds a nice black outline around the label for some odd # this adds a nice black outline around the label for some odd
# reason; ok by us # reason; ok by us
p.setOpacity(self.opacity)
p.drawRect(self.rect) p.drawRect(self.rect)
def boundingRect(self): # noqa def boundingRect(self): # noqa
@ -214,11 +215,11 @@ class XAxisLabel(AxisLabel):
def update_label( def update_label(
self, self,
abs_pos: QPointF, # scene coords abs_pos: QPointF, # scene coords
data: float, # data for text value: float, # data for text
offset: int = 1 # if have margins, k? offset: int = 1 # if have margins, k?
) -> None: ) -> None:
timestrs = self.parent._indexes_to_timestrs([int(data)]) timestrs = self.parent._indexes_to_timestrs([int(value)])
if not timestrs.any(): if not timestrs.any():
return return
@ -230,6 +231,7 @@ class XAxisLabel(AxisLabel):
abs_pos.x() - w / 2 - offset, abs_pos.x() - w / 2 - offset,
0, 0,
)) ))
self.update()
class YAxisLabel(AxisLabel): class YAxisLabel(AxisLabel):
@ -248,13 +250,13 @@ class YAxisLabel(AxisLabel):
def update_label( def update_label(
self, self,
abs_pos: QPointF, # scene coords abs_pos: QPointF, # scene coords
data: float, # data for text value: float, # data for text
offset: int = 1 # on odd dimension and/or adds nice black line offset: int = 1 # on odd dimension and/or adds nice black line
) -> None: ) -> None:
# this is read inside ``.paint()`` # this is read inside ``.paint()``
self.label_str = '{data: ,.{digits}f}'.format( self.label_str = '{value: ,.{digits}f}'.format(
digits=self.digits, data=data).replace(',', ' ') digits=self.digits, value=value).replace(',', ' ')
br = self.boundingRect() br = self.boundingRect()
h = br.height() h = br.height()
@ -262,6 +264,7 @@ class YAxisLabel(AxisLabel):
0, 0,
abs_pos.y() - h / 2 - offset abs_pos.y() - h / 2 - offset
)) ))
self.update()
class YSticky(YAxisLabel): class YSticky(YAxisLabel):
@ -271,31 +274,23 @@ class YSticky(YAxisLabel):
self, self,
chart, chart,
*args, *args,
orient_v: str = 'bottom',
orient_h: str = 'left',
**kwargs **kwargs
) -> None: ) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._orient_v = orient_v
self._orient_h = orient_h
self._chart = chart self._chart = chart
chart.sigRangeChanged.connect(self.update_on_resize) chart.sigRangeChanged.connect(self.update_on_resize)
self._last_datum = (None, None) self._last_datum = (None, None)
self._v_shift = {'top': 1., 'bottom': 0, 'middle': 1/2.}[orient_v]
self._h_shift = {'left': -1., 'right': 0}[orient_h]
def update_on_resize(self, vr, r): def update_on_resize(self, vr, r):
# TODO: add an `.index` to the array data-buffer layer # TODO: add an `.index` to the array data-buffer layer
# and make this way less shitty... # and make this way less shitty...
# pretty sure we did that ^ ?
index, last = self._last_datum index, last = self._last_datum
if index is not None: if index is not None:
self.update_from_data( self.update_from_data(index, last)
index,
last,
)
def update_from_data( def update_from_data(
self, self,

View File

@ -15,6 +15,9 @@ from ._style import _xaxis_at, hcolor, _font
from ._axes import YAxisLabel, XAxisLabel, YSticky from ._axes import YAxisLabel, XAxisLabel, YSticky
# XXX: these settings seem to result in really decent mouse scroll
# latency (in terms of perceived lag in cross hair) so really be sure
# there's an improvement if you want to change it.
_mouse_rate_limit = 60 # calc current screen refresh rate? _mouse_rate_limit = 60 # calc current screen refresh rate?
_debounce_delay = 1 / 2e3 _debounce_delay = 1 / 2e3
_ch_label_opac = 1 _ch_label_opac = 1
@ -266,7 +269,7 @@ class CrossHair(pg.GraphicsObject):
self.graphics[plot]['hl'].setY(y) self.graphics[plot]['hl'].setY(y)
self.graphics[self.active_plot]['yl'].update_label( self.graphics[self.active_plot]['yl'].update_label(
abs_pos=pos, data=y abs_pos=pos, value=y
) )
# Update x if cursor changed after discretization calc # Update x if cursor changed after discretization calc
@ -296,7 +299,7 @@ class CrossHair(pg.GraphicsObject):
# map back to abs (label-local) coordinates # map back to abs (label-local) coordinates
abs_pos=plot.mapFromView(QPointF(ix, y)), abs_pos=plot.mapFromView(QPointF(ix, y)),
data=x, value=x,
) )
self._lastx = ix self._lastx = ix
@ -553,7 +556,7 @@ class BarItems(pg.GraphicsObject):
# writer is responsible for changing open on "first" volume of bar # writer is responsible for changing open on "first" volume of bar
larm.setLine(larm.x1(), o, larm.x2(), o) larm.setLine(larm.x1(), o, larm.x2(), o)
if l != h: if l != h: # noqa
if body is None: if body is None:
body = self.lines[index - 1][0] = QLineF(i, l, i, h) body = self.lines[index - 1][0] = QLineF(i, l, i, h)
else: else:
@ -649,29 +652,61 @@ class LevelLabel(YSticky):
_w_margin = 4 _w_margin = 4
_h_margin = 3 _h_margin = 3
level: float = 0
def __init__(
self,
chart,
*args,
orient_v: str = 'bottom',
orient_h: str = 'left',
**kwargs
) -> None:
super().__init__(chart, *args, **kwargs)
# orientation around axis options
self._orient_v = orient_v
self._orient_h = orient_h
self._v_shift = {
'top': 1.,
'bottom': 0,
'middle': 1 / 2.
}[orient_v]
self._h_shift = {
'left': -1., 'right': 0
}[orient_h]
def update_label( def update_label(
self, self,
abs_pos: QPointF, # scene coords abs_pos: QPointF, # scene coords
data: float, # data for text level: float, # data for text
offset: int = 1 # if have margins, k? offset: int = 1 # if have margins, k?
) -> None: ) -> None:
# this is read inside ``.paint()`` # write contents, type specific
self.label_str = '{data: ,.{digits}f}'.format( self.set_label_str(level)
digits=self.digits,
data=data
).replace(',', ' ')
self._size_br_from_str(self.label_str)
br = self.boundingRect() br = self.boundingRect()
h, w = br.height(), br.width() h, w = br.height(), br.width()
# this triggers ``.pain()`` implicitly?
self.setPos(QPointF( self.setPos(QPointF(
self._h_shift * w - offset, self._h_shift * w - offset,
abs_pos.y() - (self._v_shift * h) - offset abs_pos.y() - (self._v_shift * h) - offset
)) ))
self.update()
self.level = level
def set_label_str(self, level: float):
# this is read inside ``.paint()``
# self.label_str = '{size} x {level:.{digits}f}'.format(
self.label_str = '{level:.{digits}f}'.format(
# size=self._size,
digits=self.digits,
level=level
).replace(',', ' ')
def size_hint(self) -> Tuple[None, None]: def size_hint(self) -> Tuple[None, None]:
return None, None return None, None
@ -687,19 +722,43 @@ class LevelLabel(YSticky):
p.drawLine(rect.bottomLeft(), rect.bottomRight()) p.drawLine(rect.bottomLeft(), rect.bottomRight())
class L1Label(LevelLabel):
size: float = 0
text_flags = (
QtCore.Qt.TextDontClip
| QtCore.Qt.AlignLeft
)
def set_label_str(self, level: float) -> None:
"""Reimplement the label string write to include the level's order-queue's
size in the text, eg. 100 x 323.3.
"""
self.label_str = '{size} x {level:,.{digits}f}'.format(
size=self.size or '?',
digits=self.digits,
level=level
).replace(',', ' ')
class L1Labels: class L1Labels:
"""Level 1 bid ask labels for dynamic update on price-axis. """Level 1 bid ask labels for dynamic update on price-axis.
""" """
max_value: float = '100 x 100 000'
def __init__( def __init__(
self, self,
chart: 'ChartPlotWidget', # noqa chart: 'ChartPlotWidget', # noqa
# level: float,
digits: int = 2, digits: int = 2,
font_size: int = 4, font_size: int = 4,
) -> None: ) -> None:
self.chart = chart self.chart = chart
self.bid_label = LevelLabel( self.bid_label = L1Label(
chart=chart, chart=chart,
parent=chart.getAxis('right'), parent=chart.getAxis('right'),
# TODO: pass this from symbol data # TODO: pass this from symbol data
@ -710,8 +769,9 @@ class L1Labels:
fg_color='bracket', fg_color='bracket',
orient_v='bottom', orient_v='bottom',
) )
self.bid_label._size_br_from_str(self.max_value)
self.ask_label = LevelLabel( self.ask_label = L1Label(
chart=chart, chart=chart,
parent=chart.getAxis('right'), parent=chart.getAxis('right'),
# TODO: pass this from symbol data # TODO: pass this from symbol data
@ -722,6 +782,7 @@ class L1Labels:
fg_color='bracket', fg_color='bracket',
orient_v='top', orient_v='top',
) )
self.ask_label._size_br_from_str(self.max_value)
class LevelLine(pg.InfiniteLine): class LevelLine(pg.InfiniteLine):
@ -732,9 +793,9 @@ class LevelLine(pg.InfiniteLine):
) -> None: ) -> None:
self.label = label self.label = label
super().__init__(**kwargs) super().__init__(**kwargs)
self.sigPositionChanged.connect(self.set_value) self.sigPositionChanged.connect(self.set_level)
def set_value(self, value: float) -> None: def set_level(self, value: float) -> None:
self.label.update_from_data(0, self.value()) self.label.update_from_data(0, self.value())
@ -755,11 +816,14 @@ def level_line(
digits=digits, digits=digits,
opacity=1, opacity=1,
font_size=font_size, font_size=font_size,
# TODO: make this take the view's bg pen
bg_color='papas_special', bg_color='papas_special',
fg_color='default', fg_color='default',
**linelabelkwargs **linelabelkwargs
) )
label.update_from_data(0, level) label.update_from_data(0, level)
# TODO: can we somehow figure out a max value from the parent axis?
label._size_br_from_str(label.label_str)
line = LevelLine( line = LevelLine(
label, label,