Linked behavior#

It’s often useful to link plots to add connected interactivity between plots. This section shows an easy way to do this, using the bokeh.plotting interface.

Linked panning#

It’s often desired to link pan or zooming actions across many plots. All that is needed to enable this feature is to share range objects between figure() calls.

from bokeh.layouts import gridplot
from bokeh.plotting import figure, show

x = list(range(21))
y0 = x
y1 = [20-xx for xx in x]
y2 = [abs(xx-10) for xx in x]

# create a new plot
s1 = figure(width=250, height=250, title=None)
s1.circle(x, y0, size=10, color="navy", alpha=0.5)

# create a new plot and share both ranges
s2 = figure(width=250, height=250, x_range=s1.x_range, y_range=s1.y_range, title=None)
s2.triangle(x, y1, size=10, color="firebrick", alpha=0.5)

# create a new plot and share only one range
s3 = figure(width=250, height=250, x_range=s1.x_range, title=None)
s3.square(x, y2, size=10, color="olive", alpha=0.5)

p = gridplot([[s1, s2, s3]], toolbar_location=None)

show(p)

Now you have learned how to link panning between multiple plots with the bokeh.plotting interface.

A more sophisicated example of a linked scatterplot matric can be found in the SPLOM section of the Statistical plots chapter.

Linked brushing#

Linked brushing in Bokeh is expressed by sharing data sources between glyph renderers. This is all Bokeh needs to understand that selections acted on one glyph must pass to all other glyphs that share that same source. To see how linked selection extends to glyph renderers that plot only a subset of data from a data source, see Linked selection with filtered data.

The following code shows an example of linked brushing between circle glyphs on two different figure() calls:

from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.sampledata.penguins import data
from bokeh.transform import factor_cmap

SPECIES = sorted(data.species.unique())

TOOLS = "box_select,lasso_select,help"

source = ColumnDataSource(data)

left = figure(width=300, height=400, title=None, tools=TOOLS,
              background_fill_color="#fafafa")
left.scatter("bill_length_mm", "body_mass_g", source=source,
             color=factor_cmap('species', 'Category10_3', SPECIES))

right = figure(width=300, height=400, title=None, tools=TOOLS,
               background_fill_color="#fafafa", y_axis_location="right")
right.scatter("bill_depth_mm", "body_mass_g", source=source,
              color=factor_cmap('species', 'Category10_3', SPECIES))

show(gridplot([[left, right]]))

A more sophisticated example below demonstrates linked selection between a DataTable widget and a scatter plot:

from bokeh.layouts import column
from bokeh.models import (ColumnDataSource, DataTable, HoverTool, IntEditor,
                          NumberEditor, NumberFormatter, SelectEditor,
                          StringEditor, StringFormatter, TableColumn)
from bokeh.plotting import figure, show
from bokeh.sampledata.autompg2 import autompg2 as mpg

source = ColumnDataSource(mpg)

manufacturers = sorted(mpg["manufacturer"].unique())
models = sorted(mpg["model"].unique())
transmissions = sorted(mpg["trans"].unique())
drives = sorted(mpg["drv"].unique())
classes = sorted(mpg["class"].unique())

columns = [
    TableColumn(field="manufacturer", title="Manufacturer",
                editor=SelectEditor(options=manufacturers),
                formatter=StringFormatter(font_style="bold")),
    TableColumn(field="model", title="Model",
                editor=StringEditor(completions=models)),
    TableColumn(field="displ", title="Displacement",
                editor=NumberEditor(step=0.1), formatter=NumberFormatter(format="0.0")),
    TableColumn(field="year", title="Year", editor=IntEditor()),
    TableColumn(field="cyl", title="Cylinders", editor=IntEditor()),
    TableColumn(field="trans", title="Transmission",
                editor=SelectEditor(options=transmissions)),
    TableColumn(field="drv", title="Drive", editor=SelectEditor(options=drives)),
    TableColumn(field="class", title="Class", editor=SelectEditor(options=classes)),
    TableColumn(field="cty", title="City MPG", editor=IntEditor()),
    TableColumn(field="hwy", title="Highway MPG", editor=IntEditor()),
]
data_table = DataTable(source=source, columns=columns, editable=True, width=800,
                       index_position=-1, index_header="row index", index_width=60)

p = figure(width=800, height=300, tools="pan,wheel_zoom,xbox_select,reset", active_drag="xbox_select")

cty = p.circle(x="index", y="cty", fill_color="#396285", size=8, alpha=0.5, source=source)
hwy = p.circle(x="index", y="hwy", fill_color="#CE603D", size=8, alpha=0.5, source=source)

tooltips = [
    ("Manufacturer", "@manufacturer"),
    ("Model", "@model"),
    ("Displacement", "@displ"),
    ("Year", "@year"),
    ("Cylinders", "@cyl"),
    ("Transmission", "@trans"),
    ("Drive", "@drv"),
    ("Class", "@class"),
]
cty_hover_tool = HoverTool(renderers=[cty], tooltips=tooltips + [("City MPG", "@cty")])
hwy_hover_tool = HoverTool(renderers=[hwy], tooltips=tooltips + [("Highway MPG", "@hwy")])

p.add_tools(cty_hover_tool, hwy_hover_tool)

show(column(p, data_table))

Linked crosshair#

Linking crosshair tools between plots is another technique that can help make comparisons across different plots easier. In Bokeh, crosshair tools may be configured with shared Span instances for their overlays, which will cause those crosshairs to be linked together. This is demonstrated below:

from random import random

from bokeh.layouts import row
from bokeh.models import CrosshairTool, Span
from bokeh.plotting import figure, show

x = [random() * 10 for _ in range(200)]
y = [random() * 10 for _ in range(200)]

width = Span(dimension="width", line_dash="dashed", line_width=2)
height = Span(dimension="height", line_dash="dotted", line_width=2)

p1 = figure(height=400, width=400, x_range=(0, 10), y_range=(0, 10),
            tools="hover", toolbar_location=None)
p1.add_tools(CrosshairTool(overlay=[width, height]))
p1.circle(x, y, radius=0.2, alpha=0.3, hover_alpha=1.0)

p2 = figure(height=400, width=250, x_range=(0, 10), y_range=(0, 10),
            tools="hover", toolbar_location=None)
p2.add_tools(CrosshairTool(overlay=[width, height]))
p2.circle(x, y, radius=0.2, alpha=0.3, hover_alpha=1.0)

show(row(p1, p2))

Linked properties#

It is also possible to link values of Bokeh model properties together so that they remain synchronized, using the js_link() method. The example below links a circle glyph radius to the value of a Slider widget:

from bokeh.layouts import column
from bokeh.models import Slider
from bokeh.plotting import figure, show

plot = figure(width=400, height=400)
r = plot.circle([1,2,3,4,5,], [3,2,5,6,4], radius=0.2, alpha=0.5)

slider = Slider(start=0.1, end=2, step=0.01, value=0.2)
slider.js_link('value', r.glyph, 'radius')

show(column(plot, slider))

The linking is accomplished in JavaScript, so this method works in standalone Bokeh documents, or in Bokeh server applications.

See Widgets and DOM elements for more information about different widgets, and JavaScript callbacks for more information about creating arbitrary JavaScript callbacks.