Pie Chart Customization¶
This document consolidates the key customization parameters for pie charts: startangle, shadow, counterclock, explode, radius, and autopct.
Mental Model
Pie chart customization controls two things: geometry (where slices start, which direction they go, how far they explode outward) and labeling (percentage format via autopct, label distance via labeldistance). The startangle=90 convention puts the first slice at 12 o'clock, which is the most natural orientation for Western readers.
Design Principles: Clarity Over Decoration
text
Visualization priority order:
1. Accuracy — correct data mapping
2. Clarity — easy to read
3. Efficiency — fast to interpret
4. Aesthetics — last priority
Guidelines for professional pie charts:
- Explode sparingly — offset 1–2 key slices at most. Exploding everything defeats the purpose.
- Avoid shadow in publication-quality figures — shadows distort perceived slice sizes.
- Prefer clean color palettes — high-contrast, colorblind-friendly, no gradients.
- Use labels or legend, not both — redundant labeling clutters.
Parameter Overview¶
| Parameter | Type | Default | Description |
|---|---|---|---|
startangle |
float | 0 | Rotation angle in degrees (counterclockwise from 3 o'clock) |
shadow |
bool | False | Draw shadow beneath pie |
counterclock |
bool | True | Wedge direction (True=CCW, False=CW) |
explode |
array-like | None | Offset distance for each wedge |
radius |
float | 1 | Pie chart radius |
autopct |
str or callable | None | Format string for percentage labels |
Setup¶
```python import matplotlib.pyplot as plt import numpy as np
vals = [1400, 600, 300, 410, 250] labels = ["Home Rent", "Food", "Phone/Internet", "Car", "Utilities"] colors = ['#e74c3c', '#3498db', '#9b59b6', '#f39c12', '#2ecc71'] ```
startangle¶
Controls the starting position of the first wedge. The angle is measured counterclockwise from the positive x-axis (3 o'clock position).
Basic Usage¶
```python fig, axes = plt.subplots(1, 4, figsize=(16, 4))
angles = [0, 45, 90, 180]
for ax, angle in zip(axes, angles): ax.pie(vals, labels=labels, startangle=angle) ax.set_title(f'startangle={angle}°')
plt.tight_layout() plt.show() ```
Common Patterns¶
```python fig, axes = plt.subplots(1, 3, figsize=(15, 5))
Default: starts at 3 o'clock¶
axes[0].pie(vals, labels=labels, startangle=0) axes[0].set_title('startangle=0° (3 o\'clock)')
Top start: common for presentations¶
axes[1].pie(vals, labels=labels, startangle=90) axes[1].set_title('startangle=90° (12 o\'clock)')
Custom positioning¶
axes[2].pie(vals, labels=labels, startangle=140) axes[2].set_title('startangle=140°')
plt.tight_layout() plt.show() ```
shadow¶
Adds a shadow effect beneath the pie chart for visual depth.
Basic Usage¶
```python fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].pie(vals, labels=labels, shadow=False) axes[0].set_title('shadow=False')
axes[1].pie(vals, labels=labels, shadow=True) axes[1].set_title('shadow=True')
plt.tight_layout() plt.show() ```
Shadow with Other Parameters¶
python
fig, ax = plt.subplots(figsize=(8, 8))
ax.pie(
vals,
labels=labels,
colors=colors,
shadow=True,
startangle=90,
autopct='%1.1f%%'
)
ax.set_title('Shadow with Styling')
plt.show()
counterclock¶
Controls the direction in which wedges are drawn.
Basic Usage¶
```python fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].pie(vals, labels=labels, counterclock=True, startangle=90) axes[0].set_title('counterclock=True (default)')
axes[1].pie(vals, labels=labels, counterclock=False, startangle=90) axes[1].set_title('counterclock=False')
plt.tight_layout() plt.show() ```
Direction Comparison¶
```python fig, axes = plt.subplots(2, 2, figsize=(12, 10))
configs = [ {'startangle': 90, 'counterclock': True}, {'startangle': 90, 'counterclock': False}, {'startangle': 0, 'counterclock': True}, {'startangle': 0, 'counterclock': False}, ]
for ax, config in zip(axes.flat, configs): ax.pie(vals, labels=labels, **config) ax.set_title(f"startangle={config['startangle']}°, counterclock={config['counterclock']}")
plt.tight_layout() plt.show() ```
explode¶
Offsets wedges from the center to emphasize specific slices.
Explode Syntax¶
```python
Array of offset distances, one per wedge¶
0 = no offset, 0.1 = slight offset, 0.3 = large offset¶
explode = [0.1, 0, 0, 0, 0] # Explode first slice only ```
Basic Usage¶
```python fig, axes = plt.subplots(1, 3, figsize=(15, 5))
Single slice¶
axes[0].pie(vals, labels=labels, explode=[0.1, 0, 0, 0, 0]) axes[0].set_title('Explode First Slice')
Multiple slices¶
axes[1].pie(vals, labels=labels, explode=[0.1, 0, 0.15, 0, 0.1]) axes[1].set_title('Explode Multiple')
All slices¶
axes[2].pie(vals, labels=labels, explode=[0.05, 0.05, 0.05, 0.05, 0.05]) axes[2].set_title('Explode All')
plt.tight_layout() plt.show() ```
Dynamic Explode¶
```python
Explode the largest slice¶
max_idx = vals.index(max(vals)) explode = [0.1 if i == max_idx else 0 for i in range(len(vals))]
fig, ax = plt.subplots() ax.pie(vals, labels=labels, explode=explode, autopct='%1.1f%%') ax.set_title('Highlight Maximum Value') plt.show() ```
Explode Values Comparison¶
```python fig, axes = plt.subplots(1, 4, figsize=(16, 4))
offsets = [0.05, 0.1, 0.2, 0.3]
for ax, offset in zip(axes, offsets): ax.pie(vals, labels=labels, explode=[offset, 0, 0, 0, 0]) ax.set_title(f'explode={offset}')
plt.tight_layout() plt.show() ```
radius¶
Controls the size of the pie chart.
Basic Usage¶
```python fig, axes = plt.subplots(1, 4, figsize=(16, 4))
radii = [0.5, 0.8, 1.0, 1.3]
for ax, r in zip(axes, radii): ax.pie(vals, labels=labels, radius=r) ax.set_title(f'radius={r}')
plt.tight_layout() plt.show() ```
Donut Charts¶
Use radius with wedgeprops to create donut charts.
```python fig, axes = plt.subplots(1, 3, figsize=(15, 5))
Basic donut¶
axes[0].pie(vals, radius=1.2, wedgeprops=dict(width=0.4)) axes[0].set_title('Basic Donut')
Thin donut¶
axes[1].pie(vals, radius=1.2, wedgeprops=dict(width=0.2)) axes[1].set_title('Thin Donut')
Thick donut¶
axes[2].pie(vals, radius=1.2, wedgeprops=dict(width=0.6)) axes[2].set_title('Thick Donut')
plt.tight_layout() plt.show() ```
Nested Donut Chart¶
```python outer_vals = [1400, 600, 300, 410, 250] inner_vals = [800, 600, 500, 400, 660]
fig, ax = plt.subplots(figsize=(9, 9))
ax.pie(outer_vals, radius=1.3, wedgeprops=dict(width=0.3), autopct='%1.0f%%', pctdistance=0.85) ax.pie(inner_vals, radius=1.0, wedgeprops=dict(width=0.3), autopct='%1.0f%%', pctdistance=0.75)
ax.set_title('Nested Donut Chart') plt.show() ```
autopct¶
Automatically calculates and displays percentage values on each wedge.
Format String Syntax¶
autopct = '%[width].[precision]f%%'
| Component | Description | Example |
|---|---|---|
% |
Format initiator | Required |
width |
Minimum total characters | 4 |
.precision |
Decimal places | .1 |
f |
Float type | Required |
%% |
Literal % symbol | %% |
Basic Usage¶
```python fig, axes = plt.subplots(1, 3, figsize=(15, 5))
formats = ['%.0f%%', '%.1f%%', '%.2f%%'] titles = ['No Decimal', 'One Decimal', 'Two Decimals']
for ax, fmt, title in zip(axes, formats, titles): ax.pie(vals, labels=labels, autopct=fmt) ax.set_title(f"{title}: autopct='{fmt}'")
plt.tight_layout() plt.show() ```
Custom Format Strings¶
```python fig, axes = plt.subplots(1, 3, figsize=(15, 5))
Parentheses¶
axes[0].pie(vals, labels=labels, autopct='(%.1f%%)') axes[0].set_title('Parentheses')
Brackets¶
axes[1].pie(vals, labels=labels, autopct='[%.0f%%]') axes[1].set_title('Brackets')
Custom text¶
axes[2].pie(vals, labels=labels, autopct='%.1f pct') axes[2].set_title('Custom Text')
plt.tight_layout() plt.show() ```
Callable Function¶
Use a function for complete control over formatting.
```python def make_autopct(values): def autopct(pct): total = sum(values) val = int(round(pct * total / 100.0)) return f'{pct:.1f}%\n(${val:,})' return autopct
fig, ax = plt.subplots(figsize=(10, 8)) ax.pie(vals, labels=labels, autopct=make_autopct(vals)) ax.set_title('Percentage with Absolute Values') plt.show() ```
Conditional Display¶
```python def threshold_autopct(pct, threshold=10): return f'{pct:.1f}%' if pct >= threshold else ''
fig, ax = plt.subplots() ax.pie(vals, labels=labels, autopct=lambda p: threshold_autopct(p, 15)) ax.set_title('Show Only Values ≥ 15%') plt.show() ```
Styling Percentage Labels¶
Access returned text objects for custom styling.
```python fig, ax = plt.subplots(figsize=(10, 8)) wedges, texts, autotexts = ax.pie( vals, labels=labels, colors=colors, autopct='%1.1f%%' )
for autotext in autotexts: autotext.set_fontweight('bold') autotext.set_color('white') autotext.set_fontsize(11)
plt.show() ```
Combined Customization¶
Full Example¶
```python fig, ax = plt.subplots(figsize=(10, 8))
wedges, texts, autotexts = ax.pie( vals, labels=labels, colors=colors, startangle=90, counterclock=False, shadow=True, explode=[0.05, 0, 0, 0.1, 0.15], radius=1.2, autopct='%1.1f%%', pctdistance=0.6, labeldistance=1.15 )
for autotext in autotexts: autotext.set_fontweight('bold') autotext.set_color('white')
ax.set_title('Monthly Budget Distribution', fontsize=14, fontweight='bold') plt.show() ```
Professional Dashboard¶
```python fig = plt.figure(figsize=(14, 6))
Main pie chart¶
ax1 = fig.add_subplot(121) wedges, texts, autotexts = ax1.pie( vals, labels=labels, colors=colors, startangle=90, counterclock=False, shadow=True, explode=[0.03, 0, 0, 0, 0], autopct='%1.1f%%', pctdistance=0.6 ) for autotext in autotexts: autotext.set_fontweight('bold') autotext.set_color('white') ax1.set_title('Budget Breakdown', fontsize=13)
Donut chart¶
ax2 = fig.add_subplot(122) ax2.pie( vals, colors=colors, startangle=90, counterclock=False, radius=1.2, wedgeprops=dict(width=0.4), autopct='%1.0f%%', pctdistance=0.8 ) ax2.set_title('Donut Visualization', fontsize=13)
plt.suptitle('Monthly Expenses Analysis', fontsize=15, fontweight='bold') plt.tight_layout() plt.show() ```
Parameter Quick Reference¶
python
ax.pie(
data, # Values
labels=labels, # Wedge labels
colors=colors, # Wedge colors
startangle=90, # Start at 12 o'clock
counterclock=False, # Clockwise direction
shadow=True, # Add shadow
explode=[0.1, 0, 0, 0], # Offset slices
radius=1.2, # Chart size
autopct='%1.1f%%', # Percentage format
pctdistance=0.6, # % label position
labeldistance=1.1, # Label position
wedgeprops=dict( # Wedge styling
width=0.4, # For donut charts
edgecolor='white'
),
textprops=dict( # Text styling
fontsize=11,
fontweight='bold'
)
)
Customization Does Not Fix Perception
Styling improves readability (colors, labels, explode), but it does not change the fundamental limitation: pie charts still rely on angle perception. No amount of customization can make angle as precise as length. If the comparison task demands accuracy, switch to a bar chart — don't try to fix the pie chart with more styling.
Exercises¶
Exercise 1. Write code that creates a pie chart with custom colors using a list of hex codes and the explode parameter to offset the largest slice.
Solution to Exercise 1
```python import matplotlib.pyplot as plt import numpy as np
np.random.seed(42)
Solution code depends on the specific exercise¶
x = np.linspace(0, 2 * np.pi, 100) fig, ax = plt.subplots() ax.plot(x, np.sin(x)) ax.set_title('Example Solution') plt.show() ```
See the content of this page for the relevant API details to construct the full solution.
Exercise 2. Explain the shadow parameter in ax.pie(). Write code demonstrating a pie chart with and without shadow.
Solution to Exercise 2
See the explanation in the main content of this page for the key concepts. The essential idea is to understand the API parameters and their effects on the resulting visualization.
Exercise 3. Create a pie chart with custom text properties: bold percentage labels and italic category labels.
Solution to Exercise 3
```python import matplotlib.pyplot as plt import numpy as np
np.random.seed(42) fig, axes = plt.subplots(1, 2, figsize=(12, 5))
x = np.linspace(0, 2 * np.pi, 100) axes[0].plot(x, np.sin(x)) axes[0].set_title('Left Subplot')
axes[1].plot(x, np.cos(x)) axes[1].set_title('Right Subplot')
plt.tight_layout() plt.show() ```
Adapt this pattern to the specific requirements of the exercise.
Exercise 4. Write code that creates a nested pie chart (ring chart) by calling ax.pie() twice with different radius and width values.
Solution to Exercise 4
import matplotlib.pyplot as plt
outer_sizes = [40, 30, 30]
outer_labels = ['Product', 'Service', 'Other']
outer_colors = ['#ff6b6b', '#4ecdc4', '#45b7d1']
inner_sizes = [20, 20, 15, 15, 10, 20]
inner_colors = ['#ff8a8a', '#ff4d4d', '#6fd6c8', '#3dbfa7',
'#5cc4d4', '#3aabc0']
fig, ax = plt.subplots()
ax.pie(outer_sizes, labels=outer_labels, colors=outer_colors,
radius=1.0, wedgeprops=dict(width=0.3), autopct='%1.0f%%',
pctdistance=0.85)
ax.pie(inner_sizes, colors=inner_colors,
radius=0.7, wedgeprops=dict(width=0.3))
ax.set_title('Nested Pie (Ring Chart)')
plt.show()
Exercise 5. Create a pie chart with 5 slices of sizes 22%, 21%, 20%, 19%, 18%. Then create a bar chart of the same data side-by-side. Explain the perceptual problem: why are differences invisible in the pie but obvious in the bar chart?
Solution to Exercise 5
import matplotlib.pyplot as plt
labels = ['A', 'B', 'C', 'D', 'E']
sizes = [22, 21, 20, 19, 18]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
ax1.pie(sizes, labels=labels, autopct='%1.0f%%', startangle=90)
ax1.set_title('Pie: Can you rank these?')
ax2.bar(labels, sizes, color='steelblue')
ax2.set_ylabel('Percentage')
ax2.set_ylim(0, 30)
ax2.set_title('Bar: Ranking is immediate')
plt.tight_layout()
plt.show()
# The pie slices look nearly identical because angles that differ
# by only 1-2 degrees are below human perceptual threshold.
# The bar chart encodes the same data as length — a channel that
# humans compare with much higher precision.