Parameterized class that keeps Bokeh line plot x and y range

Hi all. I have a parameterized class that calls and updates a class of bokeh plot objects using @pn.depends. Unfortunately this means that when I launch the server and update widgets the bokeh x-y range gets reset. I have looked over the prior exampels using hvplot and hv.DynamicMap and unfortunately hv.DynamicMap does not like taking a param object. I would prefer not to use the holoviews bokeh extension for 1) the simple reason that I’ve already coded things up and have this one sticky issue and 2) I like being able to not have to call a df directly in the plot line, such that it is simple to read how things are getting plotted (at least for me). Minimum reproducible example below.

import pandas as pd
import bokeh
import numpy as np
from bokeh.plotting import figure
import panel as pn
import param

data = {'x': np.random.rand(10),'y1': np.random.rand(10),'y2': np.random.rand(10), 'y3': np.random.rand(10)}
df = pd.DataFrame(data=data)
color_palette = bokeh.palettes.Muted9

class plots():
    def __init__(self,*args):
        super().__init__(*args)
        for a in args:
            self.__setattr__(str(a), args[0])
    
    def line_plot(data,ys_to_plot,vert_line1,vert_line2):
        fig = figure(height=500,width=500,tools='pan,reset,save,wheel_zoom,xwheel_zoom,ywheel_zoom')
        
        for v,c in zip(ys_to_plot,color_palette):
            fig.line(data['x'],data[v],line_width=0.7,legend_label='{}'.format(v),color=c)
        
        fig.line([vert_line1,vert_line1],[0,1],line_width=0.4,line_dash='dashed',color='black')
        fig.line([vert_line2,vert_line2],[0,1],line_width=0.4,line_dash='solid',color='black')
        
        return fig
    

class update_plots(param.Parameterized):
    ys_to_plot = param.ListSelector(default=['y1'], objects=['y1','y2','y3'])
    vert_line1 = param.Number(default=0.2,bounds=(0,1))
    vert_line2 = param.Number(default=0.8,bounds=(0,1))
    
    def __init__(self,**params):
        super().__init__(**params)
        self.widgets = pn.Param(self,parameters=['ys_to_plot','vert_line1','vert_line2'])
        
        
    @pn.depends('ys_to_plot','vert_line1','vert_line2')
    def dynamic_plot(self):
        return plots.line_plot(df,self.ys_to_plot,self.vert_line1,self.vert_line2)

callapp = update_plots(name='my application')

widgets = {'ys_to_plot': pn.widgets.CheckBoxGroup}

vanilla_layout = pn.template.VanillaTemplate(title='my application',
                                             sidebar=pn.Column(pn.WidgetBox(pn.Param(callapp.param,widgets=widgets))),
                                             sidebar_width=380)
vanilla_layout.main.append(pn.Row(callapp.dynamic_plot))      
vanilla_layout.show();

A DynamicMap has special code for figuring out in-place minimal updates, precisely to avoid having to re-render the whole plot as you see in this case. I don’t know of any direct equivalent to that for pure bokeh plots, but I’d guess there’s some way to use a Bokeh JS link (JavaScript callbacks — Bokeh 2.4.2 Documentation) to wire the widget to the position of that marker.