☰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

Bokeh: how to update/redraw the plot data after slider.on_change (with AJAX)?

Community Supportflaskbokehjsserverbokeh
stamljf
stamljf

I’m creating a heatmap + colormap with Bokeh + Flask. I would like to use Flask routes (instead of running a second Bokeh server involving Tornado…).

The following code works, but the slider1.on_change is now connected to a dummy CustomJS callback.

How to make that the slider changes trigger a re-sending of new data to the plot widget, with “AJAX”-like communication (here with a POST request /data/)?

import numpy as np
from flask import Flask, jsonify, request
from jinja2 import Template
from bokeh.models import ColorBar, LinearColorMapper
import bokeh.plotting, bokeh.embed, bokeh.models.callbacks, bokeh.models, bokeh.layouts
from bokeh.resources import INLINE

app = Flask(__name__)

template = Template('''<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Streaming Example</title>{{ js_resources }}{{ css_resources }}</head><body>{{ plot_div }}{{ plot_script }}</body></html>''')

def new_data(mu, sigma):
    return mu + np.random.randn(100, 100) * sigma

@app.route("/data", methods=['POST'])
def send_data():
    params = request.json
    Z = new_data(params["mu"], params["sigma"])
    return jsonify(z=[Z])

@app.route("/")
def simple():
    Z = new_data(mu=0, sigma=1)
    color_mapper = LinearColorMapper(palette="Viridis256", low=0, high=5)
    plot = bokeh.plotting.figure(x_range=(0, 1), y_range=(0, 1), height=200)
    plot.image(image=[Z], color_mapper=color_mapper, dh=1.0, dw=1.0, x=0, y=0)
    color_bar = ColorBar(color_mapper=color_mapper)
    plot.add_layout(color_bar, "right")
    callback = bokeh.models.callbacks.CustomJS(args=dict(), code="")
    slider1 = bokeh.models.Slider(start=-5, end=5, value=0, step=1, title="Mu")
    slider1.js_on_change('value', callback)
    slider2 = bokeh.models.Slider(start=1, end=20, value=10, step=1, title="Sigma")
    slider2.js_on_change('value', callback)
    layout = bokeh.layouts.column(plot, slider1, slider2)
    script, div = bokeh.embed.components(layout, INLINE)
    html = template.render(plot_script=script, plot_div=div, js_resources=INLINE.render_js(), css_resources=INLINE.render_css())
    return html

app.run(debug=True)
⌁   Link to post
stamljf
stamljf

TL;DR: Is there a way to ask BokehJS to replace the heatmap’s content with data from an AJAX call?

JS Pseudo code which gets executed after a slider has moved:

fetch("/data").then(r => r.json()).then(data => {
    LinearColorMapper.update_content({ new_data: data });
});

?

⌁   Link to post
Bryan
Bryan

Have you looked at AjaxDataSource ? You can set the polling interval to None and call its get_data method manually from a CustomJS callback to fetch new data from some endpoint. You shouldn’t need to update the color mapper unless there is something about the color mapping that you wanted to change (e.g. different palette).

⌁   Link to post
stamljf
stamljf

Thanks @Bryan. This nearly works, except:

  • it does a new /data POST request on each slider move, but it does not update the plot accordingly, how to do this? probably a change in the CustomJS code below?

  • how to get the new slider1, slider2 values in the route for /data? (here I did a dummy incrementation of a, b)

from flask import Flask, jsonify
from jinja2 import Template
import numpy as np
from bokeh.plotting import figure
from bokeh.models import AjaxDataSource, Slider
from bokeh.models.callbacks import CustomJS
from bokeh.embed import components
from bokeh.layouts import column
from bokeh.resources import INLINE
app = Flask(__name__)
a, b = 1, 0
@app.route("/data", methods=['POST'])
def get_x():
    global a, b
    a += 1      # in real use case, I would like to get the value of the "slider1"
    b += 1      #                                                    ... "slider2"
    x = np.arange(100)
    y = a * np.sin(x) + b
    return jsonify(x=x.tolist(), y=y.tolist())
template = Template('''<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Streaming Example</title> {{ js_resources }} {{ css_resources }} </head> <body> {{ plot_div }} {{ plot_script }} </body> </html> ''') 
@app.route("/")
def simple():
    source = AjaxDataSource(data_url="http://127.0.0.1:5000/data", polling_interval=None, mode='replace')
    fig = figure(title="Streaming Example")
    fig.line( 'x', 'y', source=source)
    callback = CustomJS(args=dict(source=source), code="""
        source.get_data();     // this triggers a new POST /data request... but it does not update the plot!
        """)
    slider1 = Slider(start=1, end=5, value=1, step=1, title="a")
    slider1.js_on_change('value', callback)
    slider2 = Slider(start=0, end=20, value=0, step=1, title="b")
    slider2.js_on_change('value', callback)
    layout = column(fig, slider1, slider2)
    script, div = components(layout, INLINE)
    html = template.render(plot_script=script, plot_div=div, js_resources=INLINE.render_js(), css_resources=INLINE.render_css())
    return html
app.run(debug=True)
⌁   Link to post
Bryan
Bryan

I would have expected that to update the plot, the usua usage with a timeout interval just goes through that same path, and does update. I will have to investigate when I can get a chance.

⌁   Link to post
Bryan
Bryan

@stamljf Certainly, but do be aware that all of the actual work is done in JavaScript. You can see that when there is a polling interval, the Ajax data source just uses it to set up a setInterval callback for get_data:

Edit: and glancing at the base class code, I think the issue is that you are not passing a mode argument to get_data. The other arguments have defaults, but not mode, and some value is required to hit the codepaths that update the actual data internally. You can pass source.mode just like the snippet above does.

⌁   Link to post
Bryan
Bryan

Pass both sliders in the args parameter to the CustomJS callback, then the callback can examine the slider’s current values (to update the URL or whatever) before calling get_data.

⌁   Link to post

End of archived discussion · Replies are disabled