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
							parent
							
								
									adf6437449
								
							
						
					
					
						commit
						d1c8c2a072
					
				|  | @ -35,14 +35,15 @@ from .._style import ( | |||
| 
 | ||||
| 
 | ||||
| def mk_marker( | ||||
|     marker, | ||||
|     size: float = 20.0 | ||||
|     style, | ||||
|     size: float = 20.0, | ||||
|     use_qgpath: bool = True, | ||||
| ) -> 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: | ||||
|     style        String indicating the style of marker to add: | ||||
|                   ``'<|'``, ``'|>'``, ``'>|'``, ``'|<'``, ``'<|>'``, | ||||
|                   ``'>|<'``, ``'^'``, ``'v'``, ``'o'`` | ||||
|     size          Size of the marker in pixels. Default is 10.0. | ||||
|  | @ -50,46 +51,104 @@ def mk_marker( | |||
|     """ | ||||
|     path = QtGui.QPainterPath() | ||||
| 
 | ||||
|     if marker == 'o': | ||||
|     if style == 'o': | ||||
|         path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) | ||||
| 
 | ||||
|     # 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)]) | ||||
|         path.addPolygon(p) | ||||
|         path.closeSubpath() | ||||
| 
 | ||||
|     # 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)]) | ||||
|         path.addPolygon(p) | ||||
|         path.closeSubpath() | ||||
| 
 | ||||
|     # 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)]) | ||||
|         path.addPolygon(p) | ||||
|         path.closeSubpath() | ||||
| 
 | ||||
|     # 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)]) | ||||
|         path.addPolygon(p) | ||||
|         path.closeSubpath() | ||||
| 
 | ||||
|     if '^' in marker: | ||||
|     if '^' in style: | ||||
|         p = QtGui.QPolygonF([Point(0, -0.5), Point(0.5, 0), Point(0, 0.5)]) | ||||
|         path.addPolygon(p) | ||||
|         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)]) | ||||
|         path.addPolygon(p) | ||||
|         path.closeSubpath() | ||||
| 
 | ||||
|     # 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 | ||||
|  | @ -164,6 +223,8 @@ class LevelLine(pg.InfiniteLine): | |||
| 
 | ||||
|         self._y_incr_mult = 1 / chart._lc._symbol.tick_size | ||||
| 
 | ||||
|         self._right_end_sc: float = 0 | ||||
| 
 | ||||
|     def txt_offsets(self) -> Tuple[int, int]: | ||||
|         return 0, 0 | ||||
| 
 | ||||
|  | @ -300,67 +361,6 @@ class LevelLine(pg.InfiniteLine): | |||
|         self.movable = True | ||||
|         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): | ||||
|         """Override the ``InfiniteLine`` handler since we need more | ||||
|         detailed control and start end signalling. | ||||
|  | @ -448,44 +448,6 @@ class LevelLine(pg.InfiniteLine): | |||
|         # TODO: enter labels edit mode | ||||
|         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( | ||||
|         self, | ||||
|     ) -> float: | ||||
|  | @ -493,24 +455,17 @@ class LevelLine(pg.InfiniteLine): | |||
|         chart = self._chart | ||||
|         l1_len = chart._max_l1_line_len | ||||
|         ryaxis = chart.getAxis('right') | ||||
|         up_to_l1_sc = ryaxis.pos().x() - l1_len | ||||
| 
 | ||||
|         if self.markers: | ||||
|             size = self.markers[0][2] | ||||
|         else: | ||||
|             size = 0 | ||||
|         # right_view_coords = chart._vb.mapToView( | ||||
|         #     Point(right_scene_coords, 0)).x() | ||||
| 
 | ||||
|         r_axis_x = ryaxis.pos().x() | ||||
|         right_offset = l1_len + size + 10 | ||||
|         right_scene_coords = r_axis_x - right_offset | ||||
| 
 | ||||
|         right_view_coords = chart._vb.mapToView( | ||||
|             Point(right_scene_coords, 0)).x() | ||||
| 
 | ||||
|         return ( | ||||
|             right_scene_coords, | ||||
|             right_view_coords, | ||||
|             right_offset, | ||||
|         ) | ||||
|         return up_to_l1_sc | ||||
|         # return ( | ||||
|         #     right_scene_coords, | ||||
|         #     right_view_coords, | ||||
|         #     right_offset, | ||||
|         # ) | ||||
| 
 | ||||
|     def paint( | ||||
|         self, | ||||
|  | @ -529,32 +484,79 @@ class LevelLine(pg.InfiniteLine): | |||
|         # pen.setJoinStyle(QtCore.Qt.MiterJoin) | ||||
|         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( | ||||
|             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 | ||||
|         # 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, | ||||
|                 vb_left, | ||||
|                 vb_right, | ||||
|                 rsc | ||||
|             ) | ||||
|     def scene_right_xy(self) -> QPointF: | ||||
|         return self.getViewBox().mapFromView( | ||||
|             QPointF(0, self.value()) | ||||
|         ) | ||||
| 
 | ||||
|     def scene_y(self) -> float: | ||||
|         return self.getViewBox().mapFromView(Point(0, self.value())).y() | ||||
|  | @ -570,22 +572,79 @@ class LevelLine(pg.InfiniteLine): | |||
| 
 | ||||
|         self._marker = path | ||||
| 
 | ||||
|         rsc, rvc, rosc = self.right_point() | ||||
|         rsc = 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())) | ||||
| 
 | ||||
|         # self.update() | ||||
| 
 | ||||
|     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): | ||||
|             self.set_mouser_hover(True) | ||||
|         else: | ||||
|             self.set_mouser_hover(False) | ||||
| 
 | ||||
|             # if already hovered we don't need to run again | ||||
|             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( | ||||
|  | @ -698,21 +757,25 @@ def order_line( | |||
|             '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)) | ||||
|         # this fixes it the artifact issue! .. of course, bouding rect stuff | ||||
|         line._maxMarkerSize = marker_size | ||||
| 
 | ||||
|         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, | ||||
|             # the "position" here is now ignored since we modified | ||||
|             # 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' | ||||
| 
 | ||||
|  | @ -733,26 +796,11 @@ def order_line( | |||
|         llabel.render() | ||||
|         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: | ||||
|         # left side label | ||||
|         llabel = line.add_label( | ||||
|             side='left', | ||||
|             fmt_str='{exec_type}-{order_type}: ${$value}', | ||||
|             fmt_str=' {exec_type}-{order_type}:\n ${$value}', | ||||
|         ) | ||||
|         llabel.fields = { | ||||
|             'order_type': order_type, | ||||
|  | @ -782,24 +830,6 @@ def order_line( | |||
|         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}) | ||||
| 
 | ||||
|  | @ -839,7 +869,7 @@ def position_line( | |||
| 
 | ||||
|     rlabel = line.add_label( | ||||
|         side='left', | ||||
|         fmt_str='{direction}: {size}\n${$:.2f}', | ||||
|         fmt_str='{direction}: {size} -> ${$:.2f}', | ||||
|     ) | ||||
|     rlabel.fields = { | ||||
|         'direction': 'long' if size > 0 else 'short', | ||||
|  | @ -850,6 +880,8 @@ def position_line( | |||
|     rlabel.render() | ||||
|     rlabel.show() | ||||
| 
 | ||||
|     line.set_level(level) | ||||
| 
 | ||||
|     # sanity check | ||||
|     line.update_labels({'level': level}) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue