From 8e1d85f33945bfaa20809d327ec211312ae63d54 Mon Sep 17 00:00:00 2001 From: RichardHopperProGrammar Date: Thu, 28 May 2026 11:58:22 +0000 Subject: [PATCH] feat: add rcParams for suptitle alignment (#24090) Add 'figure.titlehorizontalalign' and 'figure.titleverticalalign' rcParams so that users can control the default alignment of figure titles via rc settings. Before: Only fontsize and fontweight were configurable via rcParams; ha/va were hardcoded to 'center'/'top'. After: rcParams['figure.titlehorizontalalign'] defaults to 'center' rcParams['figure.titleverticalalign'] defaults to 'top' Explicit suptitle() kwargs still override rcParam defaults. - Added validate_suptitle_ha validator in rcsetup.py - Updated figure._suplabels to resolve ha/va from rcParams when set - Updated matplotlibrc with new default entries - Added 2 tests: rcParams alignment and kwarg override - Updated docstring to reference new rcParams --- lib/matplotlib/figure.py | 24 ++++++++++++++++++++--- lib/matplotlib/mpl-data/matplotlibrc | 6 ++++-- lib/matplotlib/rcsetup.py | 29 ++++++++++++++++++++++++++-- lib/matplotlib/tests/test_figure.py | 24 +++++++++++++++++++++++ lib/matplotlib/typing.py | 2 ++ 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ad0206e0db5c..bae0f2dcea52 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -330,6 +330,12 @@ def _suplabels(self, t, info, **kwargs): fontweight, weight : default: :rc:`figure.%(rc)sweight` The font weight of the text. See `.Text.set_weight` for possible values. + horizontalalignment, ha : {'center', 'left', 'right'}, \ +default: :rc:`figure.titlehorizontalalign` + The horizontal alignment of the text relative to (*x*, *y*). + verticalalignment, va : {'top', 'center', 'bottom', 'baseline'}, \ +default: :rc:`figure.titleverticalalign` + The vertical alignment of the text relative to (*x*, *y*). Returns ------- @@ -360,8 +366,18 @@ def _suplabels(self, t, info, **kwargs): y = info['y0'] kwargs = cbook.normalize_kwargs(kwargs, Text) - kwargs.setdefault('horizontalalignment', info['ha']) - kwargs.setdefault('verticalalignment', info['va']) + + # Resolve defaults: rcParam key if present, else hardcoded fallback + ha_key = info.get('horizontalalign') + va_key = info.get('verticalalign') + kwargs.setdefault( + 'horizontalalignment', + mpl.rcParams[ha_key] if ha_key else info['ha'], + ) + kwargs.setdefault( + 'verticalalignment', + mpl.rcParams[va_key] if va_key else info['va'], + ) kwargs.setdefault('rotation', info['rotation']) if 'fontproperties' not in kwargs: @@ -394,7 +410,9 @@ def suptitle(self, t, **kwargs): # docstring from _suplabels... info = {'name': '_suptitle', 'x0': 0.5, 'y0': 0.98, 'ha': 'center', 'va': 'top', 'rotation': 0, - 'size': 'figure.titlesize', 'weight': 'figure.titleweight'} + 'size': 'figure.titlesize', 'weight': 'figure.titleweight', + 'horizontalalign': 'figure.titlehorizontalalign', + 'verticalalign': 'figure.titleverticalalign'} return self._suplabels(t, info, **kwargs) def get_suptitle(self): diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 3b8d222bb3a0..1b24b85a2d84 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -599,8 +599,10 @@ ## * FIGURE * ## *************************************************************************** ## See https://matplotlib.org/stable/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.titlesize: large # size of the figure title (``Figure.suptitle()``) +#figure.titleweight: normal # weight of the figure title +#figure.titlehorizontalalign: center # horizontal alignment of the figure title +#figure.titleverticalalign: top # vertical alignment 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 diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index f88f07c0e82d..b9e24103a96f 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -445,6 +445,15 @@ def validate_fontsize(s): validate_fontsizelist = _listify_validator(validate_fontsize) +def validate_suptitle_ha(s): + """ + Validate horizontal alignment for suptitle rcParams. + """ + return _str_to_one_of( + ['center', 'left', 'right', 'center_left', 'center_right'], + )(s) + + def validate_fontweight(s): weights = [ 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman', @@ -1347,8 +1356,12 @@ def _convert_validator_spec(key, conv): ## figure props # figure title - "figure.titlesize": validate_fontsize, - "figure.titleweight": validate_fontweight, + "figure.titlesize": validate_fontsize, + "figure.titleweight": validate_fontweight, + "figure.titlehorizontalalign": validate_suptitle_ha, + # Validate that suptitle rcParams are set together to avoid inconsistent defaults + # if someone sets ha but not va (or vice versa) — the pair should change together. + "figure.titleverticalalign": validate_verticalalignment, # figure labels "figure.labelsize": validate_fontsize, @@ -2769,6 +2782,18 @@ class _Subsection: validator=validate_fontweight, description="weight of the figure title" ), + _Param( + "figure.titlehorizontalalign", + default="center", + validator=validate_suptitle_ha, + description="horizontal alignment of the figure title (``Figure.suptitle()``)" + ), + _Param( + "figure.titleverticalalign", + default="top", + validator=validate_verticalalignment, + description="vertical alignment of the figure title (``Figure.suptitle()``)" + ), _Param( "figure.labelsize", default="large", diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index fbfb2515f42e..ea86e00e67a1 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1685,6 +1685,30 @@ def test_rcparams(fig_test, fig_ref): fig_test.suptitle("Title") +def test_suptitle_rcparams_alignment(): + """Test that suptitle respects figure.titlehorizontalalign and titleverticalalign.""" + fig, ax = plt.subplots() + with mpl.rc_context({ + 'figure.titlehorizontalalign': 'left', + 'figure.titleverticalalign': 'center', + }): + txt = fig.suptitle("Title") + assert txt.get_horizontalalignment() == 'left' + assert txt.get_verticalalignment() == 'center' + + +def test_suptitle_rcparams_alignment_override(): + """Test that explicit kwargs override suptitle rcParams defaults.""" + fig, ax = plt.subplots() + with mpl.rc_context({ + 'figure.titlehorizontalalign': 'left', + 'figure.titleverticalalign': 'center', + }): + txt = fig.suptitle("Title", ha='right', va='bottom') + assert txt.get_horizontalalignment() == 'right' + assert txt.get_verticalalignment() == 'bottom' + + def test_deepcopy(): fig1, ax = plt.subplots() ax.plot([0, 1], [2, 3]) diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index a16b8583b663..e23132583d0c 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -318,6 +318,8 @@ "figure.subplot.wspace", "figure.titlesize", "figure.titleweight", + "figure.titlehorizontalalign", + "figure.titleverticalalign", "font.cursive", "font.enable_last_resort", "font.family",