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
							parent
							
								
									2aa5137283
								
							
						
					
					
						commit
						d0b39e8a2b
					
				| 
						 | 
					@ -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,
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    y_maj_intersect = mshm._array[abs_i_start][key]
 | 
					                    tdiff = (major_i_start_t - minor_i_start_t)
 | 
				
			||||||
                    y_minor_intersect = viz.shm._array[abs_i_start][key]
 | 
					                    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'
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # major has later timestamp adjust minor
 | 
				
			||||||
 | 
					                    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),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue