Linking parametrized widgets of independent class instances

How is it possible to link parametrized widgets of otherwise independent instances of classes? The documentation on Panel Links only seems to cover widgets, that aren’t parametrized.

As a bonus: apply a function or mapping in the process

As a minimum example I suggest linking two sliders. Potentially, it could work better using a helper parameter and function, so I took the liberty to place one inside the class.

import param
import panel as pn

pn.extension()


class BaseClass(param.Parameterized):
    floatSlider      = param.Number(0.5, bounds=(0, 1))
    notaWidgetString = param.String(default="str", doc="A string")
    helperInt    = param.Integer(50, bounds=(0, 100))

    def __init__(
        self,
        sliderValue=0.5,
        **params
    ):
        self.floatSlider = sliderValue
        super().__init__(**params)
        return

    @param.depends('helperInt', watch=True)
    def _updateFloatSlider(self) -> None:
        self.floatSlider = self.helperInt / 100.
        return


slider01 = BaseClass(sliderValue=0.2)
slider02 = BaseClass(sliderValue=0.7)

sliderParam = []
for sld, label in zip([slider01, slider02], ["Source Slider", "Target Slider"]):
    sliderParam += [pn.Param(
            sld.param,
            widgets={
                'floatSlider': {
                    'widget_type': pn.widgets.FloatSlider,
                    'name': label,
                },
            },
            name="",
            parameters=['floatSlider'],
        )]

sliderPanel = pn.Column(*sliderParam)
sliderPanel

thank you very much for your help

You could just do this:

@param.depends(slider01.param.floatSlider, watch=True)
def g(i):
    slider02.floatSlider = i
    
@param.depends(slider02.param.floatSlider, watch=True)
def g(i):
    slider01.floatSlider = i

Alternatively, you could also do something like this:


sliderParam = []
for sld, label in zip([slider01, slider02], ["Source Slider", "Target Slider"]):
    sliderParam += [pn.widgets.FloatSlider.from_param(
            sld.param['floatSlider'],
            name = label
        )]

sliderParam[0].link(sliderParam[1], value = 'value', bidirectional = True)

Hi riziles,

@riziles thank you very much, that works. The solution in your first posting isn’t quite what I’m after, but the second using the “.link” method fits the requirement.

Now my question is, why does this work using the pn.widgets.FloatSlider.from_param() method, but spits out an error ValueError: value parameter was not found in list of parameters of class Param when the line sliderParam[0].link(sliderParam[1], value = 'value', bidirectional = True) is included in the initial example, where the parametrized slider was defined via the pn.Param(object= , widgets= , parameters= , ...) method?

edit: maybe I should rephrase that question:
Obviously the initial example yields a instance of class panel.param.Param for sliderParam[0] while the accepted solution produces a instance of class panel.widgets.slider.FloatSlider. What I don’t understand is, how to navigate from the instance of the panel.param.Param to the FloatSlider in order to use the link methods. Also my expectation was that both methods of producing the parametrized slider (i.e. pn.Param() and pn.widgets...) would provide a comparable behaviour when using the link methods.

Unfortunately, neither do I! I suspect that somebody smarter than me probably knows the answer.

Wait! I figured it out! Each sliderParam actually contains 2 widgets: the first is just the label (which we set to “”); the second is the actual slider. All you need to do is this:

sliderParam[0][1].link(sliderParam[1][1], value = 'value', bidirectional = True)
1 Like

@riziles well done!

I was trying to traverse through all the objects that are listed by dir() and couldn’t find the appropriate object for the link methods (even though I could find the actual value under sliderParam[0].object.floatSlider and the base information about the widget under sliderParam[0].widgets).
Unlike for panel and holoviews, the output of repr(sliderParam[0]) isn’t intuitively pushing the user towards trying possible list items, so I didn’t try your solution.

Ha. I did the same thing. What tipped me off was viewing sliderParam[0].controls() . If you do that, it’s obvious that the Param class is a layout object. All the Panel layout objects can be converted into lists. If you print list(sliderParam[0]) you can see two widgets in the list.

I found a caveat for using the layout object:

When the visibility of that parameter is set to False, there will be no slider widget in the layout and consequently only the StaticText widget in sliderParam[0][0] is present, but no FloatSlider => len(sliderParam[0]) == 1

Any ideas how to link the slider parameters regardless?

Not sure I follow. If there is no slider then there is no widget to link. If you want to make one param class dependent on another (without Panel involved) you can just create a parameterized wrapper class. Param supports nested dependencies: Dependencies and Watchers — param v1.12.3

@riziles I guess, if one runs into this caveat, a design change is necessary, towards using nested dependencies.

As an example:
I have a class that builds a set of graphs with some control widgets like sliders to act on the variables plotted. Then I create several instances of that class for different variables. Where the variables are identical between the instances, I want the control widgets to be linked. So I thought it might be a good solution to use the link functionality.
However, I’ve also got a few control widgets that allow for changing some umbrella parameters, for instance to filter the data, which applies on all of those instances. Linking the instance control widgets is not working well, though, for at least two reasons. One, due to the filtering it can happen that one instance of graphs and sliders is invisible, because there is no data to show. That creates issues around being able to link or to re-establish a link. Two, race conditions are possible, for example when changing bounds and value on one slider, the linked slider receives the value update, but not the update of the bounds, so it throws an error for the new value being out of bounds.

Sorry, this is all above my paygrade. Just to give you some context: whenever anything gets complicated, I try to imagine I’m just building a Parameterized class that is going to return data with no GUI. So basically I do everything in Param and pretend that Panel doesn’t exist. Once I get that working, then I worry about how to interface with it using Panel components. Maybe that doesn’t work for this context, but I find it to be a pretty good paradigm.