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

2matplotlib includes a framework for arbitrary geometric 

3transformations that is used determine the final position of all 

4elements drawn on the canvas. 

5 

6Transforms are composed into trees of `TransformNode` objects 

7whose actual value depends on their children. When the contents of 

8children change, their parents are automatically invalidated. The 

9next time an invalidated transform is accessed, it is recomputed to 

10reflect those changes. This invalidation/caching approach prevents 

11unnecessary recomputations of transforms, and contributes to better 

12interactive performance. 

13 

14For example, here is a graph of the transform tree used to plot data 

15to the graph: 

16 

17.. image:: ../_static/transforms.png 

18 

19The framework can be used for both affine and non-affine 

20transformations. However, for speed, we want use the backend 

21renderers to perform affine transformations whenever possible. 

22Therefore, it is possible to perform just the affine or non-affine 

23part of a transformation on a set of data. The affine is always 

24assumed to occur after the non-affine. For any transform:: 

25 

26 full transform == non-affine part + affine part 

27 

28The backends are not expected to handle non-affine transformations 

29themselves. 

30""" 

31 

32# Note: There are a number of places in the code where we use `np.min` or 

33# `np.minimum` instead of the builtin `min`, and likewise for `max`. This is 

34# done so that `nan`s are propagated, instead of being silently dropped. 

35 

36import re 

37import weakref 

38 

39import numpy as np 

40from numpy.linalg import inv 

41 

42from matplotlib import cbook 

43from matplotlib._path import ( 

44 affine_transform, count_bboxes_overlapping_bbox, update_path_extents) 

45from .path import Path 

46 

47DEBUG = False 

48 

49 

50def _indent_str(obj): # textwrap.indent(str(obj), 4) on Py3. 

51 return re.sub("(^|\n)", r"\1 ", str(obj)) 

52 

53 

54class TransformNode: 

55 """ 

56 :class:`TransformNode` is the base class for anything that 

57 participates in the transform tree and needs to invalidate its 

58 parents or be invalidated. This includes classes that are not 

59 really transforms, such as bounding boxes, since some transforms 

60 depend on bounding boxes to compute their values. 

61 """ 

62 _gid = 0 

63 

64 # Invalidation may affect only the affine part. If the 

65 # invalidation was "affine-only", the _invalid member is set to 

66 # INVALID_AFFINE_ONLY 

67 INVALID_NON_AFFINE = 1 

68 INVALID_AFFINE = 2 

69 INVALID = INVALID_NON_AFFINE | INVALID_AFFINE 

70 

71 # Some metadata about the transform, used to determine whether an 

72 # invalidation is affine-only 

73 is_affine = False 

74 is_bbox = False 

75 

76 pass_through = False 

77 """ 

78 If pass_through is True, all ancestors will always be 

79 invalidated, even if 'self' is already invalid. 

80 """ 

81 

82 def __init__(self, shorthand_name=None): 

83 """ 

84 Creates a new :class:`TransformNode`. 

85 

86 Parameters 

87 ---------- 

88 shorthand_name : str 

89 A string representing the "name" of the transform. The name carries 

90 no significance other than to improve the readability of 

91 ``str(transform)`` when DEBUG=True. 

92 """ 

93 self._parents = {} 

94 

95 # TransformNodes start out as invalid until their values are 

96 # computed for the first time. 

97 self._invalid = 1 

98 self._shorthand_name = shorthand_name or '' 

99 

100 if DEBUG: 

101 def __str__(self): 

102 # either just return the name of this TransformNode, or its repr 

103 return self._shorthand_name or repr(self) 

104 

105 def __getstate__(self): 

106 # turn the dictionary with weak values into a normal dictionary 

107 return {**self.__dict__, 

108 '_parents': {k: v() for k, v in self._parents.items()}} 

109 

110 def __setstate__(self, data_dict): 

111 self.__dict__ = data_dict 

112 # turn the normal dictionary back into a dictionary with weak values 

113 # The extra lambda is to provide a callback to remove dead 

114 # weakrefs from the dictionary when garbage collection is done. 

115 self._parents = {k: weakref.ref(v, lambda ref, sid=k, 

116 target=self._parents: 

117 target.pop(sid)) 

118 for k, v in self._parents.items() if v is not None} 

119 

120 def __copy__(self, *args): 

121 raise NotImplementedError( 

122 "TransformNode instances can not be copied. " 

123 "Consider using frozen() instead.") 

124 __deepcopy__ = __copy__ 

125 

126 def invalidate(self): 

127 """ 

128 Invalidate this `TransformNode` and triggers an invalidation of its 

129 ancestors. Should be called any time the transform changes. 

130 """ 

131 value = self.INVALID 

132 if self.is_affine: 

133 value = self.INVALID_AFFINE 

134 return self._invalidate_internal(value, invalidating_node=self) 

135 

136 def _invalidate_internal(self, value, invalidating_node): 

137 """ 

138 Called by :meth:`invalidate` and subsequently ascends the transform 

139 stack calling each TransformNode's _invalidate_internal method. 

140 """ 

141 # determine if this call will be an extension to the invalidation 

142 # status. If not, then a shortcut means that we needn't invoke an 

143 # invalidation up the transform stack as it will already have been 

144 # invalidated. 

145 

146 # N.B This makes the invalidation sticky, once a transform has been 

147 # invalidated as NON_AFFINE, then it will always be invalidated as 

148 # NON_AFFINE even when triggered with a AFFINE_ONLY invalidation. 

149 # In most cases this is not a problem (i.e. for interactive panning and 

150 # zooming) and the only side effect will be on performance. 

151 status_changed = self._invalid < value 

152 

153 if self.pass_through or status_changed: 

154 self._invalid = value 

155 

156 for parent in list(self._parents.values()): 

157 # Dereference the weak reference 

158 parent = parent() 

159 if parent is not None: 

160 parent._invalidate_internal( 

161 value=value, invalidating_node=self) 

162 

163 def set_children(self, *children): 

164 """ 

165 Set the children of the transform, to let the invalidation 

166 system know which transforms can invalidate this transform. 

167 Should be called from the constructor of any transforms that 

168 depend on other transforms. 

169 """ 

170 # Parents are stored as weak references, so that if the 

171 # parents are destroyed, references from the children won't 

172 # keep them alive. 

173 for child in children: 

174 # Use weak references so this dictionary won't keep obsolete nodes 

175 # alive; the callback deletes the dictionary entry. This is a 

176 # performance improvement over using WeakValueDictionary. 

177 ref = weakref.ref(self, lambda ref, sid=id(self), 

178 target=child._parents: target.pop(sid)) 

179 child._parents[id(self)] = ref 

180 

181 if DEBUG: 

182 _set_children = set_children 

183 

184 def set_children(self, *children): 

185 self._set_children(*children) 

186 self._children = children 

187 set_children.__doc__ = _set_children.__doc__ 

188 

189 def frozen(self): 

190 """ 

191 Returns a frozen copy of this transform node. The frozen copy 

192 will not update when its children change. Useful for storing 

193 a previously known state of a transform where 

194 ``copy.deepcopy()`` might normally be used. 

195 """ 

196 return self 

197 

198 if DEBUG: 

199 def write_graphviz(self, fobj, highlight=[]): 

200 """ 

201 For debugging purposes. 

202 

203 Writes the transform tree rooted at 'self' to a graphviz "dot" 

204 format file. This file can be run through the "dot" utility 

205 to produce a graph of the transform tree. 

206 

207 Affine transforms are marked in blue. Bounding boxes are 

208 marked in yellow. 

209 

210 *fobj*: A Python file-like object 

211 

212 Once the "dot" file has been created, it can be turned into a 

213 png easily with:: 

214 

215 $> dot -Tpng -o $OUTPUT_FILE $DOT_FILE 

216 

217 """ 

218 seen = set() 

219 

220 def recurse(root): 

221 if root in seen: 

222 return 

223 seen.add(root) 

224 props = {} 

225 label = root.__class__.__name__ 

226 if root._invalid: 

227 label = '[%s]' % label 

228 if root in highlight: 

229 props['style'] = 'bold' 

230 props['shape'] = 'box' 

231 props['label'] = '"%s"' % label 

232 props = ' '.join(map('{0[0]}={0[1]}'.format, props.items())) 

233 

234 fobj.write('%s [%s];\n' % (hash(root), props)) 

235 

236 if hasattr(root, '_children'): 

237 for child in root._children: 

238 name = next((key for key, val in root.__dict__.items() 

239 if val is child), '?') 

240 fobj.write('"%s" -> "%s" [label="%s", fontsize=10];\n' 

241 % (hash(root), 

242 hash(child), 

243 name)) 

244 recurse(child) 

245 

246 fobj.write("digraph G {\n") 

247 recurse(self) 

248 fobj.write("}\n") 

249 

250 

251class BboxBase(TransformNode): 

252 """ 

253 This is the base class of all bounding boxes, and provides read-only access 

254 to its data. A mutable bounding box is provided by the `Bbox` class. 

255 

256 The canonical representation is as two points, with no 

257 restrictions on their ordering. Convenience properties are 

258 provided to get the left, bottom, right and top edges and width 

259 and height, but these are not stored explicitly. 

260 """ 

261 is_bbox = True 

262 is_affine = True 

263 

264 if DEBUG: 

265 @staticmethod 

266 def _check(points): 

267 if isinstance(points, np.ma.MaskedArray): 

268 cbook._warn_external("Bbox bounds are a masked array.") 

269 points = np.asarray(points) 

270 if (points[1, 0] - points[0, 0] == 0 or 

271 points[1, 1] - points[0, 1] == 0): 

272 cbook._warn_external("Singular Bbox.") 

273 

274 def frozen(self): 

275 return Bbox(self.get_points().copy()) 

276 frozen.__doc__ = TransformNode.__doc__ 

277 

278 def __array__(self, *args, **kwargs): 

279 return self.get_points() 

280 

281 @cbook.deprecated("3.2") 

282 def is_unit(self): 

283 """Return whether this is the unit box (from (0, 0) to (1, 1)).""" 

284 return self.get_points().tolist() == [[0., 0.], [1., 1.]] 

285 

286 @property 

287 def x0(self): 

288 """ 

289 The first of the pair of *x* coordinates that define the bounding box. 

290 

291 This is not guaranteed to be less than :attr:`x1` (for that, use 

292 :attr:`xmin`). 

293 """ 

294 return self.get_points()[0, 0] 

295 

296 @property 

297 def y0(self): 

298 """ 

299 The first of the pair of *y* coordinates that define the bounding box. 

300 

301 This is not guaranteed to be less than :attr:`y1` (for that, use 

302 :attr:`ymin`). 

303 """ 

304 return self.get_points()[0, 1] 

305 

306 @property 

307 def x1(self): 

308 """ 

309 The second of the pair of *x* coordinates that define the bounding box. 

310 

311 This is not guaranteed to be greater than :attr:`x0` (for that, use 

312 :attr:`xmax`). 

313 """ 

314 return self.get_points()[1, 0] 

315 

316 @property 

317 def y1(self): 

318 """ 

319 The second of the pair of *y* coordinates that define the bounding box. 

320 

321 This is not guaranteed to be greater than :attr:`y0` (for that, use 

322 :attr:`ymax`). 

323 """ 

324 return self.get_points()[1, 1] 

325 

326 @property 

327 def p0(self): 

328 """ 

329 The first pair of (*x*, *y*) coordinates that define the bounding box. 

330 

331 This is not guaranteed to be the bottom-left corner (for that, use 

332 :attr:`min`). 

333 """ 

334 return self.get_points()[0] 

335 

336 @property 

337 def p1(self): 

338 """ 

339 The second pair of (*x*, *y*) coordinates that define the bounding box. 

340 

341 This is not guaranteed to be the top-right corner (for that, use 

342 :attr:`max`). 

343 """ 

344 return self.get_points()[1] 

345 

346 @property 

347 def xmin(self): 

348 """The left edge of the bounding box.""" 

349 return np.min(self.get_points()[:, 0]) 

350 

351 @property 

352 def ymin(self): 

353 """The bottom edge of the bounding box.""" 

354 return np.min(self.get_points()[:, 1]) 

355 

356 @property 

357 def xmax(self): 

358 """The right edge of the bounding box.""" 

359 return np.max(self.get_points()[:, 0]) 

360 

361 @property 

362 def ymax(self): 

363 """The top edge of the bounding box.""" 

364 return np.max(self.get_points()[:, 1]) 

365 

366 @property 

367 def min(self): 

368 """The bottom-left corner of the bounding box.""" 

369 return np.min(self.get_points(), axis=0) 

370 

371 @property 

372 def max(self): 

373 """The top-right corner of the bounding box.""" 

374 return np.max(self.get_points(), axis=0) 

375 

376 @property 

377 def intervalx(self): 

378 """ 

379 The pair of *x* coordinates that define the bounding box. 

380 

381 This is not guaranteed to be sorted from left to right. 

382 """ 

383 return self.get_points()[:, 0] 

384 

385 @property 

386 def intervaly(self): 

387 """ 

388 The pair of *y* coordinates that define the bounding box. 

389 

