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 Titleobjects. 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.dimensionalproperty. 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 CustomDimensionalmodel. 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 ArrowHeadobjects 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:

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 leftvalue 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:

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:

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:

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:

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)