Search code examples
pythonpanelbokehholoviewspyviz

Panel widgets do not update plot


I'm trying to use panel widgets inside a class in order to change a plot. The example below outputs the plot once, but when I select a different variable from the widget it doesn't update the plot.

import pandas as pd
import param

import holoviews as hv
hv.extension('bokeh')

import panel as pn
pn.extension()

df = pd.DataFrame({'index':[1,2,3,4,5],
                   'signal1':[1,2,3,4,5],
                   'signal2':[5,4,3,2,1]}).set_index('index')

class Plot(param.Parameterized):
    def __init__(self, df):
        self.multi_select = pn.widgets.MultiSelect(name='Vars', value=['signal1'],
                                                   options=['signal1', 'signal2'])
        self.df = df
        self.watcher = self.multi_select.param.watch(self.dashboard, 'value')
        super().__init__()

    def dashboard(self, *events, **kwargs):
        self.plt = hv.Curve(self.df, [self.df.index.name, self.multi_select.value[0]])
        return self.plt

a = Plot(df)
pn.Row(a.multi_select,a.dashboard)

However, if instead of panel widgets I use param, it works as expected.

class Plot2(param.Parameterized):
    multi_select = param.ListSelector(default=['signal1'],
                                      objects=['signal1', 'signal2'])

    df = df

    def dashboard(self, **kwargs):
        self.plt = hv.Curve(self.df, [self.df.index.name, self.multi_select[0]])
        return self.plt

b = Plot2()
pn.Row(b.param,b.dashboard)

Am I doing something wrong or it's not possible at all? Thanks


Solution

  • First I modified you code a bit. Main changes are: I added

    • a try-except clause in Plot.dashboard() with print statements on the event and the variable self.multi_select
    • used an external bokeh server with view = pn.Row(a.multi_select,a.dashboard) and view.app() to get the prints from the try-except-clause.

    The complete Plot():

    class Plot(param.Parameterized):
    
        def __init__(self, df, **params):
            super(Plot, self).__init__(**params)
            self.multi_select = pn.widgets.MultiSelect(name='Vars', value=['signal1'],
                                                       options=['signal1', 'signal2'])
            self.df = df
            self.watcher = self.multi_select.param.watch(self.dashboard, 'value')
    
    
        def dashboard(self, *events, **kwargs):
            try: 
                event = events[0] # get first event in events
                select = events[0].obj # obj is our multi_select
    
                # see, what event looks like, and if select and self.multi_select are the same
                print(event, '\n', select, '\n', self.multi_select)
    
            except IndexError: pass
    
            self.plt = hv.Curve(self.df, [self.df.index.name, self.multi_select.value[0]])
            return self.plt
    
    a = Plot(df)
    view = pn.Row(a.multi_select,a.dashboard)
    view.app()
    

    enter image description here

    So your code is working as one might expect, but the changes are not displayed.

    Second: Separating the Curve plot from the multiselect and toying around shows, that the curve is only changed, if the method view depends on a changed parameter (here the name of the column to use for the plot):

    class Curve_(param.Parameterized):
        col = param.String()
    
        def __init__(self, df, col, **params):
            super(Curve_, self).__init__(**params)
            self.df = df
            self.col = col
    
        @param.depends('col', watch=True)
        def view(self): 
            return hv.Curve(self.df, [self.df.index.name, self.col])
    
    t=Curve_(df, 'signal1')
    pn.Column(t.view)
    

    Combining the classes Plot and Curve_ and using a parameter dashboard_trigger to keep the dashboard updated, everything is now working as expected.

    class Plot(param.Parameterized):
        dashboard_trigger = param.Number(1)
    
        def __init__(self, df, **params):
            super(Plot, self).__init__(**params)
            self.multi_select = pn.widgets.MultiSelect(name='Vars', value=['signal1'],
                                                       options=['signal1', 'signal2'])
            self.curve = Curve_(df, self.multi_select.value[0])
            self.watcher = self.multi_select.param.watch(self.set_column, 'value')
    
        def set_column(self, *events):
            self.curve.col = events[0].obj.value[0]
            self.dashboard_trigger = self.dashboard_trigger + 1
    
        @param.depends('dashboard_trigger', watch=True)
        def dashboard(self, *events, **kwargs):
            return self.curve.view
    
    a = Plot(df)
    pn.Row(a.multi_select,a.dashboard)
    

    enter image description here