From 46286fa1b67252cb17e1c25464b2cf1c3adcd7a7 Mon Sep 17 00:00:00 2001 From: Jake <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:54:58 +0100 Subject: [PATCH 1/7] ENH: Change logging to warning when creating a legend with no labels Fixes #27145 Also add test_legend_nolabels_warning() --- lib/matplotlib/legend.py | 2 +- lib/matplotlib/tests/test_legend.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 58e5dfcfe3b8..fa43a31d7c17 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1358,7 +1358,7 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): elif len(args) == 0: # 0 args: automatically detect labels and handles. handles, labels = _get_legend_handles_labels(axs, handlers) if not handles: - log.warning( + _api.warn_external( "No artists with labels found to put in legend. Note that " "artists whose label start with an underscore are ignored " "when legend() is called with no argument.") diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 3a35b4051c71..59e82339b0ef 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1375,3 +1375,9 @@ def test_legend_handle_label_mismatch_no_len(): labels=iter(["pl1", "pl2", "pl3"])) assert len(legend.legend_handles) == 2 assert len(legend.get_texts()) == 2 + + +def test_legend_nolabels_warning(): + plt.plot([1, 2, 3]) + with pytest.raises(UserWarning): + plt.legend() From f53b9d94ab6537a549257cfaa5412e49a8431f41 Mon Sep 17 00:00:00 2001 From: Jake <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:07:46 +0100 Subject: [PATCH 2/7] TST: Add mock labels to mock data. --- lib/matplotlib/tests/test_legend.py | 76 +++++++++++++++-------------- lib/matplotlib/tests/test_pickle.py | 2 +- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 59e82339b0ef..2f8cdc827570 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -636,6 +636,7 @@ def test_handler_numpoints(): def test_text_nohandler_warning(): """Test that Text artists with labels raise a warning""" fig, ax = plt.subplots() + ax.plot([0], label="mock data") ax.text(x=0, y=0, s="text", label="label") with pytest.warns(UserWarning) as record: ax.legend() @@ -703,7 +704,7 @@ def test_legend_title_empty(): # it comes back as an empty string, and that it is not # visible: fig, ax = plt.subplots() - ax.plot(range(10)) + ax.plot(range(10), label="mock data") leg = ax.legend() assert leg.get_title().get_text() == "" assert not leg.get_title().get_visible() @@ -736,7 +737,7 @@ def test_window_extent_cached_renderer(): def test_legend_title_fontprop_fontsize(): # test the title_fontsize kwarg - plt.plot(range(10)) + plt.plot(range(10), label="mock data") with pytest.raises(ValueError): plt.legend(title='Aardvark', title_fontsize=22, title_fontproperties={'family': 'serif', 'size': 22}) @@ -747,27 +748,27 @@ def test_legend_title_fontprop_fontsize(): fig, axes = plt.subplots(2, 3, figsize=(10, 6)) axes = axes.flat - axes[0].plot(range(10)) + axes[0].plot(range(10), label="mock data") leg0 = axes[0].legend(title='Aardvark', title_fontsize=22) assert leg0.get_title().get_fontsize() == 22 - axes[1].plot(range(10)) + axes[1].plot(range(10), label="mock data") leg1 = axes[1].legend(title='Aardvark', title_fontproperties={'family': 'serif', 'size': 22}) assert leg1.get_title().get_fontsize() == 22 - axes[2].plot(range(10)) + axes[2].plot(range(10), label="mock data") mpl.rcParams['legend.title_fontsize'] = None leg2 = axes[2].legend(title='Aardvark', title_fontproperties={'family': 'serif'}) assert leg2.get_title().get_fontsize() == mpl.rcParams['font.size'] - axes[3].plot(range(10)) + axes[3].plot(range(10), label="mock data") leg3 = axes[3].legend(title='Aardvark') assert leg3.get_title().get_fontsize() == mpl.rcParams['font.size'] - axes[4].plot(range(10)) + axes[4].plot(range(10), label="mock data") mpl.rcParams['legend.title_fontsize'] = 20 leg4 = axes[4].legend(title='Aardvark', title_fontproperties={'family': 'serif'}) assert leg4.get_title().get_fontsize() == 20 - axes[5].plot(range(10)) + axes[5].plot(range(10), label="mock data") leg5 = axes[5].legend(title='Aardvark') assert leg5.get_title().get_fontsize() == 20 @@ -1072,7 +1073,7 @@ def test_legend_labelcolor_rcparam_markerfacecolor_short(): def test_get_set_draggable(): - legend = plt.legend() + legend = plt.legend(labels=["mock data"]) assert not legend.get_draggable() legend.set_draggable(True) assert legend.get_draggable() @@ -1289,74 +1290,75 @@ def test_loc_invalid_tuple_exception(): fig, ax = plt.subplots() with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\(1.1,\\)')): - ax.legend(loc=(1.1, )) + ax.legend(loc=(1.1, ), labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\(0.481, 0.4227, 0.4523\\)')): - ax.legend(loc=(0.481, 0.4227, 0.4523)) + ax.legend(loc=(0.481, 0.4227, 0.4523), labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\(0.481, \'go blue\'\\)')): - ax.legend(loc=(0.481, "go blue")) + ax.legend(loc=(0.481, "go blue"), labels=["mock data"]) def test_loc_valid_tuple(): fig, ax = plt.subplots() - ax.legend(loc=(0.481, 0.442)) - ax.legend(loc=(1, 2)) + ax.legend(loc=(0.481, 0.442), labels=["mock data"]) + ax.legend(loc=(1, 2), labels=["mock data"]) def test_loc_valid_list(): fig, ax = plt.subplots() - ax.legend(loc=[0.481, 0.442]) - ax.legend(loc=[1, 2]) + ax.legend(loc=[0.481, 0.442], labels=["mock data"]) + ax.legend(loc=[1, 2], labels=["mock data"]) def test_loc_invalid_list_exception(): fig, ax = plt.subplots() with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not \\[1.1, 2.2, 3.3\\]')): - ax.legend(loc=[1.1, 2.2, 3.3]) + ax.legend(loc=[1.1, 2.2, 3.3], labels=["mock data"]) def test_loc_invalid_type(): fig, ax = plt.subplots() with pytest.raises(ValueError, match=("loc must be string, coordinate " "tuple, or an integer 0-10, not {'not': True}")): - ax.legend(loc={'not': True}) + ax.legend(loc={'not': True}, labels=["mock data"]) def test_loc_validation_numeric_value(): fig, ax = plt.subplots() - ax.legend(loc=0) - ax.legend(loc=1) - ax.legend(loc=5) - ax.legend(loc=10) + ax.legend(loc=0, labels=["mock data"]) + ax.legend(loc=1, labels=["mock data"]) + ax.legend(loc=5, labels=["mock data"]) + ax.legend(loc=10, labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not 11')): - ax.legend(loc=11) + ax.legend(loc=11, labels=["mock data"]) with pytest.raises(ValueError, match=('loc must be string, coordinate ' 'tuple, or an integer 0-10, not -1')): - ax.legend(loc=-1) + ax.legend(loc=-1, labels=["mock data"]) def test_loc_validation_string_value(): fig, ax = plt.subplots() - ax.legend(loc='best') - ax.legend(loc='upper right') - ax.legend(loc='best') - ax.legend(loc='upper right') - ax.legend(loc='upper left') - ax.legend(loc='lower left') - ax.legend(loc='lower right') - ax.legend(loc='right') - ax.legend(loc='center left') - ax.legend(loc='center right') - ax.legend(loc='lower center') - ax.legend(loc='upper center') + labels = ["mock data"] + ax.legend(loc='best', labels=labels) + ax.legend(loc='upper right', labels=labels) + ax.legend(loc='best', labels=labels) + ax.legend(loc='upper right', labels=labels) + ax.legend(loc='upper left', labels=labels) + ax.legend(loc='lower left', labels=labels) + ax.legend(loc='lower right', labels=labels) + ax.legend(loc='right', labels=labels) + ax.legend(loc='center left', labels=labels) + ax.legend(loc='center right', labels=labels) + ax.legend(loc='lower center', labels=labels) + ax.legend(loc='upper center', labels=labels) with pytest.raises(ValueError, match="'wrong' is not a valid value for"): - ax.legend(loc='wrong') + ax.legend(loc='wrong', labels=labels) def test_legend_handle_label_mismatch(): diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index e222a495e437..cab412bff561 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -90,7 +90,7 @@ def _generate_complete_test_figure(fig_ref): plt.legend(loc='upper left') plt.subplot(3, 3, 9) - plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4) + plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4, label='$-.5 x$') plt.legend(draggable=True) fig_ref.align_ylabels() # Test handling of _align_label_groups Groupers. From a413a0291040986956028b100a7646d3586c279c Mon Sep 17 00:00:00 2001 From: Jake <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Tue, 24 Oct 2023 12:24:04 +0100 Subject: [PATCH 3/7] DOC: Note warning in legend() documentation --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0fcabac8c7c0..b6935ddbac0a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -239,8 +239,8 @@ def legend(self, *args, **kwargs): selection by using a label starting with an underscore, "_". A string starting with an underscore is the default label for all artists, so calling `.Axes.legend` without any arguments and - without setting the labels manually will result in no legend being - drawn. + without setting the labels manually will result in a ``UserWarning`` + and no legend being drawn. **2. Explicitly listing the artists and labels in the legend** From bc1054b855cdc0a94c6d939cbfb8c23f0aafb551 Mon Sep 17 00:00:00 2001 From: Jake <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Tue, 24 Oct 2023 12:25:09 +0100 Subject: [PATCH 4/7] DOC: Correct examples to no longer raise UserWarning With modified explanation where appropriate --- .../examples/text_labels_and_annotations/custom_legends.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/galleries/examples/text_labels_and_annotations/custom_legends.py b/galleries/examples/text_labels_and_annotations/custom_legends.py index f1714a21a481..18ace0513228 100644 --- a/galleries/examples/text_labels_and_annotations/custom_legends.py +++ b/galleries/examples/text_labels_and_annotations/custom_legends.py @@ -28,6 +28,7 @@ # Fixing random state for reproducibility np.random.seed(19680801) +# %% N = 10 data = (np.geomspace(1, 10, 100) + np.random.randn(N, 100)).T cmap = plt.cm.coolwarm @@ -35,10 +36,10 @@ fig, ax = plt.subplots() lines = ax.plot(data) -ax.legend() # %% -# Note that no legend entries were created. +# Since the data does not have any labels, creating a legend requires +# us to define the icons and labels. # In this case, we can compose a legend using Matplotlib objects that aren't # explicitly tied to the data that was plotted. For example: From 9f80d5598d0ffa101e8db0968788290caa72a105 Mon Sep 17 00:00:00 2001 From: Jake <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:43:40 +0100 Subject: [PATCH 5/7] TST: Add test for drawing empty legend() --- lib/matplotlib/tests/test_legend.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 2f8cdc827570..422c85b37ba3 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1383,3 +1383,10 @@ def test_legend_nolabels_warning(): plt.plot([1, 2, 3]) with pytest.raises(UserWarning): plt.legend() + + +def test_legend_nolabels_draw(): + plt.plot([1, 2, 3]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + plt.legend() From 04e0cebfac3b1cb96a4337365ce35e6f8fd76c99 Mon Sep 17 00:00:00 2001 From: Jake <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:02:24 +0100 Subject: [PATCH 6/7] TST: Test legend for specific warning when no labels present Also: DOC: Correct Axes docstring when no labels in legend. --- lib/matplotlib/axes/_axes.py | 2 +- lib/matplotlib/tests/test_legend.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b6935ddbac0a..8ed6e10770e8 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -240,7 +240,7 @@ def legend(self, *args, **kwargs): A string starting with an underscore is the default label for all artists, so calling `.Axes.legend` without any arguments and without setting the labels manually will result in a ``UserWarning`` - and no legend being drawn. + and an empty legend being drawn. **2. Explicitly listing the artists and labels in the legend** diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 422c85b37ba3..5ff3ab9d68b9 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1381,7 +1381,7 @@ def test_legend_handle_label_mismatch_no_len(): def test_legend_nolabels_warning(): plt.plot([1, 2, 3]) - with pytest.raises(UserWarning): + with pytest.raises(UserWarning, match="No artists with labels found"): plt.legend() From dbe82fe1a4a7d5b33a4d4dd5e66e66ed4f242f22 Mon Sep 17 00:00:00 2001 From: Jake Stevens-Haas <37048747+Jacob-Stevens-Haas@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:11:43 +0000 Subject: [PATCH 7/7] TST: Filter no labels warning by message. Also assert that a legend is at least created --- lib/matplotlib/tests/test_legend.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 5ff3ab9d68b9..5a64df78fbc9 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1072,8 +1072,9 @@ def test_legend_labelcolor_rcparam_markerfacecolor_short(): assert mpl.colors.same_color(text.get_color(), color) +@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") def test_get_set_draggable(): - legend = plt.legend(labels=["mock data"]) + legend = plt.legend() assert not legend.get_draggable() legend.set_draggable(True) assert legend.get_draggable() @@ -1385,8 +1386,8 @@ def test_legend_nolabels_warning(): plt.legend() +@pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") def test_legend_nolabels_draw(): plt.plot([1, 2, 3]) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - plt.legend() + plt.legend() + assert plt.gca().get_legend() is not None