Add "native" downsampling to our `FastAppendCurve`
Build out an interface that makes it super easy to downsample curves using the m4 algorithm while keeping our incremental `QPainterPath` update feature. A lot of hard work and tinkering went into getting this working all in-thread correctly and there are quite a few details.. New interface methods: - `.x_uppx()` which returns the x-axis "view units per pixel" - `.px_width()` which returns the total (rounded) x-axis pixels spanned by the curve in view. - `.should_ds_or_redraw()` a predicate which checks internal state to see if either downsampling of the curve should take place, or the curve should have all downsampling removed and be redrawn with source array data. - `.downsample()` the actual ds processing routine which delegates into the m4 algo impl. - `.maybe_downsample()` a simple update method which can be called by the view box when the user changes the zoom level. Implementation details/changes: - make `.update_from_array()` check for downsample (or revert to source aka de-downsample) conditions exist and then downsample and re-draw path graphics accordingly. - in order to even further speed up path appends (since our main bottleneck is measured to be `QPainter.drawPath()` calls with large paths which are frequently updates), add a secondary path `.fast_path` which is the path that is real-time updates by incremental appends and which is painted separately for speed in `.pain()`. - drop all the `QPolyLine` stuff since it was tested to be much slower in general and especially so for append-updates. - stop disabling the cache settings on updates since it doesn't seem to be required any more? - more move toward deprecating and removing all lingering interface requirements from `pg.PlotCurveItem` (like `.xData`/`.yData`). - adjust `.paint()` and `.boundingRect()` to compensate for the new `.fast_path` - add a butt-load of profiling B)marketstore_backup
parent
e298fa2122
commit
ab58a05a11
|
@ -34,6 +34,14 @@ from PyQt5.QtCore import (
|
||||||
|
|
||||||
from .._profile import pg_profile_enabled
|
from .._profile import pg_profile_enabled
|
||||||
from ._style import hcolor
|
from ._style import hcolor
|
||||||
|
from ._compression import (
|
||||||
|
# ohlc_to_m4_line,
|
||||||
|
ds_m4,
|
||||||
|
)
|
||||||
|
from ..log import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def step_path_arrays_from_1d(
|
def step_path_arrays_from_1d(
|
||||||
|
@ -120,19 +128,22 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
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,
|
use_fpath: bool = True,
|
||||||
|
|
||||||
**kwargs
|
**kwargs
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
# brutaaalll, see comments within..
|
# brutaaalll, see comments within..
|
||||||
self.yData = y
|
self._y = self.yData = y
|
||||||
self.xData = x
|
self._x = self.xData = x
|
||||||
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self.path: Optional[QtGui.QPainterPath] = None
|
self.path: Optional[QtGui.QPainterPath] = None
|
||||||
|
|
||||||
|
self.use_fpath = use_fpath
|
||||||
|
self.fast_path: Optional[QtGui.QPainterPath] = None
|
||||||
|
|
||||||
# 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)
|
||||||
|
@ -141,9 +152,8 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
self._xrange: Optional[tuple[int, int]] = None
|
self._xrange: Optional[tuple[int, int]] = None
|
||||||
|
|
||||||
# self._last_draw = time.time()
|
# self._last_draw = time.time()
|
||||||
self._use_poly = use_polyline
|
self._in_ds: bool = False
|
||||||
self.poly = None
|
self._last_uppx: float = 0
|
||||||
self._redraw: bool = False
|
|
||||||
|
|
||||||
# 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))
|
||||||
|
@ -171,7 +181,7 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
# 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 or self._use_poly:
|
if step_mode:
|
||||||
# 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
|
||||||
|
@ -182,6 +192,107 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
def x_uppx(self) -> int:
|
||||||
|
|
||||||
|
px_vecs = self.pixelVectors()[0]
|
||||||
|
if px_vecs:
|
||||||
|
xs_in_px = px_vecs.x()
|
||||||
|
return round(xs_in_px)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def px_width(self) -> float:
|
||||||
|
|
||||||
|
vb = self.getViewBox()
|
||||||
|
if not vb:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
vr = self.viewRect()
|
||||||
|
l, r = int(vr.left()), int(vr.right())
|
||||||
|
|
||||||
|
start, stop = self._xrange
|
||||||
|
lbar = max(l, start)
|
||||||
|
rbar = min(r, stop)
|
||||||
|
|
||||||
|
return vb.mapViewToDevice(
|
||||||
|
QLineF(lbar, 0, rbar, 0)
|
||||||
|
).length()
|
||||||
|
|
||||||
|
def should_ds_or_redraw(
|
||||||
|
self,
|
||||||
|
|
||||||
|
) -> tuple[bool, bool]:
|
||||||
|
|
||||||
|
uppx = self.x_uppx()
|
||||||
|
px_width = self.px_width()
|
||||||
|
uppx_diff = abs(uppx - self._last_uppx)
|
||||||
|
self._last_uppx = uppx
|
||||||
|
|
||||||
|
should_redraw: bool = False
|
||||||
|
should_ds: bool = False
|
||||||
|
|
||||||
|
if (
|
||||||
|
uppx <= 4
|
||||||
|
):
|
||||||
|
# trigger redraw or original non-downsampled data
|
||||||
|
if self._in_ds:
|
||||||
|
print('REVERTING BACK TO SRC DATA')
|
||||||
|
# clear downsampled curve(s) and expect
|
||||||
|
# refresh of path segments.
|
||||||
|
should_redraw = True
|
||||||
|
|
||||||
|
elif (
|
||||||
|
uppx_diff >= 4
|
||||||
|
or self._step_mode and uppx_diff >= 1
|
||||||
|
):
|
||||||
|
log.info(
|
||||||
|
f'{self._name} downsampler change: {self._last_uppx} -> {uppx}'
|
||||||
|
)
|
||||||
|
should_ds = {'px_width': px_width, 'uppx': uppx}
|
||||||
|
should_redraw = True
|
||||||
|
|
||||||
|
return should_ds, should_redraw
|
||||||
|
|
||||||
|
def downsample(
|
||||||
|
self,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
px_width,
|
||||||
|
uppx,
|
||||||
|
|
||||||
|
) -> tuple[np.ndarray, np.ndarray]:
|
||||||
|
|
||||||
|
# downsample whenever more then 1 pixels per datum can be shown.
|
||||||
|
# always refresh data bounds until we get diffing
|
||||||
|
# working properly, see above..
|
||||||
|
bins, x, y = ds_m4(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
px_width=px_width,
|
||||||
|
uppx=uppx,
|
||||||
|
log_scale=bool(uppx)
|
||||||
|
)
|
||||||
|
x = np.broadcast_to(x[:, None], y.shape)
|
||||||
|
# x = (x + np.array([-0.43, 0, 0, 0.43])).flatten()
|
||||||
|
x = (x + np.array([-0.5, 0, 0, 0.5])).flatten()
|
||||||
|
y = y.flatten()
|
||||||
|
|
||||||
|
# presumably?
|
||||||
|
self._in_ds = True
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
def maybe_downsample(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Simple update call but with previously cached arrays data.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# print('DS CALLED FROM INTERACTION?')
|
||||||
|
# presume this is a so called "interaction update", see
|
||||||
|
# ``ChartView.maybe_downsample_graphics()``.
|
||||||
|
self.update_from_array(self._x, self._y)
|
||||||
|
|
||||||
def update_from_array(
|
def update_from_array(
|
||||||
self,
|
self,
|
||||||
x: np.ndarray,
|
x: np.ndarray,
|
||||||
|
@ -195,61 +306,84 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
a length diff.
|
a length diff.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
profiler = pg.debug.Profiler(disabled=not pg_profile_enabled())
|
profiler = pg.debug.Profiler(
|
||||||
|
msg=f'{self._name}.update_from_array()',
|
||||||
|
disabled=not pg_profile_enabled(),
|
||||||
|
)
|
||||||
flip_cache = False
|
flip_cache = False
|
||||||
|
|
||||||
if self._xrange:
|
if self._xrange:
|
||||||
istart, istop = self._xrange
|
istart, istop = self._xrange
|
||||||
else:
|
else:
|
||||||
istart, istop = x[0], x[-1]
|
self._xrange = istart, istop = x[0], x[-1]
|
||||||
|
|
||||||
# print(f"xrange: {self._xrange}")
|
# print(f"xrange: {self._xrange}")
|
||||||
|
|
||||||
|
should_ds, should_redraw = self.should_ds_or_redraw()
|
||||||
|
|
||||||
# compute the length diffs between the first/last index entry in
|
# compute the length diffs between the first/last index entry in
|
||||||
# the input data and the last indexes we have on record from the
|
# the input data and the last indexes we have on record from the
|
||||||
# last time we updated the curve index.
|
# last time we updated the curve index.
|
||||||
prepend_length = int(istart - x[0])
|
prepend_length = int(istart - x[0])
|
||||||
append_length = int(x[-1] - istop)
|
append_length = int(x[-1] - istop)
|
||||||
|
no_path_yet = self.path is None
|
||||||
# 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])
|
|
||||||
|
|
||||||
else:
|
|
||||||
# by default we only pull data up to the last (current) index
|
|
||||||
x_out, y_out = x[:-1], y[:-1]
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.path is None
|
should_redraw or should_ds
|
||||||
|
or self.path is None
|
||||||
or prepend_length > 0
|
or prepend_length > 0
|
||||||
or self._redraw
|
|
||||||
):
|
):
|
||||||
if self._use_poly:
|
# step mode: draw flat top discrete "step"
|
||||||
self.poly = pg.functions.arrayToQPolygonF(
|
# over the index space for each datum.
|
||||||
x_out,
|
if self._step_mode:
|
||||||
y_out,
|
x_out, y_out = step_path_arrays_from_1d(
|
||||||
|
x[:-1], y[:-1]
|
||||||
)
|
)
|
||||||
|
profiler('generated step arrays')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.path = pg.functions.arrayToQPath(
|
# by default we only pull data up to the last (current) index
|
||||||
|
x_out, y_out = x[:-1], y[:-1]
|
||||||
|
|
||||||
|
if should_ds:
|
||||||
|
x_out, y_out = self.downsample(
|
||||||
x_out,
|
x_out,
|
||||||
y_out,
|
y_out,
|
||||||
connect='all',
|
**should_ds,
|
||||||
finiteCheck=False,
|
|
||||||
path=self.path,
|
|
||||||
)
|
)
|
||||||
# reserve mem allocs see:
|
profiler(f'path downsample redraw={should_ds}')
|
||||||
# - https://doc.qt.io/qt-5/qpainterpath.html#reserve
|
self._in_ds = True
|
||||||
# - https://doc.qt.io/qt-5/qpainterpath.html#capacity
|
|
||||||
# - https://doc.qt.io/qt-5/qpainterpath.html#clear
|
if should_redraw:
|
||||||
# XXX: right now this is based on had hoc checks on a
|
profiler('path reversion to non-ds')
|
||||||
# hidpi 3840x2160 4k monitor but we should optimize for
|
if self.path:
|
||||||
# the target display(s) on the sys.
|
self.path.clear()
|
||||||
|
|
||||||
|
if self.fast_path:
|
||||||
|
self.fast_path.clear()
|
||||||
|
|
||||||
|
if should_redraw and not should_ds:
|
||||||
|
log.info(f'DEDOWN -> {self._name}')
|
||||||
|
self._in_ds = False
|
||||||
|
|
||||||
|
# else:
|
||||||
|
self.path = pg.functions.arrayToQPath(
|
||||||
|
x_out,
|
||||||
|
y_out,
|
||||||
|
connect='all',
|
||||||
|
finiteCheck=False,
|
||||||
|
path=self.path,
|
||||||
|
)
|
||||||
|
# reserve mem allocs see:
|
||||||
|
# - https://doc.qt.io/qt-5/qpainterpath.html#reserve
|
||||||
|
# - https://doc.qt.io/qt-5/qpainterpath.html#capacity
|
||||||
|
# - https://doc.qt.io/qt-5/qpainterpath.html#clear
|
||||||
|
# XXX: right now this is based on had hoc checks on a
|
||||||
|
# hidpi 3840x2160 4k monitor but we should optimize for
|
||||||
|
# the target display(s) on the sys.
|
||||||
|
if no_path_yet:
|
||||||
self.path.reserve(int(500e3))
|
self.path.reserve(int(500e3))
|
||||||
|
|
||||||
profiler('generate fresh path')
|
profiler('generated fresh path')
|
||||||
self._redraw = False
|
|
||||||
|
|
||||||
# if self._step_mode:
|
# if self._step_mode:
|
||||||
# self.path.closeSubpath()
|
# self.path.closeSubpath()
|
||||||
|
@ -271,7 +405,9 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
# # self.path.moveTo(new_x[0], new_y[0])
|
# # self.path.moveTo(new_x[0], new_y[0])
|
||||||
# self.path.connectPath(old_path)
|
# self.path.connectPath(old_path)
|
||||||
|
|
||||||
elif append_length > 0:
|
elif (
|
||||||
|
append_length > 0
|
||||||
|
):
|
||||||
if self._step_mode:
|
if self._step_mode:
|
||||||
new_x, new_y = step_path_arrays_from_1d(
|
new_x, new_y = step_path_arrays_from_1d(
|
||||||
x[-append_length - 2:-1],
|
x[-append_length - 2:-1],
|
||||||
|
@ -290,53 +426,60 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
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._use_poly:
|
profiler('diffed append arrays')
|
||||||
union_poly = pg.functions.arrayToQPolygonF(
|
|
||||||
|
if should_ds:
|
||||||
|
new_x, new_y = self.downsample(
|
||||||
new_x,
|
new_x,
|
||||||
new_y,
|
new_y,
|
||||||
|
**should_ds,
|
||||||
)
|
)
|
||||||
|
profiler(f'fast path downsample redraw={should_ds}')
|
||||||
|
|
||||||
else:
|
append_path = pg.functions.arrayToQPath(
|
||||||
append_path = pg.functions.arrayToQPath(
|
new_x,
|
||||||
new_x,
|
new_y,
|
||||||
new_y,
|
connect='all',
|
||||||
connect='all',
|
finiteCheck=False,
|
||||||
finiteCheck=False,
|
path=self.fast_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
# other merging ideas:
|
if self.use_fpath:
|
||||||
# https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths
|
# an attempt at trying to make append-updates faster..
|
||||||
if self._step_mode:
|
if self.fast_path is None:
|
||||||
assert not self._use_poly, 'Dunno howw this worx yet'
|
self.fast_path = append_path
|
||||||
|
self.fast_path.reserve(int(6e3))
|
||||||
# path.addPath(append_path)
|
|
||||||
self.path.connectPath(append_path)
|
|
||||||
|
|
||||||
# TODO: try out new work from `pyqtgraph` main which
|
|
||||||
# should repair horrid perf:
|
|
||||||
# https://github.com/pyqtgraph/pyqtgraph/pull/2032
|
|
||||||
# ok, nope still horrible XD
|
|
||||||
# if self._fill:
|
|
||||||
# # XXX: super slow set "union" op
|
|
||||||
# self.path = self.path.united(append_path).simplified()
|
|
||||||
|
|
||||||
# # path.addPath(append_path)
|
|
||||||
# # path.closeSubpath()
|
|
||||||
|
|
||||||
else:
|
|
||||||
if self._use_poly:
|
|
||||||
self.poly = self.poly.united(union_poly)
|
|
||||||
else:
|
else:
|
||||||
|
self.fast_path.connectPath(append_path)
|
||||||
|
size = self.fast_path.capacity()
|
||||||
|
profiler(f'connected fast path w size: {size}')
|
||||||
|
|
||||||
# print(f"append_path br: {append_path.boundingRect()}")
|
# print(f"append_path br: {append_path.boundingRect()}")
|
||||||
# self.path.moveTo(new_x[0], new_y[0])
|
# self.path.moveTo(new_x[0], new_y[0])
|
||||||
self.path.connectPath(append_path)
|
|
||||||
# path.connectPath(append_path)
|
# path.connectPath(append_path)
|
||||||
|
|
||||||
# XXX: lol this causes a hang..
|
# XXX: lol this causes a hang..
|
||||||
# self.path = self.path.simplified()
|
# self.path = self.path.simplified()
|
||||||
|
else:
|
||||||
|
size = self.path.capacity()
|
||||||
|
profiler(f'connected history path w size: {size}')
|
||||||
|
self.path.connectPath(append_path)
|
||||||
|
|
||||||
self.disable_cache()
|
# other merging ideas:
|
||||||
flip_cache = True
|
# https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths
|
||||||
|
# path.addPath(append_path)
|
||||||
|
# path.closeSubpath()
|
||||||
|
|
||||||
|
# TODO: try out new work from `pyqtgraph` main which
|
||||||
|
# should repair horrid perf:
|
||||||
|
# https://github.com/pyqtgraph/pyqtgraph/pull/2032
|
||||||
|
# ok, nope still horrible XD
|
||||||
|
# if self._fill:
|
||||||
|
# # XXX: super slow set "union" op
|
||||||
|
# self.path = self.path.united(append_path).simplified()
|
||||||
|
|
||||||
|
# self.disable_cache()
|
||||||
|
# flip_cache = True
|
||||||
|
|
||||||
# XXX: do we need this any more?
|
# XXX: do we need this any more?
|
||||||
# if (
|
# if (
|
||||||
|
@ -345,8 +488,6 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
# self.disable_cache()
|
# self.disable_cache()
|
||||||
# flip_cache = True
|
# flip_cache = True
|
||||||
|
|
||||||
# print(f"update br: {self.path.boundingRect()}")
|
|
||||||
|
|
||||||
# XXX: lol brutal, the internals of `CurvePoint` (inherited by
|
# XXX: lol brutal, the internals of `CurvePoint` (inherited by
|
||||||
# our `LineDot`) required ``.getData()`` to work..
|
# our `LineDot`) required ``.getData()`` to work..
|
||||||
self.xData = x
|
self.xData = x
|
||||||
|
@ -366,6 +507,11 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
x_last - 0.5, 0,
|
x_last - 0.5, 0,
|
||||||
x_last + 0.5, y_last
|
x_last + 0.5, y_last
|
||||||
)
|
)
|
||||||
|
# print(
|
||||||
|
# f"path br: {self.path.boundingRect()}",
|
||||||
|
# f"fast path br: {self.fast_path.boundingRect()}",
|
||||||
|
# f"last rect br: {self._last_step_rect}",
|
||||||
|
# )
|
||||||
else:
|
else:
|
||||||
# print((x[-1], y_last))
|
# print((x[-1], y_last))
|
||||||
self._last_line = QLineF(
|
self._last_line = QLineF(
|
||||||
|
@ -373,19 +519,28 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
x[-1], y_last
|
x[-1], y_last
|
||||||
)
|
)
|
||||||
|
|
||||||
|
profiler('draw last segment')
|
||||||
|
|
||||||
# trigger redraw of path
|
# trigger redraw of path
|
||||||
# do update before reverting to cache mode
|
# do update before reverting to cache mode
|
||||||
self.prepareGeometryChange()
|
# self.prepareGeometryChange()
|
||||||
self.update()
|
self.update()
|
||||||
|
profiler('.update()')
|
||||||
|
|
||||||
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(QGraphicsItem.DeviceCoordinateCache)
|
self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
|
||||||
|
|
||||||
|
self._x, self._y = x, y
|
||||||
|
|
||||||
# XXX: lol brutal, the internals of `CurvePoint` (inherited by
|
# XXX: lol brutal, the internals of `CurvePoint` (inherited by
|
||||||
# our `LineDot`) required ``.getData()`` to work..
|
# our `LineDot`) required ``.getData()`` to work..
|
||||||
def getData(self):
|
def getData(self):
|
||||||
return self.xData, self.yData
|
return self._x, self._y
|
||||||
|
|
||||||
|
# TODO: drop the above after ``Cursor`` re-work
|
||||||
|
def get_arrays(self) -> tuple[np.ndarray, np.ndarray]:
|
||||||
|
return self._x, self._y
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
'''
|
'''
|
||||||
|
@ -396,14 +551,20 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
self.xData = None
|
self.xData = None
|
||||||
self.yData = None
|
self.yData = None
|
||||||
|
|
||||||
|
# XXX: previously, if not trying to leverage `.reserve()` allocs
|
||||||
|
# then you might as well create a new one..
|
||||||
|
# self.path = None
|
||||||
|
|
||||||
# path reservation aware non-mem de-alloc cleaning
|
# path reservation aware non-mem de-alloc cleaning
|
||||||
if self.path:
|
if self.path:
|
||||||
self.path.clear()
|
self.path.clear()
|
||||||
self._redraw = True
|
|
||||||
|
|
||||||
# XXX: if not trying to leverage `.reserve()` allocs
|
if self.fast_path:
|
||||||
# then you might as well create a new one..
|
# self.fast_path.clear()
|
||||||
# self.path = None
|
self.fast_path = None
|
||||||
|
|
||||||
|
# self.disable_cache()
|
||||||
|
# self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
|
||||||
|
|
||||||
def disable_cache(self) -> None:
|
def disable_cache(self) -> None:
|
||||||
'''
|
'''
|
||||||
|
@ -419,20 +580,13 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
'''
|
'''
|
||||||
Compute and then cache our rect.
|
Compute and then cache our rect.
|
||||||
'''
|
'''
|
||||||
if self._use_poly:
|
if self.path is None:
|
||||||
if self.poly is None:
|
return QtGui.QPainterPath().boundingRect()
|
||||||
return QtGui.QPolygonF().boundingRect()
|
|
||||||
else:
|
|
||||||
br = self.boundingRect = self.poly.boundingRect
|
|
||||||
return br()
|
|
||||||
else:
|
else:
|
||||||
if self.path is None:
|
# dynamically override this method after initial
|
||||||
return QtGui.QPainterPath().boundingRect()
|
# path is created to avoid requiring the above None check
|
||||||
else:
|
self.boundingRect = self._path_br
|
||||||
# dynamically override this method after initial
|
return self._path_br()
|
||||||
# path is created to avoid requiring the above None check
|
|
||||||
self.boundingRect = self._path_br
|
|
||||||
return self._path_br()
|
|
||||||
|
|
||||||
def _path_br(self):
|
def _path_br(self):
|
||||||
'''
|
'''
|
||||||
|
@ -441,6 +595,11 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
'''
|
'''
|
||||||
hb = self.path.controlPointRect()
|
hb = self.path.controlPointRect()
|
||||||
hb_size = hb.size()
|
hb_size = hb.size()
|
||||||
|
|
||||||
|
fp = self.fast_path
|
||||||
|
if fp:
|
||||||
|
fhb = fp.controlPointRect()
|
||||||
|
hb_size = fhb.size() + hb_size
|
||||||
# print(f'hb_size: {hb_size}')
|
# print(f'hb_size: {hb_size}')
|
||||||
|
|
||||||
w = hb_size.width() + 1
|
w = hb_size.width() + 1
|
||||||
|
@ -466,6 +625,7 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
profiler = pg.debug.Profiler(
|
profiler = pg.debug.Profiler(
|
||||||
|
msg=f'{self._name}.paint()',
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -486,14 +646,15 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
p.setPen(self._pen)
|
p.setPen(self._pen)
|
||||||
|
|
||||||
path = self.path
|
path = self.path
|
||||||
if self._use_poly:
|
|
||||||
assert self.poly
|
|
||||||
p.drawPolyline(self.poly)
|
|
||||||
profiler('.drawPolyline()')
|
|
||||||
|
|
||||||
elif path:
|
if path:
|
||||||
|
profiler('.drawPath(path)')
|
||||||
p.drawPath(path)
|
p.drawPath(path)
|
||||||
profiler('.drawPath()')
|
|
||||||
|
fp = self.fast_path
|
||||||
|
if fp:
|
||||||
|
p.drawPath(fp)
|
||||||
|
profiler('.drawPath(fast_path)')
|
||||||
|
|
||||||
# TODO: try out new work from `pyqtgraph` main which should
|
# TODO: try out new work from `pyqtgraph` main which should
|
||||||
# repair horrid perf (pretty sure i did and it was still
|
# repair horrid perf (pretty sure i did and it was still
|
||||||
|
|
Loading…
Reference in New Issue