Coverage for /usr/lib/python3/dist-packages/matplotlib/transforms.py: 32%

1166 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

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

31See the tutorial :doc:`/tutorials/advanced/transforms_tutorial` for examples 

32of how to use transforms. 

33""" 

34 

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

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

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

38 

39import copy 

40import functools 

41import textwrap 

42import weakref 

43import math 

44 

45import numpy as np 

46from numpy.linalg import inv 

47 

48from matplotlib import _api 

49from matplotlib._path import ( 

50 affine_transform, count_bboxes_overlapping_bbox, update_path_extents) 

51from .path import Path 

52 

53DEBUG = False 

54 

55 

56def _make_str_method(*args, **kwargs): 

57 """ 

58 Generate a ``__str__`` method for a `.Transform` subclass. 

59 

60 After :: 

61 

62 class T: 

63 __str__ = _make_str_method("attr", key="other") 

64 

65 ``str(T(...))`` will be 

66 

67 .. code-block:: text 

68 

69 {type(T).__name__}( 

70 {self.attr}, 

71 key={self.other}) 

72 """ 

73 indent = functools.partial(textwrap.indent, prefix=" " * 4) 

74 def strrepr(x): return repr(x) if isinstance(x, str) else str(x) 

75 return lambda self: ( 

76 type(self).__name__ + "(" 

77 + ",".join([*(indent("\n" + strrepr(getattr(self, arg))) 

78 for arg in args), 

79 *(indent("\n" + k + "=" + strrepr(getattr(self, arg))) 

80 for k, arg in kwargs.items())]) 

81 + ")") 

82 

83 

84class TransformNode: 

85 """ 

86 The base class for anything that participates in the transform tree 

87 and needs to invalidate its parents or be invalidated. This includes 

88 classes that are not really transforms, such as bounding boxes, since some 

89 transforms depend on bounding boxes to compute their values. 

90 """ 

91 

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

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

94 # INVALID_AFFINE_ONLY 

95 INVALID_NON_AFFINE = 1 

96 INVALID_AFFINE = 2 

97 INVALID = INVALID_NON_AFFINE | INVALID_AFFINE 

98 

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

100 # invalidation is affine-only 

101 is_affine = False 

102 is_bbox = False 

103 

104 pass_through = False 

105 """ 

106 If pass_through is True, all ancestors will always be 

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

108 """ 

109 

110 def __init__(self, shorthand_name=None): 

111 """ 

112 Parameters 

113 ---------- 

114 shorthand_name : str 

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

116 no significance other than to improve the readability of 

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

118 """ 

119 self._parents = {} 

120 

121 # TransformNodes start out as invalid until their values are 

122 # computed for the first time. 

123 self._invalid = 1 

124 self._shorthand_name = shorthand_name or '' 

125 

126 if DEBUG: 

127 def __str__(self): 

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

129 return self._shorthand_name or repr(self) 

130 

131 def __getstate__(self): 

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

133 return {**self.__dict__, 

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

135 

136 def __setstate__(self, data_dict): 

137 self.__dict__ = data_dict 

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

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

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

141 self._parents = { 

142 k: weakref.ref(v, lambda _, pop=self._parents.pop, k=k: pop(k)) 

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

144 

145 def __copy__(self): 

146 other = copy.copy(super()) 

147 # If `c = a + b; a1 = copy(a)`, then modifications to `a1` do not 

148 # propagate back to `c`, i.e. we need to clear the parents of `a1`. 

149 other._parents = {} 

150 # If `c = a + b; c1 = copy(c)`, then modifications to `a` also need to 

151 # be propagated to `c1`. 

152 for key, val in vars(self).items(): 

153 if isinstance(val, TransformNode) and id(self) in val._parents: 

154 other.set_children(val) # val == getattr(other, key) 

155 return other 

156 

157 def invalidate(self): 

158 """ 

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

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

161 """ 

162 value = self.INVALID 

163 if self.is_affine: 

164 value = self.INVALID_AFFINE 

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

166 

167 def _invalidate_internal(self, value, invalidating_node): 

168 """ 

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

170 stack calling each TransformNode's _invalidate_internal method. 

171 """ 

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

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

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

175 # invalidated. 

176 

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

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

179 # NON_AFFINE even when triggered with a AFFINE_ONLY invalidation. 

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

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

182 status_changed = self._invalid < value 

183 

184 if self.pass_through or status_changed: 

185 self._invalid = value 

186 

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

188 # Dereference the weak reference 

189 parent = parent() 

190 if parent is not None: 

191 parent._invalidate_internal( 

192 value=value, invalidating_node=self) 

193 

194 def set_children(self, *children): 

195 """ 

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

197 system know which transforms can invalidate this transform. 

198 Should be called from the constructor of any transforms that 

199 depend on other transforms. 

200 """ 

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

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

203 # keep them alive. 

204 for child in children: 

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

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

207 # performance improvement over using WeakValueDictionary. 

208 ref = weakref.ref( 

209 self, lambda _, pop=child._parents.pop, k=id(self): pop(k)) 

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

211 

212 def frozen(self): 

213 """ 

214 Return a frozen copy of this transform node. The frozen copy will not 

215 be updated when its children change. Useful for storing a previously 

216 known state of a transform where ``copy.deepcopy()`` might normally be 

217 used. 

218 """ 

219 return self 

220 

221 

222class BboxBase(TransformNode): 

223 """ 

224 The base class of all bounding boxes. 

225 

226 This class is immutable; `Bbox` is a mutable subclass. 

227 

228 The canonical representation is as two points, with no 

229 restrictions on their ordering. Convenience properties are 

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

231 and height, but these are not stored explicitly. 

232 """ 

233 

234 is_bbox = True 

235 is_affine = True 

236 

237 if DEBUG: 

238 @staticmethod 

239 def _check(points): 

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

241 _api.warn_external("Bbox bounds are a masked array.") 

242 points = np.asarray(points) 

243 if any((points[1, :] - points[0, :]) == 0): 

244 _api.warn_external("Singular Bbox.") 

245 

246 def frozen(self): 

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

248 frozen.__doc__ = TransformNode.__doc__ 

249 

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

251 return self.get_points() 

252 

253 @property 

254 def x0(self): 

255 """ 

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

257 

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

259 :attr:`xmin`). 

260 """ 

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

262 

263 @property 

264 def y0(self): 

265 """ 

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

267 

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

269 :attr:`ymin`). 

270 """ 

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

272 

273 @property 

274 def x1(self): 

275 """ 

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

277 

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

279 :attr:`xmax`). 

280 """ 

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

282 

283 @property 

284 def y1(self): 

285 """ 

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

287 

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

289 :attr:`ymax`). 

290 """ 

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

292 

293 @property 

294 def p0(self): 

295 """ 

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

297 

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

299 :attr:`min`). 

300 """ 

301 return self.get_points()[0] 

302 

303 @property 

304 def p1(self): 

305 """ 

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

307 

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

309 :attr:`max`). 

310 """ 

311 return self.get_points()[1] 

312 

313 @property 

314 def xmin(self): 

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

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

317 

318 @property 

319 def ymin(self): 

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

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

322 

323 @property 

324 def xmax(self): 

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

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

327 

328 @property 

329 def ymax(self): 

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

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

332 

333 @property 

334 def min(self): 

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

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

337 

338 @property 

339 def max(self): 

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

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

342 

343 @property 

344 def intervalx(self): 

345 """ 

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

347 

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

349 """ 

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

351 

352 @property 

353 def intervaly(self): 

354 """ 

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

356 

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

358 """ 

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

360 

361 @property 

362 def width(self): 

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

364 points = self.get_points() 

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

366 

367 @property 

368 def height(self): 

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

370 points = self.get_points() 

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

372 

373 @property 

374 def size(self): 

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

376 points = self.get_points() 

377 return points[1] - points[0] 

378 

379 @property 

380 def bounds(self): 

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

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

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

384 

385 @property 

386 def extents(self): 

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

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

389 

390 def get_points(self): 

391 raise NotImplementedError 

392 

393 def containsx(self, x): 

394 """ 

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

396 """ 

397 x0, x1 = self.intervalx 

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

399 

400 def containsy(self, y): 

401 """ 

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

403 """ 

404 y0, y1 = self.intervaly 

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

406 

407 def contains(self, x, y): 

408 """ 

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

410 """ 

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

412 

413 def overlaps(self, other): 

414 """ 

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

416 

417 Parameters 

418 ---------- 

419 other : `.BboxBase` 

420 """ 

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

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

423 if ax2 < ax1: 

424 ax2, ax1 = ax1, ax2 

425 if ay2 < ay1: 

426 ay2, ay1 = ay1, ay2 

427 if bx2 < bx1: 

428 bx2, bx1 = bx1, bx2 

429 if by2 < by1: 

430 by2, by1 = by1, by2 

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

432 

433 def fully_containsx(self, x): 

434 """ 

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

436 """ 

437 x0, x1 = self.intervalx 

438 return x0 < x < x1 or x0 > x > x1 

439 

440 def fully_containsy(self, y): 

441 """ 

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

443 """ 

444 y0, y1 = self.intervaly 

445 return y0 < y < y1 or y0 > y > y1 

446 

447 def fully_contains(self, x, y): 

448 """ 

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

