More level line reworking

- break (custom) graphics item style marker drawing into separate func
 but keep using it since it still seems oddly faster then the
 QGraphicsPathItem thing..
- unfactor hover handler; it was uncessary
- make both the graphics path item and custom graphics items approaches
  both work inside ``.paint()``
basic_orders
Tyler Goodlet 2021-03-16 19:36:05 -04:00
parent adf6437449
commit d1c8c2a072
1 changed files with 230 additions and 198 deletions

View File

@ -35,14 +35,15 @@ from .._style import (
def mk_marker( def mk_marker(
marker, style,
size: float = 20.0 size: float = 20.0,
use_qgpath: bool = True,
) -> QGraphicsPathItem: ) -> QGraphicsPathItem:
"""Add a marker to be displayed on the line wrapped in a ``QGraphicsPathItem`` """Add a marker to be displayed on the line wrapped in a ``QGraphicsPathItem``
ready to be placed using scene coordinates (not view). ready to be placed using scene coordinates (not view).
**Arguments** **Arguments**
marker String indicating the style of marker to add: style String indicating the style of marker to add:
``'<|'``, ``'|>'``, ``'>|'``, ``'|<'``, ``'<|>'``, ``'<|'``, ``'|>'``, ``'>|'``, ``'|<'``, ``'<|>'``,
``'>|<'``, ``'^'``, ``'v'``, ``'o'`` ``'>|<'``, ``'^'``, ``'v'``, ``'o'``
size Size of the marker in pixels. Default is 10.0. size Size of the marker in pixels. Default is 10.0.
@ -50,46 +51,104 @@ def mk_marker(
""" """
path = QtGui.QPainterPath() path = QtGui.QPainterPath()
if marker == 'o': if style == 'o':
path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
# arrow pointing away-from the top of line # arrow pointing away-from the top of line
if '<|' in marker: if '<|' in style:
p = QtGui.QPolygonF([Point(0.5, 0), Point(0, -0.5), Point(-0.5, 0)]) p = QtGui.QPolygonF([Point(0.5, 0), Point(0, -0.5), Point(-0.5, 0)])
path.addPolygon(p) path.addPolygon(p)
path.closeSubpath() path.closeSubpath()
# arrow pointing away-from the bottom of line # arrow pointing away-from the bottom of line
if '|>' in marker: if '|>' in style:
p = QtGui.QPolygonF([Point(0.5, 0), Point(0, 0.5), Point(-0.5, 0)]) p = QtGui.QPolygonF([Point(0.5, 0), Point(0, 0.5), Point(-0.5, 0)])
path.addPolygon(p) path.addPolygon(p)
path.closeSubpath() path.closeSubpath()
# arrow pointing in-to the top of line # arrow pointing in-to the top of line
if '>|' in marker: if '>|' in style:
p = QtGui.QPolygonF([Point(0.5, -0.5), Point(0, 0), Point(-0.5, -0.5)]) p = QtGui.QPolygonF([Point(0.5, -0.5), Point(0, 0), Point(-0.5, -0.5)])
path.addPolygon(p) path.addPolygon(p)
path.closeSubpath() path.closeSubpath()
# arrow pointing in-to the bottom of line # arrow pointing in-to the bottom of line
if '|<' in marker: if '|<' in style:
p = QtGui.QPolygonF([Point(0.5, 0.5), Point(0, 0), Point(-0.5, 0.5)]) p = QtGui.QPolygonF([Point(0.5, 0.5), Point(0, 0), Point(-0.5, 0.5)])
path.addPolygon(p) path.addPolygon(p)
path.closeSubpath() path.closeSubpath()
if '^' in marker: if '^' in style:
p = QtGui.QPolygonF([Point(0, -0.5), Point(0.5, 0), Point(0, 0.5)]) p = QtGui.QPolygonF([Point(0, -0.5), Point(0.5, 0), Point(0, 0.5)])
path.addPolygon(p) path.addPolygon(p)
path.closeSubpath() path.closeSubpath()
if 'v' in marker: if 'v' in style:
p = QtGui.QPolygonF([Point(0, -0.5), Point(-0.5, 0), Point(0, 0.5)]) p = QtGui.QPolygonF([Point(0, -0.5), Point(-0.5, 0), Point(0, 0.5)])
path.addPolygon(p) path.addPolygon(p)
path.closeSubpath() 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) if use_qgpath:
path = QGraphicsPathItem(path)
path.scale(size, size)
return path
def draw_markers(
markers: list,
color: pg.Color,
p: QtGui.QPainter,
left: float,
right: float,
right_offset: float,
) -> None:
"""Pain markers in ``pg.GraphicsItem`` style by first
removing the view transform for the painter, drawing the markers
in scene coords, then restoring the view coords.
"""
# paint markers in native coordinate system
orig_tr = p.transform()
start = orig_tr.map(Point(left, 0))
end = orig_tr.map(Point(right, 0))
up = orig_tr.map(Point(left, 1))
dif = end - start
# length = Point(dif).length()
angle = np.arctan2(dif.y(), dif.x()) * 180 / np.pi
p.resetTransform()
p.translate(start)
p.rotate(angle)
up = up - start
det = up.x() * dif.y() - dif.x() * up.y()
p.scale(1, 1 if det > 0 else -1)
p.setBrush(fn.mkBrush(color))
# p.setBrush(fn.mkBrush(self.currentPen.color()))
tr = p.transform()
sizes = []
for path, pos, size in 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)
sizes.append(size)
p.setTransform(orig_tr)
return max(sizes)
# TODO: probably worth investigating if we can # TODO: probably worth investigating if we can
@ -164,6 +223,8 @@ class LevelLine(pg.InfiniteLine):
self._y_incr_mult = 1 / chart._lc._symbol.tick_size self._y_incr_mult = 1 / chart._lc._symbol.tick_size
self._right_end_sc: float = 0
def txt_offsets(self) -> Tuple[int, int]: def txt_offsets(self) -> Tuple[int, int]:
return 0, 0 return 0, 0
@ -300,67 +361,6 @@ class LevelLine(pg.InfiniteLine):
self.movable = True self.movable = True
self.set_level(y) # implictly calls reposition handler self.set_level(y) # implictly calls reposition handler
# TODO: just put this in the hoverEvent handler
def set_mouser_hover(self, hover: bool) -> None:
"""Mouse hover callback.
"""
# XXX: currently we'll just return if _hoh is False
if self.mouseHovering == hover:
return
self.mouseHovering = hover
chart = self._chart
cur = chart._cursor
if hover:
# highlight if so configured
if self._hoh:
self.currentPen = self.hoverPen
if self not in cur._trackers:
# 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(
# 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()
else:
cur._y_label_update = True
self.currentPen = self.pen
cur._hovered.remove(self)
if self not in cur._trackers:
cur.show_xhair(y_label_level=self.value())
if not self._always_show_labels:
for at, label in self._labels:
label.hide()
label.txt.update()
# label.unhighlight()
# highlight any attached label
self.update()
def mouseDragEvent(self, ev): def mouseDragEvent(self, ev):
"""Override the ``InfiniteLine`` handler since we need more """Override the ``InfiniteLine`` handler since we need more
detailed control and start end signalling. detailed control and start end signalling.
@ -448,44 +448,6 @@ class LevelLine(pg.InfiniteLine):
# TODO: enter labels edit mode # TODO: enter labels edit mode
print(f'double click {ev}') print(f'double click {ev}')
def draw_markers(
self,
p: QtGui.QPainter,
left: float,
right: float,
right_offset: float,
) -> None:
# paint markers in native coordinate system
tr = p.transform()
p.resetTransform()
start = tr.map(Point(left, 0))
end = tr.map(Point(right, 0))
up = tr.map(Point(left, 1))
dif = end - start
# length = Point(dif).length()
angle = np.arctan2(dif.y(), dif.x()) * 180 / np.pi
p.translate(start)
p.rotate(angle)
up = up - start
det = up.x() * dif.y() - dif.x() * up.y()
p.scale(1, 1 if det > 0 else -1)
p.setBrush(fn.mkBrush(self.currentPen.color()))
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)
def right_point( def right_point(
self, self,
) -> float: ) -> float:
@ -493,24 +455,17 @@ class LevelLine(pg.InfiniteLine):
chart = self._chart chart = self._chart
l1_len = chart._max_l1_line_len l1_len = chart._max_l1_line_len
ryaxis = chart.getAxis('right') ryaxis = chart.getAxis('right')
up_to_l1_sc = ryaxis.pos().x() - l1_len
if self.markers: # right_view_coords = chart._vb.mapToView(
size = self.markers[0][2] # Point(right_scene_coords, 0)).x()
else:
size = 0
r_axis_x = ryaxis.pos().x() return up_to_l1_sc
right_offset = l1_len + size + 10 # return (
right_scene_coords = r_axis_x - right_offset # right_scene_coords,
# right_view_coords,
right_view_coords = chart._vb.mapToView( # right_offset,
Point(right_scene_coords, 0)).x() # )
return (
right_scene_coords,
right_view_coords,
right_offset,
)
def paint( def paint(
self, self,
@ -529,31 +484,78 @@ class LevelLine(pg.InfiniteLine):
# pen.setJoinStyle(QtCore.Qt.MiterJoin) # pen.setJoinStyle(QtCore.Qt.MiterJoin)
p.setPen(pen) p.setPen(pen)
rsc, rvc, rosc = self.right_point() # l1_sc, rvc, rosc = self.right_point()
chart = self._chart
l1_len = chart._max_l1_line_len
ryaxis = chart.getAxis('right')
r_axis_x = ryaxis.pos().x()
# right_offset = l1_len # + size #+ 10
up_to_l1_sc = r_axis_x - l1_len
vb = self.getViewBox()
size = 20 # default marker size
marker_right = up_to_l1_sc - (1.375 * size)
if self.markers:
# size = self.markers[0][2]
# # three_m_right_of_last_bar = last_bar_sc + 3*size
# # marker_right = min(
# # two_m_left_of_l1,
# # three_m_right_of_last_bar
# # )
size = draw_markers(
self.markers,
self.currentPen.color(),
p,
vb_left,
# right,
vb_right,
# rsc - 6,
marker_right,
# right_scene_coords,
)
# marker_size = self.markers[0][2]
self._maxMarkerSize = max([m[2] / 2. for m in self.markers])
line_end = marker_right - (6/16 * size)
# else:
# line_end = last_bar_sc
# line_end_view = rvc
# # 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.
elif self._marker:
self._marker.setPos(QPointF(marker_right, self.scene_y()))
line_end = marker_right - (6/16 * size)
else:
# leave small blank gap for style
line_end = r_axis_x - 10
line_end_view = vb.mapToView(Point(line_end, 0)).x()
# # 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()
p.drawLine( p.drawLine(
Point(vb_left, 0), Point(vb_left, 0),
Point(rvc, 0) # Point(right, 0)
Point(line_end_view, 0)
) )
self._right_end_sc = line_end
# this seems slower when moving around def scene_right_xy(self) -> QPointF:
# order lines.. not sure wtf is up with that. return self.getViewBox().mapFromView(
# for now we're just using it on the position line. QPointF(0, self.value())
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,
vb_left,
vb_right,
rsc
) )
def scene_y(self) -> float: def scene_y(self) -> float:
@ -570,22 +572,79 @@ class LevelLine(pg.InfiniteLine):
self._marker = path self._marker = path
rsc, rvc, rosc = self.right_point() rsc = self.right_point()
self._marker.setPen(self.currentPen) self._marker.setPen(self.currentPen)
self._marker.setBrush(fn.mkBrush(self.currentPen.color())) self._marker.setBrush(fn.mkBrush(self.currentPen.color()))
self._marker.scale(20, 20)
# 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()))
# self.update()
def hoverEvent(self, ev): def hoverEvent(self, ev):
"""Gawd, basically overriding it all at this point... """Mouse hover callback.
""" """
chart = self._chart
cur = chart._cursor
# hovered
if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
self.set_mouser_hover(True)
else: # if already hovered we don't need to run again
self.set_mouser_hover(False) if self.mouseHovering is True:
return
# highlight if so configured
if self._hoh:
self.currentPen = self.hoverPen
if self not in cur._trackers:
# 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(
# 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()
self.mouseHovering = True
else: # un-hovered
if self.mouseHovering is False:
return
cur._y_label_update = True
self.currentPen = self.pen
cur._hovered.remove(self)
if self not in cur._trackers:
cur.show_xhair(y_label_level=self.value())
if not self._always_show_labels:
for at, label in self._labels:
label.hide()
label.txt.update()
# label.unhighlight()
self.mouseHovering = False
self.update()
def level_line( def level_line(
@ -698,21 +757,25 @@ def order_line(
'alert': ('^', 12), 'alert': ('^', 12),
}[action] }[action]
# XXX: not sure wtf but this is somehow laggier # this fixes it the artifact issue! .. of course, bouding rect stuff
# when tested manually staging an order.. line._maxMarkerSize = marker_size
# 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( # use ``QPathGraphicsItem``s to draw markers in scene coords
# instead of the old way that was doing the same but by
# resetting the graphics item transform intermittently
# line.add_marker(mk_marker(marker_style, marker_size))
assert not line.markers
# the old way which is still somehow faster?
path = mk_marker(
marker_style, marker_style,
# the "position" here is now ignored since we modified # the "position" here is now ignored since we modified
# internals to pin markers to the right end of the line # internals to pin markers to the right end of the line
0.9, marker_size,
marker_size use_qgpath=False,
) )
# manually append for later ``.pain()`` drawing
line.markers.append((path, 0, marker_size))
orient_v = 'top' if action == 'sell' else 'bottom' orient_v = 'top' if action == 'sell' else 'bottom'
@ -733,26 +796,11 @@ def order_line(
llabel.render() llabel.render()
llabel.show() llabel.show()
# right before L1 label
rlabel = line.add_label(
side='right',
side_of_axis='left',
fmt_str=fmt_str,
)
rlabel.fields = {
'level': level,
'level_digits': level_digits,
}
rlabel.orient_v = orient_v
rlabel.render()
rlabel.show()
else: else:
# left side label # left side label
llabel = line.add_label( llabel = line.add_label(
side='left', side='left',
fmt_str='{exec_type}-{order_type}: ${$value}', fmt_str=' {exec_type}-{order_type}:\n ${$value}',
) )
llabel.fields = { llabel.fields = {
'order_type': order_type, 'order_type': order_type,
@ -782,24 +830,6 @@ def order_line(
rlabel.render() rlabel.render()
rlabel.show() 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 # sanity check
line.update_labels({'level': level}) line.update_labels({'level': level})
@ -839,7 +869,7 @@ def position_line(
rlabel = line.add_label( rlabel = line.add_label(
side='left', side='left',
fmt_str='{direction}: {size}\n${$:.2f}', fmt_str='{direction}: {size} -> ${$:.2f}',
) )
rlabel.fields = { rlabel.fields = {
'direction': 'long' if size > 0 else 'short', 'direction': 'long' if size > 0 else 'short',
@ -850,6 +880,8 @@ def position_line(
rlabel.render() rlabel.render()
rlabel.show() rlabel.show()
line.set_level(level)
# sanity check # sanity check
line.update_labels({'level': level}) line.update_labels({'level': level})