diff --git a/doc/users/next_whats_new/pie_shadow_control.rst b/doc/users/next_whats_new/pie_shadow_control.rst new file mode 100644 index 000000000000..a112928e5b6f --- /dev/null +++ b/doc/users/next_whats_new/pie_shadow_control.rst @@ -0,0 +1,5 @@ +The pie chart shadow can be controlled +-------------------------------------- + +The *shadow* argument to `~.Axes.pie` can now be a dict, allowing more control +of the `.Shadow`-patch used. diff --git a/doc/users/next_whats_new/shadow_shade.rst b/doc/users/next_whats_new/shadow_shade.rst new file mode 100644 index 000000000000..cafa2f4d346f --- /dev/null +++ b/doc/users/next_whats_new/shadow_shade.rst @@ -0,0 +1,6 @@ +Shadow shade can be controlled +------------------------------ + +The `.Shadow` patch now has a *shade* argument to control the shadow darkness. +If 1, the shadow is black, if 0, the shadow has the same color as the patch that +is shadowed. The default value, which earlier was fixed, is 0.7. diff --git a/galleries/examples/pie_and_polar_charts/pie_demo2.py b/galleries/examples/pie_and_polar_charts/pie_demo2.py deleted file mode 100644 index 61f88a15d8c8..000000000000 --- a/galleries/examples/pie_and_polar_charts/pie_demo2.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -========= -Pie Demo2 -========= - -Make a pie charts using `~.axes.Axes.pie`. - -This example demonstrates some pie chart features like labels, varying size, -autolabeling the percentage, offsetting a slice and adding a shadow. -""" - -import matplotlib.pyplot as plt - -# Some data -labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' -fracs = [15, 30, 45, 10] - -# Make figure and axes -fig, axs = plt.subplots(2, 2) - -# A standard pie plot -axs[0, 0].pie(fracs, labels=labels, autopct='%1.1f%%', shadow=True) - -# Shift the second slice using explode -axs[0, 1].pie(fracs, labels=labels, autopct='%.0f%%', shadow=True, - explode=(0, 0.1, 0, 0)) - -# Adapt radius and text size for a smaller pie -patches, texts, autotexts = axs[1, 0].pie(fracs, labels=labels, - autopct='%.0f%%', - textprops={'size': 'smaller'}, - shadow=True, radius=0.5) -# Make percent texts even smaller -plt.setp(autotexts, size='x-small') -autotexts[0].set_color('white') - -# Use a smaller explode and turn of the shadow for better visibility -patches, texts, autotexts = axs[1, 1].pie(fracs, labels=labels, - autopct='%.0f%%', - textprops={'size': 'smaller'}, - shadow=False, radius=0.5, - explode=(0, 0.05, 0, 0)) -plt.setp(autotexts, size='x-small') -autotexts[0].set_color('white') - -plt.show() - -# %% -# -# .. admonition:: References -# -# The use of the following functions, methods, classes and modules is shown -# in this example: -# -# - `matplotlib.axes.Axes.pie` / `matplotlib.pyplot.pie` diff --git a/galleries/examples/pie_and_polar_charts/pie_features.py b/galleries/examples/pie_and_polar_charts/pie_features.py index cb6ab51c0eac..7794a3d22a7e 100644 --- a/galleries/examples/pie_and_polar_charts/pie_features.py +++ b/galleries/examples/pie_and_polar_charts/pie_features.py @@ -1,4 +1,6 @@ """ +.. redirect-from:: gallery/pie_and_polar_charts/pie_demo2 + ========== Pie charts ========== @@ -88,7 +90,7 @@ fig, ax = plt.subplots() ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', - shadow=True, startangle=90) + shadow=True, startangle=90) plt.show() # %% @@ -97,6 +99,31 @@ # slices are rotated counter-clockwise by 90 degrees, and the frog slice starts # on the positive y-axis. # +# Controlling the size +# -------------------- +# +# By changing the *radius* parameter, and often the text size for better visual +# appearance, the pie chart can be scaled. + +fig, ax = plt.subplots() + +ax.pie(sizes, labels=labels, autopct='%.0f%%', + textprops={'size': 'smaller'}, radius=0.5) +plt.show() + +# %% +# Modifying the shadow +# -------------------- +# +# The *shadow* parameter may optionally take a dictionary with arguments to +# the `.Shadow` patch. This can be used to modify the default shadow. + +fig, ax = plt.subplots() +ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', + shadow={'ox': -0.04, 'edgecolor': 'none', 'shade': 0.9}, startangle=90) +plt.show() + +# %% # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 99a8a9f6fc51..e59b31a6055d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3102,8 +3102,12 @@ def pie(self, x, explode=None, labels=None, colors=None, 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. + shadow : bool or dict, default: False + If bool, whether to draw a shadow beneath the pie. If dict, draw a shadow + passing the properties in the dict to `.Shadow`. + + .. versionadded:: 3.8 + *shadow* can be a dict. startangle : float, default: 0 degrees The angle by which the start of the pie is rotated, @@ -3231,8 +3235,10 @@ def get_next_color(): if shadow: # Make sure to add a shadow after the call to add_patch so the # figure and transform props will be set. - shad = mpatches.Shadow(w, -0.02, -0.02, label='_nolegend_') - self.add_patch(shad) + 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)) if labeldistance is not None: xt = x + labeldistance * radius * math.cos(thetam) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 3b370521ed17..bd18f0a79e0e 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -613,12 +613,12 @@ def __str__(self): return f"Shadow({self.patch})" @_docstring.dedent_interpd - def __init__(self, patch, ox, oy, **kwargs): + def __init__(self, patch, ox, oy, *, shade=0.7, **kwargs): """ Create a shadow of the given *patch*. By default, the shadow will have the same face color as the *patch*, - but darkened. + but darkened. The darkness can be controlled by *shade*. Parameters ---------- @@ -627,6 +627,12 @@ def __init__(self, patch, ox, oy, **kwargs): ox, oy : float The shift of the shadow in data coordinates, scaled by a factor of dpi/72. + shade : float, default: 0.7 + How the darkness of the shadow relates to the original color. If 1, the + shadow is black, if 0, the shadow has the same color as the *patch*. + + .. versionadded:: 3.8 + **kwargs Properties of the shadow patch. Supported keys are: @@ -638,7 +644,9 @@ def __init__(self, patch, ox, oy, **kwargs): self._shadow_transform = transforms.Affine2D() self.update_from(self.patch) - color = .3 * np.asarray(colors.to_rgb(self.patch.get_facecolor())) + if not 0 <= shade <= 1: + raise ValueError("shade must be between 0 and 1.") + color = (1 - shade) * np.asarray(colors.to_rgb(self.patch.get_facecolor())) self.update({'facecolor': color, 'edgecolor': color, 'alpha': 0.5, # Place shadow patch directly behind the inherited patch. 'zorder': np.nextafter(self.patch.zorder, -np.inf), diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_shadow.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_shadow.png new file mode 100644 index 000000000000..fc8076486661 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/pie_shadow.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index b68ebcf57ba7..f1b7d7281c45 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5697,6 +5697,30 @@ def test_pie_nolabel_but_legend(): plt.legend() +@image_comparison(['pie_shadow.png'], style='mpl20') +def test_pie_shadow(): + # Also acts as a test for the shade argument of Shadow + sizes = [15, 30, 45, 10] + colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] + explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice + _, axes = plt.subplots(2, 2) + axes[0][0].pie(sizes, explode=explode, colors=colors, + shadow=True, startangle=90, + wedgeprops={'linewidth': 0}) + + axes[0][1].pie(sizes, explode=explode, colors=colors, + shadow=False, startangle=90, + wedgeprops={'linewidth': 0}) + + axes[1][0].pie(sizes, explode=explode, colors=colors, + shadow={'ox': -0.05, 'oy': -0.05, 'shade': 0.9, 'edgecolor': 'none'}, + startangle=90, wedgeprops={'linewidth': 0}) + + axes[1][1].pie(sizes, explode=explode, colors=colors, + shadow={'ox': 0.05, 'linewidth': 2, 'shade': 0.2}, + startangle=90, wedgeprops={'linewidth': 0}) + + def test_pie_textprops(): data = [23, 34, 45] labels = ["Long name 1", "Long name 2", "Long name 3"] diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 74d054af6b05..2f488e44ecc7 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -196,8 +196,10 @@ def test_alpha_rcparam(): leg.legendPatch.set_facecolor([1, 0, 0, 0.5]) -@image_comparison(['fancy'], remove_text=True) +@image_comparison(['fancy'], remove_text=True, tol=0.05) def test_fancy(): + # Tolerance caused by changing default shadow "shade" from 0.3 to 1 - 0.7 = + # 0.30000000000000004 # using subplot triggers some offsetbox functionality untested elsewhere plt.subplot(121) plt.plot([5] * 10, 'o--', label='XX')