Add `Viz.reset_graphics()` for "force re-render"

Since we're now using it multiple layers probably makes sense to impl
and wrap it more correctly / publicly. The main (recent) use case is
where editing an underlying time series and then wanting to refresh the
graphics layers to reflect the changes in a chart. Part of this also
obviously includes wiping the y-range mx/mn cache.

Also ensure that `force_redraw` is proxying through to any `BarItems`
via the new `render_baritems()` func kwarg even when switching between
downsampled-line vs. bars modes.
distribute_dis
Tyler Goodlet 2023-12-28 18:00:26 -05:00
parent 661805695e
commit a7ad50cf8f
1 changed files with 87 additions and 24 deletions

View File

@ -36,6 +36,9 @@ from msgspec import (
field, field,
) )
import numpy as np import numpy as np
from numpy import (
ndarray,
)
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5.QtCore import QLineF from PyQt5.QtCore import QLineF
@ -82,10 +85,11 @@ def render_baritems(
viz: Viz, viz: Viz,
graphics: BarItems, graphics: BarItems,
read: tuple[ read: tuple[
int, int, np.ndarray, int, int, ndarray,
int, int, np.ndarray, int, int, ndarray,
], ],
profiler: Profiler, profiler: Profiler,
force_redraw: bool = False,
**kwargs, **kwargs,
) -> None: ) -> None:
@ -216,9 +220,11 @@ def render_baritems(
viz._in_ds = should_line viz._in_ds = should_line
should_redraw = ( should_redraw = (
changed_to_line force_redraw
or changed_to_line
or not should_line or not should_line
) )
# print(f'should_redraw: {should_redraw}')
return ( return (
graphics, graphics,
r, r,
@ -250,7 +256,7 @@ class ViewState(Struct):
] | None = None ] | None = None
# last in view ``ShmArray.array[read_slc]`` data # last in view ``ShmArray.array[read_slc]`` data
in_view: np.ndarray | None = None in_view: ndarray | None = None
class Viz(Struct): class Viz(Struct):
@ -375,11 +381,11 @@ class Viz(Struct):
self._index_step is None self._index_step is None
or index_field is not None or index_field is not None
): ):
index: np.ndarray = self.shm.array[ index: ndarray = self.shm.array[
index_field index_field
or self.index_field or self.index_field
] ]
isample: np.ndarray = index[-16:] isample: ndarray = index[-16:]
mxdiff: None | float = None mxdiff: None | float = None
for step in np.diff(isample): for step in np.diff(isample):
@ -430,6 +436,9 @@ class Viz(Struct):
i_read_range: tuple[int, int] | None = None, i_read_range: tuple[int, int] | None = None,
use_caching: bool = True, use_caching: bool = True,
# XXX: internal debug
_do_print: bool = False
) -> tuple[float, float] | None: ) -> tuple[float, float] | None:
''' '''
Compute the cached max and min y-range values for a given Compute the cached max and min y-range values for a given
@ -449,15 +458,14 @@ class Viz(Struct):
if shm is None: if shm is None:
return None return None
do_print: bool = False arr: ndarray = shm.array
arr = shm.array
if i_read_range is not None: if i_read_range is not None:
read_slc = slice(*i_read_range) read_slc = slice(*i_read_range)
index = arr[read_slc][self.index_field] index: float | int = arr[read_slc][self.index_field]
if not index.size: if not index.size:
return None return None
ixrng = (index[0], index[-1]) ixrng: tuple[int, int] = (index[0], index[-1])
else: else:
if x_range is None: if x_range is None:
@ -475,15 +483,24 @@ class Viz(Struct):
# TODO: hash the slice instead maybe? # TODO: hash the slice instead maybe?
# https://stackoverflow.com/a/29980872 # https://stackoverflow.com/a/29980872
ixrng = lbar, rbar = round(x_range[0]), round(x_range[1]) ixrng = lbar, rbar = (
round(x_range[0]),
round(x_range[1]),
)
if ( if (
use_caching use_caching
and self._mxmn_cache_enabled and self._mxmn_cache_enabled
): ):
# TODO: is there a way to ONLY clear ranges containing
# a certain sub-range?
# -[ ] currently we have a problem where a previously
# cached mxmn will persist even if the viz is "hard
# re-rendered" (usually bc underlying data was
# corrected)
cached_result = self._mxmns.get(ixrng) cached_result = self._mxmns.get(ixrng)
if cached_result: if cached_result:
if do_print: if _do_print:
print( print(
f'{self.name} CACHED maxmin\n' f'{self.name} CACHED maxmin\n'
f'{ixrng} -> {cached_result}' f'{ixrng} -> {cached_result}'
@ -513,7 +530,7 @@ class Viz(Struct):
(rbar - ifirst) + 1 (rbar - ifirst) + 1
) )
slice_view = arr[read_slc] slice_view: ndarray = arr[read_slc]
if not slice_view.size: if not slice_view.size:
log.warning( log.warning(
@ -524,7 +541,7 @@ class Viz(Struct):
elif self.ds_yrange: elif self.ds_yrange:
mxmn = self.ds_yrange mxmn = self.ds_yrange
if do_print: if _do_print:
print( print(
f'{self.name} M4 maxmin:\n' f'{self.name} M4 maxmin:\n'
f'{ixrng} -> {mxmn}' f'{ixrng} -> {mxmn}'
@ -541,7 +558,7 @@ class Viz(Struct):
mxmn = ylow, yhigh mxmn = ylow, yhigh
if ( if (
do_print _do_print
): ):
s = 3 s = 3
print( print(
@ -555,14 +572,23 @@ class Viz(Struct):
# cache result for input range # cache result for input range
ylow, yhi = mxmn ylow, yhi = mxmn
diff: float = yhi - ylow
# order-of-magnitude check
# TODO: really we should be checking the hi or low
# against the previous sample to catch stuff like,
# - rando stock (reverse-)split
# - null-segments written by some prior
# crash-during-backfil
if diff > 0:
omg: float = abs(logf(diff, 10))
else:
omg: float = 0
try: try:
prolly_anomaly: bool = ( prolly_anomaly: bool = (
( # diff == 0
abs(logf(ylow, 10)) > 16 (ylow and omg > 10)
if ylow
else False
)
or ( or (
isnan(ylow) or isnan(yhi) isnan(ylow) or isnan(yhi)
) )
@ -603,7 +629,7 @@ class Viz(Struct):
self, self,
view_range: None | tuple[float, float] = None, view_range: None | tuple[float, float] = None,
index_field: str | None = None, index_field: str | None = None,
array: np.ndarray | None = None, array: ndarray | None = None,
) -> tuple[ ) -> tuple[
int, int, int, int, int, int int, int, int, int, int, int
@ -674,8 +700,8 @@ class Viz(Struct):
profiler: None | Profiler = None, profiler: None | Profiler = None,
) -> tuple[ ) -> tuple[
int, int, np.ndarray, int, int, ndarray,
int, int, np.ndarray, int, int, ndarray,
]: ]:
''' '''
Read the underlying shm array buffer and Read the underlying shm array buffer and
@ -845,6 +871,10 @@ class Viz(Struct):
graphics, graphics,
read, read,
profiler, profiler,
# NOTE: only set when caller says to
force_redraw=should_redraw,
**kwargs, **kwargs,
) )
@ -1007,6 +1037,39 @@ class Viz(Struct):
graphics, graphics,
) )
def reset_graphics(
self,
# TODO: allow only resetting within some x-domain range?
# ixrng: tuple[int, int] | None = None,
) -> None:
'''
Hard reset all graphics (rendering) layers for this
data viz including clearing the mxmn auto-y-range
cache.
Normally called when the underlying data set is modified
(probably by some `.tsp` correcting/editing routine) and
the (now cached) graphics need to be fully re-rendered from
source.
'''
log.warning(
f'Forcing hard Viz graphihcs RESET:\n'
f'.name: {self.name}\n'
f'.index_field: {self.index_field}\n'
f'.index_step(): {self.index_step()}\n'
f'.time_step(): {self.time_step()}\n'
)
# XXX: always clear the mxn y-range cache
# to avoid old data (anomalies) from being
# retained in auto-yrange output.
self._mxmn_cache_enabled = False
self._mxmns.clear()
self.update_graphics(force_redraw=True)
self._mxmn_cache_enabled = True
def draw_last( def draw_last(
self, self,
array_key: str | None = None, array_key: str | None = None,
@ -1099,7 +1162,7 @@ class Viz(Struct):
''' '''
shm: ShmArray = self.shm shm: ShmArray = self.shm
array: np.ndarray = shm.array array: ndarray = shm.array
view: ChartView = self.plot.vb view: ChartView = self.plot.vb
( (
vl, vl,