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

2 

3Conventions: 

4 

5"constrain_x" means to constrain the variable with either 

6another kiwisolver variable, or a float. i.e. `constrain_width(0.2)` 

7will set a constraint that the width has to be 0.2 and this constraint is 

8permanent - i.e. it will not be removed if it becomes obsolete. 

9 

10"edit_x" means to set x to a value (just a float), and that this value can 

11change. So `edit_width(0.2)` will set width to be 0.2, but `edit_width(0.3)` 

12will allow it to change to 0.3 later. Note that these values are still just 

13"suggestions" in `kiwisolver` parlance, and could be over-ridden by 

14other constrains. 

15 

16""" 

17 

18import itertools 

19import kiwisolver as kiwi 

20import logging 

21import numpy as np 

22 

23 

24_log = logging.getLogger(__name__) 

25 

26 

27# renderers can be complicated 

28def get_renderer(fig): 

29 if fig._cachedRenderer: 

30 renderer = fig._cachedRenderer 

31 else: 

32 canvas = fig.canvas 

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

34 renderer = canvas.get_renderer() 

35 else: 

36 # not sure if this can happen 

37 # seems to with PDF... 

38 _log.info("constrained_layout : falling back to Agg renderer") 

39 from matplotlib.backends.backend_agg import FigureCanvasAgg 

40 canvas = FigureCanvasAgg(fig) 

41 renderer = canvas.get_renderer() 

42 

43 return renderer 

44 

45 

46class LayoutBox: 

47 """ 

48 Basic rectangle representation using kiwi solver variables 

49 """ 

50 

51 def __init__(self, parent=None, name='', tightwidth=False, 

52 tightheight=False, artist=None, 

53 lower_left=(0, 0), upper_right=(1, 1), pos=False, 

54 subplot=False, h_pad=None, w_pad=None): 

55 Variable = kiwi.Variable 

56 self.parent = parent 

57 self.name = name 

58 sn = self.name + '_' 

59 if parent is None: 

60 self.solver = kiwi.Solver() 

61 self.constrained_layout_called = 0 

62 else: 

63 self.solver = parent.solver 

64 self.constrained_layout_called = None 

65 # parent wants to know about this child! 

66 parent.add_child(self) 

67 # keep track of artist associated w/ this layout. Can be none 

68 self.artist = artist 

69 # keep track if this box is supposed to be a pos that is constrained 

70 # by the parent. 

71 self.pos = pos 

72 # keep track of whether we need to match this subplot up with others. 

73 self.subplot = subplot 

74 

75 # we need the str below for Py 2 which complains the string is unicode 

76 self.top = Variable(str(sn + 'top')) 

77 self.bottom = Variable(str(sn + 'bottom')) 

78 self.left = Variable(str(sn + 'left')) 

79 self.right = Variable(str(sn + 'right')) 

80 

81 self.width = Variable(str(sn + 'width')) 

82 self.height = Variable(str(sn + 'height')) 

83 self.h_center = Variable(str(sn + 'h_center')) 

84 self.v_center = Variable(str(sn + 'v_center')) 

85 

86 self.min_width = Variable(str(sn + 'min_width')) 

87 self.min_height = Variable(str(sn + 'min_height')) 

88 self.pref_width = Variable(str(sn + 'pref_width')) 

89 self.pref_height = Variable(str(sn + 'pref_height')) 

90 # margins are only used for axes-position layout boxes. maybe should 

91 # be a separate subclass: 

92 self.left_margin = Variable(str(sn + 'left_margin')) 

93 self.right_margin = Variable(str(sn + 'right_margin')) 

94 self.bottom_margin = Variable(str(sn + 'bottom_margin')) 

95 self.top_margin = Variable(str(sn + 'top_margin')) 

96 # mins 

97 self.left_margin_min = Variable(str(sn + 'left_margin_min')) 

98 self.right_margin_min = Variable(str(sn + 'right_margin_min')) 

99 self.bottom_margin_min = Variable(str(sn + 'bottom_margin_min')) 

100 self.top_margin_min = Variable(str(sn + 'top_margin_min')) 

101 

102 right, top = upper_right 

103 left, bottom = lower_left 

104 self.tightheight = tightheight 

105 self.tightwidth = tightwidth 

106 self.add_constraints() 

107 self.children = [] 

108 self.subplotspec = None 

109 if self.pos: 

110 self.constrain_margins() 

111 self.h_pad = h_pad 

112 self.w_pad = w_pad 

113 

114 def constrain_margins(self): 

115 """ 

