Add basic optional polyline support, draft out downsampling routine

m4_corrections
Tyler Goodlet 2022-03-09 11:07:53 -05:00
parent da5d2ef331
commit 93d2c715e7
1 changed files with 125 additions and 37 deletions

View File

@ -23,6 +23,7 @@ from typing import Optional
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5 import QtGui, QtWidgets from PyQt5 import QtGui, QtWidgets
from PyQt5.QtWidgets import QGraphicsItem
from PyQt5.QtCore import ( from PyQt5.QtCore import (
Qt, Qt,
QLineF, QLineF,
@ -94,6 +95,38 @@ _line_styles: dict[str, int] = {
} }
def downsample(
x: np.ndarray,
y: np.ndarray,
bins: int,
method: str = 'peak',
) -> tuple[np.ndarray, np.ndarray]:
'''
Downsample x/y data for lesser curve graphics gen.
The "peak" method is originally copied verbatim from
``pyqtgraph.PlotDataItem.getDisplayDataset()``.
'''
match method:
case 'peak':
ds = bins
n = len(x) // ds
x1 = np.empty((n, 2))
# start of x-values; try to select a somewhat centered point
stx = ds//2
x1[:] = x[stx:stx+n*ds:ds, np.newaxis]
x = x1.reshape(n*2)
y1 = np.empty((n, 2))
y2 = y[:n*ds].reshape((n, ds))
y1[:, 0] = y2.max(axis=1)
y1[:, 1] = y2.min(axis=1)
y = y1.reshape(n*2)
return x, y
# 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
class FastAppendCurve(pg.PlotCurveItem): class FastAppendCurve(pg.PlotCurveItem):
''' '''
@ -116,17 +149,23 @@ class FastAppendCurve(pg.PlotCurveItem):
fill_color: Optional[str] = None, fill_color: Optional[str] = None,
style: str = 'solid', style: str = 'solid',
name: Optional[str] = None, name: Optional[str] = None,
use_polyline: bool = False,
**kwargs **kwargs
) -> None: ) -> None:
self._name = name
# TODO: we can probably just dispense with the parent since # TODO: we can probably just dispense with the parent since
# 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._name = name
self._xrange: tuple[int, int] = self.dataBounds(ax=0) self._xrange: tuple[int, int] = self.dataBounds(ax=0)
# self._last_draw = time.time()
self._use_poly = use_polyline
self.poly = None
# all history of curve is drawn in single px thickness # all history of curve is drawn in single px thickness
pen = pg.mkPen(hcolor(color)) pen = pg.mkPen(hcolor(color))
pen.setStyle(_line_styles[style]) pen.setStyle(_line_styles[style])
@ -153,12 +192,14 @@ class FastAppendCurve(pg.PlotCurveItem):
# 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
# that mode? # that mode?
if step_mode: if step_mode or self._use_poly:
# don't enable caching by default for the case where the # don't enable caching by default for the case where the
# only thing drawn is the "last" line segment which can # only thing drawn is the "last" line segment which can
# have a weird artifact where it won't be fully drawn to its # have a weird artifact where it won't be fully drawn to its
# endpoint (something we saw on trade rate curves) # endpoint (something we saw on trade rate curves)
self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) self.setCacheMode(
QGraphicsItem.DeviceCoordinateCache
)
def update_from_array( def update_from_array(
self, self,
@ -195,12 +236,20 @@ class FastAppendCurve(pg.PlotCurveItem):
x_out, y_out = x[:-1], y[:-1] x_out, y_out = x[:-1], y[:-1]
if self.path is None or prepend_length > 0: if self.path is None or prepend_length > 0:
self.path = pg.functions.arrayToQPath(
x_out, if self._use_poly:
y_out, self.poly = pg.functions.arrayToQPolygonF(
connect='all', x_out,
finiteCheck=False, y_out,
) )
else:
self.path = pg.functions.arrayToQPath(
x_out,
y_out,
connect='all',
finiteCheck=False,
)
profiler('generate fresh path') profiler('generate fresh path')
# if self._step_mode: # if self._step_mode:
@ -242,18 +291,27 @@ 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))
append_path = pg.functions.arrayToQPath( if self._use_poly:
new_x, union_poly = pg.functions.arrayToQPolygonF(
new_y, new_x,
connect='all', new_y,
# finiteCheck=False, )
)
else:
append_path = pg.functions.arrayToQPath(
new_x,
new_y,
connect='all',
finiteCheck=False,
)
path = self.path path = self.path
# other merging ideas: # other merging ideas:
# https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths # https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths
if self._step_mode: if self._step_mode:
assert not self._use_poly, 'Dunno howw this worx yet'
# path.addPath(append_path) # path.addPath(append_path)
self.path.connectPath(append_path) self.path.connectPath(append_path)
@ -269,19 +327,26 @@ class FastAppendCurve(pg.PlotCurveItem):
# # path.closeSubpath() # # path.closeSubpath()
else: else:
# print(f"append_path br: {append_path.boundingRect()}") if self._use_poly:
# self.path.moveTo(new_x[0], new_y[0]) self.poly = self.poly.united(union_poly)
# self.path.connectPath(append_path) else:
path.connectPath(append_path) # print(f"append_path br: {append_path.boundingRect()}")
# self.path.moveTo(new_x[0], new_y[0])
self.path.connectPath(append_path)
# path.connectPath(append_path)
# XXX: lol this causes a hang..
# self.path = self.path.simplified()
self.disable_cache() self.disable_cache()
flip_cache = True flip_cache = True
if ( # XXX: do we need this any more?
self._step_mode # if (
): # self._step_mode
self.disable_cache() # ):
flip_cache = True # self.disable_cache()
# flip_cache = True
# print(f"update br: {self.path.boundingRect()}") # print(f"update br: {self.path.boundingRect()}")
@ -318,7 +383,7 @@ class FastAppendCurve(pg.PlotCurveItem):
if flip_cache: if flip_cache:
# XXX: seems to be needed to avoid artifacts (see above). # XXX: seems to be needed to avoid artifacts (see above).
self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
def disable_cache(self) -> None: def disable_cache(self) -> None:
''' '''
@ -334,15 +399,22 @@ class FastAppendCurve(pg.PlotCurveItem):
''' '''
Compute and then cache our rect. Compute and then cache our rect.
''' '''
if self.path is None: if self._use_poly:
return QtGui.QPainterPath().boundingRect() if self.poly is None:
return QtGui.QPolygonF().boundingRect()
else:
br = self.boundingRect = self.poly.boundingRect
return br()
else: else:
# dynamically override this method after initial if self.path is None:
# path is created to avoid requiring the above None check return QtGui.QPainterPath().boundingRect()
self.boundingRect = self._br else:
return self._br() # dynamically override this method after initial
# path is created to avoid requiring the above None check
self.boundingRect = self._path_br
return self._path_br()
def _br(self): def _path_br(self):
''' '''
Post init ``.boundingRect()```. Post init ``.boundingRect()```.
@ -373,7 +445,9 @@ class FastAppendCurve(pg.PlotCurveItem):
) -> None: ) -> None:
profiler = pg.debug.Profiler(disabled=not pg_profile_enabled()) profiler = pg.debug.Profiler(
# disabled=False, #not pg_profile_enabled(),
)
# p.setRenderHint(p.Antialiasing, True) # p.setRenderHint(p.Antialiasing, True)
if ( if (
@ -395,12 +469,26 @@ class FastAppendCurve(pg.PlotCurveItem):
p.setPen(self.opts['pen']) p.setPen(self.opts['pen'])
# else: # else:
p.drawPath(self.path) if self._use_poly:
profiler('.drawPath()') assert self.poly
p.drawPolyline(self.poly)
profiler('.drawPolyline()')
else:
p.drawPath(self.path)
profiler('.drawPath()')
# TODO: try out new work from `pyqtgraph` main which # TODO: try out new work from `pyqtgraph` main which should
# should repair horrid perf: # repair horrid perf (pretty sure i did and it was still
# horrible?):
# https://github.com/pyqtgraph/pyqtgraph/pull/2032 # https://github.com/pyqtgraph/pyqtgraph/pull/2032
# if self._fill: # if self._fill:
# brush = self.opts['brush'] # brush = self.opts['brush']
# p.fillPath(self.path, brush) # p.fillPath(self.path, brush)
# now = time.time()
# print(f'DRAW RATE {1/(now - self._last_draw)}')
# self._last_draw = now
# import time
# _last_draw: float = time.time()