Repository: matplotlib/matplotlib

Issue:
Impossible to configure minor/major grid line style independently in rcParams
Hi,

As far as I can see, the only customization to the grid that is possible right now in rcParams is:

```
mpl.rcParams['grid.color']  
mpl.rcParams['grid.alpha']
mpl.rcParams['grid.linewidth']
mpl.rcParams['grid.linestyle']
```

Is it possible to change these keys so that it is possible to configure minor and major grid line styles independently from each other?

Something like:

```
mpl.rcParams['grid.minor.color']  
mpl.rcParams['grid.minor.alpha']
mpl.rcParams['grid.minor.linewidth']
mpl.rcParams['grid.minor.linestyle']
mpl.rcParams['grid.major.color']  
mpl.rcParams['grid.major.alpha']
mpl.rcParams['grid.major.linewidth']
mpl.rcParams['grid.major.linestyle']
```


Discussion:
There are no separate parameters for major/minor grid properties in `rcParams`.

You can currently only configure them programmatically.

~~~
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

x = np.arange(10)

ax = plt.gca()
ax.plot(x, x**2)
ax.xaxis.set_minor_locator(MultipleLocator(0.5))
ax.grid(which='both')
ax.tick_params(which='minor', grid_linestyle='--')
~~~

I was just looking for exactly this feature. I already did it programmatically before, but as I have a large number of different plots with major and minor gridlines, this is quite inconvenient.

Looking forward such an option in rcParams!
Yes, **PLEASE**. I'm with @NGJROMO. 
The programmatic way shown above is too cumbersome (and, is there a way to also set line width, color, and alpha?). 

This is back to one year ago. Is there any way it can be implemented soon?

Here is what I want:
![ExamplePlot-Two Grids](https://user-images.githubusercontent.com/3887028/78744224-e2871880-7915-11ea-94d7-2ff336257e62.png)

Thanks.

I'm surprised that the settings are not automatically mirrored in the style sheet & rcParams.  In fact, I would have thought that all of the defaults would be set up in rcParams & that would drive everything else.
Bumping this issue -- I've found it unwieldy to copy and paste the same code snippet to get a fixed style that I almost always want, and would also appreciate support for differently styled major and minor gridlines via rc params.
A motivated user could always write a PR for this.  https://matplotlib.org/stable/devel/contributing.html  However, be aware that new rcparams face a relatively high bar for acceptance.

One can also write their own styling wrapper if they are always doing this.

```python
import matplotlib.pyplot as plt 

my_style_subplots(*arg, **kwargs):
    fig, axs = plt.subplots(*args, **kwargs)
    for ax in axs.flat:
       ax.xaxis.set_minor_locator(MultipleLocator(0.5))
       ax.grid(which='both')
       ax.tick_params(which='minor', grid_linestyle='--')
    return fig, axs
```

```python
import mystyles as myplt

fig, ax = mplt.subplots(3, 2, sharedx=True)
```
Sorry but above answer isn't satisfactory IMHO. That is not what we are asking. Also, it forces ax.xaxis.set_minor_locator(MultipleLocator(0.5)), which I would not want in general. 

This is a straightforward request and not hard to implement. It's from 2019 for crying out loud. 
> This is a straightforward request and not hard to implement. It's from 2019 for crying out loud.

Matplotlib is a volunteer organization and PRs are always welcome!


I agree with @neutrinonerd3333, and am in the same boat as him and @zertrin.

One other thing that I think is related, is the inability to specify tick label font in rcParams. I am able to specify the overall general font as Helvetica for example, successfully changing the font of everything in my generated plots, however this does not change the font of the tick labels (they remain in a serif font..)! I thought I could specify this in the rcParams (with something like font..ticklabel.family:  sans-serif, font.ticklabel.serif: Helvetica), but it turns out this is another limitation that must be done programmatically... making each of my plots even more cumbersome and repetitive to code. 
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear.  Thanks for your help!
This is a highly requested feature. -> keep 
any updates on this?
Still need this feature
### Good first issue - notes for new contributors

This issue is suited to new contributors because it does not require understanding of the
Matplotlib internals. To get started, please see our [contributing
guide](https://matplotlib.org/stable/devel/index).

**We do not assign issues**. Check the *Development* section in the sidebar for linked pull
requests (PRs). If there are none, feel free to start working on it. If there is an open PR, please
collaborate on the work by reviewing it rather than duplicating it in a competing PR.

If something is unclear, please reach out on any of our [communication
channels](https://matplotlib.org/stable/devel/contributing.html#get-connected).
Maybe some extra tagging will help, but someone who wants this feature needs to propose a PR for it to get implemented.  
As a word of warning: This is not as trivial as it sounds. We want to maintain backward-compatibility, i.e. if somebody changes th `grid.*` parameters, they should still affect both grids - unless of course they specify `grid.minor.*` explicitly. This possibly needs some hierachial lookup/dependency between the keys, which is not a concept we have in `rcParams` so far.



Patch:
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..c2c0455eecbb
--- /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 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..8a26946702a0 100644
--- a/lib/matplotlib/axis.py
+++ b/lib/matplotlib/axis.py
@@ -124,16 +124,33 @@ 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 = mpl._val_or_rc(
+                # grid_alpha is None so we can use the first key
+                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..d663cab1ad07 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(s):
+    if s is None or cbook._str_equal(s, "None"):
+        return None
+
+    return _validate_linestyle(s)
+
+
 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..22551d9b2df1 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,
@@ -140,6 +141,7 @@ def validate_fillstylelist(
 ) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ...
 def validate_markevery(s: Any) -> MarkEveryType: ...
 def _validate_linestyle(s: Any) -> LineStyleType: ...
+def _validate_linestyle_or_None(s: 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]: ...


Test patch:
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