diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 05972603ec..874132b499 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() {{ @@ -1349,30 +1348,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 +1372,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] @@ -2665,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) @@ -2986,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 new file mode 100644 index 0000000000..787f3167a9 --- /dev/null +++ b/holoviews/tests/ui/bokeh/test_autorange.py @@ -0,0 +1,86 @@ +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_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']) + + 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) + +@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'] + wait_until(lambda: y_range.start == -486 and y_range.end == 486, page)