Skip to content

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 Stringloc 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

ParameterDescriptionExample
locPosition string or number'upper right', 2
bbox_to_anchorAnchor point (x, y)(1.0, 0.5)
ncolNumber of columns3
fontsizeText size12, 'small'
titleLegend title'My Legend'
frameonShow frameTrue, False
framealphaFrame transparency0.8
facecolorBackground color'white'
edgecolorBorder color'gray'
shadowDrop shadowTrue
markerscaleMarker size multiplier1.5
labelspacingVertical space between entries0.5
handlelengthLength of legend line2.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.

📚