From c5afbfc28e035b5b428e133bca071ed0d5a7911b Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 5 Mar 2025 19:32:22 +0000 Subject: [PATCH] MNT: expire legend-related deprecations --- .../next_api_changes/behavior/29832-REC.rst | 11 +++++++ lib/matplotlib/axes/_base.py | 7 ---- lib/matplotlib/legend.py | 8 ++--- lib/matplotlib/tests/test_legend.py | 32 ++++++------------- 4 files changed, 24 insertions(+), 34 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/29832-REC.rst diff --git a/doc/api/next_api_changes/behavior/29832-REC.rst b/doc/api/next_api_changes/behavior/29832-REC.rst new file mode 100644 index 000000000000..a23b043a823b --- /dev/null +++ b/doc/api/next_api_changes/behavior/29832-REC.rst @@ -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. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 4b41cf462f80..c5c525b29a06 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -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 " diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index a6244b002006..d01a8dca0847 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -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 ---------- @@ -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 diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index e238e1f17124..577ceaf8fd0d 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -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] @@ -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(): @@ -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():