diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 34b85464f841..edd68ddf198d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8967,6 +8967,14 @@ def violin(self, vpstats, positions=None, vert=None, .. versionadded:: 3.11 + For backward compatibility, if *facecolor* is not given, the body + will get an Artist-level transparency `alpha <.Artist.set_alpha>` + of 0.3, which will persist if you afterwards change the facecolor, + e.g. via ``result['bodies'][0].set_facecolor('red')``. + If *facecolor* is given, there is no Artist-level transparency. + To set transparency for *facecolor* or *edgecolor* use + ``(color, alpha)`` tuples. + linecolor : :mpltype:`color` or list of :mpltype:`color`, optional If provided, will set the line color(s) of the violins (the horizontal and vertical spines and body edges). @@ -9074,13 +9082,14 @@ def cycle_color(color, alpha=None): if facecolor is not None: facecolor = cycle_color(facecolor) + body_artist_alpha = None else: - default_facealpha = 0.3 + body_artist_alpha = 0.3 # Use default colors if user doesn't provide them if mpl.rcParams['_internal.classic_mode']: - facecolor = cycle_color('y', alpha=default_facealpha) + facecolor = cycle_color('y') else: - facecolor = cycle_color(next_color, alpha=default_facealpha) + facecolor = cycle_color(next_color) if mpl.rcParams['_internal.classic_mode']: # Classic mode uses patch.force_edgecolor=True, so we need to @@ -9129,7 +9138,8 @@ def cycle_color(color, alpha=None): bodies += [fill(stats['coords'], -vals + pos if side in ['both', 'low'] else pos, vals + pos if side in ['both', 'high'] else pos, - facecolor=facecolor, edgecolor=body_edgecolor)] + facecolor=facecolor, edgecolor=body_edgecolor, + alpha=body_artist_alpha)] means.append(stats['mean']) mins.append(stats['min']) maxes.append(stats['max']) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3bb374302eaa..5214ce51d13c 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4171,6 +4171,10 @@ def color_violins(parts, facecolor=None, linecolor=None): if facecolor is not None: for pc in parts['bodies']: pc.set_facecolor(facecolor) + # disable alpha Artist property to counter the legacy behavior + # that applies an alpha of 0.3 to the bodies if no facecolor + # was set + pc.set_alpha(None) if linecolor is not None: for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): if partname in parts: @@ -4228,6 +4232,33 @@ def assert_colors_equal(colors1, colors2): assert_colors_equal(colors_test, mcolors.to_rgba_array(linecolors)) +def test_violinplot_alpha(): + matplotlib.style.use('default') + data = [(np.random.normal(0, 1, 100))] + + fig, ax = plt.subplots() + parts = ax.violinplot(data, positions=[1]) + + # Case 1: If facecolor is unspecified, it's the first color from the color cycle + # with Artist-level alpha=0.3 + facecolor = ('y' if mpl.rcParams['_internal.classic_mode'] + else plt.rcParams['axes.prop_cycle'].by_key()['color'][0]) + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), (facecolor, 0.3)) + assert parts['bodies'][0].get_alpha() == 0.3 + # setting a new facecolor maintains the alpha + parts['bodies'][0].set_facecolor('red') + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), ('red', 0.3)) + + # Case 2: If facecolor is explicitly given, it's alpha does not become an + # Artist property + parts = ax.violinplot(data, positions=[1], facecolor=('blue', 0.3)) + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), ('blue', 0.3)) + assert parts['bodies'][0].get_alpha() is None + # so setting a new color does not maintain the alpha + parts['bodies'][0].set_facecolor('red') + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), 'red') + + @check_figures_equal() def test_violinplot_single_list_quantiles(fig_test, fig_ref): # Ensures quantile list for 1D can be passed in as single list