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 the routine to adjust subplot layouts so that there are 

3no overlapping axes or axes decorations. All axes decorations are dealt with 

4(labels, ticks, titles, ticklabels) and some dependent artists are also dealt 

5with (colorbar, suptitle, legend). 

6 

7Layout is done via :meth:`~matplotlib.gridspec`, with one constraint per 

8gridspec, so it is possible to have overlapping axes if the gridspecs 

9overlap (i.e. using :meth:`~matplotlib.gridspec.GridSpecFromSubplotSpec`). 

10Axes placed using ``figure.subplots()`` or ``figure.add_subplots()`` will 

11participate in the layout. Axes manually placed via ``figure.add_axes()`` 

12will not. 

13 

14See Tutorial: :doc:`/tutorials/intermediate/constrainedlayout_guide` 

15 

16""" 

17 

18# Development Notes: 

19 

20# What gets a layoutbox: 

21# - figure 

22# - gridspec 

23# - subplotspec 

24# EITHER: 

25# - axes + pos for the axes (i.e. the total area taken by axis and 

26# the actual "position" argument that needs to be sent to 

27# ax.set_position.) 

28# - The axes layout box will also encompass the legend, and that is 

29# how legends get included (axes legends, not figure legends) 

30# - colorbars are siblings of the axes if they are single-axes 

31# colorbars 

32# OR: 

33# - a gridspec can be inside a subplotspec. 

34# - subplotspec 

35# EITHER: 

36# - axes... 

37# OR: 

38# - gridspec... with arbitrary nesting... 

39# - colorbars are siblings of the subplotspecs if they are multi-axes 

40# colorbars. 

41# - suptitle: 

42# - right now suptitles are just stacked atop everything else in figure. 

43# Could imagine suptitles being gridspec suptitles, but not implemented 

44# 

45# Todo: AnchoredOffsetbox connected to gridspecs or axes. This would 

46# be more general way to add extra-axes annotations. 

47 

48import logging 

49 

50import numpy as np 

51 

52import matplotlib.cbook as cbook 

53import matplotlib._layoutbox as layoutbox 

54 

55_log = logging.getLogger(__name__) 

56 

57 

58def _in_same_column(colnum0min, colnum0max, colnumCmin, colnumCmax): 

59 return (colnumCmin <= colnum0min <= colnumCmax 

60 or colnumCmin <= colnum0max <= colnumCmax) 

61 

62 

63def _in_same_row(rownum0min, rownum0max, rownumCmin, rownumCmax): 

64 return (rownumCmin <= rownum0min <= rownumCmax 

65 or rownumCmin <= rownum0max <= rownumCmax) 

66 

67 

68def _axes_all_finite_sized(fig): 

69 """Return whether all axes in the figure have a finite width and height.""" 

70 for ax in fig.axes: 

71 if ax._layoutbox is not None: 

72 newpos = ax._poslayoutbox.get_rect() 

73 if newpos[2] <= 0 or newpos[3] <= 0: 

74 return False 

75 return True 

76 

77 

78###################################################### 

79def do_constrained_layout(fig, renderer, h_pad, w_pad, 

80 hspace=None, wspace=None): 

81 """ 

82 Do the constrained_layout. Called at draw time in 

83 ``figure.constrained_layout()`` 

84 

85 Parameters 

86 ---------- 

87 fig : Figure 

88 is the ``figure`` instance to do the layout in. 

89 

90 renderer : Renderer 

91 the renderer to use. 

92 

93 h_pad, w_pad : float 

94 are in figure-normalized units, and are a padding around the axes 

95 elements. 

96 

97 hspace, wspace : float 

98 are in fractions of the subplot sizes. 

99 

