Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2This module provides routines 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, it assumes that the margins (left_margin, etc.) which are 

7differences between ax.get_tightbbox and ax.bbox are independent of axes 

8position. This may fail if Axes.adjustable is datalim. Also, This will fail 

9for some cases (for example, left or right margin is affected by xlabel). 

10""" 

11 

12from matplotlib import cbook, rcParams 

13from matplotlib.font_manager import FontProperties 

14from matplotlib.transforms import TransformedBbox, Bbox 

15 

16 

17def _get_left(tight_bbox, axes_bbox): 

18 return axes_bbox.xmin - tight_bbox.xmin 

19 

20 

21def _get_right(tight_bbox, axes_bbox): 

22 return tight_bbox.xmax - axes_bbox.xmax 

23 

24 

25def _get_bottom(tight_bbox, axes_bbox): 

26 return axes_bbox.ymin - tight_bbox.ymin 

27 

28 

29def _get_top(tight_bbox, axes_bbox): 

30 return tight_bbox.ymax - axes_bbox.ymax 

31 

32 

33def auto_adjust_subplotpars( 

34 fig, renderer, nrows_ncols, num1num2_list, subplot_list, 

35 ax_bbox_list=None, pad=1.08, h_pad=None, w_pad=None, rect=None): 

36 """ 

37 Return a dict of subplot parameters to adjust spacing between subplots 

38 or ``None`` if resulting axes would have zero height or width. 

39 

40 Note that this function ignores geometry information of subplot 

41 itself, but uses what is given by the *nrows_ncols* and *num1num2_list* 

42 parameters. Also, the results could be incorrect if some subplots have 

43 ``adjustable=datalim``. 

44 

45 Parameters 

46 ---------- 

47 nrows_ncols : Tuple[int, int] 

48 Number of rows and number of columns of the grid. 

49 num1num2_list : List[int] 

50 List of numbers specifying the area occupied by the subplot 

51 subplot_list : list of subplots 

52 List of subplots that will be used to calculate optimal subplot_params. 

53 pad : float 

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

55 fraction of the font size. 

56 h_pad, w_pad : float 

57 Padding (height/width) between edges of adjacent subplots, as a 

58 fraction of the font size. Defaults to *pad*. 

59 rect : Tuple[float, float, float, float] 

60 [left, bottom, right, top] in normalized (0, 1) figure coordinates. 

