From 92d6b19777e0a9b28eb16055d20576b09be4eac6 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 15 Aug 2021 12:17:45 -0400 Subject: [PATCH] Rejig order line creation / config In an effort to simplify line creation and management from an order mode here's a slew of changes: - use our new ``LevelMarker`` for order lines and fully drop usage of the original marker implementation stuff from `pg.InfiniteLine` - add a left side label which shows the instrument's "units" value - the most fundamental unit for the "size" of the order - allow passing in an optional `marker_size: str` so that `action: str` doesn't necessarily have to be passed (eg. when copying from an existing line) - change a couple of internal line config options to be public attrs which can now be configured dynamically in real-time (since they're all `bool` anyway): * `hl_on_hover` -> `highlight_on_hover` * `_always_show_labels` -> `always_show_labels` - `LevelLine.set_level()` now only sets the position if it was **not** called from the position changed signal (which would be redundant) --- piker/ui/_lines.py | 223 +++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 108 deletions(-) diff --git a/piker/ui/_lines.py b/piker/ui/_lines.py index 85a9a77c..350d999c 100644 --- a/piker/ui/_lines.py +++ b/piker/ui/_lines.py @@ -18,22 +18,20 @@ Lines for orders, alerts, L2. """ -from functools import partial from math import floor -from typing import Tuple, Optional, List +from typing import Tuple, Optional, List, Callable import pyqtgraph as pg from pyqtgraph import Point, functions as fn from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import QPointF -from PyQt5.QtGui import QGraphicsPathItem -from ._annotate import mk_marker_path, qgo_draw_markers +from ._annotate import qgo_draw_markers, LevelMarker from ._anchors import ( marker_right_points, vbr_left, right_axis, - gpath_pin, + # gpath_pin, ) from ._label import Label from ._style import hcolor, _font @@ -52,15 +50,13 @@ class LevelLine(pg.InfiniteLine): color: str = 'default', highlight_color: str = 'default_light', dotted: bool = False, - # marker_size: int = 20, # UX look and feel opts always_show_labels: bool = False, - hl_on_hover: bool = True, + highlight_on_hover: bool = True, hide_xhair_on_hover: bool = True, only_show_markers_on_hover: bool = True, use_marker_margin: bool = False, - movable: bool = True, ) -> None: @@ -77,7 +73,7 @@ class LevelLine(pg.InfiniteLine): ) self._chart = chart - self._hoh = hl_on_hover + self.highlight_on_hover = highlight_on_hover self._dotted = dotted self._hide_xhair_on_hover = hide_xhair_on_hover @@ -117,7 +113,7 @@ class LevelLine(pg.InfiniteLine): # TODO: for when we want to move groups of lines? self._track_cursor: bool = False - self._always_show_labels = always_show_labels + self.always_show_labels = always_show_labels self._on_drag_start = lambda l: None self._on_drag_end = lambda l: None @@ -156,7 +152,9 @@ class LevelLine(pg.InfiniteLine): """Position changed handler. """ - self.update_labels({'level': self.value()}) + level = self.value() + self.update_labels({'level': level}) + self.set_level(level, called_from_on_pos_change=True) def update_labels( self, @@ -190,19 +188,23 @@ class LevelLine(pg.InfiniteLine): def set_level( self, level: float, + called_from_on_pos_change: bool = False, ) -> None: - last = self.value() - # if the position hasn't changed then ``.update_labels()`` - # will not be called by a non-triggered `.on_pos_change()`, - # so we need to call it manually to avoid mismatching - # label-to-line color when the line is updated but not - # "moved". - if level == last: - self.update_labels({'level': level}) + if not called_from_on_pos_change: + last = self.value() + + # if the position hasn't changed then ``.update_labels()`` + # will not be called by a non-triggered `.on_pos_change()`, + # so we need to call it manually to avoid mismatching + # label-to-line color when the line is updated but not + # "moved". + if level == last: + self.update_labels({'level': level}) + + self.setPos(level) - self.setPos(level) self.level = self.value() self.update() @@ -453,7 +455,7 @@ class LevelLine(pg.InfiniteLine): self._marker.show() # highlight if so configured - if self._hoh: + if self.highlight_on_hover: self.currentPen = self.hoverPen @@ -502,7 +504,7 @@ class LevelLine(pg.InfiniteLine): if self not in cur._trackers: cur.show_xhair(y_label_level=self.value()) - if not self._always_show_labels: + if not self.always_show_labels: for label in self._labels: label.hide() label.txt.update() @@ -514,6 +516,7 @@ class LevelLine(pg.InfiniteLine): def level_line( + chart: 'ChartPlotWidget', # noqa level: float, @@ -522,7 +525,7 @@ def level_line( color: str = 'default', # ux - hl_on_hover: bool = True, + highlight_on_hover: bool = True, # label fields and options always_show_labels: bool = False, @@ -534,7 +537,7 @@ def level_line( """Convenience routine to add a styled horizontal line to a plot. """ - hl_color = color + '_light' if hl_on_hover else color + hl_color = color + '_light' if highlight_on_hover else color line = LevelLine( chart, @@ -546,7 +549,7 @@ def level_line( dotted=dotted, # UX related options - hl_on_hover=hl_on_hover, + highlight_on_hover=highlight_on_hover, # when set to True the label is always shown instead of just on # highlight (which is a privacy thing for orders) @@ -599,15 +602,14 @@ def order_line( chart, level: float, - level_digits: float, action: str, # buy or sell + marker_style: Optional[str] = None, + level_digits: Optional[float] = 3, size: Optional[int] = 1, size_digits: int = 1, show_markers: bool = False, submit_price: float = None, - exec_type: str = 'dark', - order_type: str = 'limit', orient_v: str = 'bottom', **line_kwargs, @@ -626,18 +628,72 @@ def order_line( **line_kwargs ) + font_size = _font.font.pixelSize() + # scale marker size with dpi-aware font size + marker_size = floor(1.375 * font_size) + + orient_v = 'top' if action == 'sell' else 'bottom' + + if action == 'alert': + + label = Label( + + view=line.getViewBox(), + color=line.color, + + # completely different labelling for alerts + fmt_str='alert => {level}', + ) + + # for now, we're just duplicating the label contents i guess.. + line._labels.append(label) + + # anchor to left side of view / line + label.set_x_anchor_func(vbr_left(label)) + + label.fields = { + 'level': level, + 'level_digits': level_digits, + } + + marker_size = marker_size * 0.666 + + else: + + # pp_label.scene_anchor = partial( + # gpath_pin, + # location_description='right-of-path-centered', + # gpath=marker, + # label=label, + # ) + + label = Label( + view=line.getViewBox(), + # display the order pos size, which is some multiple + # of the user defined base unit size + fmt_str=('units: {size:.{size_digits}f}'), # old + color=line.color, + ) + + label.set_x_anchor_func(vbr_left(label)) + + line._labels.append(label) + + label.fields = { + 'size': size, + 'size_digits': 0, + } + + label.orient_v = orient_v + label.render() + label.show() + if show_markers: - font_size = _font.font.pixelSize() - - # scale marker size with dpi-aware font size - arrow_size = floor(1.375 * font_size) - alert_size = arrow_size * 0.666 - # add arrow marker on end of line nearest y-axis - marker_style, marker_size = { - 'buy': ('|<', arrow_size), - 'sell': ('>|', arrow_size), - 'alert': ('v', alert_size), + marker_style = marker_style or { + 'buy': '|<', + 'sell': '>|', + 'alert': 'v', }[action] # this fixes it the artifact issue! .. of course, bounding rect stuff @@ -648,18 +704,27 @@ def order_line( # resetting the graphics item transform intermittently # the old way which is still somehow faster? - marker = QGraphicsPathItem( - mk_marker_path( - marker_style, - # the "position" here is now ignored since we modified - # internals to pin markers to the right end of the line - # marker_size, - - # uncommment for the old transform / .paint() marker method - # use_qgpath=False, - ) + marker = LevelMarker( + chart=chart, + style=marker_style, + get_level=line.value, + size=marker_size, + keep_in_view=False, + # on_paint=self.update_graphics, ) - marker.scale(marker_size, marker_size) + + # marker = QGraphicsPathItem( + # mk_marker_path( + # marker_style, + # # the "position" here is now ignored since we modified + # # internals to pin markers to the right end of the line + # # marker_size, + + # # uncommment for the old transform / .paint() marker method + # # use_qgpath=False, + # ) + # ) + # marker.scale(marker_size, marker_size) # XXX: this is our new approach but seems slower? marker = line.add_marker(marker) @@ -677,65 +742,7 @@ def order_line( # # testing to figure out why tf that's true. # line.markers.append((marker, 0, marker_size)) - orient_v = 'top' if action == 'sell' else 'bottom' - - if action == 'alert': - - llabel = Label( - - view=line.getViewBox(), - color=line.color, - - # completely different labelling for alerts - fmt_str='alert => {level}', - ) - - # for now, we're just duplicating the label contents i guess.. - line._labels.append(llabel) - - # anchor to left side of view / line - llabel.set_x_anchor_func(vbr_left(llabel)) - - llabel.fields = { - 'level': level, - 'level_digits': level_digits, - } - llabel.orient_v = orient_v - llabel.render() - llabel.show() - - marker.label = llabel - - else: - - rlabel = Label( - - view=line.getViewBox(), - - # display the order pos size, which is some multiple - # of the user defined base unit size - fmt_str=('{size:.{size_digits}f}'), # old - color=line.color, - ) - marker.label = rlabel - - rlabel.scene_anchor = partial( - gpath_pin, - location_description='right-of-path-centered', - gpath=marker, - label=rlabel, - ) - - line._labels.append(rlabel) - - rlabel.fields = { - 'size': size, - 'size_digits': 0, - } - - rlabel.orient_v = orient_v - rlabel.render() - rlabel.show() + marker.label = label # sanity check line.update_labels({'level': level})