Add an experimental "fast appendable curve" graphic
							parent
							
								
									93e76fa12c
								
							
						
					
					
						commit
						588213a230
					
				| 
						 | 
					@ -339,6 +339,133 @@ class LinkedSplitCharts(QtGui.QWidget):
 | 
				
			||||||
        return cpw
 | 
					        return cpw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FastAppendCurve(pg.PlotCurveItem):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: we can probably just dispense with the parent since
 | 
				
			||||||
 | 
					        # we're basically only using the pen setting now...
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._last_line: QtCore.QLineF = None
 | 
				
			||||||
 | 
					        self._xrange: Tuple[int, int] = self.dataBounds(ax=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: one question still remaining is if this makes trasform
 | 
				
			||||||
 | 
					        # interactions slower (such as zooming) and if so maybe if/when
 | 
				
			||||||
 | 
					        # we implement a "history" mode for the view we disable this in
 | 
				
			||||||
 | 
					        # that mode?
 | 
				
			||||||
 | 
					        self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_from_array(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        x,
 | 
				
			||||||
 | 
					        y,
 | 
				
			||||||
 | 
					    ) -> QtGui.QPainterPath:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        profiler = pg.debug.Profiler(disabled=True)
 | 
				
			||||||
 | 
					        flip_cache = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # print(f"xrange: {self._xrange}")
 | 
				
			||||||
 | 
					        istart, istop = self._xrange
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        prepend_length = istart - x[0]
 | 
				
			||||||
 | 
					        append_length = x[-1] - istop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.path is None or prepend_length:
 | 
				
			||||||
 | 
					            self.path = pg.functions.arrayToQPath(
 | 
				
			||||||
 | 
					                x[:-1],
 | 
				
			||||||
 | 
					                y[:-1],
 | 
				
			||||||
 | 
					                connect='all'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            profiler('generate fresh path')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: get this working - right now it's giving heck on vwap...
 | 
				
			||||||
 | 
					        # if prepend_length:
 | 
				
			||||||
 | 
					        #     breakpoint()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #     prepend_path = pg.functions.arrayToQPath(
 | 
				
			||||||
 | 
					        #         x[0:prepend_length],
 | 
				
			||||||
 | 
					        #         y[0:prepend_length],
 | 
				
			||||||
 | 
					        #         connect='all'
 | 
				
			||||||
 | 
					        #     )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #     # swap prepend path in "front"
 | 
				
			||||||
 | 
					        #     old_path = self.path
 | 
				
			||||||
 | 
					        #     self.path = prepend_path
 | 
				
			||||||
 | 
					        #     # self.path.moveTo(new_x[0], new_y[0])
 | 
				
			||||||
 | 
					        #     self.path.connectPath(old_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if append_length:
 | 
				
			||||||
 | 
					            # print(f"append_length: {append_length}")
 | 
				
			||||||
 | 
					            new_x = x[-append_length - 2:-1]
 | 
				
			||||||
 | 
					            new_y = y[-append_length - 2:-1]
 | 
				
			||||||
 | 
					            # print((new_x, new_y))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            append_path = pg.functions.arrayToQPath(
 | 
				
			||||||
 | 
					                new_x,
 | 
				
			||||||
 | 
					                new_y,
 | 
				
			||||||
 | 
					                connect='all'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            # print(f"append_path br: {append_path.boundingRect()}")
 | 
				
			||||||
 | 
					            # self.path.moveTo(new_x[0], new_y[0])
 | 
				
			||||||
 | 
					            # self.path.connectPath(append_path)
 | 
				
			||||||
 | 
					            self.path.connectPath(append_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # XXX: pretty annoying but, without this there's little
 | 
				
			||||||
 | 
					            # artefacts on the append updates to the curve...
 | 
				
			||||||
 | 
					            self.setCacheMode(QtGui.QGraphicsItem.NoCache)
 | 
				
			||||||
 | 
					            self.prepareGeometryChange()
 | 
				
			||||||
 | 
					            flip_cache = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # print(f"update br: {self.path.boundingRect()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # XXX: lol brutal, the internals of `CurvePoint` (inherited by
 | 
				
			||||||
 | 
					        # our `LineDot`) required ``.getData()`` to work..
 | 
				
			||||||
 | 
					        self.xData = x
 | 
				
			||||||
 | 
					        self.yData = y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._xrange = x[0], x[-1]
 | 
				
			||||||
 | 
					        self._last_line = QtCore.QLineF(x[-2], y[-2], x[-1], y[-1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # trigger redraw of path
 | 
				
			||||||
 | 
					        # do update before reverting to cache mode
 | 
				
			||||||
 | 
					        self.prepareGeometryChange()
 | 
				
			||||||
 | 
					        self.update()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if flip_cache:
 | 
				
			||||||
 | 
					            self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def boundingRect(self):
 | 
				
			||||||
 | 
					        hb = self.path.controlPointRect()
 | 
				
			||||||
 | 
					        hb_size = hb.size()
 | 
				
			||||||
 | 
					        # print(f'hb_size: {hb_size}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        w = hb_size.width() + 1
 | 
				
			||||||
 | 
					        h = hb_size.height() + 1
 | 
				
			||||||
 | 
					        br = QtCore.QRectF(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # top left
 | 
				
			||||||
 | 
					            QtCore.QPointF(hb.topLeft()),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # total size
 | 
				
			||||||
 | 
					            QtCore.QSizeF(w, h)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        # print(f'bounding rect: {br}')
 | 
				
			||||||
 | 
					        return br
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def paint(self, p, opt, widget):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        profiler = pg.debug.Profiler(disabled=True)
 | 
				
			||||||
 | 
					        # p.setRenderHint(p.Antialiasing, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        p.setPen(self.opts['pen'])
 | 
				
			||||||
 | 
					        p.drawLine(self._last_line)
 | 
				
			||||||
 | 
					        profiler('.drawLine()')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        p.drawPath(self.path)
 | 
				
			||||||
 | 
					        profiler('.drawPath()')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ChartPlotWidget(pg.PlotWidget):
 | 
					class ChartPlotWidget(pg.PlotWidget):
 | 
				
			||||||
    """``GraphicsView`` subtype containing a single ``PlotItem``.
 | 
					    """``GraphicsView`` subtype containing a single ``PlotItem``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -550,8 +677,9 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        pdi_kwargs.update(_pdi_defaults)
 | 
					        pdi_kwargs.update(_pdi_defaults)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # curve = pg.PlotDataItem(
 | 
				
			||||||
        # curve = pg.PlotCurveItem(
 | 
					        # curve = pg.PlotCurveItem(
 | 
				
			||||||
        curve = pg.PlotDataItem(
 | 
					        curve = FastAppendCurve(
 | 
				
			||||||
            y=data[name],
 | 
					            y=data[name],
 | 
				
			||||||
            x=data['index'],
 | 
					            x=data['index'],
 | 
				
			||||||
            # antialias=True,
 | 
					            # antialias=True,
 | 
				
			||||||
| 
						 | 
					@ -663,6 +791,7 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
				
			||||||
        """Update the named internal graphics from ``array``.
 | 
					        """Update the named internal graphics from ``array``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if name not in self._overlays:
 | 
					        if name not in self._overlays:
 | 
				
			||||||
            self._ohlc = array
 | 
					            self._ohlc = array
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -670,11 +799,13 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        curve = self._graphics[name]
 | 
					        curve = self._graphics[name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if len(array):
 | 
				
			||||||
            # TODO: we should instead implement a diff based
 | 
					            # TODO: we should instead implement a diff based
 | 
				
			||||||
            # "only update with new items" on the pg.PlotCurveItem
 | 
					            # "only update with new items" on the pg.PlotCurveItem
 | 
				
			||||||
            # one place to dig around this might be the `QBackingStore`
 | 
					            # one place to dig around this might be the `QBackingStore`
 | 
				
			||||||
            # https://doc.qt.io/qt-5/qbackingstore.html
 | 
					            # https://doc.qt.io/qt-5/qbackingstore.html
 | 
				
			||||||
        curve.setData(y=array[name], x=array['index'], **kwargs)
 | 
					            # curve.setData(y=array[name], x=array['index'], **kwargs)
 | 
				
			||||||
 | 
					            curve.update_from_array(x=array['index'], y=array[name], **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return curve
 | 
					        return curve
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1061,6 +1192,7 @@ async def chart_from_quotes(
 | 
				
			||||||
                    mn_in_view = min(price, mn_in_view)
 | 
					                    mn_in_view = min(price, mn_in_view)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if mx_in_view > last_mx or mn_in_view < last_mn:
 | 
					                if mx_in_view > last_mx or mn_in_view < last_mn:
 | 
				
			||||||
 | 
					                    print('scaling')
 | 
				
			||||||
                    chart._set_yrange(yrange=(mn_in_view, mx_in_view))
 | 
					                    chart._set_yrange(yrange=(mn_in_view, mx_in_view))
 | 
				
			||||||
                    last_mx, last_mn = mx_in_view, mn_in_view
 | 
					                    last_mx, last_mn = mx_in_view, mn_in_view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue