diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index fa6431d5c81b..d2fb793756af 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2870,18 +2870,19 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, meanline=False, showmeans=False, showcaps=True, showbox=True, showfliers=True, boxprops=None, labels=None, flierprops=None, medianprops=None, meanprops=None, - manage_xticks=True): + capprops=None, whiskerprops=None, manage_xticks=True): """ Make a box and whisker plot. Call signature:: - boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, + boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, conf_intervals=None, meanline=False, showmeans=False, showcaps=True, showbox=True, showfliers=True, boxprops=None, labels=None, - flierprops=None, medianprops=None, meanprops=None) + flierprops=None, medianprops=None, meanprops=None, + capprops=None, whiskerprops=None, manage_xticks=True): Make a box and whisker plot for each column of *x* or each vector in sequence *x*. The box extends from the lower to @@ -2977,6 +2978,12 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, boxprops : dict or None (default) If provided, will set the plotting style of the boxes + whiskerprops : dict or None (default) + If provided, will set the plotting style of the whiskers + + capprops : dict or None (default) + If provided, will set the plotting style of the whiskers + flierprops : dict or None (default) If provided, will set the plotting style of the fliers @@ -3017,9 +3024,10 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, """ bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap, labels=labels) - if sym == 'b+' and flierprops is None: - flierprops = dict(linestyle='none', marker='+', - markeredgecolor='blue') + if flierprops is None: + flierprops = dict(sym=sym) + else: + flierprops['sym'] = sym # replace medians if necessary: if usermedians is not None: @@ -3056,24 +3064,27 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, boxprops=boxprops, flierprops=flierprops, medianprops=medianprops, meanprops=meanprops, meanline=meanline, showfliers=showfliers, + capprops=capprops, whiskerprops=whiskerprops, manage_xticks=manage_xticks) return artists def bxp(self, bxpstats, positions=None, widths=None, vert=True, patch_artist=False, shownotches=False, showmeans=False, showcaps=True, showbox=True, showfliers=True, - boxprops=None, flierprops=None, medianprops=None, - meanprops=None, meanline=False, manage_xticks=True): + boxprops=None, whiskerprops=None, flierprops=None, + medianprops=None, capprops=None, meanprops=None, + meanline=False, manage_xticks=True): """ Drawing function for box and whisker plots. Call signature:: - bxp(bxpstats, positions=None, widths=None, vert=True, + bxp(self, bxpstats, positions=None, widths=None, vert=True, patch_artist=False, shownotches=False, showmeans=False, showcaps=True, showbox=True, showfliers=True, - boxprops=None, flierprops=None, medianprops=None, - meanprops=None, meanline=False, manage_xticks=True) + boxprops=None, whiskerprops=None, flierprops=None, + medianprops=None, capprops=None, meanprops=None, + meanline=False, manage_xticks=True): Make a box and whisker plot for each column of *x* or each vector in sequence *x*. The box extends from the lower to @@ -3119,14 +3130,14 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, If True produces boxes with the Patch artist shownotches : bool, default = False - If False (default), produces a rectangular box plot. - If True, will produce a notched box plot + If False (default), produces a rectangular box plot. + If True, will produce a ed box plot showmeans : bool, default = False If True, will toggle one the rendering of the means showcaps : bool, default = True - If True, will toggle one the rendering of the caps + If True will toggle one the rendering of the caps showbox : bool, default = True If True, will toggle one the rendering of box @@ -3137,8 +3148,14 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, boxprops : dict or None (default) If provided, will set the plotting style of the boxes + whiskerprops : dict or None (default) + If provided, will set the plotting style of the whiskers + + capprops : dict or None (default) + If provided, will set the plotting style of the whiskers + flierprops : dict or None (default) - If provided, will set the plotting style of the fliers + If provided will set the plotting style of the fliers medianprops : dict or None (default) If provided, will set the plotting style of the medians @@ -3202,37 +3219,52 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, final_boxprops = dict(linestyle='solid', edgecolor='black', facecolor='white', linewidth=1) else: - final_boxprops = dict(linestyle='-', color='black', linewidth=1) + final_boxprops = dict(linestyle='-', color='blue') if boxprops is not None: final_boxprops.update(boxprops) # other (cap, whisker) properties - if patch_artist: - otherprops = dict( - linestyle=linestyle_map[final_boxprops['linestyle']], - color=final_boxprops['edgecolor'], - linewidth=final_boxprops.get('linewidth', 1) - ) - else: - otherprops = dict(linestyle=final_boxprops['linestyle'], - color=final_boxprops['color'], - linewidth=final_boxprops.get('linewidth', 1)) + final_whiskerprops = dict( + linestyle='--', + color='blue', + ) + + final_capprops = dict( + linestyle='-', + color='black', + ) + + if capprops is not None: + final_capprops.update(capprops) + + if whiskerprops is not None: + final_whiskerprops.update(whiskerprops) # flier (outlier) properties - final_flierprops = dict(linestyle='none', marker='+', - markeredgecolor='blue') if flierprops is not None: + sym = flierprops.pop('sym', '') + + if sym == '': + final_flierprops = dict(linestyle='none', marker='+', + markeredgecolor='b', + markerfacecolor='none') + else: + final_flierprops = dict(linestyle='none') final_flierprops.update(flierprops) + else: + sym = '' + final_flierprops = dict(linestyle='none', marker='+', + markeredgecolor='b') # median line properties - final_medianprops = dict(linestyle='-', color='blue') + final_medianprops = dict(linestyle='-', color='red') if medianprops is not None: final_medianprops.update(medianprops) # mean (line or point) properties if meanline: - final_meanprops = dict(linestyle='--', color='red') + final_meanprops = dict(linestyle='--', color='black') else: final_meanprops = dict(linestyle='none', markerfacecolor='red', marker='s') @@ -3352,13 +3384,17 @@ def dopatch(xs, ys, **kwargs): boxes.extend(doplot(box_x, box_y, **final_boxprops)) # draw the whiskers - whiskers.extend(doplot(whisker_x, whiskerlo_y, **otherprops)) - whiskers.extend(doplot(whisker_x, whiskerhi_y, **otherprops)) + whiskers.extend(doplot( + whisker_x, whiskerlo_y,**final_whiskerprops + )) + whiskers.extend(doplot( + whisker_x, whiskerhi_y,**final_whiskerprops + )) # maybe draw the caps: if showcaps: - caps.extend(doplot(cap_x, cap_lo, **otherprops)) - caps.extend(doplot(cap_x, cap_hi, **otherprops)) + caps.extend(doplot(cap_x, cap_lo, **final_capprops)) + caps.extend(doplot(cap_x, cap_hi, **final_capprops)) # draw the medians medians.extend(doplot(med_x, med_y, **final_medianprops)) @@ -3377,7 +3413,9 @@ def dopatch(xs, ys, **kwargs): # maybe draw the fliers if showfliers: - fliers.extend(doplot(flier_x, flier_y, **final_flierprops)) + fliers.extend(doplot( + flier_x, flier_y, sym, **final_flierprops + )) # fix our axes/ticks up a little if vert: diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf index 6603608c6832..8eda82a6fbd8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png index f17404f5ad48..22cae58cb7fb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.svg index e123c4a2db1c..315eca77e663 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.svg @@ -40,17 +40,17 @@ L166.86 195.55 L166.86 209.328 L175.23 216 L166.86 222.672 -L166.86 236.45" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L166.86 236.45" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> +L183.6 256.32" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L183.6 175.68" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L191.97 216" style="fill:none;stroke:#ff0000;stroke-linecap:square;"/> @@ -73,11 +73,11 @@ L191.97 216" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> M-3 0 L3 0 M0 3 -L0 -3" id="md4acab13e0" style="stroke:#0000ff;stroke-width:0.5;"/> +L0 -3" id="m459c79e89c" style="stroke:#0000ff;stroke-width:0.5;"/> - - + + @@ -92,17 +92,17 @@ L390.06 195.55 L390.06 209.328 L398.43 216 L390.06 222.672 -L390.06 236.45" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L390.06 236.45" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> +L406.8 256.32" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L406.8 175.68" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L415.17 216" style="fill:none;stroke:#ff0000;stroke-linecap:square;"/> - - + + @@ -131,85 +131,81 @@ L415.17 216" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> +L0 -4" id="mc7db9fdffb" style="stroke:#000000;stroke-width:0.5;"/> - + +L0 4" id="m5a7d422ac3" style="stroke:#000000;stroke-width:0.5;"/> - + - + +M31.7812 66.4062 +Q24.1719 66.4062 20.3281 58.9062 +Q16.5 51.4219 16.5 36.375 +Q16.5 21.3906 20.3281 13.8906 +Q24.1719 6.39062 31.7812 6.39062 +Q39.4531 6.39062 43.2812 13.8906 +Q47.125 21.3906 47.125 36.375 +Q47.125 51.4219 43.2812 58.9062 +Q39.4531 66.4062 31.7812 66.4062 +M31.7812 74.2188 +Q44.0469 74.2188 50.5156 64.5156 +Q56.9844 54.8281 56.9844 36.375 +Q56.9844 17.9688 50.5156 8.26562 +Q44.0469 -1.42188 31.7812 -1.42188 +Q19.5312 -1.42188 13.0625 8.26562 +Q6.59375 17.9688 6.59375 36.375 +Q6.59375 54.8281 13.0625 64.5156 +Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - - + + - + - + - + +M12.4062 8.29688 +L28.5156 8.29688 +L28.5156 63.9219 +L10.9844 60.4062 +L10.9844 69.3906 +L28.4219 72.9062 +L38.2812 72.9062 +L38.2812 8.29688 +L54.3906 8.29688 +L54.3906 0 +L12.4062 0 +z +" id="BitstreamVeraSans-Roman-31"/> - - + + @@ -220,33 +216,26 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> +L4 0" id="md7965d1ba0" style="stroke:#000000;stroke-width:0.5;"/> - + +L-4 0" id="md9a1c1a7cd" style="stroke:#000000;stroke-width:0.5;"/> - + - +M10.5938 35.5 +L73.1875 35.5 +L73.1875 27.2031 +L10.5938 27.2031 +z +" id="BitstreamVeraSans-Roman-2212"/> @@ -307,16 +284,41 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + + + + @@ -327,12 +329,12 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + @@ -347,12 +349,12 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + @@ -365,12 +367,12 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + @@ -384,12 +386,12 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + @@ -403,12 +405,12 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + @@ -422,23 +424,23 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> +M72 388.8 +L72 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +M72 388.8 +L518.4 388.8" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +M518.4 388.8 +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +M72 43.2 +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf index dfdb5b6f3f9c..841b92eb69ec 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.png index 3e4d8726d3a3..6f4d6c4ec746 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.png and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg index 0d070e52b984..2f45fa6743b0 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg @@ -40,17 +40,17 @@ L166.86 181.44 L166.86 181.44 L175.23 181.44 L166.86 181.44 -L166.86 181.44" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L166.86 181.44" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> +L183.6 216" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L183.6 146.88" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L191.97 181.44" style="fill:none;stroke:#ff0000;stroke-linecap:square;"/> @@ -80,17 +80,17 @@ L390.06 181.44 L390.06 181.44 L398.43 181.44 L390.06 181.44 -L390.06 181.44" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L390.06 181.44" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> +L406.8 216" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L406.8 146.88" style="fill:none;stroke:#0000ff;stroke-dasharray:6.000000,6.000000;stroke-dashoffset:0.0;"/> +L415.17 181.44" style="fill:none;stroke:#ff0000;stroke-linecap:square;"/> @@ -114,20 +114,20 @@ L415.17 181.44" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> +L0 -4" id="mc7db9fdffb" style="stroke:#000000;stroke-width:0.5;"/> - + +L0 4" id="m5a7d422ac3" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -161,12 +161,12 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + @@ -199,33 +199,26 @@ z +L4 0" id="md7965d1ba0" style="stroke:#000000;stroke-width:0.5;"/> - + +L-4 0" id="md9a1c1a7cd" style="stroke:#000000;stroke-width:0.5;"/> - + - + @@ -254,12 +254,12 @@ z - + - + @@ -298,12 +298,12 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + - + @@ -316,12 +316,12 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + - + @@ -334,12 +334,12 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + - + @@ -352,23 +352,23 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> +M72 388.8 +L72 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +M72 388.8 +L518.4 388.8" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +M518.4 388.8 +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +M72 43.2 +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym.png new file mode 100644 index 000000000000..a4ce8d83652d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_sym.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png new file mode 100644 index 000000000000..c9ceef6959ad Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customcap.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png new file mode 100644 index 000000000000..1c0cbc22519a Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customwhisker.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a9eaa8a86239..638e4907095a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1353,6 +1353,36 @@ def test_bxp_custommedian(): ax.bxp(logstats, medianprops=medianprops) +@image_comparison(baseline_images=['bxp_customcap'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +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) + + +@image_comparison(baseline_images=['bxp_customwhisker'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +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) + + @image_comparison(baseline_images=['bxp_withnotch'], remove_text=True, extensions=['png'], savefig_kwarg={'dpi': 40}) @@ -1499,6 +1529,18 @@ def test_boxplot(): ax.set_ylim((-30, 30)) +@image_comparison(baseline_images=['boxplot_sym'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_boxplot_sym(): + x = np.linspace(-7, 7, 140) + x = np.hstack([-25, x, 25]) + fig, ax = plt.subplots() + + ax.boxplot([x, x], sym='gs') + ax.set_ylim((-30, 30)) + + @image_comparison(baseline_images=['boxplot_autorange_whiskers']) def test_boxplot_autorange_whiskers(): x = np.ones(140)