Matplotlib Legend: Complete Guide to Adding and Customizing Legends
Updated on
A chart without a legend forces readers to guess which line is which. They match colors mentally, misread data, and draw wrong conclusions. Adding a legend with plt.legend() is simple -- but controlling where it appears, how it looks, and keeping it from covering your data takes more work than most tutorials show.
This guide covers everything from basic legend creation to advanced customization: placement inside and outside the plot, multi-column layouts, custom handles, and legends for complex figures.
Basic Legend
Automatic Labels
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x), label='sin(x)')
plt.plot(x, np.cos(x), label='cos(x)')
plt.legend()
plt.show()The label parameter in plot() defines what appears in the legend. Call plt.legend() to display it.
Object-Oriented Style
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x), label='sin(x)')
ax.plot(x, np.cos(x), label='cos(x)')
ax.legend()
plt.show()Legend Placement
Using loc Parameter
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x), label='sin(x)')
ax.plot(x, np.cos(x), label='cos(x)')
# Common locations
ax.legend(loc='upper right') # Default
# ax.legend(loc='upper left')
# ax.legend(loc='lower right')
# ax.legend(loc='lower left')
# ax.legend(loc='center')
# ax.legend(loc='best') # Auto-picks least overlapping spot
plt.show()All loc Options
| loc String | loc Number |
|---|---|
'best' | 0 |
'upper right' | 1 |
'upper left' | 2 |
'lower left' | 3 |
'lower right' | 4 |
'right' | 5 |
'center left' | 6 |
'center right' | 7 |
'lower center' | 8 |
'upper center' | 9 |
'center' | 10 |
Precise Positioning with bbox_to_anchor
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x), label='sin(x)')
ax.plot(x, np.cos(x), label='cos(x)')
ax.plot(x, np.sin(x) + np.cos(x), label='sin(x) + cos(x)')
# Position legend at specific coordinates (x, y) in axes fraction
ax.legend(loc='upper left', bbox_to_anchor=(0.02, 0.98))
plt.show()Legend Outside the Plot
When the legend overlaps data, move it outside:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(10, 5))
for i in range(6):
ax.plot(x, np.sin(x + i * 0.5), label=f'Phase {i}')
# Place legend to the right of the plot
ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
plt.tight_layout()
plt.show()import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(8, 6))
for i in range(5):
ax.plot(x, np.sin(x + i), label=f'Series {i+1}')
# Place legend below the plot
ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1), ncol=5)
plt.tight_layout()
plt.show()Styling the Legend
Font Size and Frame
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x), label='sin(x)')
ax.plot(x, np.cos(x), label='cos(x)')
ax.legend(
fontsize=12,
frameon=True, # Show border (default True)
framealpha=0.8, # Border transparency
facecolor='lightyellow', # Background color
edgecolor='gray', # Border color
shadow=True, # Drop shadow
)
plt.show()Multi-Column Layout
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(10, 5))
for i in range(8):
ax.plot(x, np.sin(x + i * 0.4), label=f'Signal {i+1}')
# 4 columns for compact legend
ax.legend(ncol=4, loc='upper center', fontsize=9)
plt.show()Title for Legend
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, x**2, label='Quadratic')
ax.plot(x, x**1.5, label='Power 1.5')
ax.plot(x, x, label='Linear')
ax.legend(title='Growth Models', title_fontsize=13, fontsize=11)
plt.show()Custom Legend Handles
Manual Legend Entries
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.lines as mlines
import numpy as np
fig, ax = plt.subplots(figsize=(8, 5))
# Plot without labels
ax.scatter(np.random.randn(50), np.random.randn(50), c='blue', s=20)
ax.scatter(np.random.randn(50) + 2, np.random.randn(50), c='red', s=20)
# Create custom handles
blue_patch = mpatches.Patch(color='blue', label='Group A')
red_patch = mpatches.Patch(color='red', label='Group B')
line_handle = mlines.Line2D([], [], color='green', linestyle='--', label='Threshold')
ax.legend(handles=[blue_patch, red_patch, line_handle])
plt.show()Legend for Scatter with Color Mapping
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(8, 5))
categories = ['A', 'B', 'C']
colors = ['#e74c3c', '#3498db', '#2ecc71']
for cat, color in zip(categories, colors):
x = np.random.randn(30)
y = np.random.randn(30)
ax.scatter(x, y, c=color, label=f'Category {cat}', alpha=0.7, s=50)
ax.legend()
plt.show()Legend Customization Options
| Parameter | Description | Example |
|---|---|---|
loc | Position string or number | 'upper right', 2 |
bbox_to_anchor | Anchor point (x, y) | (1.0, 0.5) |
ncol | Number of columns | 3 |
fontsize | Text size | 12, 'small' |
title | Legend title | 'My Legend' |
frameon | Show frame | True, False |
framealpha | Frame transparency | 0.8 |
facecolor | Background color | 'white' |
edgecolor | Border color | 'gray' |
shadow | Drop shadow | True |
markerscale | Marker size multiplier | 1.5 |
labelspacing | Vertical space between entries | 0.5 |
handlelength | Length of legend line | 2.0 |
Practical Examples
Legend for Multiple Plot Types
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(5)
values = [23, 45, 56, 78, 32]
trend = [20, 35, 50, 65, 40]
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(x, values, alpha=0.7, label='Actual', color='steelblue')
ax.plot(x, trend, 'ro-', label='Trend', linewidth=2)
ax.axhline(y=50, color='green', linestyle='--', label='Target')
ax.legend(loc='upper left')
ax.set_xlabel('Quarter')
ax.set_ylabel('Revenue ($K)')
plt.show()Separate Legends for Twin Axes
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(2020, 2027)
revenue = [100, 120, 115, 140, 160, 175, 200]
employees = [50, 55, 52, 60, 70, 75, 85]
fig, ax1 = plt.subplots(figsize=(8, 5))
line1 = ax1.plot(x, revenue, 'b-o', label='Revenue ($M)')
ax1.set_ylabel('Revenue ($M)', color='blue')
ax2 = ax1.twinx()
line2 = ax2.plot(x, employees, 'r-s', label='Employees')
ax2.set_ylabel('Employees', color='red')
# Combine legends
lines = line1 + line2
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left')
plt.show()Interactive Data Exploration
When iterating on chart aesthetics and legend placement, PyGWalker (opens in a new tab) lets you build interactive visualizations in Jupyter by dragging columns -- legends, colors, and sizes are generated automatically:
import pandas as pd
import pygwalker as pyg
df = pd.read_csv('your_data.csv')
walker = pyg.walk(df)FAQ
How do I add a legend to a Matplotlib plot?
Add label='name' to each plot(), scatter(), or bar() call, then call plt.legend() or ax.legend(). The legend automatically uses the labels you provided.
How do I move the legend outside the plot?
Use bbox_to_anchor with loc. For right side: ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5)). For below: ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1)). Call plt.tight_layout() to prevent clipping.
How do I change legend font size in Matplotlib?
Pass fontsize to legend(): ax.legend(fontsize=14) or use string sizes like 'small', 'medium', 'large'. For the title: ax.legend(title='Title', title_fontsize=16).
How do I create a legend with multiple columns?
Use the ncol parameter: ax.legend(ncol=3) creates a 3-column layout. This is useful when you have many legend entries and want a compact horizontal layout.
How do I remove the legend border?
Set frameon=False: ax.legend(frameon=False). To keep the frame but make it transparent, use framealpha=0: ax.legend(framealpha=0).
Conclusion
Matplotlib legends start with label parameters and plt.legend(). Use loc for standard positions, bbox_to_anchor to move the legend outside the plot, ncol for multi-column layouts, and styling parameters like fontsize, frameon, and shadow for customization. For plots with many series, place the legend outside the axes area and call tight_layout() to prevent clipping. For complex figures with twin axes, combine line handles manually to create a unified legend.