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

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. 

5 

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""" 

11 

12import numpy as np 

13 

14import matplotlib as mpl 

15from matplotlib import _api, artist as martist 

16from matplotlib.font_manager import FontProperties 

17from matplotlib.transforms import Bbox 

18 

19 

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. 

26 

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``. 

30 

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 

49 

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 

55 

56 if len(span_pairs) != len(subplot_list) or len(subplot_list) == 0: 

57 raise ValueError 

58 

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 

65 

66 vspaces = np.zeros((rows + 1, cols)) 

67 hspaces = np.zeros((rows, cols + 1)) 

68 

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] 

73 

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 

78 

79 bb = [] 

80 for ax in subplots: 

81 if ax.get_visible(): 

82 bb += [martist._get_tightbbox_for_layout_only(ax, renderer)] 

83 

84 tight_bbox_raw = Bbox.union(bb) 

85 tight_bbox = fig.transFigure.inverted().transform_bbox(tight_bbox_raw) 

86 

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 

91 

92 fig_width_inch, fig_height_inch = fig.get_size_inches() 

93 

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 

118 

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 

129 

130 kwargs = dict(left=margin_left, 

131 right=1 - margin_right, 

132 bottom=margin_bottom, 

133 top=1 - margin_top) 

134 

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 

156 

157 return kwargs 

158 

159 

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. 

167 

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``. 

172 

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) 

200 

201 

202def get_subplotspec_list(axes_list, grid_spec=None): 

203 """ 

204 Return a list of subplotspec from the given list of axes. 

205 

206 For an instance of axes that does not support subplotspec, None is inserted 

207 in the list. 

208 

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 

217 

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 

230 

231 subplotspec_list.append(subplotspec) 

232 

233 return subplotspec_list 

234 

235 

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. 

240 

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. 

258 

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 """ 

265 

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] 

279 

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) 

282 

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))) 

307 

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) 

314 

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. 

324 

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"]) 

334 

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)) 

342 

343 return kwargs