from __future__ import absolute_import import weakref from collections import defaultdict import param from ..core.util import dimension_sanitizer class Link(param.Parameterized): """ A Link defines some connection between a source and target object in their visualization. It is quite similar to a Stream as it allows defining callbacks in response to some change or event on the source object, however, unlike a Stream, it does not transfer data and make it available to user defined subscribers. Instead a Link directly causes some action to occur on the target, for JS based backends this usually means that a corresponding JS callback will effect some change on the target in response to a change on the source. A Link must define a source object which is what triggers events, but must not define a target. It is also possible to define bi- directional links between the source and target object. """ # Mapping from a source id to a Link instance registry = weakref.WeakKeyDictionary() # Mapping to define callbacks by backend and Link type. # e.g. Link._callbacks['bokeh'][Stream] = Callback _callbacks = defaultdict(dict) # Whether the link requires a target _requires_target = False def __init__(self, source, target=None, **params): if source is None: raise ValueError('%s must define a source' % type(self).__name__) if self._requires_target and target is None: raise ValueError('%s must define a target.' % type(self).__name__) # Source is stored as a weakref to allow it to be garbage collected self._source = None if source is None else weakref.ref(source) self._target = None if target is None else weakref.ref(target) super(Link, self).__init__(**params) self.link() @classmethod def register_callback(cls, backend, callback): """ Register a LinkCallback providing the implementation for the Link for a particular backend. """ cls._callbacks[backend][cls] = callback @property def source(self): return self._source() if self._source else None @property def target(self): return self._target() if self._target else None 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.param.get_param_values() if k != 'name'} for link in links: link_params = { k: v for k, v in link.param.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] def unlink(self): """ Unregisters the Link """ links = self.registry.get(self.source) if self in links: links.pop(links.index(self)) class RangeToolLink(Link): """ The RangeToolLink sets up a link between a RangeTool on the source plot and the axes on the target plot. It is useful for exploring a subset of a larger dataset in more detail. By default it will link along the x-axis but using the axes parameter both axes may be linked to the tool. """ axes = param.ListSelector(default=['x'], objects=['x', 'y'], doc=""" Which axes to link the tool to.""") _requires_target = True class DataLink(Link): """ DataLink defines a link in the data between two objects allowing them to be selected together. In order for a DataLink to be established the source and target data must be of the same length. """ _requires_target = True class SelectionLink(Link): """ Links the selection between two glyph renderers. """ _requires_target = True class VertexTableLink(Link): """ Defines a Link between a Path type and a Table that will display the vertices of selected path. """ vertex_columns = param.List(default=[]) _requires_target = True def __init__(self, source, target, **params): if 'vertex_columns' not in params: dimensions = [dimension_sanitizer(d.name) for d in target.dimensions()[:2]] params['vertex_columns'] = dimensions super(VertexTableLink, self).__init__(source, target, **params) class RectanglesTableLink(Link): """ Links a Rectangles element to a Table. """ _requires_target = True