450 """ 

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

452 

453 def fully_overlaps(self, other): 

454 """ 

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

456 not including the edges. 

457 

458 Parameters 

459 ---------- 

460 other : `.BboxBase` 

461 """ 

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

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

464 if ax2 < ax1: 

465 ax2, ax1 = ax1, ax2 

466 if ay2 < ay1: 

467 ay2, ay1 = ay1, ay2 

468 if bx2 < bx1: 

469 bx2, bx1 = bx1, bx2 

470 if by2 < by1: 

471 by2, by1 = by1, by2 

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

473 

474 def transformed(self, transform): 

475 """ 

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

477 """ 

478 pts = self.get_points() 

479 ll, ul, lr = transform.transform(np.array( 

480 [pts[0], [pts[0, 0], pts[1, 1]], [pts[1, 0], pts[0, 1]]])) 

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

482 

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

484 'SW': (0, 0), 

485 'S': (0.5, 0), 

486 'SE': (1.0, 0), 

487 'E': (1.0, 0.5), 

488 'NE': (1.0, 1.0), 

489 'N': (0.5, 1.0), 

490 'NW': (0, 1.0), 

491 'W': (0, 0.5)} 

492 

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

494 """ 

495 Return a copy of the `Bbox` anchored to *c* within *container*. 

496 

497 Parameters 

498 ---------- 

499 c : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', ...} 

500 Either an (*x*, *y*) pair of relative coordinates (0 is left or 

501 bottom, 1 is right or top), 'C' (center), or a cardinal direction 

502 ('SW', southwest, is bottom left, etc.). 

503 container : `Bbox`, optional 

504 The box within which the `Bbox` is positioned; it defaults 

505 to the initial `Bbox`. 

506 

507 See Also 

508 -------- 

509 .Axes.set_anchor 

510 """ 

511 if container is None: 

512 container = self 

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

514 if isinstance(c, str): 

515 cx, cy = self.coefs[c] 

516 else: 

517 cx, cy = c 

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

519 return Bbox(self._points + 

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

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

522 

523 def shrunk(self, mx, my): 

524 """ 

525 Return a copy of the `Bbox`, shrunk by the factor *mx* 

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

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

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

529 """ 

530 w, h = self.size 

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

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

533 

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

535 """ 

536 Return a copy of the `Bbox`, shrunk so that it is as 

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

538 *box_aspect*. If the box coordinates are relative (i.e. 

539 fractions of a larger box such as a figure) then the 

540 physical aspect ratio of that figure is specified with 

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

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

543 """ 

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

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

546 if container is None: 

547 container = self 

548 w, h = container.size 

549 H = w * box_aspect / fig_aspect 

550 if H <= h: 

551 W = w 

552 else: 

553 W = h * fig_aspect / box_aspect 

554 H = h 

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

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

557 

558 def splitx(self, *args): 

559 """ 

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

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

562 """ 

563 xf = [0, *args, 1] 

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

565 w = x1 - x0 

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

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

568 

569 def splity(self, *args): 

570 """ 

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

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

573 """ 

574 yf = [0, *args, 1] 

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

576 h = y1 - y0 

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

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

579 

580 def count_contains(self, vertices): 

581 """ 

582 Count the number of vertices contained in the `Bbox`. 

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

584 

585 Parameters 

586 ---------- 

587 vertices : Nx2 Numpy array. 

588 """ 

589 if len(vertices) == 0: 

590 return 0 

591 vertices = np.asarray(vertices) 

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

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

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

595 

596 def count_overlaps(self, bboxes): 

597 """ 

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

599 

600 Parameters 

601 ---------- 

602 bboxes : sequence of `.BboxBase` 

603 """ 

604 return count_bboxes_overlapping_bbox( 

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

606 

607 def expanded(self, sw, sh): 

608 """ 

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

610 factors *sw* and *sh*. 

611 """ 

612 width = self.width 

613 height = self.height 

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

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

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

617 return Bbox(self._points + a) 

618 

619 def padded(self, p): 

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

621 points = self.get_points() 

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

623 

624 def translated(self, tx, ty): 

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

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

627 

628 def corners(self): 

629 """ 

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

631 

632 Specifically, this returns the array 

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

634 """ 

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

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

637 

638 def rotated(self, radians): 

639 """ 

640 Return the axes-aligned bounding box that bounds the result of rotating 

641 this `Bbox` by an angle of *radians*. 

642 """ 

643 corners = self.corners() 

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

645 bbox = Bbox.unit() 

646 bbox.update_from_data_xy(corners_rotated, ignore=True) 

647 return bbox 

648 

649 @staticmethod 

650 def union(bboxes): 

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

652 if not len(bboxes): 

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

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

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

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

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

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

659 

660 @staticmethod 

661 def intersection(bbox1, bbox2): 

662 """ 

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

664 None if they don't. 

665 """ 

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

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

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

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

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

671 

672 

673class Bbox(BboxBase): 

674 """ 

675 A mutable bounding box. 

676 

677 Examples 

678 -------- 

679 **Create from known bounds** 

680 

681 The default constructor takes the boundary "points" ``[[xmin, ymin], 

682 [xmax, ymax]]``. 

683 

684 >>> Bbox([[1, 1], [3, 7]]) 

685 Bbox([[1.0, 1.0], [3.0, 7.0]]) 

686 

687 Alternatively, a Bbox can be created from the flattened points array, the 

688 so-called "extents" ``(xmin, ymin, xmax, ymax)`` 

689 

690 >>> Bbox.from_extents(1, 1, 3, 7) 

691 Bbox([[1.0, 1.0], [3.0, 7.0]]) 

692 

693 or from the "bounds" ``(xmin, ymin, width, height)``. 

694 

695 >>> Bbox.from_bounds(1, 1, 2, 6) 

696 Bbox([[1.0, 1.0], [3.0, 7.0]]) 

697 

698 **Create from collections of points** 

699 

700 The "empty" object for accumulating Bboxs is the null bbox, which is a 

701 stand-in for the empty set. 

702 

703 >>> Bbox.null() 

704 Bbox([[inf, inf], [-inf, -inf]]) 

705 

706 Adding points to the null bbox will give you the bbox of those points. 

707 

708 >>> box = Bbox.null() 

709 >>> box.update_from_data_xy([[1, 1]]) 

710 >>> box 

711 Bbox([[1.0, 1.0], [1.0, 1.0]]) 

712 >>> box.update_from_data_xy([[2, 3], [3, 2]], ignore=False) 

713 >>> box 

714 Bbox([[1.0, 1.0], [3.0, 3.0]]) 

715 

716 Setting ``ignore=True`` is equivalent to starting over from a null bbox. 

717 

718 >>> box.update_from_data_xy([[1, 1]], ignore=True) 

719 >>> box 

720 Bbox([[1.0, 1.0], [1.0, 1.0]]) 

721 

722 .. warning:: 

723 

724 It is recommended to always specify ``ignore`` explicitly. If not, the 

725 default value of ``ignore`` can be changed at any time by code with 

726 access to your Bbox, for example using the method `~.Bbox.ignore`. 

727 

728 **Properties of the ``null`` bbox** 

729 

730 .. note:: 

731 

732 The current behavior of `Bbox.null()` may be surprising as it does 

733 not have all of the properties of the "empty set", and as such does 

734 not behave like a "zero" object in the mathematical sense. We may 

735 change that in the future (with a deprecation period). 

736 

737 The null bbox is the identity for intersections 

738 

739 >>> Bbox.intersection(Bbox([[1, 1], [3, 7]]), Bbox.null()) 

740 Bbox([[1.0, 1.0], [3.0, 7.0]]) 

741 

742 except with itself, where it returns the full space. 

743 

744 >>> Bbox.intersection(Bbox.null(), Bbox.null()) 

745 Bbox([[-inf, -inf], [inf, inf]]) 

746 

747 A union containing null will always return the full space (not the other 

748 set!) 

749 

750 >>> Bbox.union([Bbox([[0, 0], [0, 0]]), Bbox.null()]) 

751 Bbox([[-inf, -inf], [inf, inf]]) 

752 """ 

753 

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

755 """ 

756 Parameters 

757 ---------- 

758 points : ndarray 

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

760 """ 

761 super().__init__(**kwargs) 

762 points = np.asarray(points, float) 

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

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

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

766 self._points = points 

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

768 self._ignore = True 

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

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

771 # support the mutated methods 

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

773 if DEBUG: 

774 ___init__ = __init__ 

775 

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

777 self._check(points) 

778 self.___init__(points, **kwargs) 

779 

780 def invalidate(self): 

781 self._check(self._points) 

782 super().invalidate() 

783 

784 def frozen(self): 

785 # docstring inherited 

786 frozen_bbox = super().frozen() 

787 frozen_bbox._minpos = self.minpos.copy() 

788 return frozen_bbox 

789 

790 @staticmethod 

791 def unit(): 

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

793 return Bbox([[0, 0], [1, 1]]) 

794 

795 @staticmethod 

796 def null(): 

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

798 return Bbox([[np.inf, np.inf], [-np.inf, -np.inf]]) 

799 

800 @staticmethod 

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

802 """ 

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

804 

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

806 """ 

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

808 

809 @staticmethod 

810 def from_extents(*args, minpos=None): 

811 """ 

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

