Level line look and feel rework

Add support for drawing ``QPathGraphicsItem`` markers but don't use them
since they seem to be shitting up when combined with the infinite line
(bounding rect?): weird artifacts and whatnot. The only way to avoid
said glitches seems to be to update inside the infinite line's
`.paint()` but that slows stuff down.. Instead stick with the manual
paint job use the same pin point: left of the L1 spread graphics - where
the lines now also extend to.

Further stuff:
- Pin the y-label to a line's value on hover.
- Disable x-dimension line moving
- Rework the labelling to be more minimal
basic_orders
Tyler Goodlet 2021-03-13 17:31:15 -05:00
parent 887c305d46
commit c71a3e0fc5
1 changed files with 204 additions and 64 deletions

View File

@ -23,6 +23,7 @@ from typing import Tuple, Optional, List
import pyqtgraph as pg
from pyqtgraph import Point, functions as fn
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QGraphicsPathItem
from PyQt5.QtCore import QPointF
import numpy as np
@ -34,22 +35,18 @@ from .._style import (
def mk_marker(
self,
marker,
position: float = 0.5,
size: float = 10.0
) -> QtGui.QPainterPath:
"""Add a marker to be displayed on the line.
size: float = 20.0
) -> QGraphicsPathItem:
"""Add a marker to be displayed on the line wrapped in a ``QGraphicsPathItem``
ready to be placed using scene coordinates (not view).
============= =========================================================
**Arguments**
marker String indicating the style of marker to add:
``'<|'``, ``'|>'``, ``'>|'``, ``'|<'``, ``'<|>'``,
``'>|<'``, ``'^'``, ``'v'``, ``'o'``
position Position (0.0-1.0) along the visible extent of the line
to place the marker. Default is 0.5.
size Size of the marker in pixels. Default is 10.0.
============= =========================================================
"""
path = QtGui.QPainterPath()
@ -90,8 +87,9 @@ def mk_marker(
path.addPolygon(p)
path.closeSubpath()
self._maxMarkerSize = max([m[2] / 2. for m in self.markers])
# self._maxMarkerSize = max([m[2] / 2. for m in self.markers])
return QGraphicsPathItem(path)
# TODO: probably worth investigating if we can
@ -133,6 +131,8 @@ class LevelLine(pg.InfiniteLine):
self._dotted = dotted
self._hide_xhair_on_hover = hide_xhair_on_hover
self._marker = None
if dotted:
self._style = QtCore.Qt.DashLine
else:
@ -146,6 +146,7 @@ class LevelLine(pg.InfiniteLine):
# list of labels anchored at one of the 2 line endpoints
# inside the viewbox
self._labels: List[(int, Label)] = []
self._markers: List[(int, Label)] = []
# whenever this line is moved trigger label updates
self.sigPositionChanged.connect(self.on_pos_change)
@ -196,26 +197,37 @@ class LevelLine(pg.InfiniteLine):
'{level:,.{level_digits}f}'
),
side: str = 'right',
side_of_axis: str = 'left',
x_offset: float = 50,
font_size_inches: float = _down_2_font_inches_we_like,
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.
"""
vb = self.getViewBox()
label = Label(
view=vb,
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))
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))
@ -288,7 +300,8 @@ class LevelLine(pg.InfiniteLine):
self.movable = True
self.set_level(y) # implictly calls reposition handler
def setMouseHover(self, hover: bool) -> None:
# TODO: just put this in the hoverEvent handler
def set_mouser_hover(self, hover: bool) -> None:
"""Mouse hover callback.
"""
@ -304,34 +317,38 @@ class LevelLine(pg.InfiniteLine):
if hover:
# highlight if so configured
if self._hoh:
self.currentPen = self.hoverPen
# for at, label in self._labels:
# label.highlight(self.hoverPen)
# only disable cursor y-label updates
# if we're highlighting a line
cur._y_label_update = False
# add us to cursor state
cur.add_hovered(self)
if self._hide_xhair_on_hover:
cur.hide_xhair()
cur.hide_xhair(
# set y-label to current value
y_label_level=self.value(),
# fg_color=self._hcolor,
# bg_color=self._hcolor,
)
# if we want highlighting of labels
# it should be delegated into this method
self.show_labels()
# TODO: hide y-crosshair?
# chart._cursor.graphics[chart]['hl'].hide()
# self.setCursor(QtCore.Qt.OpenHandCursor)
# self.setCursor(QtCore.Qt.DragMoveCursor)
else:
cur._y_label_update = True
self.currentPen = self.pen
cur._hovered.remove(self)
if self not in cur._trackers:
cur.show_xhair()
# g = cur.graphics[chart]
# g['yl'].show()
# g['hl'].show()
if not self._always_show_labels:
for at, label in self._labels:
@ -382,7 +399,7 @@ class LevelLine(pg.InfiniteLine):
m = self._y_incr_mult
self.setPos(
QPointF(
pos.x(),
self.pos().x(), # don't allow shifting horizontally
round(pos.y() * m) / m
)
)
@ -410,6 +427,9 @@ class LevelLine(pg.InfiniteLine):
self._labels.clear()
if self._marker:
self.scene().removeItem(self._marker)
# remove from chart/cursor states
chart = self._chart
cur = chart._cursor
@ -456,8 +476,11 @@ class LevelLine(pg.InfiniteLine):
tr = p.transform()
for path, pos, size in self.markers:
p.setTransform(tr)
# XXX: we drop the "scale / %" placement
# x = length * pos
x = right_offset
p.translate(x, 0)
p.scale(size, size)
p.drawPath(path)
@ -502,7 +525,7 @@ class LevelLine(pg.InfiniteLine):
vb_left, vb_right = self._endPoints
pen = self.currentPen
pen.setJoinStyle(QtCore.Qt.MiterJoin)
# pen.setJoinStyle(QtCore.Qt.MiterJoin)
p.setPen(pen)
rsc, rvc, rosc = self.right_point()
@ -512,6 +535,18 @@ class LevelLine(pg.InfiniteLine):
Point(rvc, 0)
)
# this seems slower when moving around
# order lines.. not sure wtf is up with that.
# for now we're just using it on the position line.
if self._marker:
scene_pos = QPointF(rsc, self.scene_y())
self._marker.setPos(scene_pos)
# somehow this is adding a lot of lag, but without
# if we're getting weird trail artefacs grrr.
# gotta be some kinda boundingRect problem yet again
# self._marker.update()
if self.markers:
self.draw_markers(
p,
@ -520,14 +555,36 @@ class LevelLine(pg.InfiniteLine):
rsc
)
def scene_y(self) -> float:
return self.getViewBox().mapFromView(Point(0, self.value())).y()
def add_marker(
self,
path: QtGui.QGraphicsPathItem,
) -> None:
# chart = self._chart
vb = self.getViewBox()
vb.scene().addItem(path)
self._marker = path
rsc, rvc, rosc = self.right_point()
self._marker.setPen(self.currentPen)
self._marker.setBrush(fn.mkBrush(self.currentPen.color()))
self._marker.scale(20, 20)
# y_in_sc = chart._vb.mapFromView(Point(0, self.value())).y()
path.setPos(QPointF(rsc, self.scene_y()))
def hoverEvent(self, ev):
"""Gawd, basically overriding it all at this point...
"""
if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
self.setMouseHover(True)
self.set_mouser_hover(True)
else:
self.setMouseHover(False)
self.set_mouser_hover(False)
def level_line(
@ -588,6 +645,8 @@ def level_line(
label = line.add_label(
side='right',
opacity=1,
x_offset=0,
avoid_book=False,
)
label.orient_v = orient_v
@ -606,18 +665,18 @@ def order_line(
chart,
level: float,
level_digits: float,
action: str, # buy or sell
size: Optional[int] = 1,
size_digits: int = 0,
show_markers: bool = False,
submit_price: float = None,
order_status: str = 'dark',
exec_type: str = 'dark',
order_type: str = 'limit',
orient_v: str = 'bottom',
**line_kwargs,
) -> LevelLine:
"""Convenience routine to add a line graphic representing an order
execution submitted to the EMS via the chart's "order mode".
@ -630,29 +689,56 @@ def order_line(
**line_kwargs
)
if show_markers:
# add arrow marker on end of line nearest y-axis
marker_style, marker_size = {
'buy': ('|<', 20),
'sell': ('>|', 20),
'alert': ('^', 12),
}[action]
# XXX: not sure wtf but this is somehow laggier
# when tested manually staging an order..
# I would assume it's to do either with come kinda
# conflict of the ``QGraphicsPathItem`` with the custom
# object or that those types are just slower in general...
# Pretty annoying to say the least.
# line.add_marker(mk_marker(marker_style))
line.addMarker(
marker_style,
# the "position" here is now ignored since we modified
# internals to pin markers to the right end of the line
0.9,
marker_size
)
orient_v = 'top' if action == 'sell' else 'bottom'
if action == 'alert':
# completely different labelling for alerts
fmt_str = 'alert => {level}'
# for now, we're just duplicating the label contents i guess..
llabel = line.add_label(
side='left',
fmt_str='{order_status}-{order_type}:{submit_price}',
fmt_str=fmt_str,
)
llabel.fields = {
'order_status': order_status,
'order_type': order_type,
'submit_price': submit_price,
'level': level,
'level_digits': level_digits,
}
llabel.orient_v = orient_v
llabel.render()
llabel.show()
# right before L1 label
rlabel = line.add_label(
side='right',
fmt_str=(
'{size:.{size_digits}f} x '
'{level:,.{level_digits}f}'
),
side_of_axis='left',
fmt_str=fmt_str,
)
rlabel.fields = {
'size': size,
'size_digits': size_digits,
'level': level,
'level_digits': level_digits,
}
@ -661,6 +747,58 @@ def order_line(
rlabel.render()
rlabel.show()
else:
# left side label
llabel = line.add_label(
side='left',
fmt_str='{exec_type}-{order_type}: ${$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 = line.add_label(
side='right',
side_of_axis='left',
fmt_str=(
'{size:.{size_digits}f} x'
),
)
rlabel.fields = {
'size': size,
'size_digits': size_digits,
}
rlabel.orient_v = orient_v
rlabel.render()
rlabel.show()
# axis_label = line.add_label(
# side='right',
# side_of_axis='left',
# x_offset=0,
# avoid_book=False,
# fmt_str=(
# '{level:,.{level_digits}f}'
# ),
# )
# axis_label.fields = {
# 'level': level,
# 'level_digits': level_digits,
# }
# axis_label.orient_v = orient_v
# axis_label.render()
# axis_label.show()
# sanity check
line.update_labels({'level': level})
@ -691,10 +829,12 @@ def position_line(
hide_xhair_on_hover=False,
)
if size > 0:
line.addMarker('|<', 0.9, 20)
arrow_path = mk_marker('|<')
elif size < 0:
line.addMarker('>|', 0.9, 20)
arrow_path = mk_marker('>|')
line.add_marker(arrow_path)
rlabel = line.add_label(
side='left',