From 8998cbc8ef4aaa8a1a48203770a48d4b01e75608 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Fri, 7 Oct 2022 20:23:31 -0500 Subject: [PATCH 1/7] Add textcolor to legend based on labelcolor string As raised by #20577, setting `labelcolor` to any of the string options did not work with a scatter plot as it is a PathCollection with possible multiple color values. This commit fixes that. Now, if there is a Colormap or a scatter plot with multiple values, then, the legend text color is not changed, but otherwise, the text color is changed to the color of the scatter markers. --- lib/matplotlib/legend.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 20d4a76e0db3..0ce49878977e 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -562,10 +562,24 @@ def val_or_rc(val, rc_name): if isinstance(labelcolor, str) and labelcolor in color_getters: getter_names = color_getters[labelcolor] for handle, text in zip(self.legendHandles, self.texts): + try: + if isinstance(handle.cmap, colors.LinearSegmentedColormap): + continue + except AttributeError: + pass for getter_name in getter_names: try: color = getattr(handle, getter_name)() - text.set_color(color) + if isinstance(color, np.ndarray): + if ( + color.shape[0] == 1 + or np.isclose(color, color[0]).all() + ): + text.set_color(color[0]) + else: + pass + else: + text.set_color(color) break except AttributeError: pass From b417a29d46c7469eca1dfe98ffe91ed0bd7e781c Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Sun, 9 Oct 2022 16:03:08 -0500 Subject: [PATCH 2/7] Use handle.get_array to get mappable information Due to inconsistencies in the colormap setup, it is suggested to instead check for `get_array` to get an array of values to be color mapped. This check is more robust irrespective of whatever the type of colormap is set to be. --- lib/matplotlib/legend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 0ce49878977e..d0590824ad84 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -563,7 +563,7 @@ def val_or_rc(val, rc_name): getter_names = color_getters[labelcolor] for handle, text in zip(self.legendHandles, self.texts): try: - if isinstance(handle.cmap, colors.LinearSegmentedColormap): + if handle.get_array() is not None: continue except AttributeError: pass From 66ec3a08a71dc9f9a5df07580ba84aa7b7a4a4b0 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Sun, 9 Oct 2022 16:09:15 -0500 Subject: [PATCH 3/7] Add tests for legend with str args in scatter Tests are added to check various conditions for the text color for a legend set with `linecolor`, `markeredgecolor`, `markerfacecolor` in a scatter plot. --- lib/matplotlib/tests/test_legend.py | 126 ++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index c0c5f79d71d8..a7674567c0bb 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -671,6 +671,47 @@ def test_legend_labelcolor_linecolor(): assert mpl.colors.same_color(text.get_color(), color) +def test_legend_pathcollection_labelcolor_linecolor(): + # test the labelcolor for labelcolor='linecolor' on PathCollection + fig, ax = plt.subplots() + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c='r') + ax.scatter(np.arange(10), np.arange(10)*2, label='#2', c='g') + ax.scatter(np.arange(10), np.arange(10)*3, label='#3', c='b') + + leg = ax.legend(labelcolor='linecolor') + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_linecolor_iterable(): + # test the labelcolor for labelcolor='linecolor' on PathCollection + # with iterable colors + fig, ax = plt.subplots() + colors = np.random.default_rng().choice(['r', 'g', 'b'], 10) + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c=colors) + + leg = ax.legend(labelcolor='linecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_linecolor_cmap(): + # test the labelcolor for labelcolor='linecolor' on PathCollection + # with a colormap + fig, ax = plt.subplots() + ax.scatter( + np.arange(10), + np.arange(10), + label='#1', + c=np.arange(10), + cmap="Reds" + ) + + leg = ax.legend(labelcolor='linecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + def test_legend_labelcolor_markeredgecolor(): # test the labelcolor for labelcolor='markeredgecolor' fig, ax = plt.subplots() @@ -683,6 +724,49 @@ def test_legend_labelcolor_markeredgecolor(): assert mpl.colors.same_color(text.get_color(), color) +def test_legend_pathcollection_labelcolor_markeredgecolor(): + # test the labelcolor for labelcolor='markeredgecolor' on PathCollection + fig, ax = plt.subplots() + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', edgecolor='r') + ax.scatter(np.arange(10), np.arange(10)*2, label='#2', edgecolor='g') + ax.scatter(np.arange(10), np.arange(10)*3, label='#3', edgecolor='b') + + leg = ax.legend(labelcolor='markeredgecolor') + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markeredgecolor_iterable(): + # test the labelcolor for labelcolor='markeredgecolor' on PathCollection + # with iterable colors + fig, ax = plt.subplots() + colors = np.random.default_rng().choice(['r', 'g', 'b'], 10) + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', edgecolor=colors) + + leg = ax.legend(labelcolor='markeredgecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markeredgecolor_cmap(): + # test the labelcolor for labelcolor='markeredgecolor' on PathCollection + # with a colormap + fig, ax = plt.subplots() + edgecolors = mpl.cm.viridis(np.random.rand(10)) + ax.scatter( + np.arange(10), + np.arange(10), + label='#1', + c=np.arange(10), + edgecolor=edgecolors, + cmap="Reds" + ) + + leg = ax.legend(labelcolor='markeredgecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + def test_legend_labelcolor_markerfacecolor(): # test the labelcolor for labelcolor='markerfacecolor' fig, ax = plt.subplots() @@ -695,6 +779,48 @@ def test_legend_labelcolor_markerfacecolor(): assert mpl.colors.same_color(text.get_color(), color) +def test_legend_pathcollection_labelcolor_markerfacecolor(): + # test the labelcolor for labelcolor='markerfacecolor' on PathCollection + fig, ax = plt.subplots() + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', facecolor='r') + ax.scatter(np.arange(10), np.arange(10)*2, label='#2', facecolor='g') + ax.scatter(np.arange(10), np.arange(10)*3, label='#3', facecolor='b') + + leg = ax.legend(labelcolor='markerfacecolor') + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markerfacecolor_iterable(): + # test the labelcolor for labelcolor='markerfacecolor' on PathCollection + # with iterable colors + fig, ax = plt.subplots() + colors = np.random.default_rng().choice(['r', 'g', 'b'], 10) + ax.scatter(np.arange(10), np.arange(10)*1, label='#1', facecolor=colors) + + leg = ax.legend(labelcolor='markerfacecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_pathcollection_labelcolor_markfacecolor_cmap(): + # test the labelcolor for labelcolor='markerfacecolor' on PathCollection + # with colormaps + fig, ax = plt.subplots() + facecolors = mpl.cm.viridis(np.random.rand(10)) + ax.scatter( + np.arange(10), + np.arange(10), + label='#1', + c=np.arange(10), + facecolor=facecolors + ) + + leg = ax.legend(labelcolor='markerfacecolor') + for text, color in zip(leg.get_texts(), ['k']): + assert mpl.colors.same_color(text.get_color(), color) + + @pytest.mark.parametrize('color', ('red', 'none', (.5, .5, .5))) def test_legend_labelcolor_rcparam_single(color): # test the rcParams legend.labelcolor for a single color From 41162b3505544b3ab7bf954d0509bece6b38251e Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Tue, 18 Oct 2022 13:25:21 -0500 Subject: [PATCH 4/7] Improve tests as suggested in PR --- lib/matplotlib/tests/test_legend.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index a7674567c0bb..771b2a046ebf 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -691,25 +691,19 @@ def test_legend_pathcollection_labelcolor_linecolor_iterable(): ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c=colors) leg = ax.legend(labelcolor='linecolor') - for text, color in zip(leg.get_texts(), ['k']): - assert mpl.colors.same_color(text.get_color(), color) + text, = leg.get_texts() + assert mpl.colors.same_color(text, 'black') def test_legend_pathcollection_labelcolor_linecolor_cmap(): # test the labelcolor for labelcolor='linecolor' on PathCollection # with a colormap fig, ax = plt.subplots() - ax.scatter( - np.arange(10), - np.arange(10), - label='#1', - c=np.arange(10), - cmap="Reds" - ) + ax.scatter(np.arange(10), np.arange(10), c=np.arange(10), label='#1') leg = ax.legend(labelcolor='linecolor') - for text, color in zip(leg.get_texts(), ['k']): - assert mpl.colors.same_color(text.get_color(), color) + text, = leg.get_texts() + assert mpl.colors.same_color(text, 'black') def test_legend_labelcolor_markeredgecolor(): From 9842f15d0494ceb1493fcda785d720c92687585f Mon Sep 17 00:00:00 2001 From: Chahak Mehta <201501422@daiict.ac.in> Date: Wed, 19 Oct 2022 21:12:16 -0500 Subject: [PATCH 5/7] Update lib/matplotlib/tests/test_legend.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/tests/test_legend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 771b2a046ebf..01163413bb70 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -692,7 +692,7 @@ def test_legend_pathcollection_labelcolor_linecolor_iterable(): leg = ax.legend(labelcolor='linecolor') text, = leg.get_texts() - assert mpl.colors.same_color(text, 'black') + assert mpl.colors.same_color(text.get_text(), 'black') def test_legend_pathcollection_labelcolor_linecolor_cmap(): From 3a53ebfe96bc4bfb778d2d4082d2f9b22ee42fd0 Mon Sep 17 00:00:00 2001 From: Chahak Mehta <201501422@daiict.ac.in> Date: Wed, 19 Oct 2022 21:12:21 -0500 Subject: [PATCH 6/7] Update lib/matplotlib/tests/test_legend.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/tests/test_legend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 01163413bb70..b6ccabec455a 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -703,7 +703,7 @@ def test_legend_pathcollection_labelcolor_linecolor_cmap(): leg = ax.legend(labelcolor='linecolor') text, = leg.get_texts() - assert mpl.colors.same_color(text, 'black') + assert mpl.colors.same_color(text.get_text(), 'black') def test_legend_labelcolor_markeredgecolor(): From a94dc42f6e02e8feca9892218551d169d04eaeb8 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 19 Oct 2022 21:39:44 -0500 Subject: [PATCH 7/7] Correct get_text() call to get_color() --- lib/matplotlib/tests/test_legend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index b6ccabec455a..6660b91ecdd9 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -692,7 +692,7 @@ def test_legend_pathcollection_labelcolor_linecolor_iterable(): leg = ax.legend(labelcolor='linecolor') text, = leg.get_texts() - assert mpl.colors.same_color(text.get_text(), 'black') + assert mpl.colors.same_color(text.get_color(), 'black') def test_legend_pathcollection_labelcolor_linecolor_cmap(): @@ -703,7 +703,7 @@ def test_legend_pathcollection_labelcolor_linecolor_cmap(): leg = ax.legend(labelcolor='linecolor') text, = leg.get_texts() - assert mpl.colors.same_color(text.get_text(), 'black') + assert mpl.colors.same_color(text.get_color(), 'black') def test_legend_labelcolor_markeredgecolor():