Skip to content

Add rcParams hist.edgecolor, hist.linewidth #12884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions doc/api/next_api_changes/2018-11-25-TH.rst
Original file line number Diff line number Diff line change
@@ -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.
78 changes: 62 additions & 16 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or is it broadcasted? (dunno)

Copy link
Member Author

@timhoffm timhoffm Feb 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, the description is correct:

        if color is None:
            ...
        else:
            color = mcolors.to_rgba_array(color)
            if len(color) != nx:
                raise ValueError(
                    "color kwarg must have one color per data set. %d data "

We could broadcast, but that's beyond this PR.

standard line color sequence is used.

The *color* parameter is overridden if *facecolor* is given.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the facecolor argument is mentioned nowhere else I think?


- 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`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... unless histtype=step in which case it's a single color?

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that fallback true even for histtype=step?


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess kwargs (just below) Patch properties if histtype = bar but Line2D properties if histtype = step?

I think the discussion above about color/edgecolor/facecolor is quite confusing. I would suggest instead moving everything under the doc entry for **kwargs (which should read "Patch properties if histtype=bar; Line2D properties if histtype=step") and then have an additional paragraph like (haven't checked factual accuracy...)

Color specification follows the underlying artist type.
If using Patches (histtype=bar), the patch edge colors can be set using edgecolors (a single or list of colors), the patch face colors can be set using facecolors (...), and both can be simultaneously set using color (...); the defaults are ...
If using Line2D (histtype=step), ...

(and whatever's right for stepfilled too...)

**kwargs : `~matplotlib.patches.Patch` properties

See also
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets into the color, facecolor, edgecolor confusion. Probably should be clear in the doc of color what the color means, and it'd be nice if it was consistent w/ the other instances of this. (Yes this ambiguity predated this PR due to the fact that patch kwargs are passed through to patch).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into linewidth and the color descriptions.

It's even a bit worse since the meaning of color depends on the histtype. For histtype='bar' and histtype='stepfilled' it's the facecolor. For histtype='step' it's the edgecolor and the edgecolor is ignored. Even though, this behavior is confusing, I intentionally did not touch it in this PR to keep backward compatibility.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough but the color doc string could be made more specific. When we didn’t have an edgecolor kwarg we could assume some expertise in the part of the user who were able to drill down to the patch kwargs. But with that kwarg it’s going to lead to confusion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to work in it.

Copy link
Member Author

@timhoffm timhoffm Dec 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked the documentation of the color parameter.

**{bottom_kwarg: bottom})
patches.append(patch)
if stacked:
bottom[:] = m
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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],
Expand Down
18 changes: 18 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down