Factor curve-dispersion sorting into primary loop

We can determine the major curve (in view) in the first pass of all
`Viz`s so drop the 2nd loop and thus the `mxmn_groups: dict`. Also
simplifies logic for the case of only one (the major) curve in view.
multichartz
Tyler Goodlet 2023-01-22 16:07:52 -05:00
parent 404a5e1263
commit 2aa5137283
1 changed files with 129 additions and 138 deletions

View File

@ -908,7 +908,6 @@ class ChartView(ViewBox):
profiler = Profiler( profiler = Profiler(
msg=f'ChartView.interact_graphics_cycle() for {self.name}', msg=f'ChartView.interact_graphics_cycle() for {self.name}',
# disabled=not pg_profile_enabled(), # disabled=not pg_profile_enabled(),
# ms_threshold=ms_slower_then, # ms_threshold=ms_slower_then,
disabled=True, disabled=True,
@ -941,94 +940,24 @@ class ChartView(ViewBox):
tuple[float, float], tuple[float, float],
] = {} ] = {}
# collect certain flows have grapics objects **in seperate
# plots/viewboxes** into groups and do a common calc to
# determine auto-ranging input for `._set_yrange()`.
# this is primarly used for our so called "log-linearized
mxmn_groups: dict[
set[Viz],
set[Viz, tuple[float, float]],
] = {}
for name, viz in chart._vizs.items():
if not viz.render:
# print(f'skipping {flow.name}')
continue
# pass in no array which will read and render from the last
# passed array (normally provided by the display loop.)
in_view, i_read_range, _ = viz.update_graphics()
if not in_view:
continue
profiler(f'{viz.name}@{chart_name} `Viz.update_graphics()`')
out = viz.maxmin(i_read_range=i_read_range)
if out is None:
log.warning(f'No yrange provided for {name}!?')
return
(
ixrng,
_,
yrange
) = out
pi = viz.plot
mxmn = mxmns_by_common_pi.get(pi)
if mxmn:
yrange = mxmns_by_common_pi[pi] = (
min(yrange[0], mxmn[0]),
max(yrange[1], mxmn[1]),
)
else:
mxmns_by_common_pi[pi] = yrange
# TODO: a better predicate here, likely something
# to do with overlays and their settings..
if (
viz.is_ohlc
):
# print(f'adding {viz.name} to overlay')
mxmn_groups[viz.name] = out
else:
pi.vb._set_yrange(yrange=yrange)
profiler(
f'{viz.name}@{chart_name} `Viz.plot.vb._set_yrange()`'
)
profiler(f'<{chart_name}>.interact_graphics_cycle({name})')
# if no overlays, set lone chart's yrange and short circuit
if (
len(mxmn_groups) < 2
):
print(f'ONLY ranging major: {viz.name}')
for viz_name, out in mxmn_groups.items():
(
ixrng,
read_slc,
yrange,
) = out
# determine start datum in view
viz = chart._vizs[viz_name]
viz.plot.vb._set_yrange(
yrange=yrange,
)
return
# proportional group auto-scaling per overlay set. # proportional group auto-scaling per overlay set.
# -> loop through overlays on each multi-chart widget # -> loop through overlays on each multi-chart widget
# and scale all y-ranges based on autoscale config. # and scale all y-ranges based on autoscale config.
# -> for any "group" overlay we want to dispersion normalize # -> for any "group" overlay we want to dispersion normalize
# and scale minor charts onto the major chart: the chart # and scale minor charts onto the major chart: the chart
# with the most dispersion in the set. # with the most dispersion in the set.
major_viz: Viz = None
major_mx: float = 0 major_mx: float = 0
major_mn: float = float('inf') major_mn: float = float('inf')
mx_up_rng: float = 0 mx_up_rng: float = 0
mn_down_rng: float = 0 mn_down_rng: float = 0
mx_disp: float = 0 mx_disp: float = 0
# collect certain flows have grapics objects **in seperate
# plots/viewboxes** into groups and do a common calc to
# determine auto-ranging input for `._set_yrange()`.
# this is primarly used for our so called "log-linearized
# multi-plot" overlay technique.
start_datums: dict[ start_datums: dict[
ViewBox, ViewBox,
tuple[ tuple[
@ -1042,79 +971,141 @@ class ChartView(ViewBox):
], ],
] = {} ] = {}
max_istart: float = 0 max_istart: float = 0
major_viz: Viz = None
# major_in_view: np.ndarray = None # major_in_view: np.ndarray = None
for viz_name, out in mxmn_groups.items(): for name, viz in chart._vizs.items():
if not viz.render:
# print(f'skipping {flow.name}')
continue
# pass in no array which will read and render from the last
# passed array (normally provided by the display loop.)
in_view, i_read_range, _ = viz.update_graphics()
if not in_view:
continue
profiler(f'{viz.name}@{chart_name} `Viz.update_graphics()`')
out = viz.maxmin(i_read_range=i_read_range)
if out is None:
log.warning(f'No yrange provided for {name}!?')
return
( (
ixrng, ixrng,
read_slc, read_slc,
(ymn, ymx), yrange
) = out ) = out
# determine start datum in view pi = viz.plot
viz = chart._vizs[viz_name]
arr = viz.shm.array
in_view = arr[read_slc]
row_start = arr[read_slc.start - 1]
max_istart = max(in_view[0]['index'], max_istart) # handle multiple graphics-objs per viewbox cases
mxmn = mxmns_by_common_pi.get(pi)
if mxmn:
yrange = mxmns_by_common_pi[pi] = (
min(yrange[0], mxmn[0]),
max(yrange[1], mxmn[1]),
)
if viz.is_ohlc:
y_med = np.median(in_view['close'])
y_start = row_start['open']
else: else:
y_med = np.median(in_view[viz.name]) mxmns_by_common_pi[pi] = yrange
y_start = row_start[viz.name]
# x_start = ixrng[0] # handle overlay log-linearized group scaling cases
# print( # TODO: a better predicate here, likely something
# f'{viz.name} ->\n' # to do with overlays and their settings..
# f'(x_start: {x_start}, y_start: {y_start}\n' if (
# ) viz.is_ohlc
start_datums[viz.plot.vb] = ( ):
viz, ymn, ymx = yrange
y_start, # print(f'adding {viz.name} to overlay')
ymn, # mxmn_groups[viz.name] = out
ymx, # viz = chart._vizs[viz_name]
y_med,
read_slc, # determine start datum in view
in_view, arr = viz.shm.array
in_view = arr[read_slc]
row_start = arr[read_slc.start - 1]
max_istart = max(in_view[0]['index'], max_istart)
if viz.is_ohlc:
y_med = np.median(in_view['close'])
y_start = row_start['open']
else:
y_med = np.median(in_view[viz.name])
y_start = row_start[viz.name]
# x_start = ixrng[0]
# print(
# f'{viz.name} ->\n'
# f'(x_start: {x_start}, y_start: {y_start}\n'
# )
start_datums[viz.plot.vb] = (
viz,
y_start,
ymn,
ymx,
y_med,
read_slc,
in_view,
)
# find curve with max dispersion
disp = abs(ymx - ymn) / y_med
# track the "major" curve as the curve with most
# dispersion.
if disp > mx_disp:
major_viz = viz
mx_disp = disp
major_mn = ymn
major_mx = ymx
# major_in_view = in_view
# compute directional (up/down) y-range % swing/dispersion
y_ref = y_med
up_rng = (ymx - y_ref) / y_ref
down_rng = (ymn - y_ref) / y_ref
mx_up_rng = max(mx_up_rng, up_rng)
mn_down_rng = min(mn_down_rng, down_rng)
# print(
# f'{viz.name}@{chart_name} group mxmn calc\n'
# '--------------------\n'
# f'y_start: {y_start}\n'
# f'ymn: {ymn}\n'
# f'ymx: {ymx}\n'
# f'mx_disp: {mx_disp}\n'
# f'up %: {up_rng * 100}\n'
# f'down %: {down_rng * 100}\n'
# f'mx up %: {mx_up_rng * 100}\n'
# f'mn down %: {mn_down_rng * 100}\n'
# )
# non-overlay group case
else:
pi.vb._set_yrange(yrange=yrange)
profiler(
f'{viz.name}@{chart_name} `Viz.plot.vb._set_yrange()`'
)
profiler(f'<{chart_name}>.interact_graphics_cycle({name})')
if not start_datums:
return
# if no overlays, set lone chart's yrange and short circuit
if (
len(start_datums) < 2
):
# print(f'ONLY ranging major: {viz.name}')
major_viz.plot.vb._set_yrange(
yrange=yrange,
) )
return
# find curve with max dispersion # conduct "log-linearized multi-plot" scalings for all groups
disp = abs(ymx - ymn) / y_med
# track the "major" curve as the curve with most
# dispersion.
if disp > mx_disp:
major_viz = viz
mx_disp = disp
major_mn = ymn
major_mx = ymx
# major_in_view = in_view
# compute directional (up/down) y-range % swing/dispersion
y_ref = y_med
up_rng = (ymx - y_ref) / y_ref
down_rng = (ymn - y_ref) / y_ref
mx_up_rng = max(mx_up_rng, up_rng)
mn_down_rng = min(mn_down_rng, down_rng)
# print(
# f'{viz.name}@{chart_name} group mxmn calc\n'
# '--------------------\n'
# f'y_start: {y_start}\n'
# f'ymn: {ymn}\n'
# f'ymx: {ymx}\n'
# f'mx_disp: {mx_disp}\n'
# f'up %: {up_rng * 100}\n'
# f'down %: {down_rng * 100}\n'
# f'mx up %: {mx_up_rng * 100}\n'
# f'mn down %: {mn_down_rng * 100}\n'
# )
for ( for (
view, view,
( (