diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py index d9157deca5..d03e9bbe2f 100644 --- a/holoviews/plotting/util.py +++ b/holoviews/plotting/util.py @@ -1129,6 +1129,8 @@ def hex2rgb(hex): class apply_nodata(Operation): + link_inputs = param.Boolean(default=True) + nodata = param.Integer(default=None, doc=""" Optional missing-data value for integer data. If non-None, data with this value will be replaced with NaN so diff --git a/holoviews/tests/plotting/bokeh/test_links.py b/holoviews/tests/plotting/bokeh/test_links.py index cb6efa25ef..ed11ac9a46 100644 --- a/holoviews/tests/plotting/bokeh/test_links.py +++ b/holoviews/tests/plotting/bokeh/test_links.py @@ -1,6 +1,6 @@ import numpy as np import pytest -from bokeh.models import ColumnDataSource +from bokeh.models import ColumnDataSource, RangeTool from holoviews.core.spaces import DynamicMap from holoviews.element import Curve, Image, Path, Points, Polygons, Scatter, Table @@ -12,7 +12,6 @@ class TestLinkCallbacks(TestBokehPlot): def test_range_tool_link_callback_single_axis(self): - from bokeh.models import RangeTool array = np.random.rand(100, 2) src = Curve(array) target = Scatter(array) @@ -26,7 +25,6 @@ def test_range_tool_link_callback_single_axis(self): self.assertIs(range_tool.y_range, None) def test_range_tool_link_callback_single_axis_overlay_target(self): - from bokeh.models import RangeTool array = np.random.rand(100, 2) src = Curve(array) target = Scatter(array, label='a') * Scatter(array, label='b') @@ -40,7 +38,6 @@ def test_range_tool_link_callback_single_axis_overlay_target(self): self.assertIs(range_tool.y_range, None) def test_range_tool_link_callback_single_axis_overlay_target_image_source(self): - from bokeh.models import RangeTool data = np.random.rand(50, 50) target = Curve(data) * Curve(data) source = Image(np.random.rand(50, 50), bounds=(0, 0, 1, 1)) @@ -53,8 +50,40 @@ def test_range_tool_link_callback_single_axis_overlay_target_image_source(self): self.assertEqual(range_tool.x_range, tgt_plot.handles['x_range']) self.assertIs(range_tool.y_range, None) + def test_range_tool_link_callback_single_axis_curve_target_image_dmap_source(self): + # Choosing Image to exert the apply_nodata compositor + src = DynamicMap( + lambda a: Image(a*np.random.random((20, 20)), bounds=[0, 0, 9, 9]), + kdims=['a'] + ).redim.range(a=(0.1,1)) + target = Curve(np.arange(10)) + RangeToolLink(src, target) + layout = target + src + plot = bokeh_renderer.get_plot(layout) + tgt_plot = plot.subplots[(0, 0)].subplots['main'] + src_plot = plot.subplots[(0, 1)].subplots['main'] + range_tool = src_plot.state.select_one({'type': RangeTool}) + assert range_tool.x_range == tgt_plot.handles['x_range'] + assert range_tool.y_range is None + + def test_range_tool_link_callback_single_axis_overlay_target_image_dmap_source(self): + # Choosing Image to exert the apply_nodata compositor + src = DynamicMap( + lambda a: Image(a*np.random.random((20, 20)), bounds=[0, 0, 9, 9]), + kdims=['a'] + ).redim.range(a=(0.1,1)) + data = np.random.rand(50, 50) + target = Curve(data) * Curve(data) + RangeToolLink(src, target) + layout = target + src + plot = bokeh_renderer.get_plot(layout) + tgt_plot = plot.subplots[(0, 0)].subplots['main'] + src_plot = plot.subplots[(0, 1)].subplots['main'] + range_tool = src_plot.state.select_one({'type': RangeTool}) + assert range_tool.x_range == tgt_plot.handles['x_range'] + assert range_tool.y_range is None + def test_range_tool_link_callback_both_axes(self): - from bokeh.models import RangeTool array = np.random.rand(100, 2) src = Curve(array) target = Scatter(array) diff --git a/holoviews/util/__init__.py b/holoviews/util/__init__.py index 962496927c..34d384e32c 100644 --- a/holoviews/util/__init__.py +++ b/holoviews/util/__init__.py @@ -872,7 +872,7 @@ class Dynamic(param.ParameterizedFunction): link_inputs = param.Boolean(default=True, doc=""" If Dynamic is applied to another DynamicMap, determines whether - linked streams attached to its Callable inputs are + linked streams and links attached to its Callable inputs are transferred to the output of the utility. For example if the Dynamic utility is applied to a DynamicMap @@ -900,8 +900,12 @@ def __call__(self, map_obj, **params): callback = self._dynamic_operation(map_obj) streams = self._get_streams(map_obj, watch) if isinstance(map_obj, DynamicMap): - dmap = map_obj.clone(callback=callback, shared_data=self.p.shared_data, - streams=streams) + kwargs = dict( + shared_data=self.p.shared_data, callback=callback, streams=streams + ) + if self.p.link_inputs: + kwargs['plot_id'] = map_obj._plot_id + dmap = map_obj.clone(**kwargs) if self.p.shared_data: dmap.data = dict([(k, callback.callable(*k)) for k, v in dmap.data])