Skip to content

update nested-pie example with donut #10953

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 38 additions & 24 deletions examples/pie_and_polar_charts/nested_pie.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
=================

The following examples show two ways to build a nested pie chart
in Matplotlib.
in Matplotlib. Such charts are often referred to as donut charts.

"""

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


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

size = 0.3
vals = np.array([[60., 32.], [37., 40.], [29., 10.]])

cmap = plt.get_cmap("tab20c")
outer_colors = cmap(np.arange(3)*4)
inner_colors = cmap(np.array([1, 2, 5, 6, 9, 10]))

ax.pie(vals.sum(axis=1), radius=1, colors=outer_colors,
wedgeprops=dict(width=size, edgecolor='w'))

ax.pie(vals.flatten(), radius=1-size, colors=inner_colors,
wedgeprops=dict(width=size, edgecolor='w'))

ax.set(aspect="equal", title='Pie plot with `ax.pie`')
plt.show()

###############################################################################
Expand All @@ -36,28 +49,29 @@
# the exact design of the plot.
#
# In this case, we need to map x-values of the bar chart onto radians of
# a circle.
# a circle. The cumulative sum of the values are used as the edges
# of the bars.

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

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

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

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

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

ax.set(title="Pie plot with `ax.bar` and polar coordinates")
ax.set_axis_off()
Expand Down
125 changes: 125 additions & 0 deletions examples/pie_and_polar_charts/pie_and_donut_labels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
==========================
Labeling a pie and a donut
==========================

Welcome to the matplotlib bakery. We will create a pie and a donut
chart through the :meth:`pie method <matplotlib.axes.Axes.pie>` and
show how to label them with a :meth:`legend <matplotlib.axes.Axes.legend>`
as well as with :meth:`annotations <matplotlib.axes.Axes.annotate>`.
"""

###############################################################################
# As usual we would start by defining the imports and create a figure with
# subplots.
# Now it's time for the pie. Starting with a pie recipe, we create the data
# and a list of labels from it.
#
# We can provide a function to the ``autopct`` argument, which will expand
# automatic percentage labeling by showing absolute values; we calculate
# the latter back from realtive data and the known sum of all values.
#
# We then create the pie and store the returned objects for later.
# The first returned element of the returned tuple is a list of the wedges.
# Those are
# :class:`matplotlib.patches.Wedge <matplotlib.patches.Wedge>` patches, which
# can directly be used as the handles for a legend. We can use the
# legend's ``bbox_to_anchor`` argument to position the legend outside of
# the pie. Here we use the axes coordinates ``(1, 0, 0.5, 1)`` together
# with the location ``"center left"``; i.e.
# the left central point of the legend will be at the left central point of the
# bounding box, spanning from ``(1,0)`` to ``(1.5,1)`` in axes coordinates.

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))

recipe = ["375 g flour",
"75 g sugar",
"250 g butter",
"300 g berries"]

data = [float(x.split()[0]) for x in recipe]
ingredients = [x.split()[-1] for x in recipe]


def func(pct, allvals):
absolute = int(pct/100.*np.sum(allvals))
return "{:.1f}%\n({:d} g)".format(pct, absolute)


wedges, texts, autotexts = ax.pie(data, autopct=lambda pct: func(pct, data),
textprops=dict(color="w"))

ax.legend(wedges, ingredients,
title="Ingredients",
loc="center left",
bbox_to_anchor=(1, 0, 0.5, 1))

plt.setp(autotexts, size=8, weight="bold")

ax.set_title("Matplotlib bakery: A pie")

plt.show()


###############################################################################
# Now it's time for the donut. Starting with a donut recipe, we transcribe
# the data to numbers (converting 1 egg to 50 g), and directly plot the pie.
# The pie? Wait... it's going to be donut, is it not?
# Well, as we see here, the donut is a pie, having a certain ``width`` set to
# the wedges, which is different from its radius. It's as easy as it gets.
# This is done via the ``wedgeprops`` argument.
#
# We then want to label the wedges via
# :meth:`annotations <matplotlib.axes.Axes.annotate>`. We first create some
# dictionaries of common properties, which we can later pass as keyword
# argument. We then iterate over all wedges and for each
#
# * calculate the angle of the wedge's center,
# * from that obtain the coordinates of the point at that angle on the
# circumference,
# * determine the horizontal alignment of the text, depending on which side
# of the circle the point lies,
# * update the connection style with the obtained angle to have the annotation
# arrow point outwards from the donut,
# * finally, create the annotation with all the previously
# determined parameters.


fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))

recipe = ["225 g flour",
"90 g sugar",
"1 egg",
"60 g butter",
"100 ml milk",
"1/2 package of yeast"]

data = [225, 90, 50, 60, 100, 5]

wedges, texts = ax.pie(data, wedgeprops=dict(width=0.5), startangle=-40)

bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(xycoords='data', textcoords='data', arrowprops=dict(arrowstyle="-"),
bbox=bbox_props, zorder=0, va="center")

for i, p in enumerate(wedges):
ang = (p.theta2 - p.theta1)/2. + p.theta1
y = np.sin(np.deg2rad(ang))
x = np.cos(np.deg2rad(ang))
horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(recipe[i], xy=(x, y), xytext=(1.35*np.sign(x), 1.4*y),
horizontalalignment=horizontalalignment, **kw)

ax.set_title("Matplotlib bakery: A donut")

plt.show()

###############################################################################
# And here it is, the donut. Note however, that if we were to use this recipe,
# the ingredients would suffice for around 6 donuts - producing one huge
# donut is untested and might result in kitchen errors.