diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f8aa99d48a09..441ec703d9cc 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -35,8 +35,10 @@ from matplotlib.axes import Axes, SubplotBase, subplot_class_factory from matplotlib.gridspec import GridSpec -from matplotlib.layout_engine import (ConstrainedLayoutEngine, - TightLayoutEngine, LayoutEngine) +from matplotlib.layout_engine import ( + ConstrainedLayoutEngine, TightLayoutEngine, LayoutEngine, + PlaceHolderLayoutEngine +) import matplotlib.legend as mlegend from matplotlib.patches import Rectangle from matplotlib.text import Text @@ -2382,7 +2384,9 @@ def _check_layout_engines_compat(self, old, new): If the figure has used the old engine and added a colorbar then the value of colorbar_gridspec must be the same on the new engine. """ - if old is None or old.colorbar_gridspec == new.colorbar_gridspec: + if old is None or new is None: + return True + if old.colorbar_gridspec == new.colorbar_gridspec: return True # colorbar layout different, so check if any colorbars are on the # figure... @@ -2398,15 +2402,29 @@ def set_layout_engine(self, layout=None, **kwargs): Parameters ---------- - layout: {'constrained', 'compressed', 'tight'} or `~.LayoutEngine` - 'constrained' will use `~.ConstrainedLayoutEngine`, - 'compressed' will also use ConstrainedLayoutEngine, but with a - correction that attempts to make a good layout for fixed-aspect - ratio Axes. 'tight' uses `~.TightLayoutEngine`. Users and - libraries can define their own layout engines as well. + layout: {'constrained', 'compressed', 'tight', 'none'} or \ +`LayoutEngine` or None + + - 'constrained' will use `~.ConstrainedLayoutEngine` + - 'compressed' will also use `~.ConstrainedLayoutEngine`, but with + a correction that attempts to make a good layout for fixed-aspect + ratio Axes. + - 'tight' uses `~.TightLayoutEngine` + - 'none' removes layout engine. + + If `None`, the behavior is controlled by :rc:`figure.autolayout` + (which if `True` behaves as if 'tight' were passed) and + :rc:`figure.constrained_layout.use` (which if `True` behaves as if + 'constrained' were passed). If both are `True`, + :rc:`figure.autolayout` takes priority. + + Users and libraries can define their own layout engines and pass + the instance directly as well. + kwargs: dict The keyword arguments are passed to the layout engine to set things like padding and margin sizes. Only used if *layout* is a string. + """ if layout is None: if mpl.rcParams['figure.autolayout']: @@ -2423,6 +2441,14 @@ def set_layout_engine(self, layout=None, **kwargs): elif layout == 'compressed': new_layout_engine = ConstrainedLayoutEngine(compress=True, **kwargs) + elif layout == 'none': + if self._layout_engine is not None: + new_layout_engine = PlaceHolderLayoutEngine( + self._layout_engine.adjust_compatible, + self._layout_engine.colorbar_gridspec + ) + else: + new_layout_engine = None elif isinstance(layout, LayoutEngine): new_layout_engine = layout else: diff --git a/lib/matplotlib/layout_engine.py b/lib/matplotlib/layout_engine.py index e0b058e601dd..ee04afef19c7 100644 --- a/lib/matplotlib/layout_engine.py +++ b/lib/matplotlib/layout_engine.py @@ -100,6 +100,30 @@ def execute(self, fig): raise NotImplementedError +class PlaceHolderLayoutEngine(LayoutEngine): + """ + This layout engine does not adjust the figure layout at all. + + The purpose of this `.LayoutEngine` is to act as a place holder when the + user removes a layout engine to ensure an incompatible `.LayoutEngine` can + not be set later. + + Parameters + ---------- + adjust_compatible, colorbar_gridspec : bool + Allow the PlaceHolderLayoutEngine to mirror the behavior of whatever + layout engine it is replacing. + + """ + def __init__(self, adjust_compatible, colorbar_gridspec, **kwargs): + self._adjust_compatible = adjust_compatible + self._colorbar_gridspec = colorbar_gridspec + super().__init__(**kwargs) + + def execute(self, fig): + return + + class TightLayoutEngine(LayoutEngine): """ Implements the ``tight_layout`` geometry management. See diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index ec1a814ebc59..b2da459d6cf9 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -17,7 +17,8 @@ from matplotlib.axes import Axes from matplotlib.figure import Figure, FigureBase from matplotlib.layout_engine import (ConstrainedLayoutEngine, - TightLayoutEngine) + TightLayoutEngine, + PlaceHolderLayoutEngine) from matplotlib.ticker import AutoMinorLocator, FixedFormatter, ScalarFormatter import matplotlib.pyplot as plt import matplotlib.dates as mdates @@ -578,6 +579,9 @@ def test_invalid_layouts(): fig, ax = plt.subplots(layout="constrained") pc = ax.pcolormesh(np.random.randn(2, 2)) fig.colorbar(pc) + with pytest.raises(RuntimeError, match='Colorbar layout of new layout'): + fig.set_layout_engine("tight") + fig.set_layout_engine("none") with pytest.raises(RuntimeError, match='Colorbar layout of new layout'): fig.set_layout_engine("tight") @@ -586,6 +590,11 @@ def test_invalid_layouts(): fig.colorbar(pc) with pytest.raises(RuntimeError, match='Colorbar layout of new layout'): fig.set_layout_engine("constrained") + fig.set_layout_engine("none") + assert isinstance(fig.get_layout_engine(), PlaceHolderLayoutEngine) + + with pytest.raises(RuntimeError, match='Colorbar layout of new layout'): + fig.set_layout_engine("constrained") @check_figures_equal(extensions=["png", "pdf"])
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: