diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 7dec0a48fb..9619c0ebf5 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -648,13 +648,18 @@ def _get_hover_data(self, data, element, dimensions=None): If empty initializes with no data. """ - if 'hover' not in self.handles or self.static_source: + has_hover = 'hover' in self.handles + if not has_hover and not self.overlay_dims: return - for d in (dimensions or element.dimensions()): - dim = util.dimension_sanitizer(d.name) - if dim not in data: - data[dim] = element.dimension_values(d) + if has_hover and not self.static_source: + for d in (dimensions or element.dimensions()): + dim = util.dimension_sanitizer(d.name) + if dim not in data: + data[dim] = element.dimension_values(d) + + if not data: + return for k, v in self.overlay_dims.items(): dim = util.dimension_sanitizer(k.name) @@ -3596,6 +3601,14 @@ def update_frame(self, key, ranges=None, element=None): if not self.tabs: init_kwargs['plot'] = self.handles['plot'] self._create_dynamic_subplots(key, items, ranges, **init_kwargs) + if overlay_hover := self.handles.get('hover'): + if overlay_hover.renderers == 'auto': + overlay_hover.renderers = [] + for k, _ in items: + if k in self.subplots and 'glyph_renderer' in self.subplots[k].handles: + renderer = self.subplots[k].handles['glyph_renderer'] + if renderer not in overlay_hover.renderers: + overlay_hover.renderers.append(renderer) if not self.overlaid and not self.tabs: self._process_legend(element) diff --git a/holoviews/tests/ui/bokeh/test_hover.py b/holoviews/tests/ui/bokeh/test_hover.py index 04504e7d86..d42a34398d 100644 --- a/holoviews/tests/ui/bokeh/test_hover.py +++ b/holoviews/tests/ui/bokeh/test_hover.py @@ -519,6 +519,43 @@ def test_hover_heatmap_image(serve_hv, x_axis_type, y_axis_type): expect(tooltip).to_contain_text("z: 53") +@pytest.mark.usefixtures("bokeh_backend") +def test_hover_across_dynamicmaps(serve_panel): + data = pd.DataFrame({"x": range(2), "y": range(2), "category": ["A", "B"]}) + el = ( + hv.Dataset(data) + .to(hv.Points, kdims=["x", "y"], groupby="category") + .opts(tools=["hover"], size=10, show_legend=False, xlim=(-1, 3), ylim=(-1, 3), hit_dilation=100) + ) + widget = pn.widgets.MultiSelect(value=["A"], options=["A", "B"]) + dmap = hv.DynamicMap( + lambda value: el.get(value).overlay(), + streams=[widget.param.value] + ) + col = pn.Column(widget, dmap) + page = serve_panel(col) + + hv_plot = page.locator(".bk-events") + expect(hv_plot).to_have_count(1) + bbox = hv_plot.bounding_box() + + for i, cat in enumerate(("A", "B")): + widget.value = [cat] + page.mouse.move(bbox["x"] + bbox["width"] / 2, bbox["y"] + bbox["height"] / 2) + page.mouse.up() + tooltip = page.locator(".bk-Tooltip") + expect(tooltip).to_have_count(1) + expect(tooltip.first).to_contain_text(f"category: {cat} x: {i} y: {i}") + + widget.value = ["A", "B"] + page.mouse.move(bbox["x"] + bbox["width"] / 2, bbox["y"] + bbox["height"] / 2) + page.mouse.up() + tooltip = page.locator(".bk-Tooltip") + expect(tooltip).to_have_count(2) + expect(tooltip.first).to_contain_text("category: A x: 0 y: 0") + expect(tooltip.last).to_contain_text("category: B x: 1 y: 1") + + @pytest.mark.usefixtures("bokeh_backend") def test_hover_heatmap_categorical_outside_plot_area(serve_hv, caplog): # Test for https://github.com/holoviz/holoviews/pull/6438