|
| 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