From 3348b11d12e217821d8ada61ad4b116dc82678bf Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sat, 7 Jan 2017 01:01:20 +0000
Subject: [PATCH] Refactored bokeh Callback class
---
holoviews/plotting/bokeh/callbacks.py | 91 ++++++++++++++++-----------
1 file changed, 53 insertions(+), 38 deletions(-)
diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py
index 4e3d542334..456a1de5b4 100644
--- a/holoviews/plotting/bokeh/callbacks.py
+++ b/holoviews/plotting/bokeh/callbacks.py
@@ -167,8 +167,8 @@ def __init__(self, plot, streams, source, **params):
self.plot = plot
self.streams = streams
self.comm = self._comm_type(plot, on_msg=self.on_msg)
- self.stream_handles = defaultdict(list)
self.source = source
+ self.handle_ids = defaultdict(list)
def initialize(self):
@@ -176,32 +176,46 @@ def initialize(self):
if self.plot.subplots:
plots += list(self.plot.subplots.values())
+ handles = {}
+ for plot in plots:
+ for k, v in plot.handles.items():
+ if k not in handles:
+ handles[k] = v
+ self.handle_ids.update(self._get_handle_ids(handles))
+
found = []
for plot in plots:
for handle in self.handles:
if handle not in plot.handles or handle in found:
continue
- self.set_customjs(plot.handles[handle])
+ self.set_customjs(plot.handles[handle], handles)
found.append(handle)
+
if len(found) != len(self.handles):
self.warning('Plotting handle for JS callback not found')
+ def _filter_msg(self, msg, ids):
+ """
+ Filter event values that do not originate from the plotting
+ handles associated with a particular stream using their
+ ids to match them.
+ """
+ filtered_msg = {}
+ for k, v in msg.items():
+ if isinstance(v, dict) and 'id' in v:
+ if v['id'] in ids:
+ filtered_msg[k] = v['value']
+ else:
+ filtered_msg[k] = v
+ return filtered_msg
+
+
def on_msg(self, msg):
- # For each stream check whether plot state is meant for it
- # by checking that the IDs match the IDs of the stream's plot
- # handles, dispatch only the part of the message meant for
- # a particular stream
for stream in self.streams:
- ids = self.stream_handles[stream]
- sanitized_msg = {}
- for k, v in msg.items():
- if isinstance(v, dict) and 'id' in v:
- if v['id'] in ids:
- sanitized_msg[k] = v['value']
- else:
- sanitized_msg[k] = v
- processed_msg = self._process_msg(sanitized_msg)
+ ids = self.handle_ids[stream]
+ filtered_msg = self._filter_msg(msg, ids)
+ processed_msg = self._process_msg(filtered_msg)
if not processed_msg:
continue
stream.update(trigger=False, **processed_msg)
@@ -209,10 +223,29 @@ def on_msg(self, msg):
def _process_msg(self, msg):
+ """
+ Subclassable method to preprocess JSON message in callback
+ before passing to stream.
+ """
return msg
- def set_customjs(self, handle):
+ def _get_handle_ids(self, handles):
+ """
+ Gather the ids of the plotting handles attached to this callback
+ This allows checking that a stream is not given the state
+ of a plotting handle it wasn't attached to
+ """
+ stream_handle_ids = defaultdict(list)
+ for stream in self.streams:
+ for h in self.handles:
+ if h in handles:
+ handle_id = handles[h].ref['id']
+ stream_handle_ids[stream].append(handle_id)
+ return stream_handle_ids
+
+
+ def set_customjs(self, handle, references):
"""
Generates a CustomJS callback by generating the required JS
code and gathering all plotting handles and installs it on
@@ -224,38 +257,20 @@ def set_customjs(self, handle):
timeout=self.timeout,
debounce=self.debounce)
- handles = {}
- subplots = list(self.plot.subplots.values())[::-1] if self.plot.subplots else []
- plots = [self.plot] + subplots
- for plot in plots:
- handles.update({k: v for k, v in plot.handles.items()
- if k in self.handles})
-
- attributes = attributes_js(self.attributes, handles)
+ attributes = attributes_js(self.attributes, references)
code = 'var data = {};\n' + attributes + self.code + self_callback
- # Gather the ids of the plotting handles attached to this callback
- # This allows checking that a stream is not given the state
- # of a plotting handle it wasn't attached to
- stream_handle_ids = defaultdict(list)
- for stream in self.streams:
- for h in self.handles:
- if h in handles:
- handle_id = handles[h].ref['id']
- stream_handle_ids[stream].append(handle_id)
-
# Set callback
if id(handle.callback) in self._callbacks:
cb = self._callbacks[id(handle.callback)]
if isinstance(cb, type(self)):
cb.streams += self.streams
- for k, v in stream_handle_ids.items():
- cb.stream_handles[k] += v
+ for k, v in self.handle_ids.items():
+ cb.handle_ids[k] += v
else:
handle.callback.code += code
else:
- self.stream_handles.update(stream_handle_ids)
- js_callback = CustomJS(args=handles, code=code)
+ js_callback = CustomJS(args=references, code=code)
self._callbacks[id(js_callback)] = self
handle.callback = js_callback