Swapping out a widget section in a Column layout

I have a Selector and depending on its value I’d like one set of widgets displayed in a column layout or another set of widgets.

My code is:

class A(param.Parameterized):
    select = param.Selector(objects=['opt1', 'opt2'])
    
    field1 = param.Selector(objects=['a1', 'a2', 'a3'], label='Field1')
    number1 = param.Number(3)
    
    field2 = param.Selector(objects=['b1', 'b2', 'b3'], label='Field2')
    number2 = param.Number(5)
    
    def __init__(self, **param):
        super(A, self).__init__(**param)
    
        panel1 =  pn.panel(self.param, parameters=['field1', 'number1'], widgets={'field1': pn.widgets.RadioButtonGroup})
        self.col1 = pn.Column(*panel1[1:])
    
        panel2 =  pn.panel(self.param, parameters=['field2', 'number2'], widgets={'field2': pn.widgets.RadioButtonGroup})
        self.col2 = pn.Column(*panel2[1:])

        self.column = pn.Column(panel, self.col1)
    
    @param.depends('select', watch=True)
    def _update_select(self):
        if self.select == 'opt1':
            self.column[1] = self.col1
        elif self.select == 'opt2':
            self.column[1] = self.col2
    
    def panel(self):
        return self.column
    
a = A()
a.panel()

This works but the part where I create both columns to swap out feels a bit clumsy to me, where I make a panel with two selected params, override one default widget and then chop off the title to get the output I would like to have.

Is there a function in panel where you can create a widget from a parameter in a one-to-one fashion? For example:

widget = pn.make_widget(self.param['field1'], pn.widgets.RadioButtonGroup)

Or other ways to do this better? Possibly related to: https://github.com/holoviz/panel/pull/1322

Hi @Jhsmit.

I tried to take a look at the code. But there are some issues

  • Import statements missing
  • panel is not defined error
Traceback (most recent call last):
  File "scripts\issue_dynamic_param.py", line 34, in <module>
    a = A()
  File "scripts\issue_dynamic_param.py", line 22, in __init__
    self.column = pn.Column(panel, self.col1)
NameError: name 'panel' is not defined
  • The select parameter is not used any where.

and the code it self seems over complicated to me making it hard to understand what you are trying to achieve.

It would help me understand (as opposed to guess) what you are trying to achieve if you could provide a working example and maybe some screenshots or a video .gif.

Maybe also if you could add a bit of text, a divider or something that layouts and separates things in your app would make it more clear to me what you are trying to achieve.

Thanks.

Sorry that was some sloppy copy/pasting. Updated code:


import param
import panel as pn
# pn.extension()

class A(param.Parameterized):
    select = param.Selector(objects=['opt1', 'opt2'])
    
    field1 = param.Selector(objects=['a1', 'a2', 'a3'], label='Field1')
    number1 = param.Number(3)
    
    field2 = param.Selector(objects=['b1', 'b2', 'b3'], label='Field2')
    number2 = param.Number(5, bounds=(2, 10))
    
    def __init__(self, **param):
        super(A, self).__init__(**param)
    
        panel1 =  pn.panel(self.param, parameters=['field1', 'number1'], widgets={'field1': pn.widgets.RadioButtonGroup})
        self.col1 = pn.Column(*panel1[1:])
    
        panel2 =  pn.panel(self.param, parameters=['field2', 'number2'])
        self.col2 = pn.Column(*panel2[1:])

        panel = pn.panel(self.param, parameters=['select'])
        self.column = pn.Column(panel, self.col1)
    
    @param.depends('select', watch=True)
    def _update_select(self):
        if self.select == 'opt1':
            self.column[1] = self.col1
        elif self.select == 'opt2':
            self.column[1] = self.col2
    
    def panel(self):
        return self.column
    
a = A()
a.panel()

What I’d like is a column, where depending on the value of the select parameter, either one set of widgets is shown, or the other set of widget is shown. These widgets could be anything and not necessarily have the same type of parameters.