100 """ 

101 

102 # Steps: 

103 # 

104 # 1. get a list of unique gridspecs in this figure. Each gridspec will be 

105 # constrained separately. 

106 # 2. Check for gaps in the gridspecs. i.e. if not every axes slot in the 

107 # gridspec has been filled. If empty, add a ghost axis that is made so 

108 # that it cannot be seen (though visible=True). This is needed to make 

109 # a blank spot in the layout. 

110 # 3. Compare the tight_bbox of each axes to its `position`, and assume that 

111 # the difference is the space needed by the elements around the edge of 

112 # the axes (decorations) like the title, ticklabels, x-labels, etc. This 

113 # can include legends who overspill the axes boundaries. 

114 # 4. Constrain gridspec elements to line up: 

115 # a) if colnum0 != colnumC, the two subplotspecs are stacked next to 

116 # each other, with the appropriate order. 

117 # b) if colnum0 == colnumC, line up the left or right side of the 

118 # _poslayoutbox (depending if it is the min or max num that is equal). 

119 # c) do the same for rows... 

120 # 5. The above doesn't constrain relative sizes of the _poslayoutboxes 

121 # at all, and indeed zero-size is a solution that the solver often finds 

122 # more convenient than expanding the sizes. Right now the solution is to 

123 # compare subplotspec sizes (i.e. drowsC and drows0) and constrain the 

124 # larger _poslayoutbox to be larger than the ratio of the sizes. i.e. if 

125 # drows0 > drowsC, then ax._poslayoutbox > axc._poslayoutbox*drowsC/drows0. 

126 # This works fine *if* the decorations are similar between the axes. 

127 # If the larger subplotspec has much larger axes decorations, then the 

128 # constraint above is incorrect. 

129 # 

130 # We need the greater than in the above, in general, rather than an equals 

131 # sign. Consider the case of the left column having 2 rows, and the right 

132 # column having 1 row. We want the top and bottom of the _poslayoutboxes 

133 # to line up. So that means if there are decorations on the left column 

134 # axes they will be smaller than half as large as the right hand axis. 

135 # 

136 # This can break down if the decoration size for the right hand axis (the 

137 # margins) is very large. There must be a math way to check for this case. 

138 

139 invTransFig = fig.transFigure.inverted().transform_bbox 

140 

141 # list of unique gridspecs that contain child axes: 

142 gss = set() 

143 for ax in fig.axes: 

144 if hasattr(ax, 'get_subplotspec'): 

145 gs = ax.get_subplotspec().get_gridspec() 

146 if gs._layoutbox is not None: 

147 gss.add(gs) 

148 if len(gss) == 0: 

149 cbook._warn_external('There are no gridspecs with layoutboxes. ' 

150 'Possibly did not call parent GridSpec with the' 

151 ' figure= keyword') 

152 

153 if fig._layoutbox.constrained_layout_called < 1: 

154 for gs in gss: 

155 # fill in any empty gridspec slots w/ ghost axes... 

156 _make_ghost_gridspec_slots(fig, gs) 

157 

158 for nnn in range(2): 

159 # do the algorithm twice. This has to be done because decorators 

160 # change size after the first re-position (i.e. x/yticklabels get 

161 # larger/smaller). This second reposition tends to be much milder, 

162 # so doing twice makes things work OK. 

163 for ax in fig.axes: 

164 _log.debug(ax._layoutbox) 

165 if ax._layoutbox is not None: 

166 # make margins for each layout box based on the size of 

167 # the decorators. 

168 _make_layout_margins(ax, renderer, h_pad, w_pad) 

169 

170 # do layout for suptitle. 

171 suptitle = fig._suptitle 

172 do_suptitle = (suptitle is not None and 

173 suptitle._layoutbox is not None and 

174 suptitle.get_in_layout()) 

175 if do_suptitle: 

176 bbox = invTransFig( 

177 suptitle.get_window_extent(renderer=renderer)) 

178 height = bbox.y1 - bbox.y0 

179 if np.isfinite(height): 

180 # reserve at top of figure include an h_pad above and below 

181 suptitle._layoutbox.edit_height(height + h_pad * 2) 

182 

183 # OK, the above lines up ax._poslayoutbox with ax._layoutbox 

184 # now we need to 

185 # 1) arrange the subplotspecs. We do it at this level because 

186 # the subplotspecs are meant to contain other dependent axes 

187 # like colorbars or legends. 

188 # 2) line up the right and left side of the ax._poslayoutbox 

189 # that have the same subplotspec maxes. 

190 

191 if fig._layoutbox.constrained_layout_called < 1: 

192 # arrange the subplotspecs... This is all done relative to each 

193 # other. Some subplotspecs contain axes, and others contain 

194 # gridspecs the ones that contain gridspecs are a set proportion 

195 # of their parent gridspec. The ones that contain axes are 

196 # not so constrained. 

197 figlb = fig._layoutbox 

198 for child in figlb.children: 

199 if child._is_gridspec_layoutbox(): 

200 # This routine makes all the subplot spec containers 

201 # have the correct arrangement. It just stacks the 

202 # subplot layoutboxes in the correct order... 

203 _arrange_subplotspecs(child, hspace=hspace, wspace=wspace) 

204 

205 for gs in gss: 

206 _align_spines(fig, gs) 

207 

208 fig._layoutbox.constrained_layout_called += 1 

209 fig._layoutbox.update_variables() 

210 

211 # check if any axes collapsed to zero. If not, don't change positions: 

212 if _axes_all_finite_sized(fig): 

213 # Now set the position of the axes... 

214 for ax in fig.axes: 

215 if ax._layoutbox is not None: 

216 newpos = ax._poslayoutbox.get_rect() 

217 # Now set the new position. 

218 # ax.set_position will zero out the layout for 

219 # this axis, allowing users to hard-code the position, 

220 # so this does the same w/o zeroing layout. 

221 ax._set_position(newpos, which='original') 

222 if do_suptitle: 

223 newpos = suptitle._layoutbox.get_rect() 

224 suptitle.set_y(1.0 - h_pad) 

225 else: 

226 if suptitle is not None and suptitle._layoutbox is not None: 

227 suptitle._layoutbox.edit_height(0) 

228 else: 

229 cbook._warn_external('constrained_layout not applied. At least ' 

230 'one axes collapsed to zero width or height.') 

231 

232 

233def _make_ghost_gridspec_slots(fig, gs): 

234 """ 

