Solve a final minor-should-rescale edge case
When the minor has the same scaling as the major in a given direction we should still do back-scaling against the major-target and previous minors to avoid strange edge cases where only the target-major might not be shifted correctly to show an matched intersect point? More or less this just meant making the y-mxmn checks interval-inclusive with `>=`/`<=` operators. Also adds a shite ton of detailed comments throughout the pin-to-target method blocks and moves the final major y-range call outside the final `scaled: dict` loop.log_linearized_curve_overlays
parent
01ea706644
commit
45e97dd4c8
|
@ -81,8 +81,11 @@ class OverlayT(Struct):
|
||||||
) -> float:
|
) -> float:
|
||||||
return y_ref * (1 + self.rng)
|
return y_ref * (1 + self.rng)
|
||||||
|
|
||||||
# def loglin_from_range(
|
def scalars_from_index(
|
||||||
# self,
|
self,
|
||||||
|
xref: float,
|
||||||
|
) -> tuple[float, float]:
|
||||||
|
pass
|
||||||
|
|
||||||
# y_ref: float, # reference value for dispersion metric
|
# y_ref: float, # reference value for dispersion metric
|
||||||
# mn: float, # min y in target log-lin range
|
# mn: float, # min y in target log-lin range
|
||||||
|
@ -359,6 +362,12 @@ def overlay_viewlists(
|
||||||
key = 'open' if viz.is_ohlc else viz.name
|
key = 'open' if viz.is_ohlc else viz.name
|
||||||
start_t = row_start['time']
|
start_t = row_start['time']
|
||||||
|
|
||||||
|
# TODO: call `Viz.disp_from_range()` here!
|
||||||
|
# disp = viz.disp_from_range(yref=y_ref)
|
||||||
|
# if disp is None:
|
||||||
|
# print(f'{viz.name}: WTF NO DISP')
|
||||||
|
# continue
|
||||||
|
|
||||||
# returns scalars
|
# returns scalars
|
||||||
r_up = (ymx - y_ref) / y_ref
|
r_up = (ymx - y_ref) / y_ref
|
||||||
r_down = (ymn - y_ref) / y_ref
|
r_down = (ymn - y_ref) / y_ref
|
||||||
|
@ -489,12 +498,6 @@ def overlay_viewlists(
|
||||||
profiler(msg)
|
profiler(msg)
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
# disp = viz.disp_from_range(yref=y_ref)
|
|
||||||
# if disp is None:
|
|
||||||
# print(f'{viz.name}: WTF NO DISP')
|
|
||||||
# continue
|
|
||||||
|
|
||||||
# r_up, r_dn = disp
|
|
||||||
disp = r_up - r_down
|
disp = r_up - r_down
|
||||||
|
|
||||||
# register curves by a "full" dispersion metric for
|
# register curves by a "full" dispersion metric for
|
||||||
|
@ -576,7 +579,7 @@ def overlay_viewlists(
|
||||||
mx_disp = max(overlay_table)
|
mx_disp = max(overlay_table)
|
||||||
mx_entry = overlay_table[mx_disp]
|
mx_entry = overlay_table[mx_disp]
|
||||||
(
|
(
|
||||||
_, # viewbox
|
mx_view, # viewbox
|
||||||
mx_viz, # viz
|
mx_viz, # viz
|
||||||
_, # y_ref
|
_, # y_ref
|
||||||
mx_ymn,
|
mx_ymn,
|
||||||
|
@ -586,13 +589,17 @@ def overlay_viewlists(
|
||||||
r_up_mx,
|
r_up_mx,
|
||||||
r_dn_mn,
|
r_dn_mn,
|
||||||
) = mx_entry
|
) = mx_entry
|
||||||
|
mx_disp = r_up_mx - r_dn_mn
|
||||||
|
|
||||||
scaled: dict[float, tuple[float, float, float]] = {}
|
scaled: dict[
|
||||||
|
float,
|
||||||
|
tuple[Viz, float, float, float, float]
|
||||||
|
] = {}
|
||||||
|
|
||||||
# conduct "log-linearized multi-plot" scalings for all groups
|
# conduct "log-linearized multi-plot" scalings for all groups
|
||||||
# -> iterate all curves Ci in dispersion-measure sorted order
|
# -> iterate all curves Ci in dispersion-measure sorted order
|
||||||
# going from smallest swing to largest.
|
# going from smallest swing to largest.
|
||||||
for full_disp in sorted(overlay_table):
|
for full_disp in reversed(overlay_table):
|
||||||
(
|
(
|
||||||
view,
|
view,
|
||||||
viz,
|
viz,
|
||||||
|
@ -620,17 +627,24 @@ def overlay_viewlists(
|
||||||
match overlay_technique:
|
match overlay_technique:
|
||||||
|
|
||||||
# Pin this curve to the "major dispersion" (or other
|
# Pin this curve to the "major dispersion" (or other
|
||||||
# target) curve by finding the intersect datum and
|
# target) curve:
|
||||||
# then scaling according to the returns log-lin transort
|
#
|
||||||
# 'at that intersect reference data'. If the pinning
|
# - find the intersect datum and then scaling according
|
||||||
# results in this (minor/pinned) curve being out of view
|
# to the returns log-lin tranform 'at that intersect
|
||||||
# adjust the returns scalars to match this curves min
|
# reference data'.
|
||||||
# y-range to stay in view.
|
# - if the pinning/log-returns-based transform scaling
|
||||||
|
# results in this minor/pinned curve being out of
|
||||||
|
# view, adjust the scalars to match **this** curve's
|
||||||
|
# y-range to stay in view and then backpropagate that
|
||||||
|
# scaling to all curves, including the major-target,
|
||||||
|
# which were previously scaled before.
|
||||||
case 'loglin_ref_to_curve':
|
case 'loglin_ref_to_curve':
|
||||||
|
|
||||||
# TODO: technically we only need to do this here if
|
|
||||||
#
|
|
||||||
if viz is not mx_viz:
|
if viz is not mx_viz:
|
||||||
|
|
||||||
|
# calculate y-range scalars from the earliest
|
||||||
|
# "intersect" datum with the target-major
|
||||||
|
# (dispersion) curve so as to "pin" the curves
|
||||||
|
# in the y-domain at that spot.
|
||||||
(
|
(
|
||||||
i_start,
|
i_start,
|
||||||
y_ref_major,
|
y_ref_major,
|
||||||
|
@ -638,46 +652,83 @@ def overlay_viewlists(
|
||||||
r_major_down_here,
|
r_major_down_here,
|
||||||
) = mx_viz.scalars_from_index(xref)
|
) = mx_viz.scalars_from_index(xref)
|
||||||
|
|
||||||
# transform y-range scaling to be the same as the
|
|
||||||
# equivalent "intersect" datum on the major
|
|
||||||
# dispersion curve (or other target "pin to"
|
|
||||||
# equivalent).
|
|
||||||
ymn = y_start * (1 + r_major_down_here)
|
ymn = y_start * (1 + r_major_down_here)
|
||||||
if ymn > y_min:
|
|
||||||
|
# if this curve's y-range is detected as **not
|
||||||
|
# being in view** after applying the
|
||||||
|
# target-major's transform, adjust the
|
||||||
|
# target-major curve's range to (log-linearly)
|
||||||
|
# include it (the extra missing range) by
|
||||||
|
# adjusting the y-mxmn to this new y-range and
|
||||||
|
# applying the inverse transform of the minor
|
||||||
|
# back on the target-major (and possibly any
|
||||||
|
# other previously-scaled-to-target/major, minor
|
||||||
|
# curves).
|
||||||
|
if ymn >= y_min:
|
||||||
ymn = y_min
|
ymn = y_min
|
||||||
r_dn_minor = (ymn - y_start) / y_start
|
r_dn_minor = (ymn - y_start) / y_start
|
||||||
|
|
||||||
|
# rescale major curve's y-max to include new
|
||||||
|
# range increase required by **this minor**.
|
||||||
mx_ymn = y_ref_major * (1 + r_dn_minor)
|
mx_ymn = y_ref_major * (1 + r_dn_minor)
|
||||||
|
mx_viz.vs.yrange = mx_ymn, mx_viz.vs.yrange[1]
|
||||||
|
|
||||||
# TODO: rescale all already scaled curves to
|
# rescale all already scaled curves to new
|
||||||
# new increased range for this side.
|
# increased range for this side as
|
||||||
|
# determined by ``y_min`` staying in view;
|
||||||
|
# re-set the `scaled: dict` entry to
|
||||||
|
# ensure that this minor curve will be
|
||||||
|
# entirely in view.
|
||||||
|
# TODO: re updating already-scaled minor curves
|
||||||
|
# - is there a faster way to do this by
|
||||||
|
# mutating state on some object instead?
|
||||||
for _view in scaled:
|
for _view in scaled:
|
||||||
_yref, _ymn, _ymx = scaled[_view]
|
_viz, _yref, _ymn, _ymx, _xref = scaled[_view]
|
||||||
new_ymn = _yref * (1 + r_dn_minor)
|
(
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
r_major_down_here,
|
||||||
|
) = mx_viz.scalars_from_index(_xref)
|
||||||
|
|
||||||
# TODO: is there a faster way to do this
|
new_ymn = _yref * (1 + r_major_down_here)
|
||||||
# by mutating state on some object
|
|
||||||
# instead?
|
scaled[_view] = (
|
||||||
scaled[_view] = (_yref, new_ymn, _ymx)
|
_viz, _yref, new_ymn, _ymx, _xref)
|
||||||
|
|
||||||
|
if debug_print:
|
||||||
|
print(
|
||||||
|
f'RESCALE {_viz.name} ymn -> {new_ymn}'
|
||||||
|
f'RESCALE MAJ ymn -> {mx_ymn}'
|
||||||
|
)
|
||||||
|
|
||||||
ymx = y_start * (1 + r_major_up_here)
|
ymx = y_start * (1 + r_major_up_here)
|
||||||
if ymx < y_max:
|
|
||||||
# set the `scaled: dict` entry to ensure
|
# same as above but for minor being out-of-range
|
||||||
# that this minor curve will be entirely in
|
# on the upside.
|
||||||
# view.
|
if ymx <= y_max:
|
||||||
ymx = y_max
|
ymx = y_max
|
||||||
r_up_minor = (ymx - y_start) / y_start
|
r_up_minor = (ymx - y_start) / y_start
|
||||||
|
|
||||||
# adjust the target-major curve's range to
|
|
||||||
# (log-linearly) include this extra range by
|
|
||||||
# applying the inverse transform of the
|
|
||||||
# minor.
|
|
||||||
mx_ymx = y_ref_major * (1 + r_up_minor)
|
mx_ymx = y_ref_major * (1 + r_up_minor)
|
||||||
|
mx_viz.vs.yrange = mx_viz.vs.yrange[0], mx_ymx
|
||||||
|
|
||||||
for _view in scaled:
|
for _view in scaled:
|
||||||
_yref, _ymn, _ymx = scaled[_view]
|
_viz, _yref, _ymn, _ymx, _xref = scaled[_view]
|
||||||
new_ymx = _yref * (1 + r_up_minor)
|
(
|
||||||
scaled[_view] = (_yref, _ymn, new_ymx)
|
_,
|
||||||
|
_,
|
||||||
|
r_major_up_here,
|
||||||
|
_,
|
||||||
|
) = mx_viz.scalars_from_index(_xref)
|
||||||
|
|
||||||
|
new_ymx = _yref * (1 + r_major_up_here)
|
||||||
|
scaled[_view] = (
|
||||||
|
_viz, _yref, _ymn, new_ymx, _xref)
|
||||||
|
|
||||||
|
if debug_print:
|
||||||
|
print(
|
||||||
|
f'RESCALE {_viz.name} ymn -> {new_ymx}'
|
||||||
|
)
|
||||||
|
|
||||||
if debug_print:
|
if debug_print:
|
||||||
print(
|
print(
|
||||||
|
@ -686,6 +737,12 @@ def overlay_viewlists(
|
||||||
f'dn: {r_major_down_here}\n'
|
f'dn: {r_major_down_here}\n'
|
||||||
f'up: {r_major_up_here}\n'
|
f'up: {r_major_up_here}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# register all overlays for a final pass where we
|
||||||
|
# apply all pinned-curve y-range transform scalings.
|
||||||
|
scaled[view] = (viz, y_start, ymn, ymx, xref)
|
||||||
|
|
||||||
|
# target/dispersion MAJOR case
|
||||||
else:
|
else:
|
||||||
if debug_print:
|
if debug_print:
|
||||||
print(
|
print(
|
||||||
|
@ -693,6 +750,7 @@ def overlay_viewlists(
|
||||||
f'dn: {r_dn_mn}\n'
|
f'dn: {r_dn_mn}\n'
|
||||||
f'up: {r_up_mx}\n'
|
f'up: {r_up_mx}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
# target/major curve's mxmn may have been
|
# target/major curve's mxmn may have been
|
||||||
# reset by minor overlay steps above.
|
# reset by minor overlay steps above.
|
||||||
ymn = mx_ymn
|
ymn = mx_ymn
|
||||||
|
@ -705,56 +763,53 @@ def overlay_viewlists(
|
||||||
case 'loglin_ref_to_first':
|
case 'loglin_ref_to_first':
|
||||||
ymn = dnt.apply_rng(y_start)
|
ymn = dnt.apply_rng(y_start)
|
||||||
ymx = upt.apply_rng(y_start)
|
ymx = upt.apply_rng(y_start)
|
||||||
|
view._set_yrange(yrange=(ymn, ymx))
|
||||||
|
|
||||||
# Do not pin curves by log-linearizing their y-ranges,
|
# Do not pin curves by log-linearizing their y-ranges,
|
||||||
# instead allow each curve to fully scale to the
|
# instead allow each curve to fully scale to the
|
||||||
# time-series in view's min and max y-values.
|
# time-series in view's min and max y-values.
|
||||||
case 'mxmn':
|
case 'mxmn':
|
||||||
ymn = y_min
|
view._set_yrange(yrange=(y_min, y_max))
|
||||||
ymx = y_max
|
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'overlay_technique is invalid `{overlay_technique}'
|
f'overlay_technique is invalid `{overlay_technique}'
|
||||||
)
|
)
|
||||||
|
|
||||||
scaled[view] = (y_start, ymn, ymx)
|
if scaled:
|
||||||
|
for (
|
||||||
|
view,
|
||||||
|
(viz, yref, ymn, ymx, xref)
|
||||||
|
) in scaled.items():
|
||||||
|
|
||||||
for (
|
# NOTE XXX: we have to set each curve's range once (and
|
||||||
view,
|
# ONLY ONCE) here since we're doing this entire routine
|
||||||
(yref, ymn, ymx)
|
# inside of a single render cycle (and apparently calling
|
||||||
|
# `ViewBox.setYRange()` multiple times within one only takes
|
||||||
|
# the first call as serious...) XD
|
||||||
|
view._set_yrange(yrange=(ymn, ymx))
|
||||||
|
profiler(f'{viz.name}@{chart_name} log-SCALE minor')
|
||||||
|
|
||||||
) in scaled.items():
|
if debug_print:
|
||||||
|
print(
|
||||||
|
'------------------------------\n'
|
||||||
|
f'LOGLIN SCALE CYCLE: {viz.name}@{chart_name}\n'
|
||||||
|
f'UP MAJOR C: {upt.viz.name} with disp: {upt.rng}\n'
|
||||||
|
f'DOWN MAJOR C: {dnt.viz.name} with disp: {dnt.rng}\n'
|
||||||
|
f'SIGMA MAJOR C: {mx_viz.name} -> {mx_disp}\n'
|
||||||
|
# f'disp: {disp}\n'
|
||||||
|
f'xref for MINOR: {xref}\n'
|
||||||
|
f'y_start: {y_start}\n'
|
||||||
|
f'y min: {y_min}\n'
|
||||||
|
f'y max: {y_max}\n'
|
||||||
|
f'T scaled ymn: {ymn}\n'
|
||||||
|
f'T scaled ymx: {ymx}\n'
|
||||||
|
'------------------------------\n'
|
||||||
|
)
|
||||||
|
|
||||||
# NOTE XXX: we have to set each curve's range once (and
|
# finally, scale major curve to possibly re-scaled/modified
|
||||||
# ONLY ONCE) here since we're doing this entire routine
|
# values
|
||||||
# inside of a single render cycle (and apparently calling
|
mx_view._set_yrange(yrange=(mx_ymn, mx_ymx))
|
||||||
# `ViewBox.setYRange()` multiple times within one only takes
|
|
||||||
# the first call as serious...) XD
|
|
||||||
view._set_yrange(
|
|
||||||
yrange=(ymn, ymx),
|
|
||||||
)
|
|
||||||
profiler(f'{viz.name}@{chart_name} log-SCALE minor')
|
|
||||||
|
|
||||||
if debug_print:
|
|
||||||
print(
|
|
||||||
'------------------------------\n'
|
|
||||||
f'LOGLIN SCALE CYCLE: {viz.name}@{chart_name}\n'
|
|
||||||
f'UP MAJOR C: {upt.viz.name} with disp: {upt.rng}\n'
|
|
||||||
f'DOWN MAJOR C: {dnt.viz.name} with disp: {dnt.rng}\n'
|
|
||||||
f'disp: {disp}\n'
|
|
||||||
f'xref for MINOR: {xref}\n'
|
|
||||||
f'y_start: {y_start}\n'
|
|
||||||
f'y min: {y_min}\n'
|
|
||||||
f'y max: {y_max}\n'
|
|
||||||
f'T scaled ymn: {ymn}\n'
|
|
||||||
f'T scaled ymx: {ymx}\n'
|
|
||||||
'------------------------------\n'
|
|
||||||
)
|
|
||||||
|
|
||||||
# vrs = major_sigma_viz.plot.vb.viewRange()
|
|
||||||
# if vrs[1][0] > major_mn:
|
|
||||||
# breakpoint()
|
|
||||||
|
|
||||||
if debug_print:
|
if debug_print:
|
||||||
print(
|
print(
|
||||||
|
|
Loading…
Reference in New Issue