From 7501a96b06359a4bdf5a6a551edc9537cfe12ca8 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 1 Feb 2021 19:35:22 +0100 Subject: [PATCH] Switch Param pane widget type when bounds (un)defined (#1953) * Switch Param pane widget type when bounds (un)defined * Add test * Fix test --- panel/param.py | 45 +++++++++++++++++++++++++++++---------- panel/tests/test_param.py | 38 ++++++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/panel/param.py b/panel/param.py index 7f1bb3b079..f33ee2d698 100644 --- a/panel/param.py +++ b/panel/param.py @@ -427,6 +427,7 @@ def action(change): def link(change, watchers=[watcher]): updates = {} + widget = self._widgets[p_name] if change.what == 'constant': updates['disabled'] = change.new elif change.what == 'precedence': @@ -434,22 +435,23 @@ def link(change, watchers=[watcher]): widget in self._widget_box.objects): self._widget_box.pop(widget) elif change.new >= self.display_threshold: - precedence = lambda k: self.object.param['name' if k == '_title' else k].precedence - params = self._ordered_params - if self.show_name: - params.insert(0, '_title') - widgets = [] - for k in params: - if precedence(k) is None or precedence(k) >= self.display_threshold: - widgets.append(self._widgets[k]) - self._widget_box.objects = widgets + self._rerender() return elif change.what == 'objects': updates['options'] = p_obj.get_range() elif change.what == 'bounds': start, end = p_obj.get_soft_bounds() - updates['start'] = start - updates['end'] = end + supports_bounds = hasattr(widget, 'start') + if start is None or end is None: + rerender = supports_bounds + else: + rerender = not supports_bounds + if supports_bounds: + updates['start'] = start + updates['end'] = end + if rerender: + self._rerender_widget(p_name) + return elif change.what == 'step': updates['step'] = p_obj.step elif change.what == 'label': @@ -530,6 +532,27 @@ def _ordered_params(self): # Model API #---------------------------------------------------------------- + def _rerender(self): + precedence = lambda k: self.object.param['name' if k == '_title' else k].precedence + params = self._ordered_params + if self.show_name: + params.insert(0, '_title') + widgets = [] + for k in params: + if precedence(k) is None or precedence(k) >= self.display_threshold: + widgets.append(self._widgets[k]) + self._widget_box.objects = widgets + + def _rerender_widget(self, p_name): + watchers = [] + for w in self._callbacks: + if w.inst is self._widgets[p_name]: + w.inst.param.unwatch(w) + else: + watchers.append(w) + self._widgets[p_name] = self.widget(p_name) + self._rerender() + def _get_widgets(self): """Return name,widget boxes for all parameters (i.e., a property sheet)""" # Format name specially diff --git a/panel/tests/test_param.py b/panel/tests/test_param.py index fe71c90635..14edefbc8b 100644 --- a/panel/tests/test_param.py +++ b/panel/tests/test_param.py @@ -5,13 +5,13 @@ import param from bokeh.models import ( - Div, Slider, Select, RangeSlider, MultiSelect, Row as BkRow, - CheckboxGroup, Toggle, Button, TextInput as BkTextInput, - Tabs as BkTabs, Column as BkColumn, TextInput) + Div, Slider, Select, RangeSlider as BkRangeSlider, MultiSelect, + Row as BkRow, CheckboxGroup, Toggle, Button, TextInput as + BkTextInput,Tabs as BkTabs, Column as BkColumn, TextInput) from panel.pane import Pane, PaneBase, Matplotlib, Bokeh, HTML from panel.layout import Tabs, Row from panel.param import Param, ParamMethod, ParamFunction, JSONInit -from panel.widgets import LiteralInput +from panel.widgets import LiteralInput, RangeSlider from panel.tests.util import mpl_available, mpl_figure @@ -221,7 +221,7 @@ class Test(param.Parameterized): model = test_pane.get_root(document, comm=comm) widget = model.children[1] - assert isinstance(widget, RangeSlider) + assert isinstance(widget, BkRangeSlider) assert widget.start == 0 assert widget.end == 1.1 assert widget.value == (0.1, 0.5) @@ -660,9 +660,9 @@ class Test(param.Parameterized): assert number.value != 3 assert test.a == 3 - test.a = 4 - assert number.value != 4 - assert test.a == 4 + pane._widgets['a']._process_events({'value': 4}) + assert test.a == 3 + assert number.value == 4 def test_set_show_name(document, comm): @@ -1182,3 +1182,25 @@ def _update_text_pane(self, *_): view.text = TextModel(text="New TextModel") # Then assert view.text_pane.parameters==["text"] + + +def test_rerender_bounded_widget_when_bounds_set_and_unset(): + class Test(param.Parameterized): + num = param.Range() + + test = Test() + p = Param(test) + + assert isinstance(p._widgets['num'], LiteralInput) + assert p._widgets['num'] in p._widget_box + + test.param.num.bounds = (0, 5) + + assert isinstance(p._widgets['num'], RangeSlider) + assert p._widgets['num'] in p._widget_box + + test.param.num.bounds = (None, 5) + + assert isinstance(p._widgets['num'], LiteralInput) + assert p._widgets['num'] in p._widget_box +