Coverage for /usr/lib/python3/dist-packages/matplotlib/_tight_layout.py: 7%
142 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"""
2Routines to adjust subplot params so that subplots are
3nicely fit in the figure. In doing so, only axis labels, tick labels, axes
4titles and offsetboxes that are anchored to axes are currently considered.
6Internally, this module assumes that the margins (left margin, etc.) which are
7differences between ``Axes.get_tightbbox`` and ``Axes.bbox`` are independent of
8Axes position. This may fail if ``Axes.adjustable`` is ``datalim`` as well as
9such cases as when left or right margin are affected by xlabel.
10"""
12import numpy as np
14import matplotlib as mpl
15from matplotlib import _api, artist as martist
16from matplotlib.font_manager import FontProperties
17from matplotlib.transforms import Bbox
20def _auto_adjust_subplotpars(
21 fig, renderer, shape, span_pairs, subplot_list,
22 ax_bbox_list=None, pad=1.08, h_pad=None, w_pad=None, rect=None):
23 """
24 Return a dict of subplot parameters to adjust spacing between subplots
25 or ``None`` if resulting axes would have zero height or width.
27 Note that this function ignores geometry information of subplot itself, but
28 uses what is given by the *shape* and *subplot_list* parameters. Also, the
29 results could be incorrect if some subplots have ``adjustable=datalim``.
31 Parameters
32 ----------
33 shape : tuple[int, int]
34 Number of rows and columns of the grid.
35 span_pairs : list[tuple[slice, slice]]
36 List of rowspans and colspans occupied by each subplot.
37 subplot_list : list of subplots
38 List of subplots that will be used to calculate optimal subplot_params.
39 pad : float
40 Padding between the figure edge and the edges of subplots, as a
41 fraction of the font size.
42 h_pad, w_pad : float
43 Padding (height/width) between edges of adjacent subplots, as a
44 fraction of the font size. Defaults to *pad*.
45 rect : tuple
46 (left, bottom, right, top), default: None.
47 """
48 rows, cols = shape
50 font_size_inch = (FontProperties(
51 size=mpl.rcParams["font.size"]).get_size_in_points() / 72)
52 pad_inch = pad * font_size_inch
53 vpad_inch = h_pad * font_size_inch if h_pad is not None else pad_inch
54 hpad_inch = w_pad * font_size_inch if w_pad is not None else pad_inch
56 if len(span_pairs) != len(subplot_list) or len(subplot_list) == 0:
57 raise ValueError
59 if rect is None:
60 margin_left = margin_bottom = margin_right = margin_top = None
61 else:
62 margin_left, margin_bottom, _right, _top = rect
63 margin_right = 1 - _right if _right else None
64 margin_top = 1 - _top if _top else None
66 vspaces = np.zeros((rows + 1, cols))
67 hspaces = np.zeros((rows, cols + 1))
69 if ax_bbox_list is None:
70 ax_bbox_list = [
71 Bbox.union([ax.get_position(original=True) for ax in subplots])
72 for subplots in subplot_list]
74 for subplots, ax_bbox, (rowspan, colspan) in zip(
75 subplot_list, ax_bbox_list, span_pairs):
76 if all(not ax.get_visible() for ax in subplots):
77 continue
79 bb = []
80 for ax in subplots:
81 if ax.get_visible():
82 bb += [martist._get_tightbbox_for_layout_only(ax, renderer)]
84 tight_bbox_raw = Bbox.union(bb)
85 tight_bbox = fig.transFigure.inverted().transform_bbox(tight_bbox_raw)
87 hspaces[rowspan, colspan.start] += ax_bbox.xmin - tight_bbox.xmin # l
88 hspaces[rowspan, colspan.stop] += tight_bbox.xmax - ax_bbox.xmax # r
89 vspaces[rowspan.start, colspan] += tight_bbox.ymax - ax_bbox.ymax # t
90 vspaces[rowspan.stop, colspan] += ax_bbox.ymin - tight_bbox.ymin # b
92 fig_width_inch, fig_height_inch = fig.get_size_inches()
94 # margins can be negative for axes with aspect applied, so use max(, 0) to
95 # make them nonnegative.
96 if not margin_left:
97 margin_left = max(hspaces[:, 0].max(), 0) + pad_inch/fig_width_inch
98 suplabel = fig._supylabel
99 if suplabel and suplabel.get_in_layout():
100 rel_width = fig.transFigure.inverted().transform_bbox(
101 suplabel.get_window_extent(renderer)).width
102 margin_left += rel_width + pad_inch/fig_width_inch
103 if not margin_right:
104 margin_right = max(hspaces[:, -1].max(), 0) + pad_inch/fig_width_inch
105 if not margin_top:
106 margin_top = max(vspaces[0, :].max(), 0) + pad_inch/fig_height_inch
107 if fig._suptitle and fig._suptitle.get_in_layout():
108 rel_height = fig.transFigure.inverted().transform_bbox(
109 fig._suptitle.get_window_extent(renderer)).height
110 margin_top += rel_height + pad_inch/fig_height_inch
111 if not margin_bottom:
112 margin_bottom = max(vspaces[-1, :].max(), 0) + pad_inch/fig_height_inch
113 suplabel = fig._supxlabel
114 if suplabel and suplabel.get_in_layout():
115 rel_height = fig.transFigure.inverted().transform_bbox(
116 suplabel.get_window_extent(renderer)).height
117 margin_bottom += rel_height + pad_inch/fig_height_inch
119 if margin_left + margin_right >= 1:
120 _api.warn_external('Tight layout not applied. The left and right '
121 'margins cannot be made large enough to '
122 'accommodate all axes decorations.')
123 return None
124 if margin_bottom + margin_top >= 1:
125 _api.warn_external('Tight layout not applied. The bottom and top '
126 'margins cannot be made large enough to '
127 'accommodate all axes decorations.')
128 return None
130 kwargs = dict(left=margin_left,
131 right=1 - margin_right,
132 bottom=margin_bottom,
133 top=1 - margin_top)
135 if cols > 1:
136 hspace = hspaces[:, 1:-1].max() + hpad_inch / fig_width_inch
137 # axes widths:
138 h_axes = (1 - margin_right - margin_left - hspace * (cols - 1)) / cols
139 if h_axes < 0:
140 _api.warn_external('Tight layout not applied. tight_layout '
141 'cannot make axes width small enough to '
142 'accommodate all axes decorations')
143 return None
144 else:
145 kwargs["wspace"] = hspace / h_axes
146 if rows > 1:
147 vspace = vspaces[1:-1, :].max() + vpad_inch / fig_height_inch
148 v_axes = (1 - margin_top - margin_bottom - vspace * (rows - 1)) / rows
149 if v_axes < 0:
150 _api.warn_external('Tight layout not applied. tight_layout '
151 'cannot make axes height small enough to '
152 'accommodate all axes decorations.')
153 return None
154 else:
155 kwargs["hspace"] = vspace / v_axes
157 return kwargs
160@_api.deprecated("3.5")
161def auto_adjust_subplotpars(
162 fig, renderer, nrows_ncols, num1num2_list, subplot_list,
163 ax_bbox_list=None, pad=1.08, h_pad=None, w_pad=None, rect=None):
164 """
165 Return a dict of subplot parameters to adjust spacing between subplots
166 or ``None`` if resulting axes would have zero height or width.
168 Note that this function ignores geometry information of subplot
169 itself, but uses what is given by the *nrows_ncols* and *num1num2_list*
170 parameters. Also, the results could be incorrect if some subplots have
171 ``adjustable=datalim``.
173 Parameters
174 ----------
175 nrows_ncols : tuple[int, int]
176 Number of rows and number of columns of the grid.
177 num1num2_list : list[tuple[int, int]]
178 List of numbers specifying the area occupied by the subplot
179 subplot_list : list of subplots
180 List of subplots that will be used to calculate optimal subplot_params.
181 pad : float
182 Padding between the figure edge and the edges of subplots, as a
183 fraction of the font size.
184 h_pad, w_pad : float
185 Padding (height/width) between edges of adjacent subplots, as a
186 fraction of the font size. Defaults to *pad*.
187 rect : tuple
188 (left, bottom, right, top), default: None.
189 """
190 nrows, ncols = nrows_ncols
191 span_pairs = []
192 for n1, n2 in num1num2_list:
193 if n2 is None:
194 n2 = n1
195 span_pairs.append((slice(n1 // ncols, n2 // ncols + 1),
196 slice(n1 % ncols, n2 % ncols + 1)))
197 return _auto_adjust_subplotpars(
198 fig, renderer, nrows_ncols, num1num2_list, subplot_list,
199 ax_bbox_list, pad, h_pad, w_pad, rect)
202def get_subplotspec_list(axes_list, grid_spec=None):
203 """
204 Return a list of subplotspec from the given list of axes.
206 For an instance of axes that does not support subplotspec, None is inserted
207 in the list.
209 If grid_spec is given, None is inserted for those not from the given
210 grid_spec.
211 """
212 subplotspec_list = []
213 for ax in axes_list:
214 axes_or_locator = ax.get_axes_locator()
215 if axes_or_locator is None:
216 axes_or_locator = ax
218 if hasattr(axes_or_locator, "get_subplotspec"):
219 subplotspec = axes_or_locator.get_subplotspec()
220 if subplotspec is not None:
221 subplotspec = subplotspec.get_topmost_subplotspec()
222 gs = subplotspec.get_gridspec()
223 if grid_spec is not None:
224 if gs != grid_spec:
225 subplotspec = None
226 elif gs.locally_modified_subplot_params():
227 subplotspec = None
228 else:
229 subplotspec = None
231 subplotspec_list.append(subplotspec)
233 return subplotspec_list
236def get_tight_layout_figure(fig, axes_list, subplotspec_list, renderer,
237 pad=1.08, h_pad=None, w_pad=None, rect=None):
238 """
239 Return subplot parameters for tight-layouted-figure with specified padding.
241 Parameters
242 ----------
243 fig : Figure
244 axes_list : list of Axes
245 subplotspec_list : list of `.SubplotSpec`
246 The subplotspecs of each axes.
247 renderer : renderer
248 pad : float
249 Padding between the figure edge and the edges of subplots, as a
250 fraction of the font size.
251 h_pad, w_pad : float
252 Padding (height/width) between edges of adjacent subplots. Defaults to
253 *pad*.
254 rect : tuple (left, bottom, right, top), default: None.
255 rectangle in normalized figure coordinates
256 that the whole subplots area (including labels) will fit into.
257 Defaults to using the entire figure.
259 Returns
260 -------
261 subplotspec or None
262 subplotspec kwargs to be passed to `.Figure.subplots_adjust` or
263 None if tight_layout could not be accomplished.
264 """
266 # Multiple axes can share same subplotspec (e.g., if using axes_grid1);
267 # we need to group them together.
268 ss_to_subplots = {ss: [] for ss in subplotspec_list}
269 for ax, ss in zip(axes_list, subplotspec_list):
270 ss_to_subplots[ss].append(ax)
271 if ss_to_subplots.pop(None, None):
272 _api.warn_external(
273 "This figure includes Axes that are not compatible with "
274 "tight_layout, so results might be incorrect.")
275 if not ss_to_subplots:
276 return {}
277 subplot_list = list(ss_to_subplots.values())
278 ax_bbox_list = [ss.get_position(fig) for ss in ss_to_subplots]
280 max_nrows = max(ss.get_gridspec().nrows for ss in ss_to_subplots)
281 max_ncols = max(ss.get_gridspec().ncols for ss in ss_to_subplots)
283 span_pairs = []
284 for ss in ss_to_subplots:
285 # The intent here is to support axes from different gridspecs where
286 # one's nrows (or ncols) is a multiple of the other (e.g. 2 and 4),
287 # but this doesn't actually work because the computed wspace, in
288 # relative-axes-height, corresponds to different physical spacings for
289 # the 2-row grid and the 4-row grid. Still, this code is left, mostly
290 # for backcompat.
291 rows, cols = ss.get_gridspec().get_geometry()
292 div_row, mod_row = divmod(max_nrows, rows)
293 div_col, mod_col = divmod(max_ncols, cols)
294 if mod_row != 0:
295 _api.warn_external('tight_layout not applied: number of rows '
296 'in subplot specifications must be '
297 'multiples of one another.')
298 return {}
299 if mod_col != 0:
300 _api.warn_external('tight_layout not applied: number of '
301 'columns in subplot specifications must be '
302 'multiples of one another.')
303 return {}
304 span_pairs.append((
305 slice(ss.rowspan.start * div_row, ss.rowspan.stop * div_row),
306 slice(ss.colspan.start * div_col, ss.colspan.stop * div_col)))
308 kwargs = _auto_adjust_subplotpars(fig, renderer,
309 shape=(max_nrows, max_ncols),
310 span_pairs=span_pairs,
311 subplot_list=subplot_list,
312 ax_bbox_list=ax_bbox_list,
313 pad=pad, h_pad=h_pad, w_pad=w_pad)
315 # kwargs can be none if tight_layout fails...
316 if rect is not None and kwargs is not None:
317 # if rect is given, the whole subplots area (including
318 # labels) will fit into the rect instead of the
319 # figure. Note that the rect argument of
320 # *auto_adjust_subplotpars* specify the area that will be
321 # covered by the total area of axes.bbox. Thus we call
322 # auto_adjust_subplotpars twice, where the second run
323 # with adjusted rect parameters.
325 left, bottom, right, top = rect
326 if left is not None:
327 left += kwargs["left"]
328 if bottom is not None:
329 bottom += kwargs["bottom"]
330 if right is not None:
331 right -= (1 - kwargs["right"])
332 if top is not None:
333 top -= (1 - kwargs["top"])
335 kwargs = _auto_adjust_subplotpars(fig, renderer,
336 shape=(max_nrows, max_ncols),
337 span_pairs=span_pairs,
338 subplot_list=subplot_list,
339 ax_bbox_list=ax_bbox_list,
340 pad=pad, h_pad=h_pad, w_pad=w_pad,
341 rect=(left, bottom, right, top))
343 return kwargs