☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

How to add and remove periodic callbacks, from on_click button events?

Community Supportserver
mfjimenez
mfjimenez

First of all I would like to extend my congratulations to the developers, as of recent I checked on the different plotting options with Python, and Bokeh has delivered! I recently published some nice XPS spectrum deconvolution for scientific publication via Bokeh! I may display these pictures here once editorial publishes the document.

Now I am heading towards server applications, I would like to develop a GUI for electrochemical readouts. But I am currently struggling with the add_periodic_callback and remove_periodic_callback methods.

I require two buttons, one for data acquisition and another one to stop acquisition. So far, I am able to add periodic callbacks at the global scope, and then remove these via on_click function.

However, I am not able to add periodic callbacks from a button event and then delete these from a second one. Evidently, this problem has to do with the scope of variables/classes, but I haven’t found my way around it. I provide an example code, which is a modified version of the “Updating from Unlocked Callbacks” example.

from functools import partial

import time

from concurrent.futures import ThreadPoolExecutor

from tornado import gen

from bokeh.document import without_document_lock

from bokeh.models import ColumnDataSource

from bokeh.plotting import curdoc, figure

from bokeh.models.widgets import Button

from bokeh.layouts import column, row

source = ColumnDataSource(data=dict(x=[0], y=[0], color=["blue"]))

i = 0

doc = curdoc()

executor = ThreadPoolExecutor(max_workers=2)

def blocking_task(i):

    time.sleep(1.5)

    return i

@gen.coroutine

def locked_update(i):

    source.stream(dict(x=[source.data['x'][-1]+1], y=[i], color=["blue"]))

@gen.coroutine

@without_document_lock

def unlocked_task():

    global i

    i += 1

    res = yield executor.submit(blocking_task, i)

    doc.add_next_tick_callback(partial(locked_update, i=res))

@gen.coroutine

def update():

    source.stream(dict(x=[source.data['x'][-1]+1], y=[i], color=["red"]))

    #print("Just streamed")

p = figure(x_range=[0, 100], y_range=[0, 20])

l = p.circle(x='x', y='y', color='color', source=source)

#---------------------------#

#          Buttons          #

#---------------------------#

Add_Callbacks = Button(label='Add Callbacks', button_type='success', width=320)

Remove_Callbacks = Button(label='Remove Callbacks',

                          button_type='warning', width=320)

def callback_Add_threads():

    #NOTE: Adding callbacks from here, can't be removed

    nonlocal callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)

    callback_update = doc.add_periodic_callback(update, 200)

def callback_Remove_threads():

    #The following functions are not able to acces these threads

    doc.remove_periodic_callback(callback_unlocked_task)

    doc.remove_periodic_callback(callback_update)

Add_Callbacks.on_click(callback_Add_threads)

Remove_Callbacks.on_click(callback_Remove_threads)

#NOTE: If I add the periodic callbacks from here, these can be removed by my "remove" button.

#UNCOMMENT callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)

#UNCOMMENT callback_update = doc.add_periodic_callback(update, 200)

doc.add_root(row(p, column(Add_Callbacks, Remove_Callbacks)))
⌁   Link to post
p-himik
p-himik

Python has global and nonlocal keywords for that. Also, instead of storing the required objects in variables, you can store them as fields of some object. With just a = 1, a cannot be a global object (unless you use one of the aforementioned keywords), but with x.a = 1 the x object can come from anywhere since you don’t assign directly to it.

⌁   Link to post
mfjimenez
mfjimenez

Have tried the global and nonlocal approaches, but as far as my understanding goes, we use nonlocal when we are nesting functions and you want to access to a variable that its on higher scope (hopefully my lexicon is correct).

Thing is, the callback id is on a different function, like this:

def callback_Add_threads():
    #NOTE: Adding callbacks from here, can't be removed
    callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)
    callback_update = doc.add_periodic_callback(update, 200)

def callback_Remove_threads():
    #The following functions are not able to acces these threads
    doc.remove_periodic_callback(callback_unlocked_task)
    doc.remove_periodic_callback(callback_update)

Which didn’t work. I will try your second proposal.

I am sorry if this is more fundamental, I am learning python on the go and still don’t know what would be the best coding philosophy when it somes to this kind of aaplications.

⌁   Link to post
p-himik
p-himik

So if you have code like

def change_a():
    a = 1

def use_a():
    print(a)

it will not work.
To make it work, you can change it so it looks like this:

a = None

def change_a():
    global a
    a = 1

def use_a():
    print(a)

There are other options, but this one is the most straightforward.

⌁   Link to post
mfjimenez
mfjimenez

This worked! Really appreciate your support. I didn’t try to assign a None in the global scope (didn’t know until now about it, I come from a C background, so it is still weird to me not to assign a type to variables).

callback_unlocked_task = None
callback_update = None

def callback_Add_threads():
    #NOTE: Adding callbacks from here, can't be removed
    global callback_unlocked_task
    global callback_update
    callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)
    callback_update = doc.add_periodic_callback(update, 200)

def callback_Remove_threads():
    #The following functions are not able to acces these threads
    doc.remove_periodic_callback(callback_unlocked_task)
    doc.remove_periodic_callback(callback_update)

I will use this for my application. Just one more question, is this the only way how I could had implemented adding and removing threads with different buttons? Usually people discourage the use of global variables.

I am very thankful! :smile:

⌁   Link to post
p-himik
p-himik

Of course it’s not the only way - the amount of ways is virtually infinite. Although, only a few of them are practical.

Another way would be wrap everything in a function. Or a class.

⌁   Link to post

End of archived discussion · Replies are disabled