Skip to content

Commit

Permalink
Backport PR matplotlib#29133: Creating_parse_bar_color_args to unify …
Browse files Browse the repository at this point in the history
…color handling in plt.bar with precedence and sequence support for facecolor and edgecolor
  • Loading branch information
Lucx33 authored and meeseeksmachine committed Nov 30, 2024
1 parent 53a9605 commit 71c55ab
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 8 deletions.
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 @@ def _convert_dx(dx, x0, xconv, convert):
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(
"'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 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
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.
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 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
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 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",

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 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
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 @@ -9451,3 +9451,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')

0 comments on commit 71c55ab

Please sign in to comment.