813 

814 The *y*-axis increases upwards. 

815 

816 Parameters 

817 ---------- 

818 left, bottom, right, top : float 

819 The four extents of the bounding box. 

820 

821 minpos : float or None 

822 If this is supplied, the Bbox will have a minimum positive value 

823 set. This is useful when dealing with logarithmic scales and other 

824 scales where negative bounds result in floating point errors. 

825 """ 

826 bbox = Bbox(np.reshape(args, (2, 2))) 

827 if minpos is not None: 

828 bbox._minpos[:] = minpos 

829 return bbox 

830 

831 def __format__(self, fmt): 

832 return ( 

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

834 format(self, fmt)) 

835 

836 def __str__(self): 

837 return format(self, '') 

838 

839 def __repr__(self): 

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

841 

842 def ignore(self, value): 

843 """ 

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

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

846 

847 value : bool 

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

849 will ignore the existing bounds of the `Bbox`. 

850 

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

852 will include the existing bounds of the `Bbox`. 

853 """ 

854 self._ignore = value 

855 

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

857 """ 

858 Update the bounds of the `Bbox` to contain the vertices of the 

859 provided path. After updating, the bounds will have positive *width* 

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

861 

862 Parameters 

863 ---------- 

864 path : `~matplotlib.path.Path` 

865 

866 ignore : bool, optional 

867 - when ``True``, ignore the existing bounds of the `Bbox`. 

868 - when ``False``, include the existing bounds of the `Bbox`. 

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

870 

871 updatex, updatey : bool, default: True 

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

873 """ 

874 if ignore is None: 

875 ignore = self._ignore 

876 

877 if path.vertices.size == 0: 

878 return 

879 

880 points, minpos, changed = update_path_extents( 

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

882 

883 if changed: 

884 self.invalidate() 

885 if updatex: 

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

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

888 if updatey: 

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

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

891 

892 def update_from_data_x(self, x, ignore=None): 

893 """ 

894 Update the x-bounds of the `Bbox` based on the passed in data. After 

895 updating, the bounds will have positive *width*, and *x0* will be the 

896 minimal value. 

897 

898 Parameters 

899 ---------- 

900 x : ndarray 

901 Array of x-values. 

902 

903 ignore : bool, optional 

904 - When ``True``, ignore the existing bounds of the `Bbox`. 

905 - When ``False``, include the existing bounds of the `Bbox`. 

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

907 """ 

908 x = np.ravel(x) 

909 self.update_from_data_xy(np.column_stack([x, np.ones(x.size)]), 

910 ignore=ignore, updatey=False) 

911 

912 def update_from_data_y(self, y, ignore=None): 

913 """ 

914 Update the y-bounds of the `Bbox` based on the passed in data. After 

915 updating, the bounds will have positive *height*, and *y0* will be the 

916 minimal value. 

917 

918 Parameters 

919 ---------- 

920 y : ndarray 

921 Array of y-values. 

922 

923 ignore : bool, optional 

924 - When ``True``, ignore the existing bounds of the `Bbox`. 

925 - When ``False``, include the existing bounds of the `Bbox`. 

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

927 """ 

928 y = np.array(y).ravel() 

929 self.update_from_data_xy(np.column_stack([np.ones(y.size), y]), 

930 ignore=ignore, updatex=False) 

931 

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

933 """ 

934 Update the bounds of the `Bbox` based on the passed in data. After 

935 updating, the bounds will have positive *width* and *height*; 

936 *x0* and *y0* will be the minimal values. 

937 

938 Parameters 

939 ---------- 

940 xy : ndarray 

941 A numpy array of 2D points. 

942 

943 ignore : bool, optional 

944 - When ``True``, ignore the existing bounds of the `Bbox`. 

945 - When ``False``, include the existing bounds of the `Bbox`. 

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

947 

948 updatex, updatey : bool, default: True 

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

950 """ 

951 if len(xy) == 0: 

952 return 

953 

954 path = Path(xy) 

955 self.update_from_path(path, ignore=ignore, 

956 updatex=updatex, updatey=updatey) 

957 

958 @BboxBase.x0.setter 

959 def x0(self, val): 

960 self._points[0, 0] = val 

961 self.invalidate() 

962 

963 @BboxBase.y0.setter 

964 def y0(self, val): 

965 self._points[0, 1] = val 

966 self.invalidate() 

967 

968 @BboxBase.x1.setter 

969 def x1(self, val): 

970 self._points[1, 0] = val 

971 self.invalidate() 

972 

973 @BboxBase.y1.setter 

974 def y1(self, val): 

975 self._points[1, 1] = val 

976 self.invalidate() 

977 

978 @BboxBase.p0.setter 

979 def p0(self, val): 

980 self._points[0] = val 

981 self.invalidate() 

982 

983 @BboxBase.p1.setter 

984 def p1(self, val): 

985 self._points[1] = val 

986 self.invalidate() 

987 

988 @BboxBase.intervalx.setter 

989 def intervalx(self, interval): 

990 self._points[:, 0] = interval 

991 self.invalidate() 

992 

993 @BboxBase.intervaly.setter 

994 def intervaly(self, interval): 

995 self._points[:, 1] = interval 

996 self.invalidate() 

997 

998 @BboxBase.bounds.setter 

999 def bounds(self, bounds): 

1000 l, b, w, h = bounds 

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

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

1003 self._points = points 

1004 self.invalidate() 

1005 

1006 @property 

1007 def minpos(self): 

1008 """ 

1009 The minimum positive value in both directions within the Bbox. 

1010 

1011 This is useful when dealing with logarithmic scales and other scales 

1012 where negative bounds result in floating point errors, and will be used 

1013 as the minimum extent instead of *p0*. 

1014 """ 

1015 return self._minpos 

1016 

1017 @property 

1018 def minposx(self): 

1019 """ 

1020 The minimum positive value in the *x*-direction within the Bbox. 

1021 

1022 This is useful when dealing with logarithmic scales and other scales 

1023 where negative bounds result in floating point errors, and will be used 

1024 as the minimum *x*-extent instead of *x0*. 

1025 """ 

1026 return self._minpos[0] 

1027 

1028 @property 

1029 def minposy(self): 

1030 """ 

1031 The minimum positive value in the *y*-direction within the Bbox. 

1032 

1033 This is useful when dealing with logarithmic scales and other scales 

1034 where negative bounds result in floating point errors, and will be used 

1035 as the minimum *y*-extent instead of *y0*. 

1036 """ 

1037 return self._minpos[1] 

1038 

1039 def get_points(self): 

1040 """ 

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

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

1043 """ 

1044 self._invalid = 0 

1045 return self._points 

1046 

1047 def set_points(self, points): 

1048 """ 

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

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

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

1052 """ 

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

1054 self._points = points 

1055 self.invalidate() 

1056 

1057 def set(self, other): 

1058 """ 

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

1060 """ 

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

1062 self._points = other.get_points() 

1063 self.invalidate() 

1064 

1065 def mutated(self): 

1066 """Return whether the bbox has changed since init.""" 

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

1068 

1069 def mutatedx(self): 

1070 """Return whether the x-limits have changed since init.""" 

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

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

1073 

1074 def mutatedy(self): 

1075 """Return whether the y-limits have changed since init.""" 

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

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

1078 

1079 

1080class TransformedBbox(BboxBase): 

1081 """ 

1082 A `Bbox` that is automatically transformed by a given 

1083 transform. When either the child bounding box or transform 

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

1085 """ 

1086 

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

1088 """ 

1089 Parameters 

1090 ---------- 

1091 bbox : `Bbox` 

1092 transform : `Transform` 

1093 """ 

1094 if not bbox.is_bbox: 

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

1096 _api.check_isinstance(Transform, transform=transform) 

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

1098 raise ValueError( 

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

1100 

1101 super().__init__(**kwargs) 

1102 self._bbox = bbox 

1103 self._transform = transform 

1104 self.set_children(bbox, transform) 

1105 self._points = None 

1106 

1107 __str__ = _make_str_method("_bbox", "_transform") 

1108 

1109 def get_points(self): 

1110 # docstring inherited 

1111 if self._invalid: 

1112 p = self._bbox.get_points() 

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

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

1115 # same. 

1116 points = self._transform.transform( 

1117 [[p[0, 0], p[0, 1]], 

1118 [p[1, 0], p[0, 1]], 

1119 [p[0, 0], p[1, 1]], 

1120 [p[1, 0], p[1, 1]]]) 

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

1122 

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

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

1125 xs = xs[::-1] 

1126 

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

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

1129 ys = ys[::-1] 

1130 

1131 self._points = np.array([ 

1132 [xs[0], ys[0]], 

1133 [xs[1], ys[1]] 

1134 ]) 

1135 

1136 self._invalid = 0 

1137 return self._points 

1138 

1139 if DEBUG: 

1140 _get_points = get_points 

1141 

1142 def get_points(self): 

1143 points = self._get_points() 

1144 self._check(points) 

1145 return points 

1146 

1147 

1148class LockableBbox(BboxBase): 

1149 """ 

1150 A `Bbox` where some elements may be locked at certain values. 

1151 

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

1153 accordingly with the exception of the locked elements. 

1154 """ 

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

1156 """ 

1157 Parameters 

