Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e5db08a

Browse files
committed
ENH: add option to label pie charts with absolute values
1 parent 183b04f commit e5db08a

File tree

8 files changed

+132
-30
lines changed

8 files changed

+132
-30
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The *pctdistance* parameter of ``Axes.pie``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
... is deprecated. Please use *autodistance* instead.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Label pie charts with absolute input values
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Pie charts may now be automatically labelled with the original input values
5+
using the new *absolutefmt* parameter in `~.Axes.pie`.
6+
7+
.. plot::
8+
:include-source: true
9+
:alt: Pie chart with each wedge labelled with its input value
10+
11+
import matplotlib.pyplot as plt
12+
13+
x = [4, 2, 1]
14+
15+
fig, ax = plt.subplots()
16+
ax.pie(x, absolutefmt='%d')
17+
18+
plt.show()

galleries/examples/pie_and_polar_charts/pie_and_donut_labels.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@
3838
"250 g butter",
3939
"300 g berries"]
4040

41-
data = [float(x.split()[0]) for x in recipe]
41+
data = [int(x.split()[0]) for x in recipe]
4242
ingredients = [x.split()[-1] for x in recipe]
43+
total = np.sum(data)
4344

4445

45-
def func(pct, allvals):
46-
absolute = int(np.round(pct/100.*np.sum(allvals)))
47-
return f"{pct:.1f}%\n({absolute:d} g)"
46+
def func(value):
47+
fraction = value / total
48+
return f"{fraction:.1%}\n({value:d} g)"
4849

4950

50-
wedges, texts, autotexts = ax.pie(data, autopct=lambda pct: func(pct, data),
51-
textprops=dict(color="w"))
51+
wedges, texts, autotexts = ax.pie(data, absolutefmt=func, textprops=dict(color="w"))
5252

5353
ax.legend(wedges, ingredients,
5454
title="Ingredients",

galleries/examples/pie_and_polar_charts/pie_features.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,19 @@
3434
# Auto-label slices
3535
# -----------------
3636
#
37-
# Pass a function or format string to *autopct* to label slices.
37+
# Pass a function or format string to *absolutefmt* to label slices with your
38+
# input values...
39+
40+
fig, ax = plt.subplots()
41+
ax.pie(sizes, labels=labels, absolutefmt='%d')
42+
43+
# %%
44+
# ...or to *autopct* to label slices with the percent sizes of the slice.
3845

3946
fig, ax = plt.subplots()
4047
ax.pie(sizes, labels=labels, autopct='%1.1f%%')
4148

4249
# %%
43-
# By default, the label values are obtained from the percent size of the slice.
4450
#
4551
# Color slices
4652
# ------------
@@ -63,15 +69,15 @@
6369
# %%
6470
# Swap label and autopct text positions
6571
# -------------------------------------
66-
# Use the *labeldistance* and *pctdistance* parameters to position the *labels*
72+
# Use the *labeldistance* and *autodistance* parameters to position the *labels*
6773
# and *autopct* text respectively.
6874

6975
fig, ax = plt.subplots()
7076
ax.pie(sizes, labels=labels, autopct='%1.1f%%',
71-
pctdistance=1.25, labeldistance=.6)
77+
autodistance=1.25, labeldistance=.6)
7278

7379
# %%
74-
# *labeldistance* and *pctdistance* are ratios of the radius; therefore they
80+
# *labeldistance* and *autodistance* are ratios of the radius; therefore they
7581
# vary between ``0`` for the center of the pie and ``1`` for the edge of the
7682
# pie, and can be set to greater than ``1`` to place text outside the pie.
7783
#

lib/matplotlib/axes/_axes.py

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3154,13 +3154,13 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0,
31543154
self.add_container(stem_container)
31553155
return stem_container
31563156