390 This is not guaranteed to be sorted from bottom to top. 

391 """ 

392 return self.get_points()[:, 1] 

393 

394 @property 

395 def width(self): 

396 """The (signed) width of the bounding box.""" 

397 points = self.get_points() 

398 return points[1, 0] - points[0, 0] 

399 

400 @property 

401 def height(self): 

402 """The (signed) height of the bounding box.""" 

403 points = self.get_points() 

404 return points[1, 1] - points[0, 1] 

405 

406 @property 

407 def size(self): 

408 """The (signed) width and height of the bounding box.""" 

409 points = self.get_points() 

410 return points[1] - points[0] 

411 

412 @property 

413 def bounds(self): 

414 """Return (:attr:`x0`, :attr:`y0`, :attr:`width`, :attr:`height`).""" 

415 (x0, y0), (x1, y1) = self.get_points() 

416 return (x0, y0, x1 - x0, y1 - y0) 

417 

418 @property 

419 def extents(self): 

420 """Return (:attr:`x0`, :attr:`y0`, :attr:`x1`, :attr:`y1`).""" 

421 return self.get_points().flatten() # flatten returns a copy. 

422 

423 def get_points(self): 

424 raise NotImplementedError 

425 

426 def containsx(self, x): 

427 """ 

428 Return whether *x* is in the closed (:attr:`x0`, :attr:`x1`) interval. 

429 """ 

430 x0, x1 = self.intervalx 

431 return x0 <= x <= x1 or x0 >= x >= x1 

432 

433 def containsy(self, y): 

434 """ 

435 Return whether *y* is in the closed (:attr:`y0`, :attr:`y1`) interval. 

436 """ 

437 y0, y1 = self.intervaly 

438 return y0 <= y <= y1 or y0 >= y >= y1 

439 

440 def contains(self, x, y): 

441 """ 

442 Return whether ``(x, y)`` is in the bounding box or on its edge. 

443 """ 

444 return self.containsx(x) and self.containsy(y) 

445 

446 def overlaps(self, other): 

447 """ 

448 Return whether this bounding box overlaps with the other bounding box. 

449 

450 Parameters 

451 ---------- 

452 other : `.BboxBase` 

453 """ 

454 ax1, ay1, ax2, ay2 = self.extents 

455 bx1, by1, bx2, by2 = other.extents 

456 if ax2 < ax1: 

457 ax2, ax1 = ax1, ax2 

458 if ay2 < ay1: 

459 ay2, ay1 = ay1, ay2 

460 if bx2 < bx1: 

461 bx2, bx1 = bx1, bx2 

462 if by2 < by1: 

463 by2, by1 = by1, by2 

464 return ax1 <= bx2 and bx1 <= ax2 and ay1 <= by2 and by1 <= ay2 

465 

466 def fully_containsx(self, x): 

467 """ 

468 Return whether *x* is in the open (:attr:`x0`, :attr:`x1`) interval. 

469 """ 

470 x0, x1 = self.intervalx 

471 return x0 < x < x1 or x0 > x > x1 

472 

473 def fully_containsy(self, y): 

474 """ 

475 Return whether *y* is in the open (:attr:`y0`, :attr:`y1`) interval. 

476 """ 

477 y0, y1 = self.intervaly 

478 return y0 < y < y1 or y0 > y > y1 

479 

480 def fully_contains(self, x, y): 

481 """ 

482 Return whether ``x, y`` is in the bounding box, but not on its edge. 

483 """ 

484 return self.fully_containsx(x) and self.fully_containsy(y) 

485 

486 def fully_overlaps(self, other): 

487 """ 

488 Return whether this bounding box overlaps with the other bounding box, 

489 not including the edges. 

490 

491 Parameters 

492 ---------- 

493 other : `.BboxBase` 

494 """ 

495 ax1, ay1, ax2, ay2 = self.extents 

496 bx1, by1, bx2, by2 = other.extents 

497 if ax2 < ax1: 

498 ax2, ax1 = ax1, ax2 

499 if ay2 < ay1: 

500 ay2, ay1 = ay1, ay2 

501 if bx2 < bx1: 

502 bx2, bx1 = bx1, bx2 

503 if by2 < by1: 

504 by2, by1 = by1, by2 

505 return ax1 < bx2 and bx1 < ax2 and ay1 < by2 and by1 < ay2 

506 

507 def transformed(self, transform): 

508 """ 

509 Construct a `Bbox` by statically transforming this one by *transform*. 

510 """ 

511 pts = self.get_points() 

512 ll, ul, lr = transform.transform(np.array([pts[0], 

513 [pts[0, 0], pts[1, 1]], [pts[1, 0], pts[0, 1]]])) 

514 return Bbox([ll, [lr[0], ul[1]]]) 

515 

516 def inverse_transformed(self, transform): 

517 """ 

518 Construct a `Bbox` by statically transforming this one by the inverse 

519 of *transform*. 

520 """ 

521 return self.transformed(transform.inverted()) 

522 

523 coefs = {'C': (0.5, 0.5), 

524 'SW': (0, 0), 

525 'S': (0.5, 0), 

526 'SE': (1.0, 0), 

527 'E': (1.0, 0.5), 

528 'NE': (1.0, 1.0), 

529 'N': (0.5, 1.0), 

530 'NW': (0, 1.0), 

531 'W': (0, 0.5)} 

532 

533 def anchored(self, c, container=None): 

534 """ 

535 Return a copy of the `Bbox` shifted to position *c* within *container*. 

536 

537 Parameters 

538 ---------- 

539 c : (float, float) or str 

540 May be either: 

541 

542 * A sequence (*cx*, *cy*) where *cx* and *cy* range from 0 

543 to 1, where 0 is left or bottom and 1 is right or top 

544 

545 * a string: 

546 - 'C' for centered 

547 - 'S' for bottom-center 

548 - 'SE' for bottom-left 

549 - 'E' for left 

550 - etc. 

551 

552 container : Bbox, optional 

553 The box within which the :class:`Bbox` is positioned; it defaults 

554 to the initial :class:`Bbox`. 

555 """ 

556 if container is None: 

557 container = self 

558 l, b, w, h = container.bounds 

559 if isinstance(c, str): 

560 cx, cy = self.coefs[c] 

561 else: 

562 cx, cy = c 

563 L, B, W, H = self.bounds 

564 return Bbox(self._points + 

565 [(l + cx * (w - W)) - L, 

566 (b + cy * (h - H)) - B]) 

567 

568 def shrunk(self, mx, my): 

569 """ 

570 Return a copy of the :class:`Bbox`, shrunk by the factor *mx* 

571 in the *x* direction and the factor *my* in the *y* direction. 

572 The lower left corner of the box remains unchanged. Normally 

573 *mx* and *my* will be less than 1, but this is not enforced. 

574 """ 

575 w, h = self.size 

576 return Bbox([self._points[0], 

577 self._points[0] + [mx * w, my * h]]) 

578 

579 def shrunk_to_aspect(self, box_aspect, container=None, fig_aspect=1.0): 

580 """ 

581 Return a copy of the :class:`Bbox`, shrunk so that it is as 

582 large as it can be while having the desired aspect ratio, 

583 *box_aspect*. If the box coordinates are relative---that 

584 is, fractions of a larger box such as a figure---then the 

585 physical aspect ratio of that figure is specified with 

586 *fig_aspect*, so that *box_aspect* can also be given as a 

587 ratio of the absolute dimensions, not the relative dimensions. 

588 """ 

589 if box_aspect <= 0 or fig_aspect <= 0: 

590 raise ValueError("'box_aspect' and 'fig_aspect' must be positive") 

591 if container is None: 

592 container = self 

593 w, h = container.size 

594 H = w * box_aspect / fig_aspect 

595 if H <= h: 

596 W = w 

597 else: 

598 W = h * fig_aspect / box_aspect 

599 H = h 

600 return Bbox([self._points[0], 

601 self._points[0] + (W, H)]) 

602 

603 def splitx(self, *args): 

604 """ 

605 Return a list of new `Bbox` objects formed by splitting the original 

606 one with vertical lines at fractional positions given by *args*. 

607 """ 

608 xf = [0, *args, 1] 

609 x0, y0, x1, y1 = self.extents 

610 w = x1 - x0 

611 return [Bbox([[x0 + xf0 * w, y0], [x0 + xf1 * w, y1]]) 

612 for xf0, xf1 in zip(xf[:-1], xf[1:])] 

613 

614 def splity(self, *args): 

615 """ 

616 Return a list of new `Bbox` objects formed by splitting the original 

617 one with horizontal lines at fractional positions given by *args*. 

618 """ 

619 yf = [0, *args, 1] 

620 x0, y0, x1, y1 = self.extents 

621 h = y1 - y0 

622 return [Bbox([[x0, y0 + yf0 * h], [x1, y0 + yf1 * h]]) 

623 for yf0, yf1 in zip(yf[:-1], yf[1:])] 

624 

625 def count_contains(self, vertices): 

626 """ 

627 Count the number of vertices contained in the :class:`Bbox`. 

628 Any vertices with a non-finite x or y value are ignored. 

629 

630 Parameters 

631 ---------- 

632 vertices : Nx2 Numpy array. 

633 """ 

634 if len(vertices) == 0: 

635 return 0 

636 vertices = np.asarray(vertices) 

637 with np.errstate(invalid='ignore'): 

638 return (((self.min < vertices) & 

639 (vertices < self.max)).all(axis=1).sum()) 

640 

641 def count_overlaps(self, bboxes): 

642 """ 

643 Count the number of bounding boxes that overlap this one. 

644 

645 Parameters 

646 ---------- 

647 bboxes : sequence of `.BboxBase` 

648 """ 

649 return count_bboxes_overlapping_bbox( 

650 self, np.atleast_3d([np.array(x) for x in bboxes])) 

651 

652 def expanded(self, sw, sh): 

653 """ 

654 Construct a `Bbox` by expanding this one around its center by the 

655 factors *sw* and *sh*. 

656 """ 

657 width = self.width 

658 height = self.height 

659 deltaw = (sw * width - width) / 2.0 

660 deltah = (sh * height - height) / 2.0 

661 a = np.array([[-deltaw, -deltah], [deltaw, deltah]]) 

662 return Bbox(self._points + a) 

663 

664 def padded(self, p): 

665 """Construct a `Bbox` by padding this one on all four sides by *p*.""" 

666 points = self.get_points() 

667 return Bbox(points + [[-p, -p], [p, p]]) 

668 

669 def translated(self, tx, ty): 

670 """Construct a `Bbox` by translating this one by *tx* and *ty*.""" 

671 return Bbox(self._points + (tx, ty)) 

672 

673 def corners(self): 

674 """ 

675 Return the corners of this rectangle as an array of points. 

676 

677 Specifically, this returns the array 

678 ``[[x0, y0], [x0, y1], [x1, y0], [x1, y1]]``. 

679 """ 

680 (x0, y0), (x1, y1) = self.get_points() 

681 return np.array([[x0, y0], [x0, y1], [x1, y0], [x1, y1]]) 

682 

683 def rotated(self, radians): 

684 """ 

685 Return a new bounding box that bounds a rotated version of 

686 this bounding box by the given radians. The new bounding box 

687 is still aligned with the axes, of course. 

688 """ 

689 corners = self.corners() 

690 corners_rotated = Affine2D().rotate(radians).transform(corners) 

691 bbox = Bbox.unit() 

692 bbox.update_from_data_xy(corners_rotated, ignore=True) 

693 return bbox 

694 

695 @staticmethod 

696 def union(bboxes): 

697 """Return a `Bbox` that contains all of the given *bboxes*.""" 

698 if not len(bboxes): 

699 raise ValueError("'bboxes' cannot be empty") 

700 # needed for 1.14.4 < numpy_version < 1.15 

701 # can remove once we are at numpy >= 1.15 

702 with np.errstate(invalid='ignore'): 

703 x0 = np.min([bbox.xmin for bbox in bboxes]) 

704 x1 = np.max([bbox.xmax for bbox in bboxes]) 

705 y0 = np.min([bbox.ymin for bbox in bboxes]) 

706 y1 = np.max([bbox.ymax for bbox in bboxes]) 

707 return Bbox([[x0, y0], [x1, y1]]) 

708 

709 @staticmethod 

710 def intersection(bbox1, bbox2): 

711 """ 

712 Return the intersection of *bbox1* and *bbox2* if they intersect, or 

713 None if they don't. 

714 """ 

715 x0 = np.maximum(bbox1.xmin, bbox2.xmin) 

716 x1 = np.minimum(bbox1.xmax, bbox2.xmax) 

717 y0 = np.maximum(bbox1.ymin, bbox2.ymin) 

718 y1 = np.minimum(bbox1.ymax, bbox2.ymax) 

719 return Bbox([[x0, y0], [x1, y1]]) if x0 <= x1 and y0 <= y1 else None 

720 

721 

722class Bbox(BboxBase): 

723 """ 

724 A mutable bounding box. 

725 """ 

726 

727 def __init__(self, points, **kwargs): 

728 """ 

729 Parameters 

730 ---------- 

731 points : ndarray 

