From 277b3b04c393c530f84add37d4aa2b6c971ed646 Mon Sep 17 00:00:00 2001 From: ImportanceOfBeingErnest Date: Thu, 5 Apr 2018 21:35:36 +0200 Subject: [PATCH 1/2] add donut examples to doc --- examples/pie_and_polar_charts/nested_pie.py | 62 +++++---- .../pie_and_donut_labels.py | 125 ++++++++++++++++++ 2 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 examples/pie_and_polar_charts/pie_and_donut_labels.py diff --git a/examples/pie_and_polar_charts/nested_pie.py b/examples/pie_and_polar_charts/nested_pie.py index 8f593d36397a..d1a3855b9601 100644 --- a/examples/pie_and_polar_charts/nested_pie.py +++ b/examples/pie_and_polar_charts/nested_pie.py @@ -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. """ @@ -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() ############################################################################### @@ -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() diff --git a/examples/pie_and_polar_charts/pie_and_donut_labels.py b/examples/pie_and_polar_charts/pie_and_donut_labels.py new file mode 100644 index 000000000000..c9a8eea6a5f1 --- /dev/null +++ b/examples/pie_and_polar_charts/pie_and_donut_labels.py @@ -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 ` and +show how to label them with a :meth:`legend ` +as well as with :meth:`annotations `. +""" + +############################################################################### +# 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 ` 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 `. 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 = ["", "left", "right"][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. From 7f33bb189552d81d895b02b5b2d8a62dc1482f21 Mon Sep 17 00:00:00 2001 From: ImportanceOfBeingErnest Date: Thu, 5 Apr 2018 22:48:29 +0200 Subject: [PATCH 2/2] add donut examples to doc (final) --- examples/pie_and_polar_charts/pie_and_donut_labels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pie_and_polar_charts/pie_and_donut_labels.py b/examples/pie_and_polar_charts/pie_and_donut_labels.py index c9a8eea6a5f1..7e6e606e8cb2 100644 --- a/examples/pie_and_polar_charts/pie_and_donut_labels.py +++ b/examples/pie_and_polar_charts/pie_and_donut_labels.py @@ -109,7 +109,7 @@ def func(pct, allvals): ang = (p.theta2 - p.theta1)/2. + p.theta1 y = np.sin(np.deg2rad(ang)) x = np.cos(np.deg2rad(ang)) - horizontalalignment = ["", "left", "right"][int(np.sign(x))] + 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),