Annotations (original) (raw)
Bokeh includes several different types of annotations you can use to add supplemental information to your visualizations.
Titles#
Use Title
annotations to add descriptive text which is rendered around the edges of a plot.
If you use the bokeh.plotting interface, the quickest way to add a basic title is to pass the text as the title
parameter to figure():
from bokeh.plotting import figure, show
p = figure(title="Basic Title", width=300, height=300)
p.scatter([1, 2], [3, 4])
show(p)
The default title is generally located above a plot, aligned to the left.
The title text may value contain newline characters which will result in a multi-line title.
p = figure(title="A longer title\nwith a second line underneath")
To define the placement of the title in relation to the plot, use thetitle_location
parameter. A title can be located above, below, left, or right of a plot. For example:
from bokeh.plotting import figure, show
p = figure(title="Left Title", title_location="left", width=300, height=300)
p.scatter([1, 2], [3, 4])
show(p)
Use your plot’s .title
property to customize the default Title
. Use the standard text properties to define visual properties such as font, border, and background.
This example uses the .title
property to set the font and background properties as well as the title text and title alignment:
from bokeh.plotting import figure, show
p = figure(width=300, height=300)
p.scatter([1, 2], [3, 4])
configure visual properties on a plot's title attribute
p.title.text = "Title With Options" p.title.align = "right" p.title.text_color = "orange" p.title.text_font_size = "25px" p.title.background_fill_color = "#aaaaee"
show(p)
Note that the align
property is relative to the direction of the text. For example: If you have placed your title on the left side of your plot, setting the align
property to "left"
means your text is rendered in the lower left corner.
To add more titles to your document, you need to create additional Title
objects. Use the add_layout()
method of your plot to include those additionalTitle
objects in your document:
from bokeh.models import Title from bokeh.plotting import figure, show
p = figure(title="Left Title", title_location="left", width=300, height=300)
p.scatter([1, 2], [3, 4])
add extra titles with add_layout(...)
p.add_layout(Title(text="Bottom Centered Title", align="center"), "below")
show(p)
If a title and a toolbar are placed on the same side of a plot, they will occupy the same space:
from bokeh.plotting import figure, show
p = figure(title="Top Title with Toolbar", toolbar_location="above", width=600, height=300)
p.scatter([1, 2], [3, 4])
show(p)
If the plot size is large enough, this can result in a more compact plot. However, if the plot size is not large enough, the title and toolbar may visually overlap.
Color bars#
To create a ColorBar
, you can pass an instance of ColorMapper containing a color palette, for example:
color_bar = ColorBar(color_mapper=color_mapper, padding=5)
However, for many glyphs, you can call construct_color_bar
on the renderer returned by the glyph method to create a color bar automatically, if the glyph already has a color mapping configured:
color_bar = r.construct_color_bar(padding=5)
Color bars can be located inside as well as left, right, below, or above the plot. Specify the location of a color bar when adding the ColorBar
object to the plot using the add_layout()
method.
import numpy as np
from bokeh.models import LogColorMapper from bokeh.plotting import figure, show
def normal2d(X, Y, sigx=1.0, sigy=1.0, mux=0.0, muy=0.0): z = (X-mux)2 / sigx2 + (Y-muy)2 / sigy2 return np.exp(-z/2) / (2 * np.pi * sigx * sigy)
X, Y = np.mgrid[-3:3:200j, -2:2:200j] Z = normal2d(X, Y, 0.1, 0.2, 1.0, 1.0) + 0.1*normal2d(X, Y, 1.0, 1.0) image = Z * 1e6
color_mapper = LogColorMapper(palette="Viridis256", low=1, high=1e7)
plot = figure(x_range=(0,1), y_range=(0,1), toolbar_location=None) r = plot.image(image=[image], color_mapper=color_mapper, dh=1.0, dw=1.0, x=0, y=0)
color_bar = r.construct_color_bar(padding=1)
plot.add_layout(color_bar, "right")
show(plot)
Scale bars#
ScaleBar
is a visual indicator that allows to gauge the size of features on a plot. This is particularly useful with maps or images like CT or MRI scans, and in situations where using axes would be otherwise inappropriate or too verbose.
To create a ScaleBar
the user needs at least to provide a Range, either an explicit or an implicit one.
from bokeh.models import Range1d, ScaleBar
scale_bar = ScaleBar(range=Range1d(start=0, end=1000)) plot.add_layout(scale_bar)
The range can also be shared with a plot:
from bokeh.models import Range1d, ScaleBar
scale_bar = ScaleBar(range=plot.y_range) plot.add_layout(scale_bar)
The default range for a ScaleBar
is "auto"
, which uses the default x or y range of a plot the scale bar is associated with, depending on the orientation of the scale bar (Plot.x_range
for "horizontal"
and Plot.y_range
for"vertical"
orientation).
Additionally the user can provide units of measurement (ScaleBar.dimensional
, which takes an instance of Dimensional
model) and the unit of the data range (ScaleBar.unit
). The default units of measurement is metric length represented by MetricLength
model and the default unit of data range is meter ("m"
), the same as the base unit of the default measurement system.
If the unit of the data range is different from the base unit, then the user can indicate this by changing ScaleBar.unit
appropriately. For example, if the range is in kilometers, then the user would indicate this with:
from bokeh.models import ScaleBar, Metric
scale_bar = ScaleBar( range=plot.y_range, unit="km", ) plot.add_layout(scale_bar)
Other units of measurement can be provided by configuring ScaleBar.dimensional
property. This can be other predefined units of measurement like imperial length (ImperialLength
) or angular units (Angular
). The user can also define custom units of measurement. To define custom metric units, for example for a plot involving electron volts (eV), the user would use Metric(base_unit="eV")
as the basis:
from bokeh.models import ScaleBar, Metric
scale_bar = ScaleBar( range=plot.y_range, unit="MeV", dimensional=Metric(base_unit="eV"), ) plot.add_layout(scale_bar)
Non-metric units of measurement can be constructed with CustomDimensional
model. For example, angular units of measurements can be defined as follows:
from bokeh.models import ScaleBar from bokeh.models.annotations.dimensional import CustomDimensional
units = CustomDimensional( basis={ "°": (1, "^\circ", "degree"), "'": (1/60, "^\prime", "minute"), "''": (1/3600, "^{\prime\prime}", "second"), } ticks=[1, 3, 6, 12, 60, 120, 240, 360] )
scale_bar = ScaleBar( range=plot.y_range, unit="''", dimensional=units, ) plot.add_layout(scale_bar)
A complete example of a scale bar with custom units of measurement:
import numpy as np
from bokeh.layouts import column from bokeh.models import ColumnDataSource, Metric, RangeTool, ScaleBar from bokeh.plotting import figure, show
n_points = 3000 x_values = np.linspace(0, 100, n_points) y_values = np.random.randn(n_points).cumsum()
source = ColumnDataSource(data=dict(x=x_values, y=y_values))
detailed_plot = figure( width=800, height=300, tools=["xpan", "xzoom_in", "xzoom_out", "reset", "wheel_zoom"], toolbar_location="above", active_scroll="wheel_zoom", background_fill_color="#efefef", x_range=(22, 30), y_axis_location=None, )
detailed_plot.line("x", "y", source=source)
scale_bar = ScaleBar( range=detailed_plot.y_range, unit="MeV", dimensional=Metric(base_unit="eV"), orientation="vertical", location="top_left", background_fill_color=None, border_line_color=None, ) detailed_plot.add_layout(scale_bar)
select_plot = figure( width=detailed_plot.width, height=150, y_range=detailed_plot.y_range, y_axis_location=None, tools="", toolbar_location=None, background_fill_color=detailed_plot.background_fill_color, )
select_plot.line("x", "y", source=source) select_plot.x_range.range_padding = 0 select_plot.ygrid.grid_line_color = None
range_tool = RangeTool(x_range=detailed_plot.x_range) range_tool.overlay.fill_color = "navy" range_tool.overlay.fill_alpha = 0.2 select_plot.add_tools(range_tool)
show(column(detailed_plot, select_plot))
Arrows#
You can use Arrow
annotations to connect glyphs and label annotations. Arrows can also help highlight plot regions.
Arrows are compound annotations. This means that they use additional ArrowHead
objects as their start
and end
. By default, the Arrow
annotation is a one-sided arrow: The end
property is set to an OpenHead
-type arrowhead (looking like an open-backed wedge style) and the start
property is set toNone
. If you want to create double-sided arrows, set both the start
andend
properties to one of the available arrowheads.
The available arrowheads are:
Control the appearance of an arrowhead with these properties:
- use the
size
property to control the size of any arrowheads - use the standard line properties such as
line_color
andline_alpha
to control the appearance of the outline of the arrowhead. - use
fill_color
andfill_alpha
to control the appearance of the arrowhead’s inner surface, if applicable.
Arrow
objects themselves have the standard line properties. Set those properties to control the color and appearance of the arrow shaft. For example:
my_arrow.line_color = "blue" my_arrow.line_alpha = 0.6
Optionally, you can set the x_range
and y_range
properties to make an arrow annotation refer to additional non-default x- or y-ranges. This works the same as Twin axes.
from bokeh.models import Arrow, NormalHead, OpenHead, VeeHead from bokeh.palettes import Muted3 as color from bokeh.plotting import figure, show
p = figure(tools="", toolbar_location=None, background_fill_color="#efefef") p.grid.grid_line_color = None
p.circle(x=(0, 1, 0.5), y=(0, 0, 0.7), radius=0.1, color="#fafafa")
vh = VeeHead(size=35, fill_color=color[0]) p.add_layout(Arrow(end=vh, x_start=0.5, y_start=0.7, x_end=0, y_end=0))
nh = NormalHead(fill_color=color[1], fill_alpha=0.5, line_color=color[1]) p.add_layout(Arrow(end=nh, line_color=color[1], line_dash=[15, 5], x_start=1, y_start=0, x_end=0.5, y_end=0.7))
oh = OpenHead(line_color=color[2], line_width=5) p.add_layout(Arrow(end=oh, line_color=color[2], line_width=5, x_start=0, y_start=0, x_end=1, y_end=0))
show(p)
Bands#
A Band
annotation is a colored stripe that is dimensionally linked to the data in a plot. One common use for the band annotation is to indicate uncertainty related to a series of measurements.
To define a band, use either screen units or data units.
import numpy as np import pandas as pd
from bokeh.models import Band, ColumnDataSource from bokeh.plotting import figure, show
Create some random data
x = np.random.random(2500) * 140 +20 y = np.random.normal(size=2500) * 2 + 6 * np.log(x)
df = pd.DataFrame(data=dict(x=x, y=y)).sort_values(by="x")
df2 = df.y.rolling(window=300).agg({"y_mean": "mean", "y_std": "std"})
df = pd.concat([df, df2], axis=1) df["lower"] = df.y_mean - df.y_std df["upper"] = df.y_mean + df.y_std
source = ColumnDataSource(df.reset_index())
p = figure(tools="", toolbar_location=None, x_range=(40, 160)) p.title.text = "Rolling Standard Deviation" p.xgrid.grid_line_color=None p.ygrid.grid_line_alpha=0.5
p.scatter(x="x", y="y", color="blue", marker="dot", size=10, alpha=0.4, source=source)
p.line("x", "y_mean", line_dash=(10, 7), line_width=2, source=source)
band = Band(base="x", lower="lower", upper="upper", source=source, fill_alpha=0.3, fill_color="yellow", line_color="black") p.add_layout(band)
show(p)
Box annotations#
A BoxAnnotation
is a rectangular box that you can use to highlight specific plot regions. Use either screen units or data units to position a box annotation.
To define the bounds of these boxes, use the left
/right
or top
/bottom
properties. If you provide only one bound (for example, a left
value but no right
value), the box will extend to the edge of the available plot area for the dimension you did not specify.
from bokeh.models import BoxAnnotation from bokeh.plotting import figure, show from bokeh.sampledata.glucose import data
TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
#reduce data size data = data.loc['2010-10-06':'2010-10-13'].reset_index()
p = figure(x_axis_type="datetime", tools=TOOLS)
p.line("datetime", "glucose", source=data, color="gray", legend_label="glucose")
low_box = BoxAnnotation(top=80, fill_alpha=0.2, fill_color='#D55E00') mid_box = BoxAnnotation(bottom=80, top=180, fill_alpha=0.2, fill_color='#0072B2') high_box = BoxAnnotation(bottom=180, fill_alpha=0.2, fill_color='#D55E00')
p.add_layout(low_box) p.add_layout(mid_box) p.add_layout(high_box)
p.title.text = "Glucose Range" p.xgrid.grid_line_color=None p.ygrid.grid_line_alpha=0.5 p.xaxis.axis_label = 'Time' p.yaxis.axis_label = 'Value' p.legend.level = "overlay" p.legend.location = "top_left"
show(p)
Polygon annotations#
A PolyAnnotation
is a polygon with vertices in either screen units ordata units.
To define the polygon’s vertices, supply a series of coordinates to thexs
and ys
properties. Bokeh automatically connects the last vertex to the first to create a closed shape.
from datetime import datetime as dt
import pandas as pd
from bokeh.models import PolyAnnotation from bokeh.plotting import figure, show from bokeh.sampledata.stocks import GOOG
p = figure(height=200, x_axis_type="datetime", background_fill_color="#efefef", title="Google stock")
df = pd.DataFrame(GOOG) df["date"] = pd.to_datetime(df["date"])
p.line(df["date"], df["close"], line_width=1.5, color="grey")
start_date = dt(2008, 11, 24) start_y = df.loc[df["date"] == start_date]["close"].values[0]
end_date = dt(2010, 1, 4) end_y = df.loc[df["date"] == end_date]["close"].values[0]
polygon = PolyAnnotation( fill_color="blue", fill_alpha=0.2, xs=[start_date, start_date, end_date, end_date], ys=[start_y - 100, start_y + 100, end_y + 100, end_y - 100], ) p.add_layout(polygon)
show(p)
Labels#
Labels are rectangular boxes with additional information about glyphs or plot regions.
To create a single text label, use the Label
annotation. Those are the most important properties for this annotation:
- A
text
property containing the text to display inside the label. x
andy
properties to set the position (in screen units ordata units).x_offset
andy_offset
properties to specify where to place the label in relation to itsx
andy
coordinates.- The standard text properties as well as other styling parameters such as
border_line
andbackground_fill
properties.
Label(x=100, y=5, x_units='screen', text='Some Stuff', border_line_color='black', border_line_alpha=1.0, background_fill_color='white', background_fill_alpha=1.0)
The text
may value contain newline characters which will result in a multi-line label.
Label(x=100, y=5, text='A very long label\nwith multiple lines')
To create several labels at once, use the LabelSet
annotation. To configure the labels of a label set, use a data source that contains columns with data for the labels’ properties such as text
, x
and y
. If you assign a value to a property such as x_offset
and y_offset
directly instead of a column name, this value is used for all labels of the label set.
LabelSet(x='x', y='y', text='names', x_offset=5, y_offset=5, source=source)
The following example illustrates the use of Label
and LabelSet
:
from bokeh.models import ColumnDataSource, Label, Node from bokeh.plotting import figure, show
source = ColumnDataSource(data=dict( height=[66, 71, 72, 68, 58, 62], weight=[165, 189, 220, 141, 260, 174], names=["Mark", "Amir", "Matt", "Greg", "Owen", "Juan"], ))
p = figure(title="Dist. of 10th Grade Students", x_range=(140, 275)) p.xaxis.axis_label = "Weight (lbs)" p.yaxis.axis_label = "Height (in)"
p.scatter(x="weight", y="height", size=8, source=source)
p.text(x="weight", y="height", text="names", x_offset=5, y_offset=5, anchor="bottom_left", source=source)
frame_left = Node(target="frame", symbol="left", offset=5) frame_bottom = Node(target="frame", symbol="bottom", offset=-5)
citation = Label( x=frame_left, y=frame_bottom, anchor="bottom_left", text="Collected by Luke C. 2016-04-01", padding=10, border_radius=5, border_line_color="black", background_fill_color="white", )
p.add_layout(citation)
show(p)
The text
values for LabelSet
may value contain newline characters which will result in multi-line labels.
Slopes#
Slope
annotations are lines that can go from one edge of the plot to another at a specific angle.
These are the most commonly used properties for this annotation:
gradient
: The gradient of the line, in data units.y_intercept
: The y intercept of the line, in data units.- The standard line properties.
import numpy as np
from bokeh.models import Slope from bokeh.palettes import Sunset10 from bokeh.plotting import figure, show
linear equation parameters
slope, intercept = 2, 10
x = np.arange(0, 20, 0.2) y = slope * x + intercept + np.random.normal(0, 4, 100)
blue, yellow = Sunset10[0], Sunset10[5]
p = figure(width=600, height=600, x_axis_label='x', y_axis_label='y', background_fill_color="#fafafa") p.y_range.start = 0
p.scatter(x, y, size=8, alpha=0.8, fill_color=yellow, line_color="black")
slope = Slope(gradient=slope, y_intercept=intercept, line_color=blue, line_dash='dashed', line_width=4)
p.add_layout(slope)
show(p)
Spans#
Span
annotations are lines that are orthogonal to the x or y axis of a plot. They have a single dimension (width or height) and go from one edge of the plot area to the opposite edge.
These are the most commonly used properties for this annotation:
dimension
: The direction of the span line. The direction can be one of these two values: Either *"height"
for a line that is parallel to the plot’s x axis. Or"width"
for a line that is parallel to the plot’s y axis.location
: The location of the span along the axis specified withdimension
.location_units
: The unit type for thelocation
property. The default is to use data units.- The standard line properties.
from datetime import datetime as dt
from bokeh.models import Span from bokeh.plotting import figure, show from bokeh.sampledata.daylight import daylight_warsaw_2013
p = figure(height=350, x_axis_type="datetime", y_axis_type="datetime", title="2013 Sunrise and Sunset in Warsaw with DST dates marked", y_axis_label="Time of Day", background_fill_color="#fafafa") p.y_range.start = 0 p.y_range.end = 24 * 60 * 60 * 1000
p.line("Date", "Sunset", source=daylight_warsaw_2013, color='navy', line_dash="dotted", line_width=2, legend_label="Sunset") p.line("Date", "Sunrise", source=daylight_warsaw_2013, color='orange', line_dash="dashed", line_width=2, legend_label="Sunrise")
dst_start = Span(location=dt(2013, 3, 31, 2, 0, 0), dimension='height', line_color='#009E73', line_width=5) p.add_layout(dst_start)
dst_end = Span(location=dt(2013, 10, 27, 3, 0, 0), dimension='height', line_color='#009E73', line_width=5) p.add_layout(dst_end)
p.yaxis.formatter.context = None p.xgrid.grid_line_color = None
show(p)
Whiskers#
A Whisker
annotation is a “stem” that is dimensionally linked to the data in the plot. You can define this annotation using data units or screen units.
A common use for whisker annotations is to indicate error margins or uncertainty for measurements at a single point.
These are the most commonly used properties for this annotation:
lower
: The coordinates of the lower end of the whisker.upper
: The coordinates of the upper end of the whisker.dimension
: The direction of the whisker. The direction can be one of these two values: Either *"width"
for whiskers that are parallel to the plot’s x axis. Or"height"
for whiskers that are parallel to the plot’s y axis.base
: The location of the whisker along the dimension specified withdimension
.- The standard line properties.
from bokeh.models import ColumnDataSource, Whisker from bokeh.plotting import figure, show from bokeh.sampledata.autompg2 import autompg2 as df from bokeh.transform import factor_cmap, jitter
classes = list(sorted(df["class"].unique()))
p = figure(height=400, x_range=classes, background_fill_color="#efefef", title="Car class vs HWY mpg with quantile ranges") p.xgrid.grid_line_color = None
g = df.groupby("class") upper = g.hwy.quantile(0.80) lower = g.hwy.quantile(0.20) source = ColumnDataSource(data=dict(base=classes, upper=upper, lower=lower))
error = Whisker(base="base", upper="upper", lower="lower", source=source, level="annotation", line_width=2) error.upper_head.size=20 error.lower_head.size=20 p.add_layout(error)
p.scatter(jitter("class", 0.3, range=p.x_range), "hwy", source=df, alpha=0.5, size=13, line_color="white", color=factor_cmap("class", "Light7", classes))
show(p)