Linking a dynamic number of widgets

Hello everyone,

I am rather new to Panel, having just started a project last week. Although my problem seems basic, I haven’t found similar topics, so here I go:

I am trying to have:

  • A controller (pn.widgets.slider.IntSlider) that sets the number of rows of widgets
  • For each row, the value of of the first IntSlider should be bound to the value of the next row’s second IntSlider

As images speak better than words, here is an image of the intended layout:

And the code is below:

import panel as pn
import param

pn.extension()

class Liability(param.Parameterized):
    attachment_points = param.List([])
    detachment_points = param.List([])
    n_tranches = param.Integer()
    
    def view(self):
        
        tranches = pn.widgets.slider.IntSlider(name="Number of tranches", start=1, end=10, step=1, value=3)
        
        @pn.depends(tranches, watch=True)
        def liability_structure(tranches=tranches, self=self):
            
            if tranches is not None:
                self.n_tranches = tranches
            
            col = pn.Column()
            for i in range(tranches):
                if i == tranches - 1:  
                    attach_i =pn.widgets.slider.IntSlider(name='Attachment point', start=0, end=100, step=1, value=0, disabled=True)
                else:
                     attach_i = pn.widgets.slider.IntSlider(name='Attachment point', start=0, end=100, step=1, value=0)
                if i==0:
                    detach_i  = pn.widgets.slider.IntSlider(name='Detachment point', start=0, end=100, step=1, value=100, disabled=True)
                else:
                    detach_i  = pn.widgets.slider.IntSlider(name='Detachment point', start=0, end=100, step=1, value=0)
                    

                self.attachment_points.append(attach_i)
                self.detachment_points.append(detach_i)

                col.append(pn.Column('** Tranche {} **'.format(tranches - i),
                                     pn.Row(attach_i, detach_i)))

            return col
        
        return pn.Column(pn.Row(tranches, liability_structure))
    
    @param.depends('attachment_points', watch=True)
    def update_detach(self):
        for i in range(len(self.attachment_points) - 1):
            self.detachment_points[i+1].value = self.attachment_points[i].value
            
    @param.depends('detachment_points', watch=True)
    def update_attach(self):
        for i in range(1, len(self.attachment_points)):
            self.attachment_points[i-1].value = self.detachment_points[i].value
        
l = Liability()
l.view()

The issue I have is that the functions update_detach and update_attach are never called (I suspect because only the value of the elements of the lists they watch are changed and not the list themselves). They nevertheless work as intended when called in another cell (e.g., l.update_detach() ).

Is there any way to set up a param that would watch on any change of any of one list’s elements?

I hope that I was clear enough in my description of the problem.

Thank you in advance for any help !

CoconutZ

Maybe using on_init=True in depends might initialize it.

Other than that, I think looping through and pn.bind(…, watch=True) might work as well.

Lastly, maybe using jslink or link since they are both sliders.

2 Likes

Thank you for your answer!

Using jslink worked for me perfectly, adding those 3 lines of code in the loop:

if i >= 1:
    detach_i.jslink(self.attachment_points[-1], value ='value')
    self.attachment_points[-1].jslink(detach_i, value = 'value')

If anyone encounters the same problem:

  • Using on_init=True didn’t work
  • Using pn.bind(…, watch=True) in the loop probably works, but I couldn’t test it as I have to use an outdated version of panel (0.8.0; can’t upgrade because of organization policies).

Cheers

2 Likes

Wow @Coconutz . Panel 0.8.0 is very old. A lot of things has improved since then. You should really try to get your org to update Panel and dependencies to latest versions.

2 Likes