235 Check for unoccupied gridspec slots and make ghost axes for these 

236 slots... Do for each gs separately. This is a pretty big kludge 

237 but shouldn't have too much ill effect. The worst is that 

238 someone querying the figure will wonder why there are more 

239 axes than they thought. 

240 """ 

241 nrows, ncols = gs.get_geometry() 

242 hassubplotspec = np.zeros(nrows * ncols, dtype=bool) 

243 axs = [] 

244 for ax in fig.axes: 

245 if (hasattr(ax, 'get_subplotspec') 

246 and ax._layoutbox is not None 

247 and ax.get_subplotspec().get_gridspec() == gs): 

248 axs += [ax] 

249 for ax in axs: 

250 ss0 = ax.get_subplotspec() 

251 hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True 

252 for nn, hss in enumerate(hassubplotspec): 

253 if not hss: 

254 # this gridspec slot doesn't have an axis so we 

255 # make a "ghost". 

256 ax = fig.add_subplot(gs[nn]) 

257 ax.set_visible(False) 

258 

259 

260def _make_layout_margins(ax, renderer, h_pad, w_pad): 

261 """ 

262 For each axes, make a margin between the *pos* layoutbox and the 

263 *axes* layoutbox be a minimum size that can accommodate the 

264 decorations on the axis. 

265 """ 

266 fig = ax.figure 

267 invTransFig = fig.transFigure.inverted().transform_bbox 

268 pos = ax.get_position(original=True) 

269 tightbbox = ax.get_tightbbox(renderer=renderer) 

270 if tightbbox is None: 

271 bbox = pos 

272 else: 

273 bbox = invTransFig(tightbbox) 

274 

275 # this can go wrong: 

276 if not (np.isfinite(bbox.width) and np.isfinite(bbox.height)): 

277 # just abort, this is likely a bad set of co-ordinates that 

278 # is transitory... 

279 return 

280 # use stored h_pad if it exists 

281 h_padt = ax._poslayoutbox.h_pad 

282 if h_padt is None: 

283 h_padt = h_pad 

284 w_padt = ax._poslayoutbox.w_pad 

285 if w_padt is None: 

286 w_padt = w_pad 

287 ax._poslayoutbox.edit_left_margin_min(-bbox.x0 + 

288 pos.x0 + w_padt) 

289 ax._poslayoutbox.edit_right_margin_min(bbox.x1 - 

290 pos.x1 + w_padt) 

291 ax._poslayoutbox.edit_bottom_margin_min( 

292 -bbox.y0 + pos.y0 + h_padt) 

293 ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt) 

294 _log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad)) 

295 _log.debug('right %f', (bbox.x1 - pos.x1 + w_pad)) 

296 _log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt)) 

297 _log.debug('bbox.y0 %f', bbox.y0) 

298 _log.debug('pos.y0 %f', pos.y0) 

299 # Sometimes its possible for the solver to collapse 

300 # rather than expand axes, so they all have zero height 

301 # or width. This stops that... It *should* have been 

302 # taken into account w/ pref_width... 

303 if fig._layoutbox.constrained_layout_called < 1: 

304 ax._poslayoutbox.constrain_height_min(20, strength='weak') 

305 ax._poslayoutbox.constrain_width_min(20, strength='weak') 

306 ax._layoutbox.constrain_height_min(20, strength='weak') 

307 ax._layoutbox.constrain_width_min(20, strength='weak') 

308 ax._poslayoutbox.constrain_top_margin(0, strength='weak') 

309 ax._poslayoutbox.constrain_bottom_margin(0, 

310 strength='weak') 

311 ax._poslayoutbox.constrain_right_margin(0, strength='weak') 

312 ax._poslayoutbox.constrain_left_margin(0, strength='weak') 

313 

314 

315def _align_spines(fig, gs): 

316 """ 

317 - Align right/left and bottom/top spines of appropriate subplots. 

318 - Compare size of subplotspec including height and width ratios 

319 and make sure that the axes spines are at least as large 

320 as they should be. 

