Drop x-range query from `ChartPlotWidget.maxmin()`
Move the `Viz.datums_range()` call into `Viz.maxmin()` itself thus minimizing the chart `.maxmin()` method to an ultra light wrapper around the viz call. Also move all profiling into the `Viz` method. Adjust `Viz.maxmin()` to return both the (rounded) x-range values which correspond to the range containing the y-domain min and max so that it can be used for up and coming overlay group maxmin calcs.multichartz
parent
cec5ef1d26
commit
aa4a48cb98
|
@ -50,7 +50,6 @@ from ._cursor import (
|
||||||
ContentsLabel,
|
ContentsLabel,
|
||||||
)
|
)
|
||||||
from ..data._sharedmem import ShmArray
|
from ..data._sharedmem import ShmArray
|
||||||
from ._l1 import L1Labels
|
|
||||||
from ._ohlc import BarItems
|
from ._ohlc import BarItems
|
||||||
from ._curve import (
|
from ._curve import (
|
||||||
Curve,
|
Curve,
|
||||||
|
@ -70,12 +69,10 @@ from ..data._source import Symbol
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from ._interaction import ChartView
|
from ._interaction import ChartView
|
||||||
from ._forms import FieldsForm
|
from ._forms import FieldsForm
|
||||||
from .._profile import pg_profile_enabled, ms_slower_then
|
|
||||||
from ._overlay import PlotItemOverlay
|
from ._overlay import PlotItemOverlay
|
||||||
from ._dataviz import Viz
|
from ._dataviz import Viz
|
||||||
from ._search import SearchWidget
|
from ._search import SearchWidget
|
||||||
from . import _pg_overrides as pgo
|
from . import _pg_overrides as pgo
|
||||||
from .._profile import Profiler
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ._display import DisplayState
|
from ._display import DisplayState
|
||||||
|
@ -857,17 +854,17 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self.sidepane: Optional[FieldsForm] = None
|
self.sidepane: Optional[FieldsForm] = None
|
||||||
|
|
||||||
# source of our custom interactions
|
# source of our custom interactions
|
||||||
self.cv = cv = self.mk_vb(name)
|
self.cv = self.mk_vb(name)
|
||||||
|
|
||||||
pi = pgo.PlotItem(
|
pi = pgo.PlotItem(
|
||||||
viewBox=cv,
|
viewBox=self.cv,
|
||||||
name=name,
|
name=name,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
pi.chart_widget = self
|
pi.chart_widget = self
|
||||||
super().__init__(
|
super().__init__(
|
||||||
background=hcolor(view_color),
|
background=hcolor(view_color),
|
||||||
viewBox=cv,
|
viewBox=self.cv,
|
||||||
# parent=None,
|
# parent=None,
|
||||||
# plotItem=None,
|
# plotItem=None,
|
||||||
# antialias=True,
|
# antialias=True,
|
||||||
|
@ -878,7 +875,9 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# give viewbox as reference to chart
|
# give viewbox as reference to chart
|
||||||
# allowing for kb controls and interactions on **this** widget
|
# allowing for kb controls and interactions on **this** widget
|
||||||
# (see our custom view mode in `._interactions.py`)
|
# (see our custom view mode in `._interactions.py`)
|
||||||
cv.chart = self
|
self.cv.chart = self
|
||||||
|
|
||||||
|
self.pi_overlay: PlotItemOverlay = PlotItemOverlay(self.plotItem)
|
||||||
|
|
||||||
# ensure internal pi matches
|
# ensure internal pi matches
|
||||||
assert self.cv is self.plotItem.vb
|
assert self.cv is self.plotItem.vb
|
||||||
|
@ -907,8 +906,6 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# show background grid
|
# show background grid
|
||||||
self.showGrid(x=False, y=True, alpha=0.3)
|
self.showGrid(x=False, y=True, alpha=0.3)
|
||||||
|
|
||||||
self.pi_overlay: PlotItemOverlay = PlotItemOverlay(self.plotItem)
|
|
||||||
|
|
||||||
# indempotent startup flag for auto-yrange subsys
|
# indempotent startup flag for auto-yrange subsys
|
||||||
# to detect the "first time" y-domain graphics begin
|
# to detect the "first time" y-domain graphics begin
|
||||||
# to be shown in the (main) graphics view.
|
# to be shown in the (main) graphics view.
|
||||||
|
@ -1302,13 +1299,6 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
If ``bars_range`` is provided use that range.
|
If ``bars_range`` is provided use that range.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
profiler = Profiler(
|
|
||||||
msg=f'`{str(self)}.maxmin(name={name})`: `{self.name}`',
|
|
||||||
disabled=not pg_profile_enabled(),
|
|
||||||
ms_threshold=ms_slower_then,
|
|
||||||
delayed=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: here we should instead look up the ``Viz.shm.array``
|
# TODO: here we should instead look up the ``Viz.shm.array``
|
||||||
# and read directly from shm to avoid copying to memory first
|
# and read directly from shm to avoid copying to memory first
|
||||||
# and then reading it again here.
|
# and then reading it again here.
|
||||||
|
@ -1316,36 +1306,21 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
viz = self._vizs.get(viz_key)
|
viz = self._vizs.get(viz_key)
|
||||||
if viz is None:
|
if viz is None:
|
||||||
log.error(f"viz {viz_key} doesn't exist in chart {self.name} !?")
|
log.error(f"viz {viz_key} doesn't exist in chart {self.name} !?")
|
||||||
key = res = 0, 0
|
return 0, 0
|
||||||
|
|
||||||
|
res = viz.maxmin()
|
||||||
|
|
||||||
|
if (
|
||||||
|
res is None
|
||||||
|
):
|
||||||
|
mxmn = 0, 0
|
||||||
|
if not self._on_screen:
|
||||||
|
self.default_view(do_ds=False)
|
||||||
|
self._on_screen = True
|
||||||
else:
|
else:
|
||||||
(
|
x_range, mxmn = res
|
||||||
l,
|
|
||||||
_,
|
|
||||||
lbar,
|
|
||||||
rbar,
|
|
||||||
_,
|
|
||||||
r,
|
|
||||||
) = bars_range or viz.datums_range()
|
|
||||||
|
|
||||||
profiler(f'{self.name} got bars range')
|
return mxmn
|
||||||
key = lbar, rbar
|
|
||||||
res = viz.maxmin(*key)
|
|
||||||
|
|
||||||
if (
|
|
||||||
res is None
|
|
||||||
):
|
|
||||||
log.warning(
|
|
||||||
f"{viz_key} no mxmn for bars_range => {key} !?"
|
|
||||||
)
|
|
||||||
res = 0, 0
|
|
||||||
if not self._on_screen:
|
|
||||||
self.default_view(do_ds=False)
|
|
||||||
self._on_screen = True
|
|
||||||
|
|
||||||
profiler(f'yrange mxmn: {key} -> {res}')
|
|
||||||
# print(f'{viz_key} yrange mxmn: {key} -> {res}')
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_viz(
|
def get_viz(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -60,6 +60,7 @@ from ..log import get_logger
|
||||||
from .._profile import (
|
from .._profile import (
|
||||||
Profiler,
|
Profiler,
|
||||||
pg_profile_enabled,
|
pg_profile_enabled,
|
||||||
|
ms_slower_then,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,7 +277,10 @@ class Viz(msgspec.Struct): # , frozen=True):
|
||||||
] = (None, None)
|
] = (None, None)
|
||||||
|
|
||||||
# cache of y-range values per x-range input.
|
# cache of y-range values per x-range input.
|
||||||
_mxmns: dict[tuple[int, int], tuple[float, float]] = {}
|
_mxmns: dict[
|
||||||
|
tuple[int, int],
|
||||||
|
tuple[float, float],
|
||||||
|
] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def shm(self) -> ShmArray:
|
def shm(self) -> ShmArray:
|
||||||
|
@ -324,32 +328,29 @@ class Viz(msgspec.Struct): # , frozen=True):
|
||||||
|
|
||||||
def maxmin(
|
def maxmin(
|
||||||
self,
|
self,
|
||||||
lbar: int,
|
|
||||||
rbar: int,
|
|
||||||
|
|
||||||
|
# TODO: drop this right?
|
||||||
|
bars_range: Optional[tuple[
|
||||||
|
int, int, int, int, int, int
|
||||||
|
]] = None,
|
||||||
|
|
||||||
|
x_range: slice | tuple[int, int] | None = None,
|
||||||
use_caching: bool = True,
|
use_caching: bool = True,
|
||||||
|
|
||||||
) -> Optional[tuple[float, float]]:
|
) -> tuple[float, float] | None:
|
||||||
'''
|
'''
|
||||||
Compute the cached max and min y-range values for a given
|
Compute the cached max and min y-range values for a given
|
||||||
x-range determined by ``lbar`` and ``rbar`` or ``None``
|
x-range determined by ``lbar`` and ``rbar`` or ``None``
|
||||||
if no range can be determined (yet).
|
if no range can be determined (yet).
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# TODO: hash the slice instead maybe?
|
name = self.name
|
||||||
# https://stackoverflow.com/a/29980872
|
profiler = Profiler(
|
||||||
rkey = (round(lbar), round(rbar))
|
msg=f'{name} -> `{str(self)}.maxmin()`',
|
||||||
|
disabled=not pg_profile_enabled(),
|
||||||
do_print: bool = False
|
ms_threshold=ms_slower_then,
|
||||||
if use_caching:
|
delayed=True,
|
||||||
cached_result = self._mxmns.get(rkey)
|
)
|
||||||
if cached_result:
|
|
||||||
if do_print:
|
|
||||||
print(
|
|
||||||
f'{self.name} CACHED maxmin\n'
|
|
||||||
f'{rkey} -> {cached_result}'
|
|
||||||
)
|
|
||||||
return cached_result
|
|
||||||
|
|
||||||
shm = self.shm
|
shm = self.shm
|
||||||
if shm is None:
|
if shm is None:
|
||||||
|
@ -357,6 +358,41 @@ class Viz(msgspec.Struct): # , frozen=True):
|
||||||
|
|
||||||
arr = shm.array
|
arr = shm.array
|
||||||
|
|
||||||
|
if x_range is None:
|
||||||
|
(
|
||||||
|
l,
|
||||||
|
_,
|
||||||
|
lbar,
|
||||||
|
rbar,
|
||||||
|
_,
|
||||||
|
r,
|
||||||
|
) = (
|
||||||
|
# TODO: drop this yah?
|
||||||
|
bars_range
|
||||||
|
or self.datums_range()
|
||||||
|
)
|
||||||
|
|
||||||
|
profiler(f'{self.name} got bars range')
|
||||||
|
x_range = lbar, rbar
|
||||||
|
|
||||||
|
# TODO: hash the slice instead maybe?
|
||||||
|
# https://stackoverflow.com/a/29980872
|
||||||
|
ixrng = (round(lbar), round(rbar))
|
||||||
|
|
||||||
|
do_print: bool = False
|
||||||
|
if use_caching:
|
||||||
|
cached_result = self._mxmns.get(ixrng)
|
||||||
|
if cached_result:
|
||||||
|
if do_print:
|
||||||
|
print(
|
||||||
|
f'{self.name} CACHED maxmin\n'
|
||||||
|
f'{ixrng} -> {cached_result}'
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
ixrng,
|
||||||
|
cached_result,
|
||||||
|
)
|
||||||
|
|
||||||
# get relative slice indexes into array
|
# get relative slice indexes into array
|
||||||
if self.index_field == 'time':
|
if self.index_field == 'time':
|
||||||
read_slc = slice_from_time(
|
read_slc = slice_from_time(
|
||||||
|
@ -376,7 +412,10 @@ class Viz(msgspec.Struct): # , frozen=True):
|
||||||
slice_view = arr[read_slc]
|
slice_view = arr[read_slc]
|
||||||
|
|
||||||
if not slice_view.size:
|
if not slice_view.size:
|
||||||
log.warning(f'{self.name} no maxmin in view?')
|
log.warning(
|
||||||
|
f'{self.name} no maxmin in view?\n'
|
||||||
|
f"{name} no mxmn for bars_range => {ixrng} !?"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
elif self.yrange:
|
elif self.yrange:
|
||||||
|
@ -384,9 +423,8 @@ class Viz(msgspec.Struct): # , frozen=True):
|
||||||
if do_print:
|
if do_print:
|
||||||
print(
|
print(
|
||||||
f'{self.name} M4 maxmin:\n'
|
f'{self.name} M4 maxmin:\n'
|
||||||
f'{rkey} -> {mxmn}'
|
f'{ixrng} -> {mxmn}'
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self.is_ohlc:
|
if self.is_ohlc:
|
||||||
ylow = np.min(slice_view['low'])
|
ylow = np.min(slice_view['low'])
|
||||||
|
@ -404,7 +442,7 @@ class Viz(msgspec.Struct): # , frozen=True):
|
||||||
s = 3
|
s = 3
|
||||||
print(
|
print(
|
||||||
f'{self.name} MANUAL ohlc={self.is_ohlc} maxmin:\n'
|
f'{self.name} MANUAL ohlc={self.is_ohlc} maxmin:\n'
|
||||||
f'{rkey} -> {mxmn}\n'
|
f'{ixrng} -> {mxmn}\n'
|
||||||
f'read_slc: {read_slc}\n'
|
f'read_slc: {read_slc}\n'
|
||||||
# f'abs_slc: {slice_view["index"]}\n'
|
# f'abs_slc: {slice_view["index"]}\n'
|
||||||
f'first {s}:\n{slice_view[:s]}\n'
|
f'first {s}:\n{slice_view[:s]}\n'
|
||||||
|
@ -413,9 +451,12 @@ class Viz(msgspec.Struct): # , frozen=True):
|
||||||
|
|
||||||
# cache result for input range
|
# cache result for input range
|
||||||
assert mxmn
|
assert mxmn
|
||||||
self._mxmns[rkey] = mxmn
|
self._mxmns[ixrng] = mxmn
|
||||||
|
profiler(f'yrange mxmn cacheing: {x_range} -> {mxmn}')
|
||||||
return mxmn
|
return (
|
||||||
|
ixrng,
|
||||||
|
mxmn,
|
||||||
|
)
|
||||||
|
|
||||||
def view_range(self) -> tuple[int, int]:
|
def view_range(self) -> tuple[int, int]:
|
||||||
'''
|
'''
|
||||||
|
|
Loading…
Reference in New Issue