Add last step updates and path fill support
parent
e4e1b4d64a
commit
c378a56b29
|
@ -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()')
|
||||||
|
|
Loading…
Reference in New Issue