3157-
@_api.make_keyword_only("3.9", "explode")
3157+
@_api.rename_parameter("3.11", "pctdistance", "autodistance")
31583158
@_preprocess_data(replace_names=["x", "explode", "labels", "colors"])
3159-
def pie(self, x, explode=None, labels=None, colors=None,
3160-
autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1,
3159+
def pie(self, x, *, explode=None, labels=None, colors=None, absolutefmt=None,
3160+
autopct=None, autodistance=0.6, shadow=False, labeldistance=1.1,
31613161
startangle=0, radius=1, counterclock=True,
31623162
wedgeprops=None, textprops=None, center=(0, 0),
3163-
frame=False, rotatelabels=False, *, normalize=True, hatch=None):
3163+
frame=False, rotatelabels=False, normalize=True, hatch=None):
31643164
"""
31653165
Plot a pie chart.
31663166
@@ -3193,18 +3193,38 @@ def pie(self, x, explode=None, labels=None, colors=None,
31933193
31943194
.. versionadded:: 3.7
31953195
3196+
absolutefmt : None or str or callable, default: None
3197+
If not *None*, *absolutefmt* is a string or function used to label
3198+
the wedges with their original numeric values. The label will be
3199+
placed according to *autodistance*. If *absolutefmt* is a format
3200+
string, the label will be ``fmt % number``. If *absolutefmt* is a
3201+
function, then it will be called. Cannot be used at the same time
3202+
as *autopct*.
3203+
3204+
.. versionadded:: 3.11
3205+
31963206
autopct : None or str or callable, default: None
31973207
If not *None*, *autopct* is a string or function used to label the
3198-
wedges with their numeric value. The label will be placed inside
3199-
the wedge. If *autopct* is a format string, the label will be
3200-
``fmt % pct``. If *autopct* is a function, then it will be called.
3208+
wedges with their percentage values. The label will be placed
3209+
according to *autodistance*. If *autopct* is a format string, the
3210+
label will be ``fmt % pct``. If *autopct* is a function, then it
3211+
will be called. Cannot be used at the same time as *absolutefmt*.
3212+
3213+
autodistance : float, default: 0.6
3214+
The relative distance along the radius at which the text
3215+
generated by *absolutefmt* or *autopct* is drawn. To draw the text
3216+
outside the pie, set *autodistance* > 1. This parameter is ignored
3217+
if both *absolutefmt* and *autopct* are ``None``.
32013218
32023219
pctdistance : float, default: 0.6
32033220
The relative distance along the radius at which the text
32043221
generated by *autopct* is drawn. To draw the text outside the pie,
32053222
set *pctdistance* > 1. This parameter is ignored if *autopct* is
32063223
``None``.
32073224
3225+
.. deprecated:: 3.11
3226+
Use *autodistance* instead.
3227+
32083228
labeldistance : float or None, default: 1.1
32093229
The relative distance along the radius at which the labels are
32103230
drawn. To draw the labels inside the pie, set *labeldistance* < 1.
@@ -3275,9 +3295,7 @@ def pie(self, x, explode=None, labels=None, colors=None,
32753295
The Axes aspect ratio can be controlled with `.Axes.set_aspect`.
32763296
"""
32773297
self.set_aspect('equal')
3278-
# The use of float32 is "historical", but can't be changed without
3279-
# regenerating the test baselines.
3280-
x = np.asarray(x, np.float32)
3298+
x = np.asarray(x)
32813299
if x.ndim > 1:
32823300
raise ValueError("x must be 1D")
32833301

@@ -3287,9 +3305,11 @@ def pie(self, x, explode=None, labels=None, colors=None,
32873305
sx = x.sum()
32883306

32893307
if normalize:
3290-
x = x / sx
3308+
fracs = x / sx
32913309
elif sx > 1:
32923310
raise ValueError('Cannot plot an unnormalized pie with sum(x) > 1')
3311+
else:
3312+
fracs = x
32933313
if labels is None:
32943314
labels = [''] * len(x)
32953315
if explode is None:
@@ -3324,7 +3344,7 @@ def get_next_color():
33243344
slices = []
33253345
autotexts = []
33263346

3327-
for frac, label, expl in zip(x, labels, explode):
3347+
for n, frac, label, expl in zip(x, fracs, labels, explode):
33283348
x, y = center
33293349
theta2 = (theta1 + frac) if counterclock else (theta1 - frac)
33303350
thetam = 2 * np.pi * 0.5 * (theta1 + theta2)
@@ -3369,15 +3389,33 @@ def get_next_color():
33693389
texts.append(t)
33703390

33713391
if autopct is not None:
3372-
xt = x + pctdistance * radius * math.cos(thetam)
3373-
yt = y + pctdistance * radius * math.sin(thetam)
3392+
if absolutefmt is not None:
3393+
raise ValueError('Only one of autopct and absolutefmt may be used.')
33743394
if isinstance(autopct, str):
33753395
s = autopct % (100. * frac)
33763396
elif callable(autopct):
33773397
s = autopct(100. * frac)
33783398
else:
33793399
raise TypeError(
33803400
'autopct must be callable or a format string')
3401+
autolabels = True
3402+
3403+
elif absolutefmt is not None:
3404+
if isinstance(absolutefmt, str):
3405+
s = absolutefmt % n
3406+
elif callable(absolutefmt):
3407+
s = absolutefmt(n)
3408+
else:
3409+
raise TypeError(
3410+
'absolutefmt must be callable or a format string')
3411+
autolabels = True
3412+
3413+
else:
3414+
autolabels = False
3415+
3416+
if autolabels:
3417+
xt = x + autodistance * radius * math.cos(thetam)
3418+
yt = y + autodistance * radius * math.sin(thetam)
33813419
if mpl._val_or_rc(textprops.get("usetex"), "text.usetex"):
33823420
# escape % (i.e. \%) if it is not already escaped
33833421
s = re.sub(r"([^\\])%", r"\1\\%", s)
@@ -3397,7 +3435,7 @@ def get_next_color():
33973435
xlim=(-1.25 + center[0], 1.25 + center[0]),
33983436
ylim=(-1.25 + center[1], 1.25 + center[1]))
33993437

3400-
if autopct is None:
3438+
if not autotexts:
34013439
return slices, texts
34023440
else:
34033441
return slices, texts, autotexts

lib/matplotlib/axes/_axes.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,9 @@ class Axes(_AxesBase):
299299
explode: ArrayLike | None = ...,
300300
labels: Sequence[str] | None = ...,
301301
colors: ColorType | Sequence[ColorType] | None = ...,
302+
absolutefmt: str | Callable[[float], str] | None = ...,
302303
autopct: str | Callable[[float], str] | None = ...,
303-
pctdistance: float = ...,
304+
autodistance: float = ...,
304305
shadow: bool = ...,
305306
labeldistance: float | None = ...,
306307
startangle: float = ...,

lib/matplotlib/pyplot.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3772,11 +3772,13 @@ def phase_spectrum(
37723772
@_copy_docstring_and_deprecators(Axes.pie)
37733773
def pie(
37743774
x: ArrayLike,
3775+
*,
37753776
explode: ArrayLike | None = None,
37763777
labels: Sequence[str] | None = None,
37773778
colors: ColorType | Sequence[ColorType] | None = None,
3779+
absolutefmt: str | Callable[[float], str] | None = None,
37783780
autopct: str | Callable[[float], str] | None = None,
3779-
pctdistance: float = 0.6,
3781+
autodistance: float = 0.6,
37803782
shadow: bool = False,
37813783
labeldistance: float | None = 1.1,
37823784
startangle: float = 0,
@@ -3787,7 +3789,6 @@ def pie(
37873789
center: tuple[float, float] = (0, 0),
37883790
frame: bool = False,
37893791
rotatelabels: bool = False,
3790-
*,
37913792
normalize: bool = True,
37923793
hatch: str | Sequence[str] | None = None,
37933794
data=None,
@@ -3797,8 +3798,9 @@ def pie(
37973798
explode=explode,
37983799
labels=labels,
37993800
colors=colors,
3801+
absolutefmt=absolutefmt,
38003802
autopct=autopct,
3801-
pctdistance=pctdistance,
3803+
autodistance=autodistance,
38023804
shadow=shadow,
38033805
labeldistance=labeldistance,
38043806
startangle=startangle,

lib/matplotlib/tests/test_axes.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5961,6 +5961,39 @@ def test_pie_default():
59615961
autopct='%1.1f%%', shadow=True, startangle=90)
59625962

59635963

5964+
def test_pie_abs():
5965+
labels = ['Frogs', 'Hogs', 'Dogs', 'Logs']
5966+
sizes = [15, 30, 45, 10]
5967+
fig1, ax1 = plt.subplots(figsize=(8, 6))
5968+
slices, texts, autotexts = ax1.pie(
5969+
sizes, labels=labels, absolutefmt='%d')
5970+
5971+
assert [txt.get_text() for txt in texts] == labels
5972+
assert [txt.get_text() for txt in autotexts] == [str(size) for size in sizes]
5973+
5974+
5975+
def test_pie_abs_func():
5976+
labels = ['Frogs', 'Hogs', 'Dogs', 'Logs']
5977+
sizes = [15, 30, 45, 10]
5978+
fig1, ax1 = plt.subplots(figsize=(8, 6))
5979+
5980+
def double(n):
5981+
return n * 2
5982+
5983+
slices, texts, autotexts = ax1.pie(
5984+
sizes, labels=labels, absolutefmt=double)
5985+
5986+
assert [txt.get_text() for txt in texts] == labels
5987+
assert [txt.get_text() for txt in autotexts] == [str(size * 2) for size in sizes]
5988+
5989+
5990+
def test_pie_both_number_formats():
5991+
sizes = [15, 30, 45, 10]
5992+
fig1, ax1 = plt.subplots(figsize=(8, 6))
5993+
with pytest.raises(ValueError, match='Only one of autopct and absolutefmt'):
5994+
ax1.pie(sizes, absolutefmt='%d', autopct='%1.1f%%')
5995+
5996+
59645997
@image_comparison(['pie_linewidth_0', 'pie_linewidth_0', 'pie_linewidth_0'],
59655998
extensions=['png'], style='mpl20', tol=0.01)
59665999
def test_pie_linewidth_0():

0 commit comments

Comments
 (0)