321 """ 

322 # for each gridspec... 

323 nrows, ncols = gs.get_geometry() 

324 width_ratios = gs.get_width_ratios() 

325 height_ratios = gs.get_height_ratios() 

326 if width_ratios is None: 

327 width_ratios = np.ones(ncols) 

328 if height_ratios is None: 

329 height_ratios = np.ones(nrows) 

330 

331 # get axes in this gridspec.... 

332 axs = [] 

333 for ax in fig.axes: 

334 if (hasattr(ax, 'get_subplotspec') 

335 and ax._layoutbox is not None): 

336 if ax.get_subplotspec().get_gridspec() == gs: 

337 axs += [ax] 

338 rownummin = np.zeros(len(axs), dtype=np.int8) 

339 rownummax = np.zeros(len(axs), dtype=np.int8) 

340 colnummin = np.zeros(len(axs), dtype=np.int8) 

341 colnummax = np.zeros(len(axs), dtype=np.int8) 

342 width = np.zeros(len(axs)) 

343 height = np.zeros(len(axs)) 

344 

345 for n, ax in enumerate(axs): 

346 ss0 = ax.get_subplotspec() 

347 rownummin[n], colnummin[n] = divmod(ss0.num1, ncols) 

348 rownummax[n], colnummax[n] = divmod(ss0.num2, ncols) 

349 width[n] = np.sum( 

350 width_ratios[colnummin[n]:(colnummax[n] + 1)]) 

351 height[n] = np.sum( 

352 height_ratios[rownummin[n]:(rownummax[n] + 1)]) 

353 

354 for nn, ax in enumerate(axs[:-1]): 

355 # now compare ax to all the axs: 

356 # 

357 # If the subplotspecs have the same colnumXmax, then line 

358 # up their right sides. If they have the same min, then 

359 # line up their left sides (and vertical equivalents). 

360 rownum0min, colnum0min = rownummin[nn], colnummin[nn] 

361 rownum0max, colnum0max = rownummax[nn], colnummax[nn] 

362 width0, height0 = width[nn], height[nn] 

363 alignleft = False 

364 alignright = False 

365 alignbot = False 

366 aligntop = False 

367 alignheight = False 

368 alignwidth = False 

369 for mm in range(nn+1, len(axs)): 

370 axc = axs[mm] 

371 rownumCmin, colnumCmin = rownummin[mm], colnummin[mm] 

372 rownumCmax, colnumCmax = rownummax[mm], colnummax[mm] 

373 widthC, heightC = width[mm], height[mm] 

374 # Horizontally align axes spines if they have the 

375 # same min or max: 

376 if not alignleft and colnum0min == colnumCmin: 

377 # we want the _poslayoutboxes to line up on left 

378 # side of the axes spines... 

379 layoutbox.align([ax._poslayoutbox, 

380 axc._poslayoutbox], 

381 'left') 

382 alignleft = True 

383 

384 if not alignright and colnum0max == colnumCmax: 

385 # line up right sides of _poslayoutbox 

386 layoutbox.align([ax._poslayoutbox, 

387 axc._poslayoutbox], 

388 'right') 

389 alignright = True 

390 # Vertically align axes spines if they have the 

391 # same min or max: 

392 if not aligntop and rownum0min == rownumCmin: 

393 # line up top of _poslayoutbox 

394 _log.debug('rownum0min == rownumCmin') 

395 layoutbox.align([ax._poslayoutbox, axc._poslayoutbox], 

396 'top') 

397 aligntop = True 

398 

399 if not alignbot and rownum0max == rownumCmax: 

400 # line up bottom of _poslayoutbox 

401 _log.debug('rownum0max == rownumCmax') 

402 layoutbox.align([ax._poslayoutbox, axc._poslayoutbox], 

403 'bottom') 

404 alignbot = True 

405 ########### 

406 # Now we make the widths and heights of position boxes 

407 # similar. (i.e the spine locations) 

408 # This allows vertically stacked subplots to have 

409 # different sizes if they occupy different amounts 

410 # of the gridspec: i.e. 

411 # gs = gridspec.GridSpec(3, 1) 

412 # ax1 = gs[0,:] 

413 # ax2 = gs[1:,:] 

414 # then drows0 = 1, and drowsC = 2, and ax2 

415 # should be at least twice as large as ax1. 

416 # But it can be more than twice as large because 

417 # it needs less room for the labeling. 

418 # 

419 # For height, this only needs to be done if the 

420 # subplots share a column. For width if they 

421 # share a row. 

422 

423 drowsC = (rownumCmax - rownumCmin + 1) 

424 drows0 = (rownum0max - rownum0min + 1) 

425 dcolsC = (colnumCmax - colnumCmin + 1) 

426 dcols0 = (colnum0max - colnum0min + 1) 

427 

428 if not alignheight and drows0 == drowsC: 

429 ax._poslayoutbox.constrain_height( 

430 axc._poslayoutbox.height * height0 / heightC) 

431 alignheight = True 

432 elif _in_same_column(colnum0min, colnum0max, 

433 colnumCmin, colnumCmax): 

434 if height0 > heightC: 

435 ax._poslayoutbox.constrain_height_min( 

436 axc._poslayoutbox.height * height0 / heightC) 

437 # these constraints stop the smaller axes from 

438 # being allowed to go to zero height... 

439 axc._poslayoutbox.constrain_height_min( 

440 ax._poslayoutbox.height * heightC / 

441 (height0*1.8)) 

442 elif height0 < heightC: 

443 axc._poslayoutbox.constrain_height_min( 

444 ax._poslayoutbox.height * heightC / height0) 

445 ax._poslayoutbox.constrain_height_min( 

446 ax._poslayoutbox.height * height0 / 

447 (heightC*1.8)) 

448 # widths... 

449 if not alignwidth and dcols0 == dcolsC: 

450 ax._poslayoutbox.constrain_width( 

451 axc._poslayoutbox.width * width0 / widthC) 

452 alignwidth = True 

453 elif _in_same_row(rownum0min, rownum0max, 

454 rownumCmin, rownumCmax): 

455 if width0 > widthC: 

456 ax._poslayoutbox.constrain_width_min( 

457 axc._poslayoutbox.width * width0 / widthC) 

458 axc._poslayoutbox.constrain_width_min( 

459 ax._poslayoutbox.width * widthC / 

460 (width0*1.8)) 

461 elif width0 < widthC: 

462 axc._poslayoutbox.constrain_width_min( 

463 ax._poslayoutbox.width * widthC / width0) 

464 ax._poslayoutbox.constrain_width_min( 

465 axc._poslayoutbox.width * width0 / 

466 (widthC*1.8)) 

467 

468 

469def _arrange_subplotspecs(gs, hspace=0, wspace=0): 

470 """Recursively arrange the subplotspec children of the given gridspec.""" 

471 sschildren = [] 

472 for child in gs.children: 

473 if child._is_subplotspec_layoutbox(): 

474 for child2 in child.children: 

475 # check for gridspec children... 

476 if child2._is_gridspec_layoutbox(): 

477 _arrange_subplotspecs(child2, hspace=hspace, wspace=wspace) 

478 sschildren += [child] 

479 # now arrange the subplots... 

480 for child0 in sschildren: 

481 ss0 = child0.artist 

482 nrows, ncols = ss0.get_gridspec().get_geometry() 

483 rowNum0min, colNum0min = divmod(ss0.num1, ncols) 

484 rowNum0max, colNum0max = divmod(ss0.num2, ncols) 

485 sschildren = sschildren[1:] 

486 for childc in sschildren: 

487 ssc = childc.artist 

488 rowNumCmin, colNumCmin = divmod(ssc.num1, ncols) 

489 rowNumCmax, colNumCmax = divmod(ssc.num2, ncols) 

490 # OK, this tells us the relative layout of ax 

491 # with axc 

492 thepad = wspace / ncols 

493 if colNum0max < colNumCmin: 

494 layoutbox.hstack([ss0._layoutbox, ssc._layoutbox], 

495 padding=thepad) 

496 if colNumCmax < colNum0min: 

497 layoutbox.hstack([ssc._layoutbox, ss0._layoutbox], 

498 padding=thepad) 

499 

500 #### 

501 # vertical alignment 

502 thepad = hspace / nrows 

503 if rowNum0max < rowNumCmin: 

504 layoutbox.vstack([ss0._layoutbox, 

505 ssc._layoutbox], 

506 padding=thepad) 

507 if rowNumCmax < rowNum0min: 

508 layoutbox.vstack([ssc._layoutbox, 

509 ss0._layoutbox], 

510 padding=thepad) 

511 

512 

513def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05): 

514 """ 

515 Do the layout for a colorbar, to not overly pollute colorbar.py 

516 

517 *pad* is in fraction of the original axis size. 

518 """ 

519 axlb = ax._layoutbox 

520 axpos = ax._poslayoutbox 

521 axsslb = ax.get_subplotspec()._layoutbox 

522 lb = layoutbox.LayoutBox( 

523 parent=axsslb, 

524 name=axsslb.name + '.cbar', 

525 artist=cax) 

526 

527 if location in ('left', 'right'): 

528 lbpos = layoutbox.LayoutBox( 

529 parent=lb, 

530 name=lb.name + '.pos', 

531 tightwidth=False, 

532 pos=True, 

533 subplot=False, 

534 artist=cax) 

535 

536 if location == 'right': 

537 # arrange to right of parent axis 

538 layoutbox.hstack([axlb, lb], padding=pad * axlb.width, 

539 strength='strong') 

540 else: 

541 layoutbox.hstack([lb, axlb], padding=pad * axlb.width) 

542 # constrain the height and center... 

543 layoutbox.match_heights([axpos, lbpos], [1, shrink]) 

544 layoutbox.align([axpos, lbpos], 'v_center') 

545 # set the width of the pos box 

546 lbpos.constrain_width(shrink * axpos.height * (1/aspect), 

547 strength='strong') 

548 elif location in ('bottom', 'top'): 

549 lbpos = layoutbox.LayoutBox( 

550 parent=lb, 

551 name=lb.name + '.pos', 

552 tightheight=True, 

553 pos=True, 

554 subplot=False, 

555 artist=cax) 

556 

557 if location == 'bottom': 

558 layoutbox.vstack([axlb, lb], padding=pad * axlb.height) 

559 else: 

560 layoutbox.vstack([lb, axlb], padding=pad * axlb.height) 

561 # constrain the height and center... 

562 layoutbox.match_widths([axpos, lbpos], 

563 [1, shrink], strength='strong') 

564 layoutbox.align([axpos, lbpos], 'h_center') 

565 # set the height of the pos box 

566 lbpos.constrain_height(axpos.width * aspect * shrink, 

567 strength='medium') 

568 

569 return lb, lbpos 

570 

571 

572def _getmaxminrowcolumn(axs): 

573 # helper to get the min/max rows and columns of a list of axes. 

574 maxrow = -100000 

575 minrow = 1000000 

576 maxax = None 

577 minax = None 

578 maxcol = -100000 

579 mincol = 1000000 

580 maxax_col = None 

581 minax_col = None 

582 

583 for ax in axs: 

584 subspec = ax.get_subplotspec() 

585 nrows, ncols, row_start, row_stop, col_start, col_stop = \ 

586 subspec.get_rows_columns() 

587 if row_stop > maxrow: 

588 maxrow = row_stop 

589 maxax = ax 

590 if row_start < minrow: 

591 minrow = row_start 

592 minax = ax 

593 if col_stop > maxcol: 

594 maxcol = col_stop 

595 maxax_col = ax 

596 if col_start < mincol: 

597 mincol = col_start 

598 minax_col = ax 

599 return (minrow, maxrow, minax, maxax, mincol, maxcol, minax_col, maxax_col) 

600 

601 

602def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05): 

603 """ 

