Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/_tight_layout.py: 6%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

133 statements  

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 

160def get_subplotspec_list(axes_list, grid_spec=None): 

161 """ 

162 Return a list of subplotspec from the given list of Axes. 

163 

164 For an instance of Axes that does not support subplotspec, None is inserted 

165 in the list. 

166 

167 If grid_spec is given, None is inserted for those not from the given 

168 grid_spec. 

169 """ 

170 subplotspec_list = [] 

171 for ax in axes_list: 

172 axes_or_locator = ax.get_axes_locator() 

173 if axes_or_locator is None: 

174 axes_or_locator = ax 

175 

176 if hasattr(axes_or_locator, "get_subplotspec"): 

177 subplotspec = axes_or_locator.get_subplotspec() 

178 if subplotspec is not None: 

179 subplotspec = subplotspec.get_topmost_subplotspec() 

180 gs = subplotspec.get_gridspec() 

181 if grid_spec is not None: 

182 if gs != grid_spec: 

183 subplotspec = None 

184 elif gs.locally_modified_subplot_params(): 

185 subplotspec = None 

186 else: 

187 subplotspec = None 

188 

189 subplotspec_list.append(subplotspec) 

190 

191 return subplotspec_list 

192 

193 

194def get_tight_layout_figure(fig, axes_list, subplotspec_list, renderer, 

195 pad=1.08, h_pad=None, w_pad=None, rect=None): 

196 """ 

197 Return subplot parameters for tight-layouted-figure with specified padding. 

198 

199 Parameters 

200 ---------- 

201 fig : Figure 

202 axes_list : list of Axes 

203 subplotspec_list : list of `.SubplotSpec` 

204 The subplotspecs of each Axes. 

205 renderer : renderer 

206 pad : float 

207 Padding between the figure edge and the edges of subplots, as a 

208 fraction of the font size. 

209 h_pad, w_pad : float 

210 Padding (height/width) between edges of adjacent subplots. Defaults to 

211 *pad*. 

212 rect : tuple (left, bottom, right, top), default: None. 

213 rectangle in normalized figure coordinates 

214 that the whole subplots area (including labels) will fit into. 

215 Defaults to using the entire figure. 

216 

217 Returns 

218 ------- 

219 subplotspec or None 

220 subplotspec kwargs to be passed to `.Figure.subplots_adjust` or 

221 None if tight_layout could not be accomplished. 

222 """ 

223 

224 # Multiple Axes can share same subplotspec (e.g., if using axes_grid1); 

225 # we need to group them together. 

226 ss_to_subplots = {ss: [] for ss in subplotspec_list} 

227 for ax, ss in zip(axes_list, subplotspec_list): 

228 ss_to_subplots[ss].append(ax) 

229 if ss_to_subplots.pop(None, None): 

230 _api.warn_external( 

231 "This figure includes Axes that are not compatible with " 

232 "tight_layout, so results might be incorrect.") 

233 if not ss_to_subplots: 

234 return {} 

235 subplot_list = list(ss_to_subplots.values()) 

236 ax_bbox_list = [ss.get_position(fig) for ss in ss_to_subplots] 

237 

238 max_nrows = max(ss.get_gridspec().nrows for ss in ss_to_subplots) 

239 max_ncols = max(ss.get_gridspec().ncols for ss in ss_to_subplots) 

240 

241 span_pairs = [] 

242 for ss in ss_to_subplots: 

243 # The intent here is to support Axes from different gridspecs where 

244 # one's nrows (or ncols) is a multiple of the other (e.g. 2 and 4), 

245 # but this doesn't actually work because the computed wspace, in 

246 # relative-axes-height, corresponds to different physical spacings for 

247 # the 2-row grid and the 4-row grid. Still, this code is left, mostly 

248 # for backcompat. 

249 rows, cols = ss.get_gridspec().get_geometry() 

250 div_row, mod_row = divmod(max_nrows, rows) 

251 div_col, mod_col = divmod(max_ncols, cols) 

252 if mod_row != 0: 

253 _api.warn_external('tight_layout not applied: number of rows ' 

254 'in subplot specifications must be ' 

255 'multiples of one another.') 

256 return {} 

257 if mod_col != 0: 

258 _api.warn_external('tight_layout not applied: number of ' 

259 'columns in subplot specifications must be ' 

260 'multiples of one another.') 

261 return {} 

262 span_pairs.append(( 

263 slice(ss.rowspan.start * div_row, ss.rowspan.stop * div_row), 

264 slice(ss.colspan.start * div_col, ss.colspan.stop * div_col))) 

265 

266 kwargs = _auto_adjust_subplotpars(fig, renderer, 

267 shape=(max_nrows, max_ncols), 

268 span_pairs=span_pairs, 

269 subplot_list=subplot_list, 

270 ax_bbox_list=ax_bbox_list, 

271 pad=pad, h_pad=h_pad, w_pad=w_pad) 

272 

273 # kwargs can be none if tight_layout fails... 

274 if rect is not None and kwargs is not None: 

275 # if rect is given, the whole subplots area (including 

276 # labels) will fit into the rect instead of the 

277 # figure. Note that the rect argument of 

278 # *auto_adjust_subplotpars* specify the area that will be 

279 # covered by the total area of axes.bbox. Thus we call 

280 # auto_adjust_subplotpars twice, where the second run 

281 # with adjusted rect parameters. 

282 

283 left, bottom, right, top = rect 

284 if left is not None: 

285 left += kwargs["left"] 

286 if bottom is not None: 

287 bottom += kwargs["bottom"] 

288 if right is not None: 

289 right -= (1 - kwargs["right"]) 

290 if top is not None: 

291 top -= (1 - kwargs["top"]) 

292 

293 kwargs = _auto_adjust_subplotpars(fig, renderer, 

294 shape=(max_nrows, max_ncols), 

295 span_pairs=span_pairs, 

296 subplot_list=subplot_list, 

297 ax_bbox_list=ax_bbox_list, 

298 pad=pad, h_pad=h_pad, w_pad=w_pad, 

299 rect=(left, bottom, right, top)) 

300 

301 return kwargs