Specialize `LevelLabel` for orientation-around-axis gymnastics
parent
9c3850874d
commit
db075b81ac
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue