Add an experimental "fast appendable curve" graphic
							parent
							
								
									93e76fa12c
								
							
						
					
					
						commit
						588213a230
					
				| 
						 | 
				
			
			@ -339,6 +339,133 @@ class LinkedSplitCharts(QtGui.QWidget):
 | 
			
		|||
        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):
 | 
			
		||||
    """``GraphicsView`` subtype containing a single ``PlotItem``.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -550,8 +677,9 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
        }
 | 
			
		||||
        pdi_kwargs.update(_pdi_defaults)
 | 
			
		||||
 | 
			
		||||
        # curve = pg.PlotDataItem(
 | 
			
		||||
        # curve = pg.PlotCurveItem(
 | 
			
		||||
        curve = pg.PlotDataItem(
 | 
			
		||||
        curve = FastAppendCurve(
 | 
			
		||||
            y=data[name],
 | 
			
		||||
            x=data['index'],
 | 
			
		||||
            # antialias=True,
 | 
			
		||||
| 
						 | 
				
			
			@ -663,6 +791,7 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
        """Update the named internal graphics from ``array``.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if name not in self._overlays:
 | 
			
		||||
            self._ohlc = array
 | 
			
		||||
        else:
 | 
			
		||||
| 
						 | 
				
			
			@ -670,11 +799,13 @@ class ChartPlotWidget(pg.PlotWidget):
 | 
			
		|||
 | 
			
		||||
        curve = self._graphics[name]
 | 
			
		||||
 | 
			
		||||
        if len(array):
 | 
			
		||||
            # TODO: we should instead implement a diff based
 | 
			
		||||
            # "only update with new items" on the pg.PlotCurveItem
 | 
			
		||||
            # one place to dig around this might be the `QBackingStore`
 | 
			
		||||
            # 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1061,6 +1192,7 @@ async def chart_from_quotes(
 | 
			
		|||
                    mn_in_view = min(price, mn_in_view)
 | 
			
		||||
 | 
			
		||||
                if mx_in_view > last_mx or mn_in_view < last_mn:
 | 
			
		||||
                    print('scaling')
 | 
			
		||||
                    chart._set_yrange(yrange=(mn_in_view, mx_in_view))
 | 
			
		||||
                    last_mx, last_mn = mx_in_view, mn_in_view
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue