From c1190e65366cc26f0928bebf46219550f457f254 Mon Sep 17 00:00:00 2001 From: Andrew Landau Date: Fri, 10 Nov 2023 09:13:34 +0000 Subject: [PATCH 01/12] Add color specification and pyi docs Fix formatting issues Fix argument order in stub Update pyplot.py Show new features Added versionadded line to new arguments Add test for violinplot color arguments Add file to "whats new" Formatting issues fixed More formatting fixes... Linting Fix violinplot test Transitioning to facecolor and edgecolor Fixed argument name Update doc/users/next_whats_new/violinplot_colors.rst Co-authored-by: Oscar Gustafsson Update galleries/examples/statistics/customized_violin.py Co-authored-by: Oscar Gustafsson Update test_axes.py Consolidate versionadded directive Enable vectorized color control Fix test - ignore xaxis rendering Correct x positions in test Correct positions in test Intelligent alpha handling Cycler bug fix Added has_alpha for classic_mode No special alpha control Removed colors, added alpha parameter Add alpha control to default color sequence correct arguments in type hinting update docs to newest argument grouping remove obsolete explanation Correct testing with new color arguments --- .../next_whats_new/violinplot_colors.rst | 12 +++ .../examples/statistics/customized_violin.py | 28 +++-- lib/matplotlib/axes/_axes.py | 95 +++++++++++++--- lib/matplotlib/axes/_axes.pyi | 6 ++ lib/matplotlib/pyplot.py | 6 ++ lib/matplotlib/tests/test_axes.py | 102 ++++++++++++++++++ 6 files changed, 221 insertions(+), 28 deletions(-) create mode 100644 doc/users/next_whats_new/violinplot_colors.rst diff --git a/doc/users/next_whats_new/violinplot_colors.rst b/doc/users/next_whats_new/violinplot_colors.rst new file mode 100644 index 000000000000..342f945c560e --- /dev/null +++ b/doc/users/next_whats_new/violinplot_colors.rst @@ -0,0 +1,12 @@ +``violinplot`` now accepts color arguments +------------------------------------------- + +The ``~.Axes.violinplot`` constructor now accepts ``facecolor``, ``edgecolor`` +and ``alpha`` as input arguments. This means that users can set the color of +violinplots as they make them, rather than setting the color of individual +objects afterwards. It is possible to pass a single color to be used for all +violins, or pass a sequence of colors. + +The ``alpha`` argument is used to set the transparency of the violins. By +default, ``alpha`` is set to 0.3. However, if ``alpha`` is set to ``None``, +the violin alpha(s) will be set to the alpha value of the facecolor(s). diff --git a/galleries/examples/statistics/customized_violin.py b/galleries/examples/statistics/customized_violin.py index 29ddcda92fbe..09292f250af7 100644 --- a/galleries/examples/statistics/customized_violin.py +++ b/galleries/examples/statistics/customized_violin.py @@ -36,20 +36,26 @@ def set_axis_style(ax, labels): np.random.seed(19680801) data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] -fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(9, 4), sharey=True) +fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(9, 3), sharey=True) ax1.set_title('Default violin plot') ax1.set_ylabel('Observed values') ax1.violinplot(data) -ax2.set_title('Customized violin plot') -parts = ax2.violinplot( - data, showmeans=False, showmedians=False, - showextrema=False) +ax2.set_title('Set colors of violins') +ax2.set_ylabel('Observed values') +ax2.violinplot(data, facecolor=['y', 'b', 'r', 'g'], edgecolor='k') +# Note that when passing a sequence of colors, the method will repeat the sequence if +# less colors are provided than data distributions. + +ax3.set_title('Customized violin plot') +parts = ax3.violinplot( + data, showmeans=False, showmedians=False, showextrema=False, + facecolor='#D43F3A', edgecolor='k') for pc in parts['bodies']: - pc.set_facecolor('#D43F3A') - pc.set_edgecolor('black') + pc.set_edgecolor('k') + pc.set_linewidth(1) pc.set_alpha(1) quartile1, medians, quartile3 = np.percentile(data, [25, 50, 75], axis=1) @@ -59,13 +65,13 @@ def set_axis_style(ax, labels): whiskers_min, whiskers_max = whiskers[:, 0], whiskers[:, 1] inds = np.arange(1, len(medians) + 1) -ax2.scatter(inds, medians, marker='o', color='white', s=30, zorder=3) -ax2.vlines(inds, quartile1, quartile3, color='k', linestyle='-', lw=5) -ax2.vlines(inds, whiskers_min, whiskers_max, color='k', linestyle='-', lw=1) +ax3.scatter(inds, medians, marker='o', color='white', s=30, zorder=3) +ax3.vlines(inds, quartile1, quartile3, color='k', linestyle='-', lw=5) +ax3.vlines(inds, whiskers_min, whiskers_max, color='k', linestyle='-', lw=1) # set style for the axes labels = ['A', 'B', 'C', 'D'] -for ax in [ax1, ax2]: +for ax in [ax1, ax2, ax3]: set_axis_style(ax, labels) plt.subplots_adjust(bottom=0.15, wspace=0.05) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index bb0adae18e37..89cf9bbbba74 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8439,7 +8439,8 @@ def matshow(self, Z, **kwargs): def violinplot(self, dataset, positions=None, vert=None, orientation='vertical', widths=0.5, showmeans=False, showextrema=True, showmedians=False, quantiles=None, - points=100, bw_method=None, side='both',): + points=100, bw_method=None, side='both', + facecolor=None, edgecolor=None, alpha=0.3): """ Make a violin plot. @@ -8506,6 +8507,22 @@ def violinplot(self, dataset, positions=None, vert=None, 'both' plots standard violins. 'low'/'high' only plots the side below/above the positions value. + facecolor : color or list of colors or None; see :ref:`colors_def` + If provided, will set the face color(s) of the violin plots. + + edgecolor : color or list of colors or None; see :ref:`colors_def` + If provided, will set the edge color(s) of the violin plots (the + horizontal and vertical spines). + + alpha : float, default: 0.3 + Sets the alpha value of the facecolor(s) of the violin plots. This + overwrites any alpha value provided in facecolor, so if you want to + set the alpha via the facecolor argument, set alpha to None. + + .. versionadded:: 3.11 + + facecolor, edgecolor, alpha + data : indexable object, optional DATA_PARAMETER_PLACEHOLDER @@ -8558,12 +8575,14 @@ def _kde_method(X, coords): return self.violin(vpstats, positions=positions, vert=vert, orientation=orientation, widths=widths, showmeans=showmeans, showextrema=showextrema, - showmedians=showmedians, side=side) + showmedians=showmedians, side=side, + facecolor=facecolor, edgecolor=edgecolor, alpha=alpha) @_api.make_keyword_only("3.9", "vert") def violin(self, vpstats, positions=None, vert=None, orientation='vertical', widths=0.5, showmeans=False, - showextrema=True, showmedians=False, side='both'): + showextrema=True, showmedians=False, side='both', + facecolor=None, edgecolor=None, alpha=0.3): """ Draw a violin plot from pre-computed statistics. @@ -8635,6 +8654,22 @@ def violin(self, vpstats, positions=None, vert=None, 'both' plots standard violins. 'low'/'high' only plots the side below/above the positions value. + facecolor : color or list of colors or None; see :ref:`colors_def` + If provided, will set the face color(s) of the violin plots. + + edgecolor : color or list of colors or None; see :ref:`colors_def` + If provided, will set the edge color(s) of the violin plots (the + horizontal and vertical spines). + + alpha : float, default: 0.3 + Sets the alpha value of the facecolor(s) of the violin plots. This + overwrites any alpha value provided in facecolor, so if you want to + set the alpha via the facecolor argument, set alpha to None. + + .. versionadded:: 3.11 + + facecolor, edgecolor, alpha + Returns ------- dict @@ -8717,45 +8752,71 @@ def violin(self, vpstats, positions=None, vert=None, [0.25 if side in ['both', 'high'] else 0]] \ * np.array(widths) + positions - # Colors. + # Make a cycle of color to iterate through, using 'none' as fallback + def cycle_color(color, alpha=None): + rgba = mcolors.to_rgba_array(color, alpha=alpha) + color_cycler = itertools.chain(itertools.cycle(rgba), + itertools.repeat('none')) + color_list = [] + for _ in range(N): + color_list.append(next(color_cycler)) + return color_list + + # Set default colors for when user doesn't provide them if mpl.rcParams['_internal.classic_mode']: - fillcolor = 'y' - linecolor = 'r' + default_facecolor = cycle_color('y') + default_edgecolor = cycle_color('r') else: - fillcolor = linecolor = self._get_lines.get_next_color() + next_color = self._get_lines.get_next_color() + default_facecolor = cycle_color(next_color, alpha=alpha) + default_edgecolor = cycle_color(next_color) + + # Convert colors to chain (number of colors can be different from len(vpstats)) + if facecolor is not None: + facecolor = cycle_color(facecolor, alpha=alpha) + + if edgecolor is not None: + edgecolor = cycle_color(edgecolor) + + # Set color of violin plots + if facecolor is None: + facecolor = default_facecolor + if edgecolor is None: + edgecolor = default_edgecolor # Check whether we are rendering vertically or horizontally if orientation == 'vertical': fill = self.fill_betweenx if side in ['low', 'high']: - perp_lines = functools.partial(self.hlines, colors=linecolor, + perp_lines = functools.partial(self.hlines, colors=edgecolor, capstyle='projecting') - par_lines = functools.partial(self.vlines, colors=linecolor, + par_lines = functools.partial(self.vlines, colors=edgecolor, capstyle='projecting') else: - perp_lines = functools.partial(self.hlines, colors=linecolor) - par_lines = functools.partial(self.vlines, colors=linecolor) + perp_lines = functools.partial(self.hlines, colors=edgecolor) + par_lines = functools.partial(self.vlines, colors=edgecolor) else: fill = self.fill_between if side in ['low', 'high']: - perp_lines = functools.partial(self.vlines, colors=linecolor, + perp_lines = functools.partial(self.vlines, colors=edgecolor, capstyle='projecting') - par_lines = functools.partial(self.hlines, colors=linecolor, + par_lines = functools.partial(self.hlines, colors=edgecolor, capstyle='projecting') else: - perp_lines = functools.partial(self.vlines, colors=linecolor) - par_lines = functools.partial(self.hlines, colors=linecolor) + perp_lines = functools.partial(self.vlines, colors=edgecolor) + par_lines = functools.partial(self.hlines, colors=edgecolor) # Render violins bodies = [] - for stats, pos, width in zip(vpstats, positions, widths): + bodies_zip = zip(vpstats, positions, widths, facecolor) + for stats, pos, width, facecol in bodies_zip: # The 0.5 factor reflects the fact that we plot from v-p to v+p. vals = np.array(stats['vals']) vals = 0.5 * width * vals / vals.max() bodies += [fill(stats['coords'], -vals + pos if side in ['both', 'low'] else pos, vals + pos if side in ['both', 'high'] else pos, - facecolor=fillcolor, alpha=0.3)] + facecolor=facecol, alpha=0.3)] means.append(stats['mean']) mins.append(stats['min']) maxes.append(stats['max']) diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 1877cc192b15..60bafa3eb113 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -755,6 +755,9 @@ class Axes(_AxesBase): | Callable[[GaussianKDE], float] | None = ..., side: Literal["both", "low", "high"] = ..., + facecolor: Sequence[ColorType] | ColorType | None = ..., + edgecolor: Sequence[ColorType] | ColorType | None = ..., + alpha: float | None = ..., data=..., ) -> dict[str, Collection]: ... def violin( @@ -769,6 +772,9 @@ class Axes(_AxesBase): showextrema: bool = ..., showmedians: bool = ..., side: Literal["both", "low", "high"] = ..., + facecolor: Sequence[ColorType] | ColorType | None = ..., + edgecolor: Sequence[ColorType] | ColorType | None = ..., + alpha: float | None = ..., ) -> dict[str, Collection]: ... table = mtable.table diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 2dd14404c98e..5afeb22f01f1 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -4302,6 +4302,9 @@ def violinplot( | Callable[[GaussianKDE], float] | None = None, side: Literal["both", "low", "high"] = "both", + facecolor: Sequence[ColorType] | ColorType | None = None, + edgecolor: Sequence[ColorType] | ColorType | None = None, + alpha: float | None = 0.3, *, data=None, ) -> dict[str, Collection]: @@ -4318,6 +4321,9 @@ def violinplot( points=points, bw_method=bw_method, side=side, + facecolor=facecolor, + edgecolor=edgecolor, + alpha=alpha, **({"data": data} if data is not None else {}), ) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 37f799e522a4..0c9f60f0f460 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4021,6 +4021,108 @@ def test_violinplot_outofrange_quantiles(): ax.violinplot(data, quantiles=[[-0.05, 0.2, 0.3, 0.75]]) +@check_figures_equal(extensions=["png"]) +def test_violinplot_color_specification(fig_test, fig_ref): + # Ensures that setting colors in violinplot constructor works + # the same way as setting the color of each object manually + np.random.seed(19680801) + data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] + kwargs = {'showmeans': True, + 'showextrema': True, + 'showmedians': True + } + + # Test image + ax = fig_test.subplots(1, 5) + parts0 = ax[0].violinplot(data, **kwargs) + for pc in parts0['bodies']: + pc.set_facecolor(('r', 0.5)) + for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): + if partname in parts0: + pc = parts0[partname] + pc.set_edgecolor('r') + + parts1 = ax[1].violinplot(data, **kwargs) + for pc in parts1['bodies']: + pc.set_facecolor(('r', 0.3)) + + parts2 = ax[2].violinplot(data, **kwargs) + for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): + if partname in parts2: + pc = parts2[partname] + pc.set_edgecolor('r') + + parts3 = ax[3].violinplot(data, **kwargs) + for pc in parts3['bodies']: + pc.set_facecolor(('g', 0.3)) + for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): + if partname in parts3: + pc = parts3[partname] + pc.set_edgecolor('r') + + parts4 = ax[4].violinplot(data, **kwargs) + for pc in parts4['bodies']: + pc.set_facecolor(('k', 0.5)) + + # Reference image + ax = fig_ref.subplots(1, 5) + ax[0].violinplot(data, facecolor='r', edgecolor='r', alpha=0.5, **kwargs) + ax[1].violinplot(data, facecolor='r', **kwargs) + ax[2].violinplot(data, edgecolor='r', **kwargs) + ax[3].violinplot(data, facecolor='g', edgecolor='r', **kwargs) + ax[4].violinplot(data, facecolor=('k', 0.5), alpha=None, **kwargs) + + +@check_figures_equal(extensions=["png"]) +def test_violinplot_color_sequence(fig_test, fig_ref): + # Ensures that setting a sequence of colors works the same as setting + # each color independently + np.random.seed(19680801) + data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] + kwargs = {'showmeans': True, + 'showextrema': True, + 'showmedians': True + } + + # Color sequence + N = len(data) + positions = range(N) + colors = ['k', 'r', 'b', 'g', 'm'] + + # Test image + ax = fig_test.gca() + ax.violinplot(data, positions=positions, facecolor=colors, + edgecolor=colors, **kwargs) + # Get all x/y axis features + xlim = ax.get_xlim() + ylim = ax.get_ylim() + xticks = ax.get_xticks() + yticks = ax.get_yticks() + xticklabels = ax.get_xticklabels() + yticklabels = ax.get_yticklabels() + # Ensure all x/y axis features are identical (not what this is designed to test) + ax.set_xlim(xlim) + ax.set_ylim(ylim) + ax.set_xticks(xticks) + ax.set_yticks(yticks) + ax.set_xticklabels(xticklabels) + ax.set_yticklabels(yticklabels) + + # Reference image + ax = fig_ref.gca() + for (p, c, d) in zip(positions, colors, data): + ax.violinplot(d, positions=[p], facecolor=c, edgecolor=c, **kwargs) + # Ensure all x/y axis features are identical (not what this is designed to test) + ax.set_xlim(xlim) + ax.set_ylim(ylim) + ax.set_xticks(xticks) + ax.set_yticks(yticks) + ax.set_xticklabels(xticklabels) + ax.set_yticklabels(yticklabels) + + return fig_test, fig_ref + + @check_figures_equal(extensions=["png"]) def test_violinplot_single_list_quantiles(fig_test, fig_ref): # Ensures quantile list for 1D can be passed in as single list From 4c6def17870d6c760be49c170aea9254a1adf2be Mon Sep 17 00:00:00 2001 From: landoskape Date: Mon, 27 Jan 2025 20:56:22 +0000 Subject: [PATCH 02/12] edgecolor to linecolor, no more alpha --- .../next_whats_new/violinplot_colors.rst | 8 +-- .../examples/statistics/customized_violin.py | 8 ++- lib/matplotlib/axes/_axes.py | 67 ++++++++----------- lib/matplotlib/axes/_axes.pyi | 6 +- lib/matplotlib/pyplot.py | 6 +- lib/matplotlib/tests/test_axes.py | 18 ++--- 6 files changed, 50 insertions(+), 63 deletions(-) diff --git a/doc/users/next_whats_new/violinplot_colors.rst b/doc/users/next_whats_new/violinplot_colors.rst index 342f945c560e..0072a096e442 100644 --- a/doc/users/next_whats_new/violinplot_colors.rst +++ b/doc/users/next_whats_new/violinplot_colors.rst @@ -1,12 +1,8 @@ ``violinplot`` now accepts color arguments ------------------------------------------- -The ``~.Axes.violinplot`` constructor now accepts ``facecolor``, ``edgecolor`` -and ``alpha`` as input arguments. This means that users can set the color of +The ``~.Axes.violinplot`` constructor now accepts ``facecolor`` and +``linecolor`` as input arguments. This means that users can set the color of violinplots as they make them, rather than setting the color of individual objects afterwards. It is possible to pass a single color to be used for all violins, or pass a sequence of colors. - -The ``alpha`` argument is used to set the transparency of the violins. By -default, ``alpha`` is set to 0.3. However, if ``alpha`` is set to ``None``, -the violin alpha(s) will be set to the alpha value of the facecolor(s). diff --git a/galleries/examples/statistics/customized_violin.py b/galleries/examples/statistics/customized_violin.py index 09292f250af7..353f669f12a7 100644 --- a/galleries/examples/statistics/customized_violin.py +++ b/galleries/examples/statistics/customized_violin.py @@ -44,14 +44,18 @@ def set_axis_style(ax, labels): ax2.set_title('Set colors of violins') ax2.set_ylabel('Observed values') -ax2.violinplot(data, facecolor=['y', 'b', 'r', 'g'], edgecolor='k') +ax2.violinplot( + data, + facecolor=[('y', 0.3), ('b', 0.3), ('r', 0.3), ('g', 0.3)], + linecolor='k', +) # Note that when passing a sequence of colors, the method will repeat the sequence if # less colors are provided than data distributions. ax3.set_title('Customized violin plot') parts = ax3.violinplot( data, showmeans=False, showmedians=False, showextrema=False, - facecolor='#D43F3A', edgecolor='k') + facecolor='#D43F3A', lineolor='k') for pc in parts['bodies']: pc.set_edgecolor('k') diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 89cf9bbbba74..f79a3fd5f7f4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8440,7 +8440,7 @@ def violinplot(self, dataset, positions=None, vert=None, orientation='vertical', widths=0.5, showmeans=False, showextrema=True, showmedians=False, quantiles=None, points=100, bw_method=None, side='both', - facecolor=None, edgecolor=None, alpha=0.3): + facecolor=None, linecolor=None): """ Make a violin plot. @@ -8510,18 +8510,13 @@ def violinplot(self, dataset, positions=None, vert=None, facecolor : color or list of colors or None; see :ref:`colors_def` If provided, will set the face color(s) of the violin plots. - edgecolor : color or list of colors or None; see :ref:`colors_def` - If provided, will set the edge color(s) of the violin plots (the - horizontal and vertical spines). - - alpha : float, default: 0.3 - Sets the alpha value of the facecolor(s) of the violin plots. This - overwrites any alpha value provided in facecolor, so if you want to - set the alpha via the facecolor argument, set alpha to None. + linecolor : color or list of colors or None; see :ref:`colors_def` + If provided, will set the line color(s) of the violin plots (the + horizontal and vertical spines and body edges). .. versionadded:: 3.11 - facecolor, edgecolor, alpha + facecolor, linecolor data : indexable object, optional DATA_PARAMETER_PLACEHOLDER @@ -8576,13 +8571,13 @@ def _kde_method(X, coords): orientation=orientation, widths=widths, showmeans=showmeans, showextrema=showextrema, showmedians=showmedians, side=side, - facecolor=facecolor, edgecolor=edgecolor, alpha=alpha) + facecolor=facecolor, linecolor=linecolor) @_api.make_keyword_only("3.9", "vert") def violin(self, vpstats, positions=None, vert=None, orientation='vertical', widths=0.5, showmeans=False, showextrema=True, showmedians=False, side='both', - facecolor=None, edgecolor=None, alpha=0.3): + facecolor=None, linecolor=None): """ Draw a violin plot from pre-computed statistics. @@ -8657,18 +8652,13 @@ def violin(self, vpstats, positions=None, vert=None, facecolor : color or list of colors or None; see :ref:`colors_def` If provided, will set the face color(s) of the violin plots. - edgecolor : color or list of colors or None; see :ref:`colors_def` - If provided, will set the edge color(s) of the violin plots (the - horizontal and vertical spines). - - alpha : float, default: 0.3 - Sets the alpha value of the facecolor(s) of the violin plots. This - overwrites any alpha value provided in facecolor, so if you want to - set the alpha via the facecolor argument, set alpha to None. + linecolor : color or list of colors or None; see :ref:`colors_def` + If provided, will set the line color(s) of the violin plots (the + horizontal and vertical spines and body edges). .. versionadded:: 3.11 - facecolor, edgecolor, alpha + facecolor, linecolor Returns ------- @@ -8763,48 +8753,49 @@ def cycle_color(color, alpha=None): return color_list # Set default colors for when user doesn't provide them + default_facealpha = 0.3 if mpl.rcParams['_internal.classic_mode']: - default_facecolor = cycle_color('y') - default_edgecolor = cycle_color('r') + default_facecolor = cycle_color('y', alpha=default_facealpha) + default_linecolor = cycle_color('r') else: next_color = self._get_lines.get_next_color() - default_facecolor = cycle_color(next_color, alpha=alpha) - default_edgecolor = cycle_color(next_color) + default_facecolor = cycle_color(next_color, alpha=default_facealpha) + default_linecolor = cycle_color(next_color) # Convert colors to chain (number of colors can be different from len(vpstats)) if facecolor is not None: - facecolor = cycle_color(facecolor, alpha=alpha) + facecolor = cycle_color(facecolor) - if edgecolor is not None: - edgecolor = cycle_color(edgecolor) + if linecolor is not None: + linecolor = cycle_color(linecolor) # Set color of violin plots if facecolor is None: facecolor = default_facecolor - if edgecolor is None: - edgecolor = default_edgecolor + if linecolor is None: + linecolor = default_linecolor # Check whether we are rendering vertically or horizontally if orientation == 'vertical': fill = self.fill_betweenx if side in ['low', 'high']: - perp_lines = functools.partial(self.hlines, colors=edgecolor, + perp_lines = functools.partial(self.hlines, colors=linecolor, capstyle='projecting') - par_lines = functools.partial(self.vlines, colors=edgecolor, + par_lines = functools.partial(self.vlines, colors=linecolor, capstyle='projecting') else: - perp_lines = functools.partial(self.hlines, colors=edgecolor) - par_lines = functools.partial(self.vlines, colors=edgecolor) + perp_lines = functools.partial(self.hlines, colors=linecolor) + par_lines = functools.partial(self.vlines, colors=linecolor) else: fill = self.fill_between if side in ['low', 'high']: - perp_lines = functools.partial(self.vlines, colors=edgecolor, + perp_lines = functools.partial(self.vlines, colors=linecolor, capstyle='projecting') - par_lines = functools.partial(self.hlines, colors=edgecolor, + par_lines = functools.partial(self.hlines, colors=linecolor, capstyle='projecting') else: - perp_lines = functools.partial(self.vlines, colors=edgecolor) - par_lines = functools.partial(self.hlines, colors=edgecolor) + perp_lines = functools.partial(self.vlines, colors=linecolor) + par_lines = functools.partial(self.hlines, colors=linecolor) # Render violins bodies = [] diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 60bafa3eb113..c780fe012fa0 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -756,8 +756,7 @@ class Axes(_AxesBase): | None = ..., side: Literal["both", "low", "high"] = ..., facecolor: Sequence[ColorType] | ColorType | None = ..., - edgecolor: Sequence[ColorType] | ColorType | None = ..., - alpha: float | None = ..., + linecolor: Sequence[ColorType] | ColorType | None = ..., data=..., ) -> dict[str, Collection]: ... def violin( @@ -773,8 +772,7 @@ class Axes(_AxesBase): showmedians: bool = ..., side: Literal["both", "low", "high"] = ..., facecolor: Sequence[ColorType] | ColorType | None = ..., - edgecolor: Sequence[ColorType] | ColorType | None = ..., - alpha: float | None = ..., + linecolor: Sequence[ColorType] | ColorType | None = ..., ) -> dict[str, Collection]: ... table = mtable.table diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 5afeb22f01f1..ffbef75aa86d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -4303,8 +4303,7 @@ def violinplot( | None = None, side: Literal["both", "low", "high"] = "both", facecolor: Sequence[ColorType] | ColorType | None = None, - edgecolor: Sequence[ColorType] | ColorType | None = None, - alpha: float | None = 0.3, + linecolor: Sequence[ColorType] | ColorType | None = None, *, data=None, ) -> dict[str, Collection]: @@ -4322,8 +4321,7 @@ def violinplot( bw_method=bw_method, side=side, facecolor=facecolor, - edgecolor=edgecolor, - alpha=alpha, + linecolor=linecolor, **({"data": data} if data is not None else {}), ) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 0c9f60f0f460..820c91dd7ed2 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4044,7 +4044,7 @@ def test_violinplot_color_specification(fig_test, fig_ref): parts1 = ax[1].violinplot(data, **kwargs) for pc in parts1['bodies']: - pc.set_facecolor(('r', 0.3)) + pc.set_facecolor('r') parts2 = ax[2].violinplot(data, **kwargs) for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): @@ -4054,7 +4054,7 @@ def test_violinplot_color_specification(fig_test, fig_ref): parts3 = ax[3].violinplot(data, **kwargs) for pc in parts3['bodies']: - pc.set_facecolor(('g', 0.3)) + pc.set_facecolor('g') for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): if partname in parts3: pc = parts3[partname] @@ -4066,11 +4066,11 @@ def test_violinplot_color_specification(fig_test, fig_ref): # Reference image ax = fig_ref.subplots(1, 5) - ax[0].violinplot(data, facecolor='r', edgecolor='r', alpha=0.5, **kwargs) + ax[0].violinplot(data, facecolor=('r', 0.5), linecolor='r', **kwargs) ax[1].violinplot(data, facecolor='r', **kwargs) - ax[2].violinplot(data, edgecolor='r', **kwargs) - ax[3].violinplot(data, facecolor='g', edgecolor='r', **kwargs) - ax[4].violinplot(data, facecolor=('k', 0.5), alpha=None, **kwargs) + ax[2].violinplot(data, linecolor='r', **kwargs) + ax[3].violinplot(data, facecolor='g', linecolor='r', **kwargs) + ax[4].violinplot(data, facecolor=('k', 0.5), **kwargs) @check_figures_equal(extensions=["png"]) @@ -4087,12 +4087,12 @@ def test_violinplot_color_sequence(fig_test, fig_ref): # Color sequence N = len(data) positions = range(N) - colors = ['k', 'r', 'b', 'g', 'm'] + colors = ['k', 'r', ('b', 0.5), 'g', ('m', 0.2)] # Test image ax = fig_test.gca() ax.violinplot(data, positions=positions, facecolor=colors, - edgecolor=colors, **kwargs) + linecolor=colors, **kwargs) # Get all x/y axis features xlim = ax.get_xlim() ylim = ax.get_ylim() @@ -4111,7 +4111,7 @@ def test_violinplot_color_sequence(fig_test, fig_ref): # Reference image ax = fig_ref.gca() for (p, c, d) in zip(positions, colors, data): - ax.violinplot(d, positions=[p], facecolor=c, edgecolor=c, **kwargs) + ax.violinplot(d, positions=[p], facecolor=c, linecolor=c, **kwargs) # Ensure all x/y axis features are identical (not what this is designed to test) ax.set_xlim(xlim) ax.set_ylim(ylim) From 9ce5d121bc28720e3a3a3f3645314e1663a073c6 Mon Sep 17 00:00:00 2001 From: Andrew Landau Date: Tue, 28 Jan 2025 08:16:52 +0000 Subject: [PATCH 03/12] Arg documentation Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/axes/_axes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f79a3fd5f7f4..3af555338e2d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8507,17 +8507,17 @@ def violinplot(self, dataset, positions=None, vert=None, 'both' plots standard violins. 'low'/'high' only plots the side below/above the positions value. - facecolor : color or list of colors or None; see :ref:`colors_def` - If provided, will set the face color(s) of the violin plots. + facecolor : :mpltype`color` or list of :mpltype:`color`, optional + If provided, will set the face color(s) of the violins. - linecolor : color or list of colors or None; see :ref:`colors_def` - If provided, will set the line color(s) of the violin plots (the + .. versionadded:: 3.11 + + 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). .. versionadded:: 3.11 - facecolor, linecolor - data : indexable object, optional DATA_PARAMETER_PLACEHOLDER From 33d186b31fd6766c87ed2cc83f742e85e5b01032 Mon Sep 17 00:00:00 2001 From: Andrew Landau Date: Tue, 28 Jan 2025 08:17:57 +0000 Subject: [PATCH 04/12] completeness, formatting Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- doc/users/next_whats_new/violinplot_colors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/next_whats_new/violinplot_colors.rst b/doc/users/next_whats_new/violinplot_colors.rst index 0072a096e442..179f868c4288 100644 --- a/doc/users/next_whats_new/violinplot_colors.rst +++ b/doc/users/next_whats_new/violinplot_colors.rst @@ -1,7 +1,7 @@ ``violinplot`` now accepts color arguments ------------------------------------------- -The ``~.Axes.violinplot`` constructor now accepts ``facecolor`` and +`~.Axes.violinplot` and `~.Axes.violin` now accept ``facecolor`` and ``linecolor`` as input arguments. This means that users can set the color of violinplots as they make them, rather than setting the color of individual objects afterwards. It is possible to pass a single color to be used for all From 9e97d6fce55652c64e06038dcb04ada39db99464 Mon Sep 17 00:00:00 2001 From: landoskape Date: Tue, 28 Jan 2025 08:53:10 +0000 Subject: [PATCH 05/12] Default logic, docstring, straightforward testing --- lib/matplotlib/axes/_axes.py | 42 ++++++------ lib/matplotlib/tests/test_axes.py | 107 +++++++++++++----------------- 2 files changed, 65 insertions(+), 84 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3af555338e2d..08e64cd4d94b 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8649,17 +8649,17 @@ def violin(self, vpstats, positions=None, vert=None, 'both' plots standard violins. 'low'/'high' only plots the side below/above the positions value. - facecolor : color or list of colors or None; see :ref:`colors_def` - If provided, will set the face color(s) of the violin plots. + facecolor : :mpltype`color` or list of :mpltype:`color`, optional + If provided, will set the face color(s) of the violins. + + .. versionadded:: 3.11 - linecolor : color or list of colors or None; see :ref:`colors_def` - If provided, will set the line color(s) of the violin plots (the + 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). .. versionadded:: 3.11 - facecolor, linecolor - Returns ------- dict @@ -8752,28 +8752,26 @@ def cycle_color(color, alpha=None): color_list.append(next(color_cycler)) return color_list - # Set default colors for when user doesn't provide them - default_facealpha = 0.3 - if mpl.rcParams['_internal.classic_mode']: - default_facecolor = cycle_color('y', alpha=default_facealpha) - default_linecolor = cycle_color('r') - else: - next_color = self._get_lines.get_next_color() - default_facecolor = cycle_color(next_color, alpha=default_facealpha) - default_linecolor = cycle_color(next_color) - # Convert colors to chain (number of colors can be different from len(vpstats)) if facecolor is not None: facecolor = cycle_color(facecolor) + else: + default_facealpha = 0.3 + # Use default colors if user doesn't provide them + if mpl.rcParams['_internal.classic_mode']: + facecolor = cycle_color('y', alpha=default_facealpha) + else: + next_color = self._get_lines.get_next_color() + facecolor = cycle_color(next_color, alpha=default_facealpha) if linecolor is not None: linecolor = cycle_color(linecolor) - - # Set color of violin plots - if facecolor is None: - facecolor = default_facecolor - if linecolor is None: - linecolor = default_linecolor + else: + if mpl.rcParams['_internal.classic_mode']: + linecolor = cycle_color('r') + else: + next_color = self._get_lines.get_next_color() + linecolor = cycle_color(next_color) # Check whether we are rendering vertically or horizontally if orientation == 'vertical': diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 820c91dd7ed2..3ccc54906499 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4026,55 +4026,41 @@ def test_violinplot_color_specification(fig_test, fig_ref): # Ensures that setting colors in violinplot constructor works # the same way as setting the color of each object manually np.random.seed(19680801) - data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] + data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 4)] kwargs = {'showmeans': True, 'showextrema': True, 'showmedians': True } - # Test image - ax = fig_test.subplots(1, 5) - parts0 = ax[0].violinplot(data, **kwargs) - for pc in parts0['bodies']: - pc.set_facecolor(('r', 0.5)) - for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): - if partname in parts0: - pc = parts0[partname] - pc.set_edgecolor('r') + def color_violins(parts, facecolor=None, linecolor=None): + """Helper to color parts manually.""" + if facecolor is not None: + for pc in parts['bodies']: + pc.set_facecolor(facecolor) + if linecolor is not None: + for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): + if partname in parts: + lc = parts[partname] + lc.set_edgecolor(linecolor) + # Reference image + ax = fig_ref.subplots(1, 3) + parts0 = ax[0].violinplot(data, **kwargs) parts1 = ax[1].violinplot(data, **kwargs) - for pc in parts1['bodies']: - pc.set_facecolor('r') - parts2 = ax[2].violinplot(data, **kwargs) - for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): - if partname in parts2: - pc = parts2[partname] - pc.set_edgecolor('r') - - parts3 = ax[3].violinplot(data, **kwargs) - for pc in parts3['bodies']: - pc.set_facecolor('g') - for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): - if partname in parts3: - pc = parts3[partname] - pc.set_edgecolor('r') - - parts4 = ax[4].violinplot(data, **kwargs) - for pc in parts4['bodies']: - pc.set_facecolor(('k', 0.5)) - # Reference image - ax = fig_ref.subplots(1, 5) - ax[0].violinplot(data, facecolor=('r', 0.5), linecolor='r', **kwargs) + color_violins(parts0, facecolor=('r', 0.5), linecolor=('r', 0.2)) + color_violins(parts1, facecolor='r') + color_violins(parts2, linecolor='r') + + # Test image + ax = fig_test.subplots(1, 3) + ax[0].violinplot(data, facecolor=('r', 0.5), linecolor=('r', 0.2), **kwargs) ax[1].violinplot(data, facecolor='r', **kwargs) ax[2].violinplot(data, linecolor='r', **kwargs) - ax[3].violinplot(data, facecolor='g', linecolor='r', **kwargs) - ax[4].violinplot(data, facecolor=('k', 0.5), **kwargs) -@check_figures_equal(extensions=["png"]) -def test_violinplot_color_sequence(fig_test, fig_ref): +def test_violinplot_color_sequence(): # Ensures that setting a sequence of colors works the same as setting # each color independently np.random.seed(19680801) @@ -4084,43 +4070,40 @@ def test_violinplot_color_sequence(fig_test, fig_ref): 'showmedians': True } + def assert_colors_equal(colors1, colors2): + assert all(mcolors.same_color(c1, c2) for c1, c2 in zip(colors1, colors2)) + + parts_with_facecolor = ["bodies"] + parts_with_edgecolor = ["cbars", "cmins", "cmaxes", "cmeans", "cmedians"] + # Color sequence N = len(data) positions = range(N) colors = ['k', 'r', ('b', 0.5), 'g', ('m', 0.2)] # Test image + fig_test = plt.figure() ax = fig_test.gca() - ax.violinplot(data, positions=positions, facecolor=colors, - linecolor=colors, **kwargs) - # Get all x/y axis features - xlim = ax.get_xlim() - ylim = ax.get_ylim() - xticks = ax.get_xticks() - yticks = ax.get_yticks() - xticklabels = ax.get_xticklabels() - yticklabels = ax.get_yticklabels() - # Ensure all x/y axis features are identical (not what this is designed to test) - ax.set_xlim(xlim) - ax.set_ylim(ylim) - ax.set_xticks(xticks) - ax.set_yticks(yticks) - ax.set_xticklabels(xticklabels) - ax.set_yticklabels(yticklabels) + parts_test = ax.violinplot(data, positions=positions, facecolor=colors, + linecolor=colors, **kwargs) # Reference image + fig_ref = plt.figure() ax = fig_ref.gca() + parts_ref = [] for (p, c, d) in zip(positions, colors, data): - ax.violinplot(d, positions=[p], facecolor=c, linecolor=c, **kwargs) - # Ensure all x/y axis features are identical (not what this is designed to test) - ax.set_xlim(xlim) - ax.set_ylim(ylim) - ax.set_xticks(xticks) - ax.set_yticks(yticks) - ax.set_xticklabels(xticklabels) - ax.set_yticklabels(yticklabels) - - return fig_test, fig_ref + cparts = ax.violinplot(d, positions=[p], facecolor=c, linecolor=c, **kwargs) + parts_ref.append(cparts) + + for part in parts_with_facecolor: + colors_test = [p.get_facecolor() for p in parts_test[part]] + colors_ref = [p[part][0].get_facecolor() for p in parts_ref] + assert_colors_equal(colors_test, colors_ref) + + for part in parts_with_edgecolor: + colors_test = parts_test[part].get_edgecolor() + colors_ref = [p[part].get_edgecolor() for p in parts_ref] + assert_colors_equal(colors_test, colors_ref) @check_figures_equal(extensions=["png"]) From 2d4c7b776cc7d891ce9a7bcbfe189c80ab81e3b4 Mon Sep 17 00:00:00 2001 From: Andrew Landau Date: Tue, 28 Jan 2025 08:54:40 +0000 Subject: [PATCH 06/12] Spelling error Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- galleries/examples/statistics/customized_violin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/examples/statistics/customized_violin.py b/galleries/examples/statistics/customized_violin.py index 353f669f12a7..cea806996904 100644 --- a/galleries/examples/statistics/customized_violin.py +++ b/galleries/examples/statistics/customized_violin.py @@ -55,7 +55,7 @@ def set_axis_style(ax, labels): ax3.set_title('Customized violin plot') parts = ax3.violinplot( data, showmeans=False, showmedians=False, showextrema=False, - facecolor='#D43F3A', lineolor='k') + facecolor='#D43F3A', linecolor='k') for pc in parts['bodies']: pc.set_edgecolor('k') From 35f5c626641c52e5ef179f1d0166407dbc352d62 Mon Sep 17 00:00:00 2001 From: landoskape Date: Tue, 28 Jan 2025 09:10:43 +0000 Subject: [PATCH 07/12] single "next color" for default face/line --- lib/matplotlib/axes/_axes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 08e64cd4d94b..4a08672aeab0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8753,6 +8753,10 @@ def cycle_color(color, alpha=None): return color_list # Convert colors to chain (number of colors can be different from len(vpstats)) + if facecolor is None or linecolor is None: + if not mpl.rcParams['_internal.classic_mode']: + next_color = self._get_lines.get_next_color() + if facecolor is not None: facecolor = cycle_color(facecolor) else: @@ -8761,7 +8765,6 @@ def cycle_color(color, alpha=None): if mpl.rcParams['_internal.classic_mode']: facecolor = cycle_color('y', alpha=default_facealpha) else: - next_color = self._get_lines.get_next_color() facecolor = cycle_color(next_color, alpha=default_facealpha) if linecolor is not None: @@ -8770,7 +8773,6 @@ def cycle_color(color, alpha=None): if mpl.rcParams['_internal.classic_mode']: linecolor = cycle_color('r') else: - next_color = self._get_lines.get_next_color() linecolor = cycle_color(next_color) # Check whether we are rendering vertically or horizontally From a206de8992b9241c65376eb7010d092465da0dc6 Mon Sep 17 00:00:00 2001 From: landoskape Date: Wed, 29 Jan 2025 10:47:55 +0000 Subject: [PATCH 08/12] clarify color specification --- galleries/examples/statistics/customized_violin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/galleries/examples/statistics/customized_violin.py b/galleries/examples/statistics/customized_violin.py index cea806996904..cc18e47ebd67 100644 --- a/galleries/examples/statistics/customized_violin.py +++ b/galleries/examples/statistics/customized_violin.py @@ -46,8 +46,8 @@ def set_axis_style(ax, labels): ax2.set_ylabel('Observed values') ax2.violinplot( data, - facecolor=[('y', 0.3), ('b', 0.3), ('r', 0.3), ('g', 0.3)], - linecolor='k', + facecolor=[('yellow', 0.3), ('blue', 0.3), ('red', 0.3), ('green', 0.3)], + linecolor='black', ) # Note that when passing a sequence of colors, the method will repeat the sequence if # less colors are provided than data distributions. @@ -55,10 +55,10 @@ def set_axis_style(ax, labels): ax3.set_title('Customized violin plot') parts = ax3.violinplot( data, showmeans=False, showmedians=False, showextrema=False, - facecolor='#D43F3A', linecolor='k') + facecolor='#D43F3A', linecolor='black') for pc in parts['bodies']: - pc.set_edgecolor('k') + pc.set_edgecolor('black') pc.set_linewidth(1) pc.set_alpha(1) From 3998c6f7fae741e7a84835cc0f239649b235d39f Mon Sep 17 00:00:00 2001 From: landoskape Date: Wed, 29 Jan 2025 10:48:10 +0000 Subject: [PATCH 09/12] axes bug -- set with facecolor --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4a08672aeab0..22b973723b15 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8807,7 +8807,7 @@ 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=facecol, alpha=0.3)] + facecolor=facecol)] means.append(stats['mean']) mins.append(stats['min']) maxes.append(stats['max']) From f219c3f935d3b261c9011c65be031b881303a5f6 Mon Sep 17 00:00:00 2001 From: landoskape Date: Wed, 29 Jan 2025 10:50:39 +0000 Subject: [PATCH 10/12] direct comparison to source colors, face!=line --- lib/matplotlib/tests/test_axes.py | 32 ++++++++++++------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3ccc54906499..f83632d16872 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4065,13 +4065,11 @@ def test_violinplot_color_sequence(): # each color independently np.random.seed(19680801) data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] - kwargs = {'showmeans': True, - 'showextrema': True, - 'showmedians': True - } + kwargs = {'showmeans': True, 'showextrema': True, 'showmedians': True} def assert_colors_equal(colors1, colors2): - assert all(mcolors.same_color(c1, c2) for c1, c2 in zip(colors1, colors2)) + assert all(mcolors.same_color(c1, c2) + for c1, c2 in zip(colors1, colors2)) parts_with_facecolor = ["bodies"] parts_with_edgecolor = ["cbars", "cmins", "cmaxes", "cmeans", "cmedians"] @@ -4079,31 +4077,25 @@ def assert_colors_equal(colors1, colors2): # Color sequence N = len(data) positions = range(N) - colors = ['k', 'r', ('b', 0.5), 'g', ('m', 0.2)] + facecolors = ['k', 'r', ('b', 0.5), ('g', 0.2)] + linecolors = [('y', 0.4), 'b', 'm', ('k', 0.8)] # Test image fig_test = plt.figure() ax = fig_test.gca() - parts_test = ax.violinplot(data, positions=positions, facecolor=colors, - linecolor=colors, **kwargs) - - # Reference image - fig_ref = plt.figure() - ax = fig_ref.gca() - parts_ref = [] - for (p, c, d) in zip(positions, colors, data): - cparts = ax.violinplot(d, positions=[p], facecolor=c, linecolor=c, **kwargs) - parts_ref.append(cparts) + parts_test = ax.violinplot(data, + positions=positions, + facecolor=facecolors, + linecolor=linecolors, + **kwargs) for part in parts_with_facecolor: colors_test = [p.get_facecolor() for p in parts_test[part]] - colors_ref = [p[part][0].get_facecolor() for p in parts_ref] - assert_colors_equal(colors_test, colors_ref) + assert_colors_equal(colors_test, mcolors.to_rgba_array(facecolors)) for part in parts_with_edgecolor: colors_test = parts_test[part].get_edgecolor() - colors_ref = [p[part].get_edgecolor() for p in parts_ref] - assert_colors_equal(colors_test, colors_ref) + assert_colors_equal(colors_test, mcolors.to_rgba_array(linecolors)) @check_figures_equal(extensions=["png"]) From 60343f56251d025e7b3b1cf3ed07d70014fa23a6 Mon Sep 17 00:00:00 2001 From: landoskape Date: Thu, 30 Jan 2025 08:08:12 +0000 Subject: [PATCH 11/12] adjust behavior for classic mode --- lib/matplotlib/axes/_axes.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 22b973723b15..00fd266c4454 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8767,6 +8767,13 @@ def cycle_color(color, alpha=None): else: facecolor = cycle_color(next_color, alpha=default_facealpha) + if mpl.rcParams['_internal.classic_mode']: + # Classic mode uses patch.force_edgecolor=True, so we need to + # set the edgecolor to make sure it has an alpha. + body_edgecolor = ("k", 0.3) + else: + body_edgecolor = None + if linecolor is not None: linecolor = cycle_color(linecolor) else: @@ -8807,7 +8814,7 @@ 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=facecol)] + facecolor=facecol, edgecolor=body_edgecolor)] means.append(stats['mean']) mins.append(stats['min']) maxes.append(stats['max']) From e66e33a9b760fdf0ee0c48b33909383db2fdadbe Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:27:10 +0100 Subject: [PATCH 12/12] Apply suggestions from code review Minor formatting and code cleanup --- lib/matplotlib/axes/_axes.py | 12 ++++++------ lib/matplotlib/tests/test_axes.py | 10 +++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 00fd266c4454..16377baf8351 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -8507,12 +8507,12 @@ def violinplot(self, dataset, positions=None, vert=None, 'both' plots standard violins. 'low'/'high' only plots the side below/above the positions value. - facecolor : :mpltype`color` or list of :mpltype:`color`, optional + facecolor : :mpltype:`color` or list of :mpltype:`color`, optional If provided, will set the face color(s) of the violins. .. versionadded:: 3.11 - linecolor : :mpltype`color` or list of :mpltype:`color`, optional + 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). @@ -8649,12 +8649,12 @@ def violin(self, vpstats, positions=None, vert=None, 'both' plots standard violins. 'low'/'high' only plots the side below/above the positions value. - facecolor : :mpltype`color` or list of :mpltype:`color`, optional + facecolor : :mpltype:`color` or list of :mpltype:`color`, optional If provided, will set the face color(s) of the violins. .. versionadded:: 3.11 - linecolor : :mpltype`color` or list of :mpltype:`color`, optional + 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). @@ -8807,14 +8807,14 @@ def cycle_color(color, alpha=None): # Render violins bodies = [] bodies_zip = zip(vpstats, positions, widths, facecolor) - for stats, pos, width, facecol in bodies_zip: + for stats, pos, width, facecolor in bodies_zip: # The 0.5 factor reflects the fact that we plot from v-p to v+p. vals = np.array(stats['vals']) vals = 0.5 * width * vals / vals.max() bodies += [fill(stats['coords'], -vals + pos if side in ['both', 'low'] else pos, vals + pos if side in ['both', 'high'] else pos, - facecolor=facecol, edgecolor=body_edgecolor)] + facecolor=facecolor, edgecolor=body_edgecolor)] 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 f83632d16872..2fb7c273b5e2 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4071,9 +4071,6 @@ def assert_colors_equal(colors1, colors2): assert all(mcolors.same_color(c1, c2) for c1, c2 in zip(colors1, colors2)) - parts_with_facecolor = ["bodies"] - parts_with_edgecolor = ["cbars", "cmins", "cmaxes", "cmeans", "cmedians"] - # Color sequence N = len(data) positions = range(N) @@ -4089,11 +4086,10 @@ def assert_colors_equal(colors1, colors2): linecolor=linecolors, **kwargs) - for part in parts_with_facecolor: - colors_test = [p.get_facecolor() for p in parts_test[part]] - assert_colors_equal(colors_test, mcolors.to_rgba_array(facecolors)) + body_colors = [p.get_facecolor() for p in parts_test["bodies"]] + assert_colors_equal(body_colors, mcolors.to_rgba_array(facecolors)) - for part in parts_with_edgecolor: + for part in ["cbars", "cmins", "cmaxes", "cmeans", "cmedians"]: colors_test = parts_test[part].get_edgecolor() assert_colors_equal(colors_test, mcolors.to_rgba_array(linecolors))