Add cached dispersion methods to `Viz`

In an effort to make overlay calcs cleaner and leverage caching of view
range -> dispersion measures, this adds the following new methods:

- `._dispersion()` an lru cached returns scalar calculator given input
  y-range and y-ref values.
- `.disp_from_range()` which calls the above method and returns variable
  output depending on requested calc `method: str`.
- `.i_from_t()` a currently unused cached method for slicing the
  in-view's array index from time stamp (though not working yet due to
  needing to parameterize the cache by the input `.vs.xrange`).

Further refinements/adjustments:
- rename `.view_state: ViewState` -> `.vs`.
- drop the `.bars_range()` method as it's no longer used anywhere else
  in the code base.
- always set the `ViewState.in_view: np.ndarray` inside `.read()`.
- return the start array index (from slice) and `yref` value @ `xref`
  from `.scalars_from_index()` to aid with "pin to curve" rescaling
  caused by out-of-range pinned-minor curves.
log_linearized_curve_overlays
Tyler Goodlet 2023-02-27 14:18:41 -05:00
parent 29418e9655
commit 4d11c5c89c
1 changed files with 96 additions and 33 deletions

View File

@ -279,7 +279,7 @@ class Viz(Struct): # , frozen=True):
flume: Flume flume: Flume
graphics: Curve | BarItems graphics: Curve | BarItems
view_state: ViewState = field(default_factory=ViewState) vs: ViewState = field(default_factory=ViewState)
# last calculated y-mn/mx from m4 downsample code, this # last calculated y-mn/mx from m4 downsample code, this
# is updated in the body of `Renderer.render()`. # is updated in the body of `Renderer.render()`.
@ -520,6 +520,7 @@ class Viz(Struct): # , frozen=True):
# cache result for input range # cache result for input range
assert mxmn assert mxmn
self._mxmns[ixrng] = (read_slc, mxmn) self._mxmns[ixrng] = (read_slc, mxmn)
self.vs.yrange = mxmn
profiler(f'yrange mxmn cacheing: {x_range} -> {mxmn}') profiler(f'yrange mxmn cacheing: {x_range} -> {mxmn}')
return ( return (
ixrng, ixrng,
@ -538,15 +539,6 @@ class Viz(Struct): # , frozen=True):
vr.right(), vr.right(),
) )
def bars_range(self) -> tuple[int, int, int, int]:
'''
Return a range tuple for the left-view, left-datum, right-datum
and right-view x-indices.
'''
l, start, datum_start, datum_stop, stop, r = self.datums_range()
return l, datum_start, datum_stop, r
def datums_range( def datums_range(
self, self,
view_range: None | tuple[float, float] = None, view_range: None | tuple[float, float] = None,
@ -574,11 +566,6 @@ class Viz(Struct): # , frozen=True):
first: int = floor(index[0]) first: int = floor(index[0])
last: int = ceil(index[-1]) last: int = ceil(index[-1])
# first and last datums in view determined by
# l -> r view range.
leftmost: int = floor(l)
rightmost: int = ceil(r)
# invalid view state # invalid view state
if ( if (
r < l r < l
@ -593,13 +580,15 @@ class Viz(Struct): # , frozen=True):
rightmost: int = last rightmost: int = last
else: else:
# determine first and last datums in view determined by
# l -> r view range.
rightmost = max( rightmost = max(
min(last, rightmost), min(last, ceil(r)),
first, first,
) )
leftmost = min( leftmost = min(
max(first, leftmost), max(first, floor(l)),
last, last,
rightmost - 1, rightmost - 1,
) )
@ -607,7 +596,7 @@ class Viz(Struct): # , frozen=True):
# sanity # sanity
# assert leftmost < rightmost # assert leftmost < rightmost
self.view_state.xrange = leftmost, rightmost self.vs.xrange = leftmost, rightmost
return ( return (
l, # left x-in-view l, # left x-in-view
@ -671,14 +660,14 @@ class Viz(Struct): # , frozen=True):
# above? # above?
in_view = array[read_slc] in_view = array[read_slc]
if in_view.size: if in_view.size:
self.view_state.in_view = in_view self.vs.in_view = in_view
abs_indx = in_view['index'] abs_indx = in_view['index']
abs_slc = slice( abs_slc = slice(
int(abs_indx[0]), int(abs_indx[0]),
int(abs_indx[-1]), int(abs_indx[-1]),
) )
else: else:
self.view_state.in_view = None self.vs.in_view = None
if profiler: if profiler:
profiler( profiler(
@ -699,7 +688,7 @@ class Viz(Struct): # , frozen=True):
# BUT the ``in_view`` slice DOES.. # BUT the ``in_view`` slice DOES..
read_slc = slice(lbar_i, rbar_i) read_slc = slice(lbar_i, rbar_i)
in_view = array[lbar_i: rbar_i + 1] in_view = array[lbar_i: rbar_i + 1]
self.view_state.in_view = in_view self.vs.in_view = in_view
# in_view = array[lbar_i-1: rbar_i+1] # in_view = array[lbar_i-1: rbar_i+1]
# XXX: same as ^ # XXX: same as ^
# to_draw = array[lbar - ifirst:(rbar - ifirst) + 1] # to_draw = array[lbar - ifirst:(rbar - ifirst) + 1]
@ -1323,27 +1312,101 @@ class Viz(Struct): # , frozen=True):
return np.median(in_view[self.name]) return np.median(in_view[self.name])
@lru_cache(maxsize=6116) @lru_cache(maxsize=6116)
def dispersion( def _dispersion(
start: int, self,
stop: int, # xrange: tuple[float, float],
ymn: float,
ymx: float,
yref: float,
) -> float: ) -> tuple[float, float]:
pass return (
(ymx - yref) / yref,
(ymn - yref) / yref,
)
def disp_from_range(
self,
xrange: tuple[float, float] | None = None,
yref: float | None = None,
method: Literal[
'up',
'down',
'full', # both sides
'both', # both up and down as separate scalars
] = 'full',
) -> float | tuple[float, float] | None:
'''
Return a dispersion metric referenced from an optionally
provided ``yref`` or the left-most datum level by default.
'''
vs = self.vs
yrange = vs.yrange
if yrange is None:
return None
ymn, ymx = yrange
key = 'open' if self.is_ohlc else self.name
yref = yref or vs.in_view[0][key]
# xrange = xrange or vs.xrange
# call into the lru_cache-d sigma calculator method
r_up, r_down = self._dispersion(ymn, ymx, yref)
match method:
case 'full':
return r_up - r_down
case 'up':
return r_up
case 'down':
return r_up
case 'both':
return r_up, r_down
@lru_cache(maxsize=6116)
def i_from_t(
self,
t: float,
) -> int:
return slice_from_time(
self.vs.in_view,
start_t=t,
stop_t=t,
step=self.index_step(),
).start
def scalars_from_index( def scalars_from_index(
self, self,
xref: float, xref: float | None = None,
) -> tuple[int, float, float, float]:
vs = self.vs
arr = vs.in_view
# TODO: make this work by parametrizing over input
# .vs.xrange input for caching?
# read_slc_start = self.i_from_t(xref)
) -> tuple[float, float]:
arr = self.view_state.in_view
slc = slice_from_time( slc = slice_from_time(
arr=self.view_state.in_view, arr=self.vs.in_view,
start_t=xref, start_t=xref,
stop_t=xref, stop_t=xref,
) )
yref = arr[slc.start] read_slc_start = slc.start
ymn, ymx = self.view_state.yrange
key = 'open' if self.is_ohlc else self.name
yref = arr[read_slc_start][key]
ymn, ymx = self.vs.yrange
# print(
# f'INTERSECT xref: {read_slc_start}\n'
# f'ymn, ymx: {(ymn, ymx)}\n'
# )
return ( return (
(ymn - yref) / yref, read_slc_start,
yref,
(ymx - yref) / yref, (ymx - yref) / yref,
(ymn - yref) / yref,
) )