fixed issue 1581
Handle condition where Param.parameters was set
Keep track whether parameters was explicitly set
Co-authored-by: Marc Skov Madsen <masma@orsted.dk> Co-authored-by: Philipp Rudiger <prudiger@anaconda.com>
1 |
"""
|
|
2 |
Defines Layout classes which may be used to arrange panes and widgets
|
|
3 |
in flexible ways to build complex dashboards.
|
|
4 |
"""
|
|
5 | 2 |
from __future__ import absolute_import, division, unicode_literals |
6 |
|
|
7 | 2 |
from collections import defaultdict, namedtuple |
8 |
|
|
9 | 2 |
import param |
10 |
|
|
11 | 2 |
from bokeh.models import Column as BkColumn, Row as BkRow |
12 |
|
|
13 | 2 |
from ..io.model import hold |
14 | 2 |
from ..io.state import state |
15 | 2 |
from ..reactive import Reactive |
16 | 2 |
from ..util import param_name, param_reprs |
17 |
|
|
18 | 2 |
_row = namedtuple("row", ["children"]) |
19 | 2 |
_col = namedtuple("col", ["children"]) |
20 |
|
|
21 |
|
|
22 | 2 |
class Panel(Reactive): |
23 |
"""
|
|
24 |
Abstract baseclass for a layout of Viewables.
|
|
25 |
"""
|
|
26 |
|
|
27 | 2 |
_bokeh_model = None |
28 |
|
|
29 | 2 |
__abstract = True |
30 |
|
|
31 | 2 |
_rename = {'objects': 'children'} |
32 |
|
|
33 | 2 |
_linked_props = [] |
34 |
|
|
35 | 2 |
def __repr__(self, depth=0, max_depth=10): |
36 | 2 |
if depth > max_depth: |
37 |
return '...' |
|
38 | 2 |
spacer = '\n' + (' ' * (depth+1)) |
39 | 2 |
cls = type(self).__name__ |
40 | 2 |
params = param_reprs(self, ['objects']) |
41 | 2 |
objs = ['[%d] %s' % (i, obj.__repr__(depth+1)) for i, obj in enumerate(self)] |
42 | 2 |
if not params and not objs: |
43 |
return super(Panel, self).__repr__(depth+1) |
|
44 | 2 |
elif not params: |
45 | 2 |
template = '{cls}{spacer}{objs}' |
46 |
elif not objs: |
|
47 |
template = '{cls}({params})' |
|
48 |
else: |
|
49 |
template = '{cls}({params}){spacer}{objs}' |
|
50 | 2 |
return template.format( |
51 |
cls=cls, params=', '.join(params), |
|
52 |
objs=('%s' % spacer).join(objs), spacer=spacer) |
|
53 |
|
|
54 |
#----------------------------------------------------------------
|
|
55 |
# Callback API
|
|
56 |
#----------------------------------------------------------------
|
|
57 |
|
|
58 | 2 |
def _update_model(self, events, msg, root, model, doc, comm=None): |
59 | 2 |
msg = dict(msg) |
60 | 2 |
if self._rename['objects'] in msg: |
61 | 2 |
old = events['objects'].old |
62 | 2 |
msg[self._rename['objects']] = self._get_objects(model, old, doc, root, comm) |
63 |
|
|
64 | 2 |
with hold(doc): |
65 | 2 |
super(Panel, self)._update_model(events, msg, root, model, doc, comm) |
66 | 2 |
from ..io import state |
67 | 2 |
ref = root.ref['id'] |
68 | 2 |
if ref in state._views: |
69 | 2 |
state._views[ref][0]._preprocess(root) |
70 |
|
|
71 |
#----------------------------------------------------------------
|
|
72 |
# Model API
|
|
73 |
#----------------------------------------------------------------
|
|
74 |
|
|
75 | 2 |
def _init_properties(self): |
76 | 2 |
properties = {k: v for k, v in self.param.get_param_values() |
77 |
if v is not None} |
|
78 | 2 |
del properties['objects'] |
79 | 2 |
return self._process_param_change(properties) |
80 |
|
|
81 | 2 |
def _get_objects(self, model, old_objects, doc, root, comm=None): |
82 |
"""
|
|
83 |
Returns new child models for the layout while reusing unchanged
|
|
84 |
models and cleaning up any dropped objects.
|
|
85 |
"""
|
|
86 | 2 |
from ..pane.base import panel, RerenderError |
87 | 2 |
new_models = [] |
88 | 2 |
for i, pane in enumerate(self.objects): |
89 | 2 |
pane = panel(pane) |
90 | 2 |
self.objects[i] = pane |
91 |
|
|
92 | 2 |
for obj in old_objects: |
93 | 2 |
if obj not in self.objects: |
94 | 2 |
obj._cleanup(root) |
95 |
|
|
96 | 2 |
current_objects = list(self.objects) |
97 | 2 |
for i, pane in enumerate(self.objects): |
98 | 2 |
if pane in old_objects: |
99 | 2 |
child, _ = pane._models[root.ref['id']] |
100 |
else: |
|
101 | 2 |
try: |
102 | 2 |
child = pane._get_model(doc, root, model, comm) |
103 |
except RerenderError: |
|
104 |
return self._get_objects(model, current_objects[:i], doc, root, comm) |
|
105 | 2 |
new_models.append(child) |
106 | 2 |
return new_models |
107 |
|
|
108 | 2 |
def _get_model(self, doc, root=None, parent=None, comm=None): |
109 | 2 |
model = self._bokeh_model() |
110 | 2 |
if root is None: |
111 | 2 |
root = model |
112 | 2 |
objects = self._get_objects(model, [], doc, root, comm) |
113 | 2 |
props = dict(self._init_properties(), objects=objects) |
114 | 2 |
model.update(**self._process_param_change(props)) |
115 | 2 |
self._models[root.ref['id']] = (model, parent) |
116 | 2 |
self._link_props(model, self._linked_props, doc, root, comm) |
117 | 2 |
return model |
118 |
|
|
119 |
#----------------------------------------------------------------
|
|
120 |
# Public API
|
|
121 |
#----------------------------------------------------------------
|
|
122 |
|
|
123 | 2 |
def select(self, selector=None): |
124 |
"""
|
|
125 |
Iterates over the Viewable and any potential children in the
|
|
126 |
applying the Selector.
|
|
127 |
|
|
128 |
Arguments
|
|
129 |
---------
|
|
130 |
selector: type or callable or None
|
|
131 |
The selector allows selecting a subset of Viewables by
|
|
132 |
declaring a type or callable function to filter by.
|
|
133 |
|
|
134 |
Returns
|
|
135 |
-------
|
|
136 |
viewables: list(Viewable)
|
|
137 |
"""
|
|
138 | 2 |
objects = super(Panel, self).select(selector) |
139 | 2 |
for obj in self: |
140 | 2 |
objects += obj.select(selector) |
141 | 2 |
return objects |
142 |
|
|
143 |
|
|
144 |
|
|
145 | 2 |
class ListLike(param.Parameterized): |
146 |
|
|
147 | 2 |
objects = param.List(default=[], doc=""" |
148 |
The list of child objects that make up the layout.""") |
|
149 |
|
|
150 | 2 |
def __getitem__(self, index): |
151 | 2 |
return self.objects[index] |
152 |
|
|
153 | 2 |
def __len__(self): |
154 | 2 |
return len(self.objects) |
155 |
|
|
156 | 2 |
def __iter__(self): |
157 | 2 |
for obj in self.objects: |
158 | 2 |
yield obj |
159 |
|
|
160 | 2 |
def __iadd__(self, other): |
161 | 2 |
self.extend(other) |
162 | 2 |
return self |
163 |
|
|
164 | 2 |
def __add__(self, other): |
165 | 2 |
if isinstance(other, ListLike): |
166 | 2 |
other = other.objects |
167 | 2 |
if not isinstance(other, list): |
168 | 2 |
stype = type(self).__name__ |
169 | 2 |
otype = type(other).__name__ |
170 | 2 |
raise ValueError("Cannot add items of type %s and %s, can only " |
171 |
"combine %s.objects with list or ListLike object." |
|
172 |
% (stype, otype, stype)) |
|
173 | 2 |
return self.clone(*(self.objects+other)) |
174 |
|
|
175 | 2 |
def __radd__(self, other): |
176 | 2 |
if isinstance(other, ListLike): |
177 |
other = other.objects |
|
178 | 2 |
if not isinstance(other, list): |
179 |
stype = type(self).__name__ |
|
180 |
otype = type(other).__name__ |
|
181 |
raise ValueError("Cannot add items of type %s and %s, can only " |
|
182 |
"combine %s.objects with list or ListLike object." |
|
183 |
% (otype, stype, stype)) |
|
184 | 2 |
return self.clone(*(other+self.objects)) |
185 |
|
|
186 | 2 |
def __contains__(self, obj): |
187 | 2 |
return obj in self.objects |
188 |
|
|
189 | 2 |
def __setitem__(self, index, panes): |
190 | 2 |
from ..pane import panel |
191 | 2 |
new_objects = list(self) |
192 | 2 |
if not isinstance(index, slice): |
193 | 2 |
start, end = index, index+1 |
194 | 2 |
if start > len(self.objects): |
195 |
raise IndexError('Index %d out of bounds on %s ' |
|
196 |
'containing %d objects.' % |
|
197 |
(end, type(self).__name__, len(self.objects))) |
|
198 | 2 |
panes = [panes] |
199 |
else: |
|
200 | 2 |
start = index.start or 0 |
201 | 2 |
end = len(self) if index.stop is None else index.stop |
202 | 2 |
if index.start is None and index.stop is None: |
203 | 2 |
if not isinstance(panes, list): |
204 | 2 |
raise IndexError('Expected a list of objects to ' |
205 |
'replace the objects in the %s, ' |
|
206 |
'got a %s type.' % |
|
207 |
(type(self).__name__, type(panes).__name__)) |
|
208 | 2 |
expected = len(panes) |
209 | 2 |
new_objects = [None]*expected |
210 | 2 |
end = expected |
211 | 2 |
elif end > len(self.objects): |
212 | 2 |
raise IndexError('Index %d out of bounds on %s ' |
213 |
'containing %d objects.' % |
|
214 |
(end, type(self).__name__, len(self.objects))) |
|
215 |
else: |
|
216 | 2 |
expected = end-start |
217 | 2 |
if not isinstance(panes, list) or len(panes) != expected: |
218 | 2 |
raise IndexError('Expected a list of %d objects to set ' |
219 |
'on the %s to match the supplied slice.' % |
|
220 |
(expected, type(self).__name__)) |
|
221 | 2 |
for i, pane in zip(range(start, end), panes): |
222 | 2 |
new_objects[i] = panel(pane) |
223 |
|
|
224 | 2 |
self.objects = new_objects |
225 |
|
|
226 | 2 |
def clone(self, *objects, **params): |
227 |
"""
|
|
228 |
Makes a copy of the layout sharing the same parameters.
|
|
229 |
|
|
230 |
Arguments
|
|
231 |
---------
|
|
232 |
objects: Objects to add to the cloned layout.
|
|
233 |
params: Keyword arguments override the parameters on the clone.
|
|
234 |
|
|
235 |
Returns
|
|
236 |
-------
|
|
237 |
Cloned layout object
|
|
238 |
"""
|
|
239 | 2 |
if not objects: |
240 | 2 |
if 'objects' in params: |
241 | 2 |
objects = params.pop('objects') |
242 |
else: |
|
243 | 2 |
objects = self.objects |
244 | 2 |
elif 'objects' in params: |
245 | 2 |
raise ValueError("A %s's objects should be supplied either " |
246 |
"as arguments or as a keyword, not both."
|
|
247 |
% type(self).__name__) |
|
248 | 2 |
p = dict(self.param.get_param_values(), **params) |
249 | 2 |
del p['objects'] |
250 | 2 |
return type(self)(*objects, **p) |
251 |
|
|
252 | 2 |
def append(self, obj): |
253 |
"""
|
|
254 |
Appends an object to the layout.
|
|
255 |
|
|
256 |
Arguments
|
|
257 |
---------
|
|
258 |
obj (object): Panel component to add to the layout.
|
|
259 |
"""
|
|
260 | 2 |
from ..pane import panel |
261 | 2 |
new_objects = list(self) |
262 | 2 |
new_objects.append(panel(obj)) |
263 | 2 |
self.objects = new_objects |
264 |
|
|
265 | 2 |
def clear(self): |
266 |
"""
|
|
267 |
Clears the objects on this layout.
|
|
268 |
"""
|
|
269 | 2 |
self.objects = [] |
270 |
|
|
271 | 2 |
def extend(self, objects): |
272 |
"""
|
|
273 |
Extends the objects on this layout with a list.
|
|
274 |
|
|
275 |
Arguments
|
|
276 |
---------
|
|
277 |
objects (list): List of panel components to add to the layout.
|
|
278 |
"""
|
|
279 | 2 |
from ..pane import panel |
280 | 2 |
new_objects = list(self) |
281 | 2 |
new_objects.extend(list(map(panel, objects))) |
282 | 2 |
self.objects = new_objects |
283 |
|
|
284 | 2 |
def insert(self, index, obj): |
285 |
"""
|
|
286 |
Inserts an object in the layout at the specified index.
|
|
287 |
|
|
288 |
Arguments
|
|
289 |
---------
|
|
290 |
index (int): Index at which to insert the object.
|
|
291 |
object (object): Panel components to insert in the layout.
|
|
292 |
"""
|
|
293 | 2 |
from ..pane import panel |
294 | 2 |
new_objects = list(self) |
295 | 2 |
new_objects.insert(index, panel(obj)) |
296 | 2 |
self.objects = new_objects |
297 |
|
|
298 | 2 |
def pop(self, index): |
299 |
"""
|
|
300 |
Pops an item from the layout by index.
|
|
301 |
|
|
302 |
Arguments
|
|
303 |
---------
|
|
304 |
index (int): The index of the item to pop from the layout.
|
|
305 |
"""
|
|
306 | 2 |
new_objects = list(self) |
307 | 2 |
if index in new_objects: |
308 | 2 |
index = new_objects.index(index) |
309 | 2 |
obj = new_objects.pop(index) |
310 | 2 |
self.objects = new_objects |
311 | 2 |
return obj |
312 |
|
|
313 | 2 |
def remove(self, obj): |
314 |
"""
|
|
315 |
Removes an object from the layout.
|
|
316 |
|
|
317 |
Arguments
|
|
318 |
---------
|
|
319 |
obj (object): The object to remove from the layout.
|
|
320 |
"""
|
|
321 | 2 |
new_objects = list(self) |
322 | 2 |
new_objects.remove(obj) |
323 | 2 |
self.objects = new_objects |
324 |
|
|
325 | 2 |
def reverse(self): |
326 |
"""
|
|
327 |
Reverses the objects in the layout.
|
|
328 |
"""
|
|
329 | 2 |
new_objects = list(self) |
330 | 2 |
new_objects.reverse() |
331 | 2 |
self.objects = new_objects |
332 |
|
|
333 |
|
|
334 |
|
|
335 | 2 |
class ListPanel(ListLike, Panel): |
336 |
"""
|
|
337 |
An abstract baseclass for Panel objects with list-like children.
|
|
338 |
"""
|
|
339 |
|
|
340 | 2 |
margin = param.Parameter(default=0, doc=""" |
341 |
Allows to create additional space around the component. May
|
|
342 |
be specified as a two-tuple of the form (vertical, horizontal)
|
|
343 |
or a four-tuple (top, right, bottom, left).""") |
|
344 |
|
|
345 | 2 |
scroll = param.Boolean(default=False, doc=""" |
346 |
Whether to add scrollbars if the content overflows the size
|
|
347 |
of the container.""") |
|
348 |
|
|
349 | 2 |
_source_transforms = {'scroll': None} |
350 |
|
|
351 | 2 |
__abstract = True |
352 |
|
|
353 | 2 |
def __init__(self, *objects, **params): |
354 | 2 |
from ..pane import panel |
355 | 2 |
if objects: |
356 | 2 |
if 'objects' in params: |
357 |
raise ValueError("A %s's objects should be supplied either " |
|
358 |
"as positional arguments or as a keyword, "
|
|
359 |
"not both." % type(self).__name__) |
|
360 | 2 |
params['objects'] = [panel(pane) for pane in objects] |
361 | 2 |
elif 'objects' in params: |
362 | 2 |
params['objects'] = [panel(pane) for pane in params['objects']] |
363 | 2 |
super(Panel, self).__init__(**params) |
364 |
|
|
365 | 2 |
def _process_param_change(self, params): |
366 | 2 |
scroll = params.pop('scroll', None) |
367 | 2 |
css_classes = self.css_classes or [] |
368 | 2 |
if scroll: |
369 |
params['css_classes'] = css_classes + ['scrollable'] |
|
370 | 2 |
elif scroll == False: |
371 | 2 |
params['css_classes'] = css_classes |
372 | 2 |
return super(ListPanel, self)._process_param_change(params) |
373 |
|
|
374 | 2 |
def _cleanup(self, root): |
375 | 2 |
if root.ref['id'] in state._fake_roots: |
376 | 2 |
state._fake_roots.remove(root.ref['id']) |
377 | 2 |
super(ListPanel, self)._cleanup(root) |
378 | 2 |
for p in self.objects: |
379 | 2 |
p._cleanup(root) |
380 |
|
|
381 |
|
|
382 | 2 |
class NamedListPanel(ListPanel): |
383 |
|
|
384 | 2 |
active = param.Integer(default=0, bounds=(0, None), doc=""" |
385 |
Index of the currently displayed objects.""") |
|
386 |
|
|
387 | 2 |
objects = param.List(default=[], doc=""" |
388 |
The list of child objects that make up the tabs.""") |
|
389 |
|
|
390 | 2 |
def __init__(self, *items, **params): |
391 | 2 |
if 'objects' in params: |
392 |
if items: |
|
393 |
raise ValueError('%s objects should be supplied either ' |
|
394 |
'as positional arguments or as a keyword, '
|
|
395 |
'not both.' % type(self).__name__) |
|
396 |
items = params['objects'] |
|
397 | 2 |
objects, self._names = self._to_objects_and_names(items) |
398 | 2 |
super(NamedListPanel, self).__init__(*objects, **params) |
399 | 2 |
self._panels = defaultdict(dict) |
400 | 2 |
self.param.watch(self._update_names, 'objects') |
401 |
# ALERT: Ensure that name update happens first, should be
|
|
402 |
# replaced by watch precedence support in param
|
|
403 | 2 |
self._param_watchers['objects']['value'].reverse() |
404 |
|
|
405 | 2 |
def _to_object_and_name(self, item): |
406 | 2 |
from ..pane import panel |
407 | 2 |
if isinstance(item, tuple): |
408 | 2 |
name, item = item |
409 |
else: |
|
410 | 2 |
name = getattr(item, 'name', None) |
411 | 2 |
pane = panel(item, name=name) |
412 | 2 |
name = param_name(pane.name) if name is None else name |
413 | 2 |
return pane, name |
414 |
|
|
415 | 2 |
def _to_objects_and_names(self, items): |
416 | 2 |
objects, names = [], [] |
417 | 2 |
for item in items: |
418 | 2 |
pane, name = self._to_object_and_name(item) |
419 | 2 |
objects.append(pane) |
420 | 2 |
names.append(name) |
421 | 2 |
return objects, names |
422 |
|
|
423 | 2 |
def _update_names(self, event): |
424 | 2 |
if len(event.new) == len(self._names): |
425 | 2 |
return
|
426 | 2 |
names = [] |
427 | 2 |
for obj in event.new: |
428 | 2 |
if obj in event.old: |
429 | 2 |
index = event.old.index(obj) |
430 | 2 |
name = self._names[index] |
431 |
else: |
|
432 | 2 |
name = obj.name |
433 | 2 |
names.append(name) |
434 | 2 |
self._names = names |
435 |
|
|
436 | 2 |
def _update_active(self, *events): |
437 |
pass
|
|
438 |
|
|
439 |
#----------------------------------------------------------------
|
|
440 |
# Public API
|
|
441 |
#----------------------------------------------------------------
|
|
442 |
|
|
443 | 2 |
def __add__(self, other): |
444 | 2 |
if isinstance(other, NamedListPanel): |
445 | 2 |
other = list(zip(other._names, other.objects)) |
446 | 2 |
elif isinstance(other, ListLike): |
447 |
other = other.objects |
|
448 | 2 |
if not isinstance(other, list): |
449 |
stype = type(self).__name__ |
|
450 |
otype = type(other).__name__ |
|
451 |
raise ValueError("Cannot add items of type %s and %s, can only " |
|
452 |
"combine %s.objects with list or ListLike object." |
|
453 |
% (stype, otype, stype)) |
|
454 | 2 |
objects = list(zip(self._names, self.objects)) |
455 | 2 |
return self.clone(*(objects+other)) |
456 |
|
|
457 | 2 |
def __radd__(self, other): |
458 | 2 |
if isinstance(other, NamedListPanel): |
459 |
other = list(zip(other._names, other.objects)) |
|
460 | 2 |
elif isinstance(other, ListLike): |
461 |
other = other.objects |
|
462 | 2 |
if not isinstance(other, list): |
463 |
stype = type(self).__name__ |
|
464 |
otype = type(other).__name__ |
|
465 |
raise ValueError("Cannot add items of type %s and %s, can only " |
|
466 |
"combine %s.objects with list or ListLike object." |
|
467 |
% (otype, stype, stype)) |
|
468 | 2 |
objects = list(zip(self._names, self.objects)) |
469 | 2 |
return self.clone(*(other+objects)) |
470 |
|
|
471 | 2 |
def __setitem__(self, index, panes): |
472 | 2 |
new_objects = list(self) |
473 | 2 |
if not isinstance(index, slice): |
474 | 2 |
if index > len(self.objects): |
475 |
raise IndexError('Index %d out of bounds on %s ' |
|
476 |
'containing %d objects.' % |
|
477 |
(index, type(self).__name__, len(self.objects))) |
|
478 | 2 |
start, end = index, index+1 |
479 | 2 |
panes = [panes] |
480 |
else: |
|
481 | 2 |
start = index.start or 0 |
482 | 2 |
end = len(self.objects) if index.stop is None else index.stop |
483 | 2 |
if index.start is None and index.stop is None: |
484 | 2 |
if not isinstance(panes, list): |
485 | 2 |
raise IndexError('Expected a list of objects to ' |
486 |
'replace the objects in the %s, ' |
|
487 |
'got a %s type.' % |
|
488 |
(type(self).__name__, type(panes).__name__)) |
|
489 | 2 |
expected = len(panes) |
490 | 2 |
new_objects = [None]*expected |
491 | 2 |
self._names = [None]*len(panes) |
492 | 2 |
end = expected |
493 |
else: |
|
494 | 2 |
expected = end-start |
495 | 2 |
if end > len(self.objects): |
496 | 2 |
raise IndexError('Index %d out of bounds on %s ' |
497 |
'containing %d objects.' % |
|
498 |
(end, type(self).__name__, len(self.objects))) |
|
499 | 2 |
if not isinstance(panes, list) or len(panes) != expected: |
500 | 2 |
raise IndexError('Expected a list of %d objects to set ' |
501 |
'on the %s to match the supplied slice.' % |
|
502 |
(expected, type(self).__name__)) |
|
503 | 2 |
for i, pane in zip(range(start, end), panes): |
504 | 2 |
new_objects[i], self._names[i] = self._to_object_and_name(pane) |
505 | 2 |
self.objects = new_objects |
506 |
|
|
507 | 2 |
def clone(self, *objects, **params): |
508 |
"""
|
|
509 |
Makes a copy of the Tabs sharing the same parameters.
|
|
510 |
|
|
511 |
Arguments
|
|
512 |
---------
|
|
513 |
objects: Objects to add to the cloned Tabs object.
|
|
514 |
params: Keyword arguments override the parameters on the clone.
|
|
515 |
|
|
516 |
Returns
|
|
517 |
-------
|
|
518 |
Cloned Tabs object
|
|
519 |
"""
|
|
520 | 2 |
if not objects: |
521 | 2 |
if 'objects' in params: |
522 |
objects = params.pop('objects') |
|
523 |
else: |
|
524 | 2 |
objects = zip(self._names, self.objects) |
525 | 2 |
elif 'objects' in params: |
526 |
raise ValueError('Tabs objects should be supplied either ' |
|
527 |
'as positional arguments or as a keyword, '
|
|
528 |
'not both.') |
|
529 | 2 |
p = dict(self.param.get_param_values(), **params) |
530 | 2 |
del p['objects'] |
531 | 2 |
return type(self)(*objects, **params) |
532 |
|
|
533 | 2 |
def append(self, pane): |
534 |
"""
|
|
535 |
Appends an object to the tabs.
|
|
536 |
|
|
537 |
Arguments
|
|
538 |
---------
|
|
539 |
obj (object): Panel component to add as a tab.
|
|
540 |
"""
|
|
541 | 2 |
new_object, new_name = self._to_object_and_name(pane) |
542 | 2 |
new_objects = list(self) |
543 | 2 |
new_objects.append(new_object) |
544 | 2 |
self._names.append(new_name) |
545 | 2 |
self.objects = new_objects |
546 |
|
|
547 | 2 |
def clear(self): |
548 |
"""
|
|
549 |
Clears the tabs.
|
|
550 |
"""
|
|
551 | 2 |
self._names = [] |
552 | 2 |
self.objects = [] |
553 |
|
|
554 | 2 |
def extend(self, panes): |
555 |
"""
|
|
556 |
Extends the the tabs with a list.
|
|
557 |
|
|
558 |
Arguments
|
|
559 |
---------
|
|
560 |
objects (list): List of panel components to add as tabs.
|
|
561 |
"""
|
|
562 | 2 |
new_objects, new_names = self._to_objects_and_names(panes) |
563 | 2 |
objects = list(self) |
564 | 2 |
objects.extend(new_objects) |
565 | 2 |
self._names.extend(new_names) |
566 | 2 |
self.objects = objects |
567 |
|
|
568 | 2 |
def insert(self, index, pane): |
569 |
"""
|
|
570 |
Inserts an object in the tabs at the specified index.
|
|
571 |
|
|
572 |
Arguments
|
|
573 |
---------
|
|
574 |
index (int): Index at which to insert the object.
|
|
575 |
object (object): Panel components to insert as tabs.
|
|
576 |
"""
|
|
577 | 2 |
new_object, new_name = self._to_object_and_name(pane) |
578 | 2 |
new_objects = list(self.objects) |
579 | 2 |
new_objects.insert(index, new_object) |
580 | 2 |
self._names.insert(index, new_name) |
581 | 2 |
self.objects = new_objects |
582 |
|
|
583 | 2 |
def pop(self, index): |
584 |
"""
|
|
585 |
Pops an item from the tabs by index.
|
|
586 |
|
|
587 |
Arguments
|
|
588 |
---------
|
|
589 |
index (int): The index of the item to pop from the tabs.
|
|
590 |
"""
|
|
591 | 2 |
new_objects = list(self) |
592 | 2 |
if index in new_objects: |
593 |
index = new_objects.index(index) |
|
594 | 2 |
new_objects.pop(index) |
595 | 2 |
self._names.pop(index) |
596 | 2 |
self.objects = new_objects |
597 |
|
|
598 | 2 |
def remove(self, pane): |
599 |
"""
|
|
600 |
Removes an object from the tabs.
|
|
601 |
|
|
602 |
Arguments
|
|
603 |
---------
|
|
604 |
obj (object): The object to remove from the tabs.
|
|
605 |
"""
|
|
606 | 2 |
new_objects = list(self) |
607 | 2 |
if pane in new_objects: |
608 | 2 |
index = new_objects.index(pane) |
609 | 2 |
new_objects.remove(pane) |
610 | 2 |
self._names.pop(index) |
611 | 2 |
self.objects = new_objects |
612 |
|
|
613 | 2 |
def reverse(self): |
614 |
"""
|
|
615 |
Reverses the tabs.
|
|
616 |
"""
|
|
617 | 2 |
new_objects = list(self) |
618 | 2 |
new_objects.reverse() |
619 | 2 |
self._names.reverse() |
620 | 2 |
self.objects = new_objects |
621 |
|
|
622 |
|
|
623 |
|
|
624 | 2 |
class Row(ListPanel): |
625 |
"""
|
|
626 |
Horizontal layout of Viewables.
|
|
627 |
"""
|
|
628 |
|
|
629 | 2 |
col_sizing = param.Parameter() |
630 |
|
|
631 | 2 |
_bokeh_model = BkRow |
632 |
|
|
633 | 2 |
_rename = dict(ListPanel._rename, col_sizing='cols') |
634 |
|
|
635 |
|
|
636 | 2 |
class Column(ListPanel): |
637 |
"""
|
|
638 |
Vertical layout of Viewables.
|
|
639 |
"""
|
|
640 |
|
|
641 | 2 |
row_sizing = param.Parameter() |
642 |
|
|
643 | 2 |
_bokeh_model = BkColumn |
644 |
|
|
645 | 2 |
_rename = dict(ListPanel._rename, row_sizing='rows') |
646 |
|
|
647 |
|
|
648 | 2 |
class WidgetBox(ListPanel): |
649 |
"""
|
|
650 |
Vertical layout of widgets.
|
|
651 |
"""
|
|
652 |
|
|
653 | 2 |
css_classes = param.List(default=['panel-widget-box'], doc=""" |
654 |
CSS classes to apply to the layout.""") |
|
655 |
|
|
656 | 2 |
disabled = param.Boolean(default=False, doc=""" |
657 |
Whether the widget is disabled.""") |
|
658 |
|
|
659 | 2 |
horizontal = param.Boolean(default=False, doc=""" |
660 |
Whether to lay out the widgets in a Row layout as opposed
|
|
661 |
to a Column layout.""") |
|
662 |
|
|
663 | 2 |
margin = param.Parameter(default=5, doc=""" |
664 |
Allows to create additional space around the component. May
|
|
665 |
be specified as a two-tuple of the form (vertical, horizontal)
|
|
666 |
or a four-tuple (top, right, bottom, left).""") |
|
667 |
|
|
668 | 2 |
_source_transforms = {'disabled': None, 'horizontal': None} |
669 |
|
|
670 | 2 |
_rename = {'objects': 'children', 'horizontal': None} |
671 |
|
|
672 | 2 |
@property
|
673 |
def _bokeh_model(self): |
|
674 | 2 |
return BkRow if self.horizontal else BkColumn |
675 |
|
|
676 | 2 |
@param.depends('disabled', 'objects', watch=True) |
677 |
def _disable_widgets(self): |
|
678 | 2 |
for obj in self: |
679 | 2 |
if hasattr(obj, 'disabled'): |
680 | 2 |
obj.disabled = self.disabled |
681 |
|
|
682 | 2 |
def __init__(self, *objects, **params): |
683 | 2 |
super(WidgetBox, self).__init__(*objects, **params) |
684 | 2 |
if self.disabled: |
685 |
self._disable_widgets() |
Read our documentation on viewing source code .