_constrained_layout.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. """
  2. Adjust subplot layouts so that there are no overlapping axes or axes
  3. decorations. All axes decorations are dealt with (labels, ticks, titles,
  4. ticklabels) and some dependent artists are also dealt with (colorbar, suptitle,
  5. legend).
  6. Layout is done via `~matplotlib.gridspec`, with one constraint per gridspec,
  7. so it is possible to have overlapping axes if the gridspecs overlap (i.e.
  8. using `~matplotlib.gridspec.GridSpecFromSubplotSpec`). Axes placed using
  9. ``figure.subplots()`` or ``figure.add_subplots()`` will participate in the
  10. layout. Axes manually placed via ``figure.add_axes()`` will not.
  11. See Tutorial: :doc:`/tutorials/intermediate/constrainedlayout_guide`
  12. """
  13. # Development Notes:
  14. # What gets a layoutbox:
  15. # - figure
  16. # - gridspec
  17. # - subplotspec
  18. # EITHER:
  19. # - axes + pos for the axes (i.e. the total area taken by axis and
  20. # the actual "position" argument that needs to be sent to
  21. # ax.set_position.)
  22. # - The axes layout box will also encompass the legend, and that is
  23. # how legends get included (axes legends, not figure legends)
  24. # - colorbars are siblings of the axes if they are single-axes
  25. # colorbars
  26. # OR:
  27. # - a gridspec can be inside a subplotspec.
  28. # - subplotspec
  29. # EITHER:
  30. # - axes...
  31. # OR:
  32. # - gridspec... with arbitrary nesting...
  33. # - colorbars are siblings of the subplotspecs if they are multi-axes
  34. # colorbars.
  35. # - suptitle:
  36. # - right now suptitles are just stacked atop everything else in figure.
  37. # Could imagine suptitles being gridspec suptitles, but not implemented
  38. #
  39. # Todo: AnchoredOffsetbox connected to gridspecs or axes. This would
  40. # be more general way to add extra-axes annotations.
  41. import logging
  42. import numpy as np
  43. import matplotlib.cbook as cbook
  44. import matplotlib._layoutbox as layoutbox
  45. _log = logging.getLogger(__name__)
  46. def _spans_overlap(span0, span1):
  47. return span0.start in span1 or span1.start in span0
  48. def _axes_all_finite_sized(fig):
  49. """Return whether all axes in the figure have a finite width and height."""
  50. for ax in fig.axes:
  51. if ax._layoutbox is not None:
  52. newpos = ax._poslayoutbox.get_rect()
  53. if newpos[2] <= 0 or newpos[3] <= 0:
  54. return False
  55. return True
  56. ######################################################
  57. def do_constrained_layout(fig, renderer, h_pad, w_pad,
  58. hspace=None, wspace=None):
  59. """
  60. Do the constrained_layout. Called at draw time in
  61. ``figure.constrained_layout()``
  62. Parameters
  63. ----------
  64. fig : Figure
  65. is the ``figure`` instance to do the layout in.
  66. renderer : Renderer
  67. the renderer to use.
  68. h_pad, w_pad : float
  69. are in figure-normalized units, and are a padding around the axes
  70. elements.
  71. hspace, wspace : float
  72. are in fractions of the subplot sizes.
  73. """
  74. # Steps:
  75. #
  76. # 1. get a list of unique gridspecs in this figure. Each gridspec will be
  77. # constrained separately.
  78. # 2. Check for gaps in the gridspecs. i.e. if not every axes slot in the
  79. # gridspec has been filled. If empty, add a ghost axis that is made so
  80. # that it cannot be seen (though visible=True). This is needed to make
  81. # a blank spot in the layout.
  82. # 3. Compare the tight_bbox of each axes to its `position`, and assume that
  83. # the difference is the space needed by the elements around the edge of
  84. # the axes (decorations) like the title, ticklabels, x-labels, etc. This
  85. # can include legends who overspill the axes boundaries.
  86. # 4. Constrain gridspec elements to line up:
  87. # a) if colnum0 != colnumC, the two subplotspecs are stacked next to
  88. # each other, with the appropriate order.
  89. # b) if colnum0 == colnumC, line up the left or right side of the
  90. # _poslayoutbox (depending if it is the min or max num that is equal).
  91. # c) do the same for rows...
  92. # 5. The above doesn't constrain relative sizes of the _poslayoutboxes
  93. # at all, and indeed zero-size is a solution that the solver often finds
  94. # more convenient than expanding the sizes. Right now the solution is to
  95. # compare subplotspec sizes (i.e. drowsC and drows0) and constrain the
  96. # larger _poslayoutbox to be larger than the ratio of the sizes. i.e. if
  97. # drows0 > drowsC, then ax._poslayoutbox > axc._poslayoutbox*drowsC/drows0.
  98. # This works fine *if* the decorations are similar between the axes.
  99. # If the larger subplotspec has much larger axes decorations, then the
  100. # constraint above is incorrect.
  101. #
  102. # We need the greater than in the above, in general, rather than an equals
  103. # sign. Consider the case of the left column having 2 rows, and the right
  104. # column having 1 row. We want the top and bottom of the _poslayoutboxes
  105. # to line up. So that means if there are decorations on the left column
  106. # axes they will be smaller than half as large as the right hand axis.
  107. #
  108. # This can break down if the decoration size for the right hand axis (the
  109. # margins) is very large. There must be a math way to check for this case.
  110. invTransFig = fig.transFigure.inverted().transform_bbox
  111. # list of unique gridspecs that contain child axes:
  112. gss = set()
  113. for ax in fig.axes:
  114. if hasattr(ax, 'get_subplotspec'):
  115. gs = ax.get_subplotspec().get_gridspec()
  116. if gs._layoutbox is not None:
  117. gss.add(gs)
  118. if len(gss) == 0:
  119. cbook._warn_external('There are no gridspecs with layoutboxes. '
  120. 'Possibly did not call parent GridSpec with the'
  121. ' figure= keyword')
  122. if fig._layoutbox.constrained_layout_called < 1:
  123. for gs in gss:
  124. # fill in any empty gridspec slots w/ ghost axes...
  125. _make_ghost_gridspec_slots(fig, gs)
  126. for _ in range(2):
  127. # do the algorithm twice. This has to be done because decorators
  128. # change size after the first re-position (i.e. x/yticklabels get
  129. # larger/smaller). This second reposition tends to be much milder,
  130. # so doing twice makes things work OK.
  131. for ax in fig.axes:
  132. _log.debug(ax._layoutbox)
  133. if ax._layoutbox is not None:
  134. # make margins for each layout box based on the size of
  135. # the decorators.
  136. _make_layout_margins(ax, renderer, h_pad, w_pad)
  137. # do layout for suptitle.
  138. suptitle = fig._suptitle
  139. do_suptitle = (suptitle is not None and
  140. suptitle._layoutbox is not None and
  141. suptitle.get_in_layout())
  142. if do_suptitle:
  143. bbox = invTransFig(
  144. suptitle.get_window_extent(renderer=renderer))
  145. height = bbox.height
  146. if np.isfinite(height):
  147. # reserve at top of figure include an h_pad above and below
  148. suptitle._layoutbox.edit_height(height + h_pad * 2)
  149. # OK, the above lines up ax._poslayoutbox with ax._layoutbox
  150. # now we need to
  151. # 1) arrange the subplotspecs. We do it at this level because
  152. # the subplotspecs are meant to contain other dependent axes
  153. # like colorbars or legends.
  154. # 2) line up the right and left side of the ax._poslayoutbox
  155. # that have the same subplotspec maxes.
  156. if fig._layoutbox.constrained_layout_called < 1:
  157. # arrange the subplotspecs... This is all done relative to each
  158. # other. Some subplotspecs contain axes, and others contain
  159. # gridspecs the ones that contain gridspecs are a set proportion
  160. # of their parent gridspec. The ones that contain axes are
  161. # not so constrained.
  162. figlb = fig._layoutbox
  163. for child in figlb.children:
  164. if child._is_gridspec_layoutbox():
  165. # This routine makes all the subplot spec containers
  166. # have the correct arrangement. It just stacks the
  167. # subplot layoutboxes in the correct order...
  168. _arrange_subplotspecs(child, hspace=hspace, wspace=wspace)
  169. for gs in gss:
  170. _align_spines(fig, gs)
  171. fig._layoutbox.constrained_layout_called += 1
  172. fig._layoutbox.update_variables()
  173. # check if any axes collapsed to zero. If not, don't change positions:
  174. if _axes_all_finite_sized(fig):
  175. # Now set the position of the axes...
  176. for ax in fig.axes:
  177. if ax._layoutbox is not None:
  178. newpos = ax._poslayoutbox.get_rect()
  179. # Now set the new position.
  180. # ax.set_position will zero out the layout for
  181. # this axis, allowing users to hard-code the position,
  182. # so this does the same w/o zeroing layout.
  183. ax._set_position(newpos, which='original')
  184. if do_suptitle:
  185. newpos = suptitle._layoutbox.get_rect()
  186. suptitle.set_y(1.0 - h_pad)
  187. else:
  188. if suptitle is not None and suptitle._layoutbox is not None:
  189. suptitle._layoutbox.edit_height(0)
  190. else:
  191. cbook._warn_external('constrained_layout not applied. At least '
  192. 'one axes collapsed to zero width or height.')
  193. def _make_ghost_gridspec_slots(fig, gs):
  194. """
  195. Check for unoccupied gridspec slots and make ghost axes for these
  196. slots... Do for each gs separately. This is a pretty big kludge
  197. but shouldn't have too much ill effect. The worst is that
  198. someone querying the figure will wonder why there are more
  199. axes than they thought.
  200. """
  201. nrows, ncols = gs.get_geometry()
  202. hassubplotspec = np.zeros(nrows * ncols, dtype=bool)
  203. axs = []
  204. for ax in fig.axes:
  205. if (hasattr(ax, 'get_subplotspec')
  206. and ax._layoutbox is not None
  207. and ax.get_subplotspec().get_gridspec() == gs):
  208. axs += [ax]
  209. for ax in axs:
  210. ss0 = ax.get_subplotspec()
  211. hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True
  212. for nn, hss in enumerate(hassubplotspec):
  213. if not hss:
  214. # this gridspec slot doesn't have an axis so we
  215. # make a "ghost".
  216. ax = fig.add_subplot(gs[nn])
  217. ax.set_visible(False)
  218. def _make_layout_margins(ax, renderer, h_pad, w_pad):
  219. """
  220. For each axes, make a margin between the *pos* layoutbox and the
  221. *axes* layoutbox be a minimum size that can accommodate the
  222. decorations on the axis.
  223. """
  224. fig = ax.figure
  225. invTransFig = fig.transFigure.inverted().transform_bbox
  226. pos = ax.get_position(original=True)
  227. try:
  228. tightbbox = ax.get_tightbbox(renderer=renderer, for_layout_only=True)
  229. except TypeError:
  230. tightbbox = ax.get_tightbbox(renderer=renderer)
  231. if tightbbox is None:
  232. bbox = pos
  233. else:
  234. bbox = invTransFig(tightbbox)
  235. # this can go wrong:
  236. if not (np.isfinite(bbox.width) and np.isfinite(bbox.height)):
  237. # just abort, this is likely a bad set of coordinates that
  238. # is transitory...
  239. return
  240. # use stored h_pad if it exists
  241. h_padt = ax._poslayoutbox.h_pad
  242. if h_padt is None:
  243. h_padt = h_pad
  244. w_padt = ax._poslayoutbox.w_pad
  245. if w_padt is None:
  246. w_padt = w_pad
  247. ax._poslayoutbox.edit_left_margin_min(-bbox.x0 + pos.x0 + w_padt)
  248. ax._poslayoutbox.edit_right_margin_min(bbox.x1 - pos.x1 + w_padt)
  249. ax._poslayoutbox.edit_bottom_margin_min(-bbox.y0 + pos.y0 + h_padt)
  250. ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt)
  251. _log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad))
  252. _log.debug('right %f', (bbox.x1 - pos.x1 + w_pad))
  253. _log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt))
  254. _log.debug('bbox.y0 %f', bbox.y0)
  255. _log.debug('pos.y0 %f', pos.y0)
  256. # Sometimes its possible for the solver to collapse
  257. # rather than expand axes, so they all have zero height
  258. # or width. This stops that... It *should* have been
  259. # taken into account w/ pref_width...
  260. if fig._layoutbox.constrained_layout_called < 1:
  261. ax._poslayoutbox.constrain_height_min(20, strength='weak')
  262. ax._poslayoutbox.constrain_width_min(20, strength='weak')
  263. ax._layoutbox.constrain_height_min(20, strength='weak')
  264. ax._layoutbox.constrain_width_min(20, strength='weak')
  265. ax._poslayoutbox.constrain_top_margin(0, strength='weak')
  266. ax._poslayoutbox.constrain_bottom_margin(0, strength='weak')
  267. ax._poslayoutbox.constrain_right_margin(0, strength='weak')
  268. ax._poslayoutbox.constrain_left_margin(0, strength='weak')
  269. def _align_spines(fig, gs):
  270. """
  271. - Align right/left and bottom/top spines of appropriate subplots.
  272. - Compare size of subplotspec including height and width ratios
  273. and make sure that the axes spines are at least as large
  274. as they should be.
  275. """
  276. # for each gridspec...
  277. nrows, ncols = gs.get_geometry()
  278. width_ratios = gs.get_width_ratios()
  279. height_ratios = gs.get_height_ratios()
  280. if width_ratios is None:
  281. width_ratios = np.ones(ncols)
  282. if height_ratios is None:
  283. height_ratios = np.ones(nrows)
  284. # get axes in this gridspec....
  285. axs = [ax for ax in fig.axes
  286. if (hasattr(ax, 'get_subplotspec')
  287. and ax._layoutbox is not None
  288. and ax.get_subplotspec().get_gridspec() == gs)]
  289. rowspans = []
  290. colspans = []
  291. heights = []
  292. widths = []
  293. for ax in axs:
  294. ss0 = ax.get_subplotspec()
  295. rowspan = ss0.rowspan
  296. colspan = ss0.colspan
  297. rowspans.append(rowspan)
  298. colspans.append(colspan)
  299. heights.append(sum(height_ratios[rowspan.start:rowspan.stop]))
  300. widths.append(sum(width_ratios[colspan.start:colspan.stop]))
  301. for idx0, ax0 in enumerate(axs):
  302. # Compare ax to all other axs: If the subplotspecs start (/stop) at
  303. # the same column, then line up their left (/right) sides; likewise
  304. # for rows/top/bottom.
  305. rowspan0 = rowspans[idx0]
  306. colspan0 = colspans[idx0]
  307. height0 = heights[idx0]
  308. width0 = widths[idx0]
  309. alignleft = False
  310. alignright = False
  311. alignbot = False
  312. aligntop = False
  313. alignheight = False
  314. alignwidth = False
  315. for idx1 in range(idx0 + 1, len(axs)):
  316. ax1 = axs[idx1]
  317. rowspan1 = rowspans[idx1]
  318. colspan1 = colspans[idx1]
  319. width1 = widths[idx1]
  320. height1 = heights[idx1]
  321. # Horizontally align axes spines if they have the same min or max:
  322. if not alignleft and colspan0.start == colspan1.start:
  323. _log.debug('same start columns; line up layoutbox lefts')
  324. layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
  325. 'left')
  326. alignleft = True
  327. if not alignright and colspan0.stop == colspan1.stop:
  328. _log.debug('same stop columns; line up layoutbox rights')
  329. layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
  330. 'right')
  331. alignright = True
  332. # Vertically align axes spines if they have the same min or max:
  333. if not aligntop and rowspan0.start == rowspan1.start:
  334. _log.debug('same start rows; line up layoutbox tops')
  335. layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
  336. 'top')
  337. aligntop = True
  338. if not alignbot and rowspan0.stop == rowspan1.stop:
  339. _log.debug('same stop rows; line up layoutbox bottoms')
  340. layoutbox.align([ax0._poslayoutbox, ax1._poslayoutbox],
  341. 'bottom')
  342. alignbot = True
  343. # Now we make the widths and heights of position boxes
  344. # similar. (i.e the spine locations)
  345. # This allows vertically stacked subplots to have different sizes
  346. # if they occupy different amounts of the gridspec, e.g. if
  347. # gs = gridspec.GridSpec(3, 1)
  348. # ax0 = gs[0, :]
  349. # ax1 = gs[1:, :]
  350. # then len(rowspan0) = 1, and len(rowspan1) = 2,
  351. # and ax1 should be at least twice as large as ax0.
  352. # But it can be more than twice as large because
  353. # it needs less room for the labeling.
  354. # For heights, do it if the subplots share a column.
  355. if not alignheight and len(rowspan0) == len(rowspan1):
  356. ax0._poslayoutbox.constrain_height(
  357. ax1._poslayoutbox.height * height0 / height1)
  358. alignheight = True
  359. elif _spans_overlap(colspan0, colspan1):
  360. if height0 > height1:
  361. ax0._poslayoutbox.constrain_height_min(
  362. ax1._poslayoutbox.height * height0 / height1)
  363. elif height0 < height1:
  364. ax1._poslayoutbox.constrain_height_min(
  365. ax0._poslayoutbox.height * height1 / height0)
  366. # For widths, do it if the subplots share a row.
  367. if not alignwidth and len(colspan0) == len(colspan1):
  368. ax0._poslayoutbox.constrain_width(
  369. ax1._poslayoutbox.width * width0 / width1)
  370. alignwidth = True
  371. elif _spans_overlap(rowspan0, rowspan1):
  372. if width0 > width1:
  373. ax0._poslayoutbox.constrain_width_min(
  374. ax1._poslayoutbox.width * width0 / width1)
  375. elif width0 < width1:
  376. ax1._poslayoutbox.constrain_width_min(
  377. ax0._poslayoutbox.width * width1 / width0)
  378. def _arrange_subplotspecs(gs, hspace=0, wspace=0):
  379. """Recursively arrange the subplotspec children of the given gridspec."""
  380. sschildren = []
  381. for child in gs.children:
  382. if child._is_subplotspec_layoutbox():
  383. for child2 in child.children:
  384. # check for gridspec children...
  385. if child2._is_gridspec_layoutbox():
  386. _arrange_subplotspecs(child2, hspace=hspace, wspace=wspace)
  387. sschildren += [child]
  388. # now arrange the subplots...
  389. for child0 in sschildren:
  390. ss0 = child0.artist
  391. nrows, ncols = ss0.get_gridspec().get_geometry()
  392. rowspan0 = ss0.rowspan
  393. colspan0 = ss0.colspan
  394. sschildren = sschildren[1:]
  395. for child1 in sschildren:
  396. ss1 = child1.artist
  397. rowspan1 = ss1.rowspan
  398. colspan1 = ss1.colspan
  399. # OK, this tells us the relative layout of child0 with child1.
  400. pad = wspace / ncols
  401. if colspan0.stop <= colspan1.start:
  402. layoutbox.hstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
  403. if colspan1.stop <= colspan0.start:
  404. layoutbox.hstack([ss1._layoutbox, ss0._layoutbox], padding=pad)
  405. # vertical alignment
  406. pad = hspace / nrows
  407. if rowspan0.stop <= rowspan1.start:
  408. layoutbox.vstack([ss0._layoutbox, ss1._layoutbox], padding=pad)
  409. if rowspan1.stop <= rowspan0.start:
  410. layoutbox.vstack([ss1._layoutbox, ss0._layoutbox], padding=pad)
  411. def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
  412. """
  413. Do the layout for a colorbar, to not overly pollute colorbar.py
  414. *pad* is in fraction of the original axis size.
  415. """
  416. axlb = ax._layoutbox
  417. axpos = ax._poslayoutbox
  418. axsslb = ax.get_subplotspec()._layoutbox
  419. lb = layoutbox.LayoutBox(
  420. parent=axsslb,
  421. name=axsslb.name + '.cbar',
  422. artist=cax)
  423. if location in ('left', 'right'):
  424. lbpos = layoutbox.LayoutBox(
  425. parent=lb,
  426. name=lb.name + '.pos',
  427. tightwidth=False,
  428. pos=True,
  429. subplot=False,
  430. artist=cax)
  431. if location == 'right':
  432. # arrange to right of parent axis
  433. layoutbox.hstack([axlb, lb], padding=pad * axlb.width,
  434. strength='strong')
  435. else:
  436. layoutbox.hstack([lb, axlb], padding=pad * axlb.width)
  437. # constrain the height and center...
  438. layoutbox.match_heights([axpos, lbpos], [1, shrink])
  439. layoutbox.align([axpos, lbpos], 'v_center')
  440. # set the width of the pos box
  441. lbpos.constrain_width(shrink * axpos.height * (1/aspect),
  442. strength='strong')
  443. elif location in ('bottom', 'top'):
  444. lbpos = layoutbox.LayoutBox(
  445. parent=lb,
  446. name=lb.name + '.pos',
  447. tightheight=True,
  448. pos=True,
  449. subplot=False,
  450. artist=cax)
  451. if location == 'bottom':
  452. layoutbox.vstack([axlb, lb], padding=pad * axlb.height)
  453. else:
  454. layoutbox.vstack([lb, axlb], padding=pad * axlb.height)
  455. # constrain the height and center...
  456. layoutbox.match_widths([axpos, lbpos],
  457. [1, shrink], strength='strong')
  458. layoutbox.align([axpos, lbpos], 'h_center')
  459. # set the height of the pos box
  460. lbpos.constrain_height(axpos.width * aspect * shrink,
  461. strength='medium')
  462. return lb, lbpos
  463. def _getmaxminrowcolumn(axs):
  464. """
  465. Find axes covering the first and last rows and columns of a list of axes.
  466. """
  467. startrow = startcol = np.inf
  468. stoprow = stopcol = -np.inf
  469. startax_row = startax_col = stopax_row = stopax_col = None
  470. for ax in axs:
  471. subspec = ax.get_subplotspec()
  472. if subspec.rowspan.start < startrow:
  473. startrow = subspec.rowspan.start
  474. startax_row = ax
  475. if subspec.rowspan.stop > stoprow:
  476. stoprow = subspec.rowspan.stop
  477. stopax_row = ax
  478. if subspec.colspan.start < startcol:
  479. startcol = subspec.colspan.start
  480. startax_col = ax
  481. if subspec.colspan.stop > stopcol:
  482. stopcol = subspec.colspan.stop
  483. stopax_col = ax
  484. return (startrow, stoprow - 1, startax_row, stopax_row,
  485. startcol, stopcol - 1, startax_col, stopax_col)
  486. def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
  487. """
  488. Do the layout for a colorbar, to not overly pollute colorbar.py
  489. *pad* is in fraction of the original axis size.
  490. """
  491. gs = parents[0].get_subplotspec().get_gridspec()
  492. # parent layout box....
  493. gslb = gs._layoutbox
  494. lb = layoutbox.LayoutBox(parent=gslb.parent,
  495. name=gslb.parent.name + '.cbar',
  496. artist=cax)
  497. # figure out the row and column extent of the parents.
  498. (minrow, maxrow, minax_row, maxax_row,
  499. mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents)
  500. if location in ('left', 'right'):
  501. lbpos = layoutbox.LayoutBox(
  502. parent=lb,
  503. name=lb.name + '.pos',
  504. tightwidth=False,
  505. pos=True,
  506. subplot=False,
  507. artist=cax)
  508. for ax in parents:
  509. if location == 'right':
  510. order = [ax._layoutbox, lb]
  511. else:
  512. order = [lb, ax._layoutbox]
  513. layoutbox.hstack(order, padding=pad * gslb.width,
  514. strength='strong')
  515. # constrain the height and center...
  516. # This isn't quite right. We'd like the colorbar
  517. # pos to line up w/ the axes poss, not the size of the
  518. # gs.
  519. # Horizontal Layout: need to check all the axes in this gridspec
  520. for ch in gslb.children:
  521. subspec = ch.artist
  522. if location == 'right':
  523. if subspec.colspan.stop - 1 <= maxcol:
  524. order = [subspec._layoutbox, lb]
  525. # arrange to right of the parents
  526. elif subspec.colspan.start > maxcol:
  527. order = [lb, subspec._layoutbox]
  528. elif location == 'left':
  529. if subspec.colspan.start >= mincol:
  530. order = [lb, subspec._layoutbox]
  531. elif subspec.colspan.stop - 1 < mincol:
  532. order = [subspec._layoutbox, lb]
  533. layoutbox.hstack(order, padding=pad * gslb.width,
  534. strength='strong')
  535. # Vertical layout:
  536. maxposlb = minax_row._poslayoutbox
  537. minposlb = maxax_row._poslayoutbox
  538. # now we want the height of the colorbar pos to be
  539. # set by the top and bottom of the min/max axes...
  540. # bottom top
  541. # b t
  542. # h = (top-bottom)*shrink
  543. # b = bottom + (top-bottom - h) / 2.
  544. lbpos.constrain_height(
  545. (maxposlb.top - minposlb.bottom) *
  546. shrink, strength='strong')
  547. lbpos.constrain_bottom(
  548. (maxposlb.top - minposlb.bottom) *
  549. (1 - shrink)/2 + minposlb.bottom,
  550. strength='strong')
  551. # set the width of the pos box
  552. lbpos.constrain_width(lbpos.height * (shrink / aspect),
  553. strength='strong')
  554. elif location in ('bottom', 'top'):
  555. lbpos = layoutbox.LayoutBox(
  556. parent=lb,
  557. name=lb.name + '.pos',
  558. tightheight=True,
  559. pos=True,
  560. subplot=False,
  561. artist=cax)
  562. for ax in parents:
  563. if location == 'bottom':
  564. order = [ax._layoutbox, lb]
  565. else:
  566. order = [lb, ax._layoutbox]
  567. layoutbox.vstack(order, padding=pad * gslb.width,
  568. strength='strong')
  569. # Vertical Layout: need to check all the axes in this gridspec
  570. for ch in gslb.children:
  571. subspec = ch.artist
  572. if location == 'bottom':
  573. if subspec.rowspan.stop - 1 <= minrow:
  574. order = [subspec._layoutbox, lb]
  575. elif subspec.rowspan.start > maxrow:
  576. order = [lb, subspec._layoutbox]
  577. elif location == 'top':
  578. if subspec.rowspan.stop - 1 < minrow:
  579. order = [subspec._layoutbox, lb]
  580. elif subspec.rowspan.start >= maxrow:
  581. order = [lb, subspec._layoutbox]
  582. layoutbox.vstack(order, padding=pad * gslb.width,
  583. strength='strong')
  584. # Do horizontal layout...
  585. maxposlb = maxax_col._poslayoutbox
  586. minposlb = minax_col._poslayoutbox
  587. lbpos.constrain_width((maxposlb.right - minposlb.left) *
  588. shrink)
  589. lbpos.constrain_left(
  590. (maxposlb.right - minposlb.left) *
  591. (1-shrink)/2 + minposlb.left)
  592. # set the height of the pos box
  593. lbpos.constrain_height(lbpos.width * shrink * aspect,
  594. strength='medium')
  595. return lb, lbpos