Annotation
Where a chart stops showing data and starts making a claim
ax.textax.annotatetransAxesax.axvspanarrowpropsax.set_titleWatch it happen
Play it through, or step back and forth yourself.
ax.text(3, 60, "quiet")in DATA coordinatesha="center", va="bottom"which corner sits on the pointfontsize=11, color="#9a9a9a"notes should be quieterax.textsevery Text artist on the axesA 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.

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

Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedPlot 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.
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.
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)].
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.
