Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 38363df

Browse filesBrowse files
committed
WIP
1 parent 677d990 commit 38363df
Copy full SHA for 38363df

File tree

5 files changed

+238
-118
lines changed
Filter options

5 files changed

+238
-118
lines changed

‎galleries/examples/misc/svg_filter_pie.py

Copy file name to clipboardExpand all lines: galleries/examples/misc/svg_filter_pie.py
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@
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-
pies = ax.pie(fracs, explode=explode, labels=labels, autopct='%1.1f%%')
31+
pies = ax.pie(fracs, explode=explode,
32+
wedge_labels=[labels, '{frac:.1%}'], wedge_label_distance=[1.1, 0.6])
3233

33-
for w in pies[0]:
34+
for w, label in zip(pies[0], labels):
3435
# set the id with the label.
35-
w.set_gid(w.get_label())
36+
w.set_gid(label)
3637

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

‎galleries/examples/pie_and_polar_charts/bar_of_pie.py

Copy file name to clipboardExpand all lines: galleries/examples/pie_and_polar_charts/bar_of_pie.py
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
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-
wedges, *_ = ax1.pie(overall_ratios, autopct='%1.1f%%', startangle=angle,
29-
labels=labels, explode=explode)
28+
wedges, *_ = ax1.pie(
29+
overall_ratios, startangle=angle, explode=explode,
30+
wedge_labels=[labels, '{frac:.1%}'], wedge_label_distance=[1.1, 0.6])
3031

3132
# bar chart parameters
3233
age_ratios = [.33, .54, .07, .06]

‎galleries/examples/pie_and_polar_charts/pie_and_donut_labels.py

Copy file name to clipboardExpand all lines: galleries/examples/pie_and_polar_charts/pie_and_donut_labels.py
+5-14Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,16 @@
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]
4343

44+
ax.pie(data, wedge_labels='{frac:.1%}\n({abs:d}g)', labels=ingredients,
45+
labeldistance=None, textprops=dict(color="w", size=8, weight="bold"))
4446

45-
def func(pct, allvals):
46-
absolute = int(np.round(pct/100.*np.sum(allvals)))
47-
return f"{pct:.1f}%\n({absolute:d} g)"
48-
49-
50-
wedges, texts, autotexts = ax.pie(data, autopct=lambda pct: func(pct, data),
51-
textprops=dict(color="w"))
52-
53-
ax.legend(wedges, ingredients,
54-
title="Ingredients",
47+
ax.legend(title="Ingredients",
5548
loc="center left",
5649
bbox_to_anchor=(1, 0, 0.5, 1))
5750

58-
plt.setp(autotexts, size=8, weight="bold")
59-
6051
ax.set_title("Matplotlib bakery: A pie")
6152

6253
plt.show()
@@ -97,7 +88,7 @@ def func(pct, allvals):
9788

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

100-
wedges, texts = ax.pie(data, wedgeprops=dict(width=0.5), startangle=-40)
91+
wedges, _ = ax.pie(data, wedgeprops=dict(width=0.5), startangle=-40)
10192

10293
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
10394
kw = dict(arrowprops=dict(arrowstyle="-"),

‎lib/matplotlib/axes/_axes.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_axes.py
+177-60Lines changed: 177 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import collections.abc
12
import functools
23
import itertools
34
import logging
@@ -3202,10 +3203,10 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0,
32023203

32033204
@_api.make_keyword_only("3.9", "explode")
32043205
@_preprocess_data(replace_names=["x", "explode", "labels", "colors"])
3205-
def pie(self, x, explode=None, labels=None, colors=None,
3206-
autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1,
3207-
startangle=0, radius=1, counterclock=True,
3208-
wedgeprops=None, textprops=None, center=(0, 0),
3206+
def pie(self, x, explode=None, labels=None, colors=None, wedge_labels=None,
3207+
wedge_label_distance=0.6, rotate_wedge_labels=False, autopct=None,
3208+
pctdistance=0.6, shadow=False, labeldistance=1.1, startangle=0, radius=1,
3209+
counterclock=True, wedgeprops=None, textprops=None, center=(0, 0),
32093210
frame=False, rotatelabels=False, *, normalize=True, hatch=None):
32103211
"""
32113212
Plot a pie chart.
@@ -3239,6 +3240,8 @@ def pie(self, x, explode=None, labels=None, colors=None,
32393240
32403241
.. versionadded:: 3.7
32413242
3243+
wedge_labels :
3244+
32423245
autopct : None or str or callable, default: None
32433246
If not *None*, *autopct* is a string or function used to label the
32443247
wedges with their numeric value. The label will be placed inside
@@ -3321,9 +3324,7 @@ def pie(self, x, explode=None, labels=None, colors=None,
33213324
The Axes aspect ratio can be controlled with `.Axes.set_aspect`.
33223325
"""
33233326
self.set_aspect('equal')
3324-
# The use of float32 is "historical", but can't be changed without
3325-
# regenerating the test baselines.
3326-
x = np.asarray(x, np.float32)
3327+
x = np.asarray(x)
33273328
if x.ndim > 1:
33283329
raise ValueError("x must be 1D")
33293330

@@ -3332,18 +3333,19 @@ def pie(self, x, explode=None, labels=None, colors=None,
33323333

33333334
sx = x.sum()
33343335

3336+
def check_length(name, values):
3337+
if len(values) != len(x):
3338+
raise ValueError(f"'{name}' must be of length 'x', not {len(values)}")
3339+
33353340
if normalize:
3336-
x = x / sx
3341+
fracs = x / sx
33373342
elif sx > 1:
33383343
raise ValueError('Cannot plot an unnormalized pie with sum(x) > 1')
3339-
if labels is None:
3340-
labels = [''] * len(x)
3344+
else:
3345+
fracs = x
33413346
if explode is None:
33423347
explode = [0] * len(x)
3343-
if len(x) != len(labels):
3344-
raise ValueError(f"'labels' must be of length 'x', not {len(labels)}")
3345-
if len(x) != len(explode):
3346-
raise ValueError(f"'explode' must be of length 'x', not {len(explode)}")
3348+
check_length("explode", explode)
33473349
if colors is None:
33483350
get_next_color = self._get_patches_for_fill.get_next_color
33493351
else:
@@ -3368,16 +3370,143 @@ def get_next_color():
33683370

33693371
texts = []
33703372
slices = []
3371-
autotexts = []
33723373

3373-
for frac, label, expl in zip(x, labels, explode):
3374-
x, y = center
3374+
# Define some functions for choosing label fontize and horizontal alignment
3375+
# based on distance and whether we are right of center (i.e. cartesian x > 0)
3376+
3377+
def legacy(distance, is_right):
3378+
# Used to place `labels`. This function can be removed when the
3379+
# `labeldistance` deprecation expires. Always align so the labels
3380+
# do not overlap the pie
3381+
ha = 'left' if is_right else 'right'
3382+
return mpl.rcParams['xtick.labelsize'], ha
3383+
3384+
def flexible(distance, is_right):
3385+
if distance >= 1:
3386+
# Align so the labels do not overlap the pie
3387+
ha = 'left' if is_right else 'right'
3388+
else:
3389+
ha = 'center'
3390+
3391+
return None, ha
3392+
3393+
def fixed(distance, is_right):
3394+
# Used to place the labels generated with autopct. Always centered
3395+
# for backwards compatibility
3396+
return None, 'center'
3397+
3398+
# Build a (possibly empty) list of lists of wedge labels, with corresponding
3399+
# lists of distances, rotation choices and alignment functions
3400+
3401+
def sanitize_formatted_string(s):
3402+
if mpl._val_or_rc(textprops.get("usetex"), "text.usetex"):
3403+
# escape % (i.e. \%) if it is not already escaped
3404+
return re.sub(r"([^\\])%", r"\1\\%", s)
3405+
3406+
return s
3407+
3408+
def fmt_str_to_list(wl):
3409+
return [sanitize_formatted_string(wl.format(abs=absval, frac=frac))
3410+
for absval, frac in zip(x, fracs)]
3411+
3412+
if wedge_labels is None:
3413+
processed_wedge_labels = []
3414+
wedge_label_distance = []
3415+
rotate_wedge_labels = []
3416+
elif isinstance(wedge_labels, str):
3417+
# Format string.
3418+
processed_wedge_labels = [fmt_str_to_list(wedge_labels)]
3419+
elif not isinstance(wedge_labels, collections.abc.Sequence):
3420+
raise TypeError("wedge_labels must be a string or sequence")
3421+
else:
3422+
wl0 = wedge_labels[0]
3423+
if isinstance(wl0, str) and wl0.format(abs=1, frac=1) == wl0:
3424+
# Plain string. Assume we have a sequence of ready-made labels
3425+
check_length("wedge_labels", wedge_labels)
3426+
processed_wedge_labels = [wedge_labels]
3427+
else:
3428+
processed_wedge_labels = []
3429+
for wl in wedge_labels:
3430+
if isinstance(wl, str):
3431+
# Format string
3432+
processed_wedge_labels.append(fmt_str_to_list(wl))
3433+
else:
3434+
# Ready made list
3435+
check_length("wedge_labels[i]", wl)
3436+
processed_wedge_labels.append(wl)
3437+
3438+
if isinstance(wedge_label_distance, Number):
3439+
wedge_label_distance = [wedge_label_distance]
3440+
else:
3441+
# Copy so we won't append to user input
3442+
wedge_label_distance = wedge_label_distance[:]
3443+
3444+
nl = len(processed_wedge_labels)
3445+
if nl != (nd := len(wedge_label_distance)):
3446+
raise ValueError(
3447+
f"Found {nl} sets of wedge labels but {nd} wedge label distances.")
3448+
3449+
if isinstance(rotate_wedge_labels, bool):
3450+
rotate_wedge_labels = [rotate_wedge_labels]
3451+
else:
3452+
# Copy so we won't append to user input
3453+
rotate_wedge_labels = rotate_wedge_labels[:]
3454+
3455+
if len(rotate_wedge_labels) == 1:
3456+
rotate_wedge_labels = rotate_wedge_labels * nl
3457+
elif nl != (nr := len(rotate_wedge_labels)):
3458+
raise ValueError(f"Found {nl} sets of wedge labels but "
3459+
f"{nr} wedge label rotation choices.")
3460+
3461+
prop_funcs = [flexible] * len(processed_wedge_labels)
3462+
3463+
if autopct is not None:
3464+
if isinstance(autopct, str):
3465+
processed_pct = [sanitize_formatted_string(autopct % (100. * frac))
3466+
for frac in fracs]
3467+
elif callable(autopct):
3468+
processed_pct = [sanitize_formatted_string(autopct(100. * frac))
3469+
for frac in fracs]
3470+
else:
3471+
raise TypeError('autopct must be callable or a format string')
3472+
3473+
processed_wedge_labels.append(processed_pct)
3474+
wedge_label_distance.append(pctdistance)
3475+
prop_funcs.append(fixed)
3476+
rotate_wedge_labels.append(False)
3477+
3478+
wedgetexts = [[]] * len(processed_wedge_labels)
3479+
3480+
if labels is None:
3481+
labels = [None] * len(x)
3482+
else:
3483+
check_length("labels", labels)
3484+
if labeldistance is not None:
3485+
_api.warn_deprecated(
3486+
"3.11", pending=True,
3487+
message=("Setting a non-None labeldistance is deprecated and "
3488+
"will error in future."),
3489+
alternative="wedge_labels and wedge_label_distance")
3490+
3491+
processed_wedge_labels.append(labels)
3492+
wedge_label_distance.append(labeldistance)
3493+
prop_funcs.append(legacy)
3494+
rotate_wedge_labels.append(rotatelabels)
3495+
3496+
# Transpose so we can loop over wedges
3497+
processed_wedge_labels = np.transpose(processed_wedge_labels)
3498+
if not processed_wedge_labels.size:
3499+
processed_wedge_labels = processed_wedge_labels.reshape(len(x), 0)
3500+
3501+
for frac, label, expl, wls in zip(fracs, labels, explode,
3502+
processed_wedge_labels):
3503+
x_pos, y_pos = center
33753504
theta2 = (theta1 + frac) if counterclock else (theta1 - frac)
33763505
thetam = 2 * np.pi * 0.5 * (theta1 + theta2)
3377-
x += expl * math.cos(thetam)
3378-
y += expl * math.sin(thetam)
3506+
x_pos += expl * math.cos(thetam)
3507+
y_pos += expl * math.sin(thetam)
33793508

3380-
w = mpatches.Wedge((x, y), radius, 360. * min(theta1, theta2),
3509+
w = mpatches.Wedge((x_pos, y_pos), radius, 360. * min(theta1, theta2),
33813510
360. * max(theta1, theta2),
33823511
facecolor=get_next_color(),
33833512
hatch=next(hatch_cycle),
@@ -3395,44 +3524,30 @@ def get_next_color():
33953524
shadow_dict.update(shadow)
33963525
self.add_patch(mpatches.Shadow(w, **shadow_dict))
33973526

3398-
if labeldistance is not None:
3399-
xt = x + labeldistance * radius * math.cos(thetam)
3400-
yt = y + labeldistance * radius * math.sin(thetam)
3401-
label_alignment_h = 'left' if xt > 0 else 'right'
3402-
label_alignment_v = 'center'
3403-
label_rotation = 'horizontal'
3404-
if rotatelabels:
3405-
label_alignment_v = 'bottom' if yt > 0 else 'top'
3406-
label_rotation = (np.rad2deg(thetam)
3407-
+ (0 if xt > 0 else 180))
3408-
t = self.text(xt, yt, label,
3409-
clip_on=False,
3410-
horizontalalignment=label_alignment_h,
3411-
verticalalignment=label_alignment_v,
3412-
rotation=label_rotation,
3413-
size=mpl.rcParams['xtick.labelsize'])
3414-
t.set(**textprops)
3415-
texts.append(t)
3416-
3417-
if autopct is not None:
3418-
xt = x + pctdistance * radius * math.cos(thetam)
3419-
yt = y + pctdistance * radius * math.sin(thetam)
3420-
if isinstance(autopct, str):
3421-
s = autopct % (100. * frac)
3422-
elif callable(autopct):
3423-
s = autopct(100. * frac)
3424-
else:
3425-
raise TypeError(
3426-
'autopct must be callable or a format string')
3427-
if mpl._val_or_rc(textprops.get("usetex"), "text.usetex"):
3428-
# escape % (i.e. \%) if it is not already escaped
3429-
s = re.sub(r"([^\\])%", r"\1\\%", s)
3430-
t = self.text(xt, yt, s,
3431-
clip_on=False,
3432-
horizontalalignment='center',
3433-
verticalalignment='center')
3434-
t.set(**textprops)
3435-
autotexts.append(t)
3527+
if wls.size > 0:
3528+
# Add wedge labels
3529+
for i, (wl, ld, pf, rot) in enumerate(
3530+
zip(wls, wedge_label_distance, prop_funcs,
3531+
rotate_wedge_labels)):
3532+
xt = x_pos + ld * radius * math.cos(thetam)
3533+
yt = y_pos + ld * radius * math.sin(thetam)
3534+
fontsize, label_alignment_h = pf(ld, xt > 0)
3535+
label_alignment_v = 'center'
3536+
label_rotation = 'horizontal'
3537+
if rot:
3538+
label_alignment_v = 'bottom' if yt > 0 else 'top'
3539+
label_rotation = (np.rad2deg(thetam) + (0 if xt > 0 else 180))
3540+
t = self.text(xt, yt, wl,
3541+
clip_on=False,
3542+
horizontalalignment=label_alignment_h,
3543+
verticalalignment=label_alignment_v,
3544+
rotation=label_rotation,
3545+
size=fontsize)
3546+
t.set(**textprops)
3547+
if pf is legacy:
3548+
texts.append(t)
3549+
else:
3550+
wedgetexts[i].append(t)
34363551

34373552
theta1 = theta2
34383553

@@ -3443,10 +3558,12 @@ def get_next_color():
34433558
xlim=(-1.25 + center[0], 1.25 + center[0]),
34443559
ylim=(-1.25 + center[1], 1.25 + center[1]))
34453560

3446-
if autopct is None:
3561+
if not wedgetexts:
34473562
return slices, texts
3563+
elif len(wedgetexts) == 1:
3564+
return slices, texts, wedgetexts[0]
34483565
else:
3449-
return slices, texts, autotexts
3566+
return slices, texts, wedgetexts
34503567

34513568
@staticmethod
34523569
def _errorevery_to_mask(x, errorevery):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.