diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 6102fd3d3ed3..3ff77f1308b5 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3288,6 +3288,20 @@ def grouped_bar(self, heights, *, positions=None, group_spacing=1.5, bar_spacing boxplot(values, positions=..., tick_labels=...) """ + # grouped_bar() controls the label and color of each dataset's bars via the + # dedicated *labels* and *colors* parameters, and passes them positionally to + # bar(). A 'label' or 'color' in kwargs would therefore be passed to bar() + # twice, raising an opaque "multiple values for keyword argument" TypeError. + # Reject them early with an actionable message pointing to the plural + # parameters (see #30739). + for kwarg, alternative in [("color", "colors"), ("label", "labels")]: + if kwarg in kwargs: + raise TypeError( + f"grouped_bar() got an unexpected keyword argument '{kwarg}'. " + f"grouped_bar() sets the {kwarg} of each dataset's bars " + f"internally; use '{alternative}' to control the per-dataset " + f"{kwarg}s.") + if cbook._is_pandas_dataframe(heights): if labels is None: labels = heights.columns.tolist() diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3189076c7655..84f30258e1ac 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2315,6 +2315,20 @@ def test_grouped_bar_hatch_sequence(): assert rect.get_hatch() == hatch +@pytest.mark.parametrize("kwarg, alternative", [("label", "labels"), + ("color", "colors")]) +def test_grouped_bar_reject_singular_label_color(kwarg, alternative): + # grouped_bar() passes the per-dataset label/color to bar() itself, so a + # 'label' or 'color' in kwargs would be passed twice and raise an opaque + # "multiple values for keyword argument" TypeError. It should instead fail + # early with a message pointing to the plural parameter. See #30739. + fig, ax = plt.subplots() + heights = [np.array([1, 2]), np.array([2, 3])] + with pytest.raises(TypeError, match=f"unexpected keyword argument '{kwarg}'.*" + f"use '{alternative}'"): + ax.grouped_bar(heights, **{kwarg: "dataset"}) + + def test_boxplot_dates_pandas(pd): # smoke test for boxplot and dates in pandas data = np.random.rand(5, 2)