How to Create a Donut Chart in Matplotlib
Donut charts are circular statistical graphics divided into slices with a hollow center. They're essentially pie charts with the middle cut out, but that seemingly simple difference makes them...
Key Insights
- Donut charts are pie charts with a center hole, created in Matplotlib by setting the
wedgepropsparameter with an inner radius—this simple modification transforms visualization possibilities. - The center hole isn’t just aesthetic; use it to display critical metrics like totals or percentages, making your charts more informative than standard pie charts.
- Limit donut charts to 5-7 categories maximum and avoid them for precise value comparisons—they excel at showing proportional relationships and hierarchical data through nested rings.
Introduction to Donut Charts
Donut charts are circular statistical graphics divided into slices with a hollow center. They’re essentially pie charts with the middle cut out, but that seemingly simple difference makes them significantly more useful for modern data visualization.
The center hole serves a practical purpose: it provides space for displaying aggregate metrics, titles, or key statistics without cluttering the visualization. This makes donut charts ideal for dashboards where space is premium and you need to convey both part-to-whole relationships and summary information simultaneously.
Use donut charts when you want to show proportional data with 3-7 categories. They work particularly well for budget breakdowns, market share analysis, survey results, and any scenario where you need to emphasize one category while showing its relationship to the whole. Avoid them when precise value comparison is critical—bar charts serve that purpose better.
Basic Donut Chart Setup
Creating a donut chart in Matplotlib requires just a few lines of code. The key is using the plt.pie() function with the wedgeprops parameter to create the center hole.
import matplotlib.pyplot as plt
import numpy as np
# Sample data
categories = ['Development', 'Marketing', 'Sales', 'Operations', 'Support']
values = [35, 25, 20, 12, 8]
# Create figure and axis
fig, ax = plt.subplots(figsize=(10, 8))
# Create donut chart
wedges, texts, autotexts = ax.pie(
values,
labels=categories,
autopct='%1.1f%%',
startangle=90,
wedgeprops=dict(width=0.4)
)
# Equal aspect ratio ensures circular shape
ax.axis('equal')
plt.title('Department Budget Distribution', fontsize=16, pad=20)
plt.show()
The magic happens in wedgeprops=dict(width=0.4). The width parameter controls the thickness of the ring—values between 0.3 and 0.5 typically work well. A width of 0.4 means the donut ring occupies 40% of the radius, leaving a 60% hole in the center.
Customizing Appearance
Basic donut charts are functional but bland. Customization transforms them into professional visualizations that communicate effectively.
import matplotlib.pyplot as plt
# Data
categories = ['Development', 'Marketing', 'Sales', 'Operations', 'Support']
values = [35, 25, 20, 12, 8]
# Custom color palette
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']
# Create figure
fig, ax = plt.subplots(figsize=(12, 8))
# Explode the largest slice for emphasis
explode = (0.1, 0, 0, 0, 0)
# Create styled donut
wedges, texts, autotexts = ax.pie(
values,
labels=categories,
autopct='%1.1f%%',
startangle=90,
colors=colors,
explode=explode,
wedgeprops=dict(width=0.4, edgecolor='white', linewidth=2),
textprops=dict(color='black', weight='bold', size=11)
)
# Style percentage labels
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontsize(12)
autotext.set_weight('bold')
ax.axis('equal')
plt.title('Department Budget Distribution', fontsize=18, weight='bold', pad=20)
plt.tight_layout()
plt.show()
The explode parameter accepts a tuple where each value represents how far to pull that slice from the center. Use this sparingly to highlight the most important category. The edgecolor and linewidth parameters in wedgeprops create clean separation between slices, improving readability.
Choose colors deliberately. Use brand colors for company data or colorblind-friendly palettes for accessibility. The example uses a vibrant palette, but you can use any hex color codes or Matplotlib’s built-in color maps.
Adding Center Text and Annotations
The center hole is prime real estate for displaying summary statistics. Use it to show totals, averages, or key metrics that provide context for the proportions.
import matplotlib.pyplot as plt
# Data
categories = ['Development', 'Marketing', 'Sales', 'Operations', 'Support']
values = [35, 25, 20, 12, 8]
total = sum(values)
# Colors
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']
# Create figure
fig, ax = plt.subplots(figsize=(12, 8))
# Create donut
wedges, texts, autotexts = ax.pie(
values,
labels=categories,
autopct='%1.1f%%',
startangle=90,
colors=colors,
wedgeprops=dict(width=0.4, edgecolor='white', linewidth=2)
)
# Add center text
ax.text(0, 0.1, f'${total}M', ha='center', va='center',
fontsize=32, weight='bold', color='#2C3E50')
ax.text(0, -0.15, 'Total Budget', ha='center', va='center',
fontsize=14, color='#7F8C8D')
ax.axis('equal')
plt.title('Annual Department Budget Allocation', fontsize=18, weight='bold', pad=20)
plt.tight_layout()
plt.show()
The ax.text() method positions text at specific coordinates. The center of the chart is at (0, 0), so placing text slightly above and below creates a stacked effect. The ha and va parameters control horizontal and vertical alignment respectively.
Use different font sizes to create visual hierarchy—large for the primary metric, smaller for the label. This draws the eye to the most important number first.
Advanced Techniques
Nested donut charts display hierarchical data by creating multiple concentric rings. This technique is powerful for showing category breakdowns.
import matplotlib.pyplot as plt
import numpy as np
# Hierarchical data: Regions and their product categories
regions = ['North', 'South', 'East', 'West']
region_values = [40, 30, 20, 10]
products = ['Product A', 'Product B', 'Product C', 'Product D',
'Product A', 'Product B', 'Product C',
'Product A', 'Product B',
'Product A']
product_values = [15, 12, 8, 5, # North
12, 10, 8, # South
11, 9, # East
10] # West
# Colors
region_colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A']
product_colors = ['#FFB3B3', '#FFC8C8', '#FFE0E0', '#FFF0F0',
'#9FFFFF', '#B3FFFF', '#D1FFFF',
'#8FD9FF', '#B3E6FF',
'#FFD1B3']
# Create figure
fig, ax = plt.subplots(figsize=(14, 10))
# Outer ring (regions)
wedges1, texts1, autotexts1 = ax.pie(
region_values,
labels=regions,
autopct='%1.1f%%',
startangle=90,
colors=region_colors,
radius=1,
wedgeprops=dict(width=0.3, edgecolor='white', linewidth=2),
textprops=dict(size=12, weight='bold')
)
# Inner ring (products)
wedges2, texts2, autotexts2 = ax.pie(
product_values,
labels=products,
autopct='%1.0f%%',
startangle=90,
colors=product_colors,
radius=0.7,
wedgeprops=dict(width=0.3, edgecolor='white', linewidth=2),
textprops=dict(size=9)
)
# Center text
total_sales = sum(region_values)
ax.text(0, 0.05, f'${total_sales}M', ha='center', va='center',
fontsize=28, weight='bold', color='#2C3E50')
ax.text(0, -0.15, 'Total Sales', ha='center', va='center',
fontsize=12, color='#7F8C8D')
ax.axis('equal')
plt.title('Regional Sales by Product Category', fontsize=18, weight='bold', pad=20)
plt.tight_layout()
plt.show()
The key to nested donuts is using different radius values for each pie() call. The outer ring uses radius=1 and the inner uses radius=0.7. Adjust the width parameter so the rings don’t overlap or leave gaps.
Keep nested charts to two levels maximum. Three or more levels become difficult to interpret and defeat the purpose of clear visualization.
Best Practices and Common Pitfalls
Donut charts are often misused. Follow these guidelines to create effective visualizations:
Limit categories to 5-7 maximum. More categories create a cluttered, unreadable chart. If you have more data points, group smaller categories into “Other” or use a different chart type.
Sort slices logically. Order by size (descending) or by a meaningful sequence (chronological, hierarchical). Random ordering makes patterns harder to spot.
Use colorblind-friendly palettes. About 8% of men and 0.5% of women have color vision deficiency. Use tools like ColorBrewer or Seaborn’s palettes designed for accessibility.
Avoid 3D effects. They distort perception and make accurate comparison impossible. Stick to flat, 2D designs.
Don’t compare multiple donut charts side-by-side. Humans struggle to compare angles across separate charts. Use grouped bar charts instead for comparisons.
Ensure adequate figure size. Too small and labels overlap; too large wastes space. Start with figsize=(10, 8) and adjust based on your category count.
Complete Working Example
Here’s a production-ready example that incorporates all best practices:
import matplotlib.pyplot as plt
def create_revenue_donut(save_path=None):
"""Create a professional donut chart for revenue breakdown."""
# Data
departments = ['Engineering', 'Sales', 'Marketing', 'Operations', 'Customer Success']
revenue = [4500, 3200, 2100, 1800, 1400]
total_revenue = sum(revenue)
# Colorblind-friendly palette
colors = ['#0173B2', '#DE8F05', '#029E73', '#CC78BC', '#CA9161']
# Create figure
fig, ax = plt.subplots(figsize=(12, 9), facecolor='white')
# Create donut chart
wedges, texts, autotexts = ax.pie(
revenue,
labels=departments,
autopct=lambda pct: f'{pct:.1f}%\n(${pct*total_revenue/100:.0f}K)',
startangle=90,
colors=colors,
wedgeprops=dict(width=0.4, edgecolor='white', linewidth=3),
textprops=dict(size=11, weight='bold'),
pctdistance=0.8
)
# Style percentage labels
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontsize(10)
# Center text with total
ax.text(0, 0.08, f'${total_revenue/1000:.1f}M',
ha='center', va='center',
fontsize=36, weight='bold', color='#2C3E50')
ax.text(0, -0.12, 'Total Revenue',
ha='center', va='center',
fontsize=14, color='#7F8C8D', style='italic')
ax.text(0, -0.22, 'Q4 2024',
ha='center', va='center',
fontsize=12, color='#95A5A6')
# Title
plt.title('Quarterly Revenue by Department',
fontsize=20, weight='bold', pad=25, color='#2C3E50')
ax.axis('equal')
plt.tight_layout()
# Save if path provided
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight', facecolor='white')
print(f"Chart saved to {save_path}")
plt.show()
# Create and optionally save the chart
create_revenue_donut(save_path='revenue_donut.png')
This example demonstrates a complete, reusable function that creates a professional donut chart. The autopct parameter uses a lambda function to display both percentages and absolute values. The pctdistance parameter controls how far percentage labels sit from the center—adjust this if labels overlap with the center text.
The function accepts an optional save path, making it easy to generate charts for reports or dashboards. Use dpi=300 for print-quality output and dpi=150 for web use.
Donut charts are powerful tools when used appropriately. Master these techniques and you’ll create clear, informative visualizations that communicate proportional relationships effectively.