From 4ecbffbf8188209109ab2f1761b819f225a8e38c Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 25 Nov 2018 15:47:36 +0100 Subject: [PATCH] Add rcParam hist.edgecolor --- doc/api/next_api_changes/2018-11-25-TH.rst | 9 +++ lib/matplotlib/axes/_axes.py | 78 +++++++++++++++++----- lib/matplotlib/rcsetup.py | 8 +++ lib/matplotlib/tests/test_axes.py | 18 +++++ matplotlibrc.template | 2 + 5 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 doc/api/next_api_changes/2018-11-25-TH.rst diff --git a/doc/api/next_api_changes/2018-11-25-TH.rst b/doc/api/next_api_changes/2018-11-25-TH.rst new file mode 100644 index 000000000000..ab068f68c938 --- /dev/null +++ b/doc/api/next_api_changes/2018-11-25-TH.rst @@ -0,0 +1,9 @@ +New rcparams ``hist.edgecolor`` and ``hist.linewidth`` +`````````````````````````````````````````````````````` +The new rcparam ``hist.edgecolor`` allows to expicitly define an edge color +for the histogram bars. Previously, this could only be achieved by setting +``patch.edgecolor`` and ``patch.force_edgecolor``, which may have affected +other plot elements unwantedly. + +Additionally, the new rcparam ``hist.linewidth`` specifies the linewdith of +the bar edges. \ No newline at end of file diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 8bb104487702..5a2161c017ec 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6335,10 +6335,10 @@ def table(self, **kwargs): @_preprocess_data(replace_names=["x", 'weights'], label_namer="x") def hist(self, x, bins=None, range=None, density=None, weights=None, cumulative=False, bottom=None, histtype='bar', align='mid', - orientation='vertical', rwidth=None, log=False, - color=None, label=None, stacked=False, normed=None, + orientation='vertical', rwidth=None, log=False, color=None, + label=None, stacked=False, normed=None, **kwargs): - """ + r""" Plot a histogram. Compute and draw the histogram of *x*. The return value is a @@ -6440,7 +6440,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Default is ``None`` - histtype : {'bar', 'barstacked', 'step', 'stepfilled'}, optional + histtype : {'bar', 'barstacked', 'step', 'stepfilled'}, optional The type of histogram to draw. - 'bar' is a traditional bar-type histogram. If multiple data @@ -6488,12 +6488,6 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Default is ``False`` - color : color or array_like of colors or None, optional - Color spec or sequence of color specs, one per dataset. Default - (``None``) uses the standard line color sequence. - - Default is ``None`` - label : str or None, optional String, or sequence of strings to match multiple datasets. Bar charts yield multiple patches per dataset, but only the first gets @@ -6535,6 +6529,39 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, Other Parameters ---------------- + color : color or array_like of colors or None, optional + The meaning of the parameter depends on *histtype*: + + - For filled *histtype*\s ('bar', 'barstacked' and 'stepfilled'): + + The facecolor of the bars. If providing multiple datasets to + *x*, there must be one color per dataset. If *None* the + standard line color sequence is used. + + The *color* parameter is overridden if *facecolor* is given. + + - For *histtype* 'step': + + The linecolor of the step line. If None the standard line color + sequence is used. + + Default: *None* + + edgecolor : color or array_like of colors, optional + The edgecolor of the bars. If providing multiple datasets to + *x*, you can either pass a single edgecolor for all datasets or + one edgecolor per dataset. If not given, use :rc:`hist.edgecolor`. + If that is *None*, the default patch settings are used + (:rc:`patch.edgecolor` and :rc:`patch.force_edgecolor`). + + Note: *edgecolor* is ignored for *histtype* 'step'. Use *color* + in this case. + + linewidth : float, optional + The linewidth of the bar edges. If not given, use + :rc:`hist.linewidth`. If that is *None*, the default + :rc:`patch.linewidth` is used. + **kwargs : `~matplotlib.patches.Patch` properties See also @@ -6546,6 +6573,8 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, .. [Notes section required for data comment. See #10189.] """ + kwargs = cbook.normalize_kwargs(kwargs, mpatches.Patch._alias_map) + # Avoid shadowing the builtin. bin_range = range from builtins import range @@ -6619,10 +6648,25 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, else: color = mcolors.to_rgba_array(color) if len(color) != nx: - error_message = ( + raise ValueError( "color kwarg must have one color per data set. %d data " "sets and %d colors were provided" % (nx, len(color))) - raise ValueError(error_message) + + edgecolor = kwargs.pop('edgecolor', rcParams['hist.edgecolor']) + if edgecolor is None: + edgecolor = [None] * nx + else: + edgecolor = mcolors.to_rgba_array(edgecolor) + if len(edgecolor) == 1: + edgecolor = np.repeat(edgecolor, nx, axis=0) + elif len(edgecolor) != nx: + raise ValueError( + "edgecolor kwarg must have one color per data set. " + "%d data sets and %d colors were provided" % ( + nx, len(edgecolor))) + + if rcParams['hist.linewidth'] is not None: + kwargs.setdefault('linewidth', rcParams['hist.linewidth']) # If bins are not specified either explicitly or via range, # we need to figure out the range required for all datasets, @@ -6715,7 +6759,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, _barfunc = self.bar bottom_kwarg = 'bottom' - for m, c in zip(tops, color): + for m, c, ec in zip(tops, color, edgecolor): if bottom is None: bottom = np.zeros(len(m)) if stacked: @@ -6724,7 +6768,8 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, height = m patch = _barfunc(bins[:-1]+boffset, height, width, align='center', log=log, - color=c, **{bottom_kwarg: bottom}) + color=c, edgecolor=ec, + **{bottom_kwarg: bottom}) patches.append(patch) if stacked: bottom[:] = m @@ -6805,12 +6850,13 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, # add patches in reverse order so that when stacking, # items lower in the stack are plotted on top of # items higher in the stack - for x, y, c in reversed(list(zip(xvals, yvals, color))): + for x, y, c, ec in reversed(list(zip(xvals, yvals, color, + edgecolor))): patches.append(self.fill( x[:split], y[:split], closed=True if fill else None, facecolor=c, - edgecolor=None if fill else c, + edgecolor=ec if fill else c, fill=fill if fill else None)) for patch_list in patches: for patch in patch_list: diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 6271119f6583..1c885af0b11f 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -353,6 +353,12 @@ def validate_color_or_auto(s): return validate_color(s) +def validate_color_or_None(s): + if s is None: + return s + return validate_color(s) + + def validate_color_for_prop_cycle(s): # Special-case the N-th color cycle syntax, this obviously can not # go in the color cycle. @@ -1050,6 +1056,8 @@ def _validate_linestyle(ls): ## Histogram properties 'hist.bins': [10, validate_hist_bins], + 'hist.edgecolor': [None, validate_color_or_None], + 'hist.linewidth': [None, validate_float_or_None], ## Boxplot properties 'boxplot.notch': [False, validate_bool], diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f5007fbe8869..8014a463aa44 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3168,6 +3168,24 @@ def test_hist_labels(): assert l[2][0].get_label() == '00' +@check_figures_equal(extensions=['png']) +def test_hist_bar_rc(fig_test, fig_ref): + with plt.rc_context({'hist.edgecolor': 'darkblue', + 'hist.linewidth': 5}): + fig_test.subplots().hist([1, 5, 3], histtype='bar') + fig_ref.subplots().hist([1, 5, 3], histtype='bar', + edgecolor='darkblue', linewidth=5) + + +@check_figures_equal(extensions=['png']) +def test_hist_stepfilled_rc(fig_test, fig_ref): + with plt.rc_context({'hist.edgecolor': 'darkblue', + 'hist.linewidth': 5}): + fig_test.subplots().hist([1, 5, 3], histtype='stepfilled') + fig_ref.subplots().hist([1, 5, 3], histtype='stepfilled', + edgecolor='darkblue', linewidth=5) + + @image_comparison(baseline_images=['transparent_markers'], remove_text=True) def test_transparent_markers(): np.random.seed(0) diff --git a/matplotlibrc.template b/matplotlibrc.template index c79f81f81bc6..45edad795d75 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -485,6 +485,8 @@ #hist.bins : 10 ## The default number of histogram bins. ## If Numpy 1.11 or later is ## installed, may also be `auto` +#hist.edgecolor : None ## The edge color of the histogram bars. +#hist.linewidth : None ## The line width of the histogram bars. #### SCATTER PLOTS #scatter.marker : o ## The default marker type for scatter plots.