Skip to content

Commit f6e850d

Browse files
authored
ENH: add wedge_labels parameter for pie (#29152)
1 parent 9127a70 commit f6e850d

12 files changed

Lines changed: 262 additions & 81 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
``pie`` *labels* and *labeldistance* parameters
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Currently the *labels* parameter of `~.Axes.pie` is used both for annotating the
4+
pie wedges directly, and for automatic legend entries. For consistency
5+
with other plotting methods, in future *labels* will only be used for the legend.
6+
7+
The *labeldistance* parameter will therefore default to ``None`` from Matplotlib
8+
3.14, when it will also be deprecated and then removed in Matplotlib 3.16. To
9+
preserve the existing behavior for now, set ``labeldistance=1.1``. For the longer
10+
term, to place labels on the wedges use the new *wedge_labels* and
11+
*wedge_label_distance* parameters of `~.Axes.pie` or the `~.Axes.pie_label` method.
12+
Note that `~.Axes.pie_label` allows for more customization of the label positions via
13+
the *rotate* and *alignment* parameters as well as *distance*.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
New *wedge_labels* parameter for pie
2+
------------------------------------
3+
4+
`~.Axes.pie` now accepts a *wedge_labels* parameter as a shortcut to the
5+
`~.Axes.pie_label` method. This may be used for simple annotation of the wedges
6+
of the pie chart. It can take
7+
8+
* a list of strings, similar to the existing *labels* parameter
9+
* a format string similar to the existing *autopct* parameter, except that it
10+
uses the `str.format` method and it can handle absolute values as well as
11+
fractions/percentages
12+
13+
*wedge_labels* has an accompanying *wedge_label_distance* parameter, to control
14+
the distance of the labels from the center of the pie.
15+
16+
17+
.. plot::
18+
:include-source: true
19+
:alt: Two pie charts. The chart on the left has labels 'foo' and 'bar' outside the wedges. The chart on the right has labels '1' and '2' inside the wedges.
20+
21+
import matplotlib.pyplot as plt
22+
23+
fig, (ax1, ax2) = plt.subplots(ncols=2, layout='constrained')
24+
25+
ax1.pie([1, 2], wedge_labels=['foo', 'bar'], wedge_label_distance=1.1)
26+
ax2.pie([1, 2], wedge_labels='{absval:d}', wedge_label_distance=0.6)

galleries/examples/misc/svg_filter_pie.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828

2929
# We want to draw the shadow for each pie, but we will not use "shadow"
3030
# option as it doesn't save the references to the shadow patches.
31-
pie = ax.pie(fracs, explode=explode, labels=labels, autopct='%1.1f%%')
31+
pie = ax.pie(fracs, explode=explode, wedge_labels=labels, wedge_label_distance=1.1)
3232

33-
for w in pie.wedges:
33+
for w, label in zip(pie.wedges, labels):
3434
# set the id with the label.
35-
w.set_gid(w.get_label())
35+
w.set_gid(label)
3636

3737
# we don't want to draw the edge of the pie
3838
w.set_edgecolor("none")

galleries/examples/pie_and_polar_charts/bar_of_pie.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525
explode = [0.1, 0, 0]
2626
# rotate so that first wedge is split by the x-axis
2727
angle = -180 * overall_ratios[0]
28-
pie = ax1.pie(overall_ratios, autopct='%1.1f%%', startangle=angle,
29-
labels=labels, explode=explode)
28+
pie = ax1.pie(overall_ratios, startangle=angle, explode=explode)
29+
30+
# label the wedges with our label strings and the ratios as percentages
31+
ax1.pie_label(pie, labels, distance=1.1)
32+
ax1.pie_label(pie, '{frac:.1%}', distance=0.6)
3033

3134
# bar chart parameters
3235
age_ratios = [.33, .54, .07, .06]

galleries/examples/pie_and_polar_charts/pie_features.py

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,68 @@
1515
# ------------
1616
#
1717
# Plot a pie chart of animals and label the slices. To add
18-
# labels, pass a list of labels to the *labels* parameter
18+
# labels, pass a list of labels to the *wedge_labels* parameter.
1919

2020
import matplotlib.pyplot as plt
2121

2222
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
23-
sizes = [15, 30, 45, 10]
23+
sizes = [12, 24, 36, 8]
2424

2525
fig, ax = plt.subplots()
26-
ax.pie(sizes, labels=labels)
26+
ax.pie(sizes, wedge_labels=labels)
2727

2828
# %%
2929
# Each slice of the pie chart is a `.patches.Wedge` object; therefore in
3030
# addition to the customizations shown here, each wedge can be customized using
3131
# the *wedgeprops* argument, as demonstrated in
3232
# :doc:`/gallery/pie_and_polar_charts/nested_pie`.
3333
#
34+
# Set label positions
35+
# -------------------
36+
# If you want the labels outside the pie, set a *wedge_label_distance* greater than 1.
37+
# This is the distance from the center of the pie as a fraction of its radius.
38+
39+
fig, ax = plt.subplots()
40+
ax.pie(sizes, wedge_labels=labels, wedge_label_distance=1.1)
41+
42+
# %%
43+
#
3444
# Auto-label slices
3545
# -----------------
3646
#
37-
# Pass a function or format string to *autopct* to label slices.
47+
# Pass a format string to *wedge_labels* to label slices with their values...
48+
49+
fig, ax = plt.subplots()
50+
ax.pie(sizes, wedge_labels='{absval:.1f}')
51+
52+
# %%
53+
#
54+
# ...or with their percentages...
55+
56+
fig, ax = plt.subplots()
57+
ax.pie(sizes, wedge_labels='{frac:.1%}')
58+
59+
# %%
60+
#
61+
# ...or both.
3862

3963
fig, ax = plt.subplots()
40-
ax.pie(sizes, labels=labels, autopct='%1.1f%%')
64+
ax.pie(sizes, wedge_labels='{absval:d}\n{frac:.1%}')
65+
66+
# %%
67+
#
68+
# For more control over labels, or to add multiple sets, see
69+
# :doc:`/gallery/pie_and_polar_charts/pie_label`.
4170

4271
# %%
43-
# By default, the label values are obtained from the percent size of the slice.
4472
#
4573
# Color slices
4674
# ------------
4775
#
4876
# Pass a list of colors to *colors* to set the color of each slice.
4977

5078
fig, ax = plt.subplots()
51-
ax.pie(sizes, labels=labels,
52-
colors=['olivedrab', 'rosybrown', 'gray', 'saddlebrown'])
79+
ax.pie(sizes, colors=['olivedrab', 'rosybrown', 'gray', 'saddlebrown'])
5380

5481
# %%
5582
# Hatch slices
@@ -58,22 +85,9 @@
5885
# Pass a list of hatch patterns to *hatch* to set the pattern of each slice.
5986

6087
fig, ax = plt.subplots()
61-
ax.pie(sizes, labels=labels, hatch=['**O', 'oO', 'O.O', '.||.'])
62-
63-
# %%
64-
# Swap label and autopct text positions
65-
# -------------------------------------
66-
# Use the *labeldistance* and *pctdistance* parameters to position the *labels*
67-
# and *autopct* text respectively.
68-
69-
fig, ax = plt.subplots()
70-
ax.pie(sizes, labels=labels, autopct='%1.1f%%',
71-
pctdistance=1.25, labeldistance=.6)
88+
ax.pie(sizes, hatch=['**O', 'oO', 'O.O', '.||.'])
7289

7390
# %%
74-
# *labeldistance* and *pctdistance* are ratios of the radius; therefore they
75-
# vary between ``0`` for the center of the pie and ``1`` for the edge of the
76-
# pie, and can be set to greater than ``1`` to place text outside the pie.
7791
#
7892
# Explode, shade, and rotate slices
7993
# ---------------------------------
@@ -86,11 +100,10 @@
86100
#
87101
# This example orders the slices, separates (explodes) them, and rotates them.
88102

89-
explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
103+
explode = (0, 0.2, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
90104

91105
fig, ax = plt.subplots()
92-
ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
93-
shadow=True, startangle=90)
106+
ax.pie(sizes, explode=explode, wedge_labels='{frac:.1%}', shadow=True, startangle=90)
94107
plt.show()
95108

96109
# %%
@@ -107,8 +120,7 @@
107120

108121
fig, ax = plt.subplots()
109122

110-
ax.pie(sizes, labels=labels, autopct='%.0f%%',
111-
textprops={'size': 'small'}, radius=0.5)
123+
ax.pie(sizes, wedge_labels='{frac:.1%}', textprops={'size': 'small'}, radius=0.5)
112124
plt.show()
113125

114126
# %%
@@ -119,8 +131,8 @@
119131
# the `.Shadow` patch. This can be used to modify the default shadow.
120132

121133
fig, ax = plt.subplots()
122-
ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
123-
shadow={'ox': -0.04, 'edgecolor': 'none', 'shade': 0.9}, startangle=90)
134+
ax.pie(sizes, explode=explode, shadow={'ox': -0.04, 'edgecolor': 'none', 'shade': 0.9},
135+
startangle=90)
124136
plt.show()
125137