732 A 2x2 numpy array of the form ``[[x0, y0], [x1, y1]]``. 

733 

734 Notes 

735 ----- 

736 If you need to create a :class:`Bbox` object from another form 

737 of data, consider the static methods :meth:`unit`, 

738 :meth:`from_bounds` and :meth:`from_extents`. 

739 """ 

740 BboxBase.__init__(self, **kwargs) 

741 points = np.asarray(points, float) 

742 if points.shape != (2, 2): 

743 raise ValueError('Bbox points must be of the form ' 

744 '"[[x0, y0], [x1, y1]]".') 

745 self._points = points 

746 self._minpos = np.array([np.inf, np.inf]) 

747 self._ignore = True 

748 # it is helpful in some contexts to know if the bbox is a 

749 # default or has been mutated; we store the orig points to 

750 # support the mutated methods 

751 self._points_orig = self._points.copy() 

752 if DEBUG: 

753 ___init__ = __init__ 

754 

755 def __init__(self, points, **kwargs): 

756 self._check(points) 

757 self.___init__(points, **kwargs) 

758 

759 def invalidate(self): 

760 self._check(self._points) 

761 TransformNode.invalidate(self) 

762 

763 @staticmethod 

764 def unit(): 

765 """Create a new unit `Bbox` from (0, 0) to (1, 1).""" 

766 return Bbox(np.array([[0.0, 0.0], [1.0, 1.0]], float)) 

767 

768 @staticmethod 

769 def null(): 

770 """Create a new null `Bbox` from (inf, inf) to (-inf, -inf).""" 

771 return Bbox(np.array([[np.inf, np.inf], [-np.inf, -np.inf]], float)) 

772 

773 @staticmethod 

774 def from_bounds(x0, y0, width, height): 

775 """ 

776 Create a new `Bbox` from *x0*, *y0*, *width* and *height*. 

777 

778 *width* and *height* may be negative. 

779 """ 

780 return Bbox.from_extents(x0, y0, x0 + width, y0 + height) 

781 

782 @staticmethod 

783 def from_extents(*args): 

784 """ 

785 Create a new Bbox from *left*, *bottom*, *right* and *top*. 

786 

787 The *y*-axis increases upwards. 

788 """ 

789 points = np.array(args, dtype=float).reshape(2, 2) 

790 return Bbox(points) 

791 

792 def __format__(self, fmt): 

793 return ( 

794 'Bbox(x0={0.x0:{1}}, y0={0.y0:{1}}, x1={0.x1:{1}}, y1={0.y1:{1}})'. 

795 format(self, fmt)) 

796 

797 def __str__(self): 

798 return format(self, '') 

799 

800 def __repr__(self): 

801 return 'Bbox([[{0.x0}, {0.y0}], [{0.x1}, {0.y1}]])'.format(self) 

802 

803 def ignore(self, value): 

804 """ 

805 Set whether the existing bounds of the box should be ignored 

806 by subsequent calls to :meth:`update_from_data_xy`. 

807 

808 value : bool 

809 - When ``True``, subsequent calls to :meth:`update_from_data_xy` 

810 will ignore the existing bounds of the :class:`Bbox`. 

811 

812 - When ``False``, subsequent calls to :meth:`update_from_data_xy` 

813 will include the existing bounds of the :class:`Bbox`. 

814 """ 

815 self._ignore = value 

816 

817 def update_from_path(self, path, ignore=None, updatex=True, updatey=True): 

818 """ 

819 Update the bounds of the :class:`Bbox` based on the passed in 

820 data. After updating, the bounds will have positive *width* 

821 and *height*; *x0* and *y0* will be the minimal values. 

822 

823 Parameters 

824 ---------- 

825 path : :class:`~matplotlib.path.Path` 

826 

827 ignore : bool, optional 

828 - when ``True``, ignore the existing bounds of the :class:`Bbox`. 

829 - when ``False``, include the existing bounds of the :class:`Bbox`. 

830 - when ``None``, use the last value passed to :meth:`ignore`. 

831 

832 updatex, updatey : bool, optional 

833 When ``True``, update the x/y values. 

834 """ 

835 if ignore is None: 

836 ignore = self._ignore 

837 

838 if path.vertices.size == 0: 

839 return 

840 

841 points, minpos, changed = update_path_extents( 

842 path, None, self._points, self._minpos, ignore) 

843 

844 if changed: 

845 self.invalidate() 

846 if updatex: 

847 self._points[:, 0] = points[:, 0] 

848 self._minpos[0] = minpos[0] 

849 if updatey: 

850 self._points[:, 1] = points[:, 1] 

851 self._minpos[1] = minpos[1] 

852 

853 def update_from_data_xy(self, xy, ignore=None, updatex=True, updatey=True): 

854 """ 

855 Update the bounds of the :class:`Bbox` based on the passed in 

856 data. After updating, the bounds will have positive *width* 

857 and *height*; *x0* and *y0* will be the minimal values. 

858 

859 Parameters 

860 ---------- 

861 xy : ndarray 

862 A numpy array of 2D points. 

863 

864 ignore : bool, optional 

865 - When ``True``, ignore the existing bounds of the :class:`Bbox`. 

866 - When ``False``, include the existing bounds of the :class:`Bbox`. 

867 - When ``None``, use the last value passed to :meth:`ignore`. 

868 

869 updatex, updatey : bool, optional 

870 When ``True``, update the x/y values. 

871 """ 

872 if len(xy) == 0: 

873 return 

874 

875 path = Path(xy) 

876 self.update_from_path(path, ignore=ignore, 

877 updatex=updatex, updatey=updatey) 

878 

879 @BboxBase.x0.setter 

880 def x0(self, val): 

881 self._points[0, 0] = val 

882 self.invalidate() 

883 

884 @BboxBase.y0.setter 

885 def y0(self, val): 

886 self._points[0, 1] = val 

887 self.invalidate() 

888 

889 @BboxBase.x1.setter 

890 def x1(self, val): 

891 self._points[1, 0] = val 

892 self.invalidate() 

893 

894 @BboxBase.y1.setter 

895 def y1(self, val): 

896 self._points[1, 1] = val 

897 self.invalidate() 

898 

899 @BboxBase.p0.setter 

900 def p0(self, val): 

901 self._points[0] = val 

902 self.invalidate() 

903 

904 @BboxBase.p1.setter 

905 def p1(self, val): 

906 self._points[1] = val 

907 self.invalidate() 

908 

909 @BboxBase.intervalx.setter 

910 def intervalx(self, interval): 

911 self._points[:, 0] = interval 

912 self.invalidate() 

913 

914 @BboxBase.intervaly.setter 

915 def intervaly(self, interval): 

916 self._points[:, 1] = interval 

917 self.invalidate() 

918 

919 @BboxBase.bounds.setter 

920 def bounds(self, bounds): 

921 l, b, w, h = bounds 

922 points = np.array([[l, b], [l + w, b + h]], float) 

923 if np.any(self._points != points): 

924 self._points = points 

925 self.invalidate() 

926 

927 @property 

928 def minpos(self): 

929 return self._minpos 

930 

931 @property 

932 def minposx(self): 

933 return self._minpos[0] 

934 

935 @property 

936 def minposy(self): 

937 return self._minpos[1] 

938 

939 def get_points(self): 

940 """ 

941 Get the points of the bounding box directly as a numpy array 

942 of the form: ``[[x0, y0], [x1, y1]]``. 

943 """ 

944 self._invalid = 0 

945 return self._points 

946 

947 def set_points(self, points): 

948 """ 

949 Set the points of the bounding box directly from a numpy array 

950 of the form: ``[[x0, y0], [x1, y1]]``. No error checking is 

951 performed, as this method is mainly for internal use. 

952 """ 

953 if np.any(self._points != points): 

954 self._points = points 

955 self.invalidate() 

956 

957 def set(self, other): 

958 """ 

959 Set this bounding box from the "frozen" bounds of another `Bbox`. 

960 """ 

961 if np.any(self._points != other.get_points()): 

962 self._points = other.get_points() 

963 self.invalidate() 

964 

965 def mutated(self): 

966 'Return whether the bbox has changed since init.' 

967 return self.mutatedx() or self.mutatedy() 

968 

969 def mutatedx(self): 

970 'Return whether the x-limits have changed since init.' 

971 return (self._points[0, 0] != self._points_orig[0, 0] or 

972 self._points[1, 0] != self._points_orig[1, 0]) 

973 

974 def mutatedy(self): 

975 'Return whether the y-limits have changed since init.' 

976 return (self._points[0, 1] != self._points_orig[0, 1] or 

977 self._points[1, 1] != self._points_orig[1, 1]) 

978 

979 

980class TransformedBbox(BboxBase): 

981 """ 

982 A :class:`Bbox` that is automatically transformed by a given 

983 transform. When either the child bounding box or transform 

984 changes, the bounds of this bbox will update accordingly. 

985 """ 

986 def __init__(self, bbox, transform, **kwargs): 

987 """ 

988 Parameters 

989 ---------- 

990 bbox : :class:`Bbox` 

991 

992 transform : :class:`Transform` 

993 """ 

994 if not bbox.is_bbox: 

995 raise ValueError("'bbox' is not a bbox") 

996 cbook._check_isinstance(Transform, transform=transform) 

997 if transform.input_dims != 2 or transform.output_dims != 2: 

998 raise ValueError( 

999 "The input and output dimensions of 'transform' must be 2") 

1000 

1001 BboxBase.__init__(self, **kwargs) 

1002 self._bbox = bbox 

1003 self._transform = transform 

1004 self.set_children(bbox, transform) 

1005 self._points = None 

1006 

1007 def __str__(self): 

1008 return ("{}(\n" 

1009 "{},\n" 

1010 "{})" 

1011 .format(type(self).__name__, 

1012 _indent_str(self._bbox), 

1013 _indent_str(self._transform))) 

1014 

1015 def get_points(self): 

1016 # docstring inherited 

1017 if self._invalid: 

1018 p = self._bbox.get_points() 

1019 # Transform all four points, then make a new bounding box 

1020 # from the result, taking care to make the orientation the 

1021 # same. 

1022 points = self._transform.transform( 

1023 [[p[0, 0], p[0, 1]], 

1024 [p[1, 0], p[0, 1]], 

1025 [p[0, 0], p[1, 1]], 

1026 [p[1, 0], p[1, 1]]]) 

1027 points = np.ma.filled(points, 0.0) 

1028 

1029 xs = min(points[:, 0]), max(points[:, 0]) 

1030 if p[0, 0] > p[1, 0]: 

1031 xs = xs[::-1] 

1032 

1033 ys = min(points[:, 1]), max(points[:, 1]) 

1034 if p[0, 1] > p[1, 1]: 

1035 ys = ys[::-1] 

1036 

1037 self._points = np.array([ 

1038 [xs[0], ys[0]], 

1039 [xs[1], ys[1]] 

1040 ]) 

1041 

1042 self._invalid = 0 

1043 return self._points 

1044 

1045 if DEBUG: 

1046 _get_points = get_points 

1047 

1048 def get_points(self): 

1049 points = self._get_points() 

1050 self._check(points) 

1051 return points 

1052 

1053 

1054class LockableBbox(BboxBase): 

1055 """ 

1056 A :class:`Bbox` where some elements may be locked at certain values. 

1057 

1058 When the child bounding box changes, the bounds of this bbox will update 

1059 accordingly with the exception of the locked elements. 

1060 """ 

1061 def __init__(self, bbox, x0=None, y0=None, x1=None, y1=None, **kwargs): 

1062 """ 

1063 Parameters 

1064 ---------- 

1065 bbox : Bbox 

1066 The child bounding box to wrap. 

1067 

1068 x0 : float or None 

1069 The locked value for x0, or None to leave unlocked. 

1070 

1071 y0 : float or None 

1072 The locked value for y0, or None to leave unlocked. 

1073 

1074 x1 : float or None 

1075 The locked value for x1, or None to leave unlocked. 

1076 

1077 y1 : float or None 

1078 The locked value for y1, or None to leave unlocked. 

1079 

1080 """ 

1081 if not bbox.is_bbox: 

1082 raise ValueError("'bbox' is not a bbox") 

1083 

1084 BboxBase.__init__(self, **kwargs) 

1085 self._bbox = bbox 

1086 self.set_children(bbox) 

1087 self._points = None 

1088 fp = [x0, y0, x1, y1] 

1089 mask = [val is None for val in fp] 

1090 self._locked_points = np.ma.array(fp, float, mask=mask).reshape((2, 2)) 

1091 

1092 def __str__(self): 

1093 return ("{}(\n" 

1094 "{},\n" 

1095 "{})" 

1096 .format(type(self).__name__, 

1097 _indent_str(self._bbox), 

1098 _indent_str(self._locked_points))) 

1099 

1100 def get_points(self): 

1101 # docstring inherited 

1102 if self._invalid: 

1103 points = self._bbox.get_points() 

1104 self._points = np.where(self._locked_points.mask, 

1105 points, 

1106 self._locked_points) 

1107 self._invalid = 0 

1108 return self._points 

1109 

1110 if DEBUG: 

1111 _get_points = get_points 

1112 

1113 def get_points(self): 

1114 points = self._get_points() 

1115 self._check(points) 

1116 return points 

1117 

1118 @property 

1119 def locked_x0(self): 

1120 """ 

