Donut & Nested Pie Charts

Reviewed & published by Brayan K

A donut chart is a pie chart with its center cut out, and a nested pie stacks two or more rings, letting you show a whole-to-part breakdown with a cleaner, more modern look than a solid pie.

Part of the free Matplotlib course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll carve a hole with wedgeprops, label slices with autopct, set the startangle, add a legend, and stack two rings into a nested pie.

What You'll Learn in This Lesson

1 From Pie to Donut

A pie chart slices a whole into proportional wedges with plt.pie(values). To make a donut, pass wedgeprops=dict(width=0.4): the width is the ring's thickness inward from the outer edge, so a smaller width leaves a bigger hole. The empty center is a great place for a total or a label.

import matplotlib.pyplot as plt

labels = ["Rent", "Food", "Transport", "Savings"]
amounts = [1200, 600, 300, 400]

fig, ax = plt.subplots()
ax.pie(amounts, labels=labels,
       autopct="%1.1f%%",        # percentage on each slice
       startangle=90,            # first slice starts at the top
       wedgeprops=dict(width=0.4))  # carve the donut hole

# A label in the hole
ax.text(0, 0, "Budget", ha="center", va="center", fontsize=14)

ax.set_aspect("equal")
ax.set_title("Monthly Budget Donut")

plt.show()
The chart this code draws: Carve a hole with wedgeprops and label the slices
What this code draws, rendered with Matplotlib.

What you'll see: a ring split into four labeled slices with percentages, a hollow center holding the word "Budget", and the first slice starting cleanly at the top thanks to startangle=90.

2 Labels, Legend & Exploding a Slice

Replace cramped on-slice labels with a clean legend off to the side, pick your own colors, and pull a slice out for emphasis with the explode argument — a list of offsets, one per slice.

import matplotlib.pyplot as plt

browsers = ["Chrome", "Safari", "Edge", "Firefox", "Other"]
share = [64, 19, 5, 3, 9]
colors = ["#4285F4", "#34A853", "#0078D7", "#FF7139", "#999999"]

# Pull the biggest slice out a little
explode = [0.06, 0, 0, 0, 0]

fig, ax = plt.subplots()
wedges, _, _ = ax.pie(
    share, colors=colors, explode=explode,
    autopct="%1.0f%%", startangle=140,
    wedgeprops=dict(width=0.45, edgecolor="white"))

# A legend instead of crowded slice labels
ax.legend(wedges, browsers, title="Browser",
          loc="center left", bbox_to_anchor=(1.0, 0.5))

ax.set_aspect("equal")
ax.set_title("Browser Market Share")
fig.tight_layout()

plt.show()
The chart this code draws: Add a legend, custom colors, and an exploded slice
What this code draws, rendered with Matplotlib.

3 Nested Rings

A nested pie draws two donuts on the same axes at different radii. Plot the outer ring at radius=1, then the inner ring at a smaller radius. Order the inner values so each sub-group sits beneath its parent slice, and you get a sunburst-style whole-to-part breakdown.

import matplotlib.pyplot as plt

# Outer ring: two parent categories
outer = [60, 40]
outer_colors = ["#1f77b4", "#ff7f0e"]

# Inner ring: each parent split into two children
inner = [35, 25, 30, 10]
inner_colors = ["#aec7e8", "#9edae5", "#ffbb78", "#ffd9a0"]

fig, ax = plt.subplots()

# Outer ring at radius 1
ax.pie(outer, radius=1.0, colors=outer_colors,
       labels=["Hardware", "Software"],
       wedgeprops=dict(width=0.3, edgecolor="white"),
       startangle=90)

# Inner ring at radius 0.7
ax.pie(inner, radius=0.7, colors=inner_colors,
       wedgeprops=dict(width=0.3, edgecolor="white"),
       startangle=90)

ax.set_aspect("equal")
ax.set_title("Nested Pie: Revenue Breakdown")

plt.show()
The chart this code draws: Stack two rings at different radii
What this code draws, rendered with Matplotlib.

What you'll see: an outer ring of two parent categories surrounding a thinner inner ring that splits each parent into its children — a compact two-level breakdown in a single circular chart.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to turn this pie into a donut with percentage labels.

import matplotlib.pyplot as plt

labels = ["Desktop", "Mobile", "Tablet"]
visits = [55, 38, 7]

fig, ax = plt.subplots()
ax.pie(visits, labels=labels,
       autopct="%1.0f%%",
       startangle=90,
       # Carve a hole 0.35 thick to make a donut
       wedgeprops=dict(width=___))

ax.set_aspect("equal")
ax.set_title("Traffic by Device")

# Display the figure
plt.___()

📋 Common Errors & Quick Reference

❌ My donut is an oval

The axes are not square. ✅ Call ax.set_aspect('equal') or plt.axis('equal').

❌ The percentages do not add to 100

pie normalizes your values to fractions of their sum. ✅ That is expected — the slices always represent shares of the total.

❌ The inner ring covers the outer one

Both rings used radius=1. ✅ Give the inner ring a smaller radius and a matching width.

ArgumentWhat It Does
wedgeprops=dict(width=0.4)Carves the donut hole
autopct='%1.1f%%'Labels each slice's share
startangle=90Where the first slice begins
explode=[...]Pulls a slice outward
radius=0.7Sizes a ring for nesting

🎯 Mini-Challenge: A Survey Donut

Build a donut of survey responses with custom colors, percentage labels, and the total count in the center.

import matplotlib.pyplot as plt

answers = ["Yes", "No", "Maybe"]
counts = [120, 60, 20]
colors = ["#2ca02c", "#d62728", "#7f7f7f"]

fig, ax = plt.subplots()
ax.pie(counts, labels=answers, colors=colors,
       autopct="%1.0f%%", startangle=90,
       wedgeprops=dict(width=0.4, edgecolor="white"))

# Total in the hole
total = sum(counts)
ax.text(0, 0, f"n={total}", ha="center", va="center", fontsize=16)

ax.set_aspect("equal")
ax.set_title("Survey Results")

plt.show()
The chart this code draws: A colored survey donut with the total in the center
What this code draws, rendered with Matplotlib.
import matplotlib.pyplot as plt

labels = ["Cash", "Card", "Online"]
share = [15, 55, 30]

fig, ax = plt.subplots()
wedges, texts = ax.pie(share, labels=labels, wedgeprops=dict(width=0.4))
ax.set_aspect("equal")

print("Wedges:", len(wedges))
print("Ring width:", wedges[0].width)
print("It is a ring, not a disc:", wedges[0].width < 1.0)
print("Shares add to:", sum(share))
print("Degrees per share:", [round(s / sum(share) * 360, 1) for s in share])
print("Labels:", [t.get_text() for t in texts])

# ✅ Expected output:
# Wedges: 3
# Ring width: 0.4
# It is a ring, not a disc: True
# Shares add to: 100
# Degrees per share: [54.0, 198.0, 108.0]
# Labels: ['Cash', 'Card', 'Online']
The chart this code draws: The wedge width that turns a pie into a ring, and the degrees each share is worth
What this code draws, rendered with Matplotlib.

❓ Frequently Asked Questions

Lesson complete — your pies got an upgrade!

You carved donut holes with wedgeprops, labeled slices with autopct, set the start angle, exploded a slice, added a legend, and stacked two rings into a nested pie.

Practice quiz

How do you turn a pie chart into a donut?

  • Pass wedgeprops=dict(width=0.4)
  • Set startangle=90
  • Use plt.donut()
  • Pass hole=True

Answer: Pass wedgeprops=dict(width=0.4). wedgeprops with a width carves a hole, turning the pie into a donut.

In wedgeprops=dict(width=0.4), what does width measure?

  • The figure width
  • The ring's thickness inward from the outer edge
  • The gap between slices
  • The label size

Answer: The ring's thickness inward from the outer edge. width is the ring thickness measured inward from the outer radius of 1.

Which argument prints a percentage on each slice?

  • labels
  • explode
  • autopct
  • colors

Answer: autopct. autopct='%1.1f%%' formats and shows each slice's share.

What does startangle=90 do?

  • Hides the first slice
  • Makes the chart an oval
  • Adds a legend
  • Starts the first wedge at the top

Answer: Starts the first wedge at the top. startangle rotates where the first slice begins; 90 starts it at twelve o'clock.

Which argument pulls a slice outward for emphasis?

  • explode
  • radius
  • width
  • autopct

Answer: explode. explode is a list of offsets, one per slice, to pull slices out.

How many lists can ax.pie() return?

  • Exactly one
  • Up to three: wedges, texts, autotexts
  • Exactly two
  • None

Answer: Up to three: wedges, texts, autotexts. ax.pie() returns wedges, texts, and (with autopct) autotexts.

How do you keep a donut a perfect circle instead of an oval?

  • Set width=1
  • Use startangle=0
  • Call ax.set_aspect('equal')
  • Add more slices

Answer: Call ax.set_aspect('equal'). ax.set_aspect('equal') (or plt.axis('equal')) makes it circular.

How do you build a nested (two-ring) pie?

  • Pass nested=True
  • Use plt.pie twice with one radius
  • Set autopct on both
  • Draw two pies at different radii (e.g. radius=1 and radius=0.7)

Answer: Draw two pies at different radii (e.g. radius=1 and radius=0.7). Plot the outer ring at radius 1 and the inner ring at a smaller radius.

Why do a pie's percentages always sum to 100%?

  • pie normalizes values to fractions of their sum
  • It is a bug
  • Only when autopct is set
  • Because of startangle

Answer: pie normalizes values to fractions of their sum. pie normalizes the values to shares of the total, so they sum to 100%.

If the inner ring covers the outer ring, what went wrong?

  • Wrong colormap
  • Both rings used radius=1
  • autopct was missing
  • startangle was 90

Answer: Both rings used radius=1. Give the inner ring a smaller radius (and matching width) so it nests inside.

Continue this course

Frequently asked questions

How do I turn a pie chart into a donut?

Pass wedgeprops with a width to plt.pie: plt.pie(values, wedgeprops=dict(width=0.4)). The width is the thickness of the ring measured inward from the outer radius of 1, so width=0.4 leaves a hole of radius 0.6 in the middle. A smaller width gives a thinner ring and a bigger hole.

How do I add a percentage label to each slice?

Use the autopct argument, a format string applied to each slice's share: plt.pie(values, autopct='%1.1f%%') prints one decimal place followed by a percent sign. You can also pass a function to autopct for full control, for example converting the percentage back into the raw count.

What does startangle do?

startangle rotates where the first slice begins, measured in degrees counter-clockwise from the positive x-axis. startangle=90 starts the first wedge at the top, which is the convention most readers expect, so slices then proceed clockwise or counter-clockwise from twelve o'clock.

How do I make a nested (multi-level) pie?

Draw two pies on the same axes at different radii. Plot the outer ring first with radius=1 and a wedgeprops width, then plot the inner ring with a smaller radius (for example radius=0.7) and its own width. Aligning the inner categories under their outer parents creates a sunburst-style breakdown.