Repository: matplotlib/matplotlib
Pull request: Add rcparam for figure label size and weight
## PR Summary

Closes  #22564

Add `figure.labelsize` and `figure.labelweight` rcParams to set figure label size and weight different from title.

Test added also for titlesize/-weight rcParams as I didn't find any.

## PR Checklist

<!-- Please mark any checkboxes that do not apply to this PR as [N/A]. -->
**Tests and Styling**
- [x] Has pytest style unit tests (and `pytest` passes).
- [x] Is [Flake 8](https://flake8.pycqa.org/en/latest/) compliant (install `flake8-docstrings` and run `flake8 --docstring-convention=all`).

**Documentation**
- [N/A] New features are documented, with examples if plot related.
- [x] New features have an entry in `doc/users/next_whats_new/` (follow instructions in README.rst there).
- [N/A] API changes documented in `doc/api/next_api_changes/` (follow instructions in README.rst there).
- [x] Documentation is sphinx and numpydoc compliant (the docs should [build](https://matplotlib.org/devel/documenting_mpl.html#building-the-docs) without error).

<!--
Thank you so much for your PR!  To help us review your contribution, please
consider the following points:

- A development guide is available at https://matplotlib.org/devdocs/devel/index.html.

- Help with git and github is available at
  https://matplotlib.org/devel/gitwash/development_workflow.html.

- Do not create the PR out of main, but out of a separate branch.

- The PR title should summarize the changes, for example "Raise ValueError on
  non-numeric input to set_xlim".  Avoid non-descriptive titles such as
  "Addresses issue #8576".

- The summary should provide at least 1-2 sentences describing the pull request
  in detail (Why is this change required?  What problem does it solve?) and
  link to any relevant issues.

- If you are contributing fixes to docstrings, please pay attention to
  http://matplotlib.org/devel/documenting_mpl.html#formatting.  In particular,
  note the difference between using single backquotes, double backquotes, and
  asterisks in the markup.

We understand that PRs can sometimes be overwhelming, especially as the
reviews start coming in.  Please let us know if the reviews are unclear or
the recommended next step seems overly demanding, if you would like help in
addressing a reviewer's comments, or if you have been waiting too long to hear
back on your PR.
-->


Problem statement:
[ENH]: Font weight for Figure title and labels
### Problem

Figure title (_Figure.suptitle()_) and labels (_Figure.supxlabel()_ and _Figure.supylabel()_) have the same default font size and weight. Although one can modify these values when creating the title and/or labels, it would be easier if users were able to set different defaults.

### Proposed solution

Create rcParams called _figure.labelweight_ and _figure.labelsize_, so that _Figure.supxlabel()_ and _Figure.supylabel()_ would default to these new values, rather than _rcParams["figure.titleweight"]_ and _rcParams["figure.titlesize"]_.

Hints from issue discussion:

Commit message: Add rcparam for figure label size and weight

Patch to review:
diff --git a/doc/users/next_whats_new/figure_label_rcparams.rst b/doc/users/next_whats_new/figure_label_rcparams.rst
new file mode 100644
index 000000000000..149da1e92cf0
--- /dev/null
+++ b/doc/users/next_whats_new/figure_label_rcparams.rst
@@ -0,0 +1,10 @@
+Allow setting figure label size and weight globally and separately from title
+-----------------------------------------------------------------------------
+
+The figure labels, ``Figure.supxlabel`` and ``Figure.supylabel``, size and
+weight can be set separately from the figure title. Use :rc:`figure.labelsize`
+and :rc:`figure.labelweight`.
+
+Note that if you have locally changed :rc:`figure.titlesize` or
+:rc:`figure.titleweight`, you must now also change the introduced parameters
+for a consistent result.
diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index 0b8ad9828b3d..075cf0968e86 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -314,10 +314,10 @@ def _suplabels(self, t, info, **kwargs):
         verticalalignment, va : {'top', 'center', 'bottom', 'baseline'}, \
 default: %(va)s
             The vertical alignment of the text relative to (*x*, *y*).
-        fontsize, size : default: :rc:`figure.titlesize`
+        fontsize, size : default: :rc:`figure.%(rc)ssize`
             The font size of the text. See `.Text.set_size` for possible
             values.
-        fontweight, weight : default: :rc:`figure.titleweight`
+        fontweight, weight : default: :rc:`figure.%(rc)sweight`
             The font weight of the text. See `.Text.set_weight` for possible
             values.
 
@@ -331,8 +331,8 @@ def _suplabels(self, t, info, **kwargs):
         fontproperties : None or dict, optional
             A dict of font properties. If *fontproperties* is given the
             default values for font size and weight are taken from the
-            `.FontProperties` defaults. :rc:`figure.titlesize` and
-            :rc:`figure.titleweight` are ignored in this case.
+            `.FontProperties` defaults. :rc:`figure.%(rc)ssize` and
+            :rc:`figure.%(rc)sweight` are ignored in this case.
 
         **kwargs
             Additional kwargs are `matplotlib.text.Text` properties.
@@ -360,9 +360,9 @@ def _suplabels(self, t, info, **kwargs):
 
         if 'fontproperties' not in kwargs:
             if 'fontsize' not in kwargs and 'size' not in kwargs:
-                kwargs['size'] = mpl.rcParams['figure.titlesize']
+                kwargs['size'] = mpl.rcParams[info['size']]
             if 'fontweight' not in kwargs and 'weight' not in kwargs:
-                kwargs['weight'] = mpl.rcParams['figure.titleweight']
+                kwargs['weight'] = mpl.rcParams[info['weight']]
 
         sup = self.text(x, y, t, **kwargs)
         if suplab is not None:
@@ -378,31 +378,34 @@ def _suplabels(self, t, info, **kwargs):
         return suplab
 
     @_docstring.Substitution(x0=0.5, y0=0.98, name='suptitle', ha='center',
-                             va='top')
+                             va='top', rc='title')
     @_docstring.copy(_suplabels)
     def suptitle(self, t, **kwargs):
         # docstring from _suplabels...
         info = {'name': '_suptitle', 'x0': 0.5, 'y0': 0.98,
-                'ha': 'center', 'va': 'top', 'rotation': 0}
+                'ha': 'center', 'va': 'top', 'rotation': 0,
+                'size': 'figure.titlesize', 'weight': 'figure.titleweight'}
         return self._suplabels(t, info, **kwargs)
 
     @_docstring.Substitution(x0=0.5, y0=0.01, name='supxlabel', ha='center',
-                             va='bottom')
+                             va='bottom', rc='label')
     @_docstring.copy(_suplabels)
     def supxlabel(self, t, **kwargs):
         # docstring from _suplabels...
         info = {'name': '_supxlabel', 'x0': 0.5, 'y0': 0.01,
-                'ha': 'center', 'va': 'bottom', 'rotation': 0}
+                'ha': 'center', 'va': 'bottom', 'rotation': 0,
+                'size': 'figure.labelsize', 'weight': 'figure.labelweight'}
         return self._suplabels(t, info, **kwargs)
 
     @_docstring.Substitution(x0=0.02, y0=0.5, name='supylabel', ha='left',
-                             va='center')
+                             va='center', rc='label')
     @_docstring.copy(_suplabels)
     def supylabel(self, t, **kwargs):
         # docstring from _suplabels...
         info = {'name': '_supylabel', 'x0': 0.02, 'y0': 0.5,
                 'ha': 'left', 'va': 'center', 'rotation': 'vertical',
-                'rotation_mode': 'anchor'}
+                'rotation_mode': 'anchor', 'size': 'figure.labelsize',
+                'weight': 'figure.labelweight'}
         return self._suplabels(t, info, **kwargs)
 
     def get_edgecolor(self):
diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc
index bdb5125a3d59..597d13d34254 100644
--- a/lib/matplotlib/mpl-data/matplotlibrc
+++ b/lib/matplotlib/mpl-data/matplotlibrc
@@ -555,6 +555,8 @@
 ## See https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure
 #figure.titlesize:   large     # size of the figure title (``Figure.suptitle()``)
 #figure.titleweight: normal    # weight of the figure title
+#figure.labelsize:   large     # size of the figure label (``Figure.sup[x|y]label()``)
+#figure.labelweight: normal    # weight of the figure label
 #figure.figsize:     6.4, 4.8  # figure size in inches
 #figure.dpi:         100       # figure dots per inch
 #figure.facecolor:   white     # figure face color
diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle
index 7455c71ba258..6f65e8be95db 100644
--- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle
+++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle
@@ -307,6 +307,8 @@ legend.edgecolor     : inherit   # legend edge color (when 'inherit' uses axes.e
 # See https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure
 figure.titlesize : medium     # size of the figure title
 figure.titleweight : normal   # weight of the figure title
+figure.labelsize:   medium    # size of the figure label
+figure.labelweight: normal    # weight of the figure label
 figure.figsize   : 8, 6    # figure size in inches
 figure.dpi       : 80      # figure dots per inch
 figure.facecolor : 0.75    # figure facecolor; 0.75 is scalar gray
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index c5fa25f64714..0a0e0bb2734c 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -1132,6 +1132,10 @@ def _convert_validator_spec(key, conv):
     "figure.titlesize":   validate_fontsize,
     "figure.titleweight": validate_fontweight,
 