1121 float or None: The value used for the locked x0. 

1122 """ 

1123 if self._locked_points.mask[0, 0]: 

1124 return None 

1125 else: 

1126 return self._locked_points[0, 0] 

1127 

1128 @locked_x0.setter 

1129 def locked_x0(self, x0): 

1130 self._locked_points.mask[0, 0] = x0 is None 

1131 self._locked_points.data[0, 0] = x0 

1132 self.invalidate() 

1133 

1134 @property 

1135 def locked_y0(self): 

1136 """ 

1137 float or None: The value used for the locked y0. 

1138 """ 

1139 if self._locked_points.mask[0, 1]: 

1140 return None 

1141 else: 

1142 return self._locked_points[0, 1] 

1143 

1144 @locked_y0.setter 

1145 def locked_y0(self, y0): 

1146 self._locked_points.mask[0, 1] = y0 is None 

1147 self._locked_points.data[0, 1] = y0 

1148 self.invalidate() 

1149 

1150 @property 

1151 def locked_x1(self): 

1152 """ 

1153 float or None: The value used for the locked x1. 

1154 """ 

1155 if self._locked_points.mask[1, 0]: 

1156 return None 

1157 else: 

1158 return self._locked_points[1, 0] 

1159 

1160 @locked_x1.setter 

1161 def locked_x1(self, x1): 

1162 self._locked_points.mask[1, 0] = x1 is None 

1163 self._locked_points.data[1, 0] = x1 

1164 self.invalidate() 

1165 

1166 @property 

1167 def locked_y1(self): 

1168 """ 

1169 float or None: The value used for the locked y1. 

1170 """ 

1171 if self._locked_points.mask[1, 1]: 

1172 return None 

1173 else: 

1174 return self._locked_points[1, 1] 

1175 

1176 @locked_y1.setter 

1177 def locked_y1(self, y1): 

1178 self._locked_points.mask[1, 1] = y1 is None 

1179 self._locked_points.data[1, 1] = y1 

1180 self.invalidate() 

1181 

1182 

1183class Transform(TransformNode): 

1184 """ 

1185 The base class of all :class:`TransformNode` instances that 

1186 actually perform a transformation. 

1187 

1188 All non-affine transformations should be subclasses of this class. 

1189 New affine transformations should be subclasses of `Affine2D`. 

1190 

1191 Subclasses of this class should override the following members (at 

1192 minimum): 

1193 

1194 - :attr:`input_dims` 

1195 - :attr:`output_dims` 

1196 - :meth:`transform` 

1197 - :meth:`inverted` (if an inverse exists) 

1198 

1199 The following attributes may be overridden if the default is unsuitable: 

1200 

1201 - :attr:`is_separable` (defaults to True for 1d -> 1d transforms, False 

1202 otherwise) 

1203 - :attr:`has_inverse` (defaults to True if :meth:`inverted` is overridden, 

1204 False otherwise) 

1205 

1206 If the transform needs to do something non-standard with 

1207 :class:`matplotlib.path.Path` objects, such as adding curves 

1208 where there were once line segments, it should override: 

1209 

1210 - :meth:`transform_path` 

1211 """ 

1212 

1213 input_dims = None 

1214 """ 

1215 The number of input dimensions of this transform. 

1216 Must be overridden (with integers) in the subclass. 

1217 """ 

1218 

1219 output_dims = None 

1220 """ 

1221 The number of output dimensions of this transform. 

1222 Must be overridden (with integers) in the subclass. 

1223 """ 

1224 

1225 is_separable = False 

1226 """True if this transform is separable in the x- and y- dimensions.""" 

1227 

1228 has_inverse = False 

1229 """True if this transform has a corresponding inverse transform.""" 

1230 

1231 def __init_subclass__(cls): 

1232 # 1d transforms are always separable; we assume higher-dimensional ones 

1233 # are not but subclasses can also directly set is_separable -- this is 

1234 # verified by checking whether "is_separable" appears more than once in 

1235 # the class's MRO (it appears once in Transform). 

1236 if (sum("is_separable" in vars(parent) for parent in cls.__mro__) == 1 

1237 and cls.input_dims == cls.output_dims == 1): 

1238 cls.is_separable = True 

1239 # Transform.inverted raises NotImplementedError; we assume that if this 

1240 # is overridden then the transform is invertible but subclass can also 

1241 # directly set has_inverse. 

1242 if (sum("has_inverse" in vars(parent) for parent in cls.__mro__) == 1 

1243 and hasattr(cls, "inverted") 

1244 and cls.inverted is not Transform.inverted): 

1245 cls.has_inverse = True 

1246 

1247 def __add__(self, other): 

1248 """ 

1249 Composes two transforms together such that *self* is followed 

1250 by *other*. 

1251 """ 

1252 if isinstance(other, Transform): 

1253 return composite_transform_factory(self, other) 

1254 raise TypeError( 

1255 "Can not add Transform to object of type '%s'" % type(other)) 

1256 

1257 def __radd__(self, other): 

1258 """ 

1259 Composes two transforms together such that *self* is followed 

1260 by *other*. 

1261 """ 

1262 if isinstance(other, Transform): 

1263 return composite_transform_factory(other, self) 

1264 raise TypeError( 

1265 "Can not add Transform to object of type '%s'" % type(other)) 

1266 

1267 # Equality is based on object identity for `Transform`s (so we don't 

1268 # override `__eq__`), but some subclasses, such as TransformWrapper & 

1269 # AffineBase, override this behavior. 

1270 

1271 def _iter_break_from_left_to_right(self): 

1272 """ 

1273 Returns an iterator breaking down this transform stack from left to 

1274 right recursively. If self == ((A, N), A) then the result will be an 

1275 iterator which yields I : ((A, N), A), followed by A : (N, A), 

1276 followed by (A, N) : (A), but not ((A, N), A) : I. 

1277 

1278 This is equivalent to flattening the stack then yielding 

1279 ``flat_stack[:i], flat_stack[i:]`` where i=0..(n-1). 

1280 

1281 """ 

1282 yield IdentityTransform(), self 

1283 

1284 @property 

1285 def depth(self): 

1286 """ 

1287 Returns the number of transforms which have been chained 

1288 together to form this Transform instance. 

1289 

1290 .. note:: 

1291 

1292 For the special case of a Composite transform, the maximum depth 

1293 of the two is returned. 

1294 

1295 """ 

1296 return 1 

1297 

1298 def contains_branch(self, other): 

1299 """ 

1300 Return whether the given transform is a sub-tree of this transform. 

1301 

1302 This routine uses transform equality to identify sub-trees, therefore 

1303 in many situations it is object id which will be used. 

1304 

1305 For the case where the given transform represents the whole 

1306 of this transform, returns True. 

1307 

1308 """ 

1309 if self.depth < other.depth: 

1310 return False 

1311 

1312 # check that a subtree is equal to other (starting from self) 

1313 for _, sub_tree in self._iter_break_from_left_to_right(): 

1314 if sub_tree == other: 

1315 return True 

1316 return False 

1317 

1318 def contains_branch_seperately(self, other_transform): 

1319 """ 

1320 Returns whether the given branch is a sub-tree of this transform on 

1321 each separate dimension. 

1322 

1323 A common use for this method is to identify if a transform is a blended 

1324 transform containing an axes' data transform. e.g.:: 

1325 

1326 x_isdata, y_isdata = trans.contains_branch_seperately(ax.transData) 

1327 

1328 """ 

1329 if self.output_dims != 2: 

1330 raise ValueError('contains_branch_seperately only supports ' 

1331 'transforms with 2 output dimensions') 

1332 # for a non-blended transform each separate dimension is the same, so 

1333 # just return the appropriate shape. 

1334 return [self.contains_branch(other_transform)] * 2 

1335 

1336 def __sub__(self, other): 

1337 """ 

1338 Returns a transform stack which goes all the way down self's transform 

1339 stack, and then ascends back up other's stack. If it can, this is 

1340 optimised:: 

1341 

1342 # normally 

1343 A - B == a + b.inverted() 

1344 

1345 # sometimes, when A contains the tree B there is no need to 

1346 # descend all the way down to the base of A (via B), instead we 

1347 # can just stop at B. 

1348 

1349 (A + B) - (B)^-1 == A 

1350 

1351 # similarly, when B contains tree A, we can avoid descending A at 

1352 # all, basically: 

1353 A - (A + B) == ((B + A) - A).inverted() or B^-1 

1354 

1355 For clarity, the result of ``(A + B) - B + B == (A + B)``. 

1356 

1357 """ 

1358 # we only know how to do this operation if other is a Transform. 

1359 if not isinstance(other, Transform): 

1360 return NotImplemented 

1361 

1362 for remainder, sub_tree in self._iter_break_from_left_to_right(): 

1363 if sub_tree == other: 

1364 return remainder 

1365 

1366 for remainder, sub_tree in other._iter_break_from_left_to_right(): 

1367 if sub_tree == self: 

1368 if not remainder.has_inverse: 

1369 raise ValueError( 

1370 "The shortcut cannot be computed since 'other' " 

1371 "includes a non-invertible component") 

1372 return remainder.inverted() 

1373 

1374 # if we have got this far, then there was no shortcut possible 

1375 if other.has_inverse: 

1376 return self + other.inverted() 

1377 else: 

1378 raise ValueError('It is not possible to compute transA - transB ' 

1379 'since transB cannot be inverted and there is no ' 

1380 'shortcut possible.') 

1381 

1382 def __array__(self, *args, **kwargs): 

1383 """ 

1384 Array interface to get at this Transform's affine matrix. 

1385 """ 

1386 return self.get_affine().get_matrix() 

1387 

1388 def transform(self, values): 

1389 """ 

1390 Performs the transformation on the given array of values. 

1391 

1392 Accepts a numpy array of shape (N x :attr:`input_dims`) and 

1393 returns a numpy array of shape (N x :attr:`output_dims`). 

1394 

1395 Alternatively, accepts a numpy array of length :attr:`input_dims` 

1396 and returns a numpy array of length :attr:`output_dims`. 

1397 """ 

1398 # Ensure that values is a 2d array (but remember whether 

1399 # we started with a 1d or 2d array). 

1400 values = np.asanyarray(values) 

1401 ndim = values.ndim 

1402 values = values.reshape((-1, self.input_dims)) 

1403 

1404 # Transform the values 

1405 res = self.transform_affine(self.transform_non_affine(values)) 

1406 

1407 # Convert the result back to the shape of the input values. 

1408 if ndim == 0: 

1409 assert not np.ma.is_masked(res) # just to be on the safe side 

1410 return res[0, 0] 

1411 if ndim == 1: 

1412 return res.reshape(-1) 

1413 elif ndim == 2: 

1414 return res 

1415 raise ValueError( 

1416 "Input values must have shape (N x {dims}) " 

1417 "or ({dims}).".format(dims=self.input_dims)) 

1418 

1419 def transform_affine(self, values): 

1420 """ 

1421 Performs only the affine part of this transformation on the 

1422 given array of values. 

1423 

1424 ``transform(values)`` is always equivalent to 

1425 ``transform_affine(transform_non_affine(values))``. 

1426 

1427 In non-affine transformations, this is generally a no-op. In 

1428 affine transformations, this is equivalent to 

1429 ``transform(values)``. 

1430 

1431 Parameters 

1432 ---------- 

1433 values : array 

1434 The input values as NumPy array of length :attr:`input_dims` or 

1435 shape (N x :attr:`input_dims`). 

1436 

1437 Returns 

1438 ------- 

1439 values : array 

1440 The output values as NumPy array of length :attr:`input_dims` or 

1441 shape (N x :attr:`output_dims`), depending on the input. 

1442 """ 

1443 return self.get_affine().transform(values) 

1444 

1445 def transform_non_affine(self, values): 

1446 """ 

1447 Performs only the non-affine part of the transformation. 

1448 

1449 ``transform(values)`` is always equivalent to 

1450 ``transform_affine(transform_non_affine(values))``. 

1451 

1452 In non-affine transformations, this is generally equivalent to 

1453 ``transform(values)``. In affine transformations, this is 

1454 always a no-op. 

1455 

1456 Parameters 

1457 ---------- 

1458 values : array 

1459 The input values as NumPy array of length :attr:`input_dims` or 

1460 shape (N x :attr:`input_dims`). 

1461 

1462 Returns 

1463 ------- 

1464 values : array 

1465 The output values as NumPy array of length :attr:`input_dims` or 

1466 shape (N x :attr:`output_dims`), depending on the input. 

1467 """ 

1468 return values 

1469 

1470 def transform_bbox(self, bbox): 

1471 """ 

1472 Transform the given bounding box. 

1473 

1474 Note, for smarter transforms including caching (a common 

1475 requirement for matplotlib figures), see :class:`TransformedBbox`. 

1476 """ 

1477 return Bbox(self.transform(bbox.get_points())) 

1478 

1479 def get_affine(self): 

1480 """ 

1481 Get the affine part of this transform. 

1482 """ 

1483 return IdentityTransform() 

1484 

1485 def get_matrix(self): 

1486 """ 

1487 Get the Affine transformation array for the affine part 

1488 of this transform. 

1489 

1490 """ 

1491 return self.get_affine().get_matrix() 

1492 

1493 def transform_point(self, point): 

1494 """ 

1495 Return a transformed point. 

1496 

1497 This function is only kept for backcompatibility; the more general 

1498 `.transform` method is capable of transforming both a list of points 

1499 and a single point. 

1500 

1501 The point is given as a sequence of length :attr:`input_dims`. 

1502 The transformed point is returned as a sequence of length 

1503 :attr:`output_dims`. 

1504 """ 

1505 if len(point) != self.input_dims: 

1506 raise ValueError("The length of 'point' must be 'self.input_dims'") 

1507 return self.transform(point) 

1508 

1509 def transform_path(self, path): 

1510 """ 

1511 Returns a transformed path. 

1512 

1513 *path*: a :class:`~matplotlib.path.Path` instance. 

1514 

1515 In some cases, this transform may insert curves into the path 

1516 that began as line segments. 

1517 """ 

1518 return self.transform_path_affine(self.transform_path_non_affine(path)) 

1519 

1520 def transform_path_affine(self, path): 

1521 """ 

1522 Returns a path, transformed only by the affine part of 

1523 this transform. 

1524 

1525 *path*: a :class:`~matplotlib.path.Path` instance. 

1526 

1527 ``transform_path(path)`` is equivalent to 

1528 ``transform_path_affine(transform_path_non_affine(values))``. 

1529 """ 

1530 return self.get_affine().transform_path_affine(path) 

1531 

1532 def transform_path_non_affine(self, path): 

1533 """ 

1534 Returns a path, transformed only by the non-affine 

1535 part of this transform. 

1536 

1537 *path*: a :class:`~matplotlib.path.Path` instance. 

1538 

1539 ``transform_path(path)`` is equivalent to 

1540 ``transform_path_affine(transform_path_non_affine(values))``. 

1541 """ 

1542 x = self.transform_non_affine(path.vertices) 

1543 return Path._fast_from_codes_and_verts(x, path.codes, path) 

1544 

1545 def transform_angles(self, angles, pts, radians=False, pushoff=1e-5): 

1546 """ 

1547 Transforms a set of angles anchored at specific locations. 

1548 

1549 Parameters 

1550 ---------- 

1551 angles : (N,) array-like 

1552 The angles to transform. 

1553 pts : (N, 2) array-like 

1554 The points where the angles are anchored. 

1555 radians : bool, default: False 

1556 Whether *angles* are radians or degrees. 

1557 pushoff : float 

1558 For each point in *pts* and angle in *angles*, the transformed 

1559 angle is computed by transforming a segment of length *pushoff* 

1560 starting at that point and making that angle relative to the 

1561 horizontal axis, and measuring the angle between the horizontal 

1562 axis and the transformed segment. 

1563 

1564 Returns 

1565 ------- 

1566 transformed_angles : (N,) array 

1567 """ 

1568 # Must be 2D 

1569 if self.input_dims != 2 or self.output_dims != 2: 

1570 raise NotImplementedError('Only defined in 2D') 

1571 angles = np.asarray(angles) 

1572 pts = np.asarray(pts) 

1573 if angles.ndim != 1 or angles.shape[0] != pts.shape[0]: 

1574 raise ValueError("'angles' must be a column vector and have same " 

1575 "number of rows as 'pts'") 

1576 if pts.shape[1] != 2: 

1577 raise ValueError("'pts' must be array with 2 columns for x, y") 

1578 # Convert to radians if desired 

1579 if not radians: 

1580 angles = np.deg2rad(angles) 

1581 # Move a short distance away 

1582 pts2 = pts + pushoff * np.c_[np.cos(angles), np.sin(angles)] 

1583 # Transform both sets of points 

1584 tpts = self.transform(pts) 

1585 tpts2 = self.transform(pts2) 

1586 # Calculate transformed angles 

1587 d = tpts2 - tpts 

1588 a = np.arctan2(d[:, 1], d[:, 0]) 

1589 # Convert back to degrees if desired 

1590 if not radians: 

1591 a = np.rad2deg(a) 

1592 return a 

1593 

1594 def inverted(self): 

1595 """ 

1596 Return the corresponding inverse transformation. 

1597 

1598 It holds ``x == self.inverted().transform(self.transform(x))``. 

1599 

1600 The return value of this method should be treated as 

1601 temporary. An update to *self* does not cause a corresponding 

1602 update to its inverted copy. 

1603 """ 

1604 raise NotImplementedError() 

1605 

1606 

1607class TransformWrapper(Transform): 

1608 """ 

1609 A helper class that holds a single child transform and acts 

1610 equivalently to it. 

1611 

1612 This is useful if a node of the transform tree must be replaced at 

1613 run time with a transform of a different type. This class allows 

1614 that replacement to correctly trigger invalidation. 

1615 

1616 Note that :class:`TransformWrapper` instances must have the same 

1617 input and output dimensions during their entire lifetime, so the 

1618 child transform may only be replaced with another child transform 

1619 of the same dimensions. 

1620 """ 

1621 pass_through = True 

1622 

1623 def __init__(self, child): 

1624 """ 

1625 *child*: A class:`Transform` instance. This child may later 

1626 be replaced with :meth:`set`. 

1627 """ 

1628 cbook._check_isinstance(Transform, child=child) 

1629 self._init(child) 

1630 self.set_children(child) 

1631 

1632 def _init(self, child): 

1633 Transform.__init__(self) 

1634 self.input_dims = child.input_dims 

1635 self.output_dims = child.output_dims 

1636 self._set(child) 

1637 self._invalid = 0 

1638 

1639 def __eq__(self, other): 

1640 return self._child.__eq__(other) 

1641 

1642 def __str__(self): 

1643 return ("{}(\n" 

1644 "{})" 

1645 .format(type(self).__name__, 

1646 _indent_str(self._child))) 

1647 

1648 def frozen(self): 

1649 # docstring inherited 

1650 return self._child.frozen() 

1651 

1652 def _set(self, child): 

1653 self._child = child 

1654 

1655 self.transform = child.transform 

1656 self.transform_affine = child.transform_affine 

1657 self.transform_non_affine = child.transform_non_affine 

1658 self.transform_path = child.transform_path 

1659 self.transform_path_affine = child.transform_path_affine 

1660 self.transform_path_non_affine = child.transform_path_non_affine 

1661 self.get_affine = child.get_affine 

1662 self.inverted = child.inverted 

1663 self.get_matrix = child.get_matrix 

1664 

1665 # note we do not wrap other properties here since the transform's 

1666 # child can be changed with WrappedTransform.set and so checking 

1667 # is_affine and other such properties may be dangerous. 

1668 

1669 def set(self, child): 

1670 """ 

1671 Replace the current child of this transform with another one. 

1672 

1673 The new child must have the same number of input and output 

1674 dimensions as the current child. 

1675 """ 

1676 if (child.input_dims != self.input_dims or 

1677 child.output_dims != self.output_dims): 

1678 raise ValueError( 

1679 "The new child must have the same number of input and output " 

1680 "dimensions as the current child") 

1681 

1682 self.set_children(child) 

1683 self._set(child) 

1684 

1685 self._invalid = 0 

1686 self.invalidate() 

1687 self._invalid = 0 

1688 

1689 is_affine = property(lambda self: self._child.is_affine) 

1690 is_separable = property(lambda self: self._child.is_separable) 

1691 has_inverse = property(lambda self: self._child.has_inverse) 

1692 

1693 

1694class AffineBase(Transform): 

1695 """ 

1696 The base class of all affine transformations of any number of 

1697 dimensions. 

1698 """ 

1699 is_affine = True 

1700 

1701 def __init__(self, *args, **kwargs): 

1702 Transform.__init__(self, *args, **kwargs) 

1703 self._inverted = None 

1704 

1705 def __array__(self, *args, **kwargs): 

1706 # optimises the access of the transform matrix vs. the superclass 

1707 return self.get_matrix() 

1708 

1709 def __eq__(self, other): 

1710 if getattr(other, "is_affine", False): 

1711 return np.all(self.get_matrix() == other.get_matrix()) 

1712 return NotImplemented 

1713 

1714 def transform(self, values): 

1715 # docstring inherited 

1716 return self.transform_affine(values) 

1717 

1718 def transform_affine(self, values): 

1719 # docstring inherited 

1720 raise NotImplementedError('Affine subclasses should override this ' 

1721 'method.') 

1722 

1723 def transform_non_affine(self, points): 

1724 # docstring inherited 

1725 return points 

1726 

1727 def transform_path(self, path): 

1728 # docstring inherited 

1729 return self.transform_path_affine(path) 

1730 

1731 def transform_path_affine(self, path): 

1732 # docstring inherited 

1733 return Path(self.transform_affine(path.vertices), 

1734 path.codes, path._interpolation_steps) 

1735 

1736 def transform_path_non_affine(self, path): 

1737 # docstring inherited 

1738 return path 

1739 

1740 def get_affine(self): 

1741 # docstring inherited 

1742 return self 

1743 

1744 

1745class Affine2DBase(AffineBase): 

1746 """ 

1747 The base class of all 2D affine transformations. 

1748 

1749 2D affine transformations are performed using a 3x3 numpy array:: 

1750 

1751 a c e 

1752 b d f 

1753 0 0 1 

1754 

1755 This class provides the read-only interface. For a mutable 2D 

1756 affine transformation, use :class:`Affine2D`. 

1757 

1758 Subclasses of this class will generally only need to override a 

1759 constructor and :meth:`get_matrix` that generates a custom 3x3 matrix. 

1760 """ 

1761 input_dims = 2 

1762 output_dims = 2 

1763 

1764 def frozen(self): 

1765 # docstring inherited 

1766 return Affine2D(self.get_matrix().copy()) 

1767 

1768 @property 

1769 def is_separable(self): 

1770 mtx = self.get_matrix() 

1771 return mtx[0, 1] == mtx[1, 0] == 0.0 

1772 

1773 def to_values(self): 

1774 """ 

1775 Return the values of the matrix as an ``(a, b, c, d, e, f)`` tuple. 

1776 """ 

1777 mtx = self.get_matrix() 

1778 return tuple(mtx[:2].swapaxes(0, 1).flat) 

1779 

1780 @staticmethod 

1781 @cbook.deprecated( 

1782 "3.2", alternative="Affine2D.from_values(...).get_matrix()") 

1783 def matrix_from_values(a, b, c, d, e, f): 

1784 """ 

1785 Create a new transformation matrix as a 3x3 numpy array of the form:: 

1786 

1787 a c e 

1788 b d f 

1789 0 0 1 

1790 """ 

1791 return np.array([[a, c, e], [b, d, f], [0.0, 0.0, 1.0]], float) 

1792 

1793 def transform_affine(self, points): 

1794 mtx = self.get_matrix() 

1795 if isinstance(points, np.ma.MaskedArray): 

1796 tpoints = affine_transform(points.data, mtx) 

1797 return np.ma.MaskedArray(tpoints, mask=np.ma.getmask(points)) 

1798 return affine_transform(points, mtx) 

1799 

1800 if DEBUG: 

1801 _transform_affine = transform_affine 

1802 

1803 def transform_affine(self, points): 

1804 # docstring inherited 

1805 # The major speed trap here is just converting to the 

1806 # points to an array in the first place. If we can use 

1807 # more arrays upstream, that should help here. 

1808 if not isinstance(points, (np.ma.MaskedArray, np.ndarray)): 

1809 cbook._warn_external( 

1810 f'A non-numpy array of type {type(points)} was passed in ' 

1811 f'for transformation, which results in poor performance.') 

1812 return self._transform_affine(points) 

1813 

1814 def inverted(self): 

1815 # docstring inherited 

1816 if self._inverted is None or self._invalid: 

1817 mtx = self.get_matrix() 

1818 shorthand_name = None 

1819 if self._shorthand_name: 

1820 shorthand_name = '(%s)-1' % self._shorthand_name 

1821 self._inverted = Affine2D(inv(mtx), shorthand_name=shorthand_name) 

1822 self._invalid = 0 

1823 return self._inverted 

1824 

1825 

1826class Affine2D(Affine2DBase): 

1827 """ 

1828 A mutable 2D affine transformation. 

1829 """ 

1830 

1831 def __init__(self, matrix=None, **kwargs): 

1832 """ 

1833 Initialize an Affine transform from a 3x3 numpy float array:: 

1834 

1835 a c e 

1836 b d f 

1837 0 0 1 

1838 

1839 If *matrix* is None, initialize with the identity transform. 

1840 """ 

1841 Affine2DBase.__init__(self, **kwargs) 

1842 if matrix is None: 

1843 # A bit faster than np.identity(3). 

1844 matrix = IdentityTransform._mtx.copy() 

1845 self._mtx = matrix 

1846 self._invalid = 0 

1847 

1848 def __str__(self): 

1849 return ("{}(\n" 

1850 "{})" 

1851 .format(type(self).__name__, 

1852 _indent_str(self._mtx))) 

1853 

1854 @staticmethod 

1855 def from_values(a, b, c, d, e, f): 

1856 """ 

