Interactions¶
[1]:
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
output_notebook()
Linked interactions¶
It is possible to link different bokeh plots interactively. For example, two or more plots can be linked, whereby one of the plots is moved, zoomed or similar and the other plots are updated at the same time. It is also possible to link selections between two plots so that when elements are selected in one plot, the corresponding elements are also selected in the second plot.
Linked moving¶
Linked moving (when several plots have areas that remain synchronised) is easy to implement with Bokeh. You simply split the corresponding area objects between two (or more) plots. The following example shows how to achieve this by linking the areas of the three plots in different ways:
[2]:
from bokeh.layouts import gridplot
x = list(range(11))
y0, y1, y2 = x, [10 - i for i in x], [abs(i - 5) for i in x]
plot_options = dict(width=250, height=250, tools="pan,wheel_zoom")
# create a new plot
s1 = figure(**plot_options)
s1.circle(x, y0, size=10, color="navy")
# create a new plot and share both ranges
s2 = figure(x_range=s1.x_range, y_range=s1.y_range, **plot_options)
s2.triangle(x, y1, size=10, color="firebrick")
# create a new plot and share only one range
s3 = figure(x_range=s1.x_range, **plot_options)
s3.square(x, y2, size=10, color="olive")
p = gridplot([[s1, s2, s3]])
# show the results
show(p)
Linked selection¶
Linking selections is done in a similar way by sharing data sources between plots. Note that normally with bokeh.plotting
and bokeh.charts
, the creation of a default data source for simple plots is automatic. However, to share a data source, we need to create it manually and pass it explicitly. This is illustrated in the following example:
[3]:
from bokeh.models import ColumnDataSource
x = list(range(-20, 21))
y0, y1 = [abs(xx) for xx in x], [xx**2 for xx in x]
# create a column data source for the plots to share
source = ColumnDataSource(data=dict(x=x, y0=y0, y1=y1))
TOOLS = "box_select,lasso_select,help"
# create a new plot and add a renderer
left = figure(tools=TOOLS, width=300, height=300)
left.circle("x", "y0", source=source)
# create another new plot and add a renderer
right = figure(tools=TOOLS, width=300, height=300)
right.circle("x", "y1", source=source)
p = gridplot([[left, right]])
show(p)
Hover Tool¶
Bokeh has a hover tool that can be used to display additional information in a popup when the mouse pointer hovers over a particular glyph. The basic configuration of the hover tool means that a list of (NAME, FORMAT)
tuples is provided. The full details can be found in the User’s Guide.
The following example shows some basic uses of the hover tool with a circle glyph, using the hover information defined in utils.py
:
[4]:
from bokeh.models import HoverTool
source = ColumnDataSource(
data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
desc=["A", "b", "C", "d", "E"],
)
)
hover = HoverTool(
tooltips=[
("index", "$index"),
("(x,y)", "($x, $y)"),
("desc", "@desc"),
]
)
p = figure(
width=300, height=300, tools=[hover], title="Mouse over the dots"
)
p.circle("x", "y", size=20, source=source)
show(p)
Widgets¶
Bokeh supports direct integration with a small basic widget set. This can be used in conjunction with a Bokeh server or with CustomJS
models to make your documents more interactive. A full list with sample code can be found in the Adding Widgets section of the User Guide.
To use the widgets, add them to a layout like a plot object:
[5]:
from bokeh.models import Column
from bokeh.models.widgets import Slider
slider = Slider(start=0, end=10, value=1, step=0.1, title="foo")
show(Column(slider))
CustomJS Callbacks¶
For a widget to be useful, it must be able to execute actions. With the help of the Bokeh server, widgets can execute real Python code. This option is explained under Bokeh server. Here we look at how widgets can be configured with CustomJS callbacks that execute JavaScript code snippets.
[6]:
from bokeh.models import ColumnDataSource, CustomJS, TapTool
callback = CustomJS(code="alert('you tapped a circle!')")
tap = TapTool(callback=callback)
p = figure(width=600, height=300, tools=[tap])
p.circle(x=[1, 2, 3, 4, 5], y=[2, 5, 8, 2, 7], size=20)
show(p)
Example of a slider widget¶
The following example shows an action attached to a slider that is used to update a data source.
[7]:
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS, Slider
x = [x * 0.005 for x in range(0, 201)]
source = ColumnDataSource(data=dict(x=x, y=x))
plot = figure(width=400, height=400)
plot.line("x", "y", source=source, line_width=3, line_alpha=0.6)
slider = Slider(start=0.1, end=6, value=1, step=0.1, title="power")
update_curve = CustomJS(
args=dict(source=source, slider=slider),
code="""
var data = source.data;
var f = slider.value;
x = data['x']
y = data['y']
for (i = 0; i < x.length; i++) {
y[i] = Math.pow(x[i], f)
}
// necessary because we mutated source.data in-place
source.change.emit();
""",
)
slider.js_on_change("value", update_curve)
show(column(slider, plot))
Example of a data selection¶
It is also possible to execute JavaScript actions when a user selection (for example, box, dot, lasso) changes. This is done by attaching the same type of CustomJS object to the data source for which the selection is made.
The following example is slightly more complex and shows the updating of a glyph’s data source in response to the selection of another glyph:
[8]:
from random import random
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.plotting import figure, output_file, show
x = [random() for x in range(500)]
y = [random() for y in range(500)]
s1 = ColumnDataSource(data=dict(x=x, y=y))
p1 = figure(width=400, height=400, tools="lasso_select", title="Select Here")
p1.circle("x", "y", source=s1, alpha=0.6)
s2 = ColumnDataSource(data=dict(x=[], y=[]))
p2 = figure(
width=400,
height=400,
x_range=(0, 1),
y_range=(0, 1),
tools="",
title="Watch Here",
)
p2.circle("x", "y", source=s2, alpha=0.6)
s1.selected.js_on_change(
"indices",
CustomJS(
args=dict(s1=s1, s2=s2),
code="""
const inds = cb_obj.indices;
const d1 = s1.data;
const d2 = s2.data;
d2['x'] = []
d2['y'] = []
for (let i = 0; i < inds.length; i++) {
d2['x'].push(d1['x'][inds[i]])
d2['y'].push(d1['y'][inds[i]])
}
s2.change.emit();
""",
),
)
layout = row(p1, p2)
show(layout)
CustomJS for UI events¶
All available UI events and their properties are listed in the bokeh.events section.
[9]:
import numpy as np
from bokeh import events
from bokeh.layouts import column, row
from bokeh.models import Button, CustomJS, Div
from bokeh.plotting import figure
x = np.random.random(size=2000) * 100
y = np.random.random(size=2000) * 100
p = figure(tools="box_select")
p.scatter(x, y, radius=1, fill_alpha=0.6, line_color=None)
div = Div(width=400)
button = Button(label="Button")
layout = column(button, row(p, div))
# Events with no attributes
button.js_on_event(
events.ButtonClick,
CustomJS(
args=dict(div=div),
code="""
div.text = "Button!";
""",
),
)
p.js_on_event(
events.SelectionGeometry,
CustomJS(
args=dict(div=div),
code="""
div.text = "Selection! <p> <p>" + JSON.stringify(cb_obj.geometry, undefined, 2);
""",
),
)
show(layout)
Additional information¶
There are many types of interactions and events that can be associated with CustomJS callbacks:
Widgets: - Button, Toggle, Dropdown, TextInput, AutocompleteInput, Select, Multiselect, Slider, (DateRangeSlider), DatePicker,
Tools: - TapTool, BoxSelectTool, HoverTool,
Selection: - ColumnDataSource, AjaxDataSource, BlazeDataSource, ServerDataSource
Ranges: - Range1d, DataRange1d, FactorRange
You can find more complete examples in JavaScript callbacks.