Annotations¶
Overview¶
We can add visual cues (boundary lines, shaded areas, labels and arrows, etc.) to our plots to emphasise certain features. Bokeh offers several annotation types for this purpose. To add annotations, we usually create a low-level object and add it to our diagram using add_layout
. Let’s look at some concrete examples:
Spans¶
Spans are vertical or horizontal lines for which the spans between the dimensions can be specified, for example:
[1]:
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
output_notebook()
[2]:
import numpy as np
from bokeh.models.annotations import Span
x = np.linspace(0, 20, 200)
y = np.sin(x)
p = figure(y_range=(-2, 2))
p.line(x, y)
upper = Span(location=1, dimension="width", line_color="olive", line_width=4)
p.add_layout(upper)
lower = Span(
location=-1, dimension="width", line_color="firebrick", line_width=4
)
p.add_layout(lower)
show(p)
Box annotations¶
If an area of a diagram is to be highlighted, this can be done with BoxAnnotation
, the coordinate properties
top
left
bottom
right
as well as line and fill properties to configure the appearance.
Infinite boxes can be created by not specifying the coordinates. For example, if top
is not specified, the box is always displayed up to the top edge of the plot area, regardless of whether panning or zooming takes place, for example:
[3]:
import numpy as np
from bokeh.models.annotations import BoxAnnotation
x = np.linspace(0, 20, 200)
y = np.sin(x)
p = figure(y_range=(-2, 2))
p.line(x, y)
# region that always fills the top of the plot
upper = BoxAnnotation(bottom=1, fill_alpha=0.1, fill_color="olive")
p.add_layout(upper)
# region that always fills the bottom of the plot
lower = BoxAnnotation(top=-1, fill_alpha=0.1, fill_color="firebrick")
p.add_layout(lower)
# a finite region
center = BoxAnnotation(
top=0.6, bottom=-0.3, left=7, right=12, fill_alpha=0.1, fill_color="navy"
)
p.add_layout(center)
show(p)
Label¶
With the Label
annotation, we can easily attach individual labels to plots. The position to be displayed and the text are configured as x
, y
and text
:
Label(x=10, y=5, text="Some Label")
By default, the units are in the data space, but x_units
and y_units
can be set to screen
to position the label relative to the drawing area, and labels can be positioned with x_offset
and y_offset
.
Label
objects also have standard text, line (border_line
) and fill (background_fill
) properties. The line and fill properties apply to a bounding box around the text:
Label(x=10, y=5, text="Some Label", text_font_size="12pt",
border_line_color="red", background_fill_color="blue")
[4]:
from bokeh.models.annotations import Label
from bokeh.plotting import figure
p = figure(x_range=(0,10), y_range=(0,10))
p.circle([2, 5, 8], [4, 7, 6], color="olive", size=10)
label = Label(x=5, y=7, x_offset=12, text="Second Point", text_baseline="middle")
p.add_layout(label)
show(p)
LabelSet¶
With the LabelSet
annotation, you can create many labels at the same time, for example if you want to label a whole set of scatter markers. They are similar to Label
, but can also use a ColumnDataSource
as a source
property, where x
and y
refer to columns in the data source.
[5]:
from bokeh.models import ColumnDataSource, LabelSet
from bokeh.plotting import figure
source = ColumnDataSource(
data=dict(
temp=[166, 171, 172, 168, 174, 162],
pressure=[165, 189, 220, 141, 260, 174],
names=["A", "B", "C", "D", "E", "F"],
)
)
p = figure(x_range=(160, 175))
p.scatter(x="temp", y="pressure", size=8, source=source)
p.xaxis.axis_label = "Temperature (C)"
p.yaxis.axis_label = "Pressure (lbs)"
labels = LabelSet(
x="temp",
y="pressure",
text="names",
level="glyph",
x_offset=5,
y_offset=5,
source=source,
)
p.add_layout(labels)
show(p)
Arrows¶
You can use the Arrow
annotation to point to various elements in your drawing. This can be particularly helpful for labelling.
For example, you can create an arrow that points from (0,0)
to (1,1)
:
p.add_layout(Arrow(x_start=0, y_start=0, x_end=1, y_end=0))
This arrow has the standard OpenHead tip. Other types of arrowheads are NormalHead and VeeHead. The type of arrowhead can be configured using the start
and end
properties of Arrow
objects:
p.add_layout(Arrow(start=OpenHead(), end=VeeHead(),
x_start=0, y_start=0, x_end=1, y_end=0))
This creates a double arrow with OpenHead
and VeeHead
. Arrowheads also have the standard set of line and fill properties to configure their appearance, for example
OpenHead(line_color="firebrick", line_width=4)
The code and diagram below show several of these configurations together.
[6]:
from bokeh.models.annotations import Arrow
from bokeh.models.arrow_heads import NormalHead, OpenHead, VeeHead
p = figure(plot_width=600, plot_height=600)
p.circle(
x=[0, 1, 0.5],
y=[0, 0, 0.7],
radius=0.1,
color=["navy", "yellow", "red"],
fill_alpha=0.1,
)
p.add_layout(
Arrow(
end=OpenHead(line_color="firebrick", line_width=4),
x_start=0,
y_start=0,
x_end=1,
y_end=0,
)
)
p.add_layout(
Arrow(
end=NormalHead(fill_color="orange"),
x_start=1,
y_start=0,
x_end=0.5,
y_end=0.7,
)
)
p.add_layout(
Arrow(
end=VeeHead(size=35),
line_color="red",
x_start=0.5,
y_start=0.7,
x_end=0,
y_end=0,
)
)
show(p)
Legends¶
When diagrams contain multiple glyphs, it is desirable to add a legend to help viewers interpret them. Bokeh can easily generate legends based on the added glyphs.
Simple legends¶
In the simplest case, you can pass a string as a legend
to a glyph function:
p.circle(x, y, legend="sin(x)")
In this case, Bokeh automatically creates a legend that shows a representation of this glyph.
[7]:
import numpy as np
x = np.linspace(0, 4 * np.pi, 100)
y = np.sin(x)
p = figure(height=400)
p.circle(x, y, legend_label="sin(x)")
p.line(
x,
2 * y,
legend_label="2*sin(x)",
line_dash=[4, 4],
line_color="orange",
line_width=2,
)
show(p)
Composite legends¶
In the example above, we have provided a different label for each glyph method. Sometimes two (or more) different glyphs are used with a single data source. In this case, composite legends can be created. For example, if you plot a sin curve with a line and a marker, you can give them the same label to make them appear together in the legend:
p.circle(x, y, legend="sin(x)")
p.line(x, y, legend="sin(x)", line_dash=[4, 4], line_color="orange", line_width=2)
Colour bars¶
Colour bars are particularly useful when we vary the colour of a glyph according to the colour mapping. Bokeh colour bars are configured with a colour mapping and added to plots using the add_layout
method:
color_mapper = LinearColorMapper(palette="Viridis256", low=data_low, high=data_high)
color_bar = ColorBar(color_mapper=color_mapper, location=(0,0))
p.add_layout(color_bar, 'right')
In the following example, the colour mapping is also used to convert the glyph colour.
[8]:
from bokeh.models import ColorBar, LinearColorMapper
from bokeh.sampledata.autompg import autompg
from bokeh.transform import transform
source = ColumnDataSource(autompg)
color_mapper = LinearColorMapper(
palette="Viridis256", low=autompg.weight.min(), high=autompg.weight.max()
)
p = figure(
x_axis_label="Horsepower",
y_axis_label="MPG",
tools="",
toolbar_location=None,
)
p.circle(
x="hp",
y="mpg",
color=transform("weight", color_mapper),
size=20,
alpha=0.6,
source=autompg,
)
color_bar = ColorBar(
color_mapper=color_mapper,
label_standoff=12,
location=(0, 0),
title="Weight",
)
p.add_layout(color_bar, "right")
show(p)