1158 ---------- 

1159 bbox : `Bbox` 

1160 The child bounding box to wrap. 

1161 

1162 x0 : float or None 

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

1164 

1165 y0 : float or None 

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

1167 

1168 x1 : float or None 

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

1170 

1171 y1 : float or None 

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

1173 

1174 """ 

1175 if not bbox.is_bbox: 

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

1177 

1178 super().__init__(**kwargs) 

1179 self._bbox = bbox 

1180 self.set_children(bbox) 

1181 self._points = None 

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

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

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

1185 

1186 __str__ = _make_str_method("_bbox", "_locked_points") 

1187 

1188 def get_points(self): 

1189 # docstring inherited 

1190 if self._invalid: 

1191 points = self._bbox.get_points() 

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

1193 points, 

1194 self._locked_points) 

1195 self._invalid = 0 

1196 return self._points 

1197 

1198 if DEBUG: 

1199 _get_points = get_points 

1200 

1201 def get_points(self): 

1202 points = self._get_points() 

1203 self._check(points) 

1204 return points 

1205 

1206 @property 

1207 def locked_x0(self): 

1208 """ 

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

1210 """ 

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

1212 return None 

1213 else: 

1214 return self._locked_points[0, 0] 

1215 

1216 @locked_x0.setter 

1217 def locked_x0(self, x0): 

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

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

1220 self.invalidate() 

1221 

1222 @property 

1223 def locked_y0(self): 

1224 """ 

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

1226 """ 

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

1228 return None 

1229 else: 

1230 return self._locked_points[0, 1] 

1231 

1232 @locked_y0.setter 

1233 def locked_y0(self, y0): 

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

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

1236 self.invalidate() 

1237 

1238 @property 

1239 def locked_x1(self): 

1240 """ 

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

1242 """ 

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

1244 return None 

1245 else: 

1246 return self._locked_points[1, 0] 

1247 

1248 @locked_x1.setter 

1249 def locked_x1(self, x1): 

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

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

1252 self.invalidate() 

1253 

1254 @property 

1255 def locked_y1(self): 

1256 """ 

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

1258 """ 

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

1260 return None 

1261 else: 

1262 return self._locked_points[1, 1] 

1263 

1264 @locked_y1.setter 

1265 def locked_y1(self, y1): 

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

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

1268 self.invalidate() 

1269 

1270 

1271class Transform(TransformNode): 

1272 """ 

1273 The base class of all `TransformNode` instances that 

1274 actually perform a transformation. 

1275 

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

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

1278 

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

1280 minimum): 

1281 

1282 - :attr:`input_dims` 

1283 - :attr:`output_dims` 

1284 - :meth:`transform` 

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

1286 

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

1288 

1289 - :attr:`is_separable` (defaults to True for 1D -> 1D transforms, False 

1290 otherwise) 

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

1292 False otherwise) 

1293 

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

1295 `matplotlib.path.Path` objects, such as adding curves 

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

1297 

1298 - :meth:`transform_path` 

1299 """ 

1300 

1301 input_dims = None 

1302 """ 

1303 The number of input dimensions of this transform. 

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

1305 """ 

1306 

1307 output_dims = None 

1308 """ 

1309 The number of output dimensions of this transform. 

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

1311 """ 

1312 

1313 is_separable = False 

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

1315 

1316 has_inverse = False 

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

1318 

1319 def __init_subclass__(cls): 

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

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

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

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

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

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

1326 cls.is_separable = True 

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

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

1329 # directly set has_inverse. 

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

1331 and hasattr(cls, "inverted") 

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

1333 cls.has_inverse = True 

1334 

1335 def __add__(self, other): 

1336 """ 

1337 Compose two transforms together so that *self* is followed by *other*. 

1338 

1339 ``A + B`` returns a transform ``C`` so that 

1340 ``C.transform(x) == B.transform(A.transform(x))``. 

1341 """ 

1342 return (composite_transform_factory(self, other) 

1343 if isinstance(other, Transform) else 

1344 NotImplemented) 

1345 

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

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

1348 # AffineBase, override this behavior. 

1349 

1350 def _iter_break_from_left_to_right(self): 

1351 """ 

1352 Return an iterator breaking down this transform stack from left to 

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

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

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

1356 

1357 This is equivalent to flattening the stack then yielding 

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

1359 """ 

1360 yield IdentityTransform(), self 

1361 

1362 @property 

1363 def depth(self): 

1364 """ 

1365 Return the number of transforms which have been chained 

1366 together to form this Transform instance. 

1367 

1368 .. note:: 

1369 

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

1371 of the two is returned. 

1372 

1373 """ 

1374 return 1 

1375 

1376 def contains_branch(self, other): 

1377 """ 

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

1379 

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

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

1382 

1383 For the case where the given transform represents the whole 

1384 of this transform, returns True. 

1385 """ 

1386 if self.depth < other.depth: 

1387 return False 

1388 

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

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

1391 if sub_tree == other: 

1392 return True 

1393 return False 

1394 

1395 def contains_branch_seperately(self, other_transform): 

1396 """ 

1397 Return whether the given branch is a sub-tree of this transform on 

1398 each separate dimension. 

1399 

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

1401 transform containing an Axes' data transform. e.g.:: 

1402 

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

1404 

1405 """ 

1406 if self.output_dims != 2: 

1407 raise ValueError('contains_branch_seperately only supports ' 

1408 'transforms with 2 output dimensions') 

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

1410 # just return the appropriate shape. 

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

1412 

1413 def __sub__(self, other): 

1414 """ 

1415 Compose *self* with the inverse of *other*, cancelling identical terms 

1416 if any:: 

1417 

1418 # In general: 

1419 A - B == A + B.inverted() 

1420 # (but see note regarding frozen transforms below). 

1421 

1422 # If A "ends with" B (i.e. A == A' + B for some A') we can cancel 

1423 # out B: 

