From 47d75f20e942f2301e2184c5991170d33a30cfbe Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 16 Feb 2019 21:20:03 +0100 Subject: [PATCH 1/3] Let boxplot() defer rcParams application to bxp() instead of duplicating the defaults-applying code in both methods. This caught an oversight in the application of the boxplot.boxprops.linewidth rcParam in bxp (see changelog note); because of that, the bxp testing code needed some update (to keep the old behavior). Preliminary work to having bxp() normalize properties passed in the boxprops, meanprops, etc. kwargs as well. --- doc/api/next_api_changes/2018-02-17-AL.rst | 8 + lib/matplotlib/axes/_axes.py | 134 ++++------ lib/matplotlib/tests/test_axes.py | 278 +++++---------------- 3 files changed, 125 insertions(+), 295 deletions(-) create mode 100644 doc/api/next_api_changes/2018-02-17-AL.rst diff --git a/doc/api/next_api_changes/2018-02-17-AL.rst b/doc/api/next_api_changes/2018-02-17-AL.rst new file mode 100644 index 000000000000..632e1efd2a98 --- /dev/null +++ b/doc/api/next_api_changes/2018-02-17-AL.rst @@ -0,0 +1,8 @@ +`~Axes.bxp` now respects :rc:`boxplot.boxprops.linewidth` even when *patch_artist* is set +````````````````````````````````````````````````````````````````````````````````````````` + +Previously, when the *patch_artist* parameter was set, `~Axes.bxp` would ignore +:rc:`boxplot.boxprops.linewidth`. This was an oversight -- in particular, +`~Axes.boxplot` did not ignore it. + +This oversight is now fixed. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 657db1cf392d..c33c4bf6b3ff 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3635,33 +3635,23 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, if showfliers is None: showfliers = rcParams['boxplot.showfliers'] - def _update_dict(dictionary, rc_name, properties): - """ Loads properties in the dictionary from rc file if not already - in the dictionary""" - rc_str = 'boxplot.{0}.{1}' - if dictionary is None: - dictionary = dict() - for prop_dict in properties: - dictionary.setdefault(prop_dict, - rcParams[rc_str.format(rc_name, prop_dict)]) - return dictionary - - # Common property dicts loading from rc. - flier_props = ['color', 'marker', 'markerfacecolor', 'markeredgecolor', - 'markersize', 'linestyle', 'linewidth'] - default_props = ['color', 'linewidth', 'linestyle'] - - boxprops = _update_dict(boxprops, 'boxprops', default_props) - whiskerprops = _update_dict(whiskerprops, 'whiskerprops', - default_props) - capprops = _update_dict(capprops, 'capprops', default_props) - medianprops = _update_dict(medianprops, 'medianprops', default_props) - meanprops = _update_dict(meanprops, 'meanprops', default_props) - flierprops = _update_dict(flierprops, 'flierprops', flier_props) + if boxprops is None: + boxprops = {} + if whiskerprops is None: + whiskerprops = {} + if capprops is None: + capprops = {} + if medianprops is None: + medianprops = {} + if meanprops is None: + meanprops = {} + if flierprops is None: + flierprops = {} if patch_artist: - boxprops['linestyle'] = 'solid' - boxprops['edgecolor'] = boxprops.pop('color') + boxprops['linestyle'] = 'solid' # Not consistent with bxp. + if 'color' in boxprops: + boxprops['edgecolor'] = boxprops.pop('color') # if non-default sym value, put it into the flier dictionary # the logic for providing the default symbol ('b+') now lives @@ -3895,18 +3885,18 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, zdelta = 0.1 # box properties + final_boxprops = dict( + linestyle=rcParams['boxplot.boxprops.linestyle'], + linewidth=rcParams['boxplot.boxprops.linewidth'], + ) if patch_artist: - final_boxprops = dict( - linestyle=rcParams['boxplot.boxprops.linestyle'], + final_boxprops.update( edgecolor=rcParams['boxplot.boxprops.color'], - facecolor=rcParams['patch.facecolor'], - linewidth=rcParams['boxplot.boxprops.linewidth'] + facecolor=('white' if rcParams['_internal.classic_mode'] else + rcParams['patch.facecolor']), ) - if rcParams['_internal.classic_mode']: - final_boxprops['facecolor'] = 'white' else: - final_boxprops = dict( - linestyle=rcParams['boxplot.boxprops.linestyle'], + final_boxprops.update( color=rcParams['boxplot.boxprops.color'], ) @@ -3914,69 +3904,47 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, if boxprops is not None: final_boxprops.update(boxprops) - # other (cap, whisker) properties - final_whiskerprops = dict( - linestyle=rcParams['boxplot.whiskerprops.linestyle'], - linewidth=rcParams['boxplot.whiskerprops.linewidth'], - color=rcParams['boxplot.whiskerprops.color'], - ) - - final_capprops = dict( - linestyle=rcParams['boxplot.capprops.linestyle'], - linewidth=rcParams['boxplot.capprops.linewidth'], - color=rcParams['boxplot.capprops.color'], - ) - - final_capprops['zorder'] = zorder - if capprops is not None: - final_capprops.update(capprops) - + # whisker properties + final_whiskerprops = { + k.split('.')[-1]: v for k, v in rcParams.items() + if k.startswith('boxplot.whiskerprops') + } final_whiskerprops['zorder'] = zorder if whiskerprops is not None: final_whiskerprops.update(whiskerprops) - - # set up the default flier properties - final_flierprops = dict( - linestyle=rcParams['boxplot.flierprops.linestyle'], - linewidth=rcParams['boxplot.flierprops.linewidth'], - color=rcParams['boxplot.flierprops.color'], - marker=rcParams['boxplot.flierprops.marker'], - markerfacecolor=rcParams['boxplot.flierprops.markerfacecolor'], - markeredgecolor=rcParams['boxplot.flierprops.markeredgecolor'], - markeredgewidth=rcParams['boxplot.flierprops.markeredgewidth'], - markersize=rcParams['boxplot.flierprops.markersize'], - ) - + # cap properties + final_capprops = { + k.split('.')[-1]: v for k, v in rcParams.items() + if k.startswith('boxplot.capprops') + } + final_capprops['zorder'] = zorder + if capprops is not None: + final_capprops.update(capprops) + # flier properties + final_flierprops = { + k.split('.')[-1]: v for k, v in rcParams.items() + if k.startswith('boxplot.flierprops') + } final_flierprops['zorder'] = zorder - # flier (outlier) properties if flierprops is not None: final_flierprops.update(flierprops) - # median line properties - final_medianprops = dict( - linestyle=rcParams['boxplot.medianprops.linestyle'], - linewidth=rcParams['boxplot.medianprops.linewidth'], - color=rcParams['boxplot.medianprops.color'], - ) + final_medianprops = { + k.split('.')[-1]: v for k, v in rcParams.items() + if k.startswith('boxplot.medianprops') + } final_medianprops['zorder'] = zorder + zdelta if medianprops is not None: final_medianprops.update(medianprops) - # mean (line or point) properties + final_meanprops = { + k.split('.')[-1]: v for k, v in rcParams.items() + if k.startswith('boxplot.meanprops') + } if meanline: - final_meanprops = dict( - linestyle=rcParams['boxplot.meanprops.linestyle'], - linewidth=rcParams['boxplot.meanprops.linewidth'], - color=rcParams['boxplot.meanprops.color'], - ) + final_meanprops['marker'] = '' else: - final_meanprops = dict( - linestyle='', - marker=rcParams['boxplot.meanprops.marker'], - markerfacecolor=rcParams['boxplot.meanprops.markerfacecolor'], - markeredgecolor=rcParams['boxplot.meanprops.markeredgecolor'], - markersize=rcParams['boxplot.meanprops.markersize'], - ) + final_meanprops['linestyle'] = '' final_meanprops['zorder'] = zorder + zdelta if meanprops is not None: final_meanprops.update(meanprops) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f9519f8074cc..a62204732f85 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2108,19 +2108,30 @@ def layers(n, m): axs[1, 1].stackplot(range(100), d.T, baseline='weighted_wiggle') +def _bxp_test_helper( + stats_kwargs={}, transform_stats=lambda s: s, bxp_kwargs={}): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)), **stats_kwargs) + fig, ax = plt.subplots() + if bxp_kwargs.get('vert', True): + ax.set_yscale('log') + else: + ax.set_xscale('log') + # Work around baseline images generate back when bxp did not respect the + # boxplot.boxprops.linewidth rcParam when patch_artist is False. + if not bxp_kwargs.get('patch_artist', False): + (bxp_kwargs.setdefault('boxprops', {}) + .setdefault('linewidth', matplotlib.rcParams["lines.linewidth"])) + ax.bxp(transform_stats(logstats), **bxp_kwargs) + + @image_comparison(baseline_images=['bxp_baseline'], extensions=['png'], savefig_kwarg={'dpi': 40}, style='default') def test_bxp_baseline(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats) + _bxp_test_helper() @image_comparison(baseline_images=['bxp_rangewhis'], @@ -2128,15 +2139,7 @@ def test_bxp_baseline(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_rangewhis(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)), - whis='range' - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats) + _bxp_test_helper(stats_kwargs=dict(whis='range')) @image_comparison(baseline_images=['bxp_precentilewhis'], @@ -2144,15 +2147,7 @@ def test_bxp_rangewhis(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_precentilewhis(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)), - whis=[5, 95] - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats) + _bxp_test_helper(stats_kwargs=dict(whis=[5, 95])) @image_comparison(baseline_images=['bxp_with_xlabels'], @@ -2160,16 +2155,12 @@ def test_bxp_precentilewhis(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_with_xlabels(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - for stats, label in zip(logstats, list('ABCD')): - stats['label'] = label + def transform(stats): + for s, label in zip(stats, list('ABCD')): + s['label'] = label + return stats - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats) + _bxp_test_helper(transform_stats=transform) @image_comparison(baseline_images=['bxp_horizontal'], @@ -2178,14 +2169,7 @@ def test_bxp_with_xlabels(): style='default', tol=0.1) def test_bxp_horizontal(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_xscale('log') - ax.bxp(logstats, vert=False) + _bxp_test_helper(bxp_kwargs=dict(vert=False)) @image_comparison(baseline_images=['bxp_with_ylabels'], @@ -2194,16 +2178,12 @@ def test_bxp_horizontal(): style='default', tol=0.1,) def test_bxp_with_ylabels(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - for stats, label in zip(logstats, list('ABCD')): - stats['label'] = label + def transform(stats): + for s, label in zip(stats, list('ABCD')): + s['label'] = label + return stats - fig, ax = plt.subplots() - ax.set_xscale('log') - ax.bxp(logstats, vert=False) + _bxp_test_helper(transform_stats=transform, bxp_kwargs=dict(vert=False)) @image_comparison(baseline_images=['bxp_patchartist'], @@ -2211,14 +2191,7 @@ def test_bxp_with_ylabels(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_patchartist(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, patch_artist=True) + _bxp_test_helper(bxp_kwargs=dict(patch_artist=True)) @image_comparison(baseline_images=['bxp_custompatchartist'], @@ -2226,15 +2199,9 @@ def test_bxp_patchartist(): savefig_kwarg={'dpi': 100}, style='default') def test_bxp_custompatchartist(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - boxprops = dict(facecolor='yellow', edgecolor='green', linestyle='dotted') - ax.bxp(logstats, patch_artist=True, boxprops=boxprops) + _bxp_test_helper(bxp_kwargs=dict( + patch_artist=True, + boxprops=dict(facecolor='yellow', edgecolor='green', linestyle=':'))) @image_comparison(baseline_images=['bxp_customoutlier'], @@ -2242,15 +2209,8 @@ def test_bxp_custompatchartist(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_customoutlier(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - flierprops = dict(linestyle='none', marker='d', markerfacecolor='g') - ax.bxp(logstats, flierprops=flierprops) + _bxp_test_helper(bxp_kwargs=dict( + flierprops=dict(linestyle='none', marker='d', markerfacecolor='g'))) @image_comparison(baseline_images=['bxp_withmean_custompoint'], @@ -2258,15 +2218,10 @@ def test_bxp_customoutlier(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_showcustommean(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - meanprops = dict(linestyle='none', marker='d', markerfacecolor='green') - ax.bxp(logstats, showmeans=True, meanprops=meanprops) + _bxp_test_helper(bxp_kwargs=dict( + showmeans=True, + meanprops=dict(linestyle='none', marker='d', markerfacecolor='green'), + )) @image_comparison(baseline_images=['bxp_custombox'], @@ -2274,15 +2229,8 @@ def test_bxp_showcustommean(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_custombox(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - boxprops = dict(linestyle='--', color='b', linewidth=3) - ax.bxp(logstats, boxprops=boxprops) + _bxp_test_helper(bxp_kwargs=dict( + boxprops=dict(linestyle='--', color='b', linewidth=3))) @image_comparison(baseline_images=['bxp_custommedian'], @@ -2290,15 +2238,8 @@ def test_bxp_custombox(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_custommedian(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - medianprops = dict(linestyle='--', color='b', linewidth=3) - ax.bxp(logstats, medianprops=medianprops) + _bxp_test_helper(bxp_kwargs=dict( + medianprops=dict(linestyle='--', color='b', linewidth=3))) @image_comparison(baseline_images=['bxp_customcap'], @@ -2306,15 +2247,8 @@ def test_bxp_custommedian(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_customcap(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - capprops = dict(linestyle='--', color='g', linewidth=3) - ax.bxp(logstats, capprops=capprops) + _bxp_test_helper(bxp_kwargs=dict( + capprops=dict(linestyle='--', color='g', linewidth=3))) @image_comparison(baseline_images=['bxp_customwhisker'], @@ -2322,15 +2256,8 @@ def test_bxp_customcap(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_customwhisker(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - whiskerprops = dict(linestyle='-', color='m', linewidth=3) - ax.bxp(logstats, whiskerprops=whiskerprops) + _bxp_test_helper(bxp_kwargs=dict( + whiskerprops=dict(linestyle='-', color='m', linewidth=3))) @image_comparison(baseline_images=['bxp_withnotch'], @@ -2338,14 +2265,7 @@ def test_bxp_customwhisker(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_shownotches(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, shownotches=True) + _bxp_test_helper(bxp_kwargs=dict(shownotches=True)) @image_comparison(baseline_images=['bxp_nocaps'], @@ -2353,14 +2273,7 @@ def test_bxp_shownotches(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_nocaps(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, showcaps=False) + _bxp_test_helper(bxp_kwargs=dict(showcaps=False)) @image_comparison(baseline_images=['bxp_nobox'], @@ -2368,14 +2281,7 @@ def test_bxp_nocaps(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_nobox(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, showbox=False) + _bxp_test_helper(bxp_kwargs=dict(showbox=False)) @image_comparison(baseline_images=['bxp_no_flier_stats'], @@ -2383,16 +2289,13 @@ def test_bxp_nobox(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_no_flier_stats(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - for ls in logstats: - ls.pop('fliers', None) + def transform(stats): + for s in stats: + s.pop('fliers', None) + return stats - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, showfliers=False) + _bxp_test_helper(transform_stats=transform, + bxp_kwargs=dict(showfliers=False)) @image_comparison(baseline_images=['bxp_withmean_point'], @@ -2400,14 +2303,7 @@ def test_bxp_no_flier_stats(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_showmean(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, showmeans=True, meanline=False) + _bxp_test_helper(bxp_kwargs=dict(showmeans=True, meanline=False)) @image_comparison(baseline_images=['bxp_withmean_line'], @@ -2415,14 +2311,7 @@ def test_bxp_showmean(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_showmeanasline(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, showmeans=True, meanline=True) + _bxp_test_helper(bxp_kwargs=dict(showmeans=True, meanline=True)) @image_comparison(baseline_images=['bxp_scalarwidth'], @@ -2430,14 +2319,7 @@ def test_bxp_showmeanasline(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_scalarwidth(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, widths=0.25) + _bxp_test_helper(bxp_kwargs=dict(widths=.25)) @image_comparison(baseline_images=['bxp_customwidths'], @@ -2445,14 +2327,7 @@ def test_bxp_scalarwidth(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_customwidths(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, widths=[0.10, 0.25, 0.65, 0.85]) + _bxp_test_helper(bxp_kwargs=dict(widths=[0.10, 0.25, 0.65, 0.85])) @image_comparison(baseline_images=['bxp_custompositions'], @@ -2460,38 +2335,17 @@ def test_bxp_customwidths(): savefig_kwarg={'dpi': 40}, style='default') def test_bxp_custompositions(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') - ax.bxp(logstats, positions=[1, 5, 6, 7]) + _bxp_test_helper(bxp_kwargs=dict(positions=[1, 5, 6, 7])) def test_bxp_bad_widths(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') with pytest.raises(ValueError): - ax.bxp(logstats, widths=[1]) + _bxp_test_helper(bxp_kwargs=dict(widths=[1])) def test_bxp_bad_positions(): - np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( - np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)) - ) - - fig, ax = plt.subplots() - ax.set_yscale('log') with pytest.raises(ValueError): - ax.bxp(logstats, positions=[2, 3]) + _bxp_test_helper(bxp_kwargs=dict(positions=[2, 3])) @image_comparison(baseline_images=['boxplot', 'boxplot'], From e1cb5d2f2cbfc3c283a8770f6c78b0580623e6f5 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 18 Feb 2019 16:54:39 +0100 Subject: [PATCH 2/3] Further factor out rcdefaults application in bxp(). --- lib/matplotlib/axes/_axes.py | 82 +++++++++++------------------------- 1 file changed, 25 insertions(+), 57 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c33c4bf6b3ff..86580b9fe4d6 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3884,70 +3884,38 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, zorder = mlines.Line2D.zorder zdelta = 0.1 + + def with_rcdefaults(subkey, explicit, zdelta=0): + d = {k.split('.')[-1]: v for k, v in rcParams.items() + if k.startswith(f'boxplot.{subkey}')} + d['zorder'] = zorder + zdelta + if explicit is not None: + d.update(explicit) + return d + # box properties - final_boxprops = dict( - linestyle=rcParams['boxplot.boxprops.linestyle'], - linewidth=rcParams['boxplot.boxprops.linewidth'], - ) if patch_artist: - final_boxprops.update( + final_boxprops = dict( + linestyle=rcParams['boxplot.boxprops.linestyle'], + linewidth=rcParams['boxplot.boxprops.linewidth'], edgecolor=rcParams['boxplot.boxprops.color'], facecolor=('white' if rcParams['_internal.classic_mode'] else rcParams['patch.facecolor']), + zorder=zorder, ) + if boxprops is not None: + final_boxprops.update(boxprops) else: - final_boxprops.update( - color=rcParams['boxplot.boxprops.color'], - ) - - final_boxprops['zorder'] = zorder - if boxprops is not None: - final_boxprops.update(boxprops) - - # whisker properties - final_whiskerprops = { - k.split('.')[-1]: v for k, v in rcParams.items() - if k.startswith('boxplot.whiskerprops') - } - final_whiskerprops['zorder'] = zorder - if whiskerprops is not None: - final_whiskerprops.update(whiskerprops) - # cap properties - final_capprops = { - k.split('.')[-1]: v for k, v in rcParams.items() - if k.startswith('boxplot.capprops') - } - final_capprops['zorder'] = zorder - if capprops is not None: - final_capprops.update(capprops) - # flier properties - final_flierprops = { - k.split('.')[-1]: v for k, v in rcParams.items() - if k.startswith('boxplot.flierprops') - } - final_flierprops['zorder'] = zorder - if flierprops is not None: - final_flierprops.update(flierprops) - # median line properties - final_medianprops = { - k.split('.')[-1]: v for k, v in rcParams.items() - if k.startswith('boxplot.medianprops') - } - final_medianprops['zorder'] = zorder + zdelta - if medianprops is not None: - final_medianprops.update(medianprops) - # mean (line or point) properties - final_meanprops = { - k.split('.')[-1]: v for k, v in rcParams.items() - if k.startswith('boxplot.meanprops') - } - if meanline: - final_meanprops['marker'] = '' - else: - final_meanprops['linestyle'] = '' - final_meanprops['zorder'] = zorder + zdelta - if meanprops is not None: - final_meanprops.update(meanprops) + final_boxprops = with_rcdefaults('boxprops', boxprops) + final_whiskerprops = with_rcdefaults('whiskerprops', whiskerprops) + final_capprops = with_rcdefaults('capprops', capprops) + final_flierprops = with_rcdefaults('flierprops', flierprops) + final_medianprops = with_rcdefaults('medianprops', medianprops, zdelta) + final_meanprops = with_rcdefaults('meanprops', meanprops, zdelta) + removed_prop = 'marker' if meanline else 'linestyle' + # Only remove the property if it's not set explicitly as a parameter. + if meanprops is None or removed_prop not in meanprops: + final_meanprops[removed_prop] = '' def to_vc(xs, ys): # convert arguments to verts and codes, append (0, 0) (ignored). From 2b8aee282a0b57245aa4f1efa7a940b008899df8 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 19 Feb 2019 11:12:00 +0100 Subject: [PATCH 3/3] Check that bxp() applies the boxplot.boxprops.linewidth rc. --- lib/matplotlib/tests/test_axes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a62204732f85..8c5627d1f152 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2111,7 +2111,7 @@ def layers(n, m): def _bxp_test_helper( stats_kwargs={}, transform_stats=lambda s: s, bxp_kwargs={}): np.random.seed(937) - logstats = matplotlib.cbook.boxplot_stats( + logstats = mpl.cbook.boxplot_stats( np.random.lognormal(mean=1.25, sigma=1., size=(37, 4)), **stats_kwargs) fig, ax = plt.subplots() if bxp_kwargs.get('vert', True): @@ -2121,8 +2121,8 @@ def _bxp_test_helper( # Work around baseline images generate back when bxp did not respect the # boxplot.boxprops.linewidth rcParam when patch_artist is False. if not bxp_kwargs.get('patch_artist', False): - (bxp_kwargs.setdefault('boxprops', {}) - .setdefault('linewidth', matplotlib.rcParams["lines.linewidth"])) + mpl.rcParams['boxplot.boxprops.linewidth'] = \ + mpl.rcParams['lines.linewidth'] ax.bxp(transform_stats(logstats), **bxp_kwargs)