Temporarily setting a line to not visible

I’m working on a dashboard that uses Bokeh (v. 3.4.3) for plotting and Panel (v. 1.4.5) for layout and widgets. For one of my plots, the user presses a button to retrieve new data, perform a calculation, and update the plot.

In the context of my dashboard, the calculation takes a long time to perform (10-20 seconds). Everything works exactly how it’s supposed to, but from the user’s perspective, the plot is “frozen” because it takes that 10-20 seconds for the plot to update with the new data. When the plot doesn’t immediately update, it’s tempting to keep pressing the button.

Would it be possible for the line associated with the old data to immediately disappear and then somehow reappear once the calculation is finished?

In this code below, I added a sleep timer to mimic the calculation time. Setting the line to not visible at the beginning of the function and setting it back to visible at the end doesn’t seem to work.

from bokeh.models import ColumnDataSource
import panel as pn

pn.extension()
from bokeh.plotting import figure
import time

data = {"x": [1, 2, 3, 4, 5], "y": [4, 7, 5, 10, 2]}
source = ColumnDataSource(data=data)

p = figure()

line = p.line(x="x", y="y", source=source)

button = pn.widgets.Button(name="Add 3")


def update_data(button):
    line.visible = False
    time.sleep(10)
    x = [x + 3 for x in source.data["x"]]
    y = source.data["y"]
    source.data = {"x": x, "y": y}
    line.visible = True


button.on_click(update_data)

layout = pn.Column(button, p)

layout.servable()

Enter panel serve {file_name.py} in the terminal to serve the dashboard.

I’ve also tried putting a timer on line.visible=False and that doesn’t seem to work either.

def update_data(button):
    x = [x + 3 for x in source.data["x"]]
    y = source.data["y"]
    source.data = {"x": x, "y": y}
    line.visible = False
    timer.sleep(10)
    line.visible = True

Is what I’m trying to do even possible? Any suggestions?

The synchronization between Python and JS only happens at function exits, so the first line.visible = False has no effect since you change the value back to True before the function ends. You will need to split up your callback into two pieces using add_next_tick_callback to do the second part of the work in a separate function invocation. This technique is demonstrated here:

https://docs.bokeh.org/en/latest/docs/user_guide/server/app.html#updating-from-threads

Thanks Bryan! My bokeh figure is wrapped in a Panel layout, which complicates things. I’m going to try using a Panel PeriodicCallback.

Actually nope, nevermind. PeriodicCallback probably won’t work here.

I’m not sure how Panel is relevant here. If you are using button.on_click(update_data) as you show above then there should be no issue calling Bokeh’s add_next_tick_callback from inside update_data as demonstrated in the earlier link.

The main problem is that the Panel button isn’t compatible with curdoc(). Switching to a Bokeh button resolved this issue. Thanks for pointing me in the right direction! Here’s what I worked out if anyone is interested.

from bokeh.models import ColumnDataSource, Button
import panel as pn

from functools import partial

pn.extension()
from bokeh.plotting import figure, curdoc
import time

data = {"x": [1, 2, 3, 4, 5], "y": [4, 7, 5, 10, 2]}
source = ColumnDataSource(data=data)

p = figure()

line = p.line(x="x", y="y", source=source)

button = Button(name="Add 3")

def update(data):
    time.sleep(5)
    source.data = dict(x=data["x"], y=data["y"])
    line.visible = True


def button_callback(event):
    x = [x + 3 for x in source.data["x"]]
    y = source.data["y"]
    new_data = dict(x=x, y=y)
    line.visible = False
    curdoc().add_next_tick_callback(partial(update, new_data))


button.on_click(button_callback)
curdoc().add_root(button)

layout = pn.Column(button, p)

layout.servable()
1 Like