diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index af5173d2503e..01b058a3ed7c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -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", @@ -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. @@ -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) @@ -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: @@ -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( diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ee6f715c3ccd..0dded0515ba7 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -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')