+    # figure labels
+    "figure.labelsize":   validate_fontsize,
+    "figure.labelweight": validate_fontweight,
+
     # figure size in inches: width by height
     "figure.figsize":          _listify_validator(validate_float, n=2),
     "figure.dpi":              validate_float,
diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py
index 9ebc271b6faf..bbdf9d6bad92 100644
--- a/lib/matplotlib/tests/test_figure.py
+++ b/lib/matplotlib/tests/test_figure.py
@@ -1370,6 +1370,20 @@ def test_kwargs_pass():
     assert sub_fig.get_label() == 'sub figure'
 
 
+@check_figures_equal(extensions=["png"])
+def test_rcparams(fig_test, fig_ref):
+    fig_ref.supxlabel("xlabel", weight='bold', size=15)
+    fig_ref.supylabel("ylabel", weight='bold', size=15)
+    fig_ref.suptitle("Title", weight='light', size=20)
+    with mpl.rc_context({'figure.labelweight': 'bold',
+                         'figure.labelsize': 15,
+                         'figure.titleweight': 'light',
+                         'figure.titlesize': 20}):
+        fig_test.supxlabel("xlabel")
+        fig_test.supylabel("ylabel")
+        fig_test.suptitle("Title", weight='light')
+
+
 def test_deepcopy():
     fig1, ax = plt.subplots()
     ax.plot([0, 1], [2, 3])


Code review comments:
diff_hunk: @@ -1370,6 +1370,20 @@ def test_kwargs_pass():
     assert sub_fig.get_label() == 'sub figure'
 
 
+@check_figures_equal(extensions=["png"])
+def test_rcparams(fig_test, fig_ref):
+    fig_ref.supxlabel("xlabel", weight='bold', size=15)
+    fig_ref.supylabel("ylabel", weight='bold', size=15)
+    fig_ref.suptitle("Title", weight='light', size=20)
+    with mpl.rc_context({'figure.labelweight': 'bold',
+                         'figure.labelsize': 15,
+                         'figure.titleweight': 'light',
+                         'figure.titlesize': 20}):
+        fig_test.supxlabel("xlabel")
+        fig_test.supylabel("ylabel")
+        fig_test.suptitle("Title", weight='light')
path: lib/matplotlib/tests/test_figure.py
text: @user1:
Shouldn't the `weight` not be here?

@author:
Indeed! Well spotted!