We previously examined the Crossfilter application hosted on the bokeh.org website. We described some of the basic functionality of the application in a previous page, and will dive a bit deeper here into how that is implemented using the Bokeh server.

The source code for the crossfilter application is provided as part of the Bokeh demo site. It can be found in the Bokeh github repository, and is reproduced in the code box below. You might want to open the application in a separate browser window or tab in order to be able to look at both the application window and the source code below at the same time. The line numbers have been added to the presentation below to facilitate discussion of the code:

''' A crossfilter plot map that uses the `Auto MPG dataset`_. This example
demonstrates the relationship of datasets together. A hover tooltip displays
information on each dot.

.. note::
    This example needs the Pandas package to run.

.. _Auto MPG dataset: https://archive.ics.uci.edu/ml/datasets/auto+mpg

'''
import pandas as pd

from bokeh.layouts import column, row
from bokeh.models import Select
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure
from bokeh.sampledata.autompg import autompg_clean as df

df = df.copy()

SIZES = list(range(6, 22, 3))
COLORS = Spectral5
N_SIZES = len(SIZES)
N_COLORS = len(COLORS)

# data cleanup
df.cyl = df.cyl.astype(str)
df.yr = df.yr.astype(str)
del df['name']

columns = sorted(df.columns)
discrete = [x for x in columns if df[x].dtype == object]
continuous = [x for x in columns if x not in discrete]

def create_figure():
    xs = df[x.value].values
    ys = df[y.value].values
    x_title = x.value.title()
    y_title = y.value.title()

    kw = dict()
    if x.value in discrete:
        kw['x_range'] = sorted(set(xs))
    if y.value in discrete:
        kw['y_range'] = sorted(set(ys))
    kw['title'] = "%s vs %s" % (x_title, y_title)

    p = figure(height=600, width=800, tools='pan,box_zoom,hover,reset', **kw)
    p.xaxis.axis_label = x_title
    p.yaxis.axis_label = y_title

    if x.value in discrete:
        p.xaxis.major_label_orientation = pd.np.pi / 4

    sz = 9
    if size.value != 'None':
        if len(set(df[size.value])) > N_SIZES:
            groups = pd.qcut(df[size.value].values, N_SIZES, duplicates='drop')
        else:
            groups = pd.Categorical(df[size.value])
        sz = [SIZES[xx] for xx in groups.codes]

    c = "#31AADE"
    if color.value != 'None':
        if len(set(df[color.value])) > N_COLORS:
            groups = pd.qcut(df[color.value].values, N_COLORS, duplicates='drop')
        else:
            groups = pd.Categorical(df[color.value])
        c = [COLORS[xx] for xx in groups.codes]

    p.circle(x=xs, y=ys, color=c, size=sz, line_color="white", alpha=0.6, hover_color='white', hover_alpha=0.5)

    return p


def update(attr, old, new):
    layout.children[1] = create_figure()


x = Select(title='X-Axis', value='mpg', options=columns)
x.on_change('value', update)

y = Select(title='Y-Axis', value='hp', options=columns)
y.on_change('value', update)

size = Select(title='Size', value='None', options=['None'] + continuous)
size.on_change('value', update)

color = Select(title='Color', value='None', options=['None'] + continuous)
color.on_change('value', update)

controls = column(x, y, color, size, width=200)
layout = row(controls, create_figure())

curdoc().add_root(layout)
curdoc().title = "Crossfilter"
Python

The code included above defines a couple functions and creates several objects. Some parts of the create_figure function should look familiar to you:

  • On line 48, a Bokeh plot is created using the figure function, along with a set of attached tools, and stored in the variable p
  • On line 71, a group of circle glyphs are plotted on the figure, to produce a scatter plot
  • Most of the rest of the function is devoted to customizing the look and feel of the plot

In our previous plots using the same autompg data, we hardwired into the calls to p.circle the names of the specific data fields (columns in the ColumnDataSource) that we wanted to plot. In the code above (line 71), we see that the x and y variables of the plotted data (xs and ys) are themselves extracted from the autompg dataframe on lines 36-37.

In this server application, however, which specific columns in the dataframe get plotted (lines 36-37) are specified by the current values of the x and y Select buttons (which are initialized on lines 80 and 83, respectively). Select is a constructor in the bokeh.models API that creates a widget providing the ability to select from a set of choices. The set of available choices is specified in the options parameter, and the default value for the selection can be passed in as well.

Importantly, each of the Select buttons is provided the name of a callback function to execute when the selected value in the widget is changed by user interaction. For example, the x Selector (x-value of the scatter plot) is assigned a callback function on line 81:

x.on_change('value', update)
Python

This indicates that when the value of the x Selector is changed, the callback function named update is called. That function is defined on lines 76-77. Examining that code, one can see that the main action of the update callback is to call the create_figure function that we examined previously.

Examining the subsequent code (lines 80-90), we can see that all four Select widgets (to change what is plotted on the x and y axis, along with which data fields are used to configure the size and color of the glyphs in the scatter plot) are assigned the same callback function. Whenever any one of those four widgets is changed, the figure is updated using that new information. The associated values for x, y, size, and color are all extracted within the create_figure function, in order to configure the plot according to the chosen fields.

This is a rather simple example where every widget has the same callback, namely to trigger an update to a plot. In more complicated examples with multi-faceted user interfaces, different widgets and callbacks might serve different purposes. Some callbacks might be used to change the values of parameters which are used within data processing functions to modify the data that are plotted. It might be the case that all UI interactions eventually lead to a replotting of one or more figures, but that might be filtered through a series of function calls that is initiated with a specific callback.

 
© 2025  |   Cornell University    |   Center for Advanced Computing    |   Copyright Statement    |   Access Statement