From e5051e809b526563214af487a805be38c457997b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 22 Feb 2024 14:49:56 +0100 Subject: [PATCH 01/10] Add option to set toolbar_location in merge_tools --- holoviews/plotting/bokeh/plot.py | 3 +-- holoviews/plotting/bokeh/util.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index 37771afb1b..c46dbcb276 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -614,8 +614,7 @@ def initialize_plot(self, ranges=None, plots=None): sync_legends(plot) plot = self._make_axes(plot) if hasattr(plot, "toolbar") and self.merge_tools: - plot.toolbar = merge_tools(plots) - + plot.toolbar = merge_tools(plots, toolbar_location=None) title = self._get_title_div(self.keys[-1]) if title: plot = Column(title, plot) diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index 490fbf9035..3b318dfc80 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -384,7 +384,7 @@ def compute_layout_properties( return aspect_info, dimension_info -def merge_tools(plot_grid, disambiguation_properties=None): +def merge_tools(plot_grid, *, disambiguation_properties=None, toolbar_location='null'): """ Merges tools defined on a grid of plots into a single toolbar. All tools of the same type are merged unless they define one @@ -397,8 +397,8 @@ def merge_tools(plot_grid, disambiguation_properties=None): if isinstance(item, LayoutDOM): for p in item.select(dict(type=Plot)): tools.extend(p.toolbar.tools) - if isinstance(item, GridPlot): - item.toolbar_location = None + if toolbar_location != 'null': + item.toolbar_location = toolbar_location def merge(tool, group): if issubclass(tool, (SaveTool, CopyTool, ExamineTool, FullscreenTool)): From 97e8c7bf9be753ff383b13be0ea0206f87cf5095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 22 Feb 2024 14:50:43 +0100 Subject: [PATCH 02/10] add test --- holoviews/tests/ui/bokeh/test_layout.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 holoviews/tests/ui/bokeh/test_layout.py diff --git a/holoviews/tests/ui/bokeh/test_layout.py b/holoviews/tests/ui/bokeh/test_layout.py new file mode 100644 index 0000000000..5ea64dfd04 --- /dev/null +++ b/holoviews/tests/ui/bokeh/test_layout.py @@ -0,0 +1,29 @@ +import numpy as np +import panel as pn +import pytest + +import holoviews as hv + +pytestmark = pytest.mark.ui + +from panel.tests.util import serve_and_wait +from playwright.sync_api import expect + + +@pytest.mark.usefixtures("bokeh_backend") +def test_gridspace_toolbar(page, port): + def sine_curve(phase, freq): + xvals = [0.1 * i for i in range(100)] + return hv.Curve((xvals, [np.sin(phase + freq * x) for x in xvals])) + + phases = [0, np.pi / 2, np.pi, 3 * np.pi / 2] + frequencies = [0.5, 0.75, 1.0, 1.25] + curve_dict_2D = {(p, f): sine_curve(p, f) for p in phases for f in frequencies} + gridspace = hv.GridSpace(curve_dict_2D, kdims=["phase", "frequency"]) + pn_obj = pn.pane.HoloViews(gridspace) + + serve_and_wait(pn_obj, port=port) + page.goto(f"http://localhost:{port}") + + bokeh_logo = page.locator('.bk-logo') + expect(bokeh_logo).to_have_count(1) From 1b7d798d373009039524185c056d3d42ac345ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 22 Feb 2024 14:55:40 +0100 Subject: [PATCH 03/10] Add back if statement --- holoviews/plotting/bokeh/util.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index 3b318dfc80..f7c1a11c94 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -399,6 +399,8 @@ def merge_tools(plot_grid, *, disambiguation_properties=None, toolbar_location=' tools.extend(p.toolbar.tools) if toolbar_location != 'null': item.toolbar_location = toolbar_location + if isinstance(item, GridPlot): + item.toolbar_location = None def merge(tool, group): if issubclass(tool, (SaveTool, CopyTool, ExamineTool, FullscreenTool)): From 71f6742f3818fa47c08d931e9a85c6c8bdc37186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 22 Feb 2024 15:01:06 +0100 Subject: [PATCH 04/10] Fix core test --- holoviews/tests/ui/bokeh/test_layout.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/holoviews/tests/ui/bokeh/test_layout.py b/holoviews/tests/ui/bokeh/test_layout.py index 5ea64dfd04..7bba3d486a 100644 --- a/holoviews/tests/ui/bokeh/test_layout.py +++ b/holoviews/tests/ui/bokeh/test_layout.py @@ -7,7 +7,11 @@ pytestmark = pytest.mark.ui from panel.tests.util import serve_and_wait -from playwright.sync_api import expect + +try: + from playwright.sync_api import expect +except ImportError: + pass @pytest.mark.usefixtures("bokeh_backend") From bc0c427d5fb4ec89f4cbcf8507d23b8d53a766bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 22 Feb 2024 15:09:29 +0100 Subject: [PATCH 05/10] Add hasattr check --- holoviews/plotting/bokeh/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index f7c1a11c94..76aed6aef2 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -397,7 +397,7 @@ def merge_tools(plot_grid, *, disambiguation_properties=None, toolbar_location=' if isinstance(item, LayoutDOM): for p in item.select(dict(type=Plot)): tools.extend(p.toolbar.tools) - if toolbar_location != 'null': + if toolbar_location != 'null' and hasattr(item, 'toolbar_location'): item.toolbar_location = toolbar_location if isinstance(item, GridPlot): item.toolbar_location = None From d16cf0c16926ce66d91f32d013af5b9f0a2eb6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 23 Feb 2024 09:30:37 +0100 Subject: [PATCH 06/10] Add serve_hv fixture and toplevel expect --- holoviews/tests/conftest.py | 38 +++++++++++++++++-------- holoviews/tests/ui/__init__.py | 0 holoviews/tests/ui/bokeh/__init__.py | 4 +++ holoviews/tests/ui/bokeh/test_layout.py | 19 ++++--------- 4 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 holoviews/tests/ui/__init__.py create mode 100644 holoviews/tests/ui/bokeh/__init__.py diff --git a/holoviews/tests/conftest.py b/holoviews/tests/conftest.py index f4d9533400..b6b02d9296 100644 --- a/holoviews/tests/conftest.py +++ b/holoviews/tests/conftest.py @@ -2,14 +2,18 @@ import sys from collections.abc import Callable +import panel as pn import pytest -from panel.tests.conftest import ( # noqa +from panel.tests.conftest import ( # noqa: F401 optional_markers, port, pytest_addoption, pytest_configure, server_cleanup, ) +from panel.tests.util import serve_and_wait + +import holoviews as hv def pytest_collection_modifyitems(config, items): @@ -32,13 +36,15 @@ def pytest_collection_modifyitems(config, items): with contextlib.suppress(ImportError): import matplotlib as mpl - mpl.use('agg') + + mpl.use("agg") with contextlib.suppress(Exception): # From Dask 2023.7,1 they now automatic convert strings # https://docs.dask.org/en/stable/changelog.html#v2023-7-1 import dask + dask.config.set({"dataframe.convert-string": False}) @@ -49,40 +55,38 @@ def ibis_sqlite_backend(): except ImportError: yield None else: - ibis.set_backend('sqlite') + ibis.set_backend("sqlite") yield ibis.set_backend(None) @pytest.fixture def bokeh_backend(): - import holoviews as hv - hv.renderer('bokeh') + hv.renderer("bokeh") prev_backend = hv.Store.current_backend - hv.Store.current_backend = 'bokeh' + hv.Store.current_backend = "bokeh" yield hv.Store.current_backend = prev_backend @pytest.fixture def mpl_backend(): - import holoviews as hv - hv.renderer('matplotlib') + hv.renderer("matplotlib") prev_backend = hv.Store.current_backend - hv.Store.current_backend = 'matplotlib' + hv.Store.current_backend = "matplotlib" yield hv.Store.current_backend = prev_backend @pytest.fixture def plotly_backend(): - import holoviews as hv - hv.renderer('plotly') + hv.renderer("plotly") prev_backend = hv.Store.current_backend - hv.Store.current_backend = 'plotly' + hv.Store.current_backend = "plotly" yield hv.Store.current_backend = prev_backend + @pytest.fixture def unimport(monkeypatch: pytest.MonkeyPatch) -> Callable[[str], None]: """ @@ -98,3 +102,13 @@ def unimport_module(modname: str) -> None: monkeypatch.setattr(sys, "path", []) return unimport_module + + +@pytest.fixture +def serve_hv(page, port): # noqa: F811 + def serve_and_return_page(hv_obj): + serve_and_wait(pn.pane.HoloViews(hv_obj), port=port) + page.goto(f"http://localhost:{port}") + return page + + return serve_and_return_page diff --git a/holoviews/tests/ui/__init__.py b/holoviews/tests/ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/holoviews/tests/ui/bokeh/__init__.py b/holoviews/tests/ui/bokeh/__init__.py new file mode 100644 index 0000000000..d1207bb8c1 --- /dev/null +++ b/holoviews/tests/ui/bokeh/__init__.py @@ -0,0 +1,4 @@ +try: + from playwright.sync_api import expect +except ImportError: + expect = None diff --git a/holoviews/tests/ui/bokeh/test_layout.py b/holoviews/tests/ui/bokeh/test_layout.py index 7bba3d486a..4ada20c254 100644 --- a/holoviews/tests/ui/bokeh/test_layout.py +++ b/holoviews/tests/ui/bokeh/test_layout.py @@ -1,21 +1,15 @@ import numpy as np -import panel as pn import pytest import holoviews as hv -pytestmark = pytest.mark.ui - -from panel.tests.util import serve_and_wait +from . import expect -try: - from playwright.sync_api import expect -except ImportError: - pass +pytestmark = pytest.mark.ui @pytest.mark.usefixtures("bokeh_backend") -def test_gridspace_toolbar(page, port): +def test_gridspace_toolbar(serve_hv): def sine_curve(phase, freq): xvals = [0.1 * i for i in range(100)] return hv.Curve((xvals, [np.sin(phase + freq * x) for x in xvals])) @@ -24,10 +18,7 @@ def sine_curve(phase, freq): frequencies = [0.5, 0.75, 1.0, 1.25] curve_dict_2D = {(p, f): sine_curve(p, f) for p in phases for f in frequencies} gridspace = hv.GridSpace(curve_dict_2D, kdims=["phase", "frequency"]) - pn_obj = pn.pane.HoloViews(gridspace) - - serve_and_wait(pn_obj, port=port) - page.goto(f"http://localhost:{port}") - bokeh_logo = page.locator('.bk-logo') + page = serve_hv(gridspace) + bokeh_logo = page.locator(".bk-logo") expect(bokeh_logo).to_have_count(1) From 9699a0f6c00826ce478e911ab23e82502fdc4516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 23 Feb 2024 09:52:14 +0100 Subject: [PATCH 07/10] Automatic add marker to ui test and update test_callback --- holoviews/tests/conftest.py | 5 +++ holoviews/tests/ui/bokeh/test_callback.py | 54 ++++++----------------- holoviews/tests/ui/bokeh/test_layout.py | 2 - 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/holoviews/tests/conftest.py b/holoviews/tests/conftest.py index b6b02d9296..90c005d5b1 100644 --- a/holoviews/tests/conftest.py +++ b/holoviews/tests/conftest.py @@ -1,6 +1,7 @@ import contextlib import sys from collections.abc import Callable +from pathlib import Path import panel as pn import pytest @@ -21,6 +22,10 @@ def pytest_collection_modifyitems(config, items): markers = [m for m in optional_markers if config.getoption(f"--{m}")] empty = not markers for item in items: + if any("ui" == p.name for p in Path(item.fspath).parents): + # automatic set ui marker on ui tests + item.add_marker(pytest.mark.ui) + if empty and any(m in item.keywords for m in optional_markers): skipped.append(item) elif empty: diff --git a/holoviews/tests/ui/bokeh/test_callback.py b/holoviews/tests/ui/bokeh/test_callback.py index 09e0dbfd5b..85899fa8b0 100644 --- a/holoviews/tests/ui/bokeh/test_callback.py +++ b/holoviews/tests/ui/bokeh/test_callback.py @@ -1,23 +1,16 @@ import time import numpy as np -import pytest - -try: - from playwright.sync_api import expect -except ImportError: - pytestmark = pytest.mark.skip('playwright not available') - -pytestmark = pytest.mark.ui - import panel as pn -from panel.pane.holoviews import HoloViews -from panel.tests.util import serve_and_wait, wait_until +import pytest +from panel.tests.util import wait_until import holoviews as hv from holoviews import Curve, DynamicMap, Scatter from holoviews.streams import BoundsX, BoundsXY, BoundsY, Lasso, RangeXY +from . import expect + @pytest.mark.usefixtures("bokeh_backend") @pytest.mark.parametrize( @@ -28,18 +21,14 @@ (BoundsY, slice(1, None, 2), "boundsy"), ], ) -def test_box_select(page, port, BoundsTool, bound_slice, bound_attr): +def test_box_select(serve_hv, BoundsTool, bound_slice, bound_attr): hv_scatter = Scatter([1, 2, 3]).opts( tools=['box_select'], active_tools=['box_select'] ) bounds = BoundsTool(source=hv_scatter) - pn_scatter = HoloViews(hv_scatter) - - serve_and_wait(pn_scatter, port=port) - page.goto(f"http://localhost:{port}") - + page = serve_hv(hv_scatter) hv_plot = page.locator('.bk-events') expect(hv_plot).to_have_count(1) @@ -57,18 +46,14 @@ def test_box_select(page, port, BoundsTool, bound_slice, bound_attr): @pytest.mark.usefixtures("bokeh_backend") -def test_lasso_select(page, port): +def test_lasso_select(serve_hv): hv_scatter = Scatter([1, 2, 3]).opts( tools=['lasso_select'], active_tools=['lasso_select'] ) lasso = Lasso(source=hv_scatter) - pn_scatter = HoloViews(hv_scatter) - - serve_and_wait(pn_scatter, port=port) - page.goto(f"http://localhost:{port}") - + page = serve_hv(hv_scatter) hv_plot = page.locator('.bk-events') expect(hv_plot).to_have_count(1) @@ -100,16 +85,12 @@ def test_lasso_select(page, port): np.testing.assert_almost_equal(lasso.geometry, expected_array) @pytest.mark.usefixtures("bokeh_backend") -def test_rangexy(page, port): +def test_rangexy(serve_hv): hv_scatter = Scatter([1, 2, 3]).opts(active_tools=['box_zoom']) rangexy = RangeXY(source=hv_scatter) - pn_scatter = HoloViews(hv_scatter) - - serve_and_wait(pn_scatter, port=port) - page.goto(f"http://localhost:{port}") - + page = serve_hv(hv_scatter) hv_plot = page.locator('.bk-events') expect(hv_plot).to_have_count(1) @@ -127,7 +108,7 @@ def test_rangexy(page, port): wait_until(lambda: rangexy.x_range == expected_xrange and rangexy.y_range == expected_yrange, page) @pytest.mark.usefixtures("bokeh_backend") -def test_multi_axis_rangexy(page, port): +def test_multi_axis_rangexy(serve_hv): c1 = Curve(np.arange(100).cumsum(), vdims='y') c2 = Curve(-np.arange(100).cumsum(), vdims='y2') s1 = RangeXY(source=c1) @@ -135,11 +116,7 @@ def test_multi_axis_rangexy(page, port): overlay = (c1 * c2).opts(multi_y=True) - pn_scatter = HoloViews(overlay) - - serve_and_wait(pn_scatter, port=port) - page.goto(f"http://localhost:{port}") - + page = serve_hv(overlay) hv_plot = page.locator('.bk-events') expect(hv_plot).to_have_count(1) @@ -163,7 +140,7 @@ def test_multi_axis_rangexy(page, port): @pytest.mark.usefixtures("bokeh_backend") -def test_bind_trigger(page, port): +def test_bind_trigger(serve_hv): # Regression test for https://github.com/holoviz/holoviews/issues/6013 BOUND_COUNT, RANGE_COUNT = [0], [0] @@ -179,11 +156,8 @@ def range_function(x_range, y_range): range_dmap = DynamicMap(range_function, streams=[hv.streams.RangeXY()]) bind_dmap = DynamicMap(pn.bind(bound_function)) - widget = pn.pane.HoloViews(bind_dmap * range_dmap) - - serve_and_wait(widget, port=port) - page.goto(f"http://localhost:{port}") + page = serve_hv(bind_dmap * range_dmap) hv_plot = page.locator('.bk-events') expect(hv_plot).to_have_count(1) diff --git a/holoviews/tests/ui/bokeh/test_layout.py b/holoviews/tests/ui/bokeh/test_layout.py index 4ada20c254..e8fa74e87c 100644 --- a/holoviews/tests/ui/bokeh/test_layout.py +++ b/holoviews/tests/ui/bokeh/test_layout.py @@ -5,8 +5,6 @@ from . import expect -pytestmark = pytest.mark.ui - @pytest.mark.usefixtures("bokeh_backend") def test_gridspace_toolbar(serve_hv): From 04e1f9742c7462b329b228f008f89b0c4c4d889a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 23 Feb 2024 13:10:53 +0100 Subject: [PATCH 08/10] Revert automatic set ui marker Didn't seem to be worth it tbh --- holoviews/tests/conftest.py | 5 ----- holoviews/tests/ui/bokeh/test_callback.py | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/holoviews/tests/conftest.py b/holoviews/tests/conftest.py index 90c005d5b1..b6b02d9296 100644 --- a/holoviews/tests/conftest.py +++ b/holoviews/tests/conftest.py @@ -1,7 +1,6 @@ import contextlib import sys from collections.abc import Callable -from pathlib import Path import panel as pn import pytest @@ -22,10 +21,6 @@ def pytest_collection_modifyitems(config, items): markers = [m for m in optional_markers if config.getoption(f"--{m}")] empty = not markers for item in items: - if any("ui" == p.name for p in Path(item.fspath).parents): - # automatic set ui marker on ui tests - item.add_marker(pytest.mark.ui) - if empty and any(m in item.keywords for m in optional_markers): skipped.append(item) elif empty: diff --git a/holoviews/tests/ui/bokeh/test_callback.py b/holoviews/tests/ui/bokeh/test_callback.py index 85899fa8b0..e9141f9742 100644 --- a/holoviews/tests/ui/bokeh/test_callback.py +++ b/holoviews/tests/ui/bokeh/test_callback.py @@ -11,6 +11,8 @@ from . import expect +pytestmark = pytest.mark.ui + @pytest.mark.usefixtures("bokeh_backend") @pytest.mark.parametrize( From f9a3bc8d2eb80dea58e9142d19d36ed615436d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 23 Feb 2024 13:11:22 +0100 Subject: [PATCH 09/10] Move ui helper function to ui top folder --- holoviews/tests/ui/__init__.py | 8 ++++++++ holoviews/tests/ui/bokeh/__init__.py | 4 ---- holoviews/tests/ui/bokeh/test_callback.py | 3 +-- holoviews/tests/ui/bokeh/test_layout.py | 4 +++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/holoviews/tests/ui/__init__.py b/holoviews/tests/ui/__init__.py index e69de29bb2..36c5bd8d9f 100644 --- a/holoviews/tests/ui/__init__.py +++ b/holoviews/tests/ui/__init__.py @@ -0,0 +1,8 @@ +try: + from playwright.sync_api import expect +except ImportError: + expect = None + +from panel.tests.util import wait_until + +__all__ = ("expect", "wait_until") diff --git a/holoviews/tests/ui/bokeh/__init__.py b/holoviews/tests/ui/bokeh/__init__.py index d1207bb8c1..e69de29bb2 100644 --- a/holoviews/tests/ui/bokeh/__init__.py +++ b/holoviews/tests/ui/bokeh/__init__.py @@ -1,4 +0,0 @@ -try: - from playwright.sync_api import expect -except ImportError: - expect = None diff --git a/holoviews/tests/ui/bokeh/test_callback.py b/holoviews/tests/ui/bokeh/test_callback.py index e9141f9742..ffd3d3bc00 100644 --- a/holoviews/tests/ui/bokeh/test_callback.py +++ b/holoviews/tests/ui/bokeh/test_callback.py @@ -3,13 +3,12 @@ import numpy as np import panel as pn import pytest -from panel.tests.util import wait_until import holoviews as hv from holoviews import Curve, DynamicMap, Scatter from holoviews.streams import BoundsX, BoundsXY, BoundsY, Lasso, RangeXY -from . import expect +from .. import expect, wait_until pytestmark = pytest.mark.ui diff --git a/holoviews/tests/ui/bokeh/test_layout.py b/holoviews/tests/ui/bokeh/test_layout.py index e8fa74e87c..114ea37fa6 100644 --- a/holoviews/tests/ui/bokeh/test_layout.py +++ b/holoviews/tests/ui/bokeh/test_layout.py @@ -3,7 +3,9 @@ import holoviews as hv -from . import expect +from .. import expect + +pytestmark = pytest.mark.ui @pytest.mark.usefixtures("bokeh_backend") From 3c9656907f29e18ad18631f8254cf45c90fad8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 23 Feb 2024 14:53:48 +0100 Subject: [PATCH 10/10] Change to hide_toolbar --- holoviews/plotting/bokeh/plot.py | 2 +- holoviews/plotting/bokeh/util.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index c46dbcb276..6a97643d63 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -614,7 +614,7 @@ def initialize_plot(self, ranges=None, plots=None): sync_legends(plot) plot = self._make_axes(plot) if hasattr(plot, "toolbar") and self.merge_tools: - plot.toolbar = merge_tools(plots, toolbar_location=None) + plot.toolbar = merge_tools(plots, hide_toolbar=True) title = self._get_title_div(self.keys[-1]) if title: plot = Column(title, plot) diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index 76aed6aef2..542957d75d 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -384,7 +384,7 @@ def compute_layout_properties( return aspect_info, dimension_info -def merge_tools(plot_grid, *, disambiguation_properties=None, toolbar_location='null'): +def merge_tools(plot_grid, *, disambiguation_properties=None, hide_toolbar=False): """ Merges tools defined on a grid of plots into a single toolbar. All tools of the same type are merged unless they define one @@ -397,8 +397,8 @@ def merge_tools(plot_grid, *, disambiguation_properties=None, toolbar_location=' if isinstance(item, LayoutDOM): for p in item.select(dict(type=Plot)): tools.extend(p.toolbar.tools) - if toolbar_location != 'null' and hasattr(item, 'toolbar_location'): - item.toolbar_location = toolbar_location + if hide_toolbar and hasattr(item, 'toolbar_location'): + item.toolbar_location = None if isinstance(item, GridPlot): item.toolbar_location = None