Bokeh plot not updating when new data is set

Consider the following example, in which a user interacts with a selector in order to update a picture:

import param
import panel as pn
pn.extension()
from bokeh.models import GlyphRenderer, ColumnDataSource
from bokeh.plotting import figure

class Test_1(pn.viewable.Viewer):
    selector = param.Selector(default=0, objects=[0, 1])
    data_list = param.List([
        {
            "x": [1, 3, 4, 6],
            "y": [1, 2, 3, 4]
        },
        {
            "x": [1, 2, 3],
            "y": [5, 4, 3]
        }
    ])
    figure = param.ClassSelector(class_=figure)
    r = param.ClassSelector(class_=GlyphRenderer)
    
    def __init__(self, **params):
        super().__init__(**params)
        self.figure = figure(width=300, height=300)
        self.r = self.figure.line("x", "y", source=ColumnDataSource(data=self.data_list[self.selector]), line_width=2)

    @param.depends("selector", watch=True)
    def _update(self):
        self.r.data_source.data.update(self.data_list[self.selector])

    def __panel__(self):
        return pn.Row(
            self.param.selector, 
            self.figure
        )

d = Test_1()
d

Everything works as expected. I can even execute d.selector = 1 on a new cell, and it will trigger the update.

Now, suppose I remove the selector and I modify the code so that I can provided the data through a parameter. I would like to be able to update the plot when new data comes in:

class Test_2(pn.viewable.Viewer):
    data = param.Dict({})
    figure = param.ClassSelector(class_=figure)
    r = param.ClassSelector(class_=GlyphRenderer)
    
    def __init__(self, **params):
        super().__init__(**params)
        self.figure = figure(width=300, height=300)
        self.r = self.figure.line("x", "y", source=ColumnDataSource(data=self.data), line_width=2)

    @param.depends("data", watch=True)
    def _update(self):
        print("_update")
        self.r.data_source.data.update(self.data)

    def __panel__(self):
        return self.figure

data_list = [
    {
        "x": [1, 3, 4, 6],
        "y": [1, 2, 3, 4]
    },
    {
        "x": [1, 2, 3],
        "y": [5, 4, 3]
    }
]
d = Test_2(data=data_list[0])
d

image

Then, on a new cell I execute:

d.data = data_list[1]

Thanks to the print statement, I can see that the _update method is executed. However, the figure is not updated.

image

What do I have to do to trigger the update on the figure?

I think rather than update, you need to set it explicitly:

import param
import panel as pn
pn.extension()
from bokeh.models import GlyphRenderer, ColumnDataSource
from bokeh.plotting import figure

class Test_2(pn.viewable.Viewer):
    data = param.Dict({})
    figure = param.ClassSelector(class_=figure)
    r = param.ClassSelector(class_=GlyphRenderer)
    
    def __init__(self, **params):
        super().__init__(**params)
        self.figure = figure(width=300, height=300)
        self.r = self.figure.line("x", "y", source=ColumnDataSource(data=self.data), line_width=2)

    @param.depends("data", watch=True)
    def _update(self):
        print("_update")
        self.r.data_source.data = self.data
        
    def __panel__(self):
        return self.figure

data_list = [
    {
        "x": [1, 3, 4, 6],
        "y": [1, 2, 3, 4]
    },
    {
        "x": [1, 2, 3],
        "y": [5, 4, 3]
    }
]
d = Test_2(data=data_list[0])
d