Skip to content

Commit

Permalink
Do not merge partially overlapping Stream callbacks (#5133)
Browse files Browse the repository at this point in the history
* Do not merge partially overlapping Stream callbacks

* Add tests

* Remove print
  • Loading branch information
philippjfr committed Nov 15, 2021
1 parent 21c7c8b commit 363b970
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 13 deletions.
30 changes: 17 additions & 13 deletions holoviews/plotting/bokeh/callbacks.py
Expand Up @@ -440,28 +440,32 @@ def set_callback(self, handle):

def initialize(self, plot_id=None):
handles = self._init_plot_handles()
cb_handles = []
for handle_name in self.models:
if handle_name not in handles:
warn_args = (handle_name, type(self.plot).__name__,
type(self).__name__)
print('%s handle not found on %s, cannot '
'attach %s callback' % warn_args)
continue
handle = handles[handle_name]

# Hash the plot handle with Callback type allowing multiple
# callbacks on one handle to be merged
cb_hash = (id(handle), id(type(self)))
if cb_hash in self._callbacks:
# Merge callbacks if another callback has already been attached
cb = self._callbacks[cb_hash]
cb.streams = list(set(cb.streams+self.streams))
for k, v in self.handle_ids.items():
cb.handle_ids[k].update(v)
continue
cb_handles.append(handles[handle_name])

# Hash the plot handle with Callback type allowing multiple
# callbacks on one handle to be merged
handle_ids = [id(h) for h in cb_handles]
cb_hash = tuple(handle_ids)+(id(type(self)),)
if cb_hash in self._callbacks:
# Merge callbacks if another callback has already been attached
cb = self._callbacks[cb_hash]
cb.streams = list(set(cb.streams+self.streams))
for k, v in self.handle_ids.items():
cb.handle_ids[k].update(v)
self.cleanup()
return

for handle in cb_handles:
self.set_callback(handle)
self._callbacks[cb_hash] = self
self._callbacks[cb_hash] = self



Expand Down
47 changes: 47 additions & 0 deletions holoviews/tests/plotting/bokeh/test_callbacks.py
Expand Up @@ -459,3 +459,50 @@ def test_rangexy_framewise_not_reset_if_triggering(self):
))
stream.event(x_range=(0, 3))
self.assertEqual(stream.x_range, (0, 3))

def test_rangexy_shared_transposed_axes(self):
"Checks that stream callbacks are not shared on transposed axes"
c1 = Curve([1, 2, 3], 'x', 'y')
c2 = Curve([1, 2, 3], 'y', 'x')
stream1 = RangeXY(source=c1)
stream2 = RangeXY(source=c2)
layout = c1 + c2
plot = bokeh_server_renderer.get_plot(layout)
c1p, c2p = [p.subplots['main'] for p in plot.subplots.values()]

c1_cb = c1p.callbacks[0]
assert c1_cb.streams == [stream1]
assert stream1 in c1_cb.handle_ids
stream1_handles = c1_cb.handle_ids[stream1]
assert stream1_handles['x_range'] == c1p.handles['x_range'].ref['id']
assert stream1_handles['y_range'] == c1p.handles['y_range'].ref['id']

c2_cb = c2p.callbacks[0]
assert c2_cb.streams == [stream2]
assert stream2 in c2_cb.handle_ids
stream2_handles = c2_cb.handle_ids[stream2]
assert stream2_handles['x_range'] == c2p.handles['x_range'].ref['id']
assert stream2_handles['y_range'] == c2p.handles['y_range'].ref['id']

def test_rangexy_shared_axes(self):
"Check that stream callbacks are shared on shared axes"
c1 = Curve([1, 2, 3], 'x', 'y')
c2 = Curve([1, 2, 3], 'x', 'y')
stream1 = RangeXY(source=c1)
stream2 = RangeXY(source=c2)
layout = c1 + c2
plot = bokeh_server_renderer.get_plot(layout)
c1p, c2p = [p.subplots['main'] for p in plot.subplots.values()]
c1_cb = c1p.callbacks[0]
c2_cb = c2p.callbacks[0]

assert set(c1_cb.streams) == {stream1, stream2}
assert stream1 in c1_cb.handle_ids
stream1_handles = c1_cb.handle_ids[stream1]
assert stream1_handles['x_range'] == c1p.handles['x_range'].ref['id']
assert stream1_handles['y_range'] == c1p.handles['y_range'].ref['id']
stream2_handles = c1_cb.handle_ids[stream2]
assert stream2_handles['x_range'] == c2p.handles['x_range'].ref['id']
assert stream2_handles['y_range'] == c2p.handles['y_range'].ref['id']

assert c2_cb.streams == []

0 comments on commit 363b970

Please sign in to comment.