61 """ 

62 rows, cols = nrows_ncols 

63 

64 font_size_inches = ( 

65 FontProperties(size=rcParams["font.size"]).get_size_in_points() / 72) 

66 pad_inches = pad * font_size_inches 

67 if h_pad is not None: 

68 vpad_inches = h_pad * font_size_inches 

69 else: 

70 vpad_inches = pad_inches 

71 

72 if w_pad is not None: 

73 hpad_inches = w_pad * font_size_inches 

74 else: 

75 hpad_inches = pad_inches 

76 

77 if len(num1num2_list) != len(subplot_list) or len(subplot_list) == 0: 

78 raise ValueError 

79 

80 if rect is None: 

81 margin_left = margin_bottom = margin_right = margin_top = None 

82 else: 

83 margin_left, margin_bottom, _right, _top = rect 

84 if _right: 

85 margin_right = 1 - _right 

86 else: 

87 margin_right = None 

88 if _top: 

89 margin_top = 1 - _top 

90 else: 

91 margin_top = None 

92 

93 vspaces = [[] for i in range((rows + 1) * cols)] 

94 hspaces = [[] for i in range(rows * (cols + 1))] 

95 

96 union = Bbox.union 

97 

98 if ax_bbox_list is None: 

99 ax_bbox_list = [ 

100 union([ax.get_position(original=True) for ax in subplots]) 

101 for subplots in subplot_list] 

102 

103 for subplots, ax_bbox, (num1, num2) in zip(subplot_list, 

104 ax_bbox_list, 

105 num1num2_list): 

106 if all(not ax.get_visible() for ax in subplots): 

107 continue 

108 

109 tight_bbox_raw = union([ax.get_tightbbox(renderer) for ax in subplots 

110 if ax.get_visible()]) 

111 tight_bbox = TransformedBbox(tight_bbox_raw, 

112 fig.transFigure.inverted()) 

113 

114 row1, col1 = divmod(num1, cols) 

115 

116 if num2 is None: 

117 # left 

118 hspaces[row1 * (cols + 1) + col1].append( 

119 _get_left(tight_bbox, ax_bbox)) 

120 # right 

121 hspaces[row1 * (cols + 1) + (col1 + 1)].append( 

122 _get_right(tight_bbox, ax_bbox)) 

123 # top 

124 vspaces[row1 * cols + col1].append( 

125 _get_top(tight_bbox, ax_bbox)) 

126 # bottom 

127 vspaces[(row1 + 1) * cols + col1].append( 

128 _get_bottom(tight_bbox, ax_bbox)) 

129 

130 else: 

131 row2, col2 = divmod(num2, cols) 

132 

133 for row_i in range(row1, row2 + 1): 

134 # left 

135 hspaces[row_i * (cols + 1) + col1].append( 

136 _get_left(tight_bbox, ax_bbox)) 

137 # right 

138 hspaces[row_i * (cols + 1) + (col2 + 1)].append( 

139 _get_right(tight_bbox, ax_bbox)) 

140 for col_i in range(col1, col2 + 1): 

141 # top 

142 vspaces[row1 * cols + col_i].append( 

143 _get_top(tight_bbox, ax_bbox)) 

144 # bottom 

145 vspaces[(row2 + 1) * cols + col_i].append( 

146 _get_bottom(tight_bbox, ax_bbox)) 

147 

148 fig_width_inch, fig_height_inch = fig.get_size_inches() 

149 

150 # margins can be negative for axes with aspect applied. And we 

151 # append + [0] to make minimum margins 0 

152 

153 if not margin_left: 

154 margin_left = max([sum(s) for s in hspaces[::cols + 1]] + [0]) 

155 margin_left += pad_inches / fig_width_inch 

156 

157 if not margin_right: 

158 margin_right = max([sum(s) for s in hspaces[cols::cols + 1]] + [0]) 

159 margin_right += pad_inches / fig_width_inch 

160 

161 if not margin_top: 

162 margin_top = max([sum(s) for s in vspaces[:cols]] + [0]) 

163 margin_top += pad_inches / fig_height_inch 

164 

165 if not margin_bottom: 

166 margin_bottom = max([sum(s) for s in vspaces[-cols:]] + [0]) 

167 margin_bottom += pad_inches / fig_height_inch 

168 

169 if margin_left + margin_right >= 1: 

170 cbook._warn_external('Tight layout not applied. The left and right ' 

171 'margins cannot be made large enough to ' 

172 'accommodate all axes decorations. ') 

173 return None 

174 if margin_bottom + margin_top >= 1: 

175 cbook._warn_external('Tight layout not applied. The bottom and top ' 

176 'margins cannot be made large enough to ' 

177 'accommodate all axes decorations. ') 

178 return None 

179 

180 kwargs = dict(left=margin_left, 

181 right=1 - margin_right, 

182 bottom=margin_bottom, 

183 top=1 - margin_top) 

184 if cols > 1: 

185 hspace = ( 

186 max(sum(s) 

187 for i in range(rows) 

188 for s in hspaces[i * (cols + 1) + 1:(i + 1) * (cols + 1) - 1]) 

189 + hpad_inches / fig_width_inch) 

190 # axes widths: 

191 h_axes = (1 - margin_right - margin_left - hspace * (cols - 1)) / cols 

192 if h_axes < 0: 

193 cbook._warn_external('Tight layout not applied. tight_layout ' 

194 'cannot make axes width small enough to ' 

195 'accommodate all axes decorations') 

196 return None 

197 else: 

198 kwargs["wspace"] = hspace / h_axes 

199 

200 if rows > 1: 

201 vspace = (max(sum(s) for s in vspaces[cols:-cols]) 

202 + vpad_inches / fig_height_inch) 

203 v_axes = (1 - margin_top - margin_bottom - vspace * (rows - 1)) / rows 

204 if v_axes < 0: 

205 cbook._warn_external('Tight layout not applied. tight_layout ' 

206 'cannot make axes height small enough to ' 

207 'accommodate all axes decorations') 

208 return None 

209 else: 

210 kwargs["hspace"] = vspace / v_axes 

211 

212 return kwargs 

213 

214 

215def get_renderer(fig): 

216 if fig._cachedRenderer: 

217 renderer = fig._cachedRenderer 

218 else: 

219 canvas = fig.canvas 

220 

221 if canvas and hasattr(canvas, "get_renderer"): 

222 renderer = canvas.get_renderer() 

223 else: # Some noninteractive backends have no renderer until draw time. 

224 cbook._warn_external("tight_layout: falling back to Agg renderer") 

225 from matplotlib.backends.backend_agg import FigureCanvasAgg 

226 canvas = FigureCanvasAgg(fig) 

227 renderer = canvas.get_renderer() 

228 

229 return renderer 

230 

231 

232def get_subplotspec_list(axes_list, grid_spec=None): 

233 """Return a list of subplotspec from the given list of axes. 

234 

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

236 in the list. 

237 

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

239 grid_spec. 

