diff --git a/doc/users/next_whats_new/pie_hatch.rst b/doc/users/next_whats_new/pie_hatch.rst new file mode 100644 index 000000000000..f54def10e954 --- /dev/null +++ b/doc/users/next_whats_new/pie_hatch.rst @@ -0,0 +1,18 @@ +``hatch`` parameter for pie +------------------------------------------- + +`~matplotlib.axes.Axes.pie` now accepts a *hatch* keyword that takes as input +a hatch or list of hatches: + +.. plot:: + :include-source: true + :alt: Two pie charts, identified as ax1 and ax2, both have a small blue slice, a medium orange slice, and a large green slice. ax1 has a dot hatching on the small slice, a small open circle hatching on the medium slice, and a large open circle hatching on the large slice. ax2 has the same large open circle with a dot hatch on every slice. + + fig, (ax1, ax2) = plt.subplots(ncols=2) + x = [10, 30, 60] + + ax1.pie(x, hatch=['.', 'o', 'O']) + ax2.pie(x, hatch='.O') + + ax1.set_title("hatch=['.', 'o', 'O']") + ax2.set_title("hatch='.O'") diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 58d95912665b..c46c1b467fc1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3083,7 +3083,7 @@ def pie(self, x, explode=None, labels=None, colors=None, autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1, startangle=0, radius=1, counterclock=True, wedgeprops=None, textprops=None, center=(0, 0), - frame=False, rotatelabels=False, *, normalize=True): + frame=False, rotatelabels=False, *, normalize=True, hatch=None): """ Plot a pie chart. @@ -3109,29 +3109,34 @@ def pie(self, x, explode=None, labels=None, colors=None, A sequence of colors through which the pie chart will cycle. If *None*, will use the colors in the currently active cycle. + hatch : str or list, default: None + Hatching pattern applied to all pie wedges or sequence of patterns + through which the chart will cycle. For a list of valid patterns, + see :doc:`/gallery/shapes_and_collections/hatch_style_reference`. + + .. versionadded:: 3.7 + autopct : None or str or callable, default: None - If not *None*, is a string or function used to label the wedges - with their numeric value. The label will be placed inside the - wedge. If it is a format string, the label will be ``fmt % pct``. - If it is a function, it will be called. + If not *None*, *autopct* is a string or function used to label the + wedges with their numeric value. The label will be placed inside + the wedge. If *autopct* is a format string, the label will be + ``fmt % pct``. If *autopct* is a function, then it will be called. pctdistance : float, default: 0.6 - The ratio between the center of each pie slice and the start of - the text generated by *autopct*. Ignored if *autopct* is *None*. + The relative distance along the radius at which the the text + generated by *autopct* is drawn. To draw the text outside the pie, + set *pctdistance* > 1. This parameter is ignored if *autopct* is + ``None``. + + labeldistance : float or None, default: 1.1 + The relative distance along the radius at which the labels are + drawn. To draw the labels inside the pie, set *labeldistance* < 1. + If set to ``None``, labels are not drawn but are still stored for + use in `.legend`. shadow : bool, default: False Draw a shadow beneath the pie. - normalize : bool, default: True - When *True*, always make a full pie by normalizing x so that - ``sum(x) == 1``. *False* makes a partial pie if ``sum(x) <= 1`` - and raises a `ValueError` for ``sum(x) > 1``. - - labeldistance : float or None, default: 1.1 - The radial distance at which the pie labels are drawn. - If set to ``None``, label are not drawn, but are stored for use in - ``legend()`` - startangle : float, default: 0 degrees The angle by which the start of the pie is rotated, counterclockwise from the x-axis. @@ -3143,11 +3148,11 @@ def pie(self, x, explode=None, labels=None, colors=None, Specify fractions direction, clockwise or counterclockwise. wedgeprops : dict, default: None - Dict of arguments passed to the wedge objects making the pie. - For example, you can pass in ``wedgeprops = {'linewidth': 3}`` - to set the width of the wedge border lines equal to 3. - For more details, look at the doc/arguments of the wedge object. - By default, ``clip_on=False``. + Dict of arguments passed to each `.patches.Wedge` of the pie. + For example, ``wedgeprops = {'linewidth': 3}`` sets the width of + the wedge border lines equal to 3. By default, ``clip_on=False``. + When there is a conflict between these properties and other + keywords, properties passed to *wedgeprops* take precedence. textprops : dict, default: None Dict of arguments to pass to the text objects. @@ -3161,6 +3166,11 @@ def pie(self, x, explode=None, labels=None, colors=None, rotatelabels : bool, default: False Rotate each label to the angle of the corresponding slice if true. + normalize : bool, default: True + When *True*, always make a full pie by normalizing x so that + ``sum(x) == 1``. *False* makes a partial pie if ``sum(x) <= 1`` + and raises a `ValueError` for ``sum(x) > 1``. + data : indexable object, optional DATA_PARAMETER_PLACEHOLDER @@ -3215,6 +3225,8 @@ def pie(self, x, explode=None, labels=None, colors=None, def get_next_color(): return next(color_cycle) + hatch_cycle = itertools.cycle(np.atleast_1d(hatch)) + _api.check_isinstance(Number, radius=radius, startangle=startangle) if radius <= 0: raise ValueError(f'radius must be a positive number, not {radius}') @@ -3241,6 +3253,7 @@ def get_next_color(): w = mpatches.Wedge((x, y), radius, 360. * min(theta1, theta2), 360. * max(theta1, theta2), facecolor=get_next_color(), + hatch=next(hatch_cycle), clip_on=False, label=label) w.set(**wedgeprops) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 4e9734e184dc..e62d4f0b7481 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2784,7 +2784,7 @@ def pie( pctdistance=0.6, shadow=False, labeldistance=1.1, startangle=0, radius=1, counterclock=True, wedgeprops=None, textprops=None, center=(0, 0), frame=False, - rotatelabels=False, *, normalize=True, data=None): + rotatelabels=False, *, normalize=True, hatch=None, data=None): return gca().pie( x, explode=explode, labels=labels, colors=colors, autopct=autopct, pctdistance=pctdistance, shadow=shadow, @@ -2792,7 +2792,7 @@ def pie( radius=radius, counterclock=counterclock, wedgeprops=wedgeprops, textprops=textprops, center=center, frame=frame, rotatelabels=rotatelabels, normalize=normalize, - **({"data": data} if data is not None else {})) + hatch=hatch, **({"data": data} if data is not None else {})) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3b471d4e0d92..7acd1040c5d6 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5744,6 +5744,24 @@ def test_normalize_kwarg_pie(): assert abs(t2[0][-1].theta2 - 360.) > 1e-3 +@check_figures_equal() +def test_pie_hatch_single(fig_test, fig_ref): + x = [0.3, 0.3, 0.1] + hatch = '+' + fig_test.subplots().pie(x, hatch=hatch) + wedges, _ = fig_ref.subplots().pie(x) + [w.set_hatch(hatch) for w in wedges] + + +@check_figures_equal() +def test_pie_hatch_multi(fig_test, fig_ref): + x = [0.3, 0.3, 0.1] + hatch = ['/', '+', '.'] + fig_test.subplots().pie(x, hatch=hatch) + wedges, _ = fig_ref.subplots().pie(x) + [w.set_hatch(hp) for w, hp in zip(wedges, hatch)] + + @image_comparison(['set_get_ticklabels.png']) def test_set_get_ticklabels(): # test issue 2246