1857 Create a new Affine2D instance from the given values:: 

1858 

1859 a c e 

1860 b d f 

1861 0 0 1 

1862 

1863 . 

1864 """ 

1865 return Affine2D( 

1866 np.array([a, c, e, b, d, f, 0.0, 0.0, 1.0], float).reshape((3, 3))) 

1867 

1868 def get_matrix(self): 

1869 """ 

1870 Get the underlying transformation matrix as a 3x3 numpy array:: 

1871 

1872 a c e 

1873 b d f 

1874 0 0 1 

1875 

1876 . 

1877 """ 

1878 if self._invalid: 

1879 self._inverted = None 

1880 self._invalid = 0 

1881 return self._mtx 

1882 

1883 def set_matrix(self, mtx): 

1884 """ 

1885 Set the underlying transformation matrix from a 3x3 numpy array:: 

1886 

1887 a c e 

1888 b d f 

1889 0 0 1 

1890 

1891 . 

1892 """ 

1893 self._mtx = mtx 

1894 self.invalidate() 

1895 

1896 def set(self, other): 

1897 """ 

1898 Set this transformation from the frozen copy of another 

1899 :class:`Affine2DBase` object. 

1900 """ 

1901 cbook._check_isinstance(Affine2DBase, other=other) 

1902 self._mtx = other.get_matrix() 

1903 self.invalidate() 

1904 

1905 @staticmethod 

1906 def identity(): 

1907 """ 

1908 Return a new `Affine2D` object that is the identity transform. 

1909 

1910 Unless this transform will be mutated later on, consider using 

1911 the faster :class:`IdentityTransform` class instead. 

1912 """ 

1913 return Affine2D() 

1914 

1915 def clear(self): 

1916 """ 

1917 Reset the underlying matrix to the identity transform. 

1918 """ 

1919 # A bit faster than np.identity(3). 

1920 self._mtx = IdentityTransform._mtx.copy() 

1921 self.invalidate() 

1922 return self 

1923 

1924 def rotate(self, theta): 

1925 """ 

1926 Add a rotation (in radians) to this transform in place. 

1927 

1928 Returns *self*, so this method can easily be chained with more 

1929 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

1930 and :meth:`scale`. 

1931 """ 

1932 a = np.cos(theta) 

1933 b = np.sin(theta) 

1934 rotate_mtx = np.array([[a, -b, 0.0], [b, a, 0.0], [0.0, 0.0, 1.0]], 

1935 float) 

1936 self._mtx = np.dot(rotate_mtx, self._mtx) 

1937 self.invalidate() 

1938 return self 

1939 

1940 def rotate_deg(self, degrees): 

1941 """ 

1942 Add a rotation (in degrees) to this transform in place. 

1943 

1944 Returns *self*, so this method can easily be chained with more 

1945 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

1946 and :meth:`scale`. 

1947 """ 

1948 return self.rotate(np.deg2rad(degrees)) 

1949 

1950 def rotate_around(self, x, y, theta): 

1951 """ 

1952 Add a rotation (in radians) around the point (x, y) in place. 

1953 

1954 Returns *self*, so this method can easily be chained with more 

1955 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

1956 and :meth:`scale`. 

1957 """ 

1958 return self.translate(-x, -y).rotate(theta).translate(x, y) 

1959 

1960 def rotate_deg_around(self, x, y, degrees): 

1961 """ 

1962 Add a rotation (in degrees) around the point (x, y) in place. 

1963 

1964 Returns *self*, so this method can easily be chained with more 

1965 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

1966 and :meth:`scale`. 

1967 """ 

1968 # Cast to float to avoid wraparound issues with uint8's 

1969 x, y = float(x), float(y) 

1970 return self.translate(-x, -y).rotate_deg(degrees).translate(x, y) 

1971 

1972 def translate(self, tx, ty): 

1973 """ 

1974 Adds a translation in place. 

1975 

1976 Returns *self*, so this method can easily be chained with more 

1977 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

1978 and :meth:`scale`. 

1979 """ 

1980 translate_mtx = np.array( 

1981 [[1.0, 0.0, tx], [0.0, 1.0, ty], [0.0, 0.0, 1.0]], float) 

1982 self._mtx = np.dot(translate_mtx, self._mtx) 

1983 self.invalidate() 

1984 return self 

1985 

1986 def scale(self, sx, sy=None): 

1987 """ 

1988 Adds a scale in place. 

1989 

1990 If *sy* is None, the same scale is applied in both the *x*- and 

1991 *y*-directions. 

1992 

1993 Returns *self*, so this method can easily be chained with more 

1994 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

1995 and :meth:`scale`. 

1996 """ 

1997 if sy is None: 

1998 sy = sx 

1999 scale_mtx = np.array( 

2000 [[sx, 0.0, 0.0], [0.0, sy, 0.0], [0.0, 0.0, 1.0]], float) 

2001 self._mtx = np.dot(scale_mtx, self._mtx) 

2002 self.invalidate() 

2003 return self 

2004 

2005 def skew(self, xShear, yShear): 

2006 """ 

2007 Adds a skew in place. 

2008 

2009 *xShear* and *yShear* are the shear angles along the *x*- and 

2010 *y*-axes, respectively, in radians. 

2011 

2012 Returns *self*, so this method can easily be chained with more 

2013 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

2014 and :meth:`scale`. 

2015 """ 

2016 rotX = np.tan(xShear) 

2017 rotY = np.tan(yShear) 

2018 skew_mtx = np.array( 

2019 [[1.0, rotX, 0.0], [rotY, 1.0, 0.0], [0.0, 0.0, 1.0]], float) 

2020 self._mtx = np.dot(skew_mtx, self._mtx) 

2021 self.invalidate() 

2022 return self 

2023 

2024 def skew_deg(self, xShear, yShear): 

2025 """ 

2026 Adds a skew in place. 

2027 

2028 *xShear* and *yShear* are the shear angles along the *x*- and 

2029 *y*-axes, respectively, in degrees. 

2030 

2031 Returns *self*, so this method can easily be chained with more 

2032 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 

2033 and :meth:`scale`. 

2034 """ 

2035 return self.skew(np.deg2rad(xShear), np.deg2rad(yShear)) 

2036 

2037 

2038class IdentityTransform(Affine2DBase): 

2039 """ 

2040 A special class that does one thing, the identity transform, in a 

2041 fast way. 

2042 """ 

2043 _mtx = np.identity(3) 

2044 

2045 def frozen(self): 

2046 # docstring inherited 

2047 return self 

2048 

2049 def __str__(self): 

2050 return ("{}()" 

2051 .format(type(self).__name__)) 

2052 

2053 def get_matrix(self): 

2054 # docstring inherited 

2055 return self._mtx 

2056 

2057 def transform(self, points): 

2058 # docstring inherited 

2059 return np.asanyarray(points) 

2060 

2061 def transform_affine(self, points): 

2062 # docstring inherited 

2063 return np.asanyarray(points) 

2064 

2065 def transform_non_affine(self, points): 

2066 # docstring inherited 

2067 return np.asanyarray(points) 

2068 

2069 def transform_path(self, path): 

2070 # docstring inherited 

2071 return path 

2072 

2073 def transform_path_affine(self, path): 

2074 # docstring inherited 

2075 return path 

2076 

2077 def transform_path_non_affine(self, path): 

2078 # docstring inherited 

2079 return path 

2080 

2081 def get_affine(self): 

2082 # docstring inherited 

2083 return self 

2084 

2085 def inverted(self): 

2086 # docstring inherited 

2087 return self 

2088 

2089 

2090class _BlendedMixin: 

2091 """Common methods for `BlendedGenericTransform` and `BlendedAffine2D`.""" 

2092 

2093 def __eq__(self, other): 

2094 if isinstance(other, (BlendedAffine2D, BlendedGenericTransform)): 

2095 return (self._x == other._x) and (self._y == other._y) 

2096 elif self._x == self._y: 

2097 return self._x == other 

2098 else: 

2099 return NotImplemented 

2100 

2101 def contains_branch_seperately(self, transform): 

2102 return (self._x.contains_branch(transform), 

2103 self._y.contains_branch(transform)) 

2104 

2105 def __str__(self): 

2106 return ("{}(\n" 

2107 "{},\n" 

2108 "{})" 

2109 .format(type(self).__name__, 

2110 _indent_str(self._x), 

2111 _indent_str(self._y))) 

2112 

2113 

2114class BlendedGenericTransform(_BlendedMixin, Transform): 

2115 """ 

2116 A "blended" transform uses one transform for the *x*-direction, and 

2117 another transform for the *y*-direction. 

2118 

2119 This "generic" version can handle any given child transform in the 

2120 *x*- and *y*-directions. 

2121 """ 

2122 input_dims = 2 

2123 output_dims = 2 

2124 is_separable = True 

2125 pass_through = True 

2126 

2127 def __init__(self, x_transform, y_transform, **kwargs): 

2128 """ 

2129 Create a new "blended" transform using *x_transform* to 

2130 transform the *x*-axis and *y_transform* to transform the 

2131 *y*-axis. 

2132 

2133 You will generally not call this constructor directly but use the 

2134 `blended_transform_factory` function instead, which can determine 

2135 automatically which kind of blended transform to create. 

2136 """ 

2137 # Here we ask: "Does it blend?" 

2138 

2139 Transform.__init__(self, **kwargs) 

2140 self._x = x_transform 

2141 self._y = y_transform 

2142 self.set_children(x_transform, y_transform) 

2143 self._affine = None 

2144 

2145 @property 

2146 def depth(self): 

2147 return max(self._x.depth, self._y.depth) 

2148 

2149 def contains_branch(self, other): 

2150 # A blended transform cannot possibly contain a branch from two 

2151 # different transforms. 

2152 return False 

2153 

2154 is_affine = property(lambda self: self._x.is_affine and self._y.is_affine) 

2155 has_inverse = property( 

2156 lambda self: self._x.has_inverse and self._y.has_inverse) 

2157 

2158 def frozen(self): 

2159 # docstring inherited 

2160 return blended_transform_factory(self._x.frozen(), self._y.frozen()) 

2161 

2162 def transform_non_affine(self, points): 

2163 # docstring inherited 

2164 if self._x.is_affine and self._y.is_affine: 

2165 return points 

2166 x = self._x 

2167 y = self._y 

2168 

2169 if x == y and x.input_dims == 2: 

2170 return x.transform_non_affine(points) 

2171 

2172 if x.input_dims == 2: 

2173 x_points = x.transform_non_affine(points)[:, 0:1] 

2174 else: 

2175 x_points = x.transform_non_affine(points[:, 0]) 

2176 x_points = x_points.reshape((len(x_points), 1)) 

2177 

2178 if y.input_dims == 2: 

2179 y_points = y.transform_non_affine(points)[:, 1:] 

2180 else: 

2181 y_points = y.transform_non_affine(points[:, 1]) 

2182 y_points = y_points.reshape((len(y_points), 1)) 

2183 

2184 if (isinstance(x_points, np.ma.MaskedArray) or 

2185 isinstance(y_points, np.ma.MaskedArray)): 

2186 return np.ma.concatenate((x_points, y_points), 1) 

2187 else: 

2188 return np.concatenate((x_points, y_points), 1) 

2189 

2190 def inverted(self): 

2191 # docstring inherited 

2192 return BlendedGenericTransform(self._x.inverted(), self._y.inverted()) 

2193 

2194 def get_affine(self): 

2195 # docstring inherited 

2196 if self._invalid or self._affine is None: 

2197 if self._x == self._y: 

2198 self._affine = self._x.get_affine() 

2199 else: 

2200 x_mtx = self._x.get_affine().get_matrix() 

2201 y_mtx = self._y.get_affine().get_matrix() 

2202 # This works because we already know the transforms are 

2203 # separable, though normally one would want to set b and 

2204 # c to zero. 

2205 mtx = np.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) 

2206 self._affine = Affine2D(mtx) 

2207 self._invalid = 0 

2208 return self._affine 

2209 

2210 

2211class BlendedAffine2D(_BlendedMixin, Affine2DBase): 

2212 """ 

2213 A "blended" transform uses one transform for the *x*-direction, and 

2214 another transform for the *y*-direction. 

2215 

2216 This version is an optimization for the case where both child 

2217 transforms are of type :class:`Affine2DBase`. 

2218 """ 

2219 is_separable = True 

2220 

2221 def __init__(self, x_transform, y_transform, **kwargs): 

2222 """ 

2223 Create a new "blended" transform using *x_transform* to 

2224 transform the *x*-axis and *y_transform* to transform the 

2225 *y*-axis. 

2226 

2227 Both *x_transform* and *y_transform* must be 2D affine 

2228 transforms. 

2229 

2230 You will generally not call this constructor directly but use the 

2231 `blended_transform_factory` function instead, which can determine 

2232 automatically which kind of blended transform to create. 

