Updating a Bokeh plot with a `param.Event`

I am replicating the example given in the panel repo (panel/sinewave.py at master · holoviz/panel · GitHub), except I would like to not have the plot update until a button is pressed. As an aside, I need to use the button to simulate a lengthy computation, which is why it is necessary. Below is the code with the button and a peculiar requirement for a widget in order for the Bokeh plot to update.

I am working in Juptyer lab with

  • panel 0.14.1
  • bokeh 2.4.3
  • param 1.12.3
  • numpy 1.23.5
  • jupyterlab 3.5.1
import numpy as np
import panel as pn
import param
from bokeh.models.sources import ColumnDataSource
from bokeh.plotting.figure import figure

pn.extension()


class WatchMethodExample(param.Parameterized):

    amplitude = param.Number(default=1.0, bounds=(-5.0, 5.0))
    frequency = param.Number(default=1.0, bounds=(0.1, 5.1))
    N = param.Integer(default=200, bounds=(0, None))
    offset = param.Number(default=0.0, bounds=(-5.0, 5.0))
    phase = param.Number(default=0.0, bounds=(0.0, 2 * np.pi))
    x_range = param.Range(default=(0, 4 * np.pi), bounds=(0, 4 * np.pi))
    y_range = param.Range(default=(-2.5, 2.5), bounds=(-10, 10))
    update = param.Event()

    required_widget_display = param.Parameter(default="")

    def __init__(self, **params):
        super(WatchMethodExample, self).__init__(**params)
        x, y = self.compute_data()
        self.cds = ColumnDataSource({"x": x.tolist(), "y": y.tolist()})
        self.plot = self.create_figure()
        self.plot.line(x="x", y="y", source=self.cds)

    def compute_data(self):
        x = np.linspace(0, 4 * np.pi, self.N)
        y = self.amplitude * np.sin(self.frequency * x + self.phase) + self.offset
        return x, y

    def create_figure(self):
        return figure(
            plot_height=400,
            plot_width=400,
            outline_line_color="black",
            x_axis_label="x",
            y_axis_label="y",
            title="Sine wave",
            x_range=self.x_range,
            y_range=self.y_range,
        )

    @param.depends(
        "amplitude",
        "frequency",
        "N",
        "offset",
        "phase",
        "x_range",
        "y_range",
        "update",
        watch="queued",
    )
    def update_plot(self):
        if self.update is True:
            # COMMENT  OUT THE NEXT LINE
            self.required_widget_display = np.random.randint(1)
            x, y = self.compute_data()
            self.cds.data = {"x": x.tolist(), "y": y.tolist()}
            self.plot.x_range.start, self.plot.x_range.end = self.x_range
            self.plot.y_range.start, self.plot.y_range.end = self.y_range


w = WatchMethodExample()
pn.Row(w.param, w.plot)

I cannot upload images, but if I comment out the line indicated in the above code, the Bokeh plot no longer updates. I’m assuming I’m not using the event correctly, but I’m not entirely sure.

Not 100% sure I understand your example. But the code below seems to work.
All I did was create a callable called get_plot() to display instead of the static plot property.

import numpy as np
import panel as pn
import param
from bokeh.models.sources import ColumnDataSource
from bokeh.plotting.figure import figure

pn.extension()


class WatchMethodExample(param.Parameterized):

    amplitude = param.Number(default=1.0, bounds=(-5.0, 5.0))
    frequency = param.Number(default=1.0, bounds=(0.1, 5.1))
    N = param.Integer(default=200, bounds=(0, None))
    offset = param.Number(default=0.0, bounds=(-5.0, 5.0))
    phase = param.Number(default=0.0, bounds=(0.0, 2 * np.pi))
    x_range = param.Range(default=(0, 4 * np.pi), bounds=(0, 4 * np.pi))
    y_range = param.Range(default=(-2.5, 2.5), bounds=(-10, 10))
    update = param.Event()

    required_widget_display = param.Parameter(default="")

    def __init__(self, **params):
        super(WatchMethodExample, self).__init__(**params)
        x, y = self.compute_data()
        self.cds = ColumnDataSource({"x": x.tolist(), "y": y.tolist()})
        self.plot = self.create_figure()
        self.plot.line(x="x", y="y", source=self.cds)

    def compute_data(self):
        x = np.linspace(0, 4 * np.pi, self.N)
        y = self.amplitude * np.sin(self.frequency * x + self.phase) + self.offset
        return x, y

    def create_figure(self):
        return figure(
            plot_height=400,
            plot_width=400,
            outline_line_color="black",
            x_axis_label="x",
            y_axis_label="y",
            title="Sine wave",
            x_range=self.x_range,
            y_range=self.y_range,
        )

    @param.depends(
        "amplitude",
        "frequency",
        "N",
        "offset",
        "phase",
        "x_range",
        "y_range",
        "update",
        watch="queued",
    )
    def update_plot(self):
        if self.update is True:
            # COMMENT  OUT THE NEXT LINE
            # self.required_widget_display = np.random.randint(1)
            x, y = self.compute_data()
            self.cds.data = {"x": x.tolist(), "y": y.tolist()}
            self.plot.x_range.start, self.plot.x_range.end = self.x_range
            self.plot.y_range.start, self.plot.y_range.end = self.y_range

    def get_plot(self):
        return self.plot            

w = WatchMethodExample()
pn.Row(w.param, w.get_plot)

Fascinating.

I still cannot upload files to show as gifs of the issue (as a new user), but the solution works. When I have the ability to upload images, I will do so in order to complete the conversation.

1 Like