Axes Method - clabel¶
The plt.clabel() or ax.clabel() method adds labels to contour lines.
Mental Model
Contour lines without labels are like elevation lines on a map with no numbers -- you can see the shape but not the values. clabel() stamps numeric values directly onto the contour lines, turning your plot into a self-documenting map. Pass the contour set returned by contour() and Matplotlib places labels at readable positions automatically.
Basic Usage¶
Adding Labels to Contours¶
```python import matplotlib.pyplot as plt import numpy as np
def main(): f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, ax = plt.subplots(figsize=(12, 4))
# Plot contour
contours = ax.contour(X, Y, Z, levels=3, colors='black')
# Put labels to the contour lines
plt.clabel(contours, inline=True, fontsize=20)
plt.show()
if name == "main": main() ```
Key Parameters¶
inline Parameter¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
inline=True (default) - line is broken at label¶
contours1 = axes[0].contour(X, Y, Z, levels=5, colors='black') axes[0].clabel(contours1, inline=True, fontsize=12) axes[0].set_title('inline=True (breaks line)')
inline=False - label placed on top of line¶
contours2 = axes[1].contour(X, Y, Z, levels=5, colors='black') axes[1].clabel(contours2, inline=False, fontsize=12) axes[1].set_title('inline=False (overlaps line)')
for ax in axes: ax.set_aspect('equal') plt.tight_layout() plt.show() ```
fontsize Parameter¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 3, figsize=(15, 4)) fontsizes = [8, 14, 20]
for ax, fs in zip(axes, fontsizes): contours = ax.contour(X, Y, Z, levels=5, colors='black') ax.clabel(contours, inline=True, fontsize=fs) ax.set_title(f'fontsize={fs}') ax.set_aspect('equal')
plt.tight_layout() plt.show() ```
Format Control¶
fmt Parameter¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X2 + Y2
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
Default format¶
contours1 = axes[0].contour(X, Y, Z, levels=[1, 2, 4, 6, 8]) axes[0].clabel(contours1, inline=True, fontsize=10) axes[0].set_title('Default format')
Decimal places¶
contours2 = axes[1].contour(X, Y, Z, levels=[1, 2, 4, 6, 8]) axes[1].clabel(contours2, inline=True, fontsize=10, fmt='%.2f') axes[1].set_title("fmt='%.2f'")
Integer format¶
contours3 = axes[2].contour(X, Y, Z, levels=[1, 2, 4, 6, 8]) axes[2].clabel(contours3, inline=True, fontsize=10, fmt='%d') axes[2].set_title("fmt='%d'")
for ax in axes: ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Custom Format Function¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X2 + Y2
Custom format function¶
def fmt_func(x): return f'r={np.sqrt(x):.1f}'
fig, ax = plt.subplots(figsize=(8, 6)) contours = ax.contour(X, Y, Z, levels=[1, 4, 9, 16]) ax.clabel(contours, inline=True, fontsize=10, fmt=fmt_func) ax.set_title('Custom format: radius values') ax.set_aspect('equal') plt.show() ```
Dictionary Format¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X2 + Y2
Format dictionary for specific levels¶
fmt_dict = { 1: 'Low', 4: 'Medium', 9: 'High' }
fig, ax = plt.subplots(figsize=(8, 6)) contours = ax.contour(X, Y, Z, levels=[1, 4, 9]) ax.clabel(contours, inline=True, fontsize=12, fmt=fmt_dict) ax.set_title('Dictionary format: custom labels') ax.set_aspect('equal') plt.show() ```
Styling Labels¶
colors Parameter¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
Default (matches contour color)¶
contours1 = axes[0].contour(X, Y, Z, levels=5, colors='blue') axes[0].clabel(contours1, inline=True, fontsize=10) axes[0].set_title('Default label color')
Explicit color¶
contours2 = axes[1].contour(X, Y, Z, levels=5, colors='blue') axes[1].clabel(contours2, inline=True, fontsize=10, colors='red') axes[1].set_title("colors='red'")
With colormap¶
contours3 = axes[2].contour(X, Y, Z, levels=5, cmap='viridis') axes[2].clabel(contours3, inline=True, fontsize=10, colors='black') axes[2].set_title("colors='black' on colormap")
for ax in axes: ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Selective Labeling¶
levels Parameter in clabel¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X2 + Y2
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
Label all levels¶
contours1 = axes[0].contour(X, Y, Z, levels=[1, 2, 3, 4, 5, 6, 7, 8]) axes[0].clabel(contours1, inline=True, fontsize=10) axes[0].set_title('All levels labeled')
Label only specific levels¶
contours2 = axes[1].contour(X, Y, Z, levels=[1, 2, 3, 4, 5, 6, 7, 8]) axes[1].clabel(contours2, levels=[2, 4, 6, 8], inline=True, fontsize=10) axes[1].set_title('Only even levels labeled')
for ax in axes: ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Manual Placement¶
manual Parameter¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X2 + Y2
fig, ax = plt.subplots(figsize=(8, 6)) contours = ax.contour(X, Y, Z, levels=[1, 4, 9])
Specify label positions manually¶
manual_locations = [(0.5, 0.5), (1.5, 1.0), (2.5, 1.5)] ax.clabel(contours, inline=True, fontsize=12, manual=manual_locations)
ax.set_title('Manual label placement') ax.set_aspect('equal') plt.show() ```
Inline Spacing¶
inline_spacing Parameter¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 3, figsize=(15, 4)) spacings = [2, 5, 10]
for ax, spacing in zip(axes, spacings): contours = ax.contour(X, Y, Z, levels=5, colors='black') ax.clabel(contours, inline=True, fontsize=10, inline_spacing=spacing) ax.set_title(f'inline_spacing={spacing}') ax.set_aspect('equal')
plt.tight_layout() plt.show() ```
With Filled Contours¶
Labels on contourf¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, ax = plt.subplots(figsize=(10, 6))
Filled contours¶
cf = ax.contourf(X, Y, Z, levels=15, cmap='viridis', alpha=0.8)
Line contours for labels¶
cs = ax.contour(X, Y, Z, levels=7, colors='black', linewidths=0.5) ax.clabel(cs, inline=True, fontsize=10, fmt='%.2f')
fig.colorbar(cf, ax=ax) ax.set_title('Labels on Filled Contours') ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Practical Example¶
Topographic Map with Elevation Labels¶
```python import matplotlib.pyplot as plt import numpy as np
Create terrain¶
x = np.linspace(0, 10, 100) y = np.linspace(0, 10, 100) X, Y = np.meshgrid(x, y) Z = (2 * np.sin(X) * np.cos(Y) + np.sin(2X) * np.cos(2Y) + 0.5 * np.sin(4X) * np.cos(4Y) + 3) # Add offset for positive values
fig, ax = plt.subplots(figsize=(10, 8))
Filled contours¶
cf = ax.contourf(X, Y, Z, levels=20, cmap='terrain')
Labeled contours¶
cs = ax.contour(X, Y, Z, levels=10, colors='black', linewidths=0.7) ax.clabel(cs, inline=True, fontsize=9, fmt='%1.1f m')
fig.colorbar(cf, ax=ax, label='Elevation (m)') ax.set_title('Topographic Map with Elevation Labels', fontsize=14) ax.set_xlabel('Distance (km)') ax.set_ylabel('Distance (km)') ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Temperature Distribution¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-5, 5, 100) y = np.linspace(-5, 5, 100) X, Y = np.meshgrid(x, y) Z = 20 + 10 * np.exp(-(X2 + Y2) / 10) # Temperature distribution
fig, ax = plt.subplots(figsize=(10, 8))
cf = ax.contourf(X, Y, Z, levels=15, cmap='coolwarm') cs = ax.contour(X, Y, Z, levels=10, colors='black', linewidths=0.5) ax.clabel(cs, inline=True, fontsize=10, fmt='%.1f°C')
fig.colorbar(cf, ax=ax, label='Temperature (°C)') ax.set_title('Temperature Distribution', fontsize=14) ax.set_xlabel('X Position') ax.set_ylabel('Y Position') ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Why Labels Matter¶
Contour lines show structure (where things are equal). Labels turn that structure into numbers (what value the level represents).
- Without labels → qualitative understanding (steep here, flat there)
- With labels → quantitative understanding (this line is \(z = 0.5\), that one is \(z = 1.0\))
Use labels sparingly. Too many clutter the plot and reduce the very readability they are meant to provide.
Exercises¶
Exercise 1.
Create a contour plot of \(z = x^2 + y^2\) over \([-3, 3] \times [-3, 3]\) with 10 levels. Add contour labels using ax.clabel with inline=True and fontsize=10. Format the labels to show one decimal place using fmt='%.1f'.
Solution to Exercise 1
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = X**2 + Y**2
fig, ax = plt.subplots(figsize=(8, 6))
cs = ax.contour(X, Y, Z, levels=10)
ax.clabel(cs, inline=True, fontsize=10, fmt='%.1f')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title(r'Contour Plot of $z = x^2 + y^2$')
ax.set_aspect('equal')
plt.show()
Exercise 2.
Plot contour lines for \(z = \sin(x) \cdot \cos(y)\) and add labels only to specific levels \([-0.5, 0, 0.5]\) by passing the levels parameter to contour and then labeling. Style the labels with fontsize=12, a white background (colors='white'), and inline_spacing=5.
Solution to Exercise 2
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-np.pi, np.pi, 200)
y = np.linspace(-np.pi, np.pi, 200)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
fig, ax = plt.subplots(figsize=(8, 6))
cs = ax.contour(X, Y, Z, levels=[-0.5, 0, 0.5])
ax.clabel(cs, inline=True, fontsize=12, inline_spacing=5)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title(r'Contour Labels at Selected Levels')
ax.set_aspect('equal')
plt.show()
Exercise 3.
Create a contour plot of the 2D Gaussian \(z = e^{-(x^2 + y^2)/2}\) with manually chosen levels [0.1, 0.3, 0.5, 0.7, 0.9]. Add labels with a custom format function that displays each level as a percentage (e.g., "10%", "30%"). Use manual label placement by specifying explicit (x, y) positions for each label.
Solution to Exercise 3
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2) / 2)
levels = [0.1, 0.3, 0.5, 0.7, 0.9]
fig, ax = plt.subplots(figsize=(8, 6))
cs = ax.contour(X, Y, Z, levels=levels)
fmt = {lev: f'{lev*100:.0f}%' for lev in levels}
manual_positions = [(2.0, 0), (1.3, 0), (0.9, 0), (0.5, 0), (0.2, 0)]
ax.clabel(cs, inline=True, fontsize=11, fmt=fmt, manual=manual_positions)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('2D Gaussian with Percentage Labels')
ax.set_aspect('equal')
plt.show()