2233 """ 

2234 is_affine = x_transform.is_affine and y_transform.is_affine 

2235 is_separable = x_transform.is_separable and y_transform.is_separable 

2236 is_correct = is_affine and is_separable 

2237 if not is_correct: 

2238 raise ValueError("Both *x_transform* and *y_transform* must be 2D " 

2239 "affine transforms") 

2240 

2241 Transform.__init__(self, **kwargs) 

2242 self._x = x_transform 

2243 self._y = y_transform 

2244 self.set_children(x_transform, y_transform) 

2245 

2246 Affine2DBase.__init__(self) 

2247 self._mtx = None 

2248 

2249 def get_matrix(self): 

2250 # docstring inherited 

2251 if self._invalid: 

2252 if self._x == self._y: 

2253 self._mtx = self._x.get_matrix() 

2254 else: 

2255 x_mtx = self._x.get_matrix() 

2256 y_mtx = self._y.get_matrix() 

2257 # This works because we already know the transforms are 

2258 # separable, though normally one would want to set b and 

2259 # c to zero. 

2260 self._mtx = np.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) 

2261 self._inverted = None 

2262 self._invalid = 0 

2263 return self._mtx 

2264 

2265 

2266def blended_transform_factory(x_transform, y_transform): 

2267 """ 

2268 Create a new "blended" transform using *x_transform* to transform 

2269 the *x*-axis and *y_transform* to transform the *y*-axis. 

2270 

2271 A faster version of the blended transform is returned for the case 

2272 where both child transforms are affine. 

2273 """ 

2274 if (isinstance(x_transform, Affine2DBase) 

2275 and isinstance(y_transform, Affine2DBase)): 

2276 return BlendedAffine2D(x_transform, y_transform) 

2277 return BlendedGenericTransform(x_transform, y_transform) 

2278 

2279 

2280class CompositeGenericTransform(Transform): 

2281 """ 

2282 A composite transform formed by applying transform *a* then 

2283 transform *b*. 

2284 

2285 This "generic" version can handle any two arbitrary 

2286 transformations. 

2287 """ 

2288 pass_through = True 

2289 

2290 def __init__(self, a, b, **kwargs): 

2291 """ 

2292 Create a new composite transform that is the result of 

2293 applying transform *a* then transform *b*. 

2294 

2295 You will generally not call this constructor directly but use the 

2296 `composite_transform_factory` function instead, which can automatically 

2297 choose the best kind of composite transform instance to create. 

2298 """ 

2299 if a.output_dims != b.input_dims: 

2300 raise ValueError("The output dimension of 'a' must be equal to " 

2301 "the input dimensions of 'b'") 

2302 self.input_dims = a.input_dims 

2303 self.output_dims = b.output_dims 

2304 

2305 Transform.__init__(self, **kwargs) 

2306 self._a = a 

2307 self._b = b 

2308 self.set_children(a, b) 

2309 

2310 def frozen(self): 

2311 # docstring inherited 

2312 self._invalid = 0 

2313 frozen = composite_transform_factory( 

2314 self._a.frozen(), self._b.frozen()) 

2315 if not isinstance(frozen, CompositeGenericTransform): 

2316 return frozen.frozen() 

2317 return frozen 

2318 

2319 def _invalidate_internal(self, value, invalidating_node): 

2320 # In some cases for a composite transform, an invalidating call to 

2321 # AFFINE_ONLY needs to be extended to invalidate the NON_AFFINE part 

2322 # too. These cases are when the right hand transform is non-affine and 

2323 # either: 

2324 # (a) the left hand transform is non affine 

2325 # (b) it is the left hand node which has triggered the invalidation 

2326 if value == Transform.INVALID_AFFINE \ 

2327 and not self._b.is_affine \ 

2328 and (not self._a.is_affine or invalidating_node is self._a): 

2329 

2330 value = Transform.INVALID 

2331 

2332 Transform._invalidate_internal(self, value=value, 

2333 invalidating_node=invalidating_node) 

2334 

2335 def __eq__(self, other): 

2336 if isinstance(other, (CompositeGenericTransform, CompositeAffine2D)): 

2337 return self is other or (self._a == other._a 

2338 and self._b == other._b) 

2339 else: 

2340 return False 

2341 

2342 def _iter_break_from_left_to_right(self): 

2343 for left, right in self._a._iter_break_from_left_to_right(): 

2344 yield left, right + self._b 

2345 for left, right in self._b._iter_break_from_left_to_right(): 

2346 yield self._a + left, right 

2347 

2348 depth = property(lambda self: self._a.depth + self._b.depth) 

2349 is_affine = property(lambda self: self._a.is_affine and self._b.is_affine) 

2350 is_separable = property( 

2351 lambda self: self._a.is_separable and self._b.is_separable) 

2352 has_inverse = property( 

2353 lambda self: self._a.has_inverse and self._b.has_inverse) 

2354 

2355 def __str__(self): 

2356 return ("{}(\n" 

2357 "{},\n" 

2358 "{})" 

2359 .format(type(self).__name__, 

2360 _indent_str(self._a), 

2361 _indent_str(self._b))) 

2362 

2363 def transform_affine(self, points): 

2364 # docstring inherited 

2365 return self.get_affine().transform(points) 

2366 

2367 def transform_non_affine(self, points): 

2368 # docstring inherited 

2369 if self._a.is_affine and self._b.is_affine: 

2370 return points 

2371 elif not self._a.is_affine and self._b.is_affine: 

2372 return self._a.transform_non_affine(points) 

2373 else: 

2374 return self._b.transform_non_affine( 

2375 self._a.transform(points)) 

2376 

2377 def transform_path_non_affine(self, path): 

2378 # docstring inherited 

2379 if self._a.is_affine and self._b.is_affine: 

2380 return path 

2381 elif not self._a.is_affine and self._b.is_affine: 

2382 return self._a.transform_path_non_affine(path) 

2383 else: 

2384 return self._b.transform_path_non_affine( 

2385 self._a.transform_path(path)) 

2386 

2387 def get_affine(self): 

2388 # docstring inherited 

2389 if not self._b.is_affine: 

2390 return self._b.get_affine() 

2391 else: 

2392 return Affine2D(np.dot(self._b.get_affine().get_matrix(), 

2393 self._a.get_affine().get_matrix())) 

2394 

2395 def inverted(self): 

2396 # docstring inherited 

2397 return CompositeGenericTransform( 

2398 self._b.inverted(), self._a.inverted()) 

2399 

2400 

2401class CompositeAffine2D(Affine2DBase): 

2402 """ 

2403 A composite transform formed by applying transform *a* then transform *b*. 

2404 

2405 This version is an optimization that handles the case where both *a* 

2406 and *b* are 2D affines. 

2407 """ 

2408 def __init__(self, a, b, **kwargs): 

2409 """ 

2410 Create a new composite transform that is the result of 

2411 applying transform *a* then transform *b*. 

2412 

2413 Both *a* and *b* must be instances of :class:`Affine2DBase`. 

2414 

2415 You will generally not call this constructor directly but use the 

2416 `composite_transform_factory` function instead, which can automatically 

2417 choose the best kind of composite transform instance to create. 

2418 """ 

2419 if not a.is_affine or not b.is_affine: 

2420 raise ValueError("'a' and 'b' must be affine transforms") 

2421 if a.output_dims != b.input_dims: 

2422 raise ValueError("The output dimension of 'a' must be equal to " 

2423 "the input dimensions of 'b'") 

2424 self.input_dims = a.input_dims 

2425 self.output_dims = b.output_dims 

2426 

2427 Affine2DBase.__init__(self, **kwargs) 

2428 self._a = a 

2429 self._b = b 

2430 self.set_children(a, b) 

2431 self._mtx = None 

2432 

2433 @property 

2434 def depth(self): 

2435 return self._a.depth + self._b.depth 

2436 

2437 def _iter_break_from_left_to_right(self): 

2438 for left, right in self._a._iter_break_from_left_to_right(): 

2439 yield left, right + self._b 

2440 for left, right in self._b._iter_break_from_left_to_right(): 

2441 yield self._a + left, right 

2442 

2443 def __str__(self): 

2444 return ("{}(\n" 

2445 "{},\n" 

2446 "{})" 

2447 .format(type(self).__name__, 

2448 _indent_str(self._a), 

2449 _indent_str(self._b))) 

2450 

2451 def get_matrix(self): 

2452 # docstring inherited 

2453 if self._invalid: 

2454 self._mtx = np.dot( 

2455 self._b.get_matrix(), 

2456 self._a.get_matrix()) 

2457 self._inverted = None 

2458 self._invalid = 0 

2459 return self._mtx 

2460 

2461 

2462def composite_transform_factory(a, b): 

2463 """ 

2464 Create a new composite transform that is the result of applying 

2465 transform a then transform b. 

2466 

2467 Shortcut versions of the blended transform are provided for the 

2468 case where both child transforms are affine, or one or the other 

2469 is the identity transform. 

2470 

2471 Composite transforms may also be created using the '+' operator, 

2472 e.g.:: 

2473 

2474 c = a + b 

2475 """ 

2476 # check to see if any of a or b are IdentityTransforms. We use 

2477 # isinstance here to guarantee that the transforms will *always* 

2478 # be IdentityTransforms. Since TransformWrappers are mutable, 

2479 # use of equality here would be wrong. 

2480 if isinstance(a, IdentityTransform): 

2481 return b 

2482 elif isinstance(b, IdentityTransform): 

2483 return a 

2484 elif isinstance(a, Affine2D) and isinstance(b, Affine2D): 

2485 return CompositeAffine2D(a, b) 

2486 return CompositeGenericTransform(a, b) 

2487 

2488 

2489class BboxTransform(Affine2DBase): 

2490 """ 

2491 `BboxTransform` linearly transforms points from one `Bbox` to another. 

2492 """ 

2493 is_separable = True 

2494 

2495 def __init__(self, boxin, boxout, **kwargs): 

2496 """ 

2497 Create a new :class:`BboxTransform` that linearly transforms 

2498 points from *boxin* to *boxout*. 

2499 """ 

2500 if not boxin.is_bbox or not boxout.is_bbox: 

2501 raise ValueError("'boxin' and 'boxout' must be bbox") 

2502 

2503 Affine2DBase.__init__(self, **kwargs) 

2504 self._boxin = boxin 

2505 self._boxout = boxout 

2506 self.set_children(boxin, boxout) 

2507 self._mtx = None 

2508 self._inverted = None 

2509 

2510 def __str__(self): 

2511 return ("{}(\n" 

2512 "{},\n" 

2513 "{})" 

2514 .format(type(self).__name__, 

2515 _indent_str(self._boxin), 

2516 _indent_str(self._boxout))) 

2517 

2518 def get_matrix(self): 

2519 # docstring inherited 

2520 if self._invalid: 

2521 inl, inb, inw, inh = self._boxin.bounds 

2522 outl, outb, outw, outh = self._boxout.bounds 

2523 x_scale = outw / inw 

2524 y_scale = outh / inh 

2525 if DEBUG and (x_scale == 0 or y_scale == 0): 

2526 raise ValueError( 

2527 "Transforming from or to a singular bounding box") 

2528 self._mtx = np.array([[x_scale, 0.0 , (-inl*x_scale+outl)], 

2529 [0.0 , y_scale, (-inb*y_scale+outb)], 

2530 [0.0 , 0.0 , 1.0 ]], 

2531 float) 

2532 self._inverted = None 

2533 self._invalid = 0 

2534 return self._mtx 

2535 

2536 

2537class BboxTransformTo(Affine2DBase): 

2538 """ 

2539 `BboxTransformTo` is a transformation that linearly transforms points from 

2540 the unit bounding box to a given `Bbox`. 

2541 """ 

2542 is_separable = True 

2543 

2544 def __init__(self, boxout, **kwargs): 

2545 """ 

2546 Create a new :class:`BboxTransformTo` that linearly transforms 

2547 points from the unit bounding box to *boxout*. 

2548 """ 

2549 if not boxout.is_bbox: 

2550 raise ValueError("'boxout' must be bbox") 

2551 

2552 Affine2DBase.__init__(self, **kwargs) 

2553 self._boxout = boxout 

2554 self.set_children(boxout) 

2555 self._mtx = None 

2556 self._inverted = None 

2557 

2558 def __str__(self): 

2559 return ("{}(\n" 

2560 "{})" 

2561 .format(type(self).__name__, 

2562 _indent_str(self._boxout))) 

2563 

2564 def get_matrix(self): 

2565 # docstring inherited 

2566 if self._invalid: 

2567 outl, outb, outw, outh = self._boxout.bounds 

2568 if DEBUG and (outw == 0 or outh == 0): 

2569 raise ValueError("Transforming to a singular bounding box.") 

2570 self._mtx = np.array([[outw, 0.0, outl], 

2571 [ 0.0, outh, outb], 

2572 [ 0.0, 0.0, 1.0]], 

2573 float) 

2574 self._inverted = None 

2575 self._invalid = 0 

2576 return self._mtx 

2577 

2578 

2579class BboxTransformToMaxOnly(BboxTransformTo): 

2580 """ 

