/
base.py
213 lines (172 loc) · 6.91 KB
/
base.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
"""
Defines the Widget base class which provides bi-directional
communication between the rendered dashboard and the Widget
parameters.
"""
from __future__ import absolute_import, division, unicode_literals
from functools import partial
import param
from ..layout import Row
from ..io import push, state, unlocked
from ..reactive import Reactive
from ..viewable import Layoutable
class Widget(Reactive):
"""
Widgets allow syncing changes in bokeh widget models with the
parameters on the Widget instance.
"""
disabled = param.Boolean(default=False, doc="""
Whether the widget is disabled.""")
name = param.String(default='')
height = param.Integer(default=None, bounds=(0, None))
width = param.Integer(default=None, bounds=(0, None))
margin = param.Parameter(default=(5, 10), doc="""
Allows to create additional space around the component. May
be specified as a two-tuple of the form (vertical, horizontal)
or a four-tuple (top, right, bottom, left).""")
__abstract = True
_widget_type = None
# Whether the widget supports embedding
_supports_embed = False
# Any parameters that require manual updates handling for the models
# e.g. parameters which affect some sub-model
_manual_params = []
_rename = {'name': 'title'}
def __init__(self, **params):
if 'name' not in params:
params['name'] = ''
if '_supports_embed' in params:
self._supports_embed = params.pop('_supports_embed')
if '_param_pane' in params:
self._param_pane = params.pop('_param_pane')
else:
self._param_pane = None
super(Widget, self).__init__(**params)
self.param.watch(self._update_widget, self._manual_params)
@classmethod
def from_param(cls, parameter, **params):
"""
Construct a widget from a Parameter and link the two
bi-directionally.
Parameters
----------
parameter: param.Parameter
A parameter to create the widget from.
params: dict
Keyword arguments to be passed to the widget constructor
Returns
-------
Widget instance linked to the supplied parameter
"""
from ..param import Param
layout = Param(parameter, widgets={parameter.name: dict(type=cls, **params)})
return layout[0]
def _manual_update(self, events, model, doc, root, parent, comm):
"""
Method for handling any manual update events, i.e. events triggered
by changes in the manual params.
"""
def _update_widget(self, *events):
for ref, (model, parent) in self._models.items():
if ref not in state._views or ref in state._fake_roots:
continue
viewable, root, doc, comm = state._views[ref]
if comm or state._unblocked(doc):
with unlocked():
self._manual_update(events, model, doc, root, parent, comm)
if comm and 'embedded' not in root.tags:
push(doc, comm)
else:
cb = partial(self._manual_update, events, model, doc, root, parent, comm)
if doc.session_context:
doc.add_next_tick_callback(cb)
else:
cb()
def _get_model(self, doc, root=None, parent=None, comm=None):
model = self._widget_type(**self._process_param_change(self._init_properties()))
if root is None:
root = model
# Link parameters and bokeh model
values = dict(self.param.get_param_values())
properties = self._filter_properties(list(self._process_param_change(values)))
self._models[root.ref['id']] = (model, parent)
self._link_props(model, properties, doc, root, comm)
return model
@property
def _linkable_params(self):
return [p for p in self._synced_params() if self._rename.get(p, False) is not None
and self._source_transforms.get(p, False) is not None]
def _synced_params(self):
return [p for p in self.param if p not in self._manual_params]
def _filter_properties(self, properties):
return [p for p in properties if p not in Layoutable.param]
def _get_embed_state(self, root, values=None, max_opts=3):
"""
Returns the bokeh model and a discrete set of value states
for the widget.
Arguments
---------
root: bokeh.model.Model
The root model of the widget
values: list (optional)
An explicit list of value states to embed
max_opts: int
The maximum number of states the widget should return
Returns
-------
widget: panel.widget.Widget
The Panel widget instance to modify to effect state changes
model: bokeh.model.Model
The bokeh model to record the current value state on
values: list
A list of value states to explore.
getter: callable
A function that returns the state value given the model
on_change: string
The name of the widget property to attach a callback on
js_getter: string
JS snippet that returns the state value given the model
"""
class CompositeWidget(Widget):
"""
A baseclass for widgets which are made up of two or more other
widgets
"""
__abstract = True
_composite_type = Row
def __init__(self, **params):
super(CompositeWidget, self).__init__(**params)
layout = {p: getattr(self, p) for p in Layoutable.param
if getattr(self, p) is not None}
self._composite = self._composite_type(**layout)
self._models = self._composite._models
self.param.watch(self._update_layout_params, list(Layoutable.param))
def _update_layout_params(self, *events):
for event in events:
setattr(self._composite, event.name, event.new)
def select(self, selector=None):
"""
Iterates over the Viewable and any potential children in the
applying the Selector.
Arguments
---------
selector: type or callable or None
The selector allows selecting a subset of Viewables by
declaring a type or callable function to filter by.
Returns
-------
viewables: list(Viewable)
"""
objects = super(CompositeWidget, self).select(selector)
for obj in self._composite.objects:
objects += obj.select(selector)
return objects
def _cleanup(self, root):
self._composite._cleanup(root)
super(CompositeWidget, self)._cleanup(root)
def _get_model(self, doc, root=None, parent=None, comm=None):
return self._composite._get_model(doc, root, parent, comm)
def __contains__(self, object):
return object in self._composite.objects
def _synced_params(self):
return []