Skip to content

Creating_parse_bar_color_args to unify color handling in plt.bar with precedence and sequence support for facecolor and edgecolor #29133

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

Merged
merged 14 commits into from
Nov 30, 2024
69 changes: 61 additions & 8 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2321,6 +2321,56 @@
dx = convert(dx)
return dx

def _parse_bar_color_args(self, kwargs):
"""
Helper function to process color-related arguments of `.Axes.bar`.

Argument precedence for facecolors:

- kwargs['facecolor']
- kwargs['color']
- 'Result of ``self._get_patches_for_fill.get_next_color``

Argument precedence for edgecolors:

- kwargs['edgecolor']
- None

Parameters
----------
self : Axes

kwargs : dict
Additional kwargs. If these keys exist, we pop and process them:
'facecolor', 'edgecolor', 'color'
Note: The dict is modified by this function.


Returns
-------
facecolor
The facecolor. One or more colors as (N, 4) rgba array.
edgecolor
The edgecolor. Not normalized; may be any valid color spec or None.
"""
color = kwargs.pop('color', None)

facecolor = kwargs.pop('facecolor', color)
edgecolor = kwargs.pop('edgecolor', None)

facecolor = (facecolor if facecolor is not None
else self._get_patches_for_fill.get_next_color())

try:
facecolor = mcolors.to_rgba_array(facecolor)
except ValueError as err:
raise ValueError(

Check warning on line 2367 in lib/matplotlib/axes/_axes.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/axes/_axes.py#L2366-L2367

Added lines #L2366 - L2367 were not covered by tests
"'facecolor' or 'color' argument must be a valid color or"
"sequence of colors."
) from err

return facecolor, edgecolor

@_preprocess_data()
@_docstring.interpd
def bar(self, x, height, width=0.8, bottom=None, *, align="center",
Expand Down Expand Up @@ -2376,7 +2426,12 @@
Other Parameters
----------------
color : :mpltype:`color` or list of :mpltype:`color`, optional
The colors of the bar faces. This is an alias for *facecolor*.
If both are given, *facecolor* takes precedence.

facecolor : :mpltype:`color` or list of :mpltype:`color`, optional
The colors of the bar faces.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
The colors of the bar faces.
The colors of the bar faces.
If both *color* and *facecolor are given, *facecolor* takes precedence.

If both *color* and *facecolor are given, *facecolor* takes precedence.

edgecolor : :mpltype:`color` or list of :mpltype:`color`, optional
The colors of the bar edges.
Expand Down Expand Up @@ -2441,10 +2496,8 @@
bar. See :doc:`/gallery/lines_bars_and_markers/bar_stacked`.
"""
kwargs = cbook.normalize_kwargs(kwargs, mpatches.Patch)
color = kwargs.pop('color', None)
if color is None:
color = self._get_patches_for_fill.get_next_color()
edgecolor = kwargs.pop('edgecolor', None)
facecolor, edgecolor = self._parse_bar_color_args(kwargs)

linewidth = kwargs.pop('linewidth', None)
hatch = kwargs.pop('hatch', None)

Expand Down Expand Up @@ -2540,9 +2593,9 @@

linewidth = itertools.cycle(np.atleast_1d(linewidth))
hatch = itertools.cycle(np.atleast_1d(hatch))
color = itertools.chain(itertools.cycle(mcolors.to_rgba_array(color)),
# Fallback if color == "none".
itertools.repeat('none'))
facecolor = itertools.chain(itertools.cycle(facecolor),
# Fallback if color == "none".
itertools.repeat('none'))
if edgecolor is None:
edgecolor = itertools.repeat(None)
else:
Expand Down Expand Up @@ -2576,7 +2629,7 @@
bottom = y

patches = []
args = zip(left, bottom, width, height, color, edgecolor, linewidth,
args = zip(left, bottom, width, height, facecolor, edgecolor, linewidth,
hatch, patch_labels)
for l, b, w, h, c, e, lw, htch, lbl in args:
r = mpatches.Rectangle(
Expand Down
25 changes: 25 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9452,3 +9452,28 @@ def test_wrong_use_colorizer():
for kwrd in kwrds:
with pytest.raises(ValueError, match=match_str):
fig.figimage(c, colorizer=cl, **kwrd)


def test_bar_color_precedence():
# Test the precedence of 'color' and 'facecolor' in bar plots
fig, ax = plt.subplots()

# case 1: no color specified
bars = ax.bar([1, 2, 3], [4, 5, 6])
for bar in bars:
assert mcolors.same_color(bar.get_facecolor(), 'blue')

# case 2: Only 'color'
bars = ax.bar([11, 12, 13], [4, 5, 6], color='red')
for bar in bars:
assert mcolors.same_color(bar.get_facecolor(), 'red')

# case 3: Only 'facecolor'
bars = ax.bar([21, 22, 23], [4, 5, 6], facecolor='yellow')
for bar in bars:
assert mcolors.same_color(bar.get_facecolor(), 'yellow')

# case 4: 'facecolor' and 'color'
bars = ax.bar([31, 32, 33], [4, 5, 6], color='red', facecolor='green')
for bar in bars:
assert mcolors.same_color(bar.get_facecolor(), 'green')
Loading