diff --git a/doc/users/next_whats_new/rcparams_legend.rst b/doc/users/next_whats_new/rcparams_legend.rst new file mode 100644 index 000000000000..bd9c6b3db8c5 --- /dev/null +++ b/doc/users/next_whats_new/rcparams_legend.rst @@ -0,0 +1,25 @@ +New rcParams for legend: set legend labelcolor globally +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A new :rc:`legend.labelcolor` sets the default *labelcolor* argument for +`.Figure.legend`. The special values 'linecolor', 'markerfacecolor' +(or 'mfc'), or 'markeredgecolor' (or 'mec') will cause the legend text to match +the corresponding color of marker. + + +.. plot:: + + plt.rcParams['legend.labelcolor'] = 'linecolor' + + # Make some fake data. + a = np.arange(0, 3, .02) + c = np.exp(a) + d = c[::-1] + + fig, ax = plt.subplots() + ax.plot(a, c, 'g--', label='Model length') + ax.plot(a, d, 'r:', label='Data length') + + ax.legend() + + plt.show() \ No newline at end of file diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 9b2f79fd3556..bb49f604f37f 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -173,12 +173,15 @@ def _update_bbox_to_anchor(self, loc_in_canvas): absolute font size in points. String values are relative to the current default font size. This argument is only used if *prop* is not specified. -labelcolor : str or list +labelcolor : str or list, default: :rc:`legend.labelcolor` The color of the text in the legend. Either a valid color string (for example, 'red'), or a list of color strings. The labelcolor can also be made to match the color of the line or marker using 'linecolor', 'markerfacecolor' (or 'mfc'), or 'markeredgecolor' (or 'mec'). + Labelcolor can be set globally using :rc:`legend.labelcolor`. If None, + use :rc:`text.color`. + numpoints : int, default: :rc:`legend.numpoints` The number of marker points in the legend when creating a legend entry for a `.Line2D` (line). @@ -539,8 +542,11 @@ def __init__(self, parent, handles, labels, 'mec': ['get_markeredgecolor', 'get_edgecolor'], } if labelcolor is None: - pass - elif isinstance(labelcolor, str) and labelcolor in color_getters: + if mpl.rcParams['legend.labelcolor'] is not None: + labelcolor = mpl.rcParams['legend.labelcolor'] + else: + labelcolor = mpl.rcParams['text.color'] + if isinstance(labelcolor, str) and labelcolor in color_getters: getter_names = color_getters[labelcolor] for handle, text in zip(self.legendHandles, self.texts): for getter_name in getter_names: diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 326d02c2124b..1f8b68b4580b 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -530,6 +530,7 @@ #legend.scatterpoints: 1 # number of scatter points #legend.markerscale: 1.0 # the relative size of legend markers vs. original #legend.fontsize: medium +#legend.labelcolor: None #legend.title_fontsize: None # None sets to the same as the default axes. ## Dimensions as fraction of font size: diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 7e523897e1a5..07c9f175f373 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -289,6 +289,27 @@ def validate_color_for_prop_cycle(s): return validate_color(s) +def _validate_color_or_linecolor(s): + if cbook._str_equal(s, 'linecolor'): + return s + elif cbook._str_equal(s, 'mfc') or cbook._str_equal(s, 'markerfacecolor'): + return 'markerfacecolor' + elif cbook._str_equal(s, 'mec') or cbook._str_equal(s, 'markeredgecolor'): + return 'markeredgecolor' + elif s is None: + return None + elif isinstance(s, str) and len(s) == 6 or len(s) == 8: + stmp = '#' + s + if is_color_like(stmp): + return stmp + if s.lower() == 'none': + return None + elif is_color_like(s): + return s + + raise ValueError(f'{s!r} does not look like a color arg') + + def validate_color(s): """Return a valid color arg.""" if isinstance(s, str): @@ -1017,6 +1038,8 @@ def _convert_validator_spec(key, conv): "legend.scatterpoints": validate_int, "legend.fontsize": validate_fontsize, "legend.title_fontsize": validate_fontsize_None, + # color of the legend + "legend.labelcolor": _validate_color_or_linecolor, # the relative size of legend markers vs. original "legend.markerscale": validate_float, "legend.shadow": validate_bool, diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index b41c57469573..a86ac851dc19 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -644,6 +644,85 @@ def test_legend_labelcolor_markerfacecolor(): 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 + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1') + ax.plot(np.arange(10), np.arange(10)*2, label='#2') + ax.plot(np.arange(10), np.arange(10)*3, label='#3') + + mpl.rcParams['legend.labelcolor'] = color + leg = ax.legend() + for text in leg.get_texts(): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_linecolor(): + # test the rcParams legend.labelcolor for a linecolor + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', color='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', color='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', color='b') + + mpl.rcParams['legend.labelcolor'] = 'linecolor' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markeredgecolor(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'markeredgecolor' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markeredgecolor_short(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'mec' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markerfacecolor(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'markerfacecolor' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + +def test_legend_labelcolor_rcparam_markerfacecolor_short(): + # test the labelcolor for labelcolor='markeredgecolor' + fig, ax = plt.subplots() + ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r') + ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g') + ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b') + + mpl.rcParams['legend.labelcolor'] = 'mfc' + leg = ax.legend() + for text, color in zip(leg.get_texts(), ['r', 'g', 'b']): + assert mpl.colors.same_color(text.get_color(), color) + + def test_get_set_draggable(): legend = plt.legend() assert not legend.get_draggable() diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index df3f6d7a0413..8b80b155e5a9 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -17,6 +17,7 @@ validate_bool, validate_color, validate_colorlist, + _validate_color_or_linecolor, validate_cycler, validate_float, validate_fontweight, @@ -329,6 +330,17 @@ def generate_validator_testcases(valid): ('(0, 1, "0.5")', ValueError), # last one not a float ), }, + {'validator': _validate_color_or_linecolor, + 'success': (('linecolor', 'linecolor'), + ('markerfacecolor', 'markerfacecolor'), + ('mfc', 'markerfacecolor'), + ('markeredgecolor', 'markeredgecolor'), + ('mec', 'markeredgecolor') + ), + 'fail': (('line', ValueError), + ('marker', ValueError) + ) + }, {'validator': validate_hist_bins, 'success': (('auto', 'auto'), ('fd', 'fd'),