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
			
			
				storage_cli
			
			
		
							parent
							
								
									55ec9ef5a0
								
							
						
					
					
						commit
						b118954bf7
					
				| 
						 | 
				
			
			@ -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