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,9 +22,87 @@ 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
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 # 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... # we're basically only using the pen setting now...
super().__init__(*args, **kwargs) 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._xrange: Tuple[int, int] = self.dataBounds(ax=0)
self._step_mode: bool = step_mode self._step_mode: bool = step_mode
self.setBrush(hcolor('bracket'))
breakpoint()
# TODO: one question still remaining is if this makes trasform # TODO: one question still remaining is if this makes trasform
# interactions slower (such as zooming) and if so maybe if/when # interactions slower (such as zooming) and if so maybe if/when
# we implement a "history" mode for the view we disable this in # we implement a "history" mode for the view we disable this in
@ -53,8 +134,9 @@ class FastAppendCurve(pg.PlotCurveItem):
def update_from_array( def update_from_array(
self, self,
x, x: np.ndarray,
y, y: np.ndarray,
) -> QtGui.QPainterPath: ) -> QtGui.QPainterPath:
profiler = pg.debug.Profiler(disabled=not pg_profile_enabled()) profiler = pg.debug.Profiler(disabled=not pg_profile_enabled())
@ -69,37 +151,7 @@ class FastAppendCurve(pg.PlotCurveItem):
# step mode: draw flat top discrete "step" # step mode: draw flat top discrete "step"
# over the index space for each datum. # over the index space for each datum.
if self._step_mode: if self._step_mode:
y_out = y.copy() x_out, y_out = step_path_arrays_from_1d(x[:-1], y[:-1])
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
# 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,8 +275,19 @@ class FastAppendCurve(pg.PlotCurveItem):
# p.setRenderHint(p.Antialiasing, True) # p.setRenderHint(p.Antialiasing, True)
p.setPen(self.opts['pen']) p.setPen(self.opts['pen'])
p.drawLine(self._last_line)
profiler('.drawLine()')
p.drawPath(self.path) if self._step_mode:
profiler('.drawPath()') 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()')