← Matplotlib·Lesson 12·12 min·0/3 exercises

Annotation

Where a chart stops showing data and starts making a claim

ax.textax.annotatetransAxesax.axvspanarrowpropsax.set_title

Watch it happen

Play it through, or step back and forth yourself.

MonTueWedThuFriSatSunCups by day
ax.text(3, 60, "quiet")in DATA coordinates
ha="center", va="bottom"which corner sits on the point
fontsize=11, color="#9a9a9a"notes should be quieter
ax.textsevery Text artist on the axes

A correct, labelled, unobjectionable chart — and it doesn't say anything. The reader has to work out for themselves what they were meant to notice. Annotation is where you stop showing data and start making a claim.

The idea

You can build a correct, labelled, unobjectionable chart that says nothing at all — the reader is left to work out for themselves what they were meant to notice. Annotation is where you tell them.

ax.text

ax.text(3, 60, "midweek lull", ha="center", color="#9a9a9a")

Words at a point in data coordinates — so they move when the limits change. ha and va decide which part of the text sits on that point, and getting them wrong is why annotations so often look slightly adrift.

Notes should be quieter than the data: smaller, greyer. They're support, not content.

ax.annotate

This is the one to actually learn:

i = int(np.argmax(delhi))
ax.annotate(f"best day: {delhi[i]}",
            xy=(i, delhi[i]),            # what you're POINTING AT
            xytext=(i - 2, delhi[i] + 8), # where the WORDS go
            arrowprops=dict(arrowstyle="->", color="#9a9a9a"))

Two coordinates: xy is the target, xytext is the label. Everything else about annotate follows once those are straight. arrowprops draws the connector, and connectionstyle="arc3,rad=0.2" curves it around whatever's in the way.

Notice that the interesting point is computed — np.argmax — not typed in. Hardcoding xy=(5, 88) works today and points at empty space the moment the data changes.

Axes coordinates

ax.text(0.03, 0.95, "n = 120",
        transform=ax.transAxes, va="top",
        bbox=dict(facecolor="#161616", edgecolor="none", alpha=0.8))

transform=ax.transAxes switches to fractions of the axes: (0, 0) is bottom-left, (1, 1) is top-right. That's how you pin a note to the corner and have it stay in the corner when the data underneath changes. fig.transFigure does the same for the whole canvas — sources and footnotes.

Hand-drawn notes distinguishing text placed in data coordinates, which moves when the limits change, from axes coordinates, which run zero to one across the panel.

Label the lines directly

A legend makes the eye travel to a key, decode a colour, and travel back. Writing each series' name at the end of its own line removes that trip:

for name, line in zip(names, ax.lines):
    ax.text(x[-1] + 0.1, line.get_ydata()[-1], name,
            color=line.get_color(), va="center")
ax.margins(x=0.12)      # make room on the right

Taking the colour from line.get_color() is the trick that makes it hold together — the label is the key. This is the change that most improves a multi-series chart, and almost nobody makes it.

Hand-drawn notes contrasting a legend that forces the eye back and forth with labels written at the end of each line, read once and in place.

Highlighting a period

ax.axvspan("Sat", "Sun", alpha=0.1, zorder=0)
ax.text(5.5, 95, "weekend", ha="center", fontsize=9)

A shaded band for a closure, a campaign, a lockdown. Faint, and zorder=0 to keep it behind the data. It carries context the numbers can't.

The title is the claim

Last and most valuable. "Cups by day" describes the axes, and the axes already describe themselves. Promote the finding instead:

ax.set_title("Weekend trade is 40% above weekdays", loc="left", fontsize=13)

Now the chart is evidence for a stated claim rather than a puzzle. loc="left" reads as a headline, and a smaller grey second line beneath it can carry the detail.

The discipline: write the sentence you want the reader to leave with, then check the chart supports it. If it doesn't, you've learned something more useful than a title.

Hand-drawn notes contrasting a title that describes what is plotted with one that states what the figure found.

Practice

Write it yourself. The answer is there when you want it.

Putting the kettle on…

Starting up…

Write it yourself

not graded

Plot both cities with the name written at the end of each line, Delhi emphasised. Find Delhi's best day and annotate it with an arrow pointing at the point from a label set off to one side. Shade the weekend behind everything, and put a small "n = 7 days" in the corner using axes coordinates so it stays put. Title it left-aligned with the finding itself. Print the texts the Axes now holds and the peak day. Finish with fig.

Write something and press Run — the output appears here.

Your turn

3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Before you can annotate the peak you have to find it. Return the name of the day Delhi sold the most — computed, not typed in.

your answer

Plot delhi and annotate its peak with the text "peak", pointing at the peak and putting the words two days to its left. Return [ax.texts[0].get_text(), list(ax.texts[0].xy)].

your answer

Put a note in the top-left corner of the axes at (0.03, 0.95) using axes coordinates rather than data coordinates. Return ax.texts[0].get_transform() is ax.transAxes.

your answer