Skillversitas

Labels, Titles and Legends

The text around a chart - title, axis labels, and the legend - is what makes it self-contained. A figure without axis labels is a sketch; a figure with a complete caption could be published on its own. Matplotlib puts all of these under a consistent set of methods with font dictionaries for styling, and the legend is its own mini-layout engine with positioning options that take a little practice to master.

Titles and Axis Labels

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x))

ax.set_title("Oscillation over one period",
             fontdict={"fontsize": 14, "fontweight": "bold"})
ax.set_xlabel("time (s)")
ax.set_ylabel("amplitude")
plt.show()

Every text element accepts a fontdict (size, weight, family, color) for one-off styling; global font behavior belongs in rcParams (styling chapter). The convention worth keeping: axis labels state the quantity and unit, the title states the finding or the dataset, and figure-level context goes in a suptitle or caption, not in the per-axes title.

The Basic Legend

import matplotlib.pyplot as plt

x = [0, 1, 2, 3, 4]
fig, ax = plt.subplots()
ax.plot(x, x, label="linear")
ax.plot(x, np.array(x) ** 2, label="quadratic")
ax.plot(x, np.sqrt(np.array(x, dtype=float)), label="sqrt")
ax.legend()
plt.show()

ax.legend() collects every artist that carries a label and renders the swatch-plus-text box. The label keyword on each plot call is the input; unlabeled artists are ignored, which is how you keep reference lines out of the legend while data lines stay in.

Positioning the Legend

import matplotlib.pyplot as plt

x = [0, 1, 2, 3, 4]
y = [2, 5, 3, 8, 6]

fig, ax = plt.subplots()
ax.plot(x, y, marker="o", label="series A")

# named locations: best, upper right, lower left, center left, ...
ax.legend(loc="upper left")

# or pin it outside the axes entirely
ax.legend(loc="upper left", bbox_to_anchor=(1.02, 1),
          borderaxespad=0)
fig.tight_layout()
plt.show()

loc picks from named corners and edges; "best" (the default) searches for the least-overlapping spot. bbox_to_anchor places the legend's anchor in axes coordinates - the classic move for a legend in the right margin, with tight_layout or bbox_inches="tight" at save time so the margin is not clipped. ncol=2 spreads entries into columns for long legends, and framealpha=0 removes the background box.

Custom Legend Entries

import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

fig, ax = plt.subplots()
ax.plot([0, 1], [0, 1], color="tab:blue")
ax.plot([0, 1], [1, 0], color="tab:red", linestyle="--")

handles = [
    Line2D([0], [0], color="tab:blue", label="actual"),
    Line2D([0], [0], color="tab:red", linestyle="--", label="target"),
]
ax.legend(handles=handles, loc="center right")
plt.show()

Passing explicit handles decouples the legend from the plotted artists - the tool for proxy entries ("this gray band = 95% CI"), extra notes, or reordering without touching the data calls.

Figure-Level Titles

import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.plot([1, 2, 3], [1, 4, 9])
ax2.plot([1, 2, 3], [9, 4, 1])
fig.suptitle("Two related views", fontsize=15, fontweight="bold")
fig.tight_layout()
plt.show()

fig.suptitle titles the whole figure (one caption for all subplots), while per-axes titles describe each panel. If tight_layout clips the suptitle, add rect=(0, 0, 1, 0.95) or raise top in subplots_adjust.

Summary

Label every axis with quantity and unit, title each panel with its content and the figure with its purpose, and build legends from label keywords with loc plus bbox_to_anchor for placement. Use proxy handles when the legend should say something the plot calls cannot, and keep text styling local (fontdict) unless it is a site-wide convention (rcParams).