diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 46ec06e0a9f1..3a39159ec7c6 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -4,7 +4,6 @@ matplotlib\.tests(\..*)? matplotlib\.pylab(\..*)? matplotlib\._.* matplotlib\.rcsetup\._listify_validator -matplotlib\.rcsetup\._validate_linestyle matplotlib\.ft2font\.Glyph matplotlib\.testing\.jpl_units\..* matplotlib\.sphinxext(\..*)? diff --git a/doc/users/next_whats_new/new_rcparams_grid_options.rst b/doc/users/next_whats_new/new_rcparams_grid_options.rst new file mode 100644 index 000000000000..4bfe6949f6a2 --- /dev/null +++ b/doc/users/next_whats_new/new_rcparams_grid_options.rst @@ -0,0 +1,33 @@ +Separate styling options for major/minor grid line in rcParams +-------------------------------------------------------------- + +Using :rc:`grid.major.*` or :rc:`grid.minor.*` will overwrite the value in +:rc:`grid.*` for the major and minor gridlines, respectively. + +.. plot:: + :include-source: true + :alt: Modifying the gridlines using the new options `rcParams` + + import matplotlib as mpl + import matplotlib.pyplot as plt + + + # Set visibility for major and minor gridlines + mpl.rcParams["axes.grid"] = True + mpl.rcParams["ytick.minor.visible"] = True + mpl.rcParams["xtick.minor.visible"] = True + mpl.rcParams["axes.grid.which"] = "both" + + # Using old old values to set both major and minor properties + mpl.rcParams["grid.color"] = "red" + mpl.rcParams["grid.linewidth"] = 1 + + # Overwrite some values for major and minor separately + mpl.rcParams["grid.major.color"] = "black" + mpl.rcParams["grid.major.linewidth"] = 2 + mpl.rcParams["grid.minor.linestyle"] = ":" + mpl.rcParams["grid.minor.alpha"] = 0.6 + + plt.plot([0, 1], [0, 1]) + + plt.show() diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 5f964e0b34de..e1eda2d35659 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1348,11 +1348,18 @@ def is_interactive(): return rcParams['interactive'] -def _val_or_rc(val, rc_name): +def _val_or_rc(val, *rc_names): """ - If *val* is None, return ``mpl.rcParams[rc_name]``, otherwise return val. + If *val* is None, the first not-None value in ``mpl.rcParams[rc_names[i]]``. + If all are None returns ``mpl.rcParams[rc_names[-1]]``. """ - return val if val is not None else rcParams[rc_name] + if val is not None: + return val + + for rc_name in rc_names[:-1]: + if rcParams[rc_name] is not None: + return rcParams[rc_name] + return rcParams[rc_names[-1]] def _init_tests(): diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index c33523f628c9..32fb57a86485 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -124,16 +124,41 @@ def __init__( zorder = mlines.Line2D.zorder self._zorder = zorder - grid_color = mpl._val_or_rc(grid_color, "grid.color") - grid_linestyle = mpl._val_or_rc(grid_linestyle, "grid.linestyle") - grid_linewidth = mpl._val_or_rc(grid_linewidth, "grid.linewidth") + grid_color = ( + mpl._val_or_rc( + grid_color, + f"grid.{major_minor}.color", + "grid.color", + ) + ) + grid_linestyle = ( + mpl._val_or_rc( + grid_linestyle, + f"grid.{major_minor}.linestyle", + "grid.linestyle", + ) + ) + grid_linewidth = ( + mpl._val_or_rc( + grid_linewidth, + f"grid.{major_minor}.linewidth", + "grid.linewidth", + ) + ) if grid_alpha is None and not mcolors._has_alpha_channel(grid_color): # alpha precedence: kwarg > color alpha > rcParams['grid.alpha'] # Note: only resolve to rcParams if the color does not have alpha # otherwise `grid(color=(1, 1, 1, 0.5))` would work like # grid(color=(1, 1, 1, 0.5), alpha=rcParams['grid.alpha']) # so the that the rcParams default would override color alpha. - grid_alpha = mpl.rcParams["grid.alpha"] + grid_alpha = ( + # grid_alpha is None so we can use the first key + mpl._val_or_rc( + mpl.rcParams[f"grid.{major_minor}.alpha"], + "grid.alpha", + ) + ) + grid_kw = {k[5:]: v for k, v in kwargs.items() if k != "rotation_mode"} self.tick1line = mlines.Line2D( diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index ab412302f3b2..0a196e6d868a 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -538,6 +538,16 @@ #grid.linewidth: 0.8 # in points #grid.alpha: 1.0 # transparency, between 0.0 and 1.0 +#grid.major.color: None # If None defaults to grid.color +#grid.major.linestyle: None # If None defaults to grid.linestyle +#grid.major.linewidth: None # If None defaults to grid.linewidth +#grid.major.alpha: None # If None defaults to grid.alpha + +#grid.minor.color: None # If None defaults to grid.color +#grid.minor.linestyle: None # If None defaults to grid.linestyle +#grid.minor.linewidth: None # If None defaults to grid.linewidth +#grid.minor.alpha: None # If None defaults to grid.alpha + ## *************************************************************************** ## * LEGEND * diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index d927e93fae8b..40d556ce8c88 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -368,6 +368,12 @@ def validate_color(s): raise ValueError(f'{s!r} does not look like a color arg') +def validate_color_or_None(s): + if s is None or cbook._str_equal(s, "None"): + return None + return validate_color(s) + + validate_colorlist = _listify_validator( validate_color, allow_stringlist=True, doc='return a list of colorspecs') @@ -522,6 +528,13 @@ def _is_iterable_not_string_like(x): raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.") +def _validate_linestyle_or_None(ls): + if ls is None or cbook._str_equal(ls, "None"): + return None + + return _validate_linestyle(ls) + + validate_fillstyle = ValidateInStrings( 'markers.fillstyle', ['full', 'left', 'right', 'bottom', 'top', 'none']) @@ -1248,6 +1261,16 @@ def _convert_validator_spec(key, conv): "grid.linewidth": validate_float, # in points "grid.alpha": validate_float, + "grid.major.color": validate_color_or_None, # grid color + "grid.major.linestyle": _validate_linestyle_or_None, # solid + "grid.major.linewidth": validate_float_or_None, # in points + "grid.major.alpha": validate_float_or_None, + + "grid.minor.color": validate_color_or_None, # grid color + "grid.minor.linestyle": _validate_linestyle_or_None, # solid + "grid.minor.linewidth": validate_float_or_None, # in points + "grid.minor.alpha": validate_float_or_None, + ## figure props # figure title "figure.titlesize": validate_fontsize, diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi index 79538511c0e4..cf3fe1861674 100644 --- a/lib/matplotlib/rcsetup.pyi +++ b/lib/matplotlib/rcsetup.pyi @@ -51,6 +51,7 @@ def validate_color_or_auto(s: Any) -> ColorType | Literal["auto"]: ... def _validate_color_or_edge(s: Any) -> ColorType | Literal["edge"]: ... def validate_color_for_prop_cycle(s: Any) -> ColorType: ... def validate_color(s: Any) -> ColorType: ... +def validate_color_or_None(s: Any) -> ColorType | None: ... def validate_colorlist(s: Any) -> list[ColorType]: ... def _validate_color_or_linecolor( s: Any, @@ -139,7 +140,8 @@ def validate_fillstylelist( s: Any, ) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ... def validate_markevery(s: Any) -> MarkEveryType: ... -def _validate_linestyle(s: Any) -> LineStyleType: ... +def _validate_linestyle(ls: Any) -> LineStyleType: ... +def _validate_linestyle_or_None(ls: Any) -> LineStyleType | None: ... def validate_markeverylist(s: Any) -> list[MarkEveryType]: ... def validate_bbox(s: Any) -> Literal["tight", "standard"] | None: ... def validate_sketch(s: Any) -> None | tuple[float, float, float]: ... diff --git a/lib/matplotlib/tests/test_axis.py b/lib/matplotlib/tests/test_axis.py index e33656ea9c17..ff7d8594c689 100644 --- a/lib/matplotlib/tests/test_axis.py +++ b/lib/matplotlib/tests/test_axis.py @@ -67,3 +67,31 @@ def test_get_tick_position_tick_params(): right=True, labelright=True, left=False, labelleft=False) assert ax.xaxis.get_ticks_position() == "top" assert ax.yaxis.get_ticks_position() == "right" + + +def test_grid_rcparams(): + """Tests that `grid.major/minor.*` overwrites `grid.*` in rcParams.""" + plt.rcParams.update({ + "axes.grid": True, "axes.grid.which": "both", + "ytick.minor.visible": True, "xtick.minor.visible": True, + }) + def_linewidth = plt.rcParams["grid.linewidth"] + def_linestyle = plt.rcParams["grid.linestyle"] + def_alpha = plt.rcParams["grid.alpha"] + + plt.rcParams.update({ + "grid.color": "gray", "grid.minor.color": "red", + "grid.major.linestyle": ":", "grid.major.linewidth": 2, + "grid.minor.alpha": 0.6, + }) + _, ax = plt.subplots() + ax.plot([0, 1]) + + assert ax.xaxis.get_major_ticks()[0].gridline.get_color() == "gray" + assert ax.xaxis.get_minor_ticks()[0].gridline.get_color() == "red" + assert ax.xaxis.get_major_ticks()[0].gridline.get_linewidth() == 2 + assert ax.xaxis.get_minor_ticks()[0].gridline.get_linewidth() == def_linewidth + assert ax.xaxis.get_major_ticks()[0].gridline.get_linestyle() == ":" + assert ax.xaxis.get_minor_ticks()[0].gridline.get_linestyle() == def_linestyle + assert ax.xaxis.get_major_ticks()[0].gridline.get_alpha() == def_alpha + assert ax.xaxis.get_minor_ticks()[0].gridline.get_alpha() == 0.6