Fix intersect detection using time indexing

Facepalm, obviously absolute array indexes are not going to necessarily
align vs. time over multiple feeds/history. Instead use
`np.searchsorted()` on whatever curve has the smallest support and find
the appropriate index of intersection in time so that alignment always
starts at a sensible reference.

Also adds a `debug_print: bool` input arg which can enable all the
prints when working on this.
multichartz
Tyler Goodlet 2023-01-23 13:23:46 -05:00
parent 2aa5137283
commit d0b39e8a2b
1 changed files with 94 additions and 77 deletions

View File

@ -20,7 +20,9 @@ Chart view box primitives
""" """
from __future__ import annotations from __future__ import annotations
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import math from math import (
isinf,
)
import time import time
from typing import ( from typing import (
Optional, Optional,
@ -904,6 +906,8 @@ class ChartView(ViewBox):
def interact_graphics_cycle( def interact_graphics_cycle(
self, self,
*args,
debug_print: bool = False,
): ):
profiler = Profiler( profiler = Profiler(
msg=f'ChartView.interact_graphics_cycle() for {self.name}', msg=f'ChartView.interact_graphics_cycle() for {self.name}',
@ -971,7 +975,7 @@ class ChartView(ViewBox):
], ],
] = {} ] = {}
max_istart: float = 0 max_istart: float = 0
# major_in_view: np.ndarray = None major_in_view: np.ndarray = None
for name, viz in chart._vizs.items(): for name, viz in chart._vizs.items():
@ -1061,7 +1065,7 @@ class ChartView(ViewBox):
mx_disp = disp mx_disp = disp
major_mn = ymn major_mn = ymn
major_mx = ymx major_mx = ymx
# major_in_view = in_view major_in_view = in_view
# compute directional (up/down) y-range % swing/dispersion # compute directional (up/down) y-range % swing/dispersion
y_ref = y_med y_ref = y_med
@ -1099,6 +1103,9 @@ class ChartView(ViewBox):
if ( if (
len(start_datums) < 2 len(start_datums) < 2
): ):
if not major_viz:
major_viz = viz
# print(f'ONLY ranging major: {viz.name}') # print(f'ONLY ranging major: {viz.name}')
major_viz.plot.vb._set_yrange( major_viz.plot.vb._set_yrange(
yrange=yrange, yrange=yrange,
@ -1125,13 +1132,6 @@ class ChartView(ViewBox):
if viz is major_viz: if viz is major_viz:
ymn = y_min ymn = y_min
ymx = y_max ymx = y_max
# print(
# f'{view.name} MAJOR mxmn\n'
# '--------------------\n'
# f'scaled ymn: {ymn}\n'
# f'scaled ymx: {ymx}\n'
# f'scaled mx_disp: {mx_disp}\n'
# )
continue continue
else: else:
@ -1149,31 +1149,61 @@ class ChartView(ViewBox):
# y-range based on the major curves y-range. # y-range based on the major curves y-range.
# get intersection point y-values for both curves # get intersection point y-values for both curves
mshm = major_viz.shm minor_in_view_start = minor_in_view[0]
minor_i_start = minor_in_view_start['index']
minor_i_start_t = minor_in_view_start['time']
minor_i_start = minor_in_view[0]['index'] major_in_view_start = major_in_view[0]
major_i_start = mshm.array['index'][0] major_i_start = major_in_view_start['index']
major_i_start_t = major_in_view_start['time']
abs_i_start = max( y_major_intersect = major_in_view_start[key]
minor_i_start, y_minor_intersect = minor_in_view_start[key]
major_i_start,
tdiff = (major_i_start_t - minor_i_start_t)
if debug_print:
print(
f'{major_viz.name} time diff with minor:\n'
f'maj:{major_i_start_t}\n'
'-\n'
f'min:{minor_i_start_t}\n'
f'=> {tdiff}\n'
) )
y_maj_intersect = mshm._array[abs_i_start][key] # major has later timestamp adjust minor
y_minor_intersect = viz.shm._array[abs_i_start][key] if tdiff > 0:
y_minor_i = np.searchsorted(
minor_in_view['time'],
major_i_start_t,
)
y_minor_intersect = minor_in_view[y_minor_i][key]
# minor has later timestamp adjust major
elif tdiff < 0:
y_major_i = np.searchsorted(
major_in_view['time'],
minor_i_start_t,
)
y_major_intersect = major_in_view[y_major_i][key]
if debug_print:
print(
f'major_i_start: {major_i_start}\n'
f'major_i_start_t: {major_i_start_t}\n'
f'minor_i_start: {minor_i_start}\n'
f'minor_i_start_t: {minor_i_start_t}\n'
)
# TODO: probably write this as a compile cpython or # TODO: probably write this as a compile cpython or
# numba func. # numba func.
# if abs_i_start > major_i_start:
# compute directional (up/down) y-range # compute directional (up/down) y-range
# % swing/dispersion starting at the reference index # % swing/dispersion starting at the reference index
# determined by the above indexing arithmetic. # determined by the above indexing arithmetic.
y_ref = y_maj_intersect y_ref = y_major_intersect
if not y_ref: if not y_ref:
log.warning( log.warning(
f'BAD y_maj_intersect?!: {y_maj_intersect}' f'BAD y_major_intersect?!: {y_major_intersect}'
) )
# breakpoint() # breakpoint()
@ -1196,18 +1226,15 @@ class ChartView(ViewBox):
y_ref = y_minor_intersect y_ref = y_minor_intersect
r_up_minor = (y_max - y_ref) / y_ref r_up_minor = (y_max - y_ref) / y_ref
# y_maj_ref = max( y_maj_ref = y_major_intersect
# major_in_view[0][key],
# y_maj_intersect,
# )
y_maj_ref = y_maj_intersect
new_maj_ymx = y_maj_ref * (1 + r_up_minor) new_maj_ymx = y_maj_ref * (1 + r_up_minor)
new_maj_mxmn = (major_mn, new_maj_ymx) new_maj_mxmn = (major_mn, new_maj_ymx)
# print( if debug_print:
# f'{view.name} OUT OF RANGE:\n' print(
# '--------------------\n' f'{view.name} OUT OF RANGE:\n'
# f'y_max:{y_max} > ymx:{ymx}\n' '--------------------\n'
# ) f'y_max:{y_max} > ymx:{ymx}\n'
)
ymx = y_max ymx = y_max
if y_min < ymn: if y_min < ymn:
@ -1215,58 +1242,46 @@ class ChartView(ViewBox):
y_ref = y_minor_intersect y_ref = y_minor_intersect
r_down_minor = (y_min - y_ref) / y_ref r_down_minor = (y_min - y_ref) / y_ref
# y_maj_ref = min( y_maj_ref = y_major_intersect
# major_in_view[0][key],
# y_maj_intersect,
# )
y_maj_ref = y_maj_intersect
new_maj_ymn = y_maj_ref * (1 + r_down_minor) new_maj_ymn = y_maj_ref * (1 + r_down_minor)
new_maj_mxmn = ( new_maj_mxmn = (
new_maj_ymn, new_maj_ymn,
new_maj_mxmn[1] if new_maj_mxmn else major_mx new_maj_mxmn[1] if new_maj_mxmn else major_mx
) )
# print( if debug_print:
# f'{view.name} OUT OF RANGE:\n' print(
# '--------------------\n' f'{view.name} OUT OF RANGE:\n'
# f'y_min:{y_min} < ymn:{ymn}\n' '--------------------\n'
# ) f'y_min:{y_min} < ymn:{ymn}\n'
)
ymn = y_min ymn = y_min
# now scale opposite side to compensate
# y_ref = y_major_intersect
# r_down_minor = (major_ - y_ref) / y_ref
if new_maj_mxmn: if new_maj_mxmn:
# print( if debug_print:
# f'RESCALE MAJOR {major_viz.name}:\n' print(
# f'previous: {(major_mn, major_mx)}\n' f'RESCALE MAJOR {major_viz.name}:\n'
# f'new: {new_maj_mxmn}\n' f'previous: {(major_mn, major_mx)}\n'
# ) f'new: {new_maj_mxmn}\n'
# major_viz.plot.vb._set_yrange( )
# yrange=new_maj_mxmn,
# # range_margin=None,
# )
major_mn, major_mx = new_maj_mxmn major_mn, major_mx = new_maj_mxmn
# vrs = major_viz.plot.vb.viewRange()
# if vrs[1][0] > new_maj_mxmn[0]:
# breakpoint()
# print( if debug_print:
# f'{view.name} APPLY group mxmn\n' print(
# '--------------------\n' f'{view.name} APPLY group mxmn\n'
# f'minor_y_start: {minor_y_start}\n' '--------------------\n'
# f'mn_down_rng: {mn_down_rng * 100}\n' f'y_minor_intersect: {y_minor_intersect}\n'
# f'mx_up_rng: {mx_up_rng * 100}\n' f'y_major_intersect: {y_major_intersect}\n'
# f'scaled ymn: {ymn}\n' f'mn_down_rng: {mn_down_rng * 100}\n'
# f'scaled ymx: {ymx}\n' f'mx_up_rng: {mx_up_rng * 100}\n'
# f'scaled mx_disp: {mx_disp}\n' f'scaled ymn: {ymn}\n'
# ) f'scaled ymx: {ymx}\n'
f'scaled mx_disp: {mx_disp}\n'
)
if ( if (
math.isinf(ymx) isinf(ymx)
or math.isinf(ymn) or isinf(ymn)
): ):
# breakpoint()
log.warning( log.warning(
f'BAD ymx/ymn: {(ymn, ymx)}' f'BAD ymx/ymn: {(ymn, ymx)}'
) )
@ -1281,11 +1296,13 @@ class ChartView(ViewBox):
# inside of a single render cycle (and apparently calling # inside of a single render cycle (and apparently calling
# `ViewBox.setYRange()` multiple times within one only takes # `ViewBox.setYRange()` multiple times within one only takes
# the first call as serious...) XD # the first call as serious...) XD
# print( if debug_print:
# f'Scale MAJOR {major_viz.name}:\n' print(
# f'previous: {(major_mn, major_mx)}\n' f'Scale MAJOR {major_viz.name}:\n'
# f'new: {new_maj_mxmn}\n' f'scaled mx_disp: {mx_disp}\n'
# ) f'previous: {(major_mn, major_mx)}\n'
f'new: {new_maj_mxmn}\n'
)
major_viz.plot.vb._set_yrange( major_viz.plot.vb._set_yrange(
yrange=(major_mn, major_mx), yrange=(major_mn, major_mx),
) )