116 Only do this for pos. This sets a variable distance 

117 margin between the position of the axes and the outer edge of 

118 the axes. 

119 

120 Margins are variable because they change with the figure size. 

121 

122 Margin minimums are set to make room for axes decorations. However, 

123 the margins can be larger if we are mathicng the position size to 

124 other axes. 

125 """ 

126 sol = self.solver 

127 

128 # left 

129 if not sol.hasEditVariable(self.left_margin_min): 

130 sol.addEditVariable(self.left_margin_min, 'strong') 

131 sol.suggestValue(self.left_margin_min, 0.0001) 

132 c = (self.left_margin == self.left - self.parent.left) 

133 self.solver.addConstraint(c | 'required') 

134 c = (self.left_margin >= self.left_margin_min) 

135 self.solver.addConstraint(c | 'strong') 

136 

137 # right 

138 if not sol.hasEditVariable(self.right_margin_min): 

139 sol.addEditVariable(self.right_margin_min, 'strong') 

140 sol.suggestValue(self.right_margin_min, 0.0001) 

141 c = (self.right_margin == self.parent.right - self.right) 

142 self.solver.addConstraint(c | 'required') 

143 c = (self.right_margin >= self.right_margin_min) 

144 self.solver.addConstraint(c | 'required') 

145 # bottom 

146 if not sol.hasEditVariable(self.bottom_margin_min): 

147 sol.addEditVariable(self.bottom_margin_min, 'strong') 

148 sol.suggestValue(self.bottom_margin_min, 0.0001) 

149 c = (self.bottom_margin == self.bottom - self.parent.bottom) 

150 self.solver.addConstraint(c | 'required') 

151 c = (self.bottom_margin >= self.bottom_margin_min) 

152 self.solver.addConstraint(c | 'required') 

153 # top 

154 if not sol.hasEditVariable(self.top_margin_min): 

155 sol.addEditVariable(self.top_margin_min, 'strong') 

156 sol.suggestValue(self.top_margin_min, 0.0001) 

157 c = (self.top_margin == self.parent.top - self.top) 

158 self.solver.addConstraint(c | 'required') 

159 c = (self.top_margin >= self.top_margin_min) 

160 self.solver.addConstraint(c | 'required') 

161 

162 def add_child(self, child): 

163 self.children += [child] 

164 

165 def remove_child(self, child): 

166 try: 

167 self.children.remove(child) 

168 except ValueError: 

169 _log.info("Tried to remove child that doesn't belong to parent") 

170 

171 def add_constraints(self): 

172 sol = self.solver 

173 # never let width and height go negative. 

174 for i in [self.min_width, self.min_height]: 

175 sol.addEditVariable(i, 1e9) 

176 sol.suggestValue(i, 0.0) 

177 # define relation ships between things thing width and right and left 

178 self.hard_constraints() 

179 # self.soft_constraints() 

180 if self.parent: 

181 self.parent_constrain() 

182 # sol.updateVariables() 

183 

184 def parent_constrain(self): 

185 parent = self.parent 

186 hc = [self.left >= parent.left, 

187 self.bottom >= parent.bottom, 

188 self.top <= parent.top, 

189 self.right <= parent.right] 

190 for c in hc: 

191 self.solver.addConstraint(c | 'required') 

192 

193 def hard_constraints(self): 

194 hc = [self.width == self.right - self.left, 

195 self.height == self.top - self.bottom, 

196 self.h_center == (self.left + self.right) * 0.5, 

197 self.v_center == (self.top + self.bottom) * 0.5, 

198 self.width >= self.min_width, 

199 self.height >= self.min_height] 

200 for c in hc: 

201 self.solver.addConstraint(c | 'required') 

202 

203 def soft_constraints(self): 

204 sol = self.solver 

205 if self.tightwidth: 

206 suggest = 0. 

207 else: 

208 suggest = 20. 

209 c = (self.pref_width == suggest) 

210 for i in c: 

211 sol.addConstraint(i | 'required') 

212 if self.tightheight: 

213 suggest = 0. 

214 else: 

215 suggest = 20. 

216 c = (self.pref_height == suggest) 

217 for i in c: 

218 sol.addConstraint(i | 'required') 

219 

220 c = [(self.width >= suggest), 

221 (self.height >= suggest)] 

222 for i in c: 

223 sol.addConstraint(i | 150000) 

224 

225 def set_parent(self, parent): 

226 """Replace the parent of this with the new parent.""" 

227 self.parent = parent 

228 self.parent_constrain() 

229 

230 def constrain_geometry(self, left, bottom, right, top, strength='strong'): 

231 hc = [self.left == left, 

232 self.right == right, 

233 self.bottom == bottom, 

234 self.top == top] 

235 for c in hc: 

236 self.solver.addConstraint(c | strength) 

237 # self.solver.updateVariables() 

238 

239 def constrain_same(self, other, strength='strong'): 

240 """ 

