Interactive control of graphics with Bokeh

This recitation was created by Nastya Oguienko and Justin Bois based on the lessons on dashboarding from previous versions of this course.


[1]:
import numpy as np

import scipy.stats as st

import bokeh.io
import bokeh.plotting
import bokeh.models

bokeh.io.output_notebook()

# The following needs to be changed accordingly to your localhost
notebook_url = "localhost:8888"
Loading BokehJS ...

Important! Interactive control of graphics does not work in Google Colab. You have to run these notebooks in Jupyter Lab on your local machine!

Also, dashboards will not appear in the HTML-rendered version of this notebook. You are therefore encouraged to download and run this notebook on your local machine.

Dashboarding with Bokeh

Data dashboards allow you to display your data with interactive controls. The viewer can adjust the controls to change what data you plot, change scales, zoom in on the data, etc. There are several different libraries that allow you to make plots like that. We will use Bokeh. Take a look at the Bokeh User Guide for more explanation on how to use it and create interactions. You can find some cool examples of apps demonstrating different features here and a list of possible widgets and panes here. A lot of examples are partly written with JavaScript, but it is possible to create interactive plots with only Python, which we will do in this recitation.

We will begin our demonstration of use of Bokeh Interactive plots with….

A simple example

As an example of how you can build interactive dashboards, we will start by plotting the PDF of the Normal distribution.

[2]:
mu = 0
sigma = 1

x = np.linspace(-10, 10, 2000)
y = st.norm.pdf(x, loc=mu, scale=sigma)

p = bokeh.plotting.figure(
    frame_height=275,
    frame_width=375,
    x_axis_label="x",
    y_axis_label="f(x ; µ, σ)",
)

p.line(x, y, line_width=2, color="#1f77b4")

bokeh.io.show(p)

Looks good, but what if we want to examine how the PDF changes with µ and σ? We can use bokeh.models.Slider() to instantiate interactive sliders using the Slider widget.

[3]:
# making the widgets
mu_slider = bokeh.models.Slider(
    title="µ", start=-5, end=5, step=0.01, value=0, width=375
)

sigma_slider = bokeh.models.Slider(
    title="σ", start=0.01, end=5, step=0.01, value=1, width=375
)

Because Bokeh uses specific data type for plotting that is called ColumnDataSource we will create the ColumnDataSource object for a the data we want to plot:

[4]:
cds = bokeh.models.ColumnDataSource(dict(x=x, y=y))

We now want to link this slider to the plot. To do that, we need to write a callback function which always takes three arguments: attr, old, and new, and updates the source for the plot. Notice that the function does not return anything but it updates the global ColumnDataSource object, so you have to use the global name of this object within the body of the function.

[5]:
def callback(attr, old, new):
    # Slider values
    mu = mu_slider.value
    sigma = sigma_slider.value

    # Renewing data in cds
    cds.data["y"] = st.norm.pdf(x, loc=mu, scale=sigma)

The last thing to do is to define when to use a callback function to update the information for plotting. We want to do this whenever the slider values are changed.

[6]:
mu_slider.on_change("value", callback)
sigma_slider.on_change("value", callback)

Now we can build our plot.

[7]:
# Build plot
cds = bokeh.models.ColumnDataSource(dict(x=x, y=y))

p = bokeh.plotting.figure(
    frame_height=275,
    frame_width=375,
    x_axis_label="x",
    y_axis_label="f(x ; µ, σ)",
)

p.line(
    source=cds,
    x="x",
    y="y",
    line_width=2,
    color="#1f77b4",
)

layout = bokeh.layouts.column(
    p,
    bokeh.layouts.column(
        bokeh.layouts.row(bokeh.layouts.Spacer(width=50), mu_slider),
        bokeh.layouts.Spacer(width=30),
        bokeh.layouts.row(bokeh.layouts.Spacer(width=50), sigma_slider),
    ),
)

To enable the Python callback, we need to create a function, as below, wherein we add our layout to a document. This seemingly strange looking function needs to be defined in this way whenever we are going to build a dashboard with a Python-based callback.

[8]:
def app(doc):
    doc.add_root(layout)

Finally, we can show our app, making sure to give the appropriate notebook URL.

[9]:
bokeh.io.show(app, notebook_url=notebook_url)

The sliders for µ and σ help us visualize how the Normal PDF depends on each variable.

As a review, let’s go through each component. First, we define our widgets, mu_slider and sigma_slider. When building more complicated dashboards, we can look at the Bokeh documentation to choose which widgets we want to use. We also define the ColumnDataSource which is the source for data during plotting. It can be defined later as well, however you have to remember to use the appropriate variable name in the callback functions.

Next, we define callback function together with widgets’ on_change() method to link the widgets to the plot. Every time we change the interactive widget, the output of the function updates.

Finally, we set the layout of our dashboard. We can define rows and columns through bokeh.layouts.row() and bokeh.layouts.column() respectively. We can set their heights and widths and add spaces through bokeh.models.Spacer. You may have to play around a bit to get it in the format that looks best to you.

Computing environment

[10]:
%load_ext watermark
%watermark -v -p numpy,scipy,bokeh,jupyterlab
Python implementation: CPython
Python version       : 3.11.4
IPython version      : 8.12.0

numpy     : 1.24.3
scipy     : 1.10.1
bokeh     : 3.2.1
jupyterlab: 4.0.4