Skip to content

Commit

Permalink
Cleanup and refactoring of Streams and Callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Mar 23, 2017
1 parent 40cbea0 commit 5d54235
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 32 deletions.
70 changes: 40 additions & 30 deletions holoviews/plotting/bokeh/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from ...streams import (Stream, PositionXY, RangeXY, Selection1D, RangeX,
RangeY, PositionX, PositionY, Bounds, Tap,
DoubleTap, MouseEnter, MouseLeave, PlotDimensions)
DoubleTap, MouseEnter, MouseLeave, PlotSize)
from ..comms import JupyterCommJS
from .util import bokeh_version

Expand Down Expand Up @@ -60,15 +60,16 @@ class Callback(object):
The definition of a callback consists of a number of components:
* models : Defines which bokeh models the callback will be
* models : Defines which bokeh models the callback will be
attached on referencing the model by its key in
the plots handles, e.g. this could be the x_range,
y_range, plot, a plotting tool or any other
bokeh mode.
* extra_models: Any additional models available in handles which
should be made available in the namespace of the
objects.
objects, e.g. to make a tool available to skip
checks.
* attributes : The attributes define which attributes to send
back to Python. They are defined as a dictionary
Expand Down Expand Up @@ -258,7 +259,9 @@ def initialize(self):
'attach %s callback' % warn_args)
continue
handle = handles[handle_name]
self.callbacks.append(self.set_customjs(handle, requested))
js_callback = self.get_customjs(requested)
self.set_customjs(js_callback, handle)
self.callbacks.append(js_callback)


def _filter_msg(self, msg, ids):
Expand All @@ -279,18 +282,18 @@ def _filter_msg(self, msg, ids):

def on_msg(self, msg):
for stream in self.streams:
metadata = self.handle_ids[stream]
ids = list(metadata.values())
handle_ids = self.handle_ids[stream]
ids = list(handle_ids.values())
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)
stream._metadata = {h: {'id': hid, 'events': self.events}
for h, hid in metadata.items()}
for h, hid in handle_ids.items()}
Stream.trigger(self.streams)
for stream in self.streams:
stream._metadata = None
stream._metadata = {}


def _process_msg(self, msg):
Expand Down Expand Up @@ -327,13 +330,11 @@ def _get_stream_handle_ids(self, handles):
return stream_handle_ids


def set_customjs(self, handle, references):
def get_customjs(self, references):
"""
Generates a CustomJS callback by generating the required JS
code and gathering all plotting handles and installs it on
the requested callback handle.
Creates a CustomJS callback that will send the requested
attributes back to python.
"""

# Generate callback JS code to get all the requested data
self_callback = self.js_callback.format(comm_id=self.comm.id,
timeout=self.timeout,
Expand All @@ -346,29 +347,38 @@ def set_customjs(self, handle, references):
conditional = 'if (%s) { return };\n' % (' || '.join(conditions))
data = "var data = {};\n"
code = conditional + data + attributes + self.code + self_callback
return CustomJS(args=references, code=code)


def set_customjs(self, js_callback, handle):
"""
Generates a CustomJS callback by generating the required JS
code and gathering all plotting handles and installs it on
the requested callback handle.
"""

js_callback = CustomJS(args=references, code=code)
cb = None
if id(handle) in self._callbacks:
# 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[id(handle)]
cb = self._callbacks[cb_hash]
if isinstance(cb, type(self)):
cb.streams += self.streams
for k, v in self.handle_ids.items():
cb.handle_ids[k].update(v)
return

if not cb:
if self.events and bokeh_version >= '0.12.5':
for event in self.events:
handle.js_on_event(event, js_callback)
elif self.change and bokeh_version >= '0.12.5':
for change in self.change:
handle.js_on_change(change, js_callback)
else:
handle.callback = js_callback
self._callbacks[cb_hash] = self
if self.events and bokeh_version >= '0.12.5':
for event in self.events:
handle.js_on_event(event, js_callback)
elif self.change and bokeh_version >= '0.12.5':
for change in self.change:
handle.js_on_change(change, js_callback)
elif hasattr(handle, 'callback'):
handle.callback = js_callback

self._callbacks[id(handle)] = self
return js_callback



Expand Down Expand Up @@ -485,7 +495,7 @@ def _process_msg(self, msg):
return {}


class PlotDimensionCallback(Callback):
class PlotSizeCallback(Callback):
"""
Returns the actual width and height of a plot once the layout
solver has executed.
Expand Down Expand Up @@ -545,4 +555,4 @@ def _process_msg(self, msg):
callbacks[RangeY] = RangeYCallback
callbacks[Bounds] = BoundsCallback
callbacks[Selection1D] = Selection1DCallback
callbacks[PlotDimensions] = PlotDimensionCallback
callbacks[PlotSize] = PlotSizeCallback
2 changes: 2 additions & 0 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ def _init_plot(self, key, element, plots, ranges=None):

properties['webgl'] = Store.renderers[self.renderer.backend].webgl
with warnings.catch_warnings():
# Bokeh raises warnings about duplicate tools but these
# are not really an issue
warnings.simplefilter('ignore', UserWarning)
return bokeh.plotting.Figure(x_axis_type=x_axis_type,
y_axis_type=y_axis_type, title=title,
Expand Down
8 changes: 6 additions & 2 deletions holoviews/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,13 @@ def __init__(self, preprocessors=[], source=None, subscribers=[],
self.subscribers = subscribers
self.preprocessors = preprocessors
self._hidden_subscribers = []
self._metadata = None
self.interactive = interactive

# The metadata may provide information about the currently
# active event, i.e. the source of the stream values may
# indicate where the event originated from
self._metadata = {}

super(Stream, self).__init__(**params)
if source:
self.registry[id(source)].append(self)
Expand Down Expand Up @@ -264,7 +268,7 @@ class MouseLeave(PositionXY):
"""


class PlotDimensions(Stream):
class PlotSize(Stream):
"""
Returns the dimensions of a plot once it has been displayed.
"""
Expand Down

0 comments on commit 5d54235

Please sign in to comment.