240 """ 

241 subplotspec_list = [] 

242 for ax in axes_list: 

243 axes_or_locator = ax.get_axes_locator() 

244 if axes_or_locator is None: 

245 axes_or_locator = ax 

246 

247 if hasattr(axes_or_locator, "get_subplotspec"): 

248 subplotspec = axes_or_locator.get_subplotspec() 

249 subplotspec = subplotspec.get_topmost_subplotspec() 

250 gs = subplotspec.get_gridspec() 

251 if grid_spec is not None: 

252 if gs != grid_spec: 

253 subplotspec = None 

254 elif gs.locally_modified_subplot_params(): 

255 subplotspec = None 

256 else: 

257 subplotspec = None 

258 

259 subplotspec_list.append(subplotspec) 

260 

261 return subplotspec_list 

262 

263 

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

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

266 """ 

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

268 

269 Parameters 

270 ---------- 

271 fig : Figure 

272 axes_list : list of Axes 

273 subplotspec_list : list of `.SubplotSpec` 

274 The subplotspecs of each axes. 

275 renderer : renderer 

276 pad : float 

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

278 fraction of the font size. 

279 h_pad, w_pad : float 

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

281 *pad*. 

282 rect : Tuple[float, float, float, float], optional 

283 (left, bottom, right, top) rectangle in normalized figure coordinates 

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

285 Defaults to using the entire figure. 

286 

287 Returns 

288 ------- 

289 subplotspec or None 

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

291 None if tight_layout could not be accomplished. 

292 

293 """ 

294 

295 subplot_list = [] 

296 nrows_list = [] 

297 ncols_list = [] 

298 ax_bbox_list = [] 

299 

300 # Multiple axes can share same subplot_interface (e.g., axes_grid1); thus 

301 # we need to join them together. 

302 subplot_dict = {} 

303 

304 subplotspec_list2 = [] 

305 

306 for ax, subplotspec in zip(axes_list, subplotspec_list): 

307 if subplotspec is None: 

308 continue 

309 

310 subplots = subplot_dict.setdefault(subplotspec, []) 

311 

312 if not subplots: 

313 myrows, mycols, _, _ = subplotspec.get_geometry() 

314 nrows_list.append(myrows) 

315 ncols_list.append(mycols) 

316 subplotspec_list2.append(subplotspec) 

317 subplot_list.append(subplots) 

318 ax_bbox_list.append(subplotspec.get_position(fig)) 

319 

320 subplots.append(ax) 

321 

322 if len(nrows_list) == 0 or len(ncols_list) == 0: 

323 return {} 

324 

325 max_nrows = max(nrows_list) 

326 max_ncols = max(ncols_list) 

327 

328 num1num2_list = [] 

329 for subplotspec in subplotspec_list2: 

330 rows, cols, num1, num2 = subplotspec.get_geometry() 

331 div_row, mod_row = divmod(max_nrows, rows) 

332 div_col, mod_col = divmod(max_ncols, cols) 

333 if mod_row != 0: 

334 cbook._warn_external('tight_layout not applied: number of rows ' 

335 'in subplot specifications must be ' 

336 'multiples of one another.') 

337 return {} 

338 if mod_col != 0: 

339 cbook._warn_external('tight_layout not applied: number of ' 

340 'columns in subplot specifications must be ' 

341 'multiples of one another.') 

342 return {} 

343 

344 rowNum1, colNum1 = divmod(num1, cols) 

345 if num2 is None: 

346 rowNum2, colNum2 = rowNum1, colNum1 

347 else: 

348 rowNum2, colNum2 = divmod(num2, cols) 

349 

350 num1num2_list.append((rowNum1 * div_row * max_ncols + 

351 colNum1 * div_col, 

352 ((rowNum2 + 1) * div_row - 1) * max_ncols + 

353 (colNum2 + 1) * div_col - 1)) 

354 

355 kwargs = auto_adjust_subplotpars(fig, renderer, 

356 nrows_ncols=(max_nrows, max_ncols), 

357 num1num2_list=num1num2_list, 

358 subplot_list=subplot_list, 

359 ax_bbox_list=ax_bbox_list, 

360 pad=pad, h_pad=h_pad, w_pad=w_pad) 

361 

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

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

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

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

366 # figure. Note that the rect argument of 

367 # *auto_adjust_subplotpars* specify the area that will be 

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

369 # auto_adjust_subplotpars twice, where the second run 

370 # with adjusted rect parameters. 

371 

372 left, bottom, right, top = rect 

373 if left is not None: 

374 left += kwargs["left"] 

375 if bottom is not None: 

376 bottom += kwargs["bottom"] 

377 if right is not None: 

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

379 if top is not None: 

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

381 

382 kwargs = auto_adjust_subplotpars(fig, renderer, 

383 nrows_ncols=(max_nrows, max_ncols), 

384 num1num2_list=num1num2_list, 

385 subplot_list=subplot_list, 

386 ax_bbox_list=ax_bbox_list, 

387 pad=pad, h_pad=h_pad, w_pad=w_pad, 

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

389 

390 return kwargs