Make `FastAppendCurve` optionally view range aware
As with the `BarItems` graphics, this makes it possible to pass in a "in view" range of array data that can be *only* rendered improving performance for large(r) data sets. All the other normal behaviour is kept (i.e a persistent, (pre/ap)pendable path can still be maintained) if a ``view_range`` is not provided. Further updates, - drop the `.should_ds_or_redraw()` and `.maybe_downsample()` predicates instead moving all that logic inside `.update_from_array()`. - disable the "cache flipping", which doesn't seem to be needed to avoid artifacts any more? - handle all redraw/dowsampling logic in `.update_from_array()`. - even more profiling. - drop path `.reserve()` stuff until we better figure out how it's supposed to work.only_draw_iv_for_ohlc
parent
f7ebade9b5
commit
0cabc613a0
|
@ -214,6 +214,9 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
vr = self.viewRect()
|
vr = self.viewRect()
|
||||||
l, r = int(vr.left()), int(vr.right())
|
l, r = int(vr.left()), int(vr.right())
|
||||||
|
|
||||||
|
if not self._xrange:
|
||||||
|
return 0
|
||||||
|
|
||||||
start, stop = self._xrange
|
start, stop = self._xrange
|
||||||
lbar = max(l, start)
|
lbar = max(l, start)
|
||||||
rbar = min(r, stop)
|
rbar = min(r, stop)
|
||||||
|
@ -222,45 +225,6 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
QLineF(lbar, 0, rbar, 0)
|
QLineF(lbar, 0, rbar, 0)
|
||||||
).length()
|
).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)
|
|
||||||
uppx_diff = (uppx - self._last_uppx)
|
|
||||||
self._last_uppx = uppx
|
|
||||||
|
|
||||||
should_redraw: bool = False
|
|
||||||
should_ds: bool = False
|
|
||||||
|
|
||||||
# print(uppx_diff)
|
|
||||||
|
|
||||||
if (
|
|
||||||
uppx <= 8
|
|
||||||
):
|
|
||||||
# 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 uppx_diff <= -2
|
|
||||||
or self._step_mode and abs(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(
|
def downsample(
|
||||||
self,
|
self,
|
||||||
x,
|
x,
|
||||||
|
@ -289,23 +253,19 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
self._in_ds = True
|
self._in_ds = True
|
||||||
return x, y
|
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,
|
||||||
|
|
||||||
|
# full array input history
|
||||||
x: np.ndarray,
|
x: np.ndarray,
|
||||||
y: np.ndarray,
|
y: np.ndarray,
|
||||||
|
|
||||||
|
# pre-sliced array data that's "in view"
|
||||||
|
x_iv: np.ndarray,
|
||||||
|
y_iv: np.ndarray,
|
||||||
|
|
||||||
|
view_range: Optional[tuple[int, int]] = None,
|
||||||
|
|
||||||
) -> QtGui.QPainterPath:
|
) -> QtGui.QPainterPath:
|
||||||
'''
|
'''
|
||||||
Update curve from input 2-d data.
|
Update curve from input 2-d data.
|
||||||
|
@ -315,11 +275,11 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
profiler = pg.debug.Profiler(
|
profiler = pg.debug.Profiler(
|
||||||
msg=f'{self._name}.update_from_array()',
|
msg=f'FastAppendCurve.update_from_array(): `{self._name}`',
|
||||||
disabled=not pg_profile_enabled(),
|
disabled=not pg_profile_enabled(),
|
||||||
gt=ms_slower_then,
|
gt=ms_slower_then,
|
||||||
)
|
)
|
||||||
flip_cache = False
|
# flip_cache = False
|
||||||
|
|
||||||
if self._xrange:
|
if self._xrange:
|
||||||
istart, istop = self._xrange
|
istart, istop = self._xrange
|
||||||
|
@ -327,40 +287,110 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
self._xrange = 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()
|
# XXX: lol brutal, the internals of `CurvePoint` (inherited by
|
||||||
|
# our `LineDot`) required ``.getData()`` to work..
|
||||||
|
self.xData = x
|
||||||
|
self.yData = y
|
||||||
|
self._x, self._y = x, y
|
||||||
|
|
||||||
|
if view_range:
|
||||||
|
profiler(f'view range slice {view_range}')
|
||||||
|
|
||||||
|
# downsampling incremental state checking
|
||||||
|
uppx = self.x_uppx()
|
||||||
|
px_width = self.px_width()
|
||||||
|
uppx_diff = (uppx - self._last_uppx)
|
||||||
|
|
||||||
|
should_ds = False
|
||||||
|
should_redraw = False
|
||||||
|
|
||||||
|
# if a view range is passed, plan to draw the
|
||||||
|
# source ouput that's "in view" of the chart.
|
||||||
|
if view_range and not self._in_ds:
|
||||||
|
# print(f'{self._name} vr: {view_range}')
|
||||||
|
|
||||||
|
# by default we only pull data up to the last (current) index
|
||||||
|
x_out, y_out = x_iv[:-1], y_iv[:-1]
|
||||||
|
|
||||||
|
# step mode: draw flat top discrete "step"
|
||||||
|
# over the index space for each datum.
|
||||||
|
if self._step_mode:
|
||||||
|
# TODO: numba this bish
|
||||||
|
x_out, y_out = step_path_arrays_from_1d(
|
||||||
|
x_out,
|
||||||
|
y_out
|
||||||
|
)
|
||||||
|
profiler('generated step arrays')
|
||||||
|
|
||||||
|
should_redraw = True
|
||||||
|
profiler('sliced in-view array history')
|
||||||
|
|
||||||
|
# x_last = x_iv[-1]
|
||||||
|
# y_last = y_iv[-1]
|
||||||
|
self._last_vr = view_range
|
||||||
|
|
||||||
|
# self.disable_cache()
|
||||||
|
# flip_cache = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._xrange = x[0], x[-1]
|
||||||
|
|
||||||
|
x_last = x[-1]
|
||||||
|
y_last = y[-1]
|
||||||
|
|
||||||
|
# check for downsampling conditions
|
||||||
|
if (
|
||||||
|
# std m4 downsample conditions
|
||||||
|
uppx_diff >= 2
|
||||||
|
or uppx_diff <= -2
|
||||||
|
or self._step_mode and abs(uppx_diff) >= 2
|
||||||
|
|
||||||
|
):
|
||||||
|
log.info(
|
||||||
|
f'{self._name} sampler change: {self._last_uppx} -> {uppx}'
|
||||||
|
)
|
||||||
|
self._last_uppx = uppx
|
||||||
|
should_ds = {'px_width': px_width, 'uppx': uppx}
|
||||||
|
|
||||||
|
elif (
|
||||||
|
uppx <= 2
|
||||||
|
and self._in_ds
|
||||||
|
):
|
||||||
|
# we should de-downsample back to our original
|
||||||
|
# source data so we clear our path data in prep
|
||||||
|
# to generate a new one from original source data.
|
||||||
|
should_redraw = True
|
||||||
|
should_ds = False
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
|
# no_path_yet = self.path is None
|
||||||
if (
|
if (
|
||||||
should_redraw or should_ds
|
self.path is None
|
||||||
or self.path is None
|
or should_redraw
|
||||||
|
or should_ds
|
||||||
or prepend_length > 0
|
or prepend_length > 0
|
||||||
):
|
):
|
||||||
# step mode: draw flat top discrete "step"
|
if (
|
||||||
# over the index space for each datum.
|
not view_range
|
||||||
if self._step_mode:
|
or self._in_ds
|
||||||
x_out, y_out = step_path_arrays_from_1d(
|
):
|
||||||
x[:-1], y[:-1]
|
|
||||||
)
|
|
||||||
profiler('generated step arrays')
|
|
||||||
|
|
||||||
else:
|
|
||||||
# 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 should_ds:
|
# step mode: draw flat top discrete "step"
|
||||||
x_out, y_out = self.downsample(
|
# over the index space for each datum.
|
||||||
x_out,
|
if self._step_mode:
|
||||||
y_out,
|
x_out, y_out = step_path_arrays_from_1d(
|
||||||
**should_ds,
|
x_out,
|
||||||
)
|
y_out,
|
||||||
profiler(f'path downsample redraw={should_ds}')
|
)
|
||||||
self._in_ds = True
|
# TODO: numba this bish
|
||||||
|
profiler('generated step arrays')
|
||||||
|
|
||||||
if should_redraw:
|
if should_redraw:
|
||||||
profiler('path reversion to non-ds')
|
profiler('path reversion to non-ds')
|
||||||
|
@ -371,10 +401,20 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
self.fast_path.clear()
|
self.fast_path.clear()
|
||||||
|
|
||||||
if should_redraw and not should_ds:
|
if should_redraw and not should_ds:
|
||||||
log.info(f'DEDOWN -> {self._name}')
|
if self._in_ds:
|
||||||
|
log.info(f'DEDOWN -> {self._name}')
|
||||||
|
|
||||||
self._in_ds = False
|
self._in_ds = False
|
||||||
|
|
||||||
# else:
|
elif should_ds:
|
||||||
|
x_out, y_out = self.downsample(
|
||||||
|
x_out,
|
||||||
|
y_out,
|
||||||
|
**should_ds,
|
||||||
|
)
|
||||||
|
profiler(f'FULL PATH downsample redraw={should_ds}')
|
||||||
|
self._in_ds = True
|
||||||
|
|
||||||
self.path = pg.functions.arrayToQPath(
|
self.path = pg.functions.arrayToQPath(
|
||||||
x_out,
|
x_out,
|
||||||
y_out,
|
y_out,
|
||||||
|
@ -382,6 +422,9 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
finiteCheck=False,
|
finiteCheck=False,
|
||||||
path=self.path,
|
path=self.path,
|
||||||
)
|
)
|
||||||
|
profiler('generated fresh path')
|
||||||
|
# profiler(f'DRAW PATH IN VIEW -> {self._name}')
|
||||||
|
|
||||||
# reserve mem allocs see:
|
# reserve mem allocs see:
|
||||||
# - https://doc.qt.io/qt-5/qpainterpath.html#reserve
|
# - 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#capacity
|
||||||
|
@ -389,17 +432,12 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
# XXX: right now this is based on had hoc checks on a
|
# XXX: right now this is based on had hoc checks on a
|
||||||
# hidpi 3840x2160 4k monitor but we should optimize for
|
# hidpi 3840x2160 4k monitor but we should optimize for
|
||||||
# the target display(s) on the sys.
|
# the target display(s) on the sys.
|
||||||
if no_path_yet:
|
# if no_path_yet:
|
||||||
self.path.reserve(int(500e3))
|
# self.path.reserve(int(500e3))
|
||||||
|
|
||||||
profiler('generated fresh path')
|
|
||||||
|
|
||||||
# if self._step_mode:
|
|
||||||
# self.path.closeSubpath()
|
|
||||||
|
|
||||||
# TODO: get this piecewise prepend working - right now it's
|
# TODO: get this piecewise prepend working - right now it's
|
||||||
# giving heck on vwap...
|
# giving heck on vwap...
|
||||||
# if prepend_length:
|
# elif prepend_length:
|
||||||
# breakpoint()
|
# breakpoint()
|
||||||
|
|
||||||
# prepend_path = pg.functions.arrayToQPath(
|
# prepend_path = pg.functions.arrayToQPath(
|
||||||
|
@ -416,11 +454,15 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
append_length > 0
|
append_length > 0
|
||||||
|
and not view_range
|
||||||
):
|
):
|
||||||
|
new_x = x[-append_length - 2:-1]
|
||||||
|
new_y = y[-append_length - 2:-1]
|
||||||
|
|
||||||
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],
|
new_x,
|
||||||
y[-append_length - 2:-1],
|
new_y,
|
||||||
)
|
)
|
||||||
# [1:] since we don't need the vertical line normally at
|
# [1:] since we don't need the vertical line normally at
|
||||||
# the beginning of the step curve taking the first (x,
|
# the beginning of the step curve taking the first (x,
|
||||||
|
@ -429,12 +471,6 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
new_x = new_x[1:]
|
new_x = new_x[1:]
|
||||||
new_y = new_y[1:]
|
new_y = new_y[1:]
|
||||||
|
|
||||||
else:
|
|
||||||
# print(f"append_length: {append_length}")
|
|
||||||
new_x = x[-append_length - 2:-1]
|
|
||||||
new_y = y[-append_length - 2:-1]
|
|
||||||
# print((new_x, new_y))
|
|
||||||
|
|
||||||
profiler('diffed append arrays')
|
profiler('diffed append arrays')
|
||||||
|
|
||||||
if should_ds:
|
if should_ds:
|
||||||
|
@ -490,21 +526,6 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
# self.disable_cache()
|
# self.disable_cache()
|
||||||
# flip_cache = True
|
# flip_cache = True
|
||||||
|
|
||||||
# XXX: do we need this any more?
|
|
||||||
# if (
|
|
||||||
# self._step_mode
|
|
||||||
# ):
|
|
||||||
# self.disable_cache()
|
|
||||||
# flip_cache = True
|
|
||||||
|
|
||||||
# XXX: lol brutal, the internals of `CurvePoint` (inherited by
|
|
||||||
# our `LineDot`) required ``.getData()`` to work..
|
|
||||||
self.xData = x
|
|
||||||
self.yData = y
|
|
||||||
|
|
||||||
x0, x_last = self._xrange = x[0], x[-1]
|
|
||||||
y_last = y[-1]
|
|
||||||
|
|
||||||
# draw the "current" step graphic segment so it lines up with
|
# draw the "current" step graphic segment so it lines up with
|
||||||
# the "middle" of the current (OHLC) sample.
|
# the "middle" of the current (OHLC) sample.
|
||||||
if self._step_mode:
|
if self._step_mode:
|
||||||
|
@ -522,7 +543,6 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
# f"last rect br: {self._last_step_rect}",
|
# f"last rect br: {self._last_step_rect}",
|
||||||
# )
|
# )
|
||||||
else:
|
else:
|
||||||
# print((x[-1], y_last))
|
|
||||||
self._last_line = QLineF(
|
self._last_line = QLineF(
|
||||||
x[-2], y[-2],
|
x[-2], y[-2],
|
||||||
x[-1], y_last
|
x[-1], y_last
|
||||||
|
@ -536,11 +556,9 @@ class FastAppendCurve(pg.GraphicsObject):
|
||||||
self.update()
|
self.update()
|
||||||
profiler('.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..
|
||||||
|
|
Loading…
Reference in New Issue