Remove `LevelLine.add_label()`, add dynamic pp marker label
parent
62517c1662
commit
791fd23524
|
@ -36,12 +36,6 @@ from ._style import hcolor, _font
|
||||||
# https://stackoverflow.com/questions/26156486/determine-bounding-rect-of-line-in-qt
|
# https://stackoverflow.com/questions/26156486/determine-bounding-rect-of-line-in-qt
|
||||||
class LevelLine(pg.InfiniteLine):
|
class LevelLine(pg.InfiniteLine):
|
||||||
|
|
||||||
# TODO: fill in these slots for orders
|
|
||||||
# available parent signals
|
|
||||||
# sigDragged(self)
|
|
||||||
# sigPositionChangeFinished(self)
|
|
||||||
# sigPositionChanged(self)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
chart: 'ChartPlotWidget', # type: ignore # noqa
|
chart: 'ChartPlotWidget', # type: ignore # noqa
|
||||||
|
@ -63,6 +57,9 @@ class LevelLine(pg.InfiniteLine):
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
|
# TODO: at this point it's probably not worth the inheritance
|
||||||
|
# any more since we've reimplemented ``.pain()`` among other
|
||||||
|
# things..
|
||||||
super().__init__(
|
super().__init__(
|
||||||
movable=movable,
|
movable=movable,
|
||||||
angle=0,
|
angle=0,
|
||||||
|
@ -97,7 +94,7 @@ class LevelLine(pg.InfiniteLine):
|
||||||
|
|
||||||
# list of labels anchored at one of the 2 line endpoints
|
# list of labels anchored at one of the 2 line endpoints
|
||||||
# inside the viewbox
|
# inside the viewbox
|
||||||
self._labels: List[(int, Label)] = []
|
self._labels: List[Label] = []
|
||||||
self._markers: List[(int, Label)] = []
|
self._markers: List[(int, Label)] = []
|
||||||
|
|
||||||
# whenever this line is moved trigger label updates
|
# whenever this line is moved trigger label updates
|
||||||
|
@ -143,52 +140,6 @@ class LevelLine(pg.InfiniteLine):
|
||||||
hoverpen.setWidth(2)
|
hoverpen.setWidth(2)
|
||||||
self.hoverPen = hoverpen
|
self.hoverPen = hoverpen
|
||||||
|
|
||||||
def add_label(
|
|
||||||
self,
|
|
||||||
|
|
||||||
# by default we only display the line's level value
|
|
||||||
# in the label
|
|
||||||
fmt_str: str = (
|
|
||||||
'{level:,.{level_digits}f}'
|
|
||||||
),
|
|
||||||
side: str = 'right',
|
|
||||||
side_of_axis: str = 'left',
|
|
||||||
x_offset: float = 0,
|
|
||||||
|
|
||||||
color: str = None,
|
|
||||||
bg_color: str = None,
|
|
||||||
avoid_book: bool = True,
|
|
||||||
|
|
||||||
**label_kwargs,
|
|
||||||
) -> Label:
|
|
||||||
"""Add a ``LevelLabel`` anchored at one of the line endpoints in view.
|
|
||||||
|
|
||||||
"""
|
|
||||||
label = Label(
|
|
||||||
view=self.getViewBox(),
|
|
||||||
fmt_str=fmt_str,
|
|
||||||
color=self.color,
|
|
||||||
)
|
|
||||||
|
|
||||||
# set anchor callback
|
|
||||||
if side == 'right':
|
|
||||||
label.set_x_anchor_func(
|
|
||||||
right_axis(
|
|
||||||
self._chart,
|
|
||||||
label,
|
|
||||||
side=side_of_axis,
|
|
||||||
offset=x_offset,
|
|
||||||
avoid_book=avoid_book,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif side == 'left':
|
|
||||||
label.set_x_anchor_func(vbr_left(label))
|
|
||||||
|
|
||||||
self._labels.append((side, label))
|
|
||||||
|
|
||||||
return label
|
|
||||||
|
|
||||||
def on_pos_change(
|
def on_pos_change(
|
||||||
self,
|
self,
|
||||||
line: 'LevelLine', # noqa
|
line: 'LevelLine', # noqa
|
||||||
|
@ -201,9 +152,11 @@ class LevelLine(pg.InfiniteLine):
|
||||||
def update_labels(
|
def update_labels(
|
||||||
self,
|
self,
|
||||||
fields_data: dict,
|
fields_data: dict,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
for at, label in self._labels:
|
for label in self._labels:
|
||||||
|
|
||||||
label.color = self.color
|
label.color = self.color
|
||||||
# print(f'color is {self.color}')
|
# print(f'color is {self.color}')
|
||||||
|
|
||||||
|
@ -211,18 +164,18 @@ class LevelLine(pg.InfiniteLine):
|
||||||
|
|
||||||
level = fields_data.get('level')
|
level = fields_data.get('level')
|
||||||
if level:
|
if level:
|
||||||
label.set_view_y(level)
|
label.set_view_pos(y=level)
|
||||||
|
|
||||||
label.render()
|
label.render()
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def hide_labels(self) -> None:
|
def hide_labels(self) -> None:
|
||||||
for at, label in self._labels:
|
for label in self._labels:
|
||||||
label.hide()
|
label.hide()
|
||||||
|
|
||||||
def show_labels(self) -> None:
|
def show_labels(self) -> None:
|
||||||
for at, label in self._labels:
|
for label in self._labels:
|
||||||
label.show()
|
label.show()
|
||||||
|
|
||||||
def set_level(
|
def set_level(
|
||||||
|
@ -316,9 +269,10 @@ class LevelLine(pg.InfiniteLine):
|
||||||
"""
|
"""
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
if scene:
|
if scene:
|
||||||
for at, label in self._labels:
|
for label in self._labels:
|
||||||
label.delete()
|
label.delete()
|
||||||
|
|
||||||
|
# gc managed labels?
|
||||||
self._labels.clear()
|
self._labels.clear()
|
||||||
|
|
||||||
if self._marker:
|
if self._marker:
|
||||||
|
@ -406,6 +360,7 @@ class LevelLine(pg.InfiniteLine):
|
||||||
self._marker.setPos(
|
self._marker.setPos(
|
||||||
QPointF(marker_right, self.scene_y())
|
QPointF(marker_right, self.scene_y())
|
||||||
)
|
)
|
||||||
|
self._marker.label.update()
|
||||||
|
|
||||||
elif not self.use_marker_margin:
|
elif not self.use_marker_margin:
|
||||||
# basically means **don't** shorten the line with normally
|
# basically means **don't** shorten the line with normally
|
||||||
|
@ -439,7 +394,8 @@ class LevelLine(pg.InfiniteLine):
|
||||||
def add_marker(
|
def add_marker(
|
||||||
self,
|
self,
|
||||||
path: QtWidgets.QGraphicsPathItem,
|
path: QtWidgets.QGraphicsPathItem,
|
||||||
) -> None:
|
|
||||||
|
) -> QtWidgets.QGraphicsPathItem:
|
||||||
|
|
||||||
# add path to scene
|
# add path to scene
|
||||||
self.getViewBox().scene().addItem(path)
|
self.getViewBox().scene().addItem(path)
|
||||||
|
@ -453,6 +409,7 @@ class LevelLine(pg.InfiniteLine):
|
||||||
# y_in_sc = chart._vb.mapFromView(Point(0, self.value())).y()
|
# y_in_sc = chart._vb.mapFromView(Point(0, self.value())).y()
|
||||||
path.setPos(QPointF(rsc, self.scene_y()))
|
path.setPos(QPointF(rsc, self.scene_y()))
|
||||||
|
|
||||||
|
return path
|
||||||
# self.update()
|
# self.update()
|
||||||
|
|
||||||
def hoverEvent(self, ev):
|
def hoverEvent(self, ev):
|
||||||
|
@ -471,6 +428,9 @@ class LevelLine(pg.InfiniteLine):
|
||||||
if self._moh:
|
if self._moh:
|
||||||
self.show_markers = True
|
self.show_markers = True
|
||||||
|
|
||||||
|
if self._marker:
|
||||||
|
self._marker.show()
|
||||||
|
|
||||||
# highlight if so configured
|
# highlight if so configured
|
||||||
if self._hoh:
|
if self._hoh:
|
||||||
|
|
||||||
|
@ -514,11 +474,14 @@ class LevelLine(pg.InfiniteLine):
|
||||||
if self._moh:
|
if self._moh:
|
||||||
self.show_markers = False
|
self.show_markers = False
|
||||||
|
|
||||||
|
if self._marker:
|
||||||
|
self._marker.hide()
|
||||||
|
|
||||||
if self not in cur._trackers:
|
if self not in cur._trackers:
|
||||||
cur.show_xhair(y_label_level=self.value())
|
cur.show_xhair(y_label_level=self.value())
|
||||||
|
|
||||||
if not self._always_show_labels:
|
if not self._always_show_labels:
|
||||||
for at, label in self._labels:
|
for label in self._labels:
|
||||||
label.hide()
|
label.hide()
|
||||||
label.txt.update()
|
label.txt.update()
|
||||||
# label.unhighlight()
|
# label.unhighlight()
|
||||||
|
@ -531,24 +494,19 @@ class LevelLine(pg.InfiniteLine):
|
||||||
def level_line(
|
def level_line(
|
||||||
chart: 'ChartPlotWidget', # noqa
|
chart: 'ChartPlotWidget', # noqa
|
||||||
level: float,
|
level: float,
|
||||||
color: str = 'default',
|
|
||||||
|
|
||||||
# whether or not the line placed in view should highlight
|
|
||||||
# when moused over (aka "hovered")
|
|
||||||
hl_on_hover: bool = True,
|
|
||||||
|
|
||||||
# line style
|
# line style
|
||||||
dotted: bool = False,
|
dotted: bool = False,
|
||||||
|
color: str = 'default',
|
||||||
|
|
||||||
|
# ux
|
||||||
|
hl_on_hover: bool = True,
|
||||||
|
|
||||||
# label fields and options
|
# label fields and options
|
||||||
digits: int = 1,
|
digits: int = 1,
|
||||||
|
|
||||||
always_show_labels: bool = False,
|
always_show_labels: bool = False,
|
||||||
|
|
||||||
add_label: bool = True,
|
add_label: bool = True,
|
||||||
|
|
||||||
orient_v: str = 'bottom',
|
orient_v: str = 'bottom',
|
||||||
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
||||||
) -> LevelLine:
|
) -> LevelLine:
|
||||||
|
@ -580,14 +538,31 @@ def level_line(
|
||||||
|
|
||||||
if add_label:
|
if add_label:
|
||||||
|
|
||||||
label = line.add_label(
|
label = Label(
|
||||||
side='right',
|
|
||||||
opacity=1,
|
view=line.getViewBox(),
|
||||||
x_offset=0,
|
|
||||||
|
# by default we only display the line's level value
|
||||||
|
# in the label
|
||||||
|
fmt_str=('{level:,.{level_digits}f}'),
|
||||||
|
color=color,
|
||||||
|
)
|
||||||
|
|
||||||
|
# anchor to right side (of view ) label
|
||||||
|
label.set_x_anchor_func(
|
||||||
|
right_axis(
|
||||||
|
chart,
|
||||||
|
label,
|
||||||
|
side='left', # side of axis
|
||||||
|
offset=0,
|
||||||
avoid_book=False,
|
avoid_book=False,
|
||||||
)
|
)
|
||||||
label.orient_v = orient_v
|
)
|
||||||
|
|
||||||
|
# add to label set which will be updated on level changes
|
||||||
|
line._labels.append(label)
|
||||||
|
|
||||||
|
label.orient_v = orient_v
|
||||||
line.update_labels({'level': level, 'level_digits': 2})
|
line.update_labels({'level': level, 'level_digits': 2})
|
||||||
label.render()
|
label.render()
|
||||||
|
|
||||||
|
@ -600,6 +575,7 @@ def level_line(
|
||||||
|
|
||||||
|
|
||||||
def order_line(
|
def order_line(
|
||||||
|
|
||||||
chart,
|
chart,
|
||||||
level: float,
|
level: float,
|
||||||
level_digits: float,
|
level_digits: float,
|
||||||
|
@ -651,7 +627,8 @@ def order_line(
|
||||||
# resetting the graphics item transform intermittently
|
# resetting the graphics item transform intermittently
|
||||||
|
|
||||||
# XXX: this is our new approach but seems slower?
|
# XXX: this is our new approach but seems slower?
|
||||||
# line.add_marker(mk_marker(marker_style, marker_size))
|
# path = line.add_marker(mk_marker(marker_style, marker_size))
|
||||||
|
# assert line._marker == path
|
||||||
|
|
||||||
assert not line.markers
|
assert not line.markers
|
||||||
|
|
||||||
|
@ -672,14 +649,22 @@ def order_line(
|
||||||
orient_v = 'top' if action == 'sell' else 'bottom'
|
orient_v = 'top' if action == 'sell' else 'bottom'
|
||||||
|
|
||||||
if action == 'alert':
|
if action == 'alert':
|
||||||
|
|
||||||
|
llabel = Label(
|
||||||
|
|
||||||
|
view=line.getViewBox(),
|
||||||
|
color=line.color,
|
||||||
|
|
||||||
# completely different labelling for alerts
|
# completely different labelling for alerts
|
||||||
fmt_str = 'alert => {level}'
|
fmt_str='alert => {level}',
|
||||||
|
)
|
||||||
|
|
||||||
# for now, we're just duplicating the label contents i guess..
|
# for now, we're just duplicating the label contents i guess..
|
||||||
llabel = line.add_label(
|
line._labels.append(llabel)
|
||||||
side='left',
|
|
||||||
fmt_str=fmt_str,
|
# anchor to left side of view / line
|
||||||
)
|
llabel.set_x_anchor_func(vbr_left(llabel))
|
||||||
|
|
||||||
llabel.fields = {
|
llabel.fields = {
|
||||||
'level': level,
|
'level': level,
|
||||||
'level_digits': level_digits,
|
'level_digits': level_digits,
|
||||||
|
@ -689,31 +674,30 @@ def order_line(
|
||||||
llabel.show()
|
llabel.show()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# # left side label
|
|
||||||
# llabel = line.add_label(
|
|
||||||
# side='left',
|
|
||||||
# fmt_str=' {exec_type}-{order_type}:\n ${$value}',
|
|
||||||
# )
|
|
||||||
# llabel.fields = {
|
|
||||||
# 'order_type': order_type,
|
|
||||||
# 'level': level,
|
|
||||||
# '$value': lambda f: f['level'] * f['size'],
|
|
||||||
# 'size': size,
|
|
||||||
# 'exec_type': exec_type,
|
|
||||||
# }
|
|
||||||
# llabel.orient_v = orient_v
|
|
||||||
# llabel.render()
|
|
||||||
# llabel.show()
|
|
||||||
|
|
||||||
# right before L1 label
|
rlabel = Label(
|
||||||
rlabel = line.add_label(
|
|
||||||
side='right',
|
view=line.getViewBox(),
|
||||||
side_of_axis='left',
|
|
||||||
x_offset=4*marker_size,
|
# display the order pos size
|
||||||
fmt_str=(
|
fmt_str=('{size:.{size_digits}f} '),
|
||||||
'{size:.{size_digits}f} '
|
color=line.color,
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# set anchor callback
|
||||||
|
# right side label by default
|
||||||
|
rlabel.set_x_anchor_func(
|
||||||
|
right_axis(
|
||||||
|
chart,
|
||||||
|
rlabel,
|
||||||
|
side='left', # side of axis
|
||||||
|
offset=4*marker_size,
|
||||||
|
avoid_book=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
line._labels.append(rlabel)
|
||||||
|
|
||||||
rlabel.fields = {
|
rlabel.fields = {
|
||||||
'size': size,
|
'size': size,
|
||||||
'size_digits': size_digits,
|
'size_digits': size_digits,
|
||||||
|
@ -742,69 +726,23 @@ def position_line(
|
||||||
execution submitted to the EMS via the chart's "order mode".
|
execution submitted to the EMS via the chart's "order mode".
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
hcolor = 'default_light'
|
||||||
|
|
||||||
line = level_line(
|
line = level_line(
|
||||||
chart,
|
chart,
|
||||||
level,
|
level,
|
||||||
color='default_light',
|
color=hcolor,
|
||||||
add_label=False,
|
add_label=False,
|
||||||
hl_on_hover=False,
|
hl_on_hover=False,
|
||||||
movable=False,
|
movable=False,
|
||||||
always_show_labels=False,
|
|
||||||
hide_xhair_on_hover=False,
|
hide_xhair_on_hover=False,
|
||||||
use_marker_margin=True,
|
use_marker_margin=True,
|
||||||
|
only_show_markers_on_hover=False,
|
||||||
|
always_show_labels=True,
|
||||||
)
|
)
|
||||||
# hide position marker when out of view (for now)
|
# hide position marker when out of view (for now)
|
||||||
vb = line.getViewBox()
|
vb = line.getViewBox()
|
||||||
|
|
||||||
def update_pp_nav(chartview):
|
|
||||||
vr = vb.state['viewRange']
|
|
||||||
ymn, ymx = vr[1]
|
|
||||||
level = line.value()
|
|
||||||
path = line._marker
|
|
||||||
|
|
||||||
# provide "nav hub" like indicator for where
|
|
||||||
# the position is on the y-dimension
|
|
||||||
# print(path._height)
|
|
||||||
# print(vb.shape())
|
|
||||||
# print(vb.boundingRect())
|
|
||||||
# print(vb.height())
|
|
||||||
_, marker_right, _ = line.marker_right_points()
|
|
||||||
|
|
||||||
if level > ymx: # pin to top of view
|
|
||||||
path.setPos(
|
|
||||||
QPointF(
|
|
||||||
marker_right,
|
|
||||||
2 + path._height,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif level < ymn: # pin to bottom of view
|
|
||||||
path.setPos(
|
|
||||||
QPointF(
|
|
||||||
marker_right,
|
|
||||||
vb.height() - 16 + path._height,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# pp line is viewable so show marker
|
|
||||||
line._marker.show()
|
|
||||||
|
|
||||||
vb.sigYRangeChanged.connect(update_pp_nav)
|
|
||||||
|
|
||||||
rlabel = line.add_label(
|
|
||||||
side='right',
|
|
||||||
fmt_str='{direction}: {size} -> ${$:.2f}',
|
|
||||||
)
|
|
||||||
rlabel.fields = {
|
|
||||||
'direction': 'long' if size > 0 else 'short',
|
|
||||||
'$': size * level,
|
|
||||||
'size': size,
|
|
||||||
}
|
|
||||||
rlabel.orient_v = orient_v
|
|
||||||
rlabel.render()
|
|
||||||
rlabel.show()
|
|
||||||
|
|
||||||
# arrow marker
|
# arrow marker
|
||||||
# scale marker size with dpi-aware font size
|
# scale marker size with dpi-aware font size
|
||||||
font_size = _font.font.pixelSize()
|
font_size = _font.font.pixelSize()
|
||||||
|
@ -819,8 +757,40 @@ def position_line(
|
||||||
|
|
||||||
arrow_path = mk_marker(style, size=arrow_size)
|
arrow_path = mk_marker(style, size=arrow_size)
|
||||||
|
|
||||||
|
path_br = arrow_path.mapToScene(
|
||||||
|
arrow_path.path()
|
||||||
|
).boundingRect()
|
||||||
|
|
||||||
# monkey-cache height for sizing on pp nav-hub
|
# monkey-cache height for sizing on pp nav-hub
|
||||||
arrow_path._height = arrow_path.boundingRect().height()
|
arrow_path._height = path_br.height()
|
||||||
|
|
||||||
|
arrow_path._width = path_br.width()
|
||||||
|
# wp = QPointF(w, w)
|
||||||
|
|
||||||
|
marker_label = Label(
|
||||||
|
view=vb,
|
||||||
|
fmt_str='pp',
|
||||||
|
color=hcolor,
|
||||||
|
update_on_range_change=False,
|
||||||
|
)
|
||||||
|
arrow_path.label = marker_label
|
||||||
|
|
||||||
|
# def arrow_br():
|
||||||
|
# # get actual arrow graphics path
|
||||||
|
# path_br = arrow_path.mapToScene(
|
||||||
|
# arrow_path.path()
|
||||||
|
# ).boundingRect()
|
||||||
|
|
||||||
|
# # vb.locate(arrow_path) #, children=True)
|
||||||
|
|
||||||
|
# return path_br.bottomRight() - QPointF(0, marker_label.h / 2)
|
||||||
|
|
||||||
|
# marker_label.scene_anchor = arrow_br
|
||||||
|
|
||||||
|
line._labels.append(marker_label)
|
||||||
|
|
||||||
|
marker_label.render()
|
||||||
|
marker_label.show()
|
||||||
|
|
||||||
# XXX: uses new marker drawing approach
|
# XXX: uses new marker drawing approach
|
||||||
line.add_marker(arrow_path)
|
line.add_marker(arrow_path)
|
||||||
|
@ -829,4 +799,56 @@ def position_line(
|
||||||
# sanity check
|
# sanity check
|
||||||
line.update_labels({'level': level})
|
line.update_labels({'level': level})
|
||||||
|
|
||||||
|
def update_pp_nav(chartview):
|
||||||
|
'''Show a pp off-screen indicator when order mode is activated.
|
||||||
|
|
||||||
|
'''
|
||||||
|
vr = vb.state['viewRange']
|
||||||
|
ymn, ymx = vr[1]
|
||||||
|
level = line.value()
|
||||||
|
|
||||||
|
path = line._marker
|
||||||
|
label = path.label
|
||||||
|
|
||||||
|
# get actual arrow-marker graphics path
|
||||||
|
path_br = path.mapToScene(
|
||||||
|
path.path()
|
||||||
|
).boundingRect()
|
||||||
|
|
||||||
|
# provide "nav hub" like indicator for where
|
||||||
|
# the position is on the y-dimension
|
||||||
|
|
||||||
|
_, marker_right, _ = line.marker_right_points()
|
||||||
|
|
||||||
|
if level > ymx: # pin to top of view
|
||||||
|
path.setPos(
|
||||||
|
QPointF(
|
||||||
|
marker_right,
|
||||||
|
path._height/3,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif level < ymn: # pin to bottom of view
|
||||||
|
|
||||||
|
path.setPos(
|
||||||
|
QPointF(
|
||||||
|
marker_right,
|
||||||
|
vb.height() - 4/3*path._height,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# adjust marker labels to be above bottom of view
|
||||||
|
label.txt.setPos(path_br.topRight() - QPointF(0, label.h / 2))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# pp line is viewable so show marker normally
|
||||||
|
line._marker.show()
|
||||||
|
|
||||||
|
# place label at bottom right of pp marker
|
||||||
|
label.txt.setPos(path_br.bottomRight() - QPointF(0, label.h / 2))
|
||||||
|
|
||||||
|
line.show_labels()
|
||||||
|
|
||||||
|
vb.sigRangeChanged.connect(update_pp_nav)
|
||||||
|
|
||||||
return line
|
return line
|
||||||
|
|
Loading…
Reference in New Issue