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

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

View File

@ -15,8 +15,11 @@ from ._style import _xaxis_at, hcolor, _font
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?
_debounce_delay = 1/2e3
_debounce_delay = 1 / 2e3
_ch_label_opac = 1
@ -45,7 +48,7 @@ class LineDot(pg.CurvePoint):
# presuming this is fast since it's built in?
dot = self.dot = QtGui.QGraphicsEllipseItem(
QtCore.QRectF(-size/2, -size/2, size, size)
QtCore.QRectF(-size / 2, -size / 2, size, size)
)
# if we needed transformable dot?
# dot.translate(-size*0.5, -size*0.5)
@ -266,7 +269,7 @@ class CrossHair(pg.GraphicsObject):
self.graphics[plot]['hl'].setY(y)
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
@ -296,7 +299,7 @@ class CrossHair(pg.GraphicsObject):
# map back to abs (label-local) coordinates
abs_pos=plot.mapFromView(QPointF(ix, y)),
data=x,
value=x,
)
self._lastx = ix
@ -553,16 +556,16 @@ class BarItems(pg.GraphicsObject):
# writer is responsible for changing open on "first" volume of bar
larm.setLine(larm.x1(), o, larm.x2(), o)
if l != h:
if l != h: # noqa
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:
# update body
body.setLine(i, l, i, h)
else:
# XXX: h == l -> remove any HL line to avoid render bug
if body is not None:
body = self.lines[index-1][0] = None
body = self.lines[index - 1][0] = None
self.draw_lines(just_history=False)
@ -649,29 +652,61 @@ class LevelLabel(YSticky):
_w_margin = 4
_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(
self,
abs_pos: QPointF, # scene coords
data: float, # data for text
level: float, # data for text
offset: int = 1 # if have margins, k?
) -> None:
# this is read inside ``.paint()``
self.label_str = '{data: ,.{digits}f}'.format(
digits=self.digits,
data=data
).replace(',', ' ')
self._size_br_from_str(self.label_str)
# write contents, type specific
self.set_label_str(level)
br = self.boundingRect()
h, w = br.height(), br.width()
# this triggers ``.pain()`` implicitly?
self.setPos(QPointF(
self._h_shift * w - 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]:
return None, None
@ -687,19 +722,43 @@ class LevelLabel(YSticky):
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:
"""Level 1 bid ask labels for dynamic update on price-axis.
"""
max_value: float = '100 x 100 000'
def __init__(
self,
chart: 'ChartPlotWidget', # noqa
# level: float,
digits: int = 2,
font_size: int = 4,
) -> None:
self.chart = chart
self.bid_label = LevelLabel(
self.bid_label = L1Label(
chart=chart,
parent=chart.getAxis('right'),
# TODO: pass this from symbol data
@ -710,8 +769,9 @@ class L1Labels:
fg_color='bracket',
orient_v='bottom',
)
self.bid_label._size_br_from_str(self.max_value)
self.ask_label = LevelLabel(
self.ask_label = L1Label(
chart=chart,
parent=chart.getAxis('right'),
# TODO: pass this from symbol data
@ -722,6 +782,7 @@ class L1Labels:
fg_color='bracket',
orient_v='top',
)
self.ask_label._size_br_from_str(self.max_value)
class LevelLine(pg.InfiniteLine):
@ -732,9 +793,9 @@ class LevelLine(pg.InfiniteLine):
) -> None:
self.label = label
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())
@ -755,11 +816,14 @@ def level_line(
digits=digits,
opacity=1,
font_size=font_size,
# TODO: make this take the view's bg pen
bg_color='papas_special',
fg_color='default',
**linelabelkwargs
)
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(
label,