Search code examples
pythonpanelpyviz

How to pass values of a single widget to multiple instances of one object in Panel/Bokeh


I build a dashboard to plot parts of a dataset, and it works when I declare every single component. As oall my plots are similar, with only changes being the title and the key to select the dataset to plot, I tried to use a class to describe my plots, but I am having trouble passing the widget value to all instances of the plot class.

I have attached an example below. In the separate model, I get one widget and two graphs, and when I change the widget from 'a' to 'b', both plots get updated as expected. However in the model using classes, when I change the value of the widget, the first plot updates, while the second does not. I am assuming this is because the widget only changes the key_val of the first instance, not the second.

import pandas as pd  
import panel as pn  
import matplotlib.pyplot as plt  
import param    
pn.extension()  

##separate model
data={'a':{'Set1':[[1,2],[10,20]],'Set2':[[3,4],[13,14]]},
      'b':{'Set1':[[5,5],[6,16]],'Set2':[[10,20],[1,2]]}}  
key_val = pn.widgets.Select(name='Choose dataset', options=['a','b'])  
@pn.depends(key_val.param.value,watch=True)  
def f1(key_val):  
    fig=plt.plot(data[key_val]['Set1'][0],data[key_val]['Set1'][1])  
    curr_fig=plt.gcf()  
    plt.close(curr_fig)  
    return curr_fig  
@pn.depends(key_val.param.value,watch=True)  
def f2(key_val):  
    fig=plt.plot(data[key_val]['Set2'][0],data[key_val]['Set2'][1])  
    curr_fig=plt.gcf()  
    plt.close(curr_fig)  
    return curr_fig  

pn.Column(pn.WidgetBox(key_val), pn.Row(f1,f2))  

##class model  
class simple_plot(param.Parameterized):  
    key_val = param.Selector(default='a', objects=['a','b'])  
    my_second_option=param.String(default='Set1')  

    @param.depends('key_val',watch=True)  
    def f_both(self):  
        fig=plt.plot(data[self.key_val][self.my_second_option][0],
        data[self.key_val][self.my_second_option][1])  
        curr_fig=plt.gcf()  
        plt.close(curr_fig)  
        return curr_fig  

test=simple_plot()  
test2=simple_plot(my_second_option='Set2')  
pn.Row(test.param, pn.Row(test.f_both),pn.Row(test2.f_both))

So My question is how to setup the class and widget so I can pass the widget value for both instances of my class?


Solution

  • You can create a class CommonParameters to save common parameters:

    import pandas as pd  
    import panel as pn  
    import matplotlib.pyplot as plt  
    import param    
    pn.extension()  
    
    data = {
        "a":{"Set1":([0, 1, 2], [1, 2, 3]),
             "Set2":([0, 1, 2], [1, 3, 2]),
            },
        "b":{"Set1":([0, 10, 20], [1, 20, 3]),
             "Set2":([0, 10, 20], [1, 3, 20]),
            },    
    }
    
    class CommonParameters(param.Parameterized):  
        key_val = param.Selector(default='a', objects=['a','b'])  
    
    class simple_plot(param.Parameterized):      
        parameters = param.ObjectSelector(precedence=-1)
        my_second_option = param.String(default="Set1")
    
        @param.depends('parameters.key_val',watch=True)  
        def f_both(self):
            key_val = self.parameters.key_val
            fig=plt.plot(data[key_val][self.my_second_option][0],
            data[key_val][self.my_second_option][1])  
            curr_fig=plt.gcf()  
            plt.close(curr_fig)  
            return curr_fig  
    
    common_pars = CommonParameters()
    test=simple_plot(name="test1", parameters=common_pars)  
    test2=simple_plot(name="test2", parameters=common_pars, my_second_option="Set2")  
    pn.Row(pn.Column(common_pars.param, test.param, test2.param), pn.Row(test.f_both),pn.Row(test2.f_both))
    

    If you don't need to edit my_second_option, you can use:

    pn.Row(common_pars.param, pn.Row(test.f_both), pn.Row(test2.f_both))