Support "pin-to-target-curve" overlay method again
Yah yah, i know it's the same as before (the N > 2 curves case with
out-of-range-minor rescaling the previously scaled curves isn't fixed
yet...) but, this is a much better and optional implementation in less
code. Further we're now better leveraging various new cached properties
and methods on `Viz`.
We now handle different `overlay_technique: str` options using `match:`
syntax in the 2ndary scaling loop, stash the returns scalars per curve
in `overlay_table`, and store and iterate the curves by dispersion
measure sort order.
Further wrt "pin-to-target-curve" mode, which currently still pins to
the largest measured dispersion curve in the overlay set:
- pop major Ci overlay table entries at start for sub-calcs usage when
  handling the "minor requires major rescale after pin" case.
- (finally) correctly rescale the major curve y-mxmn to whatever the
  latest minor overlay curve by calcing the inverse transform from the
  minor *at that point*:
  - the intersect point being that which the minor has starts support on
    the major's x-domain* using the new `Viz.scalars_from_index()` and,
  - checking that the minor is not out of range (versus what the major's
    transform calcs it to be, in which case,
  - calc the inverse transform from the current out-of-range minor and
    use it to project the new y-mxmn for the major/target based on the
    same intersect-reference point in the x-domain used by the minor.
  - always handle the target-major Ci specially by only setting the
    `mx_ymn` / `mx_ymn` value when iterating that entry in the overlay
    table.
  - add todos around also doing the last sub-sub bullet for all previously
    major-transform scaled minor overlays (this is coming next..i hope).
- add a final 3rd overlay loop which goes through a final `scaled: dict`
  to apply all range values to each view; this is where we will
  eventually solve that last edge case of an out-of-range minor's
  scaling needing to be used to rescale already scaled minors XD
			
			
				log_linearized_curve_overlays
			
			
		
							parent
							
								
									4d11c5c89c
								
							
						
					
					
						commit
						6601dea8cc
					
				|  | @ -62,8 +62,8 @@ class OverlayT(Struct): | |||
|     in the target co-domain. | ||||
| 
 | ||||
|     ''' | ||||
|     start_t: float | None = None | ||||
|     viz: Viz | None = None | ||||
|     start_t: float | None = None | ||||
| 
 | ||||
|     # % "range" computed from some ref value to the mn/mx | ||||
|     rng: float | None = None | ||||
|  | @ -201,13 +201,13 @@ def overlay_viewlists( | |||
|     ] | None = None, | ||||
| 
 | ||||
|     overlay_technique: Literal[ | ||||
|         'loglin_to_first', | ||||
|         'loglin_to_sigma', | ||||
|         'mnmx', | ||||
|         'loglin_ref_to_curve', | ||||
|         'loglin_ref_to_first', | ||||
|         'mxmn', | ||||
|         'solo', | ||||
|     ] = 'loglin_to_first', | ||||
|     ] = 'loglin_ref_to_curve', | ||||
| 
 | ||||
|     # internal instrumentation | ||||
|     # internal debug | ||||
|     debug_print: bool = False, | ||||
| 
 | ||||
| ) -> None: | ||||
|  | @ -236,8 +236,6 @@ def overlay_viewlists( | |||
|         # -> for any "group" overlay we want to dispersion normalize | ||||
|         #    and scale minor charts onto the major chart: the chart | ||||
|         #    with the most dispersion in the set. | ||||
|         major_sigma_viz: Viz = None | ||||
|         mx_disp: float = 0 | ||||
| 
 | ||||
|         # collect certain flows have grapics objects **in seperate | ||||
|         # plots/viewboxes** into groups and do a common calc to | ||||
|  | @ -245,8 +243,9 @@ def overlay_viewlists( | |||
|         # this is primarly used for our so called "log-linearized | ||||
|         # multi-plot" overlay technique. | ||||
|         overlay_table: dict[ | ||||
|             ChartView, | ||||
|             float, | ||||
|             tuple[ | ||||
|                 ChartView, | ||||
|                 Viz, | ||||
|                 float,  # y start | ||||
|                 float,  # y min | ||||
|  | @ -254,6 +253,8 @@ def overlay_viewlists( | |||
|                 float,  # y median | ||||
|                 slice,  # in-view array slice | ||||
|                 np.ndarray,  # in-view array | ||||
|                 float,  # returns up scalar | ||||
|                 float,  # return down scalar | ||||
|             ], | ||||
|         ] = {} | ||||
| 
 | ||||
|  | @ -323,11 +324,15 @@ def overlay_viewlists( | |||
|             profiler(f'{viz.name}@{chart_name} common pi sort') | ||||
| 
 | ||||
|             # non-overlay group case | ||||
|             if not viz.is_ohlc: | ||||
|             if ( | ||||
|                 not viz.is_ohlc | ||||
|                 or overlay_technique == 'solo' | ||||
|             ): | ||||
|                 pi.vb._set_yrange(yrange=yrange) | ||||
|                 profiler( | ||||
|                     f'{viz.name}@{chart_name} simple std `._set_yrange()`' | ||||
|                 ) | ||||
|                 continue | ||||
| 
 | ||||
|             # handle overlay log-linearized group scaling cases | ||||
|             # TODO: a better predicate here, likely something | ||||
|  | @ -338,15 +343,12 @@ def overlay_viewlists( | |||
|                 ymn, ymx = yrange | ||||
| 
 | ||||
|                 # determine start datum in view | ||||
|                 arr = viz.shm.array | ||||
|                 in_view = arr[read_slc] | ||||
|                 in_view = viz.vs.in_view | ||||
|                 if not in_view.size: | ||||
|                     log.warning(f'{viz.name} not in view?') | ||||
|                     continue | ||||
| 
 | ||||
|                 # row_start = arr[read_slc.start - 1] | ||||
|                 row_start = arr[read_slc.start] | ||||
| 
 | ||||
|                 row_start = in_view[0] | ||||
|                 if viz.is_ohlc: | ||||
|                     y_ref = row_start['open'] | ||||
|                 else: | ||||
|  | @ -355,9 +357,11 @@ def overlay_viewlists( | |||
|                 profiler(f'{viz.name}@{chart_name} MINOR curve median') | ||||
| 
 | ||||
|                 key = 'open' if viz.is_ohlc else viz.name | ||||
|                 start_t = in_view[0]['time'] | ||||
|                 r_down = (ymn - y_ref) / y_ref | ||||
|                 start_t = row_start['time'] | ||||
| 
 | ||||
|                 # returns scalars | ||||
|                 r_up = (ymx - y_ref) / y_ref | ||||
|                 r_down = (ymn - y_ref) / y_ref | ||||
| 
 | ||||
|                 msg = ( | ||||
|                     f'### {viz.name}@{chart_name} ###\n' | ||||
|  | @ -431,6 +435,8 @@ def overlay_viewlists( | |||
|                             if debug_print: | ||||
|                                 print(msg) | ||||
| 
 | ||||
|                 # is the current up `OverlayT` not yet defined or | ||||
|                 # the current `r_up` greater then the previous max. | ||||
|                 if ( | ||||
|                     upt.rng is None | ||||
|                     or ( | ||||
|  | @ -483,27 +489,37 @@ def overlay_viewlists( | |||
|                             profiler(msg) | ||||
|                             print(msg) | ||||
| 
 | ||||
|                 # find curve with max dispersion | ||||
|                 disp = abs(ymx - ymn) / y_ref | ||||
|                 if disp > mx_disp: | ||||
|                     major_sigma_viz = viz | ||||
|                     mx_disp = disp | ||||
|                 # disp = viz.disp_from_range(yref=y_ref) | ||||
|                 # if disp is None: | ||||
|                 #     print(f'{viz.name}: WTF NO DISP') | ||||
|                 #     continue | ||||
| 
 | ||||
|                 overlay_table[viz.plot.vb] = ( | ||||
|                 # r_up, r_dn = disp | ||||
|                 disp = r_up - r_down | ||||
| 
 | ||||
|                 # register curves by a "full" dispersion metric for | ||||
|                 # later sort order in the overlay (technique | ||||
|                 # ) application loop below. | ||||
|                 overlay_table[disp] = ( | ||||
|                     viz.plot.vb, | ||||
|                     viz, | ||||
| 
 | ||||
|                     y_ref, | ||||
|                     ymn, | ||||
|                     ymx, | ||||
| 
 | ||||
|                     read_slc, | ||||
|                     in_view, | ||||
|                 ) | ||||
| 
 | ||||
|                     r_up, | ||||
|                     r_down, | ||||
|                 ) | ||||
|                 profiler(f'{viz.name}@{chart_name} yrange scan complete') | ||||
| 
 | ||||
|         # NOTE: if no there were no overlay charts | ||||
|         # detected/collected (could be either no group detected or | ||||
|         # chart with a single symbol, thus a single viz/overlay) | ||||
|         # then we ONLY set the lone chart's (viz) yrange and short | ||||
|         # then we ONLY set the mone chart's (viz) yrange and short | ||||
|         # circuit to the next chart in the linked charts loop. IOW | ||||
|         # there's no reason to go through the overlay dispersion | ||||
|         # scaling in the next loop below when only one curve is | ||||
|  | @ -534,13 +550,20 @@ def overlay_viewlists( | |||
| 
 | ||||
|         elif ( | ||||
|             mxmns_by_common_pi | ||||
|             and not major_sigma_viz | ||||
|             # and not major_sigma_viz | ||||
|             and not overlay_table | ||||
|         ): | ||||
|             # move to next chart in linked set since | ||||
|             # no overlay transforming is needed. | ||||
|             continue | ||||
| 
 | ||||
|         profiler(f'<{chart_name}>.interact_graphics_cycle({name})') | ||||
|         msg = ( | ||||
|             f'`Viz` curve first pass complete\n' | ||||
|             f'overlay_table: {overlay_table.keys()}\n' | ||||
|         ) | ||||
|         profiler(msg) | ||||
|         if debug_print: | ||||
|             print(msg) | ||||
| 
 | ||||
|         # if a minor curves scaling brings it "outside" the range of | ||||
|         # the major curve (in major curve co-domain terms) then we | ||||
|  | @ -548,21 +571,39 @@ def overlay_viewlists( | |||
|         # below placeholder denotes when this occurs. | ||||
|         # group_mxmn: None | tuple[float, float] = None | ||||
| 
 | ||||
|         # TODO: probably re-write this loop as a compiled cpython or | ||||
|         # numba func. | ||||
|         r_up_mx: float | ||||
|         r_dn_mn: float | ||||
|         mx_disp = max(overlay_table) | ||||
|         mx_entry = overlay_table[mx_disp] | ||||
|         ( | ||||
|             _,  # viewbox | ||||
|             mx_viz,  # viz | ||||
|             _,  # y_ref | ||||
|             mx_ymn, | ||||
|             mx_ymx, | ||||
|             _,  # read_slc | ||||
|             _,  # in_view array | ||||
|             r_up_mx, | ||||
|             r_dn_mn, | ||||
|         ) = mx_entry | ||||
| 
 | ||||
|         scaled: dict[float, tuple[float, float, float]] = {} | ||||
| 
 | ||||
|         # conduct "log-linearized multi-plot" scalings for all groups | ||||
|         for ( | ||||
|             view, | ||||
|         # -> iterate all curves Ci in dispersion-measure sorted order | ||||
|         # going from smallest swing to largest. | ||||
|         for full_disp in sorted(overlay_table): | ||||
|             ( | ||||
|                 view, | ||||
|                 viz, | ||||
|                 y_start, | ||||
|                 y_min, | ||||
|                 y_max, | ||||
|                 read_slc, | ||||
|                 minor_in_view, | ||||
|             ) | ||||
|         ) in overlay_table.items(): | ||||
|                 r_up, | ||||
|                 r_dn, | ||||
|             ) = overlay_table[full_disp] | ||||
| 
 | ||||
|             key = 'open' if viz.is_ohlc else viz.name | ||||
| 
 | ||||
|  | @ -575,8 +616,99 @@ def overlay_viewlists( | |||
|                 ) | ||||
|                 continue | ||||
| 
 | ||||
|             ymn = dnt.apply_rng(y_start) | ||||
|             ymx = upt.apply_rng(y_start) | ||||
|             xref = minor_in_view[0]['time'] | ||||
|             match overlay_technique: | ||||
| 
 | ||||
|                 # Pin this curve to the "major dispersion" (or other | ||||
|                 # target) curve by finding the intersect datum and | ||||
|                 # then scaling according to the returns log-lin transort | ||||
|                 # 'at that intersect reference data'. If the pinning | ||||
|                 # results in this (minor/pinned) curve being out of view | ||||
|                 # adjust the returns scalars to match this curves min | ||||
|                 # y-range to stay in view. | ||||
|                 case 'loglin_ref_to_curve': | ||||
| 
 | ||||
|                     # TODO: technically we only need to do this here if | ||||
|                     # | ||||
|                     if viz is not mx_viz: | ||||
|                         ( | ||||
|                             i_start, | ||||
|                             y_ref_major, | ||||
|                             r_major_up_here, | ||||
|                             r_major_down_here, | ||||
|                         ) = 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) | ||||
|                         if ymn > y_min: | ||||
|                             ymn = y_min | ||||
|                             r_dn_minor = (ymn - y_start) / y_start | ||||
| 
 | ||||
|                             mx_ymn = y_ref_major * (1 + r_dn_minor) | ||||
| 
 | ||||
|                             # TODO: rescale all already scaled curves to | ||||
|                             # new increased range for this side. | ||||
|                             # for ( | ||||
|                             #     view, | ||||
|                             #     (yref, ymn, ymx) | ||||
|                             # ) in scaled.items(): | ||||
|                             #     pass | ||||
| 
 | ||||
|                         ymx = y_start * (1 + r_major_up_here) | ||||
|                         if ymx < y_max: | ||||
|                             ymx = y_max | ||||
|                             r_up_minor = (ymx - y_start) / y_start | ||||
|                             mx_ymx = y_ref_major * (1 + r_up_minor) | ||||
| 
 | ||||
|                         if debug_print: | ||||
|                             print( | ||||
|                                 f'Minor SCALARS {viz.name}:\n' | ||||
|                                 f'xref: {xref}\n' | ||||
|                                 f'dn: {r_major_down_here}\n' | ||||
|                                 f'up: {r_major_up_here}\n' | ||||
|                             ) | ||||
|                     else: | ||||
|                         if debug_print: | ||||
|                             print( | ||||
|                                 f'MAJOR SCALARS {viz.name}:\n' | ||||
|                                 f'dn: {r_dn_mn}\n' | ||||
|                                 f'up: {r_up_mx}\n' | ||||
|                             ) | ||||
|                         # target/major curve's mxmn may have been | ||||
|                         # reset by minor overlay steps above. | ||||
|                         ymn = mx_ymn | ||||
|                         ymx = mx_ymx | ||||
| 
 | ||||
|                 # Pin all curves by their first datum in view to all | ||||
|                 # others such that each curve's earliest datum provides the | ||||
|                 # reference point for returns vs. every other curve in | ||||
|                 # view. | ||||
|                 case 'loglin_ref_to_first': | ||||
|                     ymn = dnt.apply_rng(y_start) | ||||
|                     ymx = upt.apply_rng(y_start) | ||||
| 
 | ||||
|                 # Do not pin curves by log-linearizing their y-ranges, | ||||
|                 # instead allow each curve to fully scale to the | ||||
|                 # time-series in view's min and max y-values. | ||||
|                 case 'mxmn': | ||||
|                     ymn = y_min | ||||
|                     ymx = y_max | ||||
| 
 | ||||
|                 case _: | ||||
|                     raise RuntimeError( | ||||
|                         f'overlay_technique is invalid `{overlay_technique}' | ||||
|                     ) | ||||
| 
 | ||||
|             scaled[view] = (y_start, ymn, ymx) | ||||
| 
 | ||||
|         for ( | ||||
|             view, | ||||
|             (yref, ymn, ymx) | ||||
| 
 | ||||
|         ) in scaled.items(): | ||||
| 
 | ||||
|             # NOTE XXX: we have to set each curve's range once (and | ||||
|             # ONLY ONCE) here since we're doing this entire routine | ||||
|  | @ -594,6 +726,8 @@ def overlay_viewlists( | |||
|                     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' | ||||
|  | @ -614,7 +748,10 @@ def overlay_viewlists( | |||
|                 + | ||||
|                 '\n' | ||||
|             ) | ||||
| 
 | ||||
|         profiler(f'<{chart_name}>.interact_graphics_cycle()') | ||||
| 
 | ||||
|         if not do_linked_charts: | ||||
|             return | ||||
|             break | ||||
| 
 | ||||
|     profiler.finish() | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue