From 14258c78d0f55f477a2e06d072f029b0739145ba Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 15:31:22 +0100 Subject: [PATCH 01/15] Add pie artist --- lib/matplotlib/axes/_axes.py | 19 +++--- lib/matplotlib/container.py | 72 ------------------- lib/matplotlib/meson.build | 1 + lib/matplotlib/pie.py | 95 ++++++++++++++++++++++++++ lib/matplotlib/tests/test_container.py | 26 ------- lib/matplotlib/tests/test_pie.py | 27 ++++++++ 6 files changed, 133 insertions(+), 107 deletions(-) create mode 100644 lib/matplotlib/pie.py create mode 100644 lib/matplotlib/tests/test_pie.py diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 276d7b61b852..5bb4bdfc76a0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -38,7 +38,8 @@ _AxesBase, _TransformedBoundsLocator, _process_plot_format) from matplotlib.axes._secondary_axes import SecondaryAxis from matplotlib.container import ( - BarContainer, ErrorbarContainer, PieContainer, StemContainer) + BarContainer, ErrorbarContainer, StemContainer) +from matplotlib.pie import Pie from matplotlib.text import Text from matplotlib.transforms import _ScaledRotation from matplotlib._api import UNSET as _UNSET @@ -3673,8 +3674,8 @@ def pie(self, x, *, explode=None, labels=None, colors=None, wedge_labels=None, Returns ------- - `.PieContainer` - Container with all the wedge patches and any associated text objects. + `.Pie` + Artist with all the wedge patches and any associated text objects. .. versionchanged:: 3.11 Previously the wedges and texts were returned in a tuple. @@ -3790,16 +3791,16 @@ def get_next_color(): theta1 = theta2 - pc = PieContainer(slices, x, normalize) + pie = Pie(slices, x, normalize) if wedge_labels is not None: - self.pie_label(pc, wedge_labels, distance=wedge_label_distance, + self.pie_label(pie, wedge_labels, distance=wedge_label_distance, textprops=textprops) elif labeldistance is None: # Insert an empty list of texts for backwards compatibility of the # return value. - pc.add_texts([]) + pie.add_texts([]) if labeldistance is not None: # Add labels to the wedges. @@ -3807,7 +3808,7 @@ def get_next_color(): 'fontsize': mpl.rcParams['xtick.labelsize'], **cbook.normalize_kwargs(textprops or {}, Text) } - self.pie_label(pc, labels, distance=labeldistance, + self.pie_label(pie, labels, distance=labeldistance, alignment='outer', rotate=rotatelabels, textprops=labels_textprops) @@ -3828,7 +3829,7 @@ def get_next_color(): s = re.sub(r"([^\\])%", r"\1\\%", s) auto_labels.append(s) - self.pie_label(pc, auto_labels, distance=pctdistance, + self.pie_label(pie, auto_labels, distance=pctdistance, alignment='center', textprops=textprops) @@ -3839,7 +3840,7 @@ def get_next_color(): xlim=(-1.25 + center[0], 1.25 + center[0]), ylim=(-1.25 + center[1], 1.25 + center[1])) - return pc + return pie def pie_label(self, container, /, labels, *, distance=0.6, textprops=None, rotate=False, alignment='auto'): diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index 36e686a16592..05eb2e3b1e14 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -168,78 +168,6 @@ def __init__(self, lines, has_xerr=False, has_yerr=False, **kwargs): super().__init__(lines, **kwargs) -class PieContainer: - """ - Container for the artists of pie charts (e.g. created by `.Axes.pie`). - - .. versionadded:: 3.11 - - .. warning:: - The class name ``PieContainer`` name is provisional and may change in future - to reflect development of its functionality. - - You can access the wedge patches and further parameters by the attributes. - - Attributes - ---------- - wedges : list of `~matplotlib.patches.Wedge` - The artists of the pie wedges. - - values : `numpy.ndarray` - The data that the pie is based on. - - fracs : `numpy.ndarray` - The fraction of the pie that each wedge represents. - - texts : list of list of `~matplotlib.text.Text` - The artists of any labels on the pie wedges. Each inner list has one - text label per wedge. - - """ - def __init__(self, wedges, values, normalize): - self.wedges = wedges - self._texts = [] - self._values = values - self._normalize = normalize - - @property - def texts(self): - # Only return non-empty sublists. An empty sublist may have been added - # for backwards compatibility of the Axes.pie return value (see __getitem__). - return [t_list for t_list in self._texts if t_list] - - @property - def values(self): - result = self._values.copy() - result.flags.writeable = False - return result - - @property - def fracs(self): - if self._normalize: - result = self._values / self._values.sum() - else: - result = self._values - - result.flags.writeable = False - return result - - def add_texts(self, texts): - """Add a list of `~matplotlib.text.Text` objects to the container.""" - self._texts.append(texts) - - def remove(self): - """Remove all wedges and texts from the axes""" - for artist_list in self.wedges, self._texts: - for artist in cbook.flatten(artist_list): - artist.remove() - - def __getitem__(self, key): - # needed to support unpacking into a tuple for backward compatibility of the - # Axes.pie return value - return (self.wedges, *self._texts)[key] - - class StemContainer(Container): """ Container for the artists created in a :meth:`.Axes.stem` plot. diff --git a/lib/matplotlib/meson.build b/lib/matplotlib/meson.build index c0bfdb227e2e..06945ada1fd6 100644 --- a/lib/matplotlib/meson.build +++ b/lib/matplotlib/meson.build @@ -57,6 +57,7 @@ python_sources = [ 'patches.py', 'patheffects.py', 'path.py', + 'pie.py', 'pylab.py', 'pyplot.py', 'quiver.py', diff --git a/lib/matplotlib/pie.py b/lib/matplotlib/pie.py new file mode 100644 index 000000000000..7a617c9a3ee9 --- /dev/null +++ b/lib/matplotlib/pie.py @@ -0,0 +1,95 @@ +from matplotlib import cbook +from .artist import Artist + +class Pie(Artist): + """ + Compound Artist representing a pie chart. + + .. versionadded:: 3.12 + + Attributes + ---------- + wedges : list of `~matplotlib.patches.Wedge` + The artists of the pie wedges. + + values : `numpy.ndarray` + The data that the pie is based on. + + fracs : `numpy.ndarray` + The fraction of the pie that each wedge represents. + + texts : list of list of `~matplotlib.text.Text` + The artists of any labels on the pie wedges. Each inner list has one + text label per wedge. + """ + + def __init__(self, wedges, values, normalize, shadows=None): + """ + Parameters + ---------- + wedges : list of `~matplotlib.patches.Wedge` + The artists of the pie wedges. + values : `numpy.ndarray` + The data that the pie is based on. + normalize : bool, default: True + Whether the pie slices are normalized to sum to 1. + shadows : list of `~matplotlib.patches.Shadow`, optional + Shadow patches associated with the wedges. + """ + super().__init__() + self.wedges = wedges + self._texts = [] + self._values = values + self._normalize = normalize + self._shadows = list(shadows) if shadows else [] + + @property + def texts(self): + # Only return non-empty sublists. An empty sublist may have been added + # for backwards compatibility of the Axes.pie return value (see __getitem__). + return [t_list for t_list in self._texts if t_list] + + @property + def values(self): + result = self._values.copy() + result.flags.writeable = False + return result + + @property + def fracs(self): + if self._normalize: + result = self._values / self._values.sum() + else: + result = self._values + + result.flags.writeable = False + return result + + def add_texts(self, texts): + """Add a list of `~matplotlib.text.Text` objects to the pie artist.""" + self._texts.append(texts) + + def remove(self): + """Remove all wedges and texts from the axes""" + for artist_list in self.wedges, self._texts: + for artist in cbook.flatten(artist_list): + artist.remove() + + def __getitem__(self, key): + # needed to support unpacking into a tuple for backward compatibility of the + # Axes.pie return value + return (self.wedges, *self._texts)[key] + + def draw(self, renderer): + if not self.get_visible(): + return + renderer.open_group('pie', gid=self.get_gid()) + for s in self._shadows: + s.draw(renderer) + for w in self.wedges: + w.draw(renderer) + for t_list in self._texts: + for t in t_list: + t.draw(renderer) + renderer.close_group('pie') + self.stale = False diff --git a/lib/matplotlib/tests/test_container.py b/lib/matplotlib/tests/test_container.py index d27ee1115171..6998101dd755 100644 --- a/lib/matplotlib/tests/test_container.py +++ b/lib/matplotlib/tests/test_container.py @@ -53,29 +53,3 @@ def test_barcontainer_position_centers__bottoms__tops(): assert_array_equal(container.position_centers, pos) assert_array_equal(container.bottoms, bottoms) assert_array_equal(container.tops, bottoms + heights) - - -def test_piecontainer_remove(): - fig, ax = plt.subplots() - pie = ax.pie([2, 3], wedge_labels=['foo', 'bar'], autopct="%1.0f%%") - ax.pie_label(pie, ['baz', 'qux']) - - assert len(ax.patches) == 2 - # We have added 6 labels but pie also adds an empty Text artist to each - # wedge if labeldistance is not None and labels is not passed - assert len(ax.texts) == 8 - - pie.remove() - assert not ax.patches - assert not ax.texts - - -def test_piecontainer_unpack_backcompat(): - fig, ax = plt.subplots() - wedges, texts, autotexts = ax.pie( - [2, 3], labels=['foo', 'bar'], autopct="%1.0f%%", labeldistance=None) - - assert len(wedges) == 2 - assert isinstance(texts, list) - assert not texts - assert len(autotexts) == 2 diff --git a/lib/matplotlib/tests/test_pie.py b/lib/matplotlib/tests/test_pie.py new file mode 100644 index 000000000000..1ae22383db8c --- /dev/null +++ b/lib/matplotlib/tests/test_pie.py @@ -0,0 +1,27 @@ +import matplotlib.pyplot as plt + + +def test_pie_remove(): + fig, ax = plt.subplots() + pie = ax.pie([2, 3], wedge_labels=['foo', 'bar'], autopct="%1.0f%%") + ax.pie_label(pie, ['baz', 'qux']) + + assert len(ax.patches) == 2 + # We have added 6 labels but pie also adds an empty Text artist to each + # wedge if labeldistance is not None and labels is not passed + assert len(ax.texts) == 8 + + pie.remove() + assert not ax.patches + assert not ax.texts + + +def test_pie_unpack_backcompat(): + fig, ax = plt.subplots() + wedges, texts, autotexts = ax.pie( + [2, 3], labels=['foo', 'bar'], autopct="%1.0f%%", labeldistance=None) + + assert len(wedges) == 2 + assert isinstance(texts, list) + assert not texts + assert len(autotexts) == 2 From d6c1f0061686874cf443b3849e30480babb06b24 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 18:06:33 +0100 Subject: [PATCH 02/15] Wire wedges and shadows through pie artist --- lib/matplotlib/axes/_axes.py | 7 ++++--- lib/matplotlib/pie.py | 27 +++++++++++++++++++++++---- lib/matplotlib/tests/test_pie.py | 7 ++++--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 5bb4bdfc76a0..3dceeceb0d74 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3763,6 +3763,7 @@ def get_next_color(): wedgeprops = {} slices = [] + shadows = [] for frac, label, expl in zip(fracs, labels, explode): x_pos, y_pos = center @@ -3779,7 +3780,6 @@ def get_next_color(): label=label) w.set(**wedgeprops) slices.append(w) - self.add_patch(w) if shadow: # Make sure to add a shadow after the call to add_patch so the @@ -3787,11 +3787,12 @@ def get_next_color(): shadow_dict = {'ox': -0.02, 'oy': -0.02, 'label': '_nolegend_'} if isinstance(shadow, dict): shadow_dict.update(shadow) - self.add_patch(mpatches.Shadow(w, **shadow_dict)) + shadows.append(mpatches.Shadow(w, **shadow_dict)) theta1 = theta2 - pie = Pie(slices, x, normalize) + pie = Pie(slices, x, normalize, shadows) + self.add_artist(pie) if wedge_labels is not None: self.pie_label(pie, wedge_labels, distance=wedge_label_distance, diff --git a/lib/matplotlib/pie.py b/lib/matplotlib/pie.py index 7a617c9a3ee9..90ebdab034ac 100644 --- a/lib/matplotlib/pie.py +++ b/lib/matplotlib/pie.py @@ -70,10 +70,7 @@ def add_texts(self, texts): self._texts.append(texts) def remove(self): - """Remove all wedges and texts from the axes""" - for artist_list in self.wedges, self._texts: - for artist in cbook.flatten(artist_list): - artist.remove() + super().remove() def __getitem__(self, key): # needed to support unpacking into a tuple for backward compatibility of the @@ -93,3 +90,25 @@ def draw(self, renderer): t.draw(renderer) renderer.close_group('pie') self.stale = False + + @Artist.axes.setter + def axes(self, new_axes): + Artist.axes.fset(self, new_axes) + for s in self._shadows: + s.axes = new_axes + for w in self.wedges: + w.axes = new_axes + + def set_transform(self, t): + super().set_transform(t) + for s in self._shadows: + s.set_transform(t) + for w in self.wedges: + w.set_transform(t) + + def set_figure(self, fig): + super().set_figure(fig) + for s in self._shadows: + s.set_figure(fig) + for w in self.wedges: + w.set_figure(fig) diff --git a/lib/matplotlib/tests/test_pie.py b/lib/matplotlib/tests/test_pie.py index 1ae22383db8c..a8de923f41a7 100644 --- a/lib/matplotlib/tests/test_pie.py +++ b/lib/matplotlib/tests/test_pie.py @@ -6,14 +6,15 @@ def test_pie_remove(): pie = ax.pie([2, 3], wedge_labels=['foo', 'bar'], autopct="%1.0f%%") ax.pie_label(pie, ['baz', 'qux']) - assert len(ax.patches) == 2 + assert len(ax.patches) == 0 + assert pie in ax._children # We have added 6 labels but pie also adds an empty Text artist to each # wedge if labeldistance is not None and labels is not passed assert len(ax.texts) == 8 pie.remove() - assert not ax.patches - assert not ax.texts + assert pie not in ax._children + # assert not ax.texts def test_pie_unpack_backcompat(): From cc56b3d5512eb581ee1921461566b5bbb1fad35c Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 18:34:16 +0100 Subject: [PATCH 03/15] Wire texts to pie artist --- lib/matplotlib/axes/_axes.py | 18 +++++++++--------- lib/matplotlib/pie.py | 15 +++++++++++++++ lib/matplotlib/tests/test_pie.py | 6 ++---- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3dceeceb0d74..dc4ef8e785fe 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3843,19 +3843,19 @@ def get_next_color(): return pie - def pie_label(self, container, /, labels, *, distance=0.6, + def pie_label(self, pie, /, labels, *, distance=0.6, textprops=None, rotate=False, alignment='auto'): """ Label a pie chart. .. versionadded:: 3.11 - Adds labels to wedges in the given `.PieContainer`. + Adds labels to wedges in the given `.Pie`. Parameters ---------- - container : `.PieContainer` - Container with all the wedges, likely returned from `.pie`. + pie : `.Pie` + Pie artist with all the wedges, likely returned from `.pie`. labels : str or list of str A sequence of strings providing the labels for each wedge, or a format @@ -3908,17 +3908,17 @@ def pie_label(self, container, /, labels, *, distance=0.6, if isinstance(labels, str): # Assume we have a format string labels = [labels.format(absval=val, frac=frac) for val, frac in - zip(container.values, container.fracs)] + zip(pie.values, pie.fracs)] if mpl._val_or_rc(textprops.get("usetex"), "text.usetex"): # escape % (i.e. \%) if it is not already escaped labels = [re.sub(r"([^\\])%", r"\1\\%", s) for s in labels] - elif (nw := len(container.wedges)) != (nl := len(labels)): + elif (nw := len(pie.wedges)) != (nl := len(labels)): raise ValueError( f'The number of labels ({nl}) must match the number of wedges ({nw})') texts = [] - for wedge, label in zip(container.wedges, labels): + for wedge, label in zip(pie.wedges, labels): thetam = 2 * np.pi * 0.5 * (wedge.theta1 + wedge.theta2) / 360 xt = wedge.center[0] + distance * wedge.r * math.cos(thetam) yt = wedge.center[1] + distance * wedge.r * math.sin(thetam) @@ -3932,13 +3932,13 @@ def pie_label(self, container, /, labels, *, distance=0.6, if alignment == 'outer': label_alignment_v = 'bottom' if yt > 0 else 'top' label_rotation = (np.rad2deg(thetam) + (0 if xt > 0 else 180)) - t = self.text(xt, yt, label, clip_on=False, rotation=label_rotation, + t = mtext.Text(xt, yt, label, clip_on=False, rotation=label_rotation, horizontalalignment=label_alignment_h, verticalalignment=label_alignment_v) t.set(**textprops) texts.append(t) - container.add_texts(texts) + pie.add_texts(texts) return texts diff --git a/lib/matplotlib/pie.py b/lib/matplotlib/pie.py index 90ebdab034ac..eef1d1a8ff3f 100644 --- a/lib/matplotlib/pie.py +++ b/lib/matplotlib/pie.py @@ -68,6 +68,12 @@ def fracs(self): def add_texts(self, texts): """Add a list of `~matplotlib.text.Text` objects to the pie artist.""" self._texts.append(texts) + fig = self.get_figure(root=False) + for t in texts: + t.set_figure(fig) + t.axes = self.axes + if not t.is_transform_set(): + t.set_transform(self.get_transform()) def remove(self): super().remove() @@ -98,6 +104,9 @@ def axes(self, new_axes): s.axes = new_axes for w in self.wedges: w.axes = new_axes + for t_list in self._texts: + for t in t_list: + t.axes = new_axes def set_transform(self, t): super().set_transform(t) @@ -105,6 +114,9 @@ def set_transform(self, t): s.set_transform(t) for w in self.wedges: w.set_transform(t) + for t_list in self._texts: + for txt in t_list: + txt.set_transform(t) def set_figure(self, fig): super().set_figure(fig) @@ -112,3 +124,6 @@ def set_figure(self, fig): s.set_figure(fig) for w in self.wedges: w.set_figure(fig) + for t_list in self._texts: + for t in t_list: + t.set_figure(fig) diff --git a/lib/matplotlib/tests/test_pie.py b/lib/matplotlib/tests/test_pie.py index a8de923f41a7..f525d72029f8 100644 --- a/lib/matplotlib/tests/test_pie.py +++ b/lib/matplotlib/tests/test_pie.py @@ -7,14 +7,12 @@ def test_pie_remove(): ax.pie_label(pie, ['baz', 'qux']) assert len(ax.patches) == 0 + assert len(ax.texts) == 0 assert pie in ax._children - # We have added 6 labels but pie also adds an empty Text artist to each - # wedge if labeldistance is not None and labels is not passed - assert len(ax.texts) == 8 pie.remove() assert pie not in ax._children - # assert not ax.texts + assert not ax.texts def test_pie_unpack_backcompat(): From 5a2f69a3e70e541a04ce5cf8ce949fe9058963aa Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 19:02:09 +0100 Subject: [PATCH 04/15] Make Pie participate in layout and autoscaling --- lib/matplotlib/axes/_axes.py | 2 ++ lib/matplotlib/pie.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index dc4ef8e785fe..b8c485fd7ebc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3793,6 +3793,8 @@ def get_next_color(): pie = Pie(slices, x, normalize, shadows) self.add_artist(pie) + for w in slices: + self._update_patch_limits(w) if wedge_labels is not None: self.pie_label(pie, wedge_labels, distance=wedge_label_distance, diff --git a/lib/matplotlib/pie.py b/lib/matplotlib/pie.py index eef1d1a8ff3f..9707caedf305 100644 --- a/lib/matplotlib/pie.py +++ b/lib/matplotlib/pie.py @@ -1,5 +1,6 @@ from matplotlib import cbook from .artist import Artist +from .transforms import Bbox class Pie(Artist): """ @@ -37,6 +38,7 @@ def __init__(self, wedges, values, normalize, shadows=None): Shadow patches associated with the wedges. """ super().__init__() + self.set_clip_on(False) self.wedges = wedges self._texts = [] self._values = values @@ -97,6 +99,19 @@ def draw(self, renderer): renderer.close_group('pie') self.stale = False + def get_children(self): + """Return the Artists contained by the pie.""" + return [*self._shadows, *self.wedges, *cbook.flatten(self._texts)] + + def get_tightbbox(self, renderer=None): + # docstring inherited + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + bboxes = [bbox for child in self.get_children() + if (bbox := child.get_tightbbox(renderer)) is not None + and bbox._is_finite()] + return Bbox.union(bboxes) if bboxes else None + @Artist.axes.setter def axes(self, new_axes): Artist.axes.fset(self, new_axes) From 8e46532d681743ee8e9f7cc5fa976a8a36582d38 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 19:46:54 +0100 Subject: [PATCH 05/15] Convert indentation to spaces --- lib/matplotlib/pie.py | 278 +++++++++++++++---------------- lib/matplotlib/tests/test_pie.py | 32 ++-- 2 files changed, 155 insertions(+), 155 deletions(-) diff --git a/lib/matplotlib/pie.py b/lib/matplotlib/pie.py index 9707caedf305..92cfb72cf4ca 100644 --- a/lib/matplotlib/pie.py +++ b/lib/matplotlib/pie.py @@ -3,142 +3,142 @@ from .transforms import Bbox class Pie(Artist): - """ - Compound Artist representing a pie chart. - - .. versionadded:: 3.12 - - Attributes - ---------- - wedges : list of `~matplotlib.patches.Wedge` - The artists of the pie wedges. - - values : `numpy.ndarray` - The data that the pie is based on. - - fracs : `numpy.ndarray` - The fraction of the pie that each wedge represents. - - texts : list of list of `~matplotlib.text.Text` - The artists of any labels on the pie wedges. Each inner list has one - text label per wedge. - """ - - def __init__(self, wedges, values, normalize, shadows=None): - """ - Parameters - ---------- - wedges : list of `~matplotlib.patches.Wedge` - The artists of the pie wedges. - values : `numpy.ndarray` - The data that the pie is based on. - normalize : bool, default: True - Whether the pie slices are normalized to sum to 1. - shadows : list of `~matplotlib.patches.Shadow`, optional - Shadow patches associated with the wedges. - """ - super().__init__() - self.set_clip_on(False) - self.wedges = wedges - self._texts = [] - self._values = values - self._normalize = normalize - self._shadows = list(shadows) if shadows else [] - - @property - def texts(self): - # Only return non-empty sublists. An empty sublist may have been added - # for backwards compatibility of the Axes.pie return value (see __getitem__). - return [t_list for t_list in self._texts if t_list] - - @property - def values(self): - result = self._values.copy() - result.flags.writeable = False - return result - - @property - def fracs(self): - if self._normalize: - result = self._values / self._values.sum() - else: - result = self._values - - result.flags.writeable = False - return result - - def add_texts(self, texts): - """Add a list of `~matplotlib.text.Text` objects to the pie artist.""" - self._texts.append(texts) - fig = self.get_figure(root=False) - for t in texts: - t.set_figure(fig) - t.axes = self.axes - if not t.is_transform_set(): - t.set_transform(self.get_transform()) - - def remove(self): - super().remove() - - def __getitem__(self, key): - # needed to support unpacking into a tuple for backward compatibility of the - # Axes.pie return value - return (self.wedges, *self._texts)[key] - - def draw(self, renderer): - if not self.get_visible(): - return - renderer.open_group('pie', gid=self.get_gid()) - for s in self._shadows: - s.draw(renderer) - for w in self.wedges: - w.draw(renderer) - for t_list in self._texts: - for t in t_list: - t.draw(renderer) - renderer.close_group('pie') - self.stale = False - - def get_children(self): - """Return the Artists contained by the pie.""" - return [*self._shadows, *self.wedges, *cbook.flatten(self._texts)] - - def get_tightbbox(self, renderer=None): - # docstring inherited - if renderer is None: - renderer = self.get_figure(root=True)._get_renderer() - bboxes = [bbox for child in self.get_children() - if (bbox := child.get_tightbbox(renderer)) is not None - and bbox._is_finite()] - return Bbox.union(bboxes) if bboxes else None - - @Artist.axes.setter - def axes(self, new_axes): - Artist.axes.fset(self, new_axes) - for s in self._shadows: - s.axes = new_axes - for w in self.wedges: - w.axes = new_axes - for t_list in self._texts: - for t in t_list: - t.axes = new_axes - - def set_transform(self, t): - super().set_transform(t) - for s in self._shadows: - s.set_transform(t) - for w in self.wedges: - w.set_transform(t) - for t_list in self._texts: - for txt in t_list: - txt.set_transform(t) - - def set_figure(self, fig): - super().set_figure(fig) - for s in self._shadows: - s.set_figure(fig) - for w in self.wedges: - w.set_figure(fig) - for t_list in self._texts: - for t in t_list: - t.set_figure(fig) + """ + Compound Artist representing a pie chart. + + .. versionadded:: 3.12 + + Attributes + ---------- + wedges : list of `~matplotlib.patches.Wedge` + The artists of the pie wedges. + + values : `numpy.ndarray` + The data that the pie is based on. + + fracs : `numpy.ndarray` + The fraction of the pie that each wedge represents. + + texts : list of list of `~matplotlib.text.Text` + The artists of any labels on the pie wedges. Each inner list has one + text label per wedge. + """ + + def __init__(self, wedges, values, normalize, shadows=None): + """ + Parameters + ---------- + wedges : list of `~matplotlib.patches.Wedge` + The artists of the pie wedges. + values : `numpy.ndarray` + The data that the pie is based on. + normalize : bool, default: True + Whether the pie slices are normalized to sum to 1. + shadows : list of `~matplotlib.patches.Shadow`, optional + Shadow patches associated with the wedges. + """ + super().__init__() + self.set_clip_on(False) + self.wedges = wedges + self._texts = [] + self._values = values + self._normalize = normalize + self._shadows = list(shadows) if shadows else [] + + @property + def texts(self): + # Only return non-empty sublists. An empty sublist may have been added + # for backwards compatibility of the Axes.pie return value (see __getitem__). + return [t_list for t_list in self._texts if t_list] + + @property + def values(self): + result = self._values.copy() + result.flags.writeable = False + return result + + @property + def fracs(self): + if self._normalize: + result = self._values / self._values.sum() + else: + result = self._values + + result.flags.writeable = False + return result + + def add_texts(self, texts): + """Add a list of `~matplotlib.text.Text` objects to the pie artist.""" + self._texts.append(texts) + fig = self.get_figure(root=False) + for t in texts: + t.set_figure(fig) + t.axes = self.axes + if not t.is_transform_set(): + t.set_transform(self.get_transform()) + + def remove(self): + super().remove() + + def __getitem__(self, key): + # needed to support unpacking into a tuple for backward compatibility of the + # Axes.pie return value + return (self.wedges, *self._texts)[key] + + def draw(self, renderer): + if not self.get_visible(): + return + renderer.open_group('pie', gid=self.get_gid()) + for s in self._shadows: + s.draw(renderer) + for w in self.wedges: + w.draw(renderer) + for t_list in self._texts: + for t in t_list: + t.draw(renderer) + renderer.close_group('pie') + self.stale = False + + def get_children(self): + """Return the Artists contained by the pie.""" + return [*self._shadows, *self.wedges, *cbook.flatten(self._texts)] + + def get_tightbbox(self, renderer=None): + # docstring inherited + if renderer is None: + renderer = self.get_figure(root=True)._get_renderer() + bboxes = [bbox for child in self.get_children() + if (bbox := child.get_tightbbox(renderer)) is not None + and bbox._is_finite()] + return Bbox.union(bboxes) if bboxes else None + + @Artist.axes.setter + def axes(self, new_axes): + Artist.axes.fset(self, new_axes) + for s in self._shadows: + s.axes = new_axes + for w in self.wedges: + w.axes = new_axes + for t_list in self._texts: + for t in t_list: + t.axes = new_axes + + def set_transform(self, t): + super().set_transform(t) + for s in self._shadows: + s.set_transform(t) + for w in self.wedges: + w.set_transform(t) + for t_list in self._texts: + for txt in t_list: + txt.set_transform(t) + + def set_figure(self, fig): + super().set_figure(fig) + for s in self._shadows: + s.set_figure(fig) + for w in self.wedges: + w.set_figure(fig) + for t_list in self._texts: + for t in t_list: + t.set_figure(fig) diff --git a/lib/matplotlib/tests/test_pie.py b/lib/matplotlib/tests/test_pie.py index f525d72029f8..14a1db8f157d 100644 --- a/lib/matplotlib/tests/test_pie.py +++ b/lib/matplotlib/tests/test_pie.py @@ -2,25 +2,25 @@ def test_pie_remove(): - fig, ax = plt.subplots() - pie = ax.pie([2, 3], wedge_labels=['foo', 'bar'], autopct="%1.0f%%") - ax.pie_label(pie, ['baz', 'qux']) + fig, ax = plt.subplots() + pie = ax.pie([2, 3], wedge_labels=['foo', 'bar'], autopct="%1.0f%%") + ax.pie_label(pie, ['baz', 'qux']) - assert len(ax.patches) == 0 - assert len(ax.texts) == 0 - assert pie in ax._children + assert len(ax.patches) == 0 + assert len(ax.texts) == 0 + assert pie in ax._children - pie.remove() - assert pie not in ax._children - assert not ax.texts + pie.remove() + assert pie not in ax._children + assert not ax.texts def test_pie_unpack_backcompat(): - fig, ax = plt.subplots() - wedges, texts, autotexts = ax.pie( - [2, 3], labels=['foo', 'bar'], autopct="%1.0f%%", labeldistance=None) + fig, ax = plt.subplots() + wedges, texts, autotexts = ax.pie( + [2, 3], labels=['foo', 'bar'], autopct="%1.0f%%", labeldistance=None) - assert len(wedges) == 2 - assert isinstance(texts, list) - assert not texts - assert len(autotexts) == 2 + assert len(wedges) == 2 + assert isinstance(texts, list) + assert not texts + assert len(autotexts) == 2 From e461515665b047ee3f894942fea1d19e846d7635 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 19:48:20 +0100 Subject: [PATCH 06/15] Run ruff format --- lib/matplotlib/pie.py | 13 ++++++++----- lib/matplotlib/tests/test_pie.py | 7 ++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/pie.py b/lib/matplotlib/pie.py index 92cfb72cf4ca..70594c07aefc 100644 --- a/lib/matplotlib/pie.py +++ b/lib/matplotlib/pie.py @@ -2,6 +2,7 @@ from .artist import Artist from .transforms import Bbox + class Pie(Artist): """ Compound Artist representing a pie chart. @@ -88,7 +89,7 @@ def __getitem__(self, key): def draw(self, renderer): if not self.get_visible(): return - renderer.open_group('pie', gid=self.get_gid()) + renderer.open_group("pie", gid=self.get_gid()) for s in self._shadows: s.draw(renderer) for w in self.wedges: @@ -96,7 +97,7 @@ def draw(self, renderer): for t_list in self._texts: for t in t_list: t.draw(renderer) - renderer.close_group('pie') + renderer.close_group("pie") self.stale = False def get_children(self): @@ -107,9 +108,11 @@ def get_tightbbox(self, renderer=None): # docstring inherited if renderer is None: renderer = self.get_figure(root=True)._get_renderer() - bboxes = [bbox for child in self.get_children() - if (bbox := child.get_tightbbox(renderer)) is not None - and bbox._is_finite()] + bboxes = [ + bbox + for child in self.get_children() + if (bbox := child.get_tightbbox(renderer)) is not None and bbox._is_finite() + ] return Bbox.union(bboxes) if bboxes else None @Artist.axes.setter diff --git a/lib/matplotlib/tests/test_pie.py b/lib/matplotlib/tests/test_pie.py index 14a1db8f157d..9394b8d935a1 100644 --- a/lib/matplotlib/tests/test_pie.py +++ b/lib/matplotlib/tests/test_pie.py @@ -3,8 +3,8 @@ def test_pie_remove(): fig, ax = plt.subplots() - pie = ax.pie([2, 3], wedge_labels=['foo', 'bar'], autopct="%1.0f%%") - ax.pie_label(pie, ['baz', 'qux']) + pie = ax.pie([2, 3], wedge_labels=["foo", "bar"], autopct="%1.0f%%") + ax.pie_label(pie, ["baz", "qux"]) assert len(ax.patches) == 0 assert len(ax.texts) == 0 @@ -18,7 +18,8 @@ def test_pie_remove(): def test_pie_unpack_backcompat(): fig, ax = plt.subplots() wedges, texts, autotexts = ax.pie( - [2, 3], labels=['foo', 'bar'], autopct="%1.0f%%", labeldistance=None) + [2, 3], labels=["foo", "bar"], autopct="%1.0f%%", labeldistance=None + ) assert len(wedges) == 2 assert isinstance(texts, list) From f8d8686f00b84d14f4394d0983fe15df0879db18 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 20:06:31 +0100 Subject: [PATCH 07/15] Create get_legend_handles protocol --- lib/matplotlib/legend.py | 20 ++++++++++++-------- lib/matplotlib/pie.py | 4 ++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 01324fed9078..853679f2819e 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1284,19 +1284,23 @@ def get_draggable(self): # `axes.legend`: def _get_legend_handles(axs, legend_handler_map=None): """Yield artists that can be used as handles in a legend.""" + def _extract_handles(ax): + handles = [] + for a in ax._children: + if isinstance(a, (Line2D, Patch, Collection, Text)): + handles.append(a) + elif hasattr(a, 'get_legend_handles'): + handles.extend(a.get_legend_handles()) + handles += ax.containers + return handles + handles_original = [] for ax in axs: - handles_original += [ - *(a for a in ax._children - if isinstance(a, (Line2D, Patch, Collection, Text))), - *ax.containers] + handles_original += _extract_handles(ax) # support parasite Axes: if hasattr(ax, 'parasites'): for axx in ax.parasites: - handles_original += [ - *(a for a in axx._children - if isinstance(a, (Line2D, Patch, Collection, Text))), - *axx.containers] + handles_original += _extract_handles(axx) handler_map = {**Legend.get_default_handler_map(), **(legend_handler_map or {})} diff --git a/lib/matplotlib/pie.py b/lib/matplotlib/pie.py index 70594c07aefc..ea45c13ab4dc 100644 --- a/lib/matplotlib/pie.py +++ b/lib/matplotlib/pie.py @@ -104,6 +104,10 @@ def get_children(self): """Return the Artists contained by the pie.""" return [*self._shadows, *self.wedges, *cbook.flatten(self._texts)] + def get_legend_handles(self): + """Return the artists to be used as legend handles.""" + return list(self.wedges) + def get_tightbbox(self, renderer=None): # docstring inherited if renderer is None: From 94d37037c832e9affb296a31f3c1818124efdfa8 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 20:33:27 +0100 Subject: [PATCH 08/15] Add deprecated PieContainer --- lib/matplotlib/container.py | 11 ++++++++++- lib/matplotlib/tests/test_container.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index 05eb2e3b1e14..f1d44a95ab62 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -1,5 +1,6 @@ -from matplotlib import cbook +from matplotlib import _api, cbook from matplotlib.artist import Artist +from matplotlib.pie import Pie class Container(tuple): @@ -201,3 +202,11 @@ def __init__(self, markerline_stemlines_baseline, **kwargs): self.stemlines = stemlines self.baseline = baseline super().__init__(markerline_stemlines_baseline, **kwargs) + + +@_api.caching_module_getattr +class __getattr__: + @_api.deprecated("3.12", alternative="matplotlib.pie.Pie") + @property + def PieContainer(self): + return Pie diff --git a/lib/matplotlib/tests/test_container.py b/lib/matplotlib/tests/test_container.py index 6998101dd755..9bdfcb83011f 100644 --- a/lib/matplotlib/tests/test_container.py +++ b/lib/matplotlib/tests/test_container.py @@ -1,5 +1,7 @@ +import pytest import numpy as np from numpy.testing import assert_array_equal +import matplotlib as mpl import matplotlib.pyplot as plt @@ -53,3 +55,11 @@ def test_barcontainer_position_centers__bottoms__tops(): assert_array_equal(container.position_centers, pos) assert_array_equal(container.bottoms, bottoms) assert_array_equal(container.tops, bottoms + heights) + + +def test_piecontainer_deprecated(): + import matplotlib.container as mc + mc.__getattr__.cache_clear() + with pytest.warns(mpl.MatplotlibDeprecationWarning, match="PieContainer"): + pc = mc.PieContainer([], [], True) + assert isinstance(pc, mpl.pie.Pie) From 769d66d3d581e0303c12e5f6a10c2da2314731f2 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 21:15:28 +0100 Subject: [PATCH 09/15] Add What's new entry, next api changes entries --- doc/api/index.rst | 1 + doc/api/next_api_changes/behavior/32320-RM.rst | 8 ++++++++ .../next_api_changes/deprecations/32320-RM.rst | 6 ++++++ doc/api/pie_api.rst | 8 ++++++++ doc/release/next_whats_new/pie_artist.rst | 18 ++++++++++++++++++ lib/matplotlib/axes/_axes.py | 9 ++++++++- 6 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 doc/api/next_api_changes/behavior/32320-RM.rst create mode 100644 doc/api/next_api_changes/deprecations/32320-RM.rst create mode 100644 doc/api/pie_api.rst create mode 100644 doc/release/next_whats_new/pie_artist.rst diff --git a/doc/api/index.rst b/doc/api/index.rst index 04c0e279a4fe..5d6fb94d890a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -117,6 +117,7 @@ Alphabetical list of modules: patches_api.rst path_api.rst patheffects_api.rst + pie_api.rst pyplot_summary.rst projections_api.rst quiver_api.rst diff --git a/doc/api/next_api_changes/behavior/32320-RM.rst b/doc/api/next_api_changes/behavior/32320-RM.rst new file mode 100644 index 000000000000..0060c9191f9a --- /dev/null +++ b/doc/api/next_api_changes/behavior/32320-RM.rst @@ -0,0 +1,8 @@ +Axes.pie returns a Pie artist +----------------------------- + +`~.Axes.pie` now returns a `.Pie` artist instead of a +``matplotlib.container.PieContainer``. The wedge patches, shadows and labels +are children of the returned `.Pie` and are no longer added to +`.Axes.patches` and `.Axes.texts`; use `.Pie.wedges` and `.Pie.texts` to +access them. diff --git a/doc/api/next_api_changes/deprecations/32320-RM.rst b/doc/api/next_api_changes/deprecations/32320-RM.rst new file mode 100644 index 000000000000..d8ce3e3ca96e --- /dev/null +++ b/doc/api/next_api_changes/deprecations/32320-RM.rst @@ -0,0 +1,6 @@ +matplotlib.container.PieContainer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``matplotlib.container.PieContainer`` is deprecated. `~.Axes.pie` now returns +a `.Pie` artist, which collects the wedge patches, shadows and labels as child +artists. Use `.Pie`, `.Pie.wedges` and `.Pie.texts` instead. diff --git a/doc/api/pie_api.rst b/doc/api/pie_api.rst new file mode 100644 index 000000000000..ce13f7955af2 --- /dev/null +++ b/doc/api/pie_api.rst @@ -0,0 +1,8 @@ +****************** +``matplotlib.pie`` +****************** + +.. automodule:: matplotlib.pie + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/release/next_whats_new/pie_artist.rst b/doc/release/next_whats_new/pie_artist.rst new file mode 100644 index 000000000000..c1b53078bc32 --- /dev/null +++ b/doc/release/next_whats_new/pie_artist.rst @@ -0,0 +1,18 @@ +Pie charts are now a single artist +---------------------------------- + +`.Axes.pie` now returns a `.Pie` artist instead of a ``PieContainer`` (which +is deprecated). The `.Pie` collects the wedge patches, the optional shadow +patches and all labels as child artists, so the whole chart can be treated +as one object:: + + pie = ax.pie([1, 2, 3], shadow=True) + pie.remove() + +Because the wedges and labels are children of the `.Pie`, they are no longer +added to `.Axes.patches` and `.Axes.texts` directly. Access them through +`~.Pie.wedges` and `~.Pie.texts` instead. + +Compound artists can provide their own legend entries by implementing +`~.Pie.get_legend_handles`; `.Axes.legend` uses this to show the individual +wedges of a pie. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b8c485fd7ebc..db13e0ec30be 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3675,11 +3675,18 @@ def pie(self, x, *, explode=None, labels=None, colors=None, wedge_labels=None, Returns ------- `.Pie` - Artist with all the wedge patches and any associated text objects. + Artist with all the wedge patches, shadow patches, and any + associated text objects. .. versionchanged:: 3.11 Previously the wedges and texts were returned in a tuple. + .. versionchanged:: 3.12 + The returned `.Pie` artist replaces the deprecated + ``matplotlib.container.PieContainer``. The wedges, shadows and + labels are now children of the returned artist instead of being + added to the Axes directly. + Notes ----- The pie chart will probably look best if the figure and Axes are From 650a4d8de687cd9f3c93d535047ceeaa5f1ecce3 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 21:25:12 +0100 Subject: [PATCH 10/15] Update .pyi files --- ci/mypy-stubtest-allowlist.txt | 3 --- lib/matplotlib/container.pyi | 25 ++++--------------------- lib/matplotlib/meson.build | 1 + lib/matplotlib/pie.pyi | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 lib/matplotlib/pie.pyi diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 6db1d6be923e..0dcbdc17505f 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -54,8 +54,5 @@ matplotlib\.animation\.EventSourceProtocol # https://github.com/python/mypy/issues/19877 matplotlib\.ft2font\.GlyphIndexType\.__init__ -# getitem method only exists for 3.11 deprecation backcompatability -matplotlib.container.PieContainer.__getitem__ - # 3.12 deprecation matplotlib\.axes\._base\._AxesBase\.ArtistList diff --git a/lib/matplotlib/container.pyi b/lib/matplotlib/container.pyi index 753fe518b9ef..aa4d8d89ac22 100644 --- a/lib/matplotlib/container.pyi +++ b/lib/matplotlib/container.pyi @@ -1,13 +1,12 @@ from matplotlib.artist import Artist from matplotlib.lines import Line2D from matplotlib.collections import LineCollection -from matplotlib.patches import Rectangle, Wedge -from matplotlib.text import Text +from matplotlib.patches import Rectangle +from matplotlib.pie import Pie from collections.abc import Callable from typing import Any, Literal from numpy.typing import ArrayLike -from numpy import ndarray class Container(tuple): def __new__(cls, *args, **kwargs): ... @@ -57,24 +56,8 @@ class ErrorbarContainer(Container): **kwargs ) -> None: ... -class PieContainer: - wedges: list[Wedge] - def __init__( - self, - wedges: list[Wedge], - values: ndarray, - normalize: bool, - ) -> None: ... - @property - def texts(self) -> list[list[Text]]: ... - @property - def values(self) -> ndarray: ... - @property - def fracs(self) -> ndarray: ... - def add_texts(self, - texts: list[Text], - ) -> None: ... - def remove(self) -> None: ... +# Deprecated alias for matplotlib.pie.Pie +PieContainer = Pie class StemContainer(Container): markerline: Line2D diff --git a/lib/matplotlib/meson.build b/lib/matplotlib/meson.build index 06945ada1fd6..3a7197772817 100644 --- a/lib/matplotlib/meson.build +++ b/lib/matplotlib/meson.build @@ -126,6 +126,7 @@ typing_sources = [ 'patches.pyi', 'patheffects.pyi', 'path.pyi', + 'pie.pyi', 'quiver.pyi', 'rcsetup.pyi', 'sankey.pyi', diff --git a/lib/matplotlib/pie.pyi b/lib/matplotlib/pie.pyi new file mode 100644 index 000000000000..5e8d8cf8c163 --- /dev/null +++ b/lib/matplotlib/pie.pyi @@ -0,0 +1,32 @@ +from .artist import Artist +from .backend_bases import RendererBase +from .patches import Shadow, Wedge +from .text import Text +from .transforms import Bbox + +from typing import Any + +from numpy import ndarray + +class Pie(Artist): + wedges: list[Wedge] + def __init__( + self, + wedges: list[Wedge], + values: ndarray, + normalize: bool, + shadows: list[Shadow] | None = ..., + ) -> None: ... + @property + def texts(self) -> list[list[Text]]: ... + @property + def values(self) -> ndarray: ... + @property + def fracs(self) -> ndarray: ... + def add_texts(self, texts: list[Text]) -> None: ... + def get_children(self) -> list[Artist]: ... + def get_legend_handles(self) -> list[Artist]: ... + def get_tightbbox( + self, renderer: RendererBase | None = ... + ) -> Bbox | None: ... + def __getitem__(self, key: Any) -> Any: ... From df5151fffd819a480ddaf21c87c595775c132daa Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Wed, 16 Sep 2026 22:08:28 +0100 Subject: [PATCH 11/15] Use Pie instead of PieContainer in _axes.pyi --- lib/matplotlib/axes/_axes.pyi | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 27dd5d997898..48a3d6d597e0 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -20,7 +20,7 @@ from matplotlib.colors import ( Normalize, ) from matplotlib.container import ( - BarContainer, PieContainer, ErrorbarContainer, StemContainer) + BarContainer, ErrorbarContainer, StemContainer) from matplotlib.contour import ContourSet, QuadContourSet from matplotlib.image import AxesImage, PcolorImage from matplotlib.inset import InsetIndicator @@ -29,6 +29,7 @@ from matplotlib.legend_handler import HandlerBase from matplotlib.lines import Line2D, AxLine from matplotlib.mlab import GaussianKDE from matplotlib.patches import Rectangle, FancyArrow, Polygon, StepPatch +from matplotlib.pie import Pie from matplotlib.quiver import Quiver, QuiverKey, Barbs from matplotlib.text import Annotation, Text from matplotlib.transforms import Transform @@ -334,10 +335,10 @@ class Axes(_AxesBase): normalize: bool = ..., hatch: str | Sequence[str] | None = ..., data: DataParamType = ..., - ) -> PieContainer: ... + ) -> Pie: ... def pie_label( self, - container: PieContainer, + pie: Pie, /, labels: str | Sequence[str], *, From f010f97e52ffb6bc0177d181fb56f7ebe68344a4 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Thu, 17 Sep 2026 00:40:22 +0100 Subject: [PATCH 12/15] Fix build documentation errors --- doc/api/next_api_changes/behavior/32320-RM.rst | 2 +- doc/api/next_api_changes/deprecations/32320-RM.rst | 2 +- doc/release/next_whats_new/pie_artist.rst | 4 ++-- .../examples/pie_and_polar_charts/pie_and_donut_labels.py | 7 ++++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/api/next_api_changes/behavior/32320-RM.rst b/doc/api/next_api_changes/behavior/32320-RM.rst index 0060c9191f9a..351c0f489260 100644 --- a/doc/api/next_api_changes/behavior/32320-RM.rst +++ b/doc/api/next_api_changes/behavior/32320-RM.rst @@ -4,5 +4,5 @@ Axes.pie returns a Pie artist `~.Axes.pie` now returns a `.Pie` artist instead of a ``matplotlib.container.PieContainer``. The wedge patches, shadows and labels are children of the returned `.Pie` and are no longer added to -`.Axes.patches` and `.Axes.texts`; use `.Pie.wedges` and `.Pie.texts` to +``Axes.patches`` and ``Axes.texts``; use ``Pie.wedges`` and `.Pie.texts` to access them. diff --git a/doc/api/next_api_changes/deprecations/32320-RM.rst b/doc/api/next_api_changes/deprecations/32320-RM.rst index d8ce3e3ca96e..e5e73078017a 100644 --- a/doc/api/next_api_changes/deprecations/32320-RM.rst +++ b/doc/api/next_api_changes/deprecations/32320-RM.rst @@ -3,4 +3,4 @@ matplotlib.container.PieContainer ``matplotlib.container.PieContainer`` is deprecated. `~.Axes.pie` now returns a `.Pie` artist, which collects the wedge patches, shadows and labels as child -artists. Use `.Pie`, `.Pie.wedges` and `.Pie.texts` instead. +artists. Use `.Pie` with its ``wedges`` and `.Pie.texts` attributes instead. diff --git a/doc/release/next_whats_new/pie_artist.rst b/doc/release/next_whats_new/pie_artist.rst index c1b53078bc32..724ee2e729f2 100644 --- a/doc/release/next_whats_new/pie_artist.rst +++ b/doc/release/next_whats_new/pie_artist.rst @@ -10,8 +10,8 @@ as one object:: pie.remove() Because the wedges and labels are children of the `.Pie`, they are no longer -added to `.Axes.patches` and `.Axes.texts` directly. Access them through -`~.Pie.wedges` and `~.Pie.texts` instead. +added to ``Axes.patches`` and ``Axes.texts`` directly. Access them through +``Pie.wedges`` and `.Pie.texts` instead. Compound artists can provide their own legend entries by implementing `~.Pie.get_legend_handles`; `.Axes.legend` uses this to show the individual diff --git a/galleries/examples/pie_and_polar_charts/pie_and_donut_labels.py b/galleries/examples/pie_and_polar_charts/pie_and_donut_labels.py index 78e884128d1e..2f703c1cd389 100644 --- a/galleries/examples/pie_and_polar_charts/pie_and_donut_labels.py +++ b/galleries/examples/pie_and_polar_charts/pie_and_donut_labels.py @@ -16,14 +16,15 @@ # Now it's time for the pie. Starting with a pie recipe, we create the data # and a list of labels from it. # -# We then create the pie and store the returned `~matplotlib.container.PieContainer` +# We then create the pie and store the returned `~matplotlib.pie.Pie` # object for later. # -# We can provide the `~matplotlib.container.PieContainer` and a format string to +# We can provide the `~matplotlib.pie.Pie` and a format string to # the `~matplotlib.axes.Axes.pie_label` method to automatically label each # ingredient's wedge with its weight in grams and percentages. # -# The `~.PieContainer` has a list of patches as one of its attributes. Those are +# The `~matplotlib.pie.Pie` has a list of ``wedges`` as one of its attributes. +# Those are # `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, From 91f6ec76c606f117c9c1048cbe4dabc4d2e60563 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Thu, 17 Sep 2026 00:43:02 +0100 Subject: [PATCH 13/15] Use correct PR number --- doc/api/next_api_changes/behavior/{32320-RM.rst => 32359-RM.rst} | 0 .../next_api_changes/deprecations/{32320-RM.rst => 32359-RM.rst} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename doc/api/next_api_changes/behavior/{32320-RM.rst => 32359-RM.rst} (100%) rename doc/api/next_api_changes/deprecations/{32320-RM.rst => 32359-RM.rst} (100%) diff --git a/doc/api/next_api_changes/behavior/32320-RM.rst b/doc/api/next_api_changes/behavior/32359-RM.rst similarity index 100% rename from doc/api/next_api_changes/behavior/32320-RM.rst rename to doc/api/next_api_changes/behavior/32359-RM.rst diff --git a/doc/api/next_api_changes/deprecations/32320-RM.rst b/doc/api/next_api_changes/deprecations/32359-RM.rst similarity index 100% rename from doc/api/next_api_changes/deprecations/32320-RM.rst rename to doc/api/next_api_changes/deprecations/32359-RM.rst From 05fa8ffd0d4c82bbdf89473553cde96a743871bb Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Thu, 17 Sep 2026 00:56:19 +0100 Subject: [PATCH 14/15] Run python tools/boilerplate.py --- lib/matplotlib/pyplot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 6671e07af64c..11d322c9ddc0 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3988,7 +3988,7 @@ def pie( normalize: bool = True, hatch: str | Sequence[str] | None = None, data: DataParamType = None, -) -> PieContainer: +) -> Pie: return gca().pie( x, explode=explode, @@ -4017,7 +4017,7 @@ def pie( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.pie_label) def pie_label( - container: PieContainer, + pie: Pie, /, labels: str | Sequence[str], *, @@ -4027,7 +4027,7 @@ def pie_label( alignment: str = "auto", ) -> list[Text]: return gca().pie_label( - container, + pie, labels, distance=distance, textprops=textprops, From ec7146fb426ac1dba4a8af43a447b04ac36ce35e Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Thu, 17 Sep 2026 01:02:02 +0100 Subject: [PATCH 15/15] Fix import in lib/matplotlib/pyplot.py --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 11d322c9ddc0..d1ce0625ee2e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -130,7 +130,6 @@ from matplotlib.container import ( BarContainer, ErrorbarContainer, - PieContainer, StemContainer, ) from matplotlib.figure import SubFigure @@ -138,6 +137,7 @@ from matplotlib.mlab import GaussianKDE from matplotlib.image import AxesImage, FigureImage from matplotlib.patches import FancyArrow, StepPatch + from matplotlib.pie import Pie from matplotlib.quiver import Barbs, Quiver, QuiverKey from matplotlib.scale import ScaleBase from matplotlib.typing import (