What I get out is expected behaviour so in principle it all works:

image
image

panel1 =  pn.panel(self.param, parameters=['field1', 'number1'], widgets={'field1': pn.widgets.RadioButtonGroup})
self.col1 = pn.Column(*panel1[1:])

These are the parts I have issues with, making a column with 2 custom widgets linked to parameters feels cumbersome. I was wondering if there is another way of doing so, but maybe I’m missing the obvious solution.

What I was looking for is a way if you have one parameter and one widget, how to easily link them together the way pn.panel or pn.Param does for several parameters?

I guess this makes the title of my questions confusing and misleading, but basically what I’m asking is can I link one parameter to a widget the way pn.Param does,

OR

Should I be implementing what I’m trying to achieve differently so that I dont have a need for this in the first place?

Hope this helps :slight_smile: Indeed I also find the code overly complicated but I couldnt figure out a better way.

1 Like

I think what you’re doing is sensible enough, I just don’t know if there is anything much simple, e.g I could imagine a method like this on pn.Param

    @classmethod
    def get_widget(cls, parameter, widget_type=None, **kwargs):
        if widget_type is not None:
            kwargs['widgets'] = {parameter.name: widget_type}
        return cls(parameter, **kwargs)

but then you’d still have something like this in your code:

        w1 = pn.Param.get_widget(self.param.field1, pn.widgets.RadioButtonGroup)
        w2 = pn.Param.get_widget(self.param.number1)
        self.col1 = pn.Column(w1, w2)

I’m very open to suggestions but don’t currently have many of my own.

1 Like

Hi @Jhsmit!

You don’t have to chop off the tittle manually, just use show_name=False:

self.col1 = pn.panel(self.param, show_name=False, parameters=['field1', 'number1'], widgets={'field1': pn.widgets.RadioButtonGroup})

You could also expand subobjects (see the User guide):

class W1(param.Parameterized):
    field1 = param.Selector(objects=['a1', 'a2', 'a3'], label='Field1')
    number1 = param.Number(3)

class W2(param.Parameterized):
    field2 = param.Selector(objects=['b1', 'b2', 'b3'], label='Field2')
    number2 = param.Number(5, bounds=(2, 10))

widgets_sets = [W1(name="opt1"), W2(name="opt2")]

class WidgetsSelector(param.Parameterized):
    
    select = param.ObjectSelector(default=widgets_sets[0], objects=widgets_sets)

widget_selector = WidgetsSelector()
app = pn.Column(
    "**Select an option to display a different set of widgets**",
    pn.Param(widget_selector.param, show_name=False, expand=True)
)
app.servable()

image

I guess that in that case you have to accept that you’re using the default mapping between Parameter and widget types.

Cheers!

Excellent, thanks!
I didn’t think of using expanded subobjects. That does provide a nice hierarchy to keep things organized. Although I might want to customize the mapping in the future but I’ll cross that bridge when I get there.

I’m using param.watch to trigger function calls when the widgets are changed. I like the cleanliness of param.depends(select.field1) but that requires both subobject fields to have the same name, and the default value of select to be set outside of __init__.

class W1(param.Parameterized):
    field1 = param.Selector(objects=['a1', 'a2', 'a3'], label='Field1')
    number1 = param.Number(3)
    
class W2(param.Parameterized):
    field2 = param.Selector(objects=['b1', 'b2', 'b3'], label='Field2')
    number2 = param.Number(5, bounds=(2, 10))

class WidgetsSelector(param.Parameterized):    
    select = param.ObjectSelector()
    
    def __init__(self, **params):
        super(WidgetsSelector, self).__init__(**params)
        self.widget_sets = [W1(name="opt1"), W2(name="opt2")]
        self.param['select'].objects = self.widget_sets
        self.select = self.widget_sets[0]
        
        self.widget_sets[0].param.watch(self._update_field1, ['field1'])
  
    def _update_field1(self, *events):
        print(events)
       

@philippjfr I wouldnt mind a function like that at all, works for me!