Coverage for /usr/lib/python3/dist-packages/matplotlib/layout_engine.py: 43%
69 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
1"""
2Classes to layout elements in a `.Figure`.
4Figures have a ``layout_engine`` property that holds a subclass of
5`~.LayoutEngine` defined here (or *None* for no layout). At draw time
6``figure.get_layout_engine().execute()`` is called, the goal of which is
7usually to rearrange Axes on the figure to produce a pleasing layout. This is
8like a ``draw`` callback but with two differences. First, when printing we
9disable the layout engine for the final draw. Second, it is useful to know the
10layout engine while the figure is being created. In particular, colorbars are
11made differently with different layout engines (for historical reasons).
13Matplotlib supplies two layout engines, `.TightLayoutEngine` and
14`.ConstrainedLayoutEngine`. Third parties can create their own layout engine
15by subclassing `.LayoutEngine`.
16"""
18from contextlib import nullcontext
20import matplotlib as mpl
22from matplotlib._constrained_layout import do_constrained_layout
23from matplotlib._tight_layout import (get_subplotspec_list,
24 get_tight_layout_figure)
27class LayoutEngine:
28 """
29 Base class for Matplotlib layout engines.
31 A layout engine can be passed to a figure at instantiation or at any time
32 with `~.figure.Figure.set_layout_engine`. Once attached to a figure, the
33 layout engine ``execute`` function is called at draw time by
34 `~.figure.Figure.draw`, providing a special draw-time hook.
36 .. note::
38 However, note that layout engines affect the creation of colorbars, so
39 `~.figure.Figure.set_layout_engine` should be called before any
40 colorbars are created.
42 Currently, there are two properties of `LayoutEngine` classes that are
43 consulted while manipulating the figure:
45 - ``engine.colorbar_gridspec`` tells `.Figure.colorbar` whether to make the
46 axes using the gridspec method (see `.colorbar.make_axes_gridspec`) or
47 not (see `.colorbar.make_axes`);
48 - ``engine.adjust_compatible`` stops `.Figure.subplots_adjust` from being
49 run if it is not compatible with the layout engine.
51 To implement a custom `LayoutEngine`:
53 1. override ``_adjust_compatible`` and ``_colorbar_gridspec``
54 2. override `LayoutEngine.set` to update *self._params*
55 3. override `LayoutEngine.execute` with your implementation
57 """
58 # override these in subclass
59 _adjust_compatible = None
60 _colorbar_gridspec = None
62 def __init__(self, **kwargs):
63 super().__init__(**kwargs)
64 self._params = {}
66 def set(self, **kwargs):
67 raise NotImplementedError
69 @property
70 def colorbar_gridspec(self):
71 """
72 Return a boolean if the layout engine creates colorbars using a
73 gridspec.
74 """
75 if self._colorbar_gridspec is None:
76 raise NotImplementedError
77 return self._colorbar_gridspec
79 @property
80 def adjust_compatible(self):
81 """
82 Return a boolean if the layout engine is compatible with
83 `~.Figure.subplots_adjust`.
84 """
85 if self._adjust_compatible is None:
86 raise NotImplementedError
87 return self._adjust_compatible
89 def get(self):
90 """
91 Return copy of the parameters for the layout engine.
92 """
93 return dict(self._params)
95 def execute(self, fig):
96 """
97 Execute the layout on the figure given by *fig*.
98 """
99 # subclasses must implement this.
100 raise NotImplementedError
103class PlaceHolderLayoutEngine(LayoutEngine):
104 """
105 This layout engine does not adjust the figure layout at all.
107 The purpose of this `.LayoutEngine` is to act as a placeholder when the
108 user removes a layout engine to ensure an incompatible `.LayoutEngine` can
109 not be set later.
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.
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)
123 def execute(self, fig):
124 return
127class TightLayoutEngine(LayoutEngine):
128 """
129 Implements the ``tight_layout`` geometry management. See
130 :doc:`/tutorials/intermediate/tight_layout_guide` for details.
131 """
132 _adjust_compatible = True
133 _colorbar_gridspec = True
135 def __init__(self, *, pad=1.08, h_pad=None, w_pad=None,
136 rect=(0, 0, 1, 1), **kwargs):
137 """
138 Initialize tight_layout engine.
140 Parameters
141 ----------
142 pad : float, 1.08
143 Padding between the figure edge and the edges of subplots, as a
144 fraction of the font size.
145 h_pad, w_pad : float
146 Padding (height/width) between edges of adjacent subplots.
147 Defaults to *pad*.
148 rect : tuple (left, bottom, right, top), default: (0, 0, 1, 1).
149 rectangle in normalized figure coordinates that the subplots
150 (including labels) will fit into.
151 """
152 super().__init__(**kwargs)
153 for td in ['pad', 'h_pad', 'w_pad', 'rect']:
154 # initialize these in case None is passed in above:
155 self._params[td] = None
156 self.set(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
158 def execute(self, fig):
159 """
160 Execute tight_layout.
162 This decides the subplot parameters given the padding that
163 will allow the axes labels to not be covered by other labels
164 and axes.
166 Parameters
167 ----------
168 fig : `.Figure` to perform layout on.
170 See also: `.figure.Figure.tight_layout` and `.pyplot.tight_layout`.
171 """
172 info = self._params
173 renderer = fig._get_renderer()
174 with getattr(renderer, "_draw_disabled", nullcontext)():
175 kwargs = get_tight_layout_figure(
176 fig, fig.axes, get_subplotspec_list(fig.axes), renderer,
177 pad=info['pad'], h_pad=info['h_pad'], w_pad=info['w_pad'],
178 rect=info['rect'])
179 if kwargs:
180 fig.subplots_adjust(**kwargs)
182 def set(self, *, pad=None, w_pad=None, h_pad=None, rect=None):
183 for td in self.set.__kwdefaults__:
184 if locals()[td] is not None:
185 self._params[td] = locals()[td]
188class ConstrainedLayoutEngine(LayoutEngine):
189 """
190 Implements the ``constrained_layout`` geometry management. See
191 :doc:`/tutorials/intermediate/constrainedlayout_guide` for details.
192 """
194 _adjust_compatible = False
195 _colorbar_gridspec = False
197 def __init__(self, *, h_pad=None, w_pad=None,
198 hspace=None, wspace=None, rect=(0, 0, 1, 1),
199 compress=False, **kwargs):
200 """
201 Initialize ``constrained_layout`` settings.
203 Parameters
204 ----------
205 h_pad, w_pad : float
206 Padding around the axes elements in figure-normalized units.
207 Default to :rc:`figure.constrained_layout.h_pad` and
208 :rc:`figure.constrained_layout.w_pad`.
209 hspace, wspace : float
210 Fraction of the figure to dedicate to space between the
211 axes. These are evenly spread between the gaps between the axes.
212 A value of 0.2 for a three-column layout would have a space
213 of 0.1 of the figure width between each column.
214 If h/wspace < h/w_pad, then the pads are used instead.
215 Default to :rc:`figure.constrained_layout.hspace` and
216 :rc:`figure.constrained_layout.wspace`.
217 rect : tuple of 4 floats
218 Rectangle in figure coordinates to perform constrained layout in
219 (left, bottom, width, height), each from 0-1.
220 compress : bool
221 Whether to shift Axes so that white space in between them is
222 removed. This is useful for simple grids of fixed-aspect Axes (e.g.
223 a grid of images). See :ref:`compressed_layout`.
224 """
225 super().__init__(**kwargs)
226 # set the defaults:
227 self.set(w_pad=mpl.rcParams['figure.constrained_layout.w_pad'],
228 h_pad=mpl.rcParams['figure.constrained_layout.h_pad'],
229 wspace=mpl.rcParams['figure.constrained_layout.wspace'],
230 hspace=mpl.rcParams['figure.constrained_layout.hspace'],
231 rect=(0, 0, 1, 1))
232 # set anything that was passed in (None will be ignored):
233 self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace,
234 rect=rect)
235 self._compress = compress
237 def execute(self, fig):
238 """
239 Perform constrained_layout and move and resize axes accordingly.
241 Parameters
242 ----------
243 fig : `.Figure` to perform layout on.
244 """
245 width, height = fig.get_size_inches()
246 # pads are relative to the current state of the figure...
247 w_pad = self._params['w_pad'] / width
248 h_pad = self._params['h_pad'] / height
250 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
251 wspace=self._params['wspace'],
252 hspace=self._params['hspace'],
253 rect=self._params['rect'],
254 compress=self._compress)
256 def set(self, *, h_pad=None, w_pad=None,
257 hspace=None, wspace=None, rect=None):
258 """
259 Set the pads for constrained_layout.
261 Parameters
262 ----------
263 h_pad, w_pad : float
264 Padding around the axes elements in figure-normalized units.
265 Default to :rc:`figure.constrained_layout.h_pad` and
266 :rc:`figure.constrained_layout.w_pad`.
267 hspace, wspace : float
268 Fraction of the figure to dedicate to space between the
269 axes. These are evenly spread between the gaps between the axes.
270 A value of 0.2 for a three-column layout would have a space
271 of 0.1 of the figure width between each column.
272 If h/wspace < h/w_pad, then the pads are used instead.
273 Default to :rc:`figure.constrained_layout.hspace` and
274 :rc:`figure.constrained_layout.wspace`.
275 rect : tuple of 4 floats
276 Rectangle in figure coordinates to perform constrained layout in
277 (left, bottom, width, height), each from 0-1.
278 """
279 for td in self.set.__kwdefaults__:
280 if locals()[td] is not None:
281 self._params[td] = locals()[td]