Mathew K Analytics

Lesson 20 · Data visualisation in python

How to Use Annotations and Highlights to Enhance Data Visualization in Plots

In this lesson, you'll learn how to add notes and highlights to your plots. We will use real-world time series data to make our graphs clear and…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Welcome to Python Plot Annotations & Highlights!#

In this lesson, you'll learn how to add notes and highlights to your plots. We will use real-world time series data to make our graphs clear and informative. By the end, you'll be able to add arrows, boxes, and colored spans to your charts.

Let's get started!

# Let's import the basic libraries we need for plotting.
import warnings; warnings.filterwarnings('ignore')
import pandas as pd
import matplotlib.pyplot as plt
# Data setup
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv"
df = pd.read_csv(url)
df.head()
Month Passengers
0 1949-01 112
1 1949-02 118
2 1949-03 132
3 1949-04 129
4 1949-05 121
# Check the shape of the data
print('Shape:', df.shape)
Shape: (144, 2)
# Convert Month to datetime for proper plotting
df['Month'] = pd.to_datetime(df['Month'])
df = df.set_index('Month')
# Plot the basic time series
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'], label='Monthly Passengers')
plt.title('Monthly Airline Passengers')
plt.xlabel('Month')
plt.ylabel('Number of Passengers')
plt.legend()
plt.show()
No description has been provided for this image

Why Add Annotations?#

Annotations help highlight important points in your data. You can add arrows, text notes, or colored areas to tell a story with your chart.

We use them to draw attention or explain special moments in the data.

# Add a simple text annotation
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'], label='Monthly Passengers')
plt.title('Passengers with Text Annotation')
plt.xlabel('Month')
plt.ylabel('Number of Passengers')
plt.legend()
plt.annotate('See big jump',
             xy=(df.index[20], df['Passengers'].iloc[20]),
             xytext=(df.index[10], 250),
             arrowprops=dict(facecolor='black', arrowstyle='->'))
plt.show()
No description has been provided for this image
# Highlight the highest point
max_idx = df['Passengers'].idxmax()
max_val = df['Passengers'].max()
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'], label='Monthly Passengers')
plt.scatter([max_idx], [max_val], color='red', zorder=5)
plt.annotate(f'Peak: {max_val}', (max_idx, max_val),
             xytext=(max_idx, max_val+30),
             ha='center', color='red',
             arrowprops=dict(facecolor='red', arrowstyle='->'))
plt.title('Highlight the Peak')
plt.legend()
plt.show()
No description has been provided for this image
# Drawing an important time window using a colored span
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
plt.axvspan(df.index[12], df.index[24], color='yellow', alpha=0.3)
plt.title('Highlight a Time Period')
plt.show()
No description has been provided for this image
# Highlight values above a certain threshold
threshold = 400
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
plt.axhline(threshold, color='green', linestyle='--', lw=2, label='Threshold')
over_idx = df[df['Passengers'] > threshold].index
over_val = df['Passengers'][df['Passengers'] > threshold]
plt.scatter(over_idx, over_val, color='green')
plt.title('Highlight All Points Above 400 Passengers')
plt.legend()
plt.show()
No description has been provided for this image

Using Shapes to Explain Events#

You can use rectangles or vertical spans to show holidays, events, or actions in your plot. This helps viewers see related changes easily. Let us learn how.

# Add a rectangle using patches
import matplotlib.patches as patches
fig, ax = plt.subplots(figsize=(10,5))
ax.plot(df.index, df['Passengers'])
rect = patches.Rectangle((df.index[40], 100),
                         df.index[10]-df.index[0], 400,
                         linewidth=2, edgecolor='purple', facecolor='none')
ax.add_patch(rect)
plt.title('Rectangle Highlight for Special Period')
plt.show()
No description has been provided for this image
# Highlight weekends over a year (example of iteration and annotation)
import numpy as np
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
for idx in df.index[:12]:
    if idx.month in [6,7,8]:
        plt.axvline(idx, color='pink', alpha=0.3)
plt.title('Highlight Summer Months')
plt.show()
No description has been provided for this image
# Annotating with images (advanced, optional)
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
# Example uses a small matrix as an image (no file needed)
arr = np.zeros((10,10))
arr[2:8,2:8] = 1
fig, ax = plt.subplots(figsize=(10,5))
ax.plot(df.index, df['Passengers'])
imagebox = OffsetImage(arr, cmap='Blues', zoom=1)
ab = AnnotationBbox(imagebox, (df.index[30], df['Passengers'].iloc[30]), frameon=False)
ax.add_artist(ab)
plt.title('Add a Simple Icon Annotation')
plt.show()
No description has been provided for this image
# Add multiple annotations in a loop (quick summary markers)
highlight_idx = [5, 15, 25]
values = df['Passengers'].iloc[highlight_idx]
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
for i, idx in enumerate(highlight_idx):
    plt.annotate(f'Point {i+1}', (df.index[idx], values.iloc[i]),
                 xytext=(df.index[idx], values.iloc[i]+20),
                 ha='center', arrowprops=dict(arrowstyle='->'))
plt.title('Multiple Highlighted Events')
plt.show()
No description has been provided for this image
# Interactive annotation: ask user for a label and point
point = int(input('Which data point (0-143) would you like to annotate? '))
label = input('What label should I use? ')
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
plt.annotate(label, (df.index[point], df['Passengers'].iloc[point]),
             xytext=(df.index[point], df['Passengers'].iloc[point] + 30),
             arrowprops=dict(arrowstyle='->', color='blue'))
plt.title('Your Annotation')
plt.show()
No description has been provided for this image

Recap and Practice#

You have learned

  • How to add notes (annotations) to charts
  • How to use arrows, boxes, colored spans, and even images
  • How to highlight events in data

Practice by choosing your own highlight on a chart today!

# Challenge: highlight largest year-over-year jump
diff = df['Passengers'].diff(12)
biggest = diff.idxmax()
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
plt.annotate('Largest jump', (biggest, df.loc[biggest, 'Passengers']),
             xytext=(biggest, df.loc[biggest, 'Passengers']+40),
             arrowprops=dict(facecolor='orange', arrowstyle='->'))
plt.title('Challenge: Largest Yearly Increase')
plt.show()
No description has been provided for this image
# Troubleshooting: Overlapping labels? Use adjust_text for smart layout
# !pip install adjustText  # Uncomment to install if needed
from adjustText import adjust_text
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
texts = []
for i in [10, 20, 30, 40, 50]:
    texts.append(plt.text(df.index[i], df['Passengers'].iloc[i]+20, f'P{i}', color='crimson'))
adjust_text(texts, arrowprops=dict(arrowstyle='->'))
plt.title('No Overlap: Smart Label Placement')
plt.show()
No description has been provided for this image
# Extra tip: Remove an annotation (refresh the plot)
plt.figure(figsize=(10,5))
plt.plot(df.index, df['Passengers'])
plt.title('No Annotation - Clean Slate')
plt.show()
No description has been provided for this image

You finished! Next steps...#

Try building your own story or project with annotations. Share your creations or questions in the comments.

Like this lesson? Subscribe and check out more videos on our channel!

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.