Online Bokeh App Compiler

Run Bokeh app code in your browser. Connect sliders, dropdowns and buttons to Python callbacks that update plots and tables.

Python
import numpy as np

from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TextInput
from bokeh.plotting import figure

# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))


# Set up plot
plot = figure(height=400, width=400, title="my sine wave",
            tools="crosshair,pan,reset,save,wheel_zoom",
            x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])

plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)


# Set up widgets
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)


def update_data(attrname, old, new):
    # Get the current slider values
    a = amplitude.value
    w = phase.value
    k = freq.value

    # Generate the new curve
    x = np.linspace(0, 4*np.pi, N)
    y = a*np.sin(k*x + w)

    source.data = dict(x=x, y=y)

for w in [amplitude, phase, freq]:
    w.on_change('value', update_data)


# Set up layouts and add to document
inputs = column(amplitude, phase, freq)

curdoc().add_root(row(inputs, plot, width=800))

A Bokeh app is a Bokeh plot with widgets, such as sliders, dropdowns and buttons, whose changes run Python callbacks. The callbacks change the data, and the plot redraws. People build apps for dashboards and tools where a chart has to respond to input. Normally an app runs with bokeh serve app.py, which starts a server to run the Python. This page is an online Bokeh app compiler: the code runs in your browser, so you can try it without installing anything. Run the example first, then paste any snippet below into a new cell to try it.

What the example does

ColumnDataSource holds the data as named columns: 200 x values from 0 to 4π and their sines. plot.line("x", "y", source=source) draws the line from those columns, so replacing source.data redraws it. x_range and y_range fix the axes, so an amplitude above 2.5 or below -2.5 runs past the edge of the plot. The three Slider widgets set the amplitude, phase and frequency. phase has no step, so it uses Bokeh's default of 1. update_data(attrname, old, new) has the signature Bokeh requires for on_change callbacks. It reads all three slider values, computes a*np.sin(k*x + w) and replaces source.data, and the loop registers it on each slider's value. column() stacks the sliders, row() puts them beside the plot, and curdoc().add_root() adds the layout to the document, which makes it an app. It appears below the cell when the cell runs. TextInput is imported but not used.

Switch data with a dropdown

A Select widget's value is the chosen option. The callback swaps in that product's numbers and renames the plot:

from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Select
from bokeh.plotting import figure

months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
sales = {"Tea": [120, 135, 150, 140, 160, 175], "Coffee": [200, 190, 210, 230, 220, 240]}
source = ColumnDataSource(data=dict(month=months, units=sales["Tea"]))

plot = figure(x_range=months, height=300, title="Tea sales")
plot.vbar(x="month", top="units", width=0.8, source=source)

product = Select(title="Product", value="Tea", options=list(sales))

def switch(attr, old, new):
    source.data = dict(month=months, units=sales[new])
    plot.title.text = f"{new} sales"

product.on_change("value", switch)
curdoc().add_root(column(product, plot))

Run code when a button is clicked

Button.on_click() runs a function on every click. source.stream() appends rows to a ColumnDataSource instead of replacing its data:

import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import Button, ColumnDataSource, Div
from bokeh.plotting import figure

rng = np.random.default_rng(seed=1)
source = ColumnDataSource(data=dict(x=[], y=[]))
plot = figure(height=300, width=400, x_range=(0, 1), y_range=(0, 1))
plot.scatter("x", "y", source=source, size=10)

button = Button(label="Add 10 points", button_type="primary")
count = Div(text="0 points")

def add_points():
    source.stream(dict(x=rng.random(10), y=rng.random(10)))
    count.text = f"{len(source.data['x'])} points"

button.on_click(add_points)
curdoc().add_root(column(button, count, plot))

Respond to a selection

The selection is stored on the data source as source.selected, so a plot and a DataTable built on one source share it. A callback on its indices runs whenever the selection changes:

from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, DataTable, Div, TableColumn
from bokeh.plotting import figure

source = ColumnDataSource(data=dict(
    fruit=["Apple", "Pear", "Plum", "Grape", "Cherry", "Mango"],
    price=[1.2, 1.5, 2.0, 3.1, 4.5, 2.8],
    sold=[340, 210, 150, 90, 60, 120],
))
plot = figure(height=300, width=350, tools="tap,box_select,reset",
              x_axis_label="Price", y_axis_label="Units sold")
plot.scatter("price", "sold", source=source, size=12)
table = DataTable(source=source, width=300, height=250, columns=[
    TableColumn(field=name, title=name.title()) for name in ["fruit", "price", "sold"]])
summary = Div(text="Select points or rows")

def selected(attr, old, new):
    sold = sum(source.data["sold"][i] for i in new)
    summary.text = f"{len(new)} selected, {sold} units sold"

source.selected.on_change("indices", selected)
curdoc().add_root(column(row(plot, table), summary))

Good to know

  • Python callbacks need an app, such as one built with curdoc().add_root(). A plot displayed with show(), as on the Bokeh page, is standalone HTML, and Bokeh warns that Python callbacks cannot work there. For a standalone plot, use js_on_change() with a CustomJS callback.
  • An on_change callback must take three arguments, (attr, old, new). With any other signature, on_change() raises a ValueError. An on_click callback takes no argument or one event.
  • The app runs in its own copy of Python, which it loads when it starts. It cannot use variables or imports from other cells, so keep everything the app needs in one cell.