Add last step updates and path fill support

fast_step_curve
Tyler Goodlet 2021-09-19 15:56:02 -04:00
parent e4e1b4d64a
commit c378a56b29
1 changed files with 115 additions and 44 deletions

View File

@ -22,53 +22,27 @@ from typing import Tuple
import numpy as np import numpy as np
import pyqtgraph as pg 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 .._profile import pg_profile_enabled
from ._style import hcolor
# TODO: got a feeling that dropping this inheritance gets us even more speedups def step_path_arrays_from_1d(
class FastAppendCurve(pg.PlotCurveItem): x: np.ndarray,
y: np.ndarray,
def __init__( ) -> (np.ndarray, np.ndarray):
self, '''Generate a "step mode" curve aligned with OHLC style bars
*args, such that each segment spans each bar (aka "centered" style).
step_mode: bool = False,
**kwargs
) -> None:
# 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)
self._step_mode: bool = step_mode
# 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(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
def update_from_array(
self,
x,
y,
) -> QtGui.QPainterPath:
profiler = pg.debug.Profiler(disabled=not pg_profile_enabled())
flip_cache = False
# print(f"xrange: {self._xrange}")
istart, istop = self._xrange
prepend_length = istart - x[0]
append_length = x[-1] - istop
# step mode: draw flat top discrete "step"
# over the index space for each datum.
if self._step_mode:
y_out = y.copy() y_out = y.copy()
x_out = x.copy() x_out = x.copy()
x2 = np.empty( x2 = np.empty(
@ -94,12 +68,90 @@ class FastAppendCurve(pg.PlotCurveItem):
dtype=y.dtype dtype=y.dtype
) )
y2 = np.empty((len(y), 2), dtype=y.dtype) y2 = np.empty((len(y), 2), dtype=y.dtype)
y2[:] = y[:,np.newaxis] y2[:] = y[:, np.newaxis]
# flatten
# flatten and set 0 endpoints
y_out[1:-1] = y2.reshape(y2.size) y_out[1:-1] = y2.reshape(y2.size)
y_out[0] = 0 y_out[0] = 0
y_out[-1] = 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
class FastAppendCurve(pg.PlotCurveItem):
def __init__(
self,
*args,
step_mode: bool = False,
**kwargs
) -> None:
# 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: 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
# that mode?
self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)
def update_from_array(
self,
x: np.ndarray,
y: np.ndarray,
) -> QtGui.QPainterPath:
profiler = pg.debug.Profiler(disabled=not pg_profile_enabled())
flip_cache = False
# print(f"xrange: {self._xrange}")
istart, istop = self._xrange
prepend_length = istart - x[0]
append_length = x[-1] - istop
# step mode: draw flat top discrete "step"
# over the index space for each datum.
if self._step_mode:
x_out, y_out = step_path_arrays_from_1d(x[:-1], y[:-1])
# TODO: see ``painter.fillPath()`` call # TODO: see ``painter.fillPath()`` call
# inside parent's ``.paint()`` to get # inside parent's ``.paint()`` to get
# a solid brush under the curve. # 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 # by default we only pull data up to the last (current) index
x_out, y_out = x[:-1], y[:-1] x_out, y_out = x[:-1], y[:-1]
if self.path is None or prepend_length: if self.path is None or prepend_length:
self.path = pg.functions.arrayToQPath( self.path = pg.functions.arrayToQPath(
x_out, x_out,
@ -139,6 +190,11 @@ class FastAppendCurve(pg.PlotCurveItem):
new_y = y[-append_length - 2:-1] new_y = y[-append_length - 2:-1]
# print((new_x, new_y)) # 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( append_path = pg.functions.arrayToQPath(
new_x, new_x,
new_y, new_y,
@ -148,6 +204,7 @@ class FastAppendCurve(pg.PlotCurveItem):
# self.path.moveTo(new_x[0], new_y[0]) # self.path.moveTo(new_x[0], new_y[0])
# self.path.connectPath(append_path) # self.path.connectPath(append_path)
self.path.connectPath(append_path) self.path.connectPath(append_path)
# self.fill_path.connectPath(
# XXX: pretty annoying but, without this there's little # XXX: pretty annoying but, without this there's little
# artefacts on the append updates to the curve... # artefacts on the append updates to the curve...
@ -163,7 +220,10 @@ class FastAppendCurve(pg.PlotCurveItem):
self.yData = y self.yData = y
self._xrange = x[0], x[-1] 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 # trigger redraw of path
# do update before reverting to cache mode # do update before reverting to cache mode
@ -193,13 +253,13 @@ class FastAppendCurve(pg.PlotCurveItem):
w = hb_size.width() + 1 w = hb_size.width() + 1
h = hb_size.height() + 1 h = hb_size.height() + 1
br = QtCore.QRectF( br = QRectF(
# top left # top left
QtCore.QPointF(hb.topLeft()), QPointF(hb.topLeft()),
# total size # total size
QtCore.QSizeF(w, h) QSizeF(w, h)
) )
# print(f'bounding rect: {br}') # print(f'bounding rect: {br}')
return br return br
@ -215,6 +275,17 @@ class FastAppendCurve(pg.PlotCurveItem):
# p.setRenderHint(p.Antialiasing, True) # p.setRenderHint(p.Antialiasing, True)
p.setPen(self.opts['pen']) p.setPen(self.opts['pen'])
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) p.drawLine(self._last_line)
profiler('.drawLine()') profiler('.drawLine()')