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 computednp.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.

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.

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.

See it run

The lesson's code, ready to run and to fiddle with.

Putting the kettle on…

Starting up…

Worked example

not graded

Already written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.

tryreplacing the title with the finding you would actually say out loud.

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