From e4289833f48cc85e5656348cd3a0661a8f9d3985 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 7 Apr 2021 23:01:39 +0200 Subject: [PATCH 1/2] Add Figure parameter layout and discourage tight_layout / constrained_layout --- .../deprecations/19892-TH.rst | 12 ++++ lib/matplotlib/figure.py | 67 +++++++++++++++---- 2 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/19892-TH.rst diff --git a/doc/api/next_api_changes/deprecations/19892-TH.rst b/doc/api/next_api_changes/deprecations/19892-TH.rst new file mode 100644 index 000000000000..7db621376d35 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/19892-TH.rst @@ -0,0 +1,12 @@ +Discouraged: ``Figure`` parameters *tight_layout* and *constrained_layout* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``Figure`` parameters *tight_layout* and *constrained_layout* are +triggering competing layout mechanisms and thus should not be used together. + +To make the API clearer, we've merged them under the new parameter *layout* +with values 'constrained' (equal to ``constrained_layout=True``), 'tight' +(equal to ``tight_layout=True``). If given *layout* takes precedence. + +The use of *tight_layout* and *constrained_layout* is discouraged in favor +of *layout*. However, these parameters will stay available for backward +compatibility. diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f1a6711cb678..c21d34f4c8d3 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2149,6 +2149,8 @@ def __init__(self, subplotpars=None, # rc figure.subplot.* tight_layout=None, # rc figure.autolayout constrained_layout=None, # rc figure.constrained_layout.use + *, + layout=None, **kwargs ): """ @@ -2178,19 +2180,42 @@ def __init__(self, parameters :rc:`figure.subplot.*` are used. tight_layout : bool or dict, default: :rc:`figure.autolayout` - If ``False`` use *subplotpars*. If ``True`` adjust subplot - parameters using `.tight_layout` with default padding. - When providing a dict containing the keys ``pad``, ``w_pad``, - ``h_pad``, and ``rect``, the default `.tight_layout` paddings - will be overridden. + Whether to use the tight layout mechanism. See `.set_tight_layout`. + + .. admonition:: Discouraged + + The use of this parameter is discouraged. Please use + ``layout='tight'`` instead for the common case of + ``tight_layout=True`` and use `.set_tight_layout` otherwise. constrained_layout : bool, default: :rc:`figure.constrained_layout.use` - If ``True`` use constrained layout to adjust positioning of plot - elements. Like ``tight_layout``, but designed to be more - flexible. See - :doc:`/tutorials/intermediate/constrainedlayout_guide` - for examples. (Note: does not work with `add_subplot` or - `~.pyplot.subplot2grid`.) + This is equal to ``layout='constrained'``. + + .. admonition:: Discouraged + + The use of this parameter is discouraged. Please use + ``layout='constrained'`` instead. + + layout : {'constrained', 'tight'}, optional + The layout mechanism for positioning of plot elements. + Supported values: + + - 'constrained': The constrained layout solver usually gives the + best layout results and is thus recommended. However, it is + computationally expensive and can be slow for complex figures + with many elements. + + See :doc:`/tutorials/intermediate/constrainedlayout_guide` + for examples. + + - 'tight': Use the tight layout mechanism. This is a relatively + simple algorithm, that adjusts the subplot parameters so that + decorations like tick labels, axis labels and titles have enough + space. See `.Figure.set_tight_layout` for further details. + + If not given, fall back to using the parameters *tight_layout* and + *constrained_layout*, including their config defaults + :rc:`figure.autolayout` and :rc:`figure.constrained_layout.use`. Other Parameters ---------------- @@ -2200,6 +2225,24 @@ def __init__(self, """ super().__init__(**kwargs) + if layout is not None: + if tight_layout is not None: + _api.warn_external( + "The Figure parameters 'layout' and 'tight_layout' " + "cannot be used together. Please use 'layout' only.") + if constrained_layout is not None: + _api.warn_external( + "The Figure parameters 'layout' and 'constrained_layout' " + "cannot be used together. Please use 'layout' only.") + if layout == 'constrained': + tight_layout = False + constrained_layout = True + elif layout == 'tight': + tight_layout = True + constrained_layout = False + else: + raise ValueError(f"Invalid value for 'layout': {layout!r}") + self.callbacks = cbook.CallbackRegistry() # Callbacks traditionally associated with the canvas (and exposed with # a proxy property), but that actually need to be on the figure for @@ -2362,7 +2405,7 @@ def set_tight_layout(self, tight): ---------- tight : bool or dict with keys "pad", "w_pad", "h_pad", "rect" or None If a bool, sets whether to call `.tight_layout` upon drawing. - If ``None``, use the ``figure.autolayout`` rcparam instead. + If ``None``, use :rc:`figure.autolayout` instead. If a dict, pass it as kwargs to `.tight_layout`, overriding the default paddings. """ From a83134f2faec283d5f38e7625782493300fffbb7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 23 Aug 2021 21:41:40 -0400 Subject: [PATCH 2/2] Add tests for new Figure layout argument. --- lib/matplotlib/figure.py | 2 +- lib/matplotlib/tests/test_figure.py | 33 ++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index c21d34f4c8d3..abe8ec694922 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2241,7 +2241,7 @@ def __init__(self, tight_layout = True constrained_layout = False else: - raise ValueError(f"Invalid value for 'layout': {layout!r}") + _api.check_in_list(['constrained', 'tight'], layout=layout) self.callbacks = cbook.CallbackRegistry() # Callbacks traditionally associated with the canvas (and exposed with diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index ad2fdf7fecc9..7a222f20a058 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -501,13 +501,44 @@ def test_figure_repr(): assert repr(fig) == "
" -def test_warn_cl_plus_tl(): +def test_valid_layouts(): + fig = Figure(layout=None) + assert not fig.get_tight_layout() + assert not fig.get_constrained_layout() + + fig = Figure(layout='tight') + assert fig.get_tight_layout() + assert not fig.get_constrained_layout() + + fig = Figure(layout='constrained') + assert not fig.get_tight_layout() + assert fig.get_constrained_layout() + + +def test_invalid_layouts(): fig, ax = plt.subplots(constrained_layout=True) with pytest.warns(UserWarning): # this should warn, fig.subplots_adjust(top=0.8) assert not(fig.get_constrained_layout()) + # Using layout + (tight|constrained)_layout warns, but the former takes + # precedence. + with pytest.warns(UserWarning, match="Figure parameters 'layout' and " + "'tight_layout' cannot"): + fig = Figure(layout='tight', tight_layout=False) + assert fig.get_tight_layout() + assert not fig.get_constrained_layout() + with pytest.warns(UserWarning, match="Figure parameters 'layout' and " + "'constrained_layout' cannot"): + fig = Figure(layout='constrained', constrained_layout=False) + assert not fig.get_tight_layout() + assert fig.get_constrained_layout() + + with pytest.raises(ValueError, + match="'foobar' is not a valid value for layout"): + Figure(layout='foobar') + @check_figures_equal(extensions=["png", "pdf"]) def test_add_artist(fig_test, fig_ref):