diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 0d15a0939a..f4db5b530d 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -1260,6 +1260,9 @@ class DataLinkCallback(LinkCallback): def __init__(self, root_model, link, source_plot, target_plot): src_cds = source_plot.handles['source'] tgt_cds = target_plot.handles['source'] + if src_cds is tgt_cds: + return + src_len = [len(v) for v in src_cds.data.values()] tgt_len = [len(v) for v in tgt_cds.data.values()] if src_len[0] != tgt_len[0]: diff --git a/holoviews/plotting/links.py b/holoviews/plotting/links.py index 0e9bfc21f3..b18727b1bb 100644 --- a/holoviews/plotting/links.py +++ b/holoviews/plotting/links.py @@ -64,6 +64,15 @@ def link(self): Registers the Link """ if self.source in self.registry: + links = self.registry[self.source] + params = { + k: v for k, v in self.get_param_values() if k != 'name'} + for link in links: + link_params = { + k: v for k, v in link.get_param_values() if k != 'name'} + if (type(link) is type(self) and link.source is self.source + and link.target is self.target and params == link_params): + return self.registry[self.source].append(self) else: self.registry[self.source] = [self] diff --git a/holoviews/tests/plotting/bokeh/testlinks.py b/holoviews/tests/plotting/bokeh/testlinks.py index 572ad22eb0..730f568fbc 100644 --- a/holoviews/tests/plotting/bokeh/testlinks.py +++ b/holoviews/tests/plotting/bokeh/testlinks.py @@ -4,7 +4,7 @@ from holoviews.core.spaces import DynamicMap from holoviews.element import Curve, Polygons, Table, Scatter, Path, Points -from holoviews.plotting.links import (RangeToolLink, DataLink) +from holoviews.plotting.links import (Link, RangeToolLink, DataLink) try: from holoviews.plotting.bokeh.util import bokeh_version @@ -116,3 +116,12 @@ def test_data_link_list(self): plot = bokeh_renderer.get_plot(layout) path_plot, table_plot = (sp.subplots['main'] for sp in plot.subplots.values()) self.assertIs(path_plot.handles['source'], table_plot.handles['source']) + + def test_data_link_idempotent(self): + table1 = Table([], 'A', 'B') + table2 = Table([], 'C', 'D') + link1 = DataLink(table1, table2) + DataLink(table1, table2) + self.assertEqual(len(Link.registry[table1]), 1) + self.assertIn(link1, Link.registry[table1]) +