126138
# %%

lib/matplotlib/_api/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ from .deprecation import ( # noqa: F401, re-exported API
1919
_T = TypeVar("_T")
2020

2121
class _Unset: ...
22+
UNSET = _Unset()
2223

2324
class classproperty(Any):
2425
def __init__(

lib/matplotlib/axes/_axes.py

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
BarContainer, ErrorbarContainer, PieContainer, StemContainer)
4242
from matplotlib.text import Text
4343
from matplotlib.transforms import _ScaledRotation
44+
from matplotlib._api import UNSET as _UNSET
4445

4546
_log = logging.getLogger(__name__)
4647

@@ -3534,13 +3535,13 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0,
35343535
self.add_container(stem_container)
35353536
return stem_container
35363537

3537-
@_api.make_keyword_only("3.10", "explode")
3538-
@_preprocess_data(replace_names=["x", "explode", "labels", "colors"])
3539-
def pie(self, x, explode=None, labels=None, colors=None,
3540-
autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1,
3541-
startangle=0, radius=1, counterclock=True,
3542-
wedgeprops=None, textprops=None, center=(0, 0),
3543-
frame=False, rotatelabels=False, *, normalize=True, hatch=None):
3538+
@_preprocess_data(replace_names=["x", "explode", "labels", "colors",
3539+
"wedge_labels"])
3540+
def pie(self, x, *, explode=None, labels=None, colors=None, wedge_labels=None,
3541+
wedge_label_distance=0.6, autopct=None, pctdistance=0.6, shadow=False,
3542+
labeldistance=_UNSET, startangle=0, radius=1, counterclock=True,
3543+
wedgeprops=None, textprops=None, center=(0, 0), frame=False,
3544+
rotatelabels=False, normalize=True, hatch=None):
35443545
"""
35453546
Plot a pie chart.
35463547
@@ -3560,7 +3561,13 @@ def pie(self, x, explode=None, labels=None, colors=None,
35603561
of the radius with which to offset each wedge.
35613562
35623563
labels : list, default: None
3563-
A sequence of strings providing the labels for each wedge
3564+
A sequence of strings providing the legend labels for each wedge.
3565+
3566+
.. deprecated:: 3.12
3567+
In future these labels will not appear on the wedges but only
3568+
be made available for the legend (see *labeldistance* below).
3569+
To place labels on the wedges, use *wedge_labels* or the
3570+
`pie_label` method.
35643571
35653572
colors : :mpltype:`color` or list of :mpltype:`color`, default: None
35663573
A sequence of colors through which the pie chart will cycle. If
@@ -3573,12 +3580,35 @@ def pie(self, x, explode=None, labels=None, colors=None,
35733580
35743581
.. versionadded:: 3.7
35753582
3583+
wedge_labels : str or list of str, optional
3584+
A sequence of strings providing the labels for each wedge, or a format
3585+
string with ``absval`` and/or ``frac`` placeholders. For example, to label
3586+
each wedge with its value and the percentage in brackets::
3587+
3588+
wedge_labels="{absval:d} ({frac:.0%})"
3589+
3590+
For more control or to add multiple sets of labels, use `pie_label`
3591+
instead.
3592+
3593+
.. versionadded:: 3.12
3594+
3595+
wedge_label_distance : float, default: 0.6
3596+
The radial position of the wedge labels, relative to the pie radius.
3597+
Values > 1 are outside the wedge and values < 1 are inside the wedge.
3598+
3599+
.. versionadded:: 3.12
3600+
35763601
autopct : None or str or callable, default: None
35773602
If not *None*, *autopct* is a string or function used to label the
35783603
wedges with their numeric value. The label will be placed inside
35793604
the wedge. If *autopct* is a format string, the label will be
35803605
``fmt % pct``. If *autopct* is a function, then it will be called.
35813606
3607+
.. admonition:: Discouraged
3608+
3609+
Consider using the *wedge_labels* parameter or `pie_label`
3610+
method instead.
3611+
35823612
pctdistance : float, default: 0.6
35833613
The relative distance along the radius at which the text
35843614
generated by *autopct* is drawn. To draw the text outside the pie,
@@ -3591,6 +3621,11 @@ def pie(self, x, explode=None, labels=None, colors=None,
35913621
If set to ``None``, labels are not drawn but are still stored for
35923622
use in `.legend`.
35933623
3624+
.. deprecated:: 3.12
3625+
From v3.14 *labeldistance* will default to ``None`` and will
3626+
later be removed altogether. Use *wedge_labels* and
3627+
*wedge_label_distance* or the `pie_label` method instead.
3628+
35943629
shadow : bool or dict, default: False
35953630
If bool, whether to draw a shadow beneath the pie. If dict, draw a shadow
35963631
passing the properties in the dict to `.Shadow`.
@@ -3672,8 +3707,33 @@ def pie(self, x, explode=None, labels=None, colors=None,
36723707
raise ValueError('Cannot plot an unnormalized pie with sum(x) > 1')
36733708
else:
36743709
fracs = x
3710+
3711+
if labeldistance is _UNSET:
3712+
# NB: when the labeldistance default changes, both labeldistance and
3713+
# rotatelabels should be deprecated for removal.
3714+
if labels is not None:
3715+
msg = (
3716+
"From %(removal)s labeldistance will default to None, so that the "
3717+
"strings provided in the labels parameter are only available for "
3718+
"the legend. Later labeldistance will be removed completely. To "
3719+
"preserve existing behavior for now, pass labeldistance=1.1. "
3720+
"Consider using the wedge_labels parameter or the pie_label method "
3721+
"instead of the labels parameter."
3722+
)
3723+
_api.warn_deprecated("3.12", message=msg)
3724+
labeldistance = 1.1
3725+
36753726
if labels is None:
36763727
labels = [''] * len(x)
3728+
else:
3729+
if wedge_labels is not None and labeldistance is not None:
3730+
raise ValueError(
3731+
'wedge_labels is a replacement for labels when annotating the '
3732+
'wedges, so the two should not be used together unless '
3733+
'labeldistance is None. To add multiple sets of labels, use the '
3734+
'pie_label method.'
3735+
)
3736+
36773737
if explode is None:
36783738
explode = [0] * len(x)
36793739
if len(x) != len(labels):
@@ -3731,11 +3791,16 @@ def get_next_color():
37313791

37323792
pc = PieContainer(slices, x, normalize)
37333793

3734-
if labeldistance is None:
3794+
if wedge_labels is not None:
3795+
self.pie_label(pc, wedge_labels, distance=wedge_label_distance,
3796+
textprops=textprops)
3797+
3798+
elif labeldistance is None:
37353799
# Insert an empty list of texts for backwards compatibility of the
37363800
# return value.
37373801
pc.add_texts([])
3738-
else:
3802+
3803+
if labeldistance is not None:
37393804
# Add labels to the wedges.
37403805
labels_textprops = {
37413806
'fontsize': mpl.rcParams['xtick.labelsize'],

lib/matplotlib/axes/_axes.pyi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import matplotlib.tri as mtri
3131
import matplotlib.table as mtable
3232
import matplotlib.stackplot as mstack
3333
import matplotlib.streamplot as mstream
34+
from matplotlib._api import _Unset
3435

3536
import PIL.Image
3637
from collections.abc import Callable, Iterable, Sequence
@@ -310,10 +311,12 @@ class Axes(_AxesBase):
310311
explode: ArrayLike | None = ...,
311312
labels: Sequence[str] | None = ...,
312313
colors: ColorType | Sequence[ColorType] | None = ...,
314+
wedge_labels: str | Sequence | None = ...,
315+
wedge_label_distance: float | Sequence = ...,
313316
autopct: str | Callable[[float], str] | None = ...,
314317
pctdistance: float = ...,
315318
shadow: bool = ...,
316-
labeldistance: float | None = ...,
319+
labeldistance: float | None | _Unset = ...,
317320
startangle: float = ...,
318321
radius: float = ...,
319322
counterclock: bool = ...,

0 commit comments

Comments
 (0)