Skip to content

Commit 1ff8d49

Browse files
tacaswelljklymak
andcommitted
ENH: add ability to remove layout engine
This also adds a "place holder" layout engine to ensure that users can not "go through zero" and change to an incompatible layout engine. Co-authored-by: Jody Klymak <jklymak@gmail.com>
1 parent 3eadeac commit 1ff8d49

File tree

3 files changed

+70
-10
lines changed

3 files changed

+70
-10
lines changed

lib/matplotlib/figure.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535

3636
from matplotlib.axes import Axes, SubplotBase, subplot_class_factory
3737
from matplotlib.gridspec import GridSpec
38-
from matplotlib.layout_engine import (ConstrainedLayoutEngine,
39-
TightLayoutEngine, LayoutEngine)
38+
from matplotlib.layout_engine import (
39+
ConstrainedLayoutEngine, TightLayoutEngine, LayoutEngine,
40+
PlaceHolderLayoutEngine
41+
)
4042
import matplotlib.legend as mlegend
4143
from matplotlib.patches import Rectangle
4244
from matplotlib.text import Text
@@ -2382,7 +2384,9 @@ def _check_layout_engines_compat(self, old, new):
23822384
If the figure has used the old engine and added a colorbar then the
23832385
value of colorbar_gridspec must be the same on the new engine.
23842386
"""
2385-
if old is None or old.colorbar_gridspec == new.colorbar_gridspec:
2387+
if old is None or new is None:
2388+
return True
2389+
if old.colorbar_gridspec == new.colorbar_gridspec:
23862390
return True
23872391
# colorbar layout different, so check if any colorbars are on the
23882392
# figure...
@@ -2398,15 +2402,29 @@ def set_layout_engine(self, layout=None, **kwargs):
23982402
23992403
Parameters
24002404
----------
2401-
layout: {'constrained', 'compressed', 'tight'} or `~.LayoutEngine`
2402-
'constrained' will use `~.ConstrainedLayoutEngine`,
2403-
'compressed' will also use ConstrainedLayoutEngine, but with a
2404-
correction that attempts to make a good layout for fixed-aspect
2405-
ratio Axes. 'tight' uses `~.TightLayoutEngine`. Users and
2406-
libraries can define their own layout engines as well.
2405+
layout: {'constrained', 'compressed', 'tight', 'none'} or \
2406+
`LayoutEngine` or None
2407+
2408+
- 'constrained' will use `~.ConstrainedLayoutEngine`
2409+
- 'compressed' will also use `~.ConstrainedLayoutEngine`, but with
2410+
a correction that attempts to make a good layout for fixed-aspect
2411+
ratio Axes.
2412+
- 'tight' uses `~.TightLayoutEngine`
2413+
- 'none' removes layout engine.
2414+
2415+
If `None`, the behavior is controlled by :rc:`figure.autolayout`
2416+
(which if `True` behaves as if `'tight'` were passed) and
2417+
:rc:`figure.constrained_layout.use` (which if true behaves as if
2418+
`'constrained'` were passed). If both are true,
2419+
:rc:`figure.autolayout` takes priority.
2420+
2421+
Users and libraries can define their own layout engines and pass
2422+
the instance directly as well.
2423+
24072424
kwargs: dict
24082425
The keyword arguments are passed to the layout engine to set things
24092426
like padding and margin sizes. Only used if *layout* is a string.
2427+
24102428
"""
24112429
if layout is None:
24122430
if mpl.rcParams['figure.autolayout']:
@@ -2423,6 +2441,14 @@ def set_layout_engine(self, layout=None, **kwargs):
24232441
elif layout == 'compressed':
24242442
new_layout_engine = ConstrainedLayoutEngine(compress=True,
24252443
**kwargs)
2444+
elif layout == 'none':
2445+
if self._layout_engine is not None:
2446+
new_layout_engine = PlaceHolderLayoutEngine(
2447+
self._layout_engine.adjust_compatible,
2448+
self._layout_engine.colorbar_gridspec
2449+
)
2450+
else:
2451+
new_layout_engine = None
24262452
elif isinstance(layout, LayoutEngine):
24272453
new_layout_engine = layout
24282454
else:

lib/matplotlib/layout_engine.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,30 @@ def execute(self, fig):
100100
raise NotImplementedError
101101

102102

103+
class PlaceHolderLayoutEngine(LayoutEngine):
104+
"""
105+
This layout engine does not adjust the figure layout at all.
106+
107+
The purpose of this `.LayoutEngine` is to act as a place holder when the
108+
user removes a layout engine to ensure an incompatible `.LayoutEngine` can
109+
not be set later.
110+
111+
Parameters
112+
----------
113+
adjust_compatible, colorbar_gridspec : bool
114+
Allow the PlaceHolderLayoutEngine to mirror the behavior of whatever
115+
layout engine it is replacing.
116+
117+
"""
118+
def __init__(self, adjust_compatible, colorbar_gridspec, **kwargs):
119+
self._adjust_compatible = adjust_compatible
120+
self._colorbar_gridspec = colorbar_gridspec
121+
super().__init__(**kwargs)
122+
123+
def execute(self, fig):
124+
return
125+
126+
103127
class TightLayoutEngine(LayoutEngine):
104128
"""
105129
Implements the ``tight_layout`` geometry management. See

lib/matplotlib/tests/test_figure.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
from matplotlib.axes import Axes
1818
from matplotlib.figure import Figure, FigureBase
1919
from matplotlib.layout_engine import (ConstrainedLayoutEngine,
20-
TightLayoutEngine)
20+
TightLayoutEngine,
21+
PlaceHolderLayoutEngine)
2122
from matplotlib.ticker import AutoMinorLocator, FixedFormatter, ScalarFormatter
2223
import matplotlib.pyplot as plt
2324
import matplotlib.dates as mdates
@@ -578,6 +579,9 @@ def test_invalid_layouts():
578579
fig, ax = plt.subplots(layout="constrained")
579580
pc = ax.pcolormesh(np.random.randn(2, 2))
580581
fig.colorbar(pc)
582+
with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
583+
fig.set_layout_engine("tight")
584+
fig.set_layout_engine("none")
581585
with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
582586
fig.set_layout_engine("tight")
583587

@@ -586,6 +590,12 @@ def test_invalid_layouts():
586590
fig.colorbar(pc)
587591
with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
588592
fig.set_layout_engine("constrained")
593+
fig.set_layout_engine("none")
594+
with pytest.raises(RuntimeError, match='Colorbar layout of new layout'):
595+
fig.set_layout_engine("constrained")
596+
597+
fig.set_layout_engine("none")
598+
assert isinstance(fig.get_layout_engine(), PlaceHolderLayoutEngine)
589599

590600

591601
@check_figures_equal(extensions=["png", "pdf"])

0 commit comments

Comments
 (0)