/
links.py
111 lines (90 loc) · 3.77 KB
/
links.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import weakref
from collections import defaultdict
import param
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.get_param_values() if k != 'name'}
for link in links:
link_params = {
k: v for k, v in link.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