Annotations¶
Annotations combine text with arrows to highlight specific features in your plot.
Mental Model
ax.annotate() is ax.text() with an arrow. You specify two points: where the arrow points to (xy) and where the text sits (xytext). The arrowprops dictionary controls the arrow style. Use annotations to call out specific data points -- peaks, anomalies, or thresholds -- making your plot tell a story.
The deeper role: annotations turn a plot into a narrative. They guide the viewer's attention to the most important insights — the peaks, anomalies, and thresholds that matter. A well-annotated plot communicates a finding; an unannotated one leaves interpretation to guesswork.
Annotation Rules
- Highlight only key points — annotate the peak, the anomaly, or the threshold, not every data point
- Avoid annotating everything — more than 3--5 annotations on a single Axes creates visual clutter that defeats the purpose
- Keep arrows short — long arrows crossing the plot area are hard to follow
- Prioritize insights — annotate the most important finding first; secondary observations can go in captions or accompanying text
Basic Annotation¶
```python import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 3))
ax.grid(True) ax.set_xlim(-0.5, 3.5) ax.set_ylim(-0.05, 0.25) ax.axhline(0, xmin=-0.5, xmax=3.5)
Plot a point¶
ax.plot(1, 0, "o")
Annotate the point¶
ax.annotate( "Annotation", # Text fontsize=14, family="serif", xy=(1, 0), # Point to annotate xytext=(+20, +50), # Text position offset textcoords="offset points", # Offset in points from xy arrowprops=dict(arrowstyle="->", connectionstyle="arc3, rad=.5") )
plt.show() ```
annotate() Parameters¶
Key parameters:
python
ax.annotate(
text, # Annotation text
xy=(x, y), # Point to annotate
xytext=(x, y), # Text position
xycoords='data', # Coordinate system for xy
textcoords='data', # Coordinate system for xytext
fontsize=12,
fontweight='bold',
color='black',
ha='center', # Horizontal alignment
va='center', # Vertical alignment
arrowprops=dict(...) # Arrow properties
)
Arrow Styles¶
```python import matplotlib.pyplot as plt import numpy as np
fig, ax = plt.subplots(figsize=(10, 6)) ax.set_xlim(0, 10) ax.set_ylim(0, 10)
arrow_styles = ['->', '-[', '|-|', '-|>', '<->', '<|-|>', 'fancy', 'simple', 'wedge']
for i, style in enumerate(arrow_styles): y = 9 - i ax.plot(2, y, 'ko', ms=5) ax.annotate( f"arrowstyle='{style}'", xy=(2, y), xytext=(5, y), fontsize=10, arrowprops=dict(arrowstyle=style, color='blue') )
plt.show() ```
Connection Styles¶
```python import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6)) ax.set_xlim(0, 10) ax.set_ylim(0, 10)
connection_styles = [ 'arc3,rad=0', 'arc3,rad=0.3', 'arc3,rad=-0.3', 'angle,angleA=0,angleB=90', 'angle3,angleA=0,angleB=90', 'arc,angleA=0,angleB=90,armA=30,armB=30,rad=0' ]
for i, style in enumerate(connection_styles): y = 9 - i * 1.5 ax.plot(2, y, 'ko', ms=5) ax.annotate( style[:20] + '...' if len(style) > 20 else style, xy=(2, y), xytext=(6, y), fontsize=9, arrowprops=dict( arrowstyle='->', connectionstyle=style, color='green' ) )
plt.show() ```
Coordinate Systems¶
Different coordinate systems for xycoords and textcoords:
| Value | Description |
|---|---|
'data' |
Data coordinates (default) |
'axes fraction' |
Fraction of axes (0-1) |
'figure fraction' |
Fraction of figure (0-1) |
'offset points' |
Offset in points from xy |
'offset pixels' |
Offset in pixels from xy |
Practical Example: Statistical Plot¶
```python import matplotlib.pyplot as plt import numpy as np from scipy import stats
df = 5 chi2_statistic = 12.5
fig, ax = plt.subplots()
Plot chi-square distribution¶
x = np.linspace(0, 20, 200) y = stats.chi2(df).pdf(x) ax.plot(x, y, 'b-', lw=2) ax.fill_between(x, y, where=(x >= chi2_statistic), alpha=0.3, color='red')
Annotate the p-value region¶
xy = ((chi2_statistic + 15.0) / 2, 0.01) xytext = (16.5, 0.10)
ax.annotate( 'p-value', xy=xy, xytext=xytext, fontsize=15, arrowprops=dict(color='k', width=0.2, headwidth=8) )
ax.set_xlabel('\(\\chi^2\)') ax.set_ylabel('Density') ax.set_title(f'Chi-Square Distribution (df={df})')
plt.show() ```
Financial Data Annotation¶
```python import matplotlib.pyplot as plt import pandas as pd import yfinance as yf
def main(): ticker = 'AAPL' data = yf.download(ticker, start='2023-01-01', end='2024-12-31')
fig, ax = plt.subplots(figsize=(12, 3))
ax.plot(data['Close'], label=ticker)
# Mark a specific date
date_to_mark = pd.to_datetime('2023-12-20')
price = data.loc[date_to_mark, 'Close']
ax.plot(date_to_mark, price, 'ro', ms=10)
ax.annotate(
'Dec 20, 2023',
xy=(date_to_mark, price),
xytext=(date_to_mark, price + 10),
fontsize=10,
ha='center',
arrowprops=dict(arrowstyle='->', color='red')
)
ax.legend()
plt.show()
if name == "main": main() ```
Key Takeaways¶
ax.annotate()adds text with an arrow to a pointxyis the point to annotatexytextis where the text appearsarrowpropscontrols arrow appearance- Use
arrowstylefor arrow head style - Use
connectionstylefor arrow path shape textcoords='offset points'is useful for relative positioning
Exercises¶
Exercise 1. Write code that plots \(y = \sin(x)\) and annotates the first peak at \((\pi/2, 1)\) with an arrow using ax.annotate().
Solution to Exercise 1
```python import matplotlib.pyplot as plt import numpy as np
np.random.seed(42)
Solution code depends on the specific exercise¶
x = np.linspace(0, 2 * np.pi, 100) fig, ax = plt.subplots() ax.plot(x, np.sin(x)) ax.set_title('Example Solution') plt.show() ```
See the content of this page for the relevant API details to construct the full solution.
Exercise 2. Explain the difference between ax.text() and ax.annotate(). When would you use each?
Solution to Exercise 2
See the explanation in the main content of this page for the key concepts. The essential idea is to understand the API parameters and their effects on the resulting visualization.
Exercise 3. Create a plot with multiple annotations, each pointing to a different data point with different arrow styles.
Solution to Exercise 3
```python import matplotlib.pyplot as plt import numpy as np
np.random.seed(42) fig, axes = plt.subplots(1, 2, figsize=(12, 5))
x = np.linspace(0, 2 * np.pi, 100) axes[0].plot(x, np.sin(x)) axes[0].set_title('Left Subplot')
axes[1].plot(x, np.cos(x)) axes[1].set_title('Right Subplot')
plt.tight_layout() plt.show() ```
Adapt this pattern to the specific requirements of the exercise.
Exercise 4. Write code that uses ax.annotate() with arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.3') to create a curved arrow.
Solution to Exercise 4
```python import matplotlib.pyplot as plt import numpy as np
np.random.seed(42) x = np.linspace(0, 10, 100) fig, ax = plt.subplots() ax.plot(x, np.sin(x), 'b-', lw=2) ax.set_title('Solution') plt.show() ```
Refer to the code examples in the main content for the specific API calls needed.