Skip to content

Commit 511cde7

Browse files
update nested-pie example with donut
1 parent 19c063f commit 511cde7

File tree

2 files changed

+163
-24
lines changed

2 files changed

+163
-24
lines changed

examples/pie_and_polar_charts/nested_pie.py

+38-24
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
=================
55
66
The following examples show two ways to build a nested pie chart
7-
in Matplotlib.
7+
in Matplotlib. Such charts are often referred to as donut charts.
88
99
"""
1010

@@ -17,17 +17,30 @@
1717
#
1818
# In this case, pie takes values corresponding to counts in a group.
1919
# We'll first generate some fake data, corresponding to three groups.
20-
# In the outer circle, we'll treat each number as belonging to its
21-
# own group. In the inner circle, we'll plot them as members of their
20+
# In the inner circle, we'll treat each number as belonging to its
21+
# own group. In the outer circle, we'll plot them as members of their
2222
# original 3 groups.
23+
#
24+
# The effect of the donut shape is achieved by setting a `width` to
25+
# the pie's wedges through the `wedgeprops` argument.
26+
2327

24-
vals = np.array([[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]])
2528
fig, ax = plt.subplots()
26-
ax.pie(vals.flatten(), radius=1.2,
27-
colors=plt.rcParams["axes.prop_cycle"].by_key()["color"][:vals.shape[1]])
28-
ax.pie(vals.sum(axis=1), radius=1)
29-
ax.set(aspect="equal", title='Pie plot with `ax.pie`')
3029

30+
size = 0.3
31+
vals = np.array([[60., 32.], [37., 40.], [29., 10.]])
32+
33+
cmap = plt.get_cmap("tab20c")
34+
outer_colors = cmap(np.arange(3)*4)
35+
inner_colors = cmap(np.array([1, 2, 5, 6, 9, 10]))
36+
37+
ax.pie(vals.sum(axis=1), radius=1, colors=outer_colors,
38+
wedgeprops=dict(width=size, edgecolor='w'))
39+
40+
ax.pie(vals.flatten(), radius=1-size, colors=inner_colors,
41+
wedgeprops=dict(width=size, edgecolor='w'))
42+
43+
ax.set(aspect="equal", title='Pie plot with `ax.pie`')
3144
plt.show()
3245

3346
###############################################################################
@@ -36,28 +49,29 @@
3649
# the exact design of the plot.
3750
#
3851
# In this case, we need to map x-values of the bar chart onto radians of
39-
# a circle.
52+
# a circle. The cumulative sum of the values are used as the edges
53+
# of the bars.
4054

4155
fig, ax = plt.subplots(subplot_kw=dict(polar=True))
4256

43-
left_inner = np.arange(0.0, 2 * np.pi, 2 * np.pi / 6)
44-
left_middle = np.arange(0.0, 2 * np.pi, 2 * np.pi / 12)
45-
left_outer = np.arange(0.0, 2 * np.pi, 2 * np.pi / 9)
57+
size = 0.3
58+
vals = np.array([[60., 32.], [37., 40.], [29., 10.]])
59+
#normalize vals to 2 pi
60+
valsnorm = vals/np.sum(vals)*2*np.pi
61+
#obtain the ordinates of the bar edges
62+
valsleft = np.cumsum(np.append(0, valsnorm.flatten()[:-1])).reshape(vals.shape)
4663

47-
ax.bar(x=left_inner,
48-
width=2 * np.pi / 6, bottom=0, color='C0',
49-
linewidth=2, edgecolor='w',
50-
height=np.zeros_like(left_inner) + 5)
64+
cmap = plt.get_cmap("tab20c")
65+
outer_colors = cmap(np.arange(3)*4)
66+
inner_colors = cmap(np.array([1, 2, 5, 6, 9, 10]))
5167

52-
ax.bar(x=left_middle,
53-
width=2 * np.pi / 12, bottom=5, color='C1',
54-
linewidth=2, edgecolor='w',
55-
height=np.zeros_like(left_middle) + 2)
68+
ax.bar(x=valsleft[:, 0],
69+
width=valsnorm.sum(axis=1), bottom=1-size, height=size,
70+
color=outer_colors, edgecolor='w', linewidth=1, align="edge")
5671

57-
ax.bar(x=left_outer,
58-
width=2 * np.pi / 9, bottom=7, color='C2',
59-
linewidth=2, edgecolor='w',
60-
height=np.zeros_like(left_outer) + 3)
72+
ax.bar(x=valsleft.flatten(),
73+
width=valsnorm.flatten(), bottom=1-2*size, height=size,
74+
color=inner_colors, edgecolor='w', linewidth=1, align="edge")
6175

6276
ax.set(title="Pie plot with `ax.bar` and polar coordinates")
6377
ax.set_axis_off()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""
2+
==========================
3+
Labeling a pie and a donut
4+
==========================
5+
6+
Welcome to the matplotlib bakery. We will create a pie and a donut
7+
chart through the :meth:`pie method <matplotlib.axes.Axes.pie>` and
8+
show how to label them with a :meth:`legend <matplotlib.axes.Axes.legend>`
9+
as well as with :meth:`annotations <matplotlib.axes.Axes.annotate>`.
10+
"""
11+
12+
###############################################################################
13+
# As usual we would start by defining the imports and create a figure with
14+
# subplots.
15+
# Now it's time for the pie. Starting with a pie recipe, we create the data
16+
# and a list of labels from it.
17+
#
18+
# We can provide a function to the ``autopct`` argument, which will expand
19+
# automatic percentage labeling by showing absolute values; we calculate
20+
# the latter back from realtive data and the known sum of all values.
21+
#
22+
# We then create the pie and store the returned objects for later.
23+
# The first returned element of the returned tuple is a list of the wedges.
24+
# Those are
25+
# :class:`matplotlib.patches.Wedge <matplotlib.patches.Wedge>` patches, which
26+
# can directly be used as the handles for a legend. We can use the
27+
# legend's ``bbox_to_anchor`` argument to position the legend outside of
28+
# the pie. Here we use the axes coordinates ``(1, 0, 0.5, 1)`` together
29+
# with the location ``"center left"``; i.e.
30+
# the left central point of the legend will be at the left central point of the
31+
# bounding box, spanning from ``(1,0)`` to ``(1.5,1)`` in axes coordinates.
32+
33+
import numpy as np
34+
import matplotlib.pyplot as plt
35+
36+
fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))
37+
38+
recipe = ["375 g flour",
39+
"75 g sugar",
40+
"250 g butter",
41+
"300 g berries"]
42+
43+
data = [float(x.split()[0]) for x in recipe]
44+
ingredients = [x.split()[-1] for x in recipe]
45+
46+
47+
def func(pct, allvals):
48+
absolute = int(pct/100.*np.sum(allvals))
49+
return "{:.1f}%\n({:d} g)".format(pct, absolute)
50+
51+
52+
wedges, texts, autotexts = ax.pie(data, autopct=lambda pct: func(pct, data),
53+
textprops=dict(color="w"))
54+
55+
ax.legend(wedges, ingredients,
56+
title="Ingredients",
57+
loc="center left",
58+
bbox_to_anchor=(1, 0, 0.5, 1))
59+
60+
plt.setp(autotexts, size=8, weight="bold")
61+
62+
ax.set_title("Matplotlib bakery: A pie")
63+
64+
plt.show()
65+
66+
67+
###############################################################################
68+
# Now it's time for the donut. Starting with a donut recipe, we transcribe
69+
# the data to numbers (converting 1 egg to 50 g), and directly plot the pie.
70+
# The pie? Wait... it's going to be donut, is it not?
71+
# Well, as we see here, the donut is a pie, having a certain ``width`` set to
72+
# the wedges, which is different from its radius. It's as easy as it gets.
73+
# This is done via the ``wedgeprops`` argument.
74+
#
75+
# We then want to label the wedges via
76+
# :meth:`annotations <matplotlib.axes.Axes.annotate>`. We first create some
77+
# dictionaries of common properties, which we can later pass as keyword
78+
# argument. We then iterate over all wedges and for each
79+
#
80+
# * calculate the angle of the wedge's center,
81+
# * from that obtain the coordinates of the point at that angle on the
82+
# circumference,
83+
# * determine the horizontal alignment of the text, depending on which side
84+
# of the circle the point lies,
85+
# * update the connection style with the obtained angle to have the annotation
86+
# arrow point outwards from the donut,
87+
# * finally, create the annotation with all the previously
88+
# determined parameters.
89+
90+
91+
fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))
92+
93+
recipe = ["225 g flour",
94+
"90 g sugar",
95+
"1 egg",
96+
"60 g butter",
97+
"100 ml milk",
98+
"1/2 package of yeast"]
99+
100+
data = [225, 90, 50, 60, 100, 5]
101+
102+
wedges, texts = ax.pie(data, wedgeprops=dict(width=0.5), startangle=-40)
103+
104+
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
105+
kw = dict(xycoords='data', textcoords='data', arrowprops=dict(arrowstyle="-"),
106+
bbox=bbox_props, zorder=0, va="center")
107+
108+
for i, p in enumerate(wedges):
109+
ang = (p.theta2 - p.theta1)/2. + p.theta1
110+
y = np.sin(np.deg2rad(ang))
111+
x = np.cos(np.deg2rad(ang))
112+
horizontalalignment = ["", "left", "right"][int(np.sign(x))]
113+
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
114+
kw["arrowprops"].update({"connectionstyle": connectionstyle})
115+
ax.annotate(recipe[i], xy=(x, y), xytext=(1.35*np.sign(x), 1.4*y),
116+
horizontalalignment=horizontalalignment, **kw)
117+
118+
ax.set_title("Matplotlib bakery: A donut")
119+
120+
plt.show()
121+
122+
###############################################################################
123+
# And here it is, the donut. Note however, that if we were to use this recipe,
124+
# the ingredients would suffice for around 6 donuts - producing one huge
125+
# donut is untested and might result in kitchen errors.

0 commit comments

Comments
 (0)