Bokeh includes several different types of annotations you can use to add supplemental information to your visualizations.
Use Title annotations to add descriptive text which is rendered around the edges of a plot.
Title
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:
title
Figure
from bokeh.plotting import figure, output_file, show p = figure(title="Basic Title", plot_width=300, plot_height=300) p.circle([1,2], [3,4]) output_file("title.html") show(p)
The default title is generally located above a plot, aligned to the left.
To define the placement of the title in relation to the plot, use the title_location parameter. A title can be located above, below, left, or right of a plot. For example:
title_location
from bokeh.plotting import figure, output_file, show p = figure(title="Left Title", title_location="left", plot_width=300, plot_height=300) p.circle([1,2], [3,4]) output_file("title.html") 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.
.title
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, output_file, show p = figure(plot_width=300, plot_height=300) p.circle([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" output_file("title.html") 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.
align
"left"
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 additional Title objects in your document:
add_layout()
from bokeh.models import Title from bokeh.plotting import figure, output_file, show p = figure(title="Left Title", title_location="left", plot_width=300, plot_height=300) p.circle([1,2], [3,4]) # add extra titles with add_layout(...) p.add_layout(Title(text="Bottom Centered Title", align="center"), "below") output_file("title.html") 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, output_file, show p = figure(title="Top Title with Toolbar", toolbar_location="above", plot_width=600, plot_height=300) p.circle([1,2], [3,4]) output_file("title.html") 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.
The easiest way to add a legend to your plot is to include any of the legend_label, legend_group, or legend_field properties when calling glyph methods. Bokeh then creates a Legend object for you automatically.
Legend
For more advanced control over a plot’s legend, access the Legend object directly.
To provide a simple explicit label for a glyph, pass the legend_label keyword argument:
legend_label
p.circle('x', 'y', legend_label="some label")
If you assign the same label name to multiple glyphs, all the glyphs will be combined into a single legend item with that label.
import numpy as np from bokeh.plotting import figure, output_file, show x = np.linspace(0, 4*np.pi, 100) y = np.sin(x) output_file("legend.html") p = figure() p.circle(x, y, legend_label="sin(x)") p.line(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) p.square(x, 3*y, legend_label="3*sin(x)", fill_color=None, line_color="green") p.line(x, 3*y, legend_label="3*sin(x)", line_color="green") show(p)
If your data is in the form of a ColumnDataSource, Bokeh can generate legend entries from strings in one of the ColumnDataSource’s columns. This way, you can create legend entries based on groups of glyphs.
To use data from a column of a ColumnDataSource to generate your plot’s legend, pass the column name as the legend_group keyword argument to a glyph method:
legend_group
p.circle('x', 'y', legend_group="colname", source=source)
Because legend_group references a column of a ColumnDataSource, you need to always provide a source argument to the glyph method as well. Additionally, the column containing the label names has to be present in the data source at that point:
source
from bokeh.io import show from bokeh.models import ColumnDataSource from bokeh.palettes import RdBu3 from bokeh.plotting import figure c1 = RdBu3[2] # red c2 = RdBu3[0] # blue source = ColumnDataSource(dict( x=[1, 2, 3, 4, 5, 6], y=[2, 1, 2, 1, 2, 1], color=[c1, c2, c1, c2, c1, c2], label=['hi', 'lo', 'hi', 'lo', 'hi', 'lo'] )) p = figure(x_range=(0, 7), y_range=(0, 3), plot_height=300, tools='save') # legend field matches the column in the source p.circle( x='x', y='y', radius=0.5, color='color', legend_group='label', source=source) show(p)
Using legend_group means that Bokeh groups the legend entries immediately. Therefore, any subsequent Python code will be able to see the individual legend items in the Legend.items property. This way, you can re-arrange or modify the legend at any time.
Legend.items
You also have the option to only group elements within your legend on the JavaScript side, in the browser. Using browser-side grouping makes sense if you want to group a column that is only computed on the JavaScript side, for example.
p.circle('x', 'y', legend_field="colname", source=source)
In this case, the Python code does not see multiple items in Legend.items. Instead, there is only a single item that represents the grouping, and the grouping happens in the browser.
from bokeh.io import show from bokeh.models import ColumnDataSource from bokeh.palettes import RdBu3 from bokeh.plotting import figure c1 = RdBu3[2] # red c2 = RdBu3[0] # blue source = ColumnDataSource(dict( x=[1, 2, 3, 4, 5, 6], y=[2, 1, 2, 1, 2, 1], color=[c1, c2, c1, c2, c1, c2], label=['hi', 'lo', 'hi', 'lo', 'hi', 'lo'] )) p = figure(x_range=(0, 7), y_range=(0, 3), plot_height=300, tools='save') # legend field matches the column in the source p.circle( x='x', y='y', radius=0.5, color='color', legend_field='label', source=source) show(p)
To build a legend by hand, don’t use any of the legend arguments and instead assign values to the various properties of a Legend object directly.
legend
See examples/models/file/legends.py for an example.
To explicitly specify which index into a ColumnDataSource to use in a legend, set the index property of a LegendItem.
index
LegendItem
This is useful for displaying multiple entries in a legend when you use glyphs that are rendered in several parts, such as MultiLine (multi_line()) or Patches patches():
MultiLine
multi_line()
Patches
patches()
from bokeh.models import Legend, LegendItem from bokeh.plotting import figure, show p = figure() r = p.multi_line([[1,2,3], [1,2,3]], [[1,3,2], [3,4,3]], color=["orange", "red"], line_width=4) legend = Legend(items=[ LegendItem(label="orange", renderers=[r], index=0), LegendItem(label="red", renderers=[r], index=1), ]) p.add_layout(legend) show(p)
You can use legends as interactive elements to control some aspects of the appearance of your plot. Clicking or tapping on interactive legend entries controls the visibility of the glyphs associated with the legend entry.
See Interactive legends in the User Guide for more information and examples.
Note
Interactive legends features currently work on the first, “per-glyph” style legends. Legends that are created by specifying a column to automatically group do not yet support interactive features.
To create a ColorBar, use an instance of ColorMapper containing a color palette.
ColorBar
ColorMapper
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 ColorBar, LogColorMapper from bokeh.plotting import figure, output_file, show output_file('color_bar.html') def normal2d(X, Y, sigx=1.0, sigy=1.0, mux=0.0, muy=0.0): z = (X-mux)**2 / sigx**2 + (Y-muy)**2 / sigy**2 return np.exp(-z/2) / (2 * np.pi * sigx * sigy) X, Y = np.mgrid[-3:3:100j, -2:2:100j] 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) plot.image(image=[image], color_mapper=color_mapper, dh=[1.0], dw=[1.0], x=[0], y=[0]) color_bar = ColorBar(color_mapper=color_mapper, label_standoff=12) plot.add_layout(color_bar, 'right') show(plot)
You can use Arrow annotations to connect glyphs and label annotations. Arrows can also help highlight plot regions.
Arrow
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 to None. If you want to create double-sided arrows, set both the start and end properties to one of the available arrowheads.
ArrowHead
start
end
OpenHead
None
The available arrowheads are:
Control the appearance of an arrowhead with these properties:
use the size property to control the size of any arrowheads
size
use the standard line properties such as line_color and line_alpha to control the appearance of the outline of the arrowhead.
line_color
line_alpha
use fill_color and fill_alpha to control the appearance of the arrowhead’s inner surface, if applicable.
fill_color
fill_alpha
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.
x_range
y_range
from bokeh.models import Arrow, NormalHead, OpenHead, VeeHead from bokeh.plotting import figure, output_file, show output_file("arrow.html", title="arrow.py example") 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)
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.
Band
To define a band, use either screen units or data-space units.
import numpy as np import pandas as pd from bokeh.models import Band, ColumnDataSource from bokeh.plotting import figure, output_file, show output_file("band.html", title="band.py example") # Create some random data x = np.random.random(2500) * 140 - 20 y = np.random.normal(size=2500) * 2 + 5 df = pd.DataFrame(data=dict(x=x, y=y)).sort_values(by="x") sem = lambda x: x.std() / np.sqrt(x.size) df2 = df.y.rolling(window=100).agg({"y_mean": np.mean, "y_std": np.std, "y_sem": sem}) df2 = df2.fillna(method='bfill') 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()) TOOLS = "pan,wheel_zoom,box_zoom,reset,save" p = figure(tools=TOOLS) p.scatter(x='x', y='y', line_color=None, fill_alpha=0.3, size=5, source=source) band = Band(base='x', lower='lower', upper='upper', source=source, level='underlay', fill_alpha=1.0, line_width=1, line_color='black') p.add_layout(band) p.title.text = "Rolling Standard Deviation" p.xgrid[0].grid_line_color=None p.ygrid[0].grid_line_alpha=0.5 p.xaxis.axis_label = 'X' p.yaxis.axis_label = 'Y' show(p)
A BoxAnnotation is a rectangular box that you can link to either data or screen coordinates in order to highlight specific plot regions.
BoxAnnotation
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.
left
right
top
bottom
from bokeh.models import BoxAnnotation from bokeh.plotting import figure, output_file, show from bokeh.sampledata.glucose import data output_file("box_annotation.html", title="box_annotation.py example") TOOLS = "pan,wheel_zoom,box_zoom,reset,save" #reduce data size data = data.loc['2010-10-06':'2010-10-13'] p = figure(x_axis_type="datetime", tools=TOOLS) p.line(data.index.to_series(), data['glucose'], line_color="gray", line_width=1, legend_label="glucose") low_box = BoxAnnotation(top=80, fill_alpha=0.1, fill_color='red') mid_box = BoxAnnotation(bottom=80, top=180, fill_alpha=0.1, fill_color='green') high_box = BoxAnnotation(bottom=180, fill_alpha=0.1, fill_color='red') p.add_layout(low_box) p.add_layout(mid_box) p.add_layout(high_box) p.title.text = "Glucose Range" p.xgrid[0].grid_line_color=None p.ygrid[0].grid_line_alpha=0.5 p.xaxis.axis_label = 'Time' p.yaxis.axis_label = 'Value' show(p)
A PolyAnnotation is a polygon with vertices in either data or screen coordinates.
PolyAnnotation
To define the polygon’s vertices, supply a series of coordinates to the xs and ys properties. Bokeh automatically connects the last vertex to the first to create a closed shape.
xs
ys
from datetime import datetime as dt import pandas as pd from bokeh.models import PolyAnnotation from bokeh.plotting import figure, output_file, show from bokeh.sampledata.stocks import GOOG output_file("polyannotation.html", title="polannotation example") p = figure( plot_width=800, plot_height=250, x_axis_type="datetime", title="Google stock", ) df = pd.DataFrame(GOOG) df["date"] = pd.to_datetime(df["date"]) p.line(df["date"], df["close"], line_width=2, color="red") start_date = dt(2008, 11, 24) start_float = start_date.timestamp() * 1000 start_data = df.loc[df["date"] == start_date]["close"].values[0] end_date = dt(2010, 1, 4) end_float = end_date.timestamp() * 1000 end_data = df.loc[df["date"] == end_date]["close"].values[0] polygon = PolyAnnotation( fill_color="blue", fill_alpha=0.3, xs=[start_float, start_float, end_float, end_float], ys=[start_data - 100, start_data + 100, end_data + 100, end_data - 100], ) p.add_layout(polygon) show(p)
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:
Label
A text property containing the text to display inside the label.
text
x and y properties to set the position (in screen or data space units).
x
y
x_offset and y_offset properties to specify where to place the label in relation to its x and y coordinates.
x_offset
y_offset
The standard text properties as well as other styling parameters such as border_line and background_fill properties.
border_line
background_fill
Label(x=70, y=70, x_units='screen', text='Some Stuff', render_mode='css', border_line_color='black', border_line_alpha=1.0, background_fill_color='white', background_fill_alpha=1.0)
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
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, LabelSet, Range1d from bokeh.plotting import figure, output_file, show output_file("label.html", title="label.py example") 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 at Lee High', x_range=Range1d(140, 275)) p.scatter(x='weight', y='height', size=8, source=source) p.xaxis[0].axis_label = 'Weight (lbs)' p.yaxis[0].axis_label = 'Height (in)' labels = LabelSet(x='weight', y='height', text='names', x_offset=5, y_offset=5, source=source, render_mode='canvas') citation = Label(x=70, y=70, x_units='screen', y_units='screen', text='Collected by Luke C. 2016-04-01', render_mode='css', border_line_color='black', border_line_alpha=1.0, background_fill_color='white', background_fill_alpha=1.0) p.add_layout(labels) p.add_layout(citation) show(p)
Slope annotations are lines that can go from one edge of the plot to another at a specific angle.
Slope
These are the most commonly used properties for this annotation:
gradient: The gradient of the line, in data units.
gradient
y_intercept: The y intercept of the line, in data units.
y_intercept
The standard line properties.
import numpy as np from bokeh.models import Slope from bokeh.plotting import figure, output_file, show output_file("slope.html", title="slope.py example") # linear equation parameters gradient = 2 y_intercept = 10 # create random data xpts = np.arange(0, 20) ypts = gradient * xpts + y_intercept + np.random.normal(0, 4, 20) p = figure(plot_width=450, plot_height=450, y_range=(0, 1.1 * max(ypts))) p.circle(xpts, ypts, size=5, color="skyblue") slope = Slope(gradient=gradient, y_intercept=y_intercept, line_color='orange', line_dash='dashed', line_width=3.5) p.add_layout(slope) p.yaxis.axis_label = 'y' p.xaxis.axis_label = 'x' show(p)
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.
Span
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.
dimension
"height"
"width"
location: The location of the span along the axis specified with dimension.
location
location_units: The unit type for the location property. The default is to use “data space” units.
location_units
import time from datetime import datetime as dt from bokeh.models import Span from bokeh.plotting import figure, output_file, show from bokeh.sampledata.daylight import daylight_warsaw_2013 output_file("span.html", title="span.py example") p = figure(x_axis_type="datetime", y_axis_type="datetime") p.line(daylight_warsaw_2013.Date, daylight_warsaw_2013.Sunset, line_dash='solid', line_width=2, legend_label="Sunset") p.line(daylight_warsaw_2013.Date, daylight_warsaw_2013.Sunrise, line_dash='dotted', line_width=2, legend_label="Sunrise") start_date = time.mktime(dt(2013, 3, 31, 2, 0, 0).timetuple())*1000 daylight_savings_start = Span(location=start_date, dimension='height', line_color='green', line_dash='dashed', line_width=3) p.add_layout(daylight_savings_start) end_date = time.mktime(dt(2013, 10, 27, 3, 0, 0).timetuple())*1000 daylight_savings_end = Span(location=end_date, dimension='height', line_color='red', line_dash='dashed', line_width=3) p.add_layout(daylight_savings_end) p.title.text = "2013 Sunrise and Sunset times in Warsaw" p.yaxis.axis_label = 'Time of Day' show(p)
A Whisker annotation is a “stem” that is dimensionally linked to the data in the plot. You can define this annotation using data or screen units.
Whisker
A common use for whisker annotations is to indicate error margins or uncertainty for measurements at a single point.
lower: The coordinates of the lower end of the whisker.
lower
upper: The coordinates of the upper end of the whisker.
upper
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 with dimension.
base
from bokeh.models import ColumnDataSource, Whisker from bokeh.plotting import figure, show from bokeh.sampledata.autompg import autompg as df colors = ["red", "olive", "darkred", "goldenrod", "skyblue", "orange", "salmon"] p = figure(plot_width=600, plot_height=300, title="Years vs mpg with Quartile Ranges") base, lower, upper = [], [], [] for i, year in enumerate(list(df.yr.unique())): year_mpgs = df[df['yr'] == year]['mpg'] mpgs_mean = year_mpgs.mean() mpgs_std = year_mpgs.std() lower.append(mpgs_mean - mpgs_std) upper.append(mpgs_mean + mpgs_std) base.append(year) source_error = ColumnDataSource(data=dict(base=base, lower=lower, upper=upper)) p.add_layout( Whisker(source=source_error, base="base", upper="upper", lower="lower") ) for i, year in enumerate(list(df.yr.unique())): y = df[df['yr'] == year]['mpg'] color = colors[i % len(colors)] p.circle(x=year, y=y, color=color) show(p)