From 3dfcb519887a9f24de6fdee9ce7d60ff5ca888de Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 13 Feb 2024 00:05:59 +0100 Subject: [PATCH] Partly revert #27711 This PR removes the propagation of `labels` to any artist legend labels. Other than the rest of the plotting functions `labels` is not used for legend labels but for xtick labels. This is only poorly documented via https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.bxp.html and in an [example](https://matplotlib.org/stable/gallery/statistics/boxplot_color.html). Whatever our way forward regarding the use of `labels` is, we should by no means propagate them simultaneously to xticks and legend entries. This coupling would cripple users' configurability and limit our ability to migrate to a clear API where legend labels and tick labels can be configured independently. Until we have sorted out a better API, the recommended solution for the original issue #20512 is to grab the artists returned from `boxplot()` and either `set_label()` on them or pass them to the legend call `ax.legend(handles, labels)`. --- lib/matplotlib/axes/_axes.py | 6 ++--- lib/matplotlib/tests/test_legend.py | 41 +++++++++-------------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b1aeb87e6b45..9eaf2dc331ed 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3885,7 +3885,8 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, boxes are drawn with Patch artists. labels : sequence, optional - Labels for each dataset (one per dataset). + Labels for each dataset (one per dataset). These are used for + x-tick labels; *not* for legend entries. manage_ticks : bool, default: True If True, the tick locations and labels will be adjusted to match @@ -4004,9 +4005,6 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, if 'color' in boxprops: boxprops['edgecolor'] = boxprops.pop('color') - if labels: - boxprops['label'] = labels - # if non-default sym value, put it into the flier dictionary # the logic for providing the default symbol ('b+') now lives # in bxp in the initial value of flierkw diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 820f9d967f24..b23649f22e48 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1429,31 +1429,16 @@ def test_legend_text(): assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds) -def test_boxplot_legend(): - # Test that boxplot legends handles are patches - # and labels are generated from boxplot's labels parameter. - fig, axs = plt.subplots() - A = 5*np.random.rand(100, 1) - B = 10*np.random.rand(100, 1) - 5 - C = 7*np.random.rand(100, 1) - 5 - labels = ['a', 'b', 'c'] - - bp0 = axs.boxplot(A, positions=[0], patch_artist=True, labels=labels[0]) - bp1 = axs.boxplot(B, positions=[1], patch_artist=True, labels=labels[1]) - bp2 = axs.boxplot(C, positions=[2], patch_artist=True, labels=labels[2]) - # red, blue, green - colors = [(1.0, 0.0, 0.0, 1), (0.0, 0.0, 1.0, 1), (0.0, 0.5, 0.0, 1)] - box_list = [bp0, bp1, bp2] - # Set colors to the boxes - lbl_index = 0 - for b_plot, color in zip(box_list, colors): - for patch in b_plot['boxes']: - patch.set_color(color) - lbl_index += 1 - - legend = axs.legend() - for index, handle in enumerate(legend.legend_handles): - assert isinstance(handle, mpl.patches.Rectangle) - assert handle.get_facecolor() == colors[index] - assert handle.get_edgecolor() == colors[index] - assert handle.get_label() == labels[index] +def test_boxplot_labels(): + # Test that boxplot(..., labels=) sets the tick labels but not legend entries + # This is not consistent with other plot types but is the current behavior. + fig, ax = plt.subplots() + np.random.seed(19680801) + data = np.random.random((10, 3)) + bp = ax.boxplot(data, labels=['A', 'B', 'C']) + # Check that labels set the tick labels ... + assert [l.get_text() for l in ax.get_xticklabels()] == ['A', 'B', 'C'] + # ... but not legend entries + handles, labels = ax.get_legend_handles_labels() + assert len(handles) == 0 + assert len(labels) == 0