Christopher Vahl
Christopher Vahl

Reputation: 33

Bokeh: Update figure multiple times in a single Python callback

I want to update a figure multiple times within a single Python callback. As a simple example, say after clicking on a button, I want to change the coordinates of a line 10 times, each time displaying the changed line for a short time. You can call that an animation if you like.

In the Bokeh documentation, I only found this.

Here is a non-working example, illustrating what I want:

from time import sleep
from bokeh.plotting import curdoc, figure
from bokeh.models import ColumnDataSource, Button
from bokeh.layouts import column
from bokeh.events import ButtonClick

source = ColumnDataSource(data=dict(x=[0, 1], y=[0, 0]))
doc = curdoc()


def button_pushed():
    for i in range(10):
        source.data = dict(x=[0, 1], y=[i, i])
        sleep(0.5)


p = figure(plot_width=600, plot_height=300)
p.line(source=source, x='x', y='y')

button = Button(label='Draw')
button.on_event(ButtonClick, lambda: button_pushed())

doc.add_root(column(button, p))

With the above code as a Bokeh app, the line is updated only once after the callback is executed completely.

Upvotes: 3

Views: 1064

Answers (1)

HYRY
HYRY

Reputation: 97281

you can use asyncio to do this. Do your calculation in loop() and then use doc.add_next_tick_callback() to update the data source.

from functools import partial
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, Button
from bokeh.layouts import column
from bokeh.events import ButtonClick
from tornado.ioloop import IOLoop
import asyncio

def view(doc):
    source = ColumnDataSource(data=dict(x=[0, 1], y=[0, 0]))

    def update_source(new_data):
        source.data = new_data

    async def loop():
        for i in range(10):
            doc.add_next_tick_callback(partial(update_source, dict(x=[0, 1], y=[i, i**2])))
            await asyncio.sleep(0.5)

    def button_pushed():
        IOLoop.current().spawn_callback(loop)

    p = figure(plot_width=600, plot_height=300)
    p.line(source=source, x='x', y='y')

    button = Button(label='Draw')
    button.on_event(ButtonClick, button_pushed)

    doc.add_root(column(button, p))

show(view)

Upvotes: 2

Related Questions