Annotations

Überblick

Wir können visuelle Hinweise (Begrenzungslinien, schattierte Bereiche, Beschriftungen und Pfeile usw.) zu unseren Plots hinzufügen, um bestimmte Merkmale hervorzuheben. Bokeh bietet hierfür mehrere Annotationstypen. Um Annotations hinzuzufügen, erstellen wir normalerweise ein Low-Level-Objekt und fügen es mit add_layout unserem Diagramm hinzu. Schauen wir uns einige konkrete Beispiele an:

Spans

Spans sind vertikale oder horizontale Linien, für die die Spannweiten zwischen den Bemaßung angegeben werden kann, z.B.:

[1]:
from bokeh.io import output_notebook, show
from bokeh.plotting import figure


output_notebook()
Loading BokehJS ...
[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

Soll ein Bereich eines Diagramms hervorgehoben werden, so können diese mit BoxAnnotation, den Koordinateneigenschaften

  • top

  • left

  • bottom

  • right

sowie Linien- und Fülleigenschaften das Erscheinungsbild konfiguriert werden.

Infinite Boxen können erstellt werden, indem die Koordinaten nicht angegeben werden. Wenn zum Beispiel top nicht angegeben ist, wird die Box immer bis zum oberen Rand des Plotbereichs angezeigt, unabhängig davon, ob ein Verschieben oder Zoomen stattfindet, z.B.:

[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

Mit der Annotation Label können wir einzelne Beschriftungen einfach an Plots anbringen. Die anzuzeigende Position und der Text sind als x, y und text konfiguriert:

Label(x=10, y=5, text="Some Label")

Standardmäßig befinden sich die Einheiten im data space, aber x_units und y_units können auf screen gesetzt werden, um die Beschriftung relativ zur Zeichenfläche zu positionieren, und Labels können mit x_offset und y_offset positioniert werden.

Label-Objekte haben auch Standardtext-, Zeilen- (border_line) und Fülleigenschaften (background_fill). Die Linien- und Fülleigenschaften gelten für einen Begrenzungsrahmen um den 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

Mit der Annotation LabelSet könnt ihr viele Beschriftungen gleichzeitig erstellen, z.B. wenn ihr einen ganzen Satz von Scatter Markers beschriften möchtet. Sie ähneln Label, können aber auch eine ColumnDataSource als source -Eigenschaft nutzen, wobei sich x und y auf Spalten in der Datenquelle beziehen.

[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

Mit der Annotation Arrow könnt ihr auf verschiedene Elemente in eurer Zeichnung zeigen. Dies kann besonders bei Beschriftungen hilfreich sein.

So könnt ihr beispielsweise einen Pfeil erstellen, der von (0,0) bis (1,1) zeigt:

p.add_layout(Arrow(x_start=0, y_start=0, x_end=1, y_end=0))

Dieser Pfeil hat die Standardspitze OpenHead. Andere Arten von Pfeilspitzen sind NormalHead und VeeHead. Der Typ des Pfeilkopfs kann durch die Eigenschaften start and end von Arrow-Objekten konfiguriert werden:

p.add_layout(Arrow(start=OpenHead(), end=VeeHead(),
             x_start=0, y_start=0, x_end=1, y_end=0))

Dies erzeugt einen Doppelpfeil mit OpenHead und VeeHead. Pfeilspitzen haben darüberhinaus den Standardsatz von Linien- und Fülleigenschaften, um deren Aussehen zu konfigurieren, z.B.:

OpenHead(line_color="firebrick", line_width=4)

Der Code und das Diagramm unten zeigen mehrere dieser Konfigurationen zusammen.

[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)

Legenden

Wenn Diagramme mehrere Glyphen enthalten, ist es wünschenswert, eine Legende hinzuzufügen, um Betrachtern die Interpretation zu erleichtern. Bokeh kann Legenden anhand der hinzugefügten Glyphen leicht generieren.

Einfache Legenden

Im einfachsten Fall könnt ihr einen String als legend an eine Glyph-Funktion übergeben:

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

In diesem Fall erstellt Bokeh automatisch eine Legende, die eine Darstellung dieser Glyphe zeigt.

[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)

Zusammengesetzte Legenden

Im obigen Beispiel haben wir für jede Glyphenmethode ein anderes Etikett bereitgestellt. Manchmal werden zwei (oder mehr) verschiedene Glyphen mit einer einzigen Datenquelle verwendet. In diesem Fall können zusammengesetzte Legenden erstellt werden. Wenn ihr z.B. eine Sinuskurve mit einer Linie und einer Markierung plottet, könnt ihr ihnen dieselbe Bezeichnung geben um sie dazu zu bringen, gemeinsam in der Legende zu erscheinen:

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

Farbbalken

Farbbalken sind besonders nützlich, wenn wir die Farbe einer Glyphe entsprechend der Farbzuordnung variiert. Bokeh-Farbbalken werden mit einer Farbzuordnung konfiguriert und mit der `add_layout‘-Methode zu Plots hinzugefügt:

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')

Im folgenden Beispiel wird die Farbzuordnung auch zur Umwandlung der Glyphenfarbe verwendet.

[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)