1424 (A' + B) - B == A' 

1425 

1426 # Likewise, if B "starts with" A (B = A + B'), we can cancel out A: 

1427 A - (A + B') == B'.inverted() == B'^-1 

1428 

1429 Cancellation (rather than naively returning ``A + B.inverted()``) is 

1430 important for multiple reasons: 

1431 

1432 - It avoids floating-point inaccuracies when computing the inverse of 

1433 B: ``B - B`` is guaranteed to cancel out exactly (resulting in the 

1434 identity transform), whereas ``B + B.inverted()`` may differ by a 

1435 small epsilon. 

1436 - ``B.inverted()`` always returns a frozen transform: if one computes 

1437 ``A + B + B.inverted()`` and later mutates ``B``, then 

1438 ``B.inverted()`` won't be updated and the last two terms won't cancel 

1439 out anymore; on the other hand, ``A + B - B`` will always be equal to 

1440 ``A`` even if ``B`` is mutated. 

1441 """ 

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

1443 if not isinstance(other, Transform): 

1444 return NotImplemented 

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

1446 if sub_tree == other: 

1447 return remainder 

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

1449 if sub_tree == self: 

1450 if not remainder.has_inverse: 

1451 raise ValueError( 

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

1453 "includes a non-invertible component") 

1454 return remainder.inverted() 

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

1456 if other.has_inverse: 

1457 return self + other.inverted() 

1458 else: 

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

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

1461 'shortcut possible.') 

1462 

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

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

1465 return self.get_affine().get_matrix() 

1466 

1467 def transform(self, values): 

1468 """ 

1469 Apply this transformation on the given array of *values*. 

1470 

1471 Parameters 

1472 ---------- 

1473 values : array 

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

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

1476 

1477 Returns 

1478 ------- 

1479 array 

1480 The output values as NumPy array of length :attr:`output_dims` or 

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

1482 """ 

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

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

1485 values = np.asanyarray(values) 

1486 ndim = values.ndim 

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

1488 

1489 # Transform the values 

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

1491 

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

1493 if ndim == 0: 

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

1495 return res[0, 0] 

1496 if ndim == 1: 

1497 return res.reshape(-1) 

1498 elif ndim == 2: 

1499 return res 

1500 raise ValueError( 

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

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

1503 

1504 def transform_affine(self, values): 

1505 """ 

1506 Apply only the affine part of this transformation on the 

1507 given array of values. 

1508 

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

1510 ``transform_affine(transform_non_affine(values))``. 

1511 

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

1513 affine transformations, this is equivalent to 

1514 ``transform(values)``. 

1515 

1516 Parameters 

1517 ---------- 

1518 values : array 

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

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

1521 

1522 Returns 

1523 ------- 

1524 array 

1525 The output values as NumPy array of length :attr:`output_dims` or 

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

1527 """ 

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

1529 

1530 def transform_non_affine(self, values): 

1531 """ 

1532 Apply only the non-affine part of this transformation. 

1533 

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

1535 ``transform_affine(transform_non_affine(values))``. 

1536 

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

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

1539 always a no-op. 

1540 

1541 Parameters 

1542 ---------- 

1543 values : array 

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

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

1546 

1547 Returns 

1548 ------- 

1549 array 

1550 The output values as NumPy array of length :attr:`output_dims` or 

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

1552 """ 

1553 return values 

1554 

1555 def transform_bbox(self, bbox): 

1556 """ 

1557 Transform the given bounding box. 

1558 

1559 For smarter transforms including caching (a common requirement in 

1560 Matplotlib), see `TransformedBbox`. 

1561 """ 

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

1563 

1564 def get_affine(self): 

1565 """Get the affine part of this transform.""" 

1566 return IdentityTransform() 

1567 

1568 def get_matrix(self): 

1569 """Get the matrix for the affine part of this transform.""" 

1570 return self.get_affine().get_matrix() 

1571 

1572 def transform_point(self, point): 

1573 """ 

1574 Return a transformed point. 

1575 

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

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

1578 and a single point. 

1579 

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

1581 The transformed point is returned as a sequence of length 

1582 :attr:`output_dims`. 

1583 """ 

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

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

1586 return self.transform(point) 

1587 

1588 def transform_path(self, path): 

1589 """ 

1590 Apply the transform to `.Path` *path*, returning a new `.Path`. 

1591 

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

1593 that began as line segments. 

1594 """ 

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

1596 

1597 def transform_path_affine(self, path): 

1598 """ 

1599 Apply the affine part of this transform to `.Path` *path*, returning a 

1600 new `.Path`. 

1601 

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

1603 ``transform_path_affine(transform_path_non_affine(values))``. 

1604 """ 

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

1606 

1607 def transform_path_non_affine(self, path): 

1608 """ 

1609 Apply the non-affine part of this transform to `.Path` *path*, 

1610 returning a new `.Path`. 

1611 

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

1613 ``transform_path_affine(transform_path_non_affine(values))``. 

1614 """ 

1615 x = self.transform_non_affine(path.vertices) 

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

1617 

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

1619 """ 

1620 Transform a set of angles anchored at specific locations. 

1621 

1622 Parameters 

1623 ---------- 

1624 angles : (N,) array-like 

1625 The angles to transform. 

1626 pts : (N, 2) array-like 

1627 The points where the angles are anchored. 

1628 radians : bool, default: False 

1629 Whether *angles* are radians or degrees. 

1630 pushoff : float 

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

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

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

1634 horizontal axis, and measuring the angle between the horizontal 

1635 axis and the transformed segment. 

1636 

1637 Returns 

1638 ------- 

1639 (N,) array 

1640 """ 

1641 # Must be 2D 

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

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

1644 angles = np.asarray(angles) 

1645 pts = np.asarray(pts) 

1646 _api.check_shape((None, 2), pts=pts) 

1647 _api.check_shape((None,), angles=angles) 

1648 if len(angles) != len(pts): 

1649 raise ValueError("There must be as many 'angles' as 'pts'") 

1650 # Convert to radians if desired 

1651 if not radians: 

1652 angles = np.deg2rad(angles) 

1653 # Move a short distance away 

1654 pts2 = pts + pushoff * np.column_stack([np.cos(angles), 

1655 np.sin(angles)]) 

1656 # Transform both sets of points 

1657 tpts = self.transform(pts) 

1658 tpts2 = self.transform(pts2) 

1659 # Calculate transformed angles 

1660 d = tpts2 - tpts 

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

1662 # Convert back to degrees if desired 

1663 if not radians: 

1664 a = np.rad2deg(a) 

1665 return a 

1666 

1667 def inverted(self): 

1668 """ 

1669 Return the corresponding inverse transformation. 

1670 

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

1672 

1673 The return value of this method should be treated as 

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

1675 update to its inverted copy. 

1676 """ 

1677 raise NotImplementedError() 

1678 

1679 

1680class TransformWrapper(Transform): 

1681 """ 

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

1683 equivalently to it. 

1684 

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

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

1687 that replacement to correctly trigger invalidation. 

1688 

1689 `TransformWrapper` instances must have the same input and output dimensions 

1690 during their entire lifetime, so the child transform may only be replaced 

1691 with another child transform of the same dimensions. 

1692 """ 

1693 

1694 pass_through = True 

1695 

1696 def __init__(self, child): 

1697 """ 

1698 *child*: A `Transform` instance. This child may later 

1699 be replaced with :meth:`set`. 

1700 """ 

1701 _api.check_isinstance(Transform, child=child) 

1702 self._init(child) 

1703 self.set_children(child) 

1704 

1705 def _init(self, child): 

1706 Transform.__init__(self) 

1707 self.input_dims = child.input_dims 

1708 self.output_dims = child.output_dims 

1709 self._set(child) 

1710 self._invalid = 0 

1711 

1712 def __eq__(self, other): 

1713 return self._child.__eq__(other) 

1714 

1715 __str__ = _make_str_method("_child") 

1716 

1717 def frozen(self): 

1718 # docstring inherited 

1719 return self._child.frozen() 

1720 

1721 def _set(self, child): 

1722 self._child = child 

1723 

1724 self.transform = child.transform 

1725 self.transform_affine = child.transform_affine 

1726 self.transform_non_affine = child.transform_non_affine 

1727 self.transform_path = child.transform_path 

1728 self.transform_path_affine = child.transform_path_affine 

1729 self.transform_path_non_affine = child.transform_path_non_affine 

1730 self.get_affine = child.get_affine 

1731 self.inverted = child.inverted 

1732 self.get_matrix = child.get_matrix 

1733 

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

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

1736 # is_affine and other such properties may be dangerous. 

1737 

1738 def set(self, child): 

1739 """ 

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

1741 

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

1743 dimensions as the current child. 

1744 """ 

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

1746 child.output_dims != self.output_dims): 

1747 raise ValueError( 

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

1749 "dimensions as the current child") 

1750 

1751 self.set_children(child) 

1752 self._set(child) 

1753 

1754 self._invalid = 0 

1755 self.invalidate() 

1756 self._invalid = 0 

1757 

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

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

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

1761 

1762 

1763class AffineBase(Transform): 

1764 """ 

1765 The base class of all affine transformations of any number of dimensions. 

1766 """ 

1767 is_affine = True 

1768 

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

1770 super().__init__(*args, **kwargs) 

1771 self._inverted = None 

1772 

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

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

1775 return self.get_matrix() 

1776 

1777 def __eq__(self, other): 

1778 if getattr(other, "is_affine", False) and hasattr(other, "get_matrix"): 

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

1780 return NotImplemented 

1781 

1782 def transform(self, values): 

1783 # docstring inherited 

1784 return self.transform_affine(values) 

1785 

1786 def transform_affine(self, values): 

1787 # docstring inherited 

1788 raise NotImplementedError('Affine subclasses should override this ' 

1789 'method.') 

1790 

1791 def transform_non_affine(self, points): 

1792 # docstring inherited 

1793 return points 

1794 

1795 def transform_path(self, path): 

1796 # docstring inherited 

1797 return self.transform_path_affine(path) 

1798 

1799 def transform_path_affine(self, path): 

1800 # docstring inherited 

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

1802 path.codes, path._interpolation_steps) 

1803 

1804 def transform_path_non_affine(self, path): 

1805 # docstring inherited 

1806 return path 

1807 

1808 def get_affine(self): 

1809 # docstring inherited 

1810 return self 

1811 

1812 

1813class Affine2DBase(AffineBase): 

1814 """ 

1815 The base class of all 2D affine transformations. 

1816 

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

1818 

1819 a c e 

1820 b d f 

1821 0 0 1 

1822 

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

1824 affine transformation, use `Affine2D`. 

1825 

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

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

1828 """ 

1829 input_dims = 2 

1830 output_dims = 2 

1831 

1832 def frozen(self): 

1833 # docstring inherited 

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

1835 

1836 @property 

1837 def is_separable(self): 

1838 mtx = self.get_matrix() 

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

1840 

1841 def to_values(self): 

1842 """ 

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

1844 """ 

1845 mtx = self.get_matrix() 

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

1847 

1848 def transform_affine(self, points): 

1849 mtx = self.get_matrix() 

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

1851 tpoints = affine_transform(points.data, mtx) 

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

1853 return affine_transform(points, mtx) 

1854 

1855 if DEBUG: 

1856 _transform_affine = transform_affine 

1857 

1858 def transform_affine(self, points): 

1859 # docstring inherited 

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

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

1862 # more arrays upstream, that should help here. 

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

1864 _api.warn_external( 

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

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

1867 return self._transform_affine(points) 

1868 

1869 def inverted(self): 

1870 # docstring inherited 

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

1872 mtx = self.get_matrix() 

1873 shorthand_name = None 

1874 if self._shorthand_name: 

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

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

1877 self._invalid = 0 

1878 return self._inverted 

1879 

1880 

1881class Affine2D(Affine2DBase): 

1882 """ 

1883 A mutable 2D affine transformation. 

1884 """ 

1885 

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

1887 """ 

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

1889 

1890 a c e 

1891 b d f 

1892 0 0 1 

1893 

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

1895 """ 

1896 super().__init__(**kwargs) 

1897 if matrix is None: 

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

1899 matrix = IdentityTransform._mtx.copy() 

1900 self._mtx = matrix.copy() 

1901 self._invalid = 0 

1902 

1903 _base_str = _make_str_method("_mtx") 

1904 

1905 def __str__(self): 

1906 return (self._base_str() 

1907 if (self._mtx != np.diag(np.diag(self._mtx))).any() 

1908 else f"Affine2D().scale({self._mtx[0, 0]}, {self._mtx[1, 1]})" 

1909 if self._mtx[0, 0] != self._mtx[1, 1] 

1910 else f"Affine2D().scale({self._mtx[0, 0]})") 

1911 

1912 @staticmethod 

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

1914 """ 

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

1916 

1917 a c e 

1918 b d f 

1919 0 0 1 

1920 

1921 . 

1922 """ 

1923 return Affine2D( 

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

1925 

1926 def get_matrix(self): 

1927 """ 

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

1929 

1930 a c e 

1931 b d f 

1932 0 0 1 

1933 

1934 . 

1935 """ 

1936 if self._invalid: 

1937 self._inverted = None 

1938 self._invalid = 0 

1939 return self._mtx 

1940 

1941 def set_matrix(self, mtx): 

1942 """ 

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

1944 

1945 a c e 

1946 b d f 

1947 0 0 1 

1948 

1949 . 

1950 """ 

1951 self._mtx = mtx 

1952 self.invalidate() 

1953 

1954 def set(self, other): 

1955 """ 

1956 Set this transformation from the frozen copy of another 

1957 `Affine2DBase` object. 

1958 """ 

1959 _api.check_isinstance(Affine2DBase, other=other) 

1960 self._mtx = other.get_matrix() 

1961 self.invalidate() 

1962 

1963 @staticmethod 

1964 @_api.deprecated("3.6", alternative="Affine2D()") 

1965 def identity(): 

1966 """ 

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

1968 

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

1970 the faster `IdentityTransform` class instead. 

1971 """ 

1972 return Affine2D() 

1973 

1974 def clear(self): 

1975 """ 

1976 Reset the underlying matrix to the identity transform. 

1977 """ 

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

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

1980 self.invalidate() 

1981 return self 

1982 

1983 def rotate(self, theta): 

1984 """ 

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

1986 

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

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

1989 and :meth:`scale`. 

1990 """ 

1991 a = math.cos(theta) 

1992 b = math.sin(theta) 

1993 mtx = self._mtx 

1994 # Operating and assigning one scalar at a time is much faster. 

1995 (xx, xy, x0), (yx, yy, y0), _ = mtx.tolist() 

1996 # mtx = [[a -b 0], [b a 0], [0 0 1]] * mtx 

1997 mtx[0, 0] = a * xx - b * yx 

1998 mtx[0, 1] = a * xy - b * yy 

1999 mtx[0, 2] = a * x0 - b * y0 

2000 mtx[1, 0] = b * xx + a * yx 

2001 mtx[1, 1] = b * xy + a * yy 

2002 mtx[1, 2] = b * x0 + a * y0 

2003 self.invalidate() 

2004 return self 

2005 

2006 def rotate_deg(self, degrees): 

2007 """ 

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

2009 

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

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

2012 and :meth:`scale`. 

2013 """ 

2014 return self.rotate(math.radians(degrees)) 

2015 

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

2017 """ 

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

2019 

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

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

2022 and :meth:`scale`. 

2023 """ 

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

2025 

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

2027 """ 

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

2029 

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

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

2032 and :meth:`scale`. 

2033 """ 

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

2035 x, y = float(x), float(y) 

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

2037 

2038 def translate(self, tx, ty): 

2039 """ 

2040 Add a translation in place. 

2041 

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

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

2044 and :meth:`scale`. 

2045 """ 

2046 self._mtx[0, 2] += tx 

2047 self._mtx[1, 2] += ty 

2048 self.invalidate() 

2049 return self 

2050 

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

2052 """ 

2053 Add a scale in place. 

2054 

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

2056 *y*-directions. 

2057 

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

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

2060 and :meth:`scale`. 

2061 """ 

2062 if sy is None: 

2063 sy = sx 

2064 # explicit element-wise scaling is fastest 

2065 self._mtx[0, 0] *= sx 

2066 self._mtx[0, 1] *= sx 

2067 self._mtx[0, 2] *= sx 

2068 self._mtx[1, 0] *= sy 

2069 self._mtx[1, 1] *= sy 

2070 self._mtx[1, 2] *= sy 

2071 self.invalidate() 

2072 return self 

2073 

2074 def skew(self, xShear, yShear): 

2075 """ 

2076 Add a skew in place. 

2077 

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

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

2080 

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

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

2083 and :meth:`scale`. 

2084 """ 

2085 rx = math.tan(xShear) 

2086 ry = math.tan(yShear) 

2087 mtx = self._mtx 

2088 # Operating and assigning one scalar at a time is much faster. 

2089 (xx, xy, x0), (yx, yy, y0), _ = mtx.tolist() 

2090 # mtx = [[1 rx 0], [ry 1 0], [0 0 1]] * mtx 

2091 mtx[0, 0] += rx * yx 

2092 mtx[0, 1] += rx * yy 

2093 mtx[0, 2] += rx * y0 

2094 mtx[1, 0] += ry * xx 

2095 mtx[1, 1] += ry * xy 

2096 mtx[1, 2] += ry * x0 

2097 self.invalidate() 

2098 return self 

2099 

2100 def skew_deg(self, xShear, yShear): 

2101 """ 

2102 Add a skew in place. 

2103 

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

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

2106 

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

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

2109 and :meth:`scale`. 

2110 """ 

2111 return self.skew(math.radians(xShear), math.radians(yShear)) 

2112 

2113 

2114class IdentityTransform(Affine2DBase): 

2115 """ 

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

2117 fast way. 

2118 """ 

2119 _mtx = np.identity(3) 

2120 

2121 def frozen(self): 

2122 # docstring inherited 

2123 return self 

2124 

2125 __str__ = _make_str_method() 

2126 

2127 def get_matrix(self): 

2128 # docstring inherited 

2129 return self._mtx 

2130 

2131 def transform(self, points): 

2132 # docstring inherited 

2133 return np.asanyarray(points) 

2134 

2135 def transform_affine(self, points): 

2136 # docstring inherited 

2137 return np.asanyarray(points) 

2138 

2139 def transform_non_affine(self, points): 

2140 # docstring inherited 

2141 return np.asanyarray(points) 

2142 

2143 def transform_path(self, path): 

2144 # docstring inherited 

2145 return path 

2146 

2147 def transform_path_affine(self, path): 

2148 # docstring inherited 

2149 return path 

2150 

2151 def transform_path_non_affine(self, path): 

2152 # docstring inherited 

2153 return path 

2154 

2155 def get_affine(self): 

2156 # docstring inherited 

2157 return self 

2158 

2159 def inverted(self): 

2160 # docstring inherited 

2161 return self 

2162 

2163 

2164class _BlendedMixin: 

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

2166 

2167 def __eq__(self, other): 

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

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

2170 elif self._x == self._y: 

2171 return self._x == other 

2172 else: 

2173 return NotImplemented 

2174 

2175 def contains_branch_seperately(self, transform): 

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

2177 self._y.contains_branch(transform)) 

2178 

2179 __str__ = _make_str_method("_x", "_y") 

2180 

2181 

2182class BlendedGenericTransform(_BlendedMixin, Transform): 

2183 """ 

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

2185 another transform for the *y*-direction. 

2186 

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

2188 *x*- and *y*-directions. 

2189 """ 

2190 input_dims = 2 

2191 output_dims = 2 

2192 is_separable = True 

2193 pass_through = True 

2194 

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

2196 """ 

2197 Create a new "blended" transform using *x_transform* to transform the 

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

2199 

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

2201 `blended_transform_factory` function instead, which can determine 

2202 automatically which kind of blended transform to create. 

2203 """ 

2204 Transform.__init__(self, **kwargs) 

2205 self._x = x_transform 

2206 self._y = y_transform 

2207 self.set_children(x_transform, y_transform) 

2208 self._affine = None 

2209 

2210 @property 

2211 def depth(self): 

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

2213 

2214 def contains_branch(self, other): 

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

2216 # different transforms. 

2217 return False 

2218 

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

2220 has_inverse = property( 

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

2222 

2223 def frozen(self): 

2224 # docstring inherited 

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

2226 

2227 def transform_non_affine(self, points): 

2228 # docstring inherited 

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

2230 return points 

2231 x = self._x 

2232 y = self._y 

2233 

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

2235 return x.transform_non_affine(points) 

2236 

2237 if x.input_dims == 2: 

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

2239 else: 

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

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

2242 

2243 if y.input_dims == 2: 

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

2245 else: 

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

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

2248 

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

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

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

2252 else: 

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

2254 

2255 def inverted(self): 

2256 # docstring inherited 

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

2258 

2259 def get_affine(self): 

2260 # docstring inherited 

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

2262 if self._x == self._y: 

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

2264 else: 

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

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

2267 # We already know the transforms are separable, so we can skip 

2268 # setting b and c to zero. 

2269 mtx = np.array([x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]]) 

2270 self._affine = Affine2D(mtx) 

2271 self._invalid = 0 

2272 return self._affine 

2273 

2274 

2275class BlendedAffine2D(_BlendedMixin, Affine2DBase): 

2276 """ 

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

2278 another transform for the *y*-direction. 

2279 

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

2281 transforms are of type `Affine2DBase`. 

2282 """ 

2283 

2284 is_separable = True 

2285 

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

2287 """ 

2288 Create a new "blended" transform using *x_transform* to transform the 

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

2290 

2291 Both *x_transform* and *y_transform* must be 2D affine transforms. 

2292 

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

2294 `blended_transform_factory` function instead, which can determine 

2295 automatically which kind of blended transform to create. 

2296 """ 

2297 is_affine = x_transform.is_affine and y_transform.is_affine 

2298 is_separable = x_transform.is_separable and y_transform.is_separable 

2299 is_correct = is_affine and is_separable 

2300 if not is_correct: 

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

2302 "affine transforms") 

2303 

2304 Transform.__init__(self, **kwargs) 

2305 self._x = x_transform 

2306 self._y = y_transform 

2307 self.set_children(x_transform, y_transform) 

2308 

2309 Affine2DBase.__init__(self) 

2310 self._mtx = None 

2311 

2312 def get_matrix(self): 

2313 # docstring inherited 

2314 if self._invalid: 

2315 if self._x == self._y: 

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

2317 else: 

2318 x_mtx = self._x.get_matrix() 

2319 y_mtx = self._y.get_matrix() 

2320 # We already know the transforms are separable, so we can skip 

2321 # setting b and c to zero. 

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

2323 self._inverted = None 

2324 self._invalid = 0 

2325 return self._mtx 

2326 

2327 

2328def blended_transform_factory(x_transform, y_transform): 

2329 """ 

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

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

2332 

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

2334 where both child transforms are affine. 

2335 """ 

2336 if (isinstance(x_transform, Affine2DBase) and 

2337 isinstance(y_transform, Affine2DBase)): 

2338 return BlendedAffine2D(x_transform, y_transform) 

2339 return BlendedGenericTransform(x_transform, y_transform) 

2340 

2341 

2342class CompositeGenericTransform(Transform): 

2343 """ 

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

2345 transform *b*. 

2346 

2347 This "generic" version can handle any two arbitrary 

2348 transformations. 

2349 """ 

2350 pass_through = True 

2351 

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

2353 """ 

2354 Create a new composite transform that is the result of 

2355 applying transform *a* then transform *b*. 

2356 

2357 You will generally not call this constructor directly but write ``a + 

2358 b`` instead, which will automatically choose the best kind of composite 

2359 transform instance to create. 

2360 """ 

2361 if a.output_dims != b.input_dims: 

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

2363 "the input dimensions of 'b'") 

2364 self.input_dims = a.input_dims 

2365 self.output_dims = b.output_dims 

2366 

2367 super().__init__(**kwargs) 

2368 self._a = a 

2369 self._b = b 

2370 self.set_children(a, b) 

2371 

2372 def frozen(self): 

2373 # docstring inherited 

2374 self._invalid = 0 

2375 frozen = composite_transform_factory( 

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

2377 if not isinstance(frozen, CompositeGenericTransform): 

2378 return frozen.frozen() 

2379 return frozen 

2380 

2381 def _invalidate_internal(self, value, invalidating_node): 

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

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

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

2385 # either: 

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

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

2388 if (value == Transform.INVALID_AFFINE and 

2389 not self._b.is_affine and 

2390 (not self._a.is_affine or invalidating_node is self._a)): 

2391 value = Transform.INVALID 

2392 

2393 super()._invalidate_internal(value=value, 

2394 invalidating_node=invalidating_node) 

2395 

2396 def __eq__(self, other): 

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

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

2399 and self._b == other._b) 

2400 else: 

2401 return False 

2402 

2403 def _iter_break_from_left_to_right(self): 

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

2405 yield left, right + self._b 

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

2407 yield self._a + left, right 

2408 

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

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

2411 is_separable = property( 

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

2413 has_inverse = property( 

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

2415 

2416 __str__ = _make_str_method("_a", "_b") 

2417 

2418 def transform_affine(self, points): 

2419 # docstring inherited 

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

2421 

2422 def transform_non_affine(self, points): 

2423 # docstring inherited 

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

2425 return points 

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

2427 return self._a.transform_non_affine(points) 

2428 else: 

2429 return self._b.transform_non_affine(self._a.transform(points)) 

2430 

2431 def transform_path_non_affine(self, path): 

2432 # docstring inherited 

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

2434 return path 

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

2436 return self._a.transform_path_non_affine(path) 

2437 else: 

2438 return self._b.transform_path_non_affine( 

2439 self._a.transform_path(path)) 

2440 

2441 def get_affine(self): 

2442 # docstring inherited 

2443 if not self._b.is_affine: 

2444 return self._b.get_affine() 

2445 else: 

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

2447 self._a.get_affine().get_matrix())) 

2448 

2449 def inverted(self): 

2450 # docstring inherited 

2451 return CompositeGenericTransform( 

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

2453 

2454 

2455class CompositeAffine2D(Affine2DBase): 

2456 """ 

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

2458 

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

2460 and *b* are 2D affines. 

2461 """ 

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

2463 """ 

2464 Create a new composite transform that is the result of 

2465 applying `Affine2DBase` *a* then `Affine2DBase` *b*. 

2466 

2467 You will generally not call this constructor directly but write ``a + 

2468 b`` instead, which will automatically choose the best kind of composite 

2469 transform instance to create. 

2470 """ 

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

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

2473 if a.output_dims != b.input_dims: 

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

2475 "the input dimensions of 'b'") 

2476 self.input_dims = a.input_dims 

2477 self.output_dims = b.output_dims 

2478 

2479 super().__init__(**kwargs) 

2480 self._a = a 

2481 self._b = b 

2482 self.set_children(a, b) 

2483 self._mtx = None 

2484 

2485 @property 

2486 def depth(self): 

2487 return self._a.depth + self._b.depth 

2488 

2489 def _iter_break_from_left_to_right(self): 

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

2491 yield left, right + self._b 

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

2493 yield self._a + left, right 

2494 

2495 __str__ = _make_str_method("_a", "_b") 

2496 

2497 def get_matrix(self): 

2498 # docstring inherited 

2499 if self._invalid: 

2500 self._mtx = np.dot( 

2501 self._b.get_matrix(), 

2502 self._a.get_matrix()) 

2503 self._inverted = None 

2504 self._invalid = 0 

2505 return self._mtx 

2506 

2507 

2508def composite_transform_factory(a, b): 

2509 """ 

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

2511 transform a then transform b. 

2512 

2513 Shortcut versions of the blended transform are provided for the 

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

2515 is the identity transform. 

2516 

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

2518 e.g.:: 

2519 

2520 c = a + b 

2521 """ 

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

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

2524 # be IdentityTransforms. Since TransformWrappers are mutable, 

2525 # use of equality here would be wrong. 

2526 if isinstance(a, IdentityTransform): 

2527 return b 

2528 elif isinstance(b, IdentityTransform): 

2529 return a 

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

2531 return CompositeAffine2D(a, b) 

2532 return CompositeGenericTransform(a, b) 

2533 

2534 

2535class BboxTransform(Affine2DBase): 

2536 """ 

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

2538 """ 

2539 

2540 is_separable = True 

2541 

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

2543 """ 

2544 Create a new `BboxTransform` that linearly transforms 

2545 points from *boxin* to *boxout*. 

2546 """ 

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

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

2549 

2550 super().__init__(**kwargs) 

2551 self._boxin = boxin 

2552 self._boxout = boxout 

2553 self.set_children(boxin, boxout) 

2554 self._mtx = None 

2555 self._inverted = None 

2556 

2557 __str__ = _make_str_method("_boxin", "_boxout") 

2558 

2559 def get_matrix(self): 

2560 # docstring inherited 

2561 if self._invalid: 

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

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

2564 x_scale = outw / inw 

2565 y_scale = outh / inh 

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

2567 raise ValueError( 

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

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

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

2571 [0.0 , 0.0 , 1.0 ]], 

2572 float) 

2573 self._inverted = None 

2574 self._invalid = 0 

2575 return self._mtx 

2576 

2577 

2578class BboxTransformTo(Affine2DBase): 

2579 """ 

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

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

2582 """ 

2583 

2584 is_separable = True 

2585 

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

2587 """ 

2588 Create a new `BboxTransformTo` that linearly transforms 

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

2590 """ 

2591 if not boxout.is_bbox: 

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

2593 

2594 super().__init__(**kwargs) 

2595 self._boxout = boxout 

2596 self.set_children(boxout) 

2597 self._mtx = None 

2598 self._inverted = None 

2599 

2600 __str__ = _make_str_method("_boxout") 

2601 

2602 def get_matrix(self): 

2603 # docstring inherited 

2604 if self._invalid: 

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

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

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

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

2609 [ 0.0, outh, outb], 

2610 [ 0.0, 0.0, 1.0]], 

2611 float) 

2612 self._inverted = None 

2613 self._invalid = 0 

2614 return self._mtx 

2615 

2616 

2617class BboxTransformToMaxOnly(BboxTransformTo): 

2618 """ 

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

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

2621 """ 

2622 def get_matrix(self): 

2623 # docstring inherited 

2624 if self._invalid: 

2625 xmax, ymax = self._boxout.max 

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

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

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

2629 [ 0.0, ymax, 0.0], 

2630 [ 0.0, 0.0, 1.0]], 

2631 float) 

2632 self._inverted = None 

2633 self._invalid = 0 

2634 return self._mtx 

2635 

2636 

2637class BboxTransformFrom(Affine2DBase): 

2638 """ 

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

2640 unit bounding box. 

2641 """ 

2642 is_separable = True 

2643 

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

2645 if not boxin.is_bbox: 

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

2647 

2648 super().__init__(**kwargs) 

2649 self._boxin = boxin 

2650 self.set_children(boxin) 

2651 self._mtx = None 

2652 self._inverted = None 

2653 

2654 __str__ = _make_str_method("_boxin") 

2655 

2656 def get_matrix(self): 

2657 # docstring inherited 

2658 if self._invalid: 

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

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

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

2662 x_scale = 1.0 / inw 

2663 y_scale = 1.0 / inh 

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

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

2666 [0.0 , 0.0 , 1.0 ]], 

2667 float) 

2668 self._inverted = None 

2669 self._invalid = 0 

2670 return self._mtx 

2671 

2672 

2673class ScaledTranslation(Affine2DBase): 

2674 """ 

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

2676 have been transformed by *scale_trans*. 

2677 """ 

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

2679 super().__init__(**kwargs) 

2680 self._t = (xt, yt) 

2681 self._scale_trans = scale_trans 

2682 self.set_children(scale_trans) 

2683 self._mtx = None 

2684 self._inverted = None 

2685 

2686 __str__ = _make_str_method("_t") 

2687 

2688 def get_matrix(self): 

2689 # docstring inherited 

2690 if self._invalid: 

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

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

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

2694 self._invalid = 0 

2695 self._inverted = None 

2696 return self._mtx 

2697 

2698 

2699class AffineDeltaTransform(Affine2DBase): 

2700 r""" 

2701 A transform wrapper for transforming displacements between pairs of points. 

2702 

2703 This class is intended to be used to transform displacements ("position 

2704 deltas") between pairs of points (e.g., as the ``offset_transform`` 

2705 of `.Collection`\s): given a transform ``t`` such that ``t = 

2706 AffineDeltaTransform(t) + offset``, ``AffineDeltaTransform`` 

2707 satisfies ``AffineDeltaTransform(a - b) == AffineDeltaTransform(a) - 

2708 AffineDeltaTransform(b)``. 

2709 

2710 This is implemented by forcing the offset components of the transform 

2711 matrix to zero. 

2712 

2713 This class is experimental as of 3.3, and the API may change. 

2714 """ 

2715 

2716 def __init__(self, transform, **kwargs): 

2717 super().__init__(**kwargs) 

2718 self._base_transform = transform 

2719 

2720 __str__ = _make_str_method("_base_transform") 

2721 

2722 def get_matrix(self): 

2723 if self._invalid: 

2724 self._mtx = self._base_transform.get_matrix().copy() 

2725 self._mtx[:2, -1] = 0 

2726 return self._mtx 

2727 

2728 

2729class TransformedPath(TransformNode): 

2730 """ 

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

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

2733 non-affine part of the transform changes. 

2734 

2735 .. note:: 

2736 

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

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

2739 

2740 """ 

2741 def __init__(self, path, transform): 

2742 """ 

2743 Parameters 

2744 ---------- 

2745 path : `~.path.Path` 

2746 transform : `Transform` 

2747 """ 

2748 _api.check_isinstance(Transform, transform=transform) 

2749 super().__init__() 

2750 self._path = path 

2751 self._transform = transform 

2752 self.set_children(transform) 

2753 self._transformed_path = None 

2754 self._transformed_points = None 

2755 

2756 def _revalidate(self): 

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

2758 # the transform 

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

2760 or self._transformed_path is None): 

2761 self._transformed_path = \ 

2762 self._transform.transform_path_non_affine(self._path) 

2763 self._transformed_points = \ 

2764 Path._fast_from_codes_and_verts( 

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

2766 None, self._path) 

2767 self._invalid = 0 

2768 

2769 def get_transformed_points_and_affine(self): 

2770 """ 

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

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

2773 the path necessary to complete the transformation. Unlike 

2774 :meth:`get_transformed_path_and_affine`, no interpolation will 

2775 be performed. 

2776 """ 

2777 self._revalidate() 

2778 return self._transformed_points, self.get_affine() 

2779 

2780 def get_transformed_path_and_affine(self): 

2781 """ 

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

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

2784 the path necessary to complete the transformation. 

2785 """ 

2786 self._revalidate() 

2787 return self._transformed_path, self.get_affine() 

2788 

2789 def get_fully_transformed_path(self): 

2790 """ 

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

2792 """ 

2793 self._revalidate() 

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

2795 

2796 def get_affine(self): 

2797 return self._transform.get_affine() 

2798 

2799 

2800class TransformedPatchPath(TransformedPath): 

2801 """ 

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

2803 `~.patches.Patch`. This cached copy is automatically updated when the 

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

2805 """ 

2806 

2807 def __init__(self, patch): 

2808 """ 

2809 Parameters 

2810 ---------- 

2811 patch : `~.patches.Patch` 

2812 """ 

2813 # Defer to TransformedPath.__init__. 

2814 super().__init__(patch.get_path(), patch.get_transform()) 

2815 self._patch = patch 

2816 

2817 def _revalidate(self): 

2818 patch_path = self._patch.get_path() 

2819 # Force invalidation if the patch path changed; otherwise, let base 

2820 # class check invalidation. 

2821 if patch_path != self._path: 

2822 self._path = patch_path 

2823 self._transformed_path = None 

2824 super()._revalidate() 

2825 

2826 

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

2828 """ 

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

2830 

2831 Parameters 

2832 ---------- 

2833 vmin, vmax : float 

2834 The initial endpoints. 

2835 expander : float, default: 0.001 

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

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

2838 tiny : float, default: 1e-15 

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

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

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

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

2843 the double precision resolution limit. 

2844 increasing : bool, default: True 

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

2846 

2847 Returns 

2848 ------- 

2849 vmin, vmax : float 

2850 Endpoints, expanded and/or swapped if necessary. 

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

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

2853 """ 

2854 

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

2856 return -expander, expander 

2857 

2858 swapped = False 

2859 if vmax < vmin: 

2860 vmin, vmax = vmax, vmin 

2861 swapped = True 

2862 

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

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

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

2866 

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

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

2869 vmin = -expander 

2870 vmax = expander 

2871 

2872 elif vmax - vmin <= maxabsvalue * tiny: 

2873 if vmax == 0 and vmin == 0: 

2874 vmin = -expander 

2875 vmax = expander 

2876 else: 

2877 vmin -= expander*abs(vmin) 

2878 vmax += expander*abs(vmax) 

2879 

2880 if swapped and not increasing: 

2881 vmin, vmax = vmax, vmin 

2882 return vmin, vmax 

2883 

2884 

2885def interval_contains(interval, val): 

2886 """ 

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

2888 

2889 Parameters 

2890 ---------- 

2891 interval : (float, float) 

2892 The endpoints of the interval. 

2893 val : float 

2894 Value to check is within interval. 

2895 

2896 Returns 

2897 ------- 

2898 bool 

2899 Whether *val* is within the *interval*. 

2900 """ 

2901 a, b = interval 

2902 if a > b: 

2903 a, b = b, a 

2904 return a <= val <= b 

2905 

2906 

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

2908 """ 

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

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

2911 

2912 Parameters 

2913 ---------- 

2914 interval : (float, float) 

2915 The endpoints of the interval. 

2916 val : float 

2917 Value to check is within interval. 

2918 rtol : float, default: 1e-10 

2919 Relative tolerance slippage allowed outside of the interval. 

2920 For an interval ``[a, b]``, values 

2921 ``a - rtol * (b - a) <= val <= b + rtol * (b - a)`` are considered 

2922 inside the interval. 

2923 

2924 Returns 

2925 ------- 

2926 bool 

2927 Whether *val* is within the *interval* (with tolerance). 

2928 """ 

2929 a, b = interval 

2930 if a > b: 

2931 a, b = b, a 

2932 rtol = (b - a) * rtol 

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

2934 

2935 

2936def interval_contains_open(interval, val): 

2937 """ 

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

2939 

2940 Parameters 

2941 ---------- 

2942 interval : (float, float) 

2943 The endpoints of the interval. 

2944 val : float 

2945 Value to check is within interval. 

2946 

2947 Returns 

2948 ------- 

2949 bool 

2950 Whether *val* is within the *interval*. 

2951 """ 

2952 a, b = interval 

2953 return a < val < b or a > val > b 

2954 

2955 

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

2957 """ 

2958 Return a new transform with an added offset. 

2959 

2960 Parameters 

2961 ---------- 

2962 trans : `Transform` subclass 

2963 Any transform, to which offset will be applied. 

2964 fig : `~matplotlib.figure.Figure`, default: None 

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

2966 x, y : float, default: 0.0 

2967 The offset to apply. 

2968 units : {'inches', 'points', 'dots'}, default: 'inches' 

2969 Units of the offset. 

2970 

2971 Returns 

2972 ------- 

2973 `Transform` subclass 

2974 Transform with applied offset. 

2975 """ 

2976 if units == 'dots': 

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

2978 if fig is None: 

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

2980 if units == 'points': 

2981 x /= 72.0 

2982 y /= 72.0 

2983 elif units == 'inches': 

2984 pass 

2985 else: 

2986 _api.check_in_list(['dots', 'points', 'inches'], units=units) 

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