🌐 AI搜索 & 代理 主页
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'])
Expand Down
31 changes: 31 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the color controlled by if statement, rather than fixed, given that this is a test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when debugging this I ran local code snippets until it worked. Then , I turned them into a test, which failed because our tests still use the classic style.

I figured it is reasonable to make the test robust to work with any style even though we are only testing one. It may be that we switch to using our default theme for testing at some point in the future. And it would be a nuisance if that test then falls over and someone has to figure out why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is reasonable to make the test robust to work with any style even though we are only testing one

I think that's what paramaterization is for and that tests should always have the things being tested fixed, but since this is release critical you're welcome to merge.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically do not test multiple styles. And also here, the tested logic - i.e. the alpha handling - is not style dependent. It's only that the test boundary conditions - i.e. the default facecolor - are style dependent. You could work around this by doing a first plot without alpha and infer the facecolor from that, but I figure it's simpler to just hard-code the two cases, even if they are not both exercised in the tests.

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
Expand Down
Loading