241 Make the layoutbox have same position as other layoutbox 

242 """ 

243 hc = [self.left == other.left, 

244 self.right == other.right, 

245 self.bottom == other.bottom, 

246 self.top == other.top] 

247 for c in hc: 

248 self.solver.addConstraint(c | strength) 

249 

250 def constrain_left_margin(self, margin, strength='strong'): 

251 c = (self.left == self.parent.left + margin) 

252 self.solver.addConstraint(c | strength) 

253 

254 def edit_left_margin_min(self, margin): 

255 self.solver.suggestValue(self.left_margin_min, margin) 

256 

257 def constrain_right_margin(self, margin, strength='strong'): 

258 c = (self.right == self.parent.right - margin) 

259 self.solver.addConstraint(c | strength) 

260 

261 def edit_right_margin_min(self, margin): 

262 self.solver.suggestValue(self.right_margin_min, margin) 

263 

264 def constrain_bottom_margin(self, margin, strength='strong'): 

265 c = (self.bottom == self.parent.bottom + margin) 

266 self.solver.addConstraint(c | strength) 

267 

268 def edit_bottom_margin_min(self, margin): 

269 self.solver.suggestValue(self.bottom_margin_min, margin) 

270 

271 def constrain_top_margin(self, margin, strength='strong'): 

272 c = (self.top == self.parent.top - margin) 

273 self.solver.addConstraint(c | strength) 

274 

275 def edit_top_margin_min(self, margin): 

276 self.solver.suggestValue(self.top_margin_min, margin) 

277 

278 def get_rect(self): 

279 return (self.left.value(), self.bottom.value(), 

280 self.width.value(), self.height.value()) 

281 

282 def update_variables(self): 

283 ''' 

284 Update *all* the variables that are part of the solver this LayoutBox 

285 is created with 

286 ''' 

287 self.solver.updateVariables() 

288 

289 def edit_height(self, height, strength='strong'): 

290 ''' 

291 Set the height of the layout box. 

292 

293 This is done as an editable variable so that the value can change 

294 due to resizing. 

295 ''' 

296 sol = self.solver 

297 for i in [self.height]: 

298 if not sol.hasEditVariable(i): 

299 sol.addEditVariable(i, strength) 

300 sol.suggestValue(self.height, height) 

301 

302 def constrain_height(self, height, strength='strong'): 

303 ''' 

304 Constrain the height of the layout box. height is 

305 either a float or a layoutbox.height. 

306 ''' 

307 c = (self.height == height) 

308 self.solver.addConstraint(c | strength) 

309 

310 def constrain_height_min(self, height, strength='strong'): 

311 c = (self.height >= height) 

312 self.solver.addConstraint(c | strength) 

313 

314 def edit_width(self, width, strength='strong'): 

315 sol = self.solver 

316 for i in [self.width]: 

317 if not sol.hasEditVariable(i): 

318 sol.addEditVariable(i, strength) 

319 sol.suggestValue(self.width, width) 

320 

321 def constrain_width(self, width, strength='strong'): 

322 """ 

323 Constrain the width of the layout box. *width* is 

324 either a float or a layoutbox.width. 

