diff --git a/doc/users/next_whats_new/rcParams[legend.loc]_supports_float_tuple.rst b/doc/users/next_whats_new/rcParams[legend.loc]_supports_float_tuple.rst new file mode 100644 index 000000000000..a83b91b52e34 --- /dev/null +++ b/doc/users/next_whats_new/rcParams[legend.loc]_supports_float_tuple.rst @@ -0,0 +1,5 @@ +``rcParams['legend.loc']`` now accepts float-tuple inputs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :rc:`legend.loc` rcParams now accepts float-tuple inputs, same as the *loc* keyword argument to `.Legend`. +This allows users to set the location of the legend in a more flexible and consistent way. diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a9bebd209077..1cadc75273fb 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -718,6 +718,51 @@ def visit_Attribute(self, node): self.generic_visit(node) +# A validator dedicated to the named legend loc +_validate_named_legend_loc = ValidateInStrings( + 'legend.loc', + [ + "best", + "upper right", "upper left", "lower left", "lower right", "right", + "center left", "center right", "lower center", "upper center", + "center"], + ignorecase=True) + + +def _validate_legend_loc(loc): + """ + Confirm that loc is a type which rc.Params["legend.loc"] supports. + + .. versionadded:: 3.8 + + Parameters + ---------- + loc : str | int | (float, float) | str((float, float)) + The location of the legend. + + Returns + ------- + loc : str | int | (float, float) or raise ValueError exception + The location of the legend. + """ + if isinstance(loc, str): + try: + return _validate_named_legend_loc(loc) + except ValueError: + pass + try: + loc = ast.literal_eval(loc) + except (SyntaxError, ValueError): + pass + if isinstance(loc, int): + if 0 <= loc <= 10: + return loc + if isinstance(loc, tuple): + if len(loc) == 2 and all(isinstance(e, Real) for e in loc): + return loc + raise ValueError(f"{loc} is not a valid legend location.") + + def validate_cycler(s): """Return a Cycler object from a string repr or the object itself.""" if isinstance(s, str): @@ -1042,11 +1087,7 @@ def _convert_validator_spec(key, conv): # legend properties "legend.fancybox": validate_bool, - "legend.loc": _ignorecase([ - "best", - "upper right", "upper left", "lower left", "lower right", "right", - "center left", "center right", "lower center", "upper center", - "center"]), + "legend.loc": _validate_legend_loc, # the number of points in the legend line "legend.numpoints": validate_int, diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index bdaf5b593a5f..19f259c48756 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -1,7 +1,6 @@ import copy import os from pathlib import Path -import re import subprocess import sys from unittest import mock @@ -592,8 +591,33 @@ def test_deprecation(monkeypatch): # suppress_matplotlib_deprecation_warning, rather than any explicit check. -def test_rcparams_legend_loc(): - value = (0.9, .7) - match_str = f"{value} is not a valid value for legend.loc;" - with pytest.raises(ValueError, match=re.escape(match_str)): - mpl.RcParams({'legend.loc': value}) +@pytest.mark.parametrize("value", [ + "best", + 1, + "1", + (0.9, .7), + (-0.9, .7), + "(0.9, .7)" +]) +def test_rcparams_legend_loc(value): + # rcParams['legend.loc'] should allow any of the following formats. + # if any of these are not allowed, an exception will be raised + # test for gh issue #22338 + mpl.rcParams["legend.loc"] = value + + +@pytest.mark.parametrize("value", [ + "best", + 1, + (0.9, .7), + (-0.9, .7), +]) +def test_rcparams_legend_loc_from_file(tmpdir, value): + # rcParams['legend.loc'] should be settable from matplotlibrc. + # if any of these are not allowed, an exception will be raised. + # test for gh issue #22338 + rc_path = tmpdir.join("matplotlibrc") + rc_path.write(f"legend.loc: {value}") + + with mpl.rc_context(fname=rc_path): + assert mpl.rcParams["legend.loc"] == value
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: