Skip to content

Commit

Permalink
Cleaned up stream callback implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Sep 30, 2016
1 parent 8977802 commit f7da46c
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 34 deletions.
54 changes: 35 additions & 19 deletions holoviews/plotting/bokeh/callbacks.py
Expand Up @@ -14,6 +14,12 @@ def attributes_js(attributes):
"""
Generates JS code to look up attributes on JS objects from
an attributes specification dictionary.
Example:
Input : {'x': 'cb_data.geometry.x'}
Output : data['x'] = cb_data['geometry']['x']
"""
code = ''
for key, attr_path in attributes.items():
Expand All @@ -26,27 +32,34 @@ def attributes_js(attributes):
return code


class Callback(param.Parameterized):
class Callback(object):
"""
Provides a baseclass to define callbacks, which return data from
bokeh models such as the plot ranges or various tools. The callback
then makes this data available to any streams attached to it.
The defintion of a callback consists of a number of components:
* handles : The handles define which object the callback will be
attached on.
* attributes : The attributes define which attributes to send back
to Python. They are defined as a dictionary mapping
between the name under which the variable is made
available to Python and the specification of the
attribute. The specification should start with the
variable name that is to be accessed and the
location of the attribute separated by periods.
* handles : The handles define which plotting handles the
callback will be attached on, e.g. this could be
the x_range, y_range, a plotting tool or any other
bokeh object that allows callbacks.
* attributes : The attributes define which attributes to send
back to Python. They are defined as a dictionary
mapping between the name under which the variable
is made available to Python and the specification
of the attribute. The specification should start
with the variable name that is to be accessed and
the location of the attribute separated by periods.
All plotting handles such as tools, the x_range,
y_range and (data)source can be addressed in this
way.
way, e.g. to get the start of the x_range as 'x'
you can supply {'x': 'x_range.attributes.start'}.
Additionally certain handles additionally make the
cb_data and cb_obj variables available containing
additional information about the event.
* code : Defines any additional JS code to be executed,
which can modify the data object that is sent to
the backend.
Expand All @@ -56,10 +69,7 @@ class Callback(param.Parameterized):
streams.
"""

code = param.String(default="", doc="""
Custom javascript code executed on the callback. The code
has access to the plot, source and cb_obj and may modify
the data javascript object sent back to Python.""")
code = ""

attributes = {}

Expand Down Expand Up @@ -121,7 +131,13 @@ def _process_msg(self, msg):
return msg


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

# Generate callback JS code to get all the requested data
self_callback = self.js_callback.format(comms_target=self.comm.target)
attributes = attributes_js(self.attributes)
Expand All @@ -131,8 +147,8 @@ def set_customjs(self, cb_obj):
plots = [self.plot] + (self.plot.subplots.values()[::-1] if self.plot.subplots else [])
for plot in plots:
handles.update(plot.handles)
# Set cb_obj
cb_obj.callback = CustomJS(args=handles, code=code)
# Set callback
handle.callback = CustomJS(args=handles, code=code)



Expand Down
14 changes: 8 additions & 6 deletions holoviews/plotting/bokeh/element.py
Expand Up @@ -167,10 +167,10 @@ def __init__(self, element, plot=None, **params):
super(ElementPlot, self).__init__(element, **params)
self.handles = {} if plot is None else self.handles['plot']
self.static = len(self.hmap) == 1 and len(self.keys) == len(self.hmap)
self.callbacks = self._init_callbacks()
self.callbacks = self._construct_callbacks()


def _init_callbacks(self):
def _construct_callbacks(self):
"""
Initializes any callbacks for streams which have defined
the plotted object as a source.
Expand All @@ -179,14 +179,14 @@ def _init_callbacks(self):
source = self.hmap
else:
source = self.hmap.last
streams = Stream.registry.get(source, [])
streams = Stream.registry.get(id(source), [])
registry = Stream._callbacks['bokeh']
callbacks = {(registry[type(stream)], stream) for stream in streams
if type(stream) in registry and streams}
cbs = []
for cb, group in groupby(sorted(callbacks), lambda x: x[0]):
cb_streams = [s for _, s in group]
cbs.append(cb(self, streams, source))
cbs.append(cb(self, cb_streams, source))
return cbs


Expand Down Expand Up @@ -574,8 +574,10 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
if not self.overlaid:
self._update_plot(key, plot, style_element)

for cb in self.callbacks:
cb.initialize()
if not self.batched:
for cb in self.callbacks:
cb.initialize()

if not self.overlaid:
self._process_legend()
self.drawn = True
Expand Down
9 changes: 5 additions & 4 deletions holoviews/plotting/bokeh/util.py
Expand Up @@ -165,7 +165,8 @@ def get_ids(obj):

def replace_models(obj):
"""
Processes references replacing Model and HasProps objects.
Recursively processes references, replacing Models with there .ref
values and HasProps objects with their property values.
"""
if isinstance(obj, Model):
return obj.ref
Expand All @@ -181,9 +182,9 @@ def replace_models(obj):

def to_references(doc):
"""
Convert the document to a dictionary of references. Avoids
converting document to json and the performance penalty that
involves.
Convert the document to a dictionary of references. Avoids
unnecessary JSON serialization/deserialization within Python and
the corresponding performance penalty.
"""
root_ids = []
for r in doc._roots:
Expand Down
10 changes: 5 additions & 5 deletions holoviews/streams.py
Expand Up @@ -5,7 +5,6 @@
"""

import param
import uuid
from collections import defaultdict
from .core import util

Expand Down Expand Up @@ -74,9 +73,11 @@ class Stream(param.Parameterized):
the parameter dictionary when the trigger classmethod is called.
"""

# Mapping from uuid to stream instance
# Mapping from a source id to a list of streams
registry = defaultdict(list)

# Mapping to define callbacks by backend and Stream type.
# e.g. Stream._callbacks['bokeh'][Stream] = Callback
_callbacks = defaultdict(dict)

@classmethod
Expand Down Expand Up @@ -120,10 +121,9 @@ def __init__(self, preprocessors=[], source=None, subscribers=[], **params):
self.preprocessors = preprocessors
self._hidden_subscribers = []

self.uuid = uuid.uuid4().hex
super(Stream, self).__init__(**params)
if source:
self.registry[source].append(self)
self.registry[id(source)].append(self)

@property
def source(self):
Expand All @@ -134,7 +134,7 @@ def source(self, source):
if self._source:
raise Exception('source has already been defined on stream.')
self._source = source
self.registry[source].append(self)
self.registry[id(source)].append(self)


@property
Expand Down

0 comments on commit f7da46c

Please sign in to comment.