325 """ 

326 c = (self.width == width) 

327 self.solver.addConstraint(c | strength) 

328 

329 def constrain_width_min(self, width, strength='strong'): 

330 c = (self.width >= width) 

331 self.solver.addConstraint(c | strength) 

332 

333 def constrain_left(self, left, strength='strong'): 

334 c = (self.left == left) 

335 self.solver.addConstraint(c | strength) 

336 

337 def constrain_bottom(self, bottom, strength='strong'): 

338 c = (self.bottom == bottom) 

339 self.solver.addConstraint(c | strength) 

340 

341 def constrain_right(self, right, strength='strong'): 

342 c = (self.right == right) 

343 self.solver.addConstraint(c | strength) 

344 

345 def constrain_top(self, top, strength='strong'): 

346 c = (self.top == top) 

347 self.solver.addConstraint(c | strength) 

348 

349 def _is_subplotspec_layoutbox(self): 

350 ''' 

351 Helper to check if this layoutbox is the layoutbox of a 

352 subplotspec 

353 ''' 

354 name = (self.name).split('.')[-1] 

355 return name[:2] == 'ss' 

356 

357 def _is_gridspec_layoutbox(self): 

358 ''' 

359 Helper to check if this layoutbox is the layoutbox of a 

360 gridspec 

361 ''' 

362 name = (self.name).split('.')[-1] 

363 return name[:8] == 'gridspec' 

364 

365 def find_child_subplots(self): 

366 ''' 

367 Find children of this layout box that are subplots. We want to line 

368 poss up, and this is an easy way to find them all. 

369 ''' 

370 if self.subplot: 

371 subplots = [self] 

372 else: 

373 subplots = [] 

374 for child in self.children: 

375 subplots += child.find_child_subplots() 

376 return subplots 

377 

378 def layout_from_subplotspec(self, subspec, 

379 name='', artist=None, pos=False): 

380 """ 

381 Make a layout box from a subplotspec. The layout box is 

382 constrained to be a fraction of the width/height of the parent, 

383 and be a fraction of the parent width/height from the left/bottom 

384 of the parent. Therefore the parent can move around and the 

385 layout for the subplot spec should move with it. 

386 

387 The parent is *usually* the gridspec that made the subplotspec.?? 

