Skip to content

MNT: expire legend-related deprecations #29832

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 1 commit into from
Mar 31, 2025
Merged
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
11 changes: 11 additions & 0 deletions doc/api/next_api_changes/behavior/29832-REC.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Mixing positional and keyword arguments for ``legend`` handles and labels...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... is no longer valid. If passing *handles* and *labels* to ``legend``, they must
now be passed either both positionally or both as keywords.

Legend labels for ``plot``
~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously if a sequence was passed to the *label* parameter of `~.Axes.plot` when
plotting a single dataset, the sequence was automatically cast to string for the legend
label. Now, if the sequence length is not one an error is raised. To keep the old
behavior, cast the sequence to string before passing.
7 changes: 0 additions & 7 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,6 @@ def _plot_args(self, axes, tup, kwargs, *,
labels = [label] * n_datasets
elif len(label) == n_datasets:
labels = label
elif n_datasets == 1:
msg = (f'Passing label as a length {len(label)} sequence when '
'plotting a single dataset is deprecated in Matplotlib 3.9 '
'and will error in 3.11. To keep the current behavior, '
'cast the sequence to string before passing.')
_api.warn_deprecated('3.9', message=msg)
labels = [label]
else:
raise ValueError(
f"label must be scalar or have the same length as the input "
Expand Down
8 changes: 3 additions & 5 deletions lib/matplotlib/legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs):
legend(handles=handles, labels=labels)

The behavior for a mixture of positional and keyword handles and labels
is undefined and issues a warning; it will be an error in the future.
is undefined and raises an error.

Parameters
----------
Expand Down Expand Up @@ -1319,10 +1319,8 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs):
handlers = kwargs.get('handler_map')

if (handles is not None or labels is not None) and args:
_api.warn_deprecated("3.9", message=(
"You have mixed positional and keyword arguments, some input may "
"be discarded. This is deprecated since %(since)s and will "
"become an error in %(removal)s."))
raise TypeError("When passing handles and labels, they must both be "
"passed positionally or both as keywords.")

if (hasattr(handles, "__len__") and
hasattr(labels, "__len__") and
Expand Down
32 changes: 10 additions & 22 deletions lib/matplotlib/tests/test_legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,17 +390,14 @@ def test_legend_kwargs_handles_labels(self):
ax.legend(labels=('a', 'b'), handles=(lnc, lns))
Legend.assert_called_with(ax, (lnc, lns), ('a', 'b'))

def test_warn_mixed_args_and_kwargs(self):
def test_error_mixed_args_and_kwargs(self):
fig, ax = plt.subplots()
th = np.linspace(0, 2*np.pi, 1024)
lns, = ax.plot(th, np.sin(th), label='sin')
lnc, = ax.plot(th, np.cos(th), label='cos')
with pytest.warns(DeprecationWarning) as record:
msg = 'must both be passed positionally or both as keywords'
with pytest.raises(TypeError, match=msg):
ax.legend((lnc, lns), labels=('a', 'b'))
assert len(record) == 1
assert str(record[0].message).startswith(
"You have mixed positional and keyword arguments, some input may "
"be discarded.")

def test_parasite(self):
from mpl_toolkits.axes_grid1 import host_subplot # type: ignore[import]
Expand Down Expand Up @@ -460,16 +457,13 @@ def test_legend_kw_args(self):
fig, (lines, lines2), ('a', 'b'), loc='right',
bbox_transform=fig.transFigure)

def test_warn_args_kwargs(self):
def test_error_args_kwargs(self):
fig, axs = plt.subplots(1, 2)
lines = axs[0].plot(range(10))
lines2 = axs[1].plot(np.arange(10) * 2.)
with pytest.warns(DeprecationWarning) as record:
msg = 'must both be passed positionally or both as keywords'
with pytest.raises(TypeError, match=msg):
fig.legend((lines, lines2), labels=('a', 'b'))
assert len(record) == 1
assert str(record[0].message).startswith(
"You have mixed positional and keyword arguments, some input may "
"be discarded.")


def test_figure_legend_outside():
Expand Down Expand Up @@ -1178,21 +1172,15 @@ def test_plot_multiple_input_single_label(label):
assert legend_texts == [str(label)] * 2


@pytest.mark.parametrize('label_array', [['low', 'high'],
('low', 'high'),
np.array(['low', 'high'])])
def test_plot_single_input_multiple_label(label_array):
def test_plot_single_input_multiple_label():
# test ax.plot() with 1D array like input
# and iterable label
x = [1, 2, 3]
y = [2, 5, 6]
fig, ax = plt.subplots()
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='Passing label as a length 2 sequence'):
ax.plot(x, y, label=label_array)
leg = ax.legend()
assert len(leg.get_texts()) == 1
assert leg.get_texts()[0].get_text() == str(label_array)
with pytest.raises(ValueError,
match='label must be scalar or have the same length'):
ax.plot(x, y, label=['low', 'high'])


def test_plot_single_input_list_label():
Expand Down
Loading