Adding Annotations — Anaconda Enterprise 4.4 documentation

Anaconda Enterprise: Documentation

# Adding Annotations

Bokeh includes several different types of annotations to allow users to add supplemental information to their visualizations.

## Titles

`Title` annotations allow descriptive text to be rendered around the edges of a plot.

When using `bokeh.plotting` or `bokeh.Charts`, the quickest way to add a basic title is to pass the text as the `title` parameter to `Figure` or any Chart function:

    from bokeh.plotting import figure, show, output_file

    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 normally on the top of a plot, aligned to the left. But which side of the plot the default title appears on can be controlled by the `title_location` parameter:

    from bokeh.plotting import figure, show, output_file

    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)

The default `Title` is accessible through the `Plot.title` property. Visual properties for font, border, background, and others can be set directly on `.title`. Here is an example that sets font and background properties as well as the title text and title alignment using `.title`:

    from bokeh.plotting import figure, show, output_file

    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 alignment is measured along the direction of text. For example for titles on the left side of a plot “left” will be in the lower corner.

In addition to the default title, it is possible to create and add additional `Title` objects to plots using the `add_layout` method of Plots:

    from bokeh.models import Title
    from bokeh.plotting import figure, show, output_file

    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 sticky toolbar are set to the same side, they will occupy the same space:

    from bokeh.plotting import figure, show, output_file

    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 in way that is not desirable.

## Legends

It is possible to create `Legend` annotations easily by specifying a legend argument to the glyph methods, when creating a plot.

Note

This example depends on the open source NumPy library in order to more easily generate better data suitable for demonstrating legends.

    import numpy as np
    from bokeh.plotting import output_file, show, figure

    x = np.linspace(0, 4*np.pi, 100)
    y = np.sin(x)

    output_file("legend.html")

    p = figure()

    p.circle(x, y, legend="sin(x)")
    p.line(x, y, legend="sin(x)")

    p.line(x, 2*y, legend="2*sin(x)",
           line_dash=[4, 4], line_color="orange", line_width=2)

    p.square(x, 3*y, legend="3*sin(x)", fill_color=None, line_color="green")
    p.line(x, 3*y, legend="3*sin(x)", line_color="green")

    show(p)

It is also possible to create multiple legend items for the same glyph when if needed by passing a legend that is the column of the column data 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')

    # Note legend field matches the column in `source`
    p.circle( x='x', y='y', radius=0.5, color='color', legend='label', source=source)
    show(p)

If you do not want this automatic behavior, you can use the `field()` or `value()` functions from bokeh.core.properties, to be explicit about your intentions. See examples/app/gapminder/main.py for an example. Alternatively, you can not specify any legend argument, and manually build a `Legend` by hand. You can see an example of this in examples/models/file/legends.py:

It’s also possible to configure legends to be interactive, so that clicking or tapping on legend entries affects the corresponding glyph visibility. See the Interactive Legends section of the User’s 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 no yet support interactive features.

## Color Bars

Users can use a `ColorMapper` instance, which contain a color palette, to create a `ColorBar`. Both on and off-plot color bars are supported, users just need to specify their desired location when adding the ColorBar to the plot.

Note

This example depends on the open source NumPy and matplotlib libraries in order to more easily generate better data suitable for demonstrating legends.

    import numpy as np

    from bokeh.plotting import figure, output_file, show
    from bokeh.models import LogColorMapper, LogTicker, ColorBar

    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, ticker=LogTicker(),
                         label_standoff=12, border_line_color=None, location=(0,0))

    plot.add_layout(color_bar, 'right')

    show(plot)

## Arrows

`Arrow` annotations can be used to connect glyphs and label annotations or to simply highlight plot regions. Arrows are compound annotations, meaning that their``start`` and `end` attributes are themselves other `ArrowHead` annotations. By default, the `Arrow` annotation is one-sided with the `end` set as an `OpenHead`-type arrow head (an open-backed wedge style) and the `start` property set to `None`. Double-sided arrows can be created by setting both the `start` and `end` properties as appropriate `ArrowHead` subclass instances.

Arrows have standard line properties to set the color and appearance of the arrow shaft:

    my_arrow.line_color = "blue"
    my_arrow.line_alpha = 0.6

Arrows may also be configured to refer to additional non-default x- or y-ranges with the `x_range` and `y_range` properties, in the same way as Twin Axes.

Additionally any arrow head objects in `start` or `end` have a `size` property to control how big the arrow head is, as well as both line and fill properties. The line properties control the outline of the arrow head, and the fill properties control the interior of the arrow head (if applicable).

    from bokeh.plotting import figure, output_file, show
    from bokeh.models import Arrow, OpenHead, NormalHead, VeeHead

    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)

## Bands

A `Band` will create a dimensionally-linked “stripe”, either located in data or screen coordinates. One common use for the Band annotation is to indicate uncertainty related to a series of measurements.

    from bokeh.plotting import figure, show, output_file
    from bokeh.models import Band, ColumnDataSource
    import pandas as pd
    import numpy as np

    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)

## Box Annotations

A `BoxAnnotation` can be linked to either data or screen coordinates in order to emphasize specific plot regions. By default, box annotation dimensions (e.g. `left` or `top`) default will extend the annotation to the edge of the plot area.

    from bokeh.sampledata.glucose import data
    from bokeh.plotting import figure, show, output_file
    from bokeh.models import BoxAnnotation

    output_file("box_annotation.html", title="box_annotation.py example")

    TOOLS = "pan,wheel_zoom,box_zoom,reset,save"

    #reduce data size
    data = data.ix['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="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)

## Labels

Labels are text elements that can be used to annotate either glyphs or plot regions.

To create a single text label, use the `Label` annotation. This annotation is configured with a `text` property containing the text to be displayed, as well as `x` and `y` properties to set the position (in screen or data space units). Additionally a render mode `"canvas"` or `"css"` may be specified. Finally, labels have `text`, `border_line`, and `background_fill` properties. These control the visual appearance of the text, as well as the border and background of the bounding box for the text:

    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, possibly to easily annotate another existing glyph, use the `LabelSet` annotation, which is configured with a data source, with the `text` and `x` and `y` positions are given as column names. `LabelSet` objects can also have `x_offset` and `y_offset`, which specify a distance in screen space units to offset the label positions from `x` and `y`. Finally the render level may be controlled with the `level` property, to place the label above or underneath other renderers:

    LabelSet(x='x', y='y', text='names', level='glyph',
             x_offset=5, y_offset=5, source=source)

The following example illustrates the use of both:

    from bokeh.plotting import figure, show, output_file
    from bokeh.models import ColumnDataSource, Range1d, LabelSet, Label

    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', level='glyph',
                  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)

## Spans

`Span` annotations are lines that have a single dimension (width or height) and extend to the edge of the plot area.

    from datetime import datetime as dt
    import time

    from bokeh.sampledata.daylight import daylight_warsaw_2013
    from bokeh.plotting import figure, show, output_file
    from bokeh.models import Span

    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="Sunset")
    p.line(daylight_warsaw_2013.Date, daylight_warsaw_2013.Sunrise,
           line_dash='dotted', line_width=2, legend="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)

## Whiskers

A `Whisker` will create a dimensionally-linked “stem”, either located in data or screen coordinates. Indicating error or uncertainty for measurements at a single point would be one common use for the Whisker annotation.

    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)