388 """ 

389 lb = LayoutBox(parent=self, name=name, artist=artist, pos=pos) 

390 gs = subspec.get_gridspec() 

391 nrows, ncols = gs.get_geometry() 

392 parent = self.parent 

393 

394 # OK, now, we want to set the position of this subplotspec 

395 # based on its subplotspec parameters. The new gridspec will inherit 

396 # from gridspec. prob should be new method in gridspec 

397 left = 0.0 

398 right = 1.0 

399 bottom = 0.0 

400 top = 1.0 

401 totWidth = right-left 

402 totHeight = top-bottom 

403 hspace = 0. 

404 wspace = 0. 

405 

406 # calculate accumulated heights of columns 

407 cellH = totHeight / (nrows + hspace * (nrows - 1)) 

408 sepH = hspace * cellH 

409 

410 if gs._row_height_ratios is not None: 

411 netHeight = cellH * nrows 

412 tr = sum(gs._row_height_ratios) 

413 cellHeights = [netHeight * r / tr for r in gs._row_height_ratios] 

414 else: 

415 cellHeights = [cellH] * nrows 

416 

417 sepHeights = [0] + ([sepH] * (nrows - 1)) 

418 cellHs = np.cumsum(np.column_stack([sepHeights, cellHeights]).flat) 

419 

420 # calculate accumulated widths of rows 

421 cellW = totWidth / (ncols + wspace * (ncols - 1)) 

422 sepW = wspace * cellW 

423 

424 if gs._col_width_ratios is not None: 

425 netWidth = cellW * ncols 

426 tr = sum(gs._col_width_ratios) 

427 cellWidths = [netWidth * r / tr for r in gs._col_width_ratios] 

428 else: 

429 cellWidths = [cellW] * ncols 

430 

431 sepWidths = [0] + ([sepW] * (ncols - 1)) 

432 cellWs = np.cumsum(np.column_stack([sepWidths, cellWidths]).flat) 

433 

434 figTops = [top - cellHs[2 * rowNum] for rowNum in range(nrows)] 

435 figBottoms = [top - cellHs[2 * rowNum + 1] for rowNum in range(nrows)] 

436 figLefts = [left + cellWs[2 * colNum] for colNum in range(ncols)] 

437 figRights = [left + cellWs[2 * colNum + 1] for colNum in range(ncols)] 

438 

439 rowNum1, colNum1 = divmod(subspec.num1, ncols) 

440 rowNum2, colNum2 = divmod(subspec.num2, ncols) 

441 figBottom = min(figBottoms[rowNum1], figBottoms[rowNum2]) 

442 figTop = max(figTops[rowNum1], figTops[rowNum2]) 

443 figLeft = min(figLefts[colNum1], figLefts[colNum2]) 

444 figRight = max(figRights[colNum1], figRights[colNum2]) 

445 

446 # These are numbers relative to (0, 0, 1, 1). Need to constrain 

447 # relative to parent. 

448 

449 width = figRight - figLeft 

450 height = figTop - figBottom 

451 parent = self.parent 

452 cs = [self.left == parent.left + parent.width * figLeft, 

453 self.bottom == parent.bottom + parent.height * figBottom, 

454 self.width == parent.width * width, 

455 self.height == parent.height * height] 

456 for c in cs: 

457 self.solver.addConstraint(c | 'required') 

458 

459 return lb 

460 

461 def __repr__(self): 

462 args = (self.name, self.left.value(), self.bottom.value(), 

463 self.right.value(), self.top.value()) 

464 return ('LayoutBox: %25s, (left: %1.3f) (bot: %1.3f) ' 

465 '(right: %1.3f) (top: %1.3f) ') % args 

466 

467 

468# Utility functions that act on layoutboxes... 

469def hstack(boxes, padding=0, strength='strong'): 

470 ''' 

471 Stack LayoutBox instances from left to right. 

472 *padding* is in figure-relative units. 

473 ''' 

474 

475 for i in range(1, len(boxes)): 

476 c = (boxes[i-1].right + padding <= boxes[i].left) 

477 boxes[i].solver.addConstraint(c | strength) 

478 

479 

480def hpack(boxes, padding=0, strength='strong'): 

481 ''' 

482 Stack LayoutBox instances from left to right. 

483 ''' 

484 

485 for i in range(1, len(boxes)): 

486 c = (boxes[i-1].right + padding == boxes[i].left) 

487 boxes[i].solver.addConstraint(c | strength) 

488 

489 

490def vstack(boxes, padding=0, strength='strong'): 

491 ''' 

492 Stack LayoutBox instances from top to bottom 

493 ''' 

494 

495 for i in range(1, len(boxes)): 

496 c = (boxes[i-1].bottom - padding >= boxes[i].top) 

497 boxes[i].solver.addConstraint(c | strength) 

498 

499 

500def vpack(boxes, padding=0, strength='strong'): 

501 ''' 

502 Stack LayoutBox instances from top to bottom 

503 ''' 

504 

505 for i in range(1, len(boxes)): 

506 c = (boxes[i-1].bottom - padding >= boxes[i].top) 

507 boxes[i].solver.addConstraint(c | strength) 

508 

509 

510def match_heights(boxes, height_ratios=None, strength='medium'): 

511 ''' 

512 Stack LayoutBox instances from top to bottom 

513 ''' 

514 

515 if height_ratios is None: 

516 height_ratios = np.ones(len(boxes)) 

517 for i in range(1, len(boxes)): 

518 c = (boxes[i-1].height == 

519 boxes[i].height*height_ratios[i-1]/height_ratios[i]) 

520 boxes[i].solver.addConstraint(c | strength) 

521 

522 

523def match_widths(boxes, width_ratios=None, strength='medium'): 

524 ''' 

525 Stack LayoutBox instances from top to bottom 

526 ''' 

527 

528 if width_ratios is None: 

529 width_ratios = np.ones(len(boxes)) 

530 for i in range(1, len(boxes)): 

531 c = (boxes[i-1].width == 

532 boxes[i].width*width_ratios[i-1]/width_ratios[i]) 

533 boxes[i].solver.addConstraint(c | strength) 

534 

535 

536def vstackeq(boxes, padding=0, height_ratios=None): 

537 vstack(boxes, padding=padding) 

538 match_heights(boxes, height_ratios=height_ratios) 

539 

540 

541def hstackeq(boxes, padding=0, width_ratios=None): 

542 hstack(boxes, padding=padding) 

543 match_widths(boxes, width_ratios=width_ratios) 

544 

545 

546def align(boxes, attr, strength='strong'): 

547 cons = [] 

548 for box in boxes[1:]: 

549 cons = (getattr(boxes[0], attr) == getattr(box, attr)) 

550 boxes[0].solver.addConstraint(cons | strength) 

551 

552 

553def match_top_margins(boxes, levels=1): 

554 box0 = boxes[0] 

555 top0 = box0 

556 for n in range(levels): 

557 top0 = top0.parent 

558 for box in boxes[1:]: 

559 topb = box 

560 for n in range(levels): 

561 topb = topb.parent 

562 c = (box0.top-top0.top == box.top-topb.top) 

563 box0.solver.addConstraint(c | 'strong') 

564 

565 

566def match_bottom_margins(boxes, levels=1): 

567 box0 = boxes[0] 

568 top0 = box0 

569 for n in range(levels): 

570 top0 = top0.parent 

571 for box in boxes[1:]: 

572 topb = box 

573 for n in range(levels): 

574 topb = topb.parent 

575 c = (box0.bottom-top0.bottom == box.bottom-topb.bottom) 

576 box0.solver.addConstraint(c | 'strong') 

577 

578 

579def match_left_margins(boxes, levels=1): 

580 box0 = boxes[0] 

581 top0 = box0 

582 for n in range(levels): 

583 top0 = top0.parent 

584 for box in boxes[1:]: 

585 topb = box 

586 for n in range(levels): 

587 topb = topb.parent 

588 c = (box0.left-top0.left == box.left-topb.left) 

589 box0.solver.addConstraint(c | 'strong') 

590 

591 

592def match_right_margins(boxes, levels=1): 

593 box0 = boxes[0] 

594 top0 = box0 

595 for n in range(levels): 

596 top0 = top0.parent 

597 for box in boxes[1:]: 

598 topb = box 

599 for n in range(levels): 

600 topb = topb.parent 

601 c = (box0.right-top0.right == box.right-topb.right) 

602 box0.solver.addConstraint(c | 'strong') 

603 

604 

605def match_width_margins(boxes, levels=1): 

606 match_left_margins(boxes, levels=levels) 

607 match_right_margins(boxes, levels=levels) 

608 

609 

610def match_height_margins(boxes, levels=1): 

611 match_top_margins(boxes, levels=levels) 

612 match_bottom_margins(boxes, levels=levels) 

613 

614 

615def match_margins(boxes, levels=1): 

616 match_width_margins(boxes, levels=levels) 

617 match_height_margins(boxes, levels=levels) 

618 

619 

620_layoutboxobjnum = itertools.count() 

621 

622 

623def seq_id(): 

624 """Generate a short sequential id for layoutbox objects.""" 

625 return '%06d' % next(_layoutboxobjnum) 

626 

627 

628def print_children(lb): 

629 """Print the children of the layoutbox.""" 

630 print(lb) 

631 for child in lb.children: 

632 print_children(child) 

633 

634 

635def nonetree(lb): 

636 """ 

