From 106caa1039b70caffd4e13400629baf6a02c4dcb Mon Sep 17 00:00:00 2001 From: saranti Date: Wed, 19 Jun 2024 13:32:59 +1000 Subject: [PATCH] add 'auto' state for boxplot patch_artist --- lib/matplotlib/axes/_axes.py | 24 +++++++++++++++--------- lib/matplotlib/axes/_axes.pyi | 2 +- lib/matplotlib/pyplot.py | 2 +- lib/matplotlib/tests/test_axes.py | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9a2b367fb502..71331ee50967 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3821,7 +3821,7 @@ def apply_mask(arrays, mask): @_api.rename_parameter("3.9", "labels", "tick_labels") def boxplot(self, x, notch=None, sym=None, vert=None, orientation='vertical', whis=None, positions=None, - widths=None, patch_artist=None, bootstrap=None, + widths=None, patch_artist='auto', bootstrap=None, usermedians=None, conf_intervals=None, meanline=None, showmeans=None, showcaps=None, showbox=None, showfliers=None, boxprops=None, @@ -3947,9 +3947,12 @@ def boxplot(self, x, notch=None, sym=None, vert=None, The widths of the boxes. The default is 0.5, or ``0.15*(distance between extreme positions)``, if that is smaller. - patch_artist : bool, default: :rc:`boxplot.patchartist` - If `False` produces boxes with the Line2D artist. Otherwise, - boxes are drawn with Patch artists. + patch_artist : bool, default: 'auto' + If 'auto', boxes are drawn with the Patch artist if a parameter + that needs the Patch artist is passed, otherwise they are drawn + with the Line2D artist. + If `True` produces boxes with the Patch artist. + If `False` produces boxes with the Line2D artist. tick_labels : list of str, optional The tick labels of each boxplot. @@ -4060,8 +4063,6 @@ def boxplot(self, x, notch=None, sym=None, vert=None, labels=tick_labels, autorange=autorange) if notch is None: notch = mpl.rcParams['boxplot.notch'] - if patch_artist is None: - patch_artist = mpl.rcParams['boxplot.patchartist'] if meanline is None: meanline = mpl.rcParams['boxplot.meanline'] if showmeans is None: @@ -4086,7 +4087,12 @@ def boxplot(self, x, notch=None, sym=None, vert=None, if flierprops is None: flierprops = {} - if patch_artist: + if patch_artist == 'auto': + patch_artist = mpl.rcParams['boxplot.patchartist'] + require_patch = {'edgecolor', 'facecolor'} + if require_patch.intersection(boxprops): + patch_artist = True + if patch_artist is True: boxprops['linestyle'] = 'solid' # Not consistent with bxp. if 'color' in boxprops: boxprops['edgecolor'] = boxprops.pop('color') @@ -4347,7 +4353,7 @@ def merge_kw_rc(subkey, explicit, zdelta=0, usemarker=True): else mpl.rcParams['patch.facecolor']), 'zorder': zorder, **cbook.normalize_kwargs(boxprops, mpatches.PathPatch) - } if patch_artist else merge_kw_rc('box', boxprops, usemarker=False) + } if patch_artist is True else merge_kw_rc('box', boxprops, usemarker=False) whisker_kw = merge_kw_rc('whisker', whiskerprops, usemarker=False) cap_kw = merge_kw_rc('cap', capprops, usemarker=False) flier_kw = merge_kw_rc('flier', flierprops) @@ -4462,7 +4468,7 @@ def do_patch(xs, ys, **kwargs): # maybe draw the box if showbox: - do_box = do_patch if patch_artist else do_plot + do_box = do_patch if patch_artist is True else do_plot boxes.append(do_box(box_x, box_y, **box_kw)) median_kw.setdefault('label', '_nolegend_') # draw the whiskers diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index b728d24d9fe9..c744c40ee373 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -354,7 +354,7 @@ class Axes(_AxesBase): whis: float | tuple[float, float] | None = ..., positions: ArrayLike | None = ..., widths: float | ArrayLike | None = ..., - patch_artist: bool | None = ..., + patch_artist: Literal["auto"] | bool | None = ..., bootstrap: int | None = ..., usermedians: ArrayLike | None = ..., conf_intervals: ArrayLike | None = ..., diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 9b516d5aae8a..73667d79283d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3008,7 +3008,7 @@ def boxplot( whis: float | tuple[float, float] | None = None, positions: ArrayLike | None = None, widths: float | ArrayLike | None = None, - patch_artist: bool | None = None, + patch_artist: Literal["auto"] | bool | None = "auto", bootstrap: int | None = None, usermedians: ArrayLike | None = None, conf_intervals: ArrayLike | None = None, diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index dd37d3d8ee80..ebc56327792a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -9153,3 +9153,21 @@ def test_boxplot_orientation(fig_test, fig_ref): ax_test = fig_test.subplots() ax_test.boxplot(all_data, orientation='horizontal') + + +def test_patch_artist_auto(): + # Test patch_artist = 'auto'. + fig, axs = plt.subplots(nrows=1, ncols=3) + np.random.seed(19680801) + all_data = [np.random.normal(0, std, 100) for std in range(7, 10)] + # Default 'auto' state, should use Line2D artist. + bp0 = axs[0].boxplot(all_data) + assert all(isinstance(i, mpl.lines.Line2D) for i in bp0['boxes']) + # 'facecolor' in boxprops should turn on patch_artist. + bp1 = axs[1].boxplot(all_data, boxprops={'facecolor': 'r'}) + assert all(i.get_facecolor() == (1.0, 0.0, 0.0, 1) for i in bp1['boxes']) + assert all(isinstance(i, mpl.patches.PathPatch) for i in bp1['boxes']) + # 'edgecolor' in boxprops should also turn on patch_artist. + bp2 = axs[2].boxplot(all_data, boxprops={'edgecolor': 'g'}) + assert all(i.get_edgecolor() == (0.0, 0.5, 0.0, 1) for i in bp2['boxes']) + assert all(isinstance(i, mpl.patches.PathPatch) for i in bp2['boxes'])