Combine Action with iteration and sliders

Hi,

I’m stuck doing a plot and I am not even sure whether Panel is able to achieve what I am looking for.
My idea is the following: I would like to create a class with param.Parameterized that has the following elements:

  • Some sliders
  • An action button
  • A scatter plot

First, I would like to use the sliders to set some parameters, like the number of dots, or the proportion of dots of one class or another. After setting the sliders a plot should appear on the Panel.

Second, the action perform an action like the following: for each dot in the figure, change its class. Thus, the action should, for each dot in the scatter, change its color and update the figure.

So far I have created this skeleton:

def plotclasschange():
    class myClass(param.Parameterized):
        slider_N = param.Integer(200, bounds=(10, 1000))
        slider_prop = param.Number(1, bounds=(0, 1))
        button = param.Action()

        @panel.depends('slider_N', 'slider_prop', watch=True)
        def create_scatter(self):
            color_palette = ['#f09711', '#0eb9ed']
            N = self.N_slider
            points = np.random.uniform(0, 1, (N, 2))
            idx = np.random.choice(np.arange(0, N), int(N * self.slider_prop))
            cats = np.zeros(N)
            cats[idx] = 1
            cats = cats.astype(bool)
            
            fig = figure(plot_height=400, plot_width=400,
                         tools='', sizing_mode='scale_width', output_backend="webgl")
            
            df = pd.DataFrame({'x': points[:, 0], 'y': points[:, 1], 'c': [color_palette[i] for i in cats]})
            
            fig.scatter(x='x', y='y', source=df, color='c')
            
            return fig

        @panel.depends('button', watch=True)
        def update_fig(self):
            for i in range(len(df)):
                df['c'].iloc[i] =  '#f09711' if df['c'].iloc[i] ==  '#0eb9ed' else  '#0eb9ed'
           ????

    sss = myClass(title='') 
    pr = panel.Row(panel.Param(sss.param, widgets={
         'slider_N': {'size': 19, 'name': 'N'},
         'slider_prop': {'size': 10, 'name': 'Proportion'},
         'button': {'name': 'Go'}, 
         }), sss.create_scatter)
    
    return pr

At this point I am stuck. I know how I should update the figure, but I don’t know how to do it when the button is pressed, and how to update it after each iteration. Of course, if any of the sliders change, I would like to update the plot to zero, and then press the button again to do the change.

Thanks for the help!

Welcome Alex! Thanks for reposting the question here. I’ve changed your code a bit and annotated comments where you were using patterns which are either not recommended or won’t work. Please feel free to ask more questions since it wasn’t always clear to me what you were trying to achieve so I may not have addressed your issues very well:

class myClass(param.Parameterized):
    
    # Name parameters after what they do
    N = param.Integer(200, bounds=(10, 1000))

    proportion = param.Number(1, bounds=(0, 1))

    # Use this trick to trigger an event on button clicks
    go = param.Action(lambda self: self.param.trigger('go'))
    
    def __init__(self, **params):
        super().__init__(**params)
        # It is **much** more efficient to create the figure once and update it
        self.fig = figure(plot_height=400, plot_width=400,
            tools='', sizing_mode='scale_width', output_backend="webgl")
        self.cds = ColumnDataSource(data={'x': [], 'y': [], 'c': []})
        self.fig.scatter(x='x', y='y', source=self.cds, color='c')

    # Only use watch=True for functions which have side effects
    @pn.depends('N', 'proportion', watch=True)
    def update_scatter(self):
        color_palette = ['#f09711', '#0eb9ed']
        N = self.N
        points = np.random.uniform(0, 1, (N, 2))
        idx = np.random.choice(np.arange(0, N), int(N * self.proportion))
        cats = np.zeros(N)
        cats[idx] = 1
        cats = cats.astype(bool)
        # Setting a ColumnDataSource will update it
        self.cds.data.update(**{'x': points[:, 0], 'y': points[:, 1], 'c': [color_palette[i] for i in cats]})

    @pn.depends('go', watch=True)
    def update_fig(self):
        # Not sure what you are doing here, but this flips the colors
        self.cds.data['c'] = ['#f09711' if c == '#0eb9ed' else '#0eb9ed' for c in self.cds.data['c']]

    # If you are recreating the figure each time do not use watch
    # supply sss.create_scatter already tells Panel it should watch for parameter
    # changes
    #@pn.depends('slider_N', 'slider_prop')
    def create_scatter(self):
        # If you want to update the figure only on button clicks only depend on the Action parameter
        ...
        return fig

# Title is not a parameter of parameterized classes, name is
sss = myClass(name='')

# Size is not a parameter of sliders
pr = pn.Row(sss.param, sss.fig)
pr