637 Make all elements in this tree None, signalling not to do any more layout. 

638 """ 

639 if lb is not None: 

640 if lb.parent is None: 

641 # Clear the solver. Hopefully this garbage collects. 

642 lb.solver.reset() 

643 nonechildren(lb) 

644 else: 

645 nonetree(lb.parent) 

646 

647 

648def nonechildren(lb): 

649 for child in lb.children: 

650 nonechildren(child) 

651 lb.artist._layoutbox = None 

652 lb = None 

653 

654 

655def print_tree(lb): 

656 ''' 

657 Print the tree of layoutboxes 

658 ''' 

659 

660 if lb.parent is None: 

661 print('LayoutBox Tree\n') 

662 print('==============\n') 

663 print_children(lb) 

664 print('\n') 

665 else: 

666 print_tree(lb.parent) 

667 

668 

669def plot_children(fig, box, level=0, printit=True): 

670 ''' 

671 Simple plotting to show where boxes are 

672 ''' 

673 import matplotlib 

674 import matplotlib.pyplot as plt 

675 

676 if isinstance(fig, matplotlib.figure.Figure): 

677 ax = fig.add_axes([0., 0., 1., 1.]) 

678 ax.set_facecolor([1., 1., 1., 0.7]) 

679 ax.set_alpha(0.3) 

680 fig.draw(fig.canvas.get_renderer()) 

681 else: 

682 ax = fig 

683 

684 import matplotlib.patches as patches 

685 colors = plt.rcParams["axes.prop_cycle"].by_key()["color"] 

686 if printit: 

687 print("Level:", level) 

688 for child in box.children: 

689 if printit: 

690 print(child) 

691 ax.add_patch( 

692 patches.Rectangle( 

693 (child.left.value(), child.bottom.value()), # (x, y) 

694 child.width.value(), # width 

695 child.height.value(), # height 

696 fc='none', 

697 alpha=0.8, 

698 ec=colors[level] 

699 ) 

700 ) 

701 if level > 0: 

702 name = child.name.split('.')[-1] 

703 if level % 2 == 0: 

704 ax.text(child.left.value(), child.bottom.value(), name, 

705 size=12-level, color=colors[level]) 

706 else: 

707 ax.text(child.right.value(), child.top.value(), name, 

708 ha='right', va='top', size=12-level, 

709 color=colors[level]) 

710 

711 plot_children(ax, child, level=level+1, printit=printit)