From b66da70fd5ea53b5bbd72143a176d0c6ab47ccd5 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 4 Apr 2024 19:26:57 +0200 Subject: [PATCH 1/5] Ensure plot ranges for all renderers are combined in auto-ranging --- holoviews/plotting/bokeh/element.py | 49 ++++++++++------------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 05972603ec..eb3dc967ec 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -1349,30 +1349,10 @@ def _setup_autorange(self): return invert ? [upper, lower] : [lower, upper] }} - const ref = plot.id - - const find = (view) => {{ - let iterable = view.child_views === undefined ? [] : view.child_views - for (const sv of iterable) {{ - if (sv.model.id == ref) - return sv - const obj = find(sv) - if (obj !== null) - return obj - }} - return null - }} - let plot_view = null; - for (const root of plot.document.roots()) {{ - const root_view = window.Bokeh.index[root.id] - if (root_view === undefined) - return - plot_view = find(root_view) - if (plot_view != null) - break - }} - if (plot_view == null) + let plot_view = Bokeh.index.find_one(plot) + if (plot_view == null) {{ return + }} let range_limits = {{}} for (const dr of plot.data_renderers) {{ @@ -1393,20 +1373,23 @@ def _setup_autorange(self): }} }} - if (y_range_name) {{ + if (y_range_name in range_limits) {{ + const [vmin_old, vmax_old] = range_limits[y_range_name] + range_limits[y_range_name] = [Math.min(vmin, vmin_old), Math.max(vmax, vmax_old)] + }} else {{ range_limits[y_range_name] = [vmin, vmax] }} }} - let range_tags_extras = plot.{dim}_range.tags[1] - if (range_tags_extras['autorange']) {{ - let lowerlim = range_tags_extras['y-lowerlim'] ?? null - let upperlim = range_tags_extras['y-upperlim'] ?? null - let [start, end] = get_padded_range('default', lowerlim, upperlim, range_tags_extras['invert_yaxis']) - if ((start != end) && window.Number.isFinite(start) && window.Number.isFinite(end)) {{ - plot.{dim}_range.setv({{start, end}}) - }} - }} + let range_tags_extras = plot.{dim}_range.tags[1] + if (range_tags_extras['autorange']) {{ + let lowerlim = range_tags_extras['y-lowerlim'] ?? null + let upperlim = range_tags_extras['y-upperlim'] ?? null + let [start, end] = get_padded_range('default', lowerlim, upperlim, range_tags_extras['invert_yaxis']) + if ((start != end) && window.Number.isFinite(start) && window.Number.isFinite(end)) {{ + plot.{dim}_range.setv({{start, end}}) + }} + }} for (let key in plot.extra_{dim}_ranges) {{ const extra_range = plot.extra_{dim}_ranges[key] From baa8feb219d7012b10fbef9b56a1262a91320b57 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 4 Apr 2024 19:40:46 +0200 Subject: [PATCH 2/5] Remove comment --- holoviews/plotting/bokeh/element.py | 1 - 1 file changed, 1 deletion(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index eb3dc967ec..eb2cc649f4 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -1327,7 +1327,6 @@ def _setup_autorange(self): else: p0, p1 = self.padding, self.padding - # Clean this up in bokeh 3.0 using View.find_one API callback = CustomJS(code=f""" const cb = function() {{ From 0865a98741d431a00aa54fc10d27225006ef1ae6 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 12 Apr 2024 18:07:47 +0200 Subject: [PATCH 3/5] Add tests --- holoviews/tests/ui/bokeh/test_autorange.py | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 holoviews/tests/ui/bokeh/test_autorange.py diff --git a/holoviews/tests/ui/bokeh/test_autorange.py b/holoviews/tests/ui/bokeh/test_autorange.py new file mode 100644 index 0000000000..34c0e43530 --- /dev/null +++ b/holoviews/tests/ui/bokeh/test_autorange.py @@ -0,0 +1,60 @@ +import numpy as np +import pytest + +from holoviews.element import Curve +from holoviews.plotting.bokeh.renderer import BokehRenderer + +from .. import expect, wait_until + +pytestmark = pytest.mark.ui + + +@pytest.mark.usefixtures("bokeh_backend") +def test_autorange_single(serve_hv): + curve = Curve(np.arange(1000)).opts(autorange='y', active_tools=['box_zoom']) + + plot = BokehRenderer.get_plot(curve) + + page = serve_hv(plot) + + hv_plot = page.locator('.bk-events') + + expect(hv_plot).to_have_count(1) + + bbox = hv_plot.bounding_box() + hv_plot.click() + + page.mouse.move(bbox['x']+100, bbox['y']+100) + page.mouse.down() + page.mouse.move(bbox['x']+150, bbox['y']+150, steps=5) + page.mouse.up() + + y_range = plot.handles['y_range'] + wait_until(lambda: y_range.start == 163.2 and y_range.end == 448.8, page) + + +@pytest.mark.usefixtures("bokeh_backend") +def test_autorange_multiple(serve_hv): + c1 = Curve(np.arange(1000)).opts(autorange='y') + c2 = Curve(-np.arange(1000)).opts(autorange='y') + + overlay = (c1*c2).opts(active_tools=['box_zoom'], autorange='y') + + plot = BokehRenderer.get_plot(overlay) + + page = serve_hv(plot) + + hv_plot = page.locator('.bk-events') + + expect(hv_plot).to_have_count(1) + + bbox = hv_plot.bounding_box() + hv_plot.click() + + page.mouse.move(bbox['x']+100, bbox['y']+100) + page.mouse.down() + page.mouse.move(bbox['x']+150, bbox['y']+150, steps=5) + page.mouse.up() + + y_range = plot.handles['y_range'] + wait_until(lambda: y_range.start == -486 and y_range.end == 486, page) From 02fed4ba5473b797bafe15f4009113ad40a8b596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 15 Apr 2024 12:41:33 +0200 Subject: [PATCH 4/5] Correct multiple example --- holoviews/plotting/bokeh/element.py | 2 +- holoviews/tests/ui/bokeh/test_autorange.py | 33 ++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index eb2cc649f4..018044f64f 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -2647,7 +2647,7 @@ class OverlayPlot(GenericOverlayPlot, LegendPlot): 'min_height', 'max_height', 'min_width', 'min_height', 'margin', 'aspect', 'data_aspect', 'frame_width', 'frame_height', 'responsive', 'fontscale', 'subcoordinate_y', - 'subcoordinate_scale'] + 'subcoordinate_scale', 'autorange'] def __init__(self, overlay, **kwargs): self._multi_y_propagation = self.lookup_options(overlay, 'plot').options.get('multi_y', False) diff --git a/holoviews/tests/ui/bokeh/test_autorange.py b/holoviews/tests/ui/bokeh/test_autorange.py index 34c0e43530..3ef26c3dc7 100644 --- a/holoviews/tests/ui/bokeh/test_autorange.py +++ b/holoviews/tests/ui/bokeh/test_autorange.py @@ -34,11 +34,11 @@ def test_autorange_single(serve_hv): @pytest.mark.usefixtures("bokeh_backend") -def test_autorange_multiple(serve_hv): - c1 = Curve(np.arange(1000)).opts(autorange='y') +def test_autorange_single_in_overlay(serve_hv): + c1 = Curve(np.arange(1000)) c2 = Curve(-np.arange(1000)).opts(autorange='y') - overlay = (c1*c2).opts(active_tools=['box_zoom'], autorange='y') + overlay = (c1*c2).opts(active_tools=['box_zoom']) plot = BokehRenderer.get_plot(overlay) @@ -58,3 +58,30 @@ def test_autorange_multiple(serve_hv): y_range = plot.handles['y_range'] wait_until(lambda: y_range.start == -486 and y_range.end == 486, page) + +@pytest.mark.usefixtures("bokeh_backend") +def test_autorange_overlay(serve_hv): + c1 = Curve(np.arange(1000)) + c2 = Curve(-np.arange(1000)) + + overlay = (c1*c2).opts(active_tools=['box_zoom'], autorange='y') + + plot = BokehRenderer.get_plot(overlay) + + page = serve_hv(plot) + + hv_plot = page.locator('.bk-events') + + expect(hv_plot).to_have_count(1) + + bbox = hv_plot.bounding_box() + hv_plot.click() + + page.mouse.move(bbox['x']+100, bbox['y']+100) + page.mouse.down() + page.mouse.move(bbox['x']+150, bbox['y']+150, steps=5) + page.mouse.up() + + y_range = plot.handles['y_range'] + expected = (-171.25714285714287, 318.0489795918367) + wait_until(lambda: np.allclose((y_range.start, y_range.end), expected) , page) From 633ea9b6eb882770195ff98cbce2d5eee2c74f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 15 Apr 2024 15:13:47 +0200 Subject: [PATCH 5/5] Also intialize autorange on overlay --- holoviews/plotting/bokeh/element.py | 3 +++ holoviews/tests/ui/bokeh/test_autorange.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 018044f64f..874132b499 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -2968,6 +2968,9 @@ def initialize_plot(self, ranges=None, plot=None, plots=None): if self.top_level: self.init_links() + if self.autorange: + self._setup_autorange() + self._execute_hooks(element) return self.handles['plot'] diff --git a/holoviews/tests/ui/bokeh/test_autorange.py b/holoviews/tests/ui/bokeh/test_autorange.py index 3ef26c3dc7..787f3167a9 100644 --- a/holoviews/tests/ui/bokeh/test_autorange.py +++ b/holoviews/tests/ui/bokeh/test_autorange.py @@ -83,5 +83,4 @@ def test_autorange_overlay(serve_hv): page.mouse.up() y_range = plot.handles['y_range'] - expected = (-171.25714285714287, 318.0489795918367) - wait_until(lambda: np.allclose((y_range.start, y_range.end), expected) , page) + wait_until(lambda: y_range.start == -486 and y_range.end == 486, page)