Right, handle y-ranging multiple paths per plot
We were hacking this before using the whole `ChartView._maxmin()` setting stuff since in some cases you might want similarly ranged paths on the same view, but of course you need to max/min them together.. This adds that group sorting by using a table of `dict[PlotItem, tuple[float, float]` and taking the abs highest/lowest value for each plot in the viz interaction update loop. Also removes the now commented signal registry calls and thus `._yranger`, drops the `set_range: bool` from `._set_yrange` and adds and extra `.maybe_downsample_graphics()` to the mouse wheel handler to avoid a weird slow debounce where ds-ing is delayed until a further interaction.multichartz
							parent
							
								
									f5b15aba11
								
							
						
					
					
						commit
						99fbce3231
					
				| 
						 | 
				
			
			@ -20,7 +20,6 @@ Chart view box primitives
 | 
			
		|||
"""
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
from contextlib import asynccontextmanager
 | 
			
		||||
from functools import partial
 | 
			
		||||
import time
 | 
			
		||||
from typing import (
 | 
			
		||||
    Optional,
 | 
			
		||||
| 
						 | 
				
			
			@ -393,7 +392,6 @@ class ChartView(ViewBox):
 | 
			
		|||
 | 
			
		||||
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
 | 
			
		||||
        self._ic = None
 | 
			
		||||
        self._yranger: Callable | None = None
 | 
			
		||||
 | 
			
		||||
        # TODO: probably just assign this whenever a new `PlotItem` is
 | 
			
		||||
        # allocated since they're 1to1 with views..
 | 
			
		||||
| 
						 | 
				
			
			@ -568,6 +566,7 @@ class ChartView(ViewBox):
 | 
			
		|||
            # "double work" is causing latency when these missing event
 | 
			
		||||
            # fires don't happen?
 | 
			
		||||
            self.maybe_downsample_graphics()
 | 
			
		||||
            self.maybe_downsample_graphics()
 | 
			
		||||
 | 
			
		||||
            ev.accept()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -763,7 +762,6 @@ class ChartView(ViewBox):
 | 
			
		|||
            ms_threshold=ms_slower_then,
 | 
			
		||||
            delayed=True,
 | 
			
		||||
        )
 | 
			
		||||
        set_range = True
 | 
			
		||||
        chart = self._chart
 | 
			
		||||
 | 
			
		||||
        # view has been set in 'axis' mode
 | 
			
		||||
| 
						 | 
				
			
			@ -772,8 +770,8 @@ class ChartView(ViewBox):
 | 
			
		|||
        # - disable autoranging
 | 
			
		||||
        # - remove any y range limits
 | 
			
		||||
        if chart._static_yrange == 'axis':
 | 
			
		||||
            set_range = False
 | 
			
		||||
            self.setLimits(yMin=None, yMax=None)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # static y-range has been set likely by
 | 
			
		||||
        # a specialized FSP configuration.
 | 
			
		||||
| 
						 | 
				
			
			@ -786,8 +784,6 @@ class ChartView(ViewBox):
 | 
			
		|||
        elif yrange is not None:
 | 
			
		||||
            ylow, yhigh = yrange
 | 
			
		||||
 | 
			
		||||
        if set_range:
 | 
			
		||||
 | 
			
		||||
        # XXX: only compute the mxmn range
 | 
			
		||||
        # if none is provided as input!
 | 
			
		||||
        if not yrange:
 | 
			
		||||
| 
						 | 
				
			
			@ -815,19 +811,36 @@ class ChartView(ViewBox):
 | 
			
		|||
 | 
			
		||||
        # view margins: stay within a % of the "true range"
 | 
			
		||||
        diff = yhigh - ylow
 | 
			
		||||
            ylow = ylow - (diff * range_margin)
 | 
			
		||||
            yhigh = yhigh + (diff * range_margin)
 | 
			
		||||
        ylow = max(
 | 
			
		||||
            ylow - (diff * range_margin),
 | 
			
		||||
            0,
 | 
			
		||||
        )
 | 
			
		||||
        yhigh = min(
 | 
			
		||||
            yhigh + (diff * range_margin),
 | 
			
		||||
            yhigh * (1 + range_margin),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # XXX: this often needs to be unset
 | 
			
		||||
        # to get different view modes to operate
 | 
			
		||||
        # correctly!
 | 
			
		||||
 | 
			
		||||
        profiler(
 | 
			
		||||
            f'set limits {self.name}:\n'
 | 
			
		||||
            f'ylow: {ylow}\n'
 | 
			
		||||
            f'yhigh: {yhigh}\n'
 | 
			
		||||
        )
 | 
			
		||||
        self.setYRange(
 | 
			
		||||
            ylow,
 | 
			
		||||
            yhigh,
 | 
			
		||||
            padding=0,
 | 
			
		||||
        )
 | 
			
		||||
        self.setLimits(
 | 
			
		||||
            yMin=ylow,
 | 
			
		||||
            yMax=yhigh,
 | 
			
		||||
        )
 | 
			
		||||
            self.setYRange(ylow, yhigh)
 | 
			
		||||
            profiler(f'set limits: {(ylow, yhigh)}')
 | 
			
		||||
 | 
			
		||||
        # LOL: yet anothercucking pg buggg..
 | 
			
		||||
        # can't use `msg=f'setYRange({ylow}, {yhigh}')`
 | 
			
		||||
        profiler.finish()
 | 
			
		||||
 | 
			
		||||
    def enable_auto_yrange(
 | 
			
		||||
| 
						 | 
				
			
			@ -844,12 +857,6 @@ class ChartView(ViewBox):
 | 
			
		|||
        if src_vb is None:
 | 
			
		||||
            src_vb = self
 | 
			
		||||
 | 
			
		||||
        if self._yranger is None:
 | 
			
		||||
            self._yranger = partial(
 | 
			
		||||
                self._set_yrange,
 | 
			
		||||
                viz=viz,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # re-sampling trigger:
 | 
			
		||||
        # TODO: a smarter way to avoid calling this needlessly?
 | 
			
		||||
        # 2 things i can think of:
 | 
			
		||||
| 
						 | 
				
			
			@ -866,25 +873,13 @@ class ChartView(ViewBox):
 | 
			
		|||
            self.maybe_downsample_graphics
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # mouse wheel doesn't emit XRangeChanged
 | 
			
		||||
        # src_vb.sigRangeChangedManually.connect(self._yranger)
 | 
			
		||||
 | 
			
		||||
    def disable_auto_yrange(self) -> None:
 | 
			
		||||
 | 
			
		||||
        # XXX: not entirely sure why we can't de-reg this..
 | 
			
		||||
        self.sigResized.disconnect(
 | 
			
		||||
            # self._yranger,
 | 
			
		||||
            self.maybe_downsample_graphics
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # self.sigRangeChangedManually.disconnect(
 | 
			
		||||
        #     self._yranger,
 | 
			
		||||
        # )
 | 
			
		||||
 | 
			
		||||
        # self.sigRangeChangedManually.disconnect(
 | 
			
		||||
        #     self.maybe_downsample_graphics
 | 
			
		||||
        # )
 | 
			
		||||
 | 
			
		||||
    def x_uppx(self) -> float:
 | 
			
		||||
        '''
 | 
			
		||||
        Return the "number of x units" within a single
 | 
			
		||||
| 
						 | 
				
			
			@ -908,14 +903,18 @@ class ChartView(ViewBox):
 | 
			
		|||
    ):
 | 
			
		||||
        profiler = Profiler(
 | 
			
		||||
            msg=f'ChartView.maybe_downsample_graphics() for {self.name}',
 | 
			
		||||
            disabled=not pg_profile_enabled(),
 | 
			
		||||
            # disabled=not pg_profile_enabled(),
 | 
			
		||||
 | 
			
		||||
            # ms_threshold=ms_slower_then,
 | 
			
		||||
 | 
			
		||||
            disabled=True,
 | 
			
		||||
            ms_threshold=4,
 | 
			
		||||
 | 
			
		||||
            # XXX: important to avoid not seeing underlying
 | 
			
		||||
            # ``.update_graphics_from_flow()`` nested profiling likely
 | 
			
		||||
            # due to the way delaying works and garbage collection of
 | 
			
		||||
            # the profiler in the delegated method calls.
 | 
			
		||||
            ms_threshold=6,
 | 
			
		||||
            # ms_threshold=ms_slower_then,
 | 
			
		||||
            delayed=True,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # TODO: a faster single-loop-iterator way of doing this XD
 | 
			
		||||
| 
						 | 
				
			
			@ -927,20 +926,22 @@ class ChartView(ViewBox):
 | 
			
		|||
            plots |= linked.subplots
 | 
			
		||||
 | 
			
		||||
        for chart_name, chart in plots.items():
 | 
			
		||||
 | 
			
		||||
            mxmns: dict[
 | 
			
		||||
                pg.PlotItem,
 | 
			
		||||
                tuple[float, float],
 | 
			
		||||
            ] = {}
 | 
			
		||||
 | 
			
		||||
            for name, viz in chart._vizs.items():
 | 
			
		||||
 | 
			
		||||
                if (
 | 
			
		||||
                    not viz.render
 | 
			
		||||
 | 
			
		||||
                    # XXX: super important to be aware of this.
 | 
			
		||||
                    # or not flow.graphics.isVisible()
 | 
			
		||||
                ):
 | 
			
		||||
                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.)
 | 
			
		||||
                i_read_range, _ = viz.update_graphics()
 | 
			
		||||
                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}!?')
 | 
			
		||||
| 
						 | 
				
			
			@ -951,13 +952,40 @@ class ChartView(ViewBox):
 | 
			
		|||
                    yrange
 | 
			
		||||
                ) = out
 | 
			
		||||
 | 
			
		||||
                pi = viz.plot
 | 
			
		||||
                mxmn = mxmns.get(pi)
 | 
			
		||||
                if mxmn:
 | 
			
		||||
                    yrange = mxmns[pi] = (
 | 
			
		||||
                        min(yrange[0], mxmn[0]),
 | 
			
		||||
                        max(yrange[1], mxmn[1]),
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                else:
 | 
			
		||||
                    mxmns[viz.plot] = yrange
 | 
			
		||||
 | 
			
		||||
                pi.vb._set_yrange(yrange=yrange)
 | 
			
		||||
                profiler(
 | 
			
		||||
                    f'{viz.name}@{chart_name} `Viz.plot.vb._set_yrange()`'
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                # if 'dolla_vlm' in viz.name:
 | 
			
		||||
                #     print(
 | 
			
		||||
                #         f'AUTO-Y-RANGING: {viz.name}\n'
 | 
			
		||||
                #         f'i_read_range: {i_read_range}\n'
 | 
			
		||||
                #         f'ixrng: {ixrng}\n'
 | 
			
		||||
                #         f'yrange: {yrange}\n'
 | 
			
		||||
                #     )
 | 
			
		||||
                viz.plot.vb._set_yrange(yrange=yrange)
 | 
			
		||||
                #     (
 | 
			
		||||
                #         view_xrange,
 | 
			
		||||
                #         view_yrange,
 | 
			
		||||
                #     ) = viz.plot.vb.viewRange()
 | 
			
		||||
                #     print(
 | 
			
		||||
                #         f'{viz.name}@{chart_name}\n'
 | 
			
		||||
                #         f'  xRange -> {view_xrange}\n'
 | 
			
		||||
                #         f'  yRange -> {view_yrange}\n'
 | 
			
		||||
                #     )
 | 
			
		||||
 | 
			
		||||
            profiler(f'autoscaled overlays {chart_name}')
 | 
			
		||||
 | 
			
		||||
        profiler(f'<{chart_name}>.update_graphics_from_flow({name})')
 | 
			
		||||
        profiler.finish()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue