plotlet v0.6.2

3. Titles, labels & legends

The text around the data: layered titles, measure-driven margins, and the two ways to place a legend. Every snippet on this page is executed on every build — the figures are its output.

Title, subtitle, caption

Three text slots stack around the figure: the title, a smaller subtitle under it, and a right-aligned caption at the very bottom — the conventional place for a data source.

df = {"displacement": [1.8, 2.0, 2.8, 3.1, 4.2, 5.3],
      "mpg": [29, 31, 26, 27, 23, 20]}

c = pt.chart(df, aes(x="displacement", y="mpg"),
             data_width=340, data_height=170,
             title="Fuel efficiency", subtitle="highway, 1999-2008",
             caption="Source: EPA", xlabel="displacement (L)",
             ylabel="mpg")
c.add_scatter(size=3)

Margins grow to fit

Label space is measured, not guessed: long tick labels, wide titles, or multi-line axis labels (\n works everywhere) grow the margins exactly as needed — and in a grid, the growth propagates so data regions stay aligned across panels.

df = {"score": [1, 2, 3, 4, 5],
      "name": ["measurement_alpha", "measurement_beta",
               "measurement_gamma", "measurement_delta",
               "measurement_epsilon"]}

c = pt.chart(df, aes(x="score", y="name"),
             data_width=300, data_height=170, xlabel="score")
c.add_scatter(size=3)
["measurement_alpha","measurement_beta","measurement_gamma","measurement_delta","measurement_epsilon"]

Legends, next to the panel

legend=True reserves the right-hand slot; entries come from mapped aesthetics automatically, or from label= on individual artist calls when you layer manually.

import math
xs = [i * 0.1 for i in range(64)]
df = {"t": xs,
      "sin": [math.sin(x) for x in xs],
      "cos": [math.cos(x) for x in xs]}

c = pt.chart(df, data_width=340, data_height=170, legend=True,
             xlabel="t", ylabel="value")
c.add_line(aes(x="t", y="sin"), label="sin(t)")
c.add_line(aes(x="t", y="cos"), label="cos(t)", linestyle="--")

Legends, as a layout cell

In a composition, pt.legend() is a grid cell of its own: it harvests entries from every panel in the layout — discrete swatches and continuous gradients together — so one legend serves the whole figure. The annotated heatmap relies on this.

df = pt.load_dataset("flights")
months = df["month"][:12]
years = sorted(set(df["year"]))
data = {"year": [str(y) for y in years]}
for m in months:
    data[m] = [n for n, month in zip(df["passengers"], df["month"])
               if month == m]

hm = pt.chart(title="airline passengers", data_width=380,
              data_height=200)
hm.add_heatmap(data=data, mapping=aes(x="year"), values=months,
               cmap="viridis", legend={"label": "passengers"})
c = pt.grid([[hm, pt.legend()]]).gap(0)
["1949","1950","1951","1952","1953","1954","1955","1956","1957","1958","1959","1960"]["January","February","March","April","May","June","July","August","September","October","November","December"]

Annotate the data

c.add_text(...) places data-anchored labels in batch; c.add_annotate(...) adds a single note, with an arrow when the label sits away from its target and bbox= for a background box over dense data.

import math
xs = [i * 0.2 for i in range(40)]
ys = [math.sin(x) + math.sin(2 * x) * 0.4 for x in xs]
df = {"x": xs, "y": ys}

c = pt.chart(df, aes(x="x", y="y"), data_width=400, data_height=190)
c.add_line()
peak = ys.index(max(ys))
c.add_annotate("global max", xy=(xs[peak], ys[peak]),
               xytext=(xs[peak] + 1.5, ys[peak] + 0.2))
c.add_annotate("baseline", xy=(6.5, -0.05), bbox=True)

← Aesthetics & scales  ·  Multi-panel composition →