From eba8488926200fa7a8fac7e3f0db6cac140c091d Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 15 Jan 2023 13:32:22 -0500 Subject: [PATCH] Add first-draft `PlotItemOverlay.group_maxmin()` Computes the maxmin values for each underlying plot's in-view range as well as the max up/down swing (in percentage terms) from the plot with most dispersion and returns a all these values plus a `dict` of plots to their ranges as part of output. --- piker/ui/_overlay.py | 97 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/piker/ui/_overlay.py b/piker/ui/_overlay.py index 7a5f047d..a17c1173 100644 --- a/piker/ui/_overlay.py +++ b/piker/ui/_overlay.py @@ -22,7 +22,6 @@ from collections import defaultdict from functools import partial from typing import ( Callable, - Optional, ) from pyqtgraph.graphicsItems.AxisItem import AxisItem @@ -246,7 +245,7 @@ class ComposedGridLayout: plot: PlotItem, name: str, - ) -> Optional[AxisItem]: + ) -> AxisItem | None: ''' Retrieve the named axis for overlayed ``plot`` or ``None`` if axis for that name is not shown. @@ -321,7 +320,7 @@ class PlotItemOverlay: def add_plotitem( self, plotitem: PlotItem, - index: Optional[int] = None, + index: int | None = None, # event/signal names which will be broadcasted to all added # (relayee) ``PlotItem``s (eg. ``ViewBox.mouseDragEvent``). @@ -376,7 +375,7 @@ class PlotItemOverlay: # TODO: drop this viewbox specific input and # allow a predicate to be passed in by user. - axis: 'Optional[int]' = None, + axis: int | None = None, *, @@ -578,3 +577,93 @@ class PlotItemOverlay: # ''' # ... + + def group_maxmin( + self, + focus_around: str | None = None, + force_min: float | None = None, + + ) -> tuple[ + float, # mn + float, # mx + float, # max range in % terms of highest sigma plot's y-range + PlotItem, # front/selected plot + ]: + ''' + Overlay "group" maxmin sorting. + + Assumes all named flows are in the same co-domain and thus can + be sorted as one set. + + Iterates all the named flows and calls the chart api to find + their range values and return. + + TODO: really we should probably have a more built-in API for + this? + + ''' + # TODO: + # - use this in the ``.ui._fsp`` mutli-maxmin stuff + # - + + # force 0 to always be in view + group_mx: float = 0 + group_mn: float = 0 + mx_up_rng: float = 0 + mn_down_rng: float = 0 + pis2ranges: dict[ + PlotItem, + tuple[float, float], + ] = {} + + for pi in self.overlays: + + # TODO: can we remove this from the widget + # and place somewhere more related to UX/Viz? + # name = pi.name + # chartw = pi.chart_widget + viz = pi.viz + # viz = chartw._vizs[name] + + out = viz.maxmin() + if out is None: + return None + + ( + (x_start, x_stop), + read_slc, + (ymn, ymx), + ) = out + + arr = viz.shm.array + + y_start = arr[read_slc.start - 1] + y_stop = arr[read_slc.stop - 1] + if viz.is_ohlc: + y_start = y_start['open'] + y_stop = y_stop['close'] + else: + y_start = y_start[viz.name] + y_stop = y_stop[viz.name] + + # update max for group + up_rng = (ymx - y_start) / y_start + down_rng = (y_stop - ymn) / y_stop + + # compute directional (up/down) y-range % swing/dispersion + mx_up_rng = max(mx_up_rng, up_rng) + mn_down_rng = min(mn_down_rng, down_rng) + + pis2ranges[pi] = (ymn, ymx) + + group_mx = max(group_mx, ymx) + if force_min is None: + group_mn = min(group_mn, ymn) + + return ( + group_mn if force_min is None else force_min, + group_mx, + mn_down_rng, + mx_up_rng, + pis2ranges, + )