Add last step updates and path fill support
							parent
							
								
									c82046d59b
								
							
						
					
					
						commit
						d6d284fec5
					
				| 
						 | 
				
			
			@ -22,9 +22,87 @@ from typing import Tuple
 | 
			
		|||
 | 
			
		||||
import numpy as np
 | 
			
		||||
import pyqtgraph as pg
 | 
			
		||||
from PyQt5 import QtCore, QtGui, QtWidgets
 | 
			
		||||
from PyQt5 import QtGui, QtWidgets
 | 
			
		||||
from PyQt5.QtCore import (
 | 
			
		||||
    QLineF,
 | 
			
		||||
    QSizeF,
 | 
			
		||||
    QRectF,
 | 
			
		||||
    QPointF,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .._profile import pg_profile_enabled
 | 
			
		||||
from ._style import hcolor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def step_path_arrays_from_1d(
 | 
			
		||||
    x: np.ndarray,
 | 
			
		||||
    y: np.ndarray,
 | 
			
		||||
 | 
			
		||||
) -> (np.ndarray, np.ndarray):
 | 
			
		||||
    '''Generate a "step mode" curve aligned with OHLC style bars
 | 
			
		||||
    such that each segment spans each bar (aka "centered" style).
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    y_out = y.copy()
 | 
			
		||||
    x_out = x.copy()
 | 
			
		||||
    x2 = np.empty(
 | 
			
		||||
        # the data + 2 endpoints on either end for
 | 
			
		||||
        # "termination of the path".
 | 
			
		||||
        (len(x) + 1, 2),
 | 
			
		||||
        # we want to align with OHLC or other sampling style
 | 
			
		||||
        # bars likely so we need fractinal values
 | 
			
		||||
        dtype=float,
 | 
			
		||||
    )
 | 
			
		||||
    x2[0] = x[0] - 0.5
 | 
			
		||||
    x2[1] = x[0] + 0.5
 | 
			
		||||
    x2[1:] = x[:, np.newaxis] + 0.5
 | 
			
		||||
 | 
			
		||||
    # flatten to 1-d
 | 
			
		||||
    x_out = x2.reshape(x2.size)
 | 
			
		||||
 | 
			
		||||
    # we create a 1d with 2 extra indexes to
 | 
			
		||||
    # hold the start and (current) end value for the steps
 | 
			
		||||
    # on either end
 | 
			
		||||
    y_out = np.empty(
 | 
			
		||||
        2*len(y) + 2,
 | 
			
		||||
        dtype=y.dtype
 | 
			
		||||
    )
 | 
			
		||||
    y2 = np.empty((len(y), 2), dtype=y.dtype)
 | 
			
		||||
    y2[:] = y[:, np.newaxis]
 | 
			
		||||
 | 
			
		||||
    # flatten and set 0 endpoints
 | 
			
		||||
    y_out[1:-1] = y2.reshape(y2.size)
 | 
			
		||||
    y_out[0] = 0
 | 
			
		||||
    y_out[-1] = 0
 | 
			
		||||
 | 
			
		||||
    return x_out, y_out
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def step_lines_from_point(
 | 
			
		||||
    index: float,
 | 
			
		||||
    level: float,
 | 
			
		||||
 | 
			
		||||
) -> Tuple[QLineF]:
 | 
			
		||||
 | 
			
		||||
    # TODO: maybe consider using `QGraphicsLineItem` ??
 | 
			
		||||
    # gives us a ``.boundingRect()`` on the objects which may make
 | 
			
		||||
    # computing the composite bounding rect of the last bars + the
 | 
			
		||||
    # history path faster since it's done in C++:
 | 
			
		||||
    # https://doc.qt.io/qt-5/qgraphicslineitem.html
 | 
			
		||||
 | 
			
		||||
    # index = x[0]
 | 
			
		||||
    # level = y[0]
 | 
			
		||||
 | 
			
		||||
    # (x0 - 0.5, 0) -> (x0 - 0.5, y0)
 | 
			
		||||
    left = QLineF(index - 0.5, 0, index - 0.5, level)
 | 
			
		||||
 | 
			
		||||
    # (x0 - 0.5, y0) -> (x1 + 0.5, y1)
 | 
			
		||||
    top = QLineF(index - 0.5, level, index + 0.5, level)
 | 
			
		||||
 | 
			
		||||
    # (x1 + 0.5, y1 -> (x1 + 0.5, 0)
 | 
			
		||||
    right = QLineF(index + 0.5, level, index + 0.5, 0)
 | 
			
		||||
 | 
			
		||||
    return [left, top, right]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: got a feeling that dropping this inheritance gets us even more speedups
 | 
			
		||||
| 
						 | 
				
			
			@ -41,10 +119,13 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
        # we're basically only using the pen setting now...
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        self._last_line: QtCore.QLineF = None
 | 
			
		||||
        self._last_line: QLineF = None
 | 
			
		||||
        self._xrange: Tuple[int, int] = self.dataBounds(ax=0)
 | 
			
		||||
        self._step_mode: bool = step_mode
 | 
			
		||||
 | 
			
		||||
        self.setBrush(hcolor('bracket'))
 | 
			
		||||
 | 
			
		||||
        breakpoint()
 | 
			
		||||
        # 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
 | 
			
		||||
| 
						 | 
				
			
			@ -53,8 +134,9 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
 | 
			
		||||
    def update_from_array(
 | 
			
		||||
        self,
 | 
			
		||||
        x,
 | 
			
		||||
        y,
 | 
			
		||||
        x: np.ndarray,
 | 
			
		||||
        y: np.ndarray,
 | 
			
		||||
 | 
			
		||||
    ) -> QtGui.QPainterPath:
 | 
			
		||||
 | 
			
		||||
        profiler = pg.debug.Profiler(disabled=not pg_profile_enabled())
 | 
			
		||||
| 
						 | 
				
			
			@ -69,37 +151,7 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
        # step mode: draw flat top discrete "step"
 | 
			
		||||
        # over the index space for each datum.
 | 
			
		||||
        if self._step_mode:
 | 
			
		||||
            y_out = y.copy()
 | 
			
		||||
            x_out = x.copy()
 | 
			
		||||
            x2 = np.empty(
 | 
			
		||||
                # the data + 2 endpoints on either end for
 | 
			
		||||
                # "termination of the path".
 | 
			
		||||
                (len(x) + 1, 2),
 | 
			
		||||
                # we want to align with OHLC or other sampling style
 | 
			
		||||
                # bars likely so we need fractinal values
 | 
			
		||||
                dtype=float,
 | 
			
		||||
            )
 | 
			
		||||
            x2[0] = x[0] - 0.5
 | 
			
		||||
            x2[1] = x[0] + 0.5
 | 
			
		||||
            x2[1:] = x[:, np.newaxis] + 0.5
 | 
			
		||||
 | 
			
		||||
            # flatten to 1-d
 | 
			
		||||
            x_out = x2.reshape(x2.size)
 | 
			
		||||
 | 
			
		||||
            # we create a 1d with 2 extra indexes to
 | 
			
		||||
            # hold the start and (current) end value for the steps
 | 
			
		||||
            # on either end
 | 
			
		||||
            y_out = np.empty(
 | 
			
		||||
                2*len(y) + 2,
 | 
			
		||||
                dtype=y.dtype
 | 
			
		||||
            )
 | 
			
		||||
            y2 = np.empty((len(y), 2), dtype=y.dtype)
 | 
			
		||||
            y2[:] = y[:,np.newaxis]
 | 
			
		||||
            # flatten 
 | 
			
		||||
            y_out[1:-1] = y2.reshape(y2.size)
 | 
			
		||||
            y_out[0] = 0
 | 
			
		||||
            y_out[-1] = 0
 | 
			
		||||
 | 
			
		||||
            x_out, y_out = step_path_arrays_from_1d(x[:-1], y[:-1])
 | 
			
		||||
            # TODO: see ``painter.fillPath()`` call
 | 
			
		||||
            # inside parent's ``.paint()`` to get
 | 
			
		||||
            # a solid brush under the curve.
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +160,6 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
            # by default we only pull data up to the last (current) index
 | 
			
		||||
            x_out, y_out = x[:-1], y[:-1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if self.path is None or prepend_length:
 | 
			
		||||
            self.path = pg.functions.arrayToQPath(
 | 
			
		||||
                x_out,
 | 
			
		||||
| 
						 | 
				
			
			@ -139,6 +190,11 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
            new_y = y[-append_length - 2:-1]
 | 
			
		||||
            # print((new_x, new_y))
 | 
			
		||||
 | 
			
		||||
            if self._step_mode:
 | 
			
		||||
                new_x, new_y = step_path_arrays_from_1d(new_x, new_y)
 | 
			
		||||
                new_x = new_x[2:]
 | 
			
		||||
                new_y = new_y[2:]
 | 
			
		||||
 | 
			
		||||
            append_path = pg.functions.arrayToQPath(
 | 
			
		||||
                new_x,
 | 
			
		||||
                new_y,
 | 
			
		||||
| 
						 | 
				
			
			@ -148,6 +204,7 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
            # self.path.moveTo(new_x[0], new_y[0])
 | 
			
		||||
            # self.path.connectPath(append_path)
 | 
			
		||||
            self.path.connectPath(append_path)
 | 
			
		||||
            # self.fill_path.connectPath(
 | 
			
		||||
 | 
			
		||||
            # XXX: pretty annoying but, without this there's little
 | 
			
		||||
            # artefacts on the append updates to the curve...
 | 
			
		||||
| 
						 | 
				
			
			@ -163,7 +220,10 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
        self.yData = y
 | 
			
		||||
 | 
			
		||||
        self._xrange = x[0], x[-1]
 | 
			
		||||
        self._last_line = QtCore.QLineF(x[-2], y[-2], x[-1], y[-1])
 | 
			
		||||
        if self._step_mode:
 | 
			
		||||
            self._last_step_lines = step_lines_from_point(x[-1], y[-1])
 | 
			
		||||
        else:
 | 
			
		||||
            self._last_line = QLineF(x[-2], y[-2], x[-1], y[-1])
 | 
			
		||||
 | 
			
		||||
        # trigger redraw of path
 | 
			
		||||
        # do update before reverting to cache mode
 | 
			
		||||
| 
						 | 
				
			
			@ -193,13 +253,13 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
        w = hb_size.width() + 1
 | 
			
		||||
        h = hb_size.height() + 1
 | 
			
		||||
 | 
			
		||||
        br = QtCore.QRectF(
 | 
			
		||||
        br = QRectF(
 | 
			
		||||
 | 
			
		||||
            # top left
 | 
			
		||||
            QtCore.QPointF(hb.topLeft()),
 | 
			
		||||
            QPointF(hb.topLeft()),
 | 
			
		||||
 | 
			
		||||
            # total size
 | 
			
		||||
            QtCore.QSizeF(w, h)
 | 
			
		||||
            QSizeF(w, h)
 | 
			
		||||
        )
 | 
			
		||||
        # print(f'bounding rect: {br}')
 | 
			
		||||
        return br
 | 
			
		||||
| 
						 | 
				
			
			@ -215,8 +275,19 @@ class FastAppendCurve(pg.PlotCurveItem):
 | 
			
		|||
        # p.setRenderHint(p.Antialiasing, True)
 | 
			
		||||
 | 
			
		||||
        p.setPen(self.opts['pen'])
 | 
			
		||||
        p.drawLine(self._last_line)
 | 
			
		||||
        profiler('.drawLine()')
 | 
			
		||||
 | 
			
		||||
        p.drawPath(self.path)
 | 
			
		||||
        profiler('.drawPath()')
 | 
			
		||||
        if self._step_mode:
 | 
			
		||||
            p.drawLines(*tuple(filter(bool, self._last_step_lines)))
 | 
			
		||||
 | 
			
		||||
            # fill_path = QtGui.QPainterPath(self.path)
 | 
			
		||||
            self.path.closeSubpath()
 | 
			
		||||
            p.fillPath(self.path, self.opts['brush'])
 | 
			
		||||
            p.drawPath(self.path)
 | 
			
		||||
            profiler('.drawPath()')
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            p.drawLine(self._last_line)
 | 
			
		||||
            profiler('.drawLine()')
 | 
			
		||||
 | 
			
		||||
            p.drawPath(self.path)
 | 
			
		||||
            profiler('.drawPath()')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue