Dynamically change hvplot opts instead of regenerating the plot

Hello,

I’d like to change the line width when I update the slider.
But it only took effect after regenerating the plot (select the other trial in my example).

I think the key command is the self.plot.opts(line_width=self.line_width_slider.value) in the following codes.
However, despite applying it, no changes were observed.
I’m wondering if I did something wrong in my codes, or if there are alternative methods to achieve the desired effect?

Thank you!

Here are my codes:

import param
import pandas as pd
import hvplot.pandas
import panel as pn
import holoviews as hv


example_data = pd.DataFrame({
    'Time': [0, 1, 2],
    'Trial_1': [1, 2, 3],
    'Trial_2': [2, 1, 3],
    })


class DataViewer(param.Parameterized):
    trial = param.Parameter(pn.widgets.Select(name='Trial'))
    plot = param.Parameter()
    line_width_slider = param.Parameter(pn.widgets.FloatSlider(name='Line width', start=0.1, end=3.0, step=0.1, value=1.0))
    debug_text = param.Parameter(pn.pane.Markdown('Initialize'))


    def __init__(self, **params):
        super().__init__(**params)
        self.trial.param.watch(self._update_plot, 'value')
        self.line_width_slider.param.watch(self._update_line_width, 'value')
        self.plot = hv.Curve([])
        self._load_data()

    def _load_data(self):
        self.df = example_data
        self.trial.options = [None] + list(self.df.columns[1:])

    def _update_plot(self, event):
        self.debug_text.object = 'Enter _update_plot'
        if self.trial.value:
            self.plot = self.df.hvplot(x=self.df.columns[0], y=self.trial.value, line_width=self.line_width_slider.value) 
        else:
            self.plot = hv.Curve([])

    def _update_line_width(self, event):
        self.debug_text.object = 'Enter _update_line_width'
        self.plot.opts(line_width=self.line_width_slider.value)  ##### change line width #####

    @pn.depends('plot')
    def plot_view(self):
        return self.plot

    def panel(self):
        return pn.Column(self.trial, self.line_width_slider, self.plot_view, self.debug_text)


viewer = DataViewer()
viewer.panel().servable()

I think you should be using self.line_width_slider.param.value for the live value.

Thanks, @ahuang11!

This modification works for the first trial. (see the video)
However, when trying the Trial_2, the error (or warning?) showed:

No such watcher Watcher(inst=FloatSlider(end=3.0, name=‘Line width’, start=0.1, value=3), cls=<class ‘panel.widgets.slider.FloatSlider’>, fn=<bound method ParamMethod._replace_pane of ParamFunction(function, _pane=HoloViews)>, mode=‘args’, onlychanged=True, parameter_names=(‘value’,), what=‘value’, queued=False, precedence=0) to remove.

Here are my revised codes:
(apply self.line_width_slider.param.value in hvplot, and remove the function _update_line_width & self.line_width_slider.param.watch(self._update_line_width, 'value') )

import param
import pandas as pd
import hvplot.pandas
import panel as pn
import holoviews as hv


example_data = pd.DataFrame({
    'Time': [0, 1, 2],
    'Trial_1': [1, 2, 3],
    'Trial_2': [2, 1, 3],
    })


class DataViewer(param.Parameterized):
    trial = param.Parameter(pn.widgets.Select(name='Trial'))
    plot = param.Parameter()
    line_width_slider = param.Parameter(pn.widgets.FloatSlider(name='Line width', start=0.1, end=3.0, step=0.1, value=1.0))
    debug_text = param.Parameter(pn.pane.Markdown('Initialize'))


    def __init__(self, **params):
        super().__init__(**params)
        self.trial.param.watch(self._update_plot, 'value')
        self.plot = hv.Curve([])
        self._load_data()

    def _load_data(self):
        self.df = example_data
        self.trial.options = [None] + list(self.df.columns[1:])

    def _update_plot(self, event):
        self.debug_text.object = 'Enter _update_plot'
        if self.trial.value:
            self.plot = self.df.hvplot(x=self.df.columns[0], y=self.trial.value, line_width=self.line_width_slider.param.value) 
        else:
            self.plot = hv.Curve([])

    @pn.depends('plot')
    def plot_view(self):
        return self.plot

    def panel(self):
        return pn.Column(self.trial, self.line_width_slider, self.plot_view, self.debug_text)


viewer = DataViewer()
viewer.panel().servable()

Another question:
I can not use .opts(...) to change the underlying plot attributes and let it show in the plot immediately?

I think what I would do is wrap pn.pane.HoloViews and replace the object instead of simply return self.plot

For your second question, maybe you can try plot.apply.opts()