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 __future__ import annotations
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from functools import partial
|
|
||||||
import time
|
import time
|
||||||
from typing import (
|
from typing import (
|
||||||
Optional,
|
Optional,
|
||||||
|
@ -393,7 +392,6 @@ class ChartView(ViewBox):
|
||||||
|
|
||||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||||
self._ic = None
|
self._ic = None
|
||||||
self._yranger: Callable | None = None
|
|
||||||
|
|
||||||
# TODO: probably just assign this whenever a new `PlotItem` is
|
# TODO: probably just assign this whenever a new `PlotItem` is
|
||||||
# allocated since they're 1to1 with views..
|
# allocated since they're 1to1 with views..
|
||||||
|
@ -568,6 +566,7 @@ class ChartView(ViewBox):
|
||||||
# "double work" is causing latency when these missing event
|
# "double work" is causing latency when these missing event
|
||||||
# fires don't happen?
|
# fires don't happen?
|
||||||
self.maybe_downsample_graphics()
|
self.maybe_downsample_graphics()
|
||||||
|
self.maybe_downsample_graphics()
|
||||||
|
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
|
||||||
|
@ -763,7 +762,6 @@ class ChartView(ViewBox):
|
||||||
ms_threshold=ms_slower_then,
|
ms_threshold=ms_slower_then,
|
||||||
delayed=True,
|
delayed=True,
|
||||||
)
|
)
|
||||||
set_range = True
|
|
||||||
chart = self._chart
|
chart = self._chart
|
||||||
|
|
||||||
# view has been set in 'axis' mode
|
# view has been set in 'axis' mode
|
||||||
|
@ -772,8 +770,8 @@ class ChartView(ViewBox):
|
||||||
# - disable autoranging
|
# - disable autoranging
|
||||||
# - remove any y range limits
|
# - remove any y range limits
|
||||||
if chart._static_yrange == 'axis':
|
if chart._static_yrange == 'axis':
|
||||||
set_range = False
|
|
||||||
self.setLimits(yMin=None, yMax=None)
|
self.setLimits(yMin=None, yMax=None)
|
||||||
|
return
|
||||||
|
|
||||||
# static y-range has been set likely by
|
# static y-range has been set likely by
|
||||||
# a specialized FSP configuration.
|
# a specialized FSP configuration.
|
||||||
|
@ -786,48 +784,63 @@ class ChartView(ViewBox):
|
||||||
elif yrange is not None:
|
elif yrange is not None:
|
||||||
ylow, yhigh = yrange
|
ylow, yhigh = yrange
|
||||||
|
|
||||||
if set_range:
|
# XXX: only compute the mxmn range
|
||||||
|
# if none is provided as input!
|
||||||
|
if not yrange:
|
||||||
|
|
||||||
# XXX: only compute the mxmn range
|
if not viz:
|
||||||
# if none is provided as input!
|
breakpoint()
|
||||||
if not yrange:
|
|
||||||
|
|
||||||
if not viz:
|
out = viz.maxmin()
|
||||||
breakpoint()
|
if out is None:
|
||||||
|
log.warning(f'No yrange provided for {name}!?')
|
||||||
|
return
|
||||||
|
(
|
||||||
|
ixrng,
|
||||||
|
_,
|
||||||
|
yrange
|
||||||
|
) = out
|
||||||
|
|
||||||
out = viz.maxmin()
|
profiler(f'`{self.name}:Viz.maxmin()` -> {ixrng}=>{yrange}')
|
||||||
if out is None:
|
|
||||||
log.warning(f'No yrange provided for {name}!?')
|
|
||||||
return
|
|
||||||
(
|
|
||||||
ixrng,
|
|
||||||
_,
|
|
||||||
yrange
|
|
||||||
) = out
|
|
||||||
|
|
||||||
profiler(f'`{self.name}:Viz.maxmin()` -> {ixrng}=>{yrange}')
|
if yrange is None:
|
||||||
|
log.warning(f'No yrange provided for {name}!?')
|
||||||
if yrange is None:
|
return
|
||||||
log.warning(f'No yrange provided for {name}!?')
|
|
||||||
return
|
|
||||||
|
|
||||||
ylow, yhigh = yrange
|
ylow, yhigh = yrange
|
||||||
|
|
||||||
# view margins: stay within a % of the "true range"
|
# view margins: stay within a % of the "true range"
|
||||||
diff = yhigh - ylow
|
diff = yhigh - ylow
|
||||||
ylow = ylow - (diff * range_margin)
|
ylow = max(
|
||||||
yhigh = yhigh + (diff * range_margin)
|
ylow - (diff * range_margin),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
yhigh = min(
|
||||||
|
yhigh + (diff * range_margin),
|
||||||
|
yhigh * (1 + range_margin),
|
||||||
|
)
|
||||||
|
|
||||||
# XXX: this often needs to be unset
|
# XXX: this often needs to be unset
|
||||||
# to get different view modes to operate
|
# to get different view modes to operate
|
||||||
# correctly!
|
# correctly!
|
||||||
self.setLimits(
|
|
||||||
yMin=ylow,
|
|
||||||
yMax=yhigh,
|
|
||||||
)
|
|
||||||
self.setYRange(ylow, yhigh)
|
|
||||||
profiler(f'set limits: {(ylow, yhigh)}')
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
# LOL: yet anothercucking pg buggg..
|
||||||
|
# can't use `msg=f'setYRange({ylow}, {yhigh}')`
|
||||||
profiler.finish()
|
profiler.finish()
|
||||||
|
|
||||||
def enable_auto_yrange(
|
def enable_auto_yrange(
|
||||||
|
@ -844,12 +857,6 @@ class ChartView(ViewBox):
|
||||||
if src_vb is None:
|
if src_vb is None:
|
||||||
src_vb = self
|
src_vb = self
|
||||||
|
|
||||||
if self._yranger is None:
|
|
||||||
self._yranger = partial(
|
|
||||||
self._set_yrange,
|
|
||||||
viz=viz,
|
|
||||||
)
|
|
||||||
|
|
||||||
# re-sampling trigger:
|
# re-sampling trigger:
|
||||||
# TODO: a smarter way to avoid calling this needlessly?
|
# TODO: a smarter way to avoid calling this needlessly?
|
||||||
# 2 things i can think of:
|
# 2 things i can think of:
|
||||||
|
@ -866,25 +873,13 @@ class ChartView(ViewBox):
|
||||||
self.maybe_downsample_graphics
|
self.maybe_downsample_graphics
|
||||||
)
|
)
|
||||||
|
|
||||||
# mouse wheel doesn't emit XRangeChanged
|
|
||||||
# src_vb.sigRangeChangedManually.connect(self._yranger)
|
|
||||||
|
|
||||||
def disable_auto_yrange(self) -> None:
|
def disable_auto_yrange(self) -> None:
|
||||||
|
|
||||||
# XXX: not entirely sure why we can't de-reg this..
|
# XXX: not entirely sure why we can't de-reg this..
|
||||||
self.sigResized.disconnect(
|
self.sigResized.disconnect(
|
||||||
# self._yranger,
|
|
||||||
self.maybe_downsample_graphics
|
self.maybe_downsample_graphics
|
||||||
)
|
)
|
||||||
|
|
||||||
# self.sigRangeChangedManually.disconnect(
|
|
||||||
# self._yranger,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# self.sigRangeChangedManually.disconnect(
|
|
||||||
# self.maybe_downsample_graphics
|
|
||||||
# )
|
|
||||||
|
|
||||||
def x_uppx(self) -> float:
|
def x_uppx(self) -> float:
|
||||||
'''
|
'''
|
||||||
Return the "number of x units" within a single
|
Return the "number of x units" within a single
|
||||||
|
@ -908,14 +903,18 @@ class ChartView(ViewBox):
|
||||||
):
|
):
|
||||||
profiler = Profiler(
|
profiler = Profiler(
|
||||||
msg=f'ChartView.maybe_downsample_graphics() for {self.name}',
|
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
|
# XXX: important to avoid not seeing underlying
|
||||||
# ``.update_graphics_from_flow()`` nested profiling likely
|
# ``.update_graphics_from_flow()`` nested profiling likely
|
||||||
# due to the way delaying works and garbage collection of
|
# due to the way delaying works and garbage collection of
|
||||||
# the profiler in the delegated method calls.
|
# the profiler in the delegated method calls.
|
||||||
ms_threshold=6,
|
delayed=True,
|
||||||
# ms_threshold=ms_slower_then,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: a faster single-loop-iterator way of doing this XD
|
# TODO: a faster single-loop-iterator way of doing this XD
|
||||||
|
@ -927,20 +926,22 @@ class ChartView(ViewBox):
|
||||||
plots |= linked.subplots
|
plots |= linked.subplots
|
||||||
|
|
||||||
for chart_name, chart in plots.items():
|
for chart_name, chart in plots.items():
|
||||||
|
|
||||||
|
mxmns: dict[
|
||||||
|
pg.PlotItem,
|
||||||
|
tuple[float, float],
|
||||||
|
] = {}
|
||||||
|
|
||||||
for name, viz in chart._vizs.items():
|
for name, viz in chart._vizs.items():
|
||||||
|
if not viz.render:
|
||||||
if (
|
|
||||||
not viz.render
|
|
||||||
|
|
||||||
# XXX: super important to be aware of this.
|
|
||||||
# or not flow.graphics.isVisible()
|
|
||||||
):
|
|
||||||
# print(f'skipping {flow.name}')
|
# print(f'skipping {flow.name}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# pass in no array which will read and render from the last
|
# pass in no array which will read and render from the last
|
||||||
# passed array (normally provided by the display loop.)
|
# passed array (normally provided by the display loop.)
|
||||||
i_read_range, _ = viz.update_graphics()
|
i_read_range, _ = viz.update_graphics()
|
||||||
|
profiler(f'{viz.name}@{chart_name} `Viz.update_graphics()`')
|
||||||
|
|
||||||
out = viz.maxmin(i_read_range=i_read_range)
|
out = viz.maxmin(i_read_range=i_read_range)
|
||||||
if out is None:
|
if out is None:
|
||||||
log.warning(f'No yrange provided for {name}!?')
|
log.warning(f'No yrange provided for {name}!?')
|
||||||
|
@ -951,13 +952,40 @@ class ChartView(ViewBox):
|
||||||
yrange
|
yrange
|
||||||
) = out
|
) = out
|
||||||
|
|
||||||
# print(
|
pi = viz.plot
|
||||||
# f'i_read_range: {i_read_range}\n'
|
mxmn = mxmns.get(pi)
|
||||||
# f'ixrng: {ixrng}\n'
|
if mxmn:
|
||||||
# f'yrange: {yrange}\n'
|
yrange = mxmns[pi] = (
|
||||||
# )
|
min(yrange[0], mxmn[0]),
|
||||||
viz.plot.vb._set_yrange(yrange=yrange)
|
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'
|
||||||
|
# )
|
||||||
|
# (
|
||||||
|
# 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'autoscaled overlays {chart_name}')
|
||||||
|
|
||||||
profiler(f'<{chart_name}>.update_graphics_from_flow({name})')
|
profiler(f'<{chart_name}>.update_graphics_from_flow({name})')
|
||||||
|
profiler.finish()
|
||||||
|
|
Loading…
Reference in New Issue