604 Do the layout for a colorbar, to not overly pollute colorbar.py 

605 

606 *pad* is in fraction of the original axis size. 

607 """ 

608 

609 gs = parents[0].get_subplotspec().get_gridspec() 

610 # parent layout box.... 

611 gslb = gs._layoutbox 

612 

613 lb = layoutbox.LayoutBox(parent=gslb.parent, 

614 name=gslb.parent.name + '.cbar', 

615 artist=cax) 

616 # figure out the row and column extent of the parents. 

617 (minrow, maxrow, minax_row, maxax_row, 

618 mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents) 

619 

620 if location in ('left', 'right'): 

621 lbpos = layoutbox.LayoutBox( 

622 parent=lb, 

623 name=lb.name + '.pos', 

624 tightwidth=False, 

625 pos=True, 

626 subplot=False, 

627 artist=cax) 

628 for ax in parents: 

629 if location == 'right': 

630 order = [ax._layoutbox, lb] 

631 else: 

632 order = [lb, ax._layoutbox] 

633 layoutbox.hstack(order, padding=pad * gslb.width, 

634 strength='strong') 

635 # constrain the height and center... 

636 # This isn't quite right. We'd like the colorbar 

637 # pos to line up w/ the axes poss, not the size of the 

638 # gs. 

639 

640 # Horizontal Layout: need to check all the axes in this gridspec 

641 for ch in gslb.children: 

642 subspec = ch.artist 

643 nrows, ncols, row_start, row_stop, col_start, col_stop = \ 

644 subspec.get_rows_columns() 

645 if location == 'right': 

646 if col_stop <= maxcol: 

647 order = [subspec._layoutbox, lb] 

648 # arrange to right of the parents 

649 if col_start > maxcol: 

650 order = [lb, subspec._layoutbox] 

651 elif location == 'left': 

652 if col_start >= mincol: 

653 order = [lb, subspec._layoutbox] 

654 if col_stop < mincol: 

655 order = [subspec._layoutbox, lb] 

656 layoutbox.hstack(order, padding=pad * gslb.width, 

657 strength='strong') 

658 

659 # Vertical layout: 

660 maxposlb = minax_row._poslayoutbox 

661 minposlb = maxax_row._poslayoutbox 

662 # now we want the height of the colorbar pos to be 

663 # set by the top and bottom of the min/max axes... 

664 # bottom top 

665 # b t 

666 # h = (top-bottom)*shrink 

667 # b = bottom + (top-bottom - h) / 2. 

668 lbpos.constrain_height( 

669 (maxposlb.top - minposlb.bottom) * 

670 shrink, strength='strong') 

671 lbpos.constrain_bottom( 

672 (maxposlb.top - minposlb.bottom) * 

673 (1 - shrink)/2 + minposlb.bottom, 

674 strength='strong') 

675 

676 # set the width of the pos box 

677 lbpos.constrain_width(lbpos.height * (shrink / aspect), 

678 strength='strong') 

679 elif location in ('bottom', 'top'): 

680 lbpos = layoutbox.LayoutBox( 

681 parent=lb, 

682 name=lb.name + '.pos', 

683 tightheight=True, 

684 pos=True, 

685 subplot=False, 

686 artist=cax) 

687 

688 for ax in parents: 

689 if location == 'bottom': 

690 order = [ax._layoutbox, lb] 

691 else: 

692 order = [lb, ax._layoutbox] 

693 layoutbox.vstack(order, padding=pad * gslb.width, 

694 strength='strong') 

695 

696 # Vertical Layout: need to check all the axes in this gridspec 

697 for ch in gslb.children: 

698 subspec = ch.artist 

699 nrows, ncols, row_start, row_stop, col_start, col_stop = \ 

700 subspec.get_rows_columns() 

701 if location == 'bottom': 

702 if row_stop <= minrow: 

703 order = [subspec._layoutbox, lb] 

704 if row_start > maxrow: 

705 order = [lb, subspec._layoutbox] 

706 elif location == 'top': 

707 if row_stop < minrow: 

708 order = [subspec._layoutbox, lb] 

709 if row_start >= maxrow: 

710 order = [lb, subspec._layoutbox] 

711 layoutbox.vstack(order, padding=pad * gslb.width, 

712 strength='strong') 

713 

714 # Do horizontal layout... 

715 maxposlb = maxax_col._poslayoutbox 

716 minposlb = minax_col._poslayoutbox 

717 lbpos.constrain_width((maxposlb.right - minposlb.left) * 

718 shrink) 

719 lbpos.constrain_left( 

720 (maxposlb.right - minposlb.left) * 

721 (1-shrink)/2 + minposlb.left) 

722 # set the height of the pos box 

723 lbpos.constrain_height(lbpos.width * shrink * aspect, 

724 strength='medium') 

725 

726 return lb, lbpos