2581 `BboxTransformTo` is a transformation that linearly transforms points from 

2582 the unit bounding box to a given `Bbox` with a fixed upper left of (0, 0). 

2583 """ 

2584 def get_matrix(self): 

2585 # docstring inherited 

2586 if self._invalid: 

2587 xmax, ymax = self._boxout.max 

2588 if DEBUG and (xmax == 0 or ymax == 0): 

2589 raise ValueError("Transforming to a singular bounding box.") 

2590 self._mtx = np.array([[xmax, 0.0, 0.0], 

2591 [ 0.0, ymax, 0.0], 

2592 [ 0.0, 0.0, 1.0]], 

2593 float) 

2594 self._inverted = None 

2595 self._invalid = 0 

2596 return self._mtx 

2597 

2598 

2599class BboxTransformFrom(Affine2DBase): 

2600 """ 

2601 `BboxTransformFrom` linearly transforms points from a given `Bbox` to the 

2602 unit bounding box. 

2603 """ 

2604 is_separable = True 

2605 

2606 def __init__(self, boxin, **kwargs): 

2607 if not boxin.is_bbox: 

2608 raise ValueError("'boxin' must be bbox") 

2609 

2610 Affine2DBase.__init__(self, **kwargs) 

2611 self._boxin = boxin 

2612 self.set_children(boxin) 

2613 self._mtx = None 

2614 self._inverted = None 

2615 

2616 def __str__(self): 

2617 return ("{}(\n" 

2618 "{})" 

2619 .format(type(self).__name__, 

2620 _indent_str(self._boxin))) 

2621 

2622 def get_matrix(self): 

2623 # docstring inherited 

2624 if self._invalid: 

2625 inl, inb, inw, inh = self._boxin.bounds 

2626 if DEBUG and (inw == 0 or inh == 0): 

2627 raise ValueError("Transforming from a singular bounding box.") 

2628 x_scale = 1.0 / inw 

2629 y_scale = 1.0 / inh 

2630 self._mtx = np.array([[x_scale, 0.0 , (-inl*x_scale)], 

2631 [0.0 , y_scale, (-inb*y_scale)], 

2632 [0.0 , 0.0 , 1.0 ]], 

2633 float) 

2634 self._inverted = None 

2635 self._invalid = 0 

2636 return self._mtx 

2637 

2638 

2639class ScaledTranslation(Affine2DBase): 

2640 """ 

2641 A transformation that translates by *xt* and *yt*, after *xt* and *yt* 

2642 have been transformed by *scale_trans*. 

2643 """ 

2644 def __init__(self, xt, yt, scale_trans, **kwargs): 

2645 Affine2DBase.__init__(self, **kwargs) 

2646 self._t = (xt, yt) 

2647 self._scale_trans = scale_trans 

2648 self.set_children(scale_trans) 

2649 self._mtx = None 

2650 self._inverted = None 

2651 

2652 def __str__(self): 

2653 return ("{}(\n" 

2654 "{})" 

2655 .format(type(self).__name__, 

2656 _indent_str(self._t))) 

2657 

2658 def get_matrix(self): 

2659 # docstring inherited 

2660 if self._invalid: 

2661 # A bit faster than np.identity(3). 

2662 self._mtx = IdentityTransform._mtx.copy() 

2663 self._mtx[:2, 2] = self._scale_trans.transform(self._t) 

2664 self._invalid = 0 

2665 self._inverted = None 

2666 return self._mtx 

2667 

2668 

2669class TransformedPath(TransformNode): 

2670 """ 

2671 A `TransformedPath` caches a non-affine transformed copy of the 

2672 `~.path.Path`. This cached copy is automatically updated when the 

2673 non-affine part of the transform changes. 

2674 

2675 .. note:: 

2676 

2677 Paths are considered immutable by this class. Any update to the 

2678 path's vertices/codes will not trigger a transform recomputation. 

2679 

2680 """ 

2681 def __init__(self, path, transform): 

2682 """ 

2683 Parameters 

2684 ---------- 

2685 path : `~.path.Path` 

2686 transform : `Transform` 

2687 """ 

2688 cbook._check_isinstance(Transform, transform=transform) 

2689 TransformNode.__init__(self) 

2690 self._path = path 

2691 self._transform = transform 

2692 self.set_children(transform) 

2693 self._transformed_path = None 

2694 self._transformed_points = None 

2695 

2696 def _revalidate(self): 

2697 # only recompute if the invalidation includes the non_affine part of 

2698 # the transform 

2699 if (self._invalid & self.INVALID_NON_AFFINE == self.INVALID_NON_AFFINE 

2700 or self._transformed_path is None): 

2701 self._transformed_path = \ 

2702 self._transform.transform_path_non_affine(self._path) 

2703 self._transformed_points = \ 

2704 Path._fast_from_codes_and_verts( 

2705 self._transform.transform_non_affine(self._path.vertices), 

2706 None, self._path) 

2707 self._invalid = 0 

2708 

2709 def get_transformed_points_and_affine(self): 

2710 """ 

2711 Return a copy of the child path, with the non-affine part of 

2712 the transform already applied, along with the affine part of 

2713 the path necessary to complete the transformation. Unlike 

2714 :meth:`get_transformed_path_and_affine`, no interpolation will 

2715 be performed. 

2716 """ 

2717 self._revalidate() 

2718 return self._transformed_points, self.get_affine() 

2719 

2720 def get_transformed_path_and_affine(self): 

2721 """ 

2722 Return a copy of the child path, with the non-affine part of 

2723 the transform already applied, along with the affine part of 

2724 the path necessary to complete the transformation. 

2725 """ 

2726 self._revalidate() 

2727 return self._transformed_path, self.get_affine() 

2728 

2729 def get_fully_transformed_path(self): 

2730 """ 

2731 Return a fully-transformed copy of the child path. 

2732 """ 

2733 self._revalidate() 

2734 return self._transform.transform_path_affine(self._transformed_path) 

2735 

2736 def get_affine(self): 

2737 return self._transform.get_affine() 

2738 

2739 

2740class TransformedPatchPath(TransformedPath): 

2741 """ 

2742 A `TransformedPatchPath` caches a non-affine transformed copy of the 

2743 `~.patch.Patch`. This cached copy is automatically updated when the 

2744 non-affine part of the transform or the patch changes. 

2745 """ 

2746 def __init__(self, patch): 

2747 """ 

2748 Parameters 

2749 ---------- 

2750 patch : `~.patches.Patch` 

2751 """ 

2752 TransformNode.__init__(self) 

2753 

2754 transform = patch.get_transform() 

2755 self._patch = patch 

2756 self._transform = transform 

2757 self.set_children(transform) 

2758 self._path = patch.get_path() 

2759 self._transformed_path = None 

2760 self._transformed_points = None 

2761 

2762 def _revalidate(self): 

2763 patch_path = self._patch.get_path() 

2764 # Only recompute if the invalidation includes the non_affine part of 

2765 # the transform, or the Patch's Path has changed. 

2766 if (self._transformed_path is None or self._path != patch_path or 

2767 (self._invalid & self.INVALID_NON_AFFINE == 

2768 self.INVALID_NON_AFFINE)): 

2769 self._path = patch_path 

2770 self._transformed_path = \ 

2771 self._transform.transform_path_non_affine(patch_path) 

2772 self._transformed_points = \ 

2773 Path._fast_from_codes_and_verts( 

2774 self._transform.transform_non_affine(patch_path.vertices), 

2775 None, patch_path) 

2776 self._invalid = 0 

2777 

2778 

2779def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): 

2780 """ 

2781 Modify the endpoints of a range as needed to avoid singularities. 

2782 

2783 Parameters 

2784 ---------- 

2785 vmin, vmax : float 

2786 The initial endpoints. 

2787 expander : float, optional, default: 0.001 

2788 Fractional amount by which *vmin* and *vmax* are expanded if 

2789 the original interval is too small, based on *tiny*. 

2790 tiny : float, optional, default: 1e-15 

2791 Threshold for the ratio of the interval to the maximum absolute 

2792 value of its endpoints. If the interval is smaller than 

2793 this, it will be expanded. This value should be around 

2794 1e-15 or larger; otherwise the interval will be approaching 

2795 the double precision resolution limit. 

2796 increasing : bool, optional, default: True 

2797 If True, swap *vmin*, *vmax* if *vmin* > *vmax*. 

2798 

2799 Returns 

2800 ------- 

2801 vmin, vmax : float 

2802 Endpoints, expanded and/or swapped if necessary. 

2803 If either input is inf or NaN, or if both inputs are 0 or very 

2804 close to zero, it returns -*expander*, *expander*. 

2805 """ 

2806 

2807 if (not np.isfinite(vmin)) or (not np.isfinite(vmax)): 

2808 return -expander, expander 

2809 

2810 swapped = False 

2811 if vmax < vmin: 

2812 vmin, vmax = vmax, vmin 

2813 swapped = True 

2814 

2815 # Expand vmin, vmax to float: if they were integer types, they can wrap 

2816 # around in abs (abs(np.int8(-128)) == -128) and vmax - vmin can overflow. 

2817 vmin, vmax = map(float, [vmin, vmax]) 

2818 

2819 maxabsvalue = max(abs(vmin), abs(vmax)) 

2820 if maxabsvalue < (1e6 / tiny) * np.finfo(float).tiny: 

2821 vmin = -expander 

2822 vmax = expander 

2823 

2824 elif vmax - vmin <= maxabsvalue * tiny: 

2825 if vmax == 0 and vmin == 0: 

2826 vmin = -expander 

2827 vmax = expander 

2828 else: 

2829 vmin -= expander*abs(vmin) 

2830 vmax += expander*abs(vmax) 

2831 

2832 if swapped and not increasing: 

2833 vmin, vmax = vmax, vmin 

2834 return vmin, vmax 

2835 

2836 

2837def interval_contains(interval, val): 

2838 """ 

2839 Check, inclusively, whether an interval includes a given value. 

2840 

2841 Parameters 

2842 ---------- 

2843 interval : sequence of scalar 

2844 A 2-length sequence, endpoints that define the interval. 

2845 val : scalar 

2846 Value to check is within interval. 

2847 

2848 Returns 

2849 ------- 

2850 bool 

2851 Returns *True* if given *val* is within the *interval*. 

2852 """ 

2853 a, b = interval 

2854 if a > b: 

2855 a, b = b, a 

2856 return a <= val <= b 

2857 

2858 

2859def _interval_contains_close(interval, val, rtol=1e-10): 

2860 """ 

2861 Check, inclusively, whether an interval includes a given value, with the 

2862 interval expanded by a small tolerance to admit floating point errors. 

2863 

2864 Parameters 

2865 ---------- 

2866 interval : sequence of scalar 

2867 A 2-length sequence, endpoints that define the interval. 

2868 val : scalar 

2869 Value to check is within interval. 

2870 rtol : scalar 

2871 Tolerance slippage allowed outside of this interval. Default 

2872 1e-10 * (b - a). 

2873 

2874 Returns 

2875 ------- 

2876 bool 

2877 Returns *True* if given *val* is within the *interval* (with tolerance) 

2878 """ 

2879 a, b = interval 

2880 if a > b: 

2881 a, b = b, a 

2882 rtol = (b - a) * rtol 

2883 return a - rtol <= val <= b + rtol 

2884 

2885 

2886def interval_contains_open(interval, val): 

2887 """ 

2888 Check, excluding endpoints, whether an interval includes a given value. 

2889 

2890 Parameters 

2891 ---------- 

2892 interval : sequence of scalar 

2893 A 2-length sequence, endpoints that define the interval. 

2894 val : scalar 

2895 Value to check is within interval. 

2896 

2897 Returns 

2898 ------- 

2899 bool 

2900 Returns true if given val is within the interval. 

2901 """ 

2902 a, b = interval 

2903 return a < val < b or a > val > b 

2904 

2905 

2906def offset_copy(trans, fig=None, x=0.0, y=0.0, units='inches'): 

2907 """ 

2908 Return a new transform with an added offset. 

2909 

2910 Parameters 

2911 ---------- 

2912 trans : :class:`Transform` instance 

2913 Any transform, to which offset will be applied. 

2914 fig : :class:`~matplotlib.figure.Figure`, optional, default: None 

2915 Current figure. It can be None if *units* are 'dots'. 

2916 x, y : float, optional, default: 0.0 

2917 Specifies the offset to apply. 

2918 units : {'inches', 'points', 'dots'}, optional 

2919 Units of the offset. 

2920 

2921 Returns 

2922 ------- 

2923 trans : :class:`Transform` instance 

2924 Transform with applied offset. 

2925 """ 

2926 if units == 'dots': 

2927 return trans + Affine2D().translate(x, y) 

2928 if fig is None: 

2929 raise ValueError('For units of inches or points a fig kwarg is needed') 

2930 if units == 'points': 

2931 x /= 72.0 

2932 y /= 72.0 

2933 elif units == 'inches': 

2934 pass 

2935 else: 

2936 cbook._check_in_list(['dots', 'points', 'inches'], units=units) 

2937 return trans + ScaledTranslation(x, y, fig.dpi_scale_trans)