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 :class:`TransformNode` objects 7whose actual value depends on their children. When the contents of 8children change, their parents are automatically invalidated. The 9next time an invalidated transform is accessed, it is recomputed to 10reflect those changes. This invalidation/caching approach prevents 11unnecessary recomputations of transforms, and contributes to better 12interactive performance. 13 14For example, here is a graph of the transform tree used to plot data 15to the graph: 16 17.. image:: ../_static/transforms.png 18 19The framework can be used for both affine and non-affine 20transformations. However, for speed, we want use the backend 21renderers to perform affine transformations whenever possible. 22Therefore, it is possible to perform just the affine or non-affine 23part of a transformation on a set of data. The affine is always 24assumed to occur after the non-affine. For any transform:: 25 26 full transform == non-affine part + affine part 27 28The backends are not expected to handle non-affine transformations 29themselves. 30""" 31 32# Note: There are a number of places in the code where we use `np.min` or 33# `np.minimum` instead of the builtin `min`, and likewise for `max`. This is 34# done so that `nan`s are propagated, instead of being silently dropped. 35 36from __future__ import (absolute_import, division, print_function, 37 unicode_literals) 38 39import six 40 41import numpy as np 42from matplotlib._path import (affine_transform, count_bboxes_overlapping_bbox, 43 update_path_extents) 44from numpy.linalg import inv 45 46import re 47import weakref 48import warnings 49 50from . import cbook 51from .path import Path 52 53DEBUG = False 54 55 56def _indent_str(obj): # textwrap.indent(str(obj), 4) on Py3. 57 return re.sub("(^|\n)", r"\1 ", str(obj)) 58 59 60class TransformNode(object): 61 """ 62 :class:`TransformNode` is the base class for anything that 63 participates in the transform tree and needs to invalidate its 64 parents or be invalidated. This includes classes that are not 65 really transforms, such as bounding boxes, since some transforms 66 depend on bounding boxes to compute their values. 67 """ 68 _gid = 0 69 70 # Invalidation may affect only the affine part. If the 71 # invalidation was "affine-only", the _invalid member is set to 72 # INVALID_AFFINE_ONLY 73 INVALID_NON_AFFINE = 1 74 INVALID_AFFINE = 2 75 INVALID = INVALID_NON_AFFINE | INVALID_AFFINE 76 77 # Some metadata about the transform, used to determine whether an 78 # invalidation is affine-only 79 is_affine = False 80 is_bbox = False 81 82 pass_through = False 83 """ 84 If pass_through is True, all ancestors will always be 85 invalidated, even if 'self' is already invalid. 86 """ 87 88 def __init__(self, shorthand_name=None): 89 """ 90 Creates a new :class:`TransformNode`. 91 92 Parameters 93 ---------- 94 shorthand_name : str 95 A string representing the "name" of the transform. The name carries 96 no significance other than to improve the readability of 97 ``str(transform)`` when DEBUG=True. 98 """ 99 self._parents = {} 100 101 # TransformNodes start out as invalid until their values are 102 # computed for the first time. 103 self._invalid = 1 104 self._shorthand_name = shorthand_name or '' 105 106 if DEBUG: 107 def __str__(self): 108 # either just return the name of this TransformNode, or it's repr 109 return self._shorthand_name or repr(self) 110 111 def __getstate__(self): 112 d = self.__dict__.copy() 113 # turn the dictionary with weak values into a normal dictionary 114 d['_parents'] = dict((k, v()) for (k, v) in 115 six.iteritems(self._parents)) 116 return d 117 118 def __setstate__(self, data_dict): 119 self.__dict__ = data_dict 120 # turn the normal dictionary back into a dictionary with weak 121 # values 122 self._parents = dict((k, weakref.ref(v)) for (k, v) in 123 six.iteritems(self._parents) if v is not None) 124 125 def __copy__(self, *args): 126 raise NotImplementedError( 127 "TransformNode instances can not be copied. " 128 "Consider using frozen() instead.") 129 __deepcopy__ = __copy__ 130 131 def invalidate(self): 132 """ 133 Invalidate this :class:`TransformNode` and triggers an 134 invalidation of its ancestors. Should be called any 135 time the transform changes. 136 """ 137 value = self.INVALID 138 if self.is_affine: 139 value = self.INVALID_AFFINE 140 return self._invalidate_internal(value, invalidating_node=self) 141 142 def _invalidate_internal(self, value, invalidating_node): 143 """ 144 Called by :meth:`invalidate` and subsequently ascends the transform 145 stack calling each TransformNode's _invalidate_internal method. 146 """ 147 # determine if this call will be an extension to the invalidation 148 # status. If not, then a shortcut means that we needn't invoke an 149 # invalidation up the transform stack as it will already have been 150 # invalidated. 151 152 # N.B This makes the invalidation sticky, once a transform has been 153 # invalidated as NON_AFFINE, then it will always be invalidated as 154 # NON_AFFINE even when triggered with a AFFINE_ONLY invalidation. 155 # In most cases this is not a problem (i.e. for interactive panning and 156 # zooming) and the only side effect will be on performance. 157 status_changed = self._invalid < value 158 159 if self.pass_through or status_changed: 160 self._invalid = value 161 162 for parent in list(six.itervalues(self._parents)): 163 # Dereference the weak reference 164 parent = parent() 165 if parent is not None: 166 parent._invalidate_internal( 167 value=value, invalidating_node=self) 168 169 def set_children(self, *children): 170 """ 171 Set the children of the transform, to let the invalidation 172 system know which transforms can invalidate this transform. 173 Should be called from the constructor of any transforms that 174 depend on other transforms. 175 """ 176 # Parents are stored as weak references, so that if the 177 # parents are destroyed, references from the children won't 178 # keep them alive. 179 for child in children: 180 child._parents[id(self)] = weakref.ref(self) 181 182 if DEBUG: 183 _set_children = set_children 184 185 def set_children(self, *children): 186 self._set_children(*children) 187 self._children = children 188 set_children.__doc__ = _set_children.__doc__ 189 190 def frozen(self): 191 """ 192 Returns a frozen copy of this transform node. The frozen copy 193 will not update when its children change. Useful for storing 194 a previously known state of a transform where 195 ``copy.deepcopy()`` might normally be used. 196 """ 197 return self 198 199 if DEBUG: 200 def write_graphviz(self, fobj, highlight=[]): 201 """ 202 For debugging purposes. 203 204 Writes the transform tree rooted at 'self' to a graphviz "dot" 205 format file. This file can be run through the "dot" utility 206 to produce a graph of the transform tree. 207 208 Affine transforms are marked in blue. Bounding boxes are 209 marked in yellow. 210 211 *fobj*: A Python file-like object 212 213 Once the "dot" file has been created, it can be turned into a 214 png easily with:: 215 216 $> dot -Tpng -o $OUTPUT_FILE $DOT_FILE 217 218 """ 219 seen = set() 220 221 def recurse(root): 222 if root in seen: 223 return 224 seen.add(root) 225 props = {} 226 label = root.__class__.__name__ 227 if root._invalid: 228 label = '[%s]' % label 229 if root in highlight: 230 props['style'] = 'bold' 231 props['shape'] = 'box' 232 props['label'] = '"%s"' % label 233 props = ' '.join(['%s=%s' % (key, val) 234 for key, val 235 in six.iteritems(props)]) 236 237 fobj.write('%s [%s];\n' % 238 (hash(root), props)) 239 240 if hasattr(root, '_children'): 241 for child in root._children: 242 name = '?' 243 for key, val in six.iteritems(root.__dict__): 244 if val is child: 245 name = key 246 break 247 fobj.write('"%s" -> "%s" [label="%s", fontsize=10];\n' 248 % (hash(root), 249 hash(child), 250 name)) 251 recurse(child) 252 253 fobj.write("digraph G {\n") 254 recurse(self) 255 fobj.write("}\n") 256 257 258class BboxBase(TransformNode): 259 """ 260 This is the base class of all bounding boxes, and provides 261 read-only access to its data. A mutable bounding box is provided 262 by the :class:`Bbox` class. 263 264 The canonical representation is as two points, with no 265 restrictions on their ordering. Convenience properties are 266 provided to get the left, bottom, right and top edges and width 267 and height, but these are not stored explicitly. 268 """ 269 is_bbox = True 270 is_affine = True 271 272 if DEBUG: 273 def _check(points): 274 if isinstance(points, np.ma.MaskedArray): 275 warnings.warn("Bbox bounds are a masked array.") 276 points = np.asarray(points) 277 if (points[1, 0] - points[0, 0] == 0 or 278 points[1, 1] - points[0, 1] == 0): 279 warnings.warn("Singular Bbox.") 280 _check = staticmethod(_check) 281 282 def frozen(self): 283 return Bbox(self.get_points().copy()) 284 frozen.__doc__ = TransformNode.__doc__ 285 286 def __array__(self, *args, **kwargs): 287 return self.get_points() 288 289 def is_unit(self): 290 """ 291 Returns True if the :class:`Bbox` is the unit bounding box 292 from (0, 0) to (1, 1). 293 """ 294 return list(self.get_points().flatten()) == [0., 0., 1., 1.] 295 296 @property 297 def x0(self): 298 """ 299 :attr:`x0` is the first of the pair of *x* coordinates that 300 define the bounding box. :attr:`x0` is not guaranteed to be less than 301 :attr:`x1`. If you require that, use :attr:`xmin`. 302 """ 303 return self.get_points()[0, 0] 304 305 @property 306 def y0(self): 307 """ 308 :attr:`y0` is the first of the pair of *y* coordinates that 309 define the bounding box. :attr:`y0` is not guaranteed to be less than 310 :attr:`y1`. If you require that, use :attr:`ymin`. 311 """ 312 return self.get_points()[0, 1] 313 314 @property 315 def x1(self): 316 """ 317 :attr:`x1` is the second of the pair of *x* coordinates that 318 define the bounding box. :attr:`x1` is not guaranteed to be greater 319 than :attr:`x0`. If you require that, use :attr:`xmax`. 320 """ 321 return self.get_points()[1, 0] 322 323 @property 324 def y1(self): 325 """ 326 :attr:`y1` is the second of the pair of *y* coordinates that 327 define the bounding box. :attr:`y1` is not guaranteed to be greater 328 than :attr:`y0`. If you require that, use :attr:`ymax`. 329 """ 330 return self.get_points()[1, 1] 331 332 @property 333 def p0(self): 334 """ 335 :attr:`p0` is the first pair of (*x*, *y*) coordinates that 336 define the bounding box. It is not guaranteed to be the bottom-left 337 corner. For that, use :attr:`min`. 338 """ 339 return self.get_points()[0] 340 341 @property 342 def p1(self): 343 """ 344 :attr:`p1` is the second pair of (*x*, *y*) coordinates that 345 define the bounding box. It is not guaranteed to be the top-right 346 corner. For that, use :attr:`max`. 347 """ 348 return self.get_points()[1] 349 350 @property 351 def xmin(self): 352 """ 353 :attr:`xmin` is the left edge of the bounding box. 354 """ 355 return np.min(self.get_points()[:, 0]) 356 357 @property 358 def ymin(self): 359 """ 360 :attr:`ymin` is the bottom edge of the bounding box. 361 """ 362 return np.min(self.get_points()[:, 1]) 363 364 @property 365 def xmax(self): 366 """ 367 :attr:`xmax` is the right edge of the bounding box. 368 """ 369 return np.max(self.get_points()[:, 0]) 370 371 @property 372 def ymax(self): 373 """ 374 :attr:`ymax` is the top edge of the bounding box. 375 """ 376 return np.max(self.get_points()[:, 1]) 377 378 @property 379 def min(self): 380 """ 381 :attr:`min` is the bottom-left corner of the bounding box. 382 """ 383 return np.min(self.get_points(), axis=0) 384 385 @property 386 def max(self): 387 """ 388 :attr:`max` is the top-right corner of the bounding box. 389 """ 390 return np.max(self.get_points(), axis=0) 391 392 @property 393 def intervalx(self): 394 """ 395 :attr:`intervalx` is the pair of *x* coordinates that define 396 the bounding box. It is not guaranteed to be sorted from left to right. 397 """ 398 return self.get_points()[:, 0] 399 400 @property 401 def intervaly(self): 402 """ 403 :attr:`intervaly` is the pair of *y* coordinates that define 404 the bounding box. It is not guaranteed to be sorted from bottom to 405 top. 406 """ 407 return self.get_points()[:, 1] 408 409 @property 410 def width(self): 411 """ 412 The width of the bounding box. It may be negative if 413 :attr:`x1` < :attr:`x0`. 414 """ 415 points = self.get_points() 416 return points[1, 0] - points[0, 0] 417 418 @property 419 def height(self): 420 """ 421 The height of the bounding box. It may be negative if 422 :attr:`y1` < :attr:`y0`. 423 """ 424 points = self.get_points() 425 return points[1, 1] - points[0, 1] 426 427 @property 428 def size(self): 429 """ 430 The width and height of the bounding box. May be negative, 431 in the same way as :attr:`width` and :attr:`height`. 432 """ 433 points = self.get_points() 434 return points[1] - points[0] 435 436 @property 437 def bounds(self): 438 """ 439 Returns (:attr:`x0`, :attr:`y0`, :attr:`width`, 440 :attr:`height`). 441 """ 442 x0, y0, x1, y1 = self.get_points().flatten() 443 return (x0, y0, x1 - x0, y1 - y0) 444 445 @property 446 def extents(self): 447 """ 448 Returns (:attr:`x0`, :attr:`y0`, :attr:`x1`, 449 :attr:`y1`). 450 """ 451 return self.get_points().flatten().copy() 452 453 def get_points(self): 454 raise NotImplementedError 455 456 def containsx(self, x): 457 """ 458 Returns whether *x* is in the closed (:attr:`x0`, :attr:`x1`) interval. 459 """ 460 x0, x1 = self.intervalx 461 return x0 <= x <= x1 or x0 >= x >= x1 462 463 def containsy(self, y): 464 """ 465 Returns whether *y* is in the closed (:attr:`y0`, :attr:`y1`) interval. 466 """ 467 y0, y1 = self.intervaly 468 return y0 <= y <= y1 or y0 >= y >= y1 469 470 def contains(self, x, y): 471 """ 472 Returns whether ``(x, y)`` is in the bounding box or on its edge. 473 """ 474 return self.containsx(x) and self.containsy(y) 475 476 def overlaps(self, other): 477 """ 478 Returns whether this bounding box overlaps with the other bounding box. 479 480 Parameters 481 ---------- 482 other : BboxBase 483 """ 484 ax1, ay1, ax2, ay2 = self.extents 485 bx1, by1, bx2, by2 = other.extents 486 if ax2 < ax1: 487 ax2, ax1 = ax1, ax2 488 if ay2 < ay1: 489 ay2, ay1 = ay1, ay2 490 if bx2 < bx1: 491 bx2, bx1 = bx1, bx2 492 if by2 < by1: 493 by2, by1 = by1, by2 494 return ax1 <= bx2 and bx1 <= ax2 and ay1 <= by2 and by1 <= ay2 495 496 def fully_containsx(self, x): 497 """ 498 Returns whether *x* is in the open (:attr:`x0`, :attr:`x1`) interval. 499 """ 500 x0, x1 = self.intervalx 501 return x0 < x < x1 or x0 > x > x1 502 503 def fully_containsy(self, y): 504 """ 505 Returns whether *y* is in the open (:attr:`y0`, :attr:`y1`) interval. 506 """ 507 y0, y1 = self.intervaly 508 return y0 < y < y1 or y0 > y > y1 509 510 def fully_contains(self, x, y): 511 """ 512 Returns whether ``x, y`` is in the bounding box, but not on its edge. 513 """ 514 return self.fully_containsx(x) and self.fully_containsy(y) 515 516 def fully_overlaps(self, other): 517 """ 518 Returns whether this bounding box overlaps with the other bounding box, 519 not including the edges. 520 521 Parameters 522 ---------- 523 other : BboxBase 524 """ 525 ax1, ay1, ax2, ay2 = self.extents 526 bx1, by1, bx2, by2 = other.extents 527 if ax2 < ax1: 528 ax2, ax1 = ax1, ax2 529 if ay2 < ay1: 530 ay2, ay1 = ay1, ay2 531 if bx2 < bx1: 532 bx2, bx1 = bx1, bx2 533 if by2 < by1: 534 by2, by1 = by1, by2 535 return ax1 < bx2 and bx1 < ax2 and ay1 < by2 and by1 < ay2 536 537 def transformed(self, transform): 538 """ 539 Return a new :class:`Bbox` object, statically transformed by 540 the given transform. 541 """ 542 pts = self.get_points() 543 ll, ul, lr = transform.transform(np.array([pts[0], 544 [pts[0, 0], pts[1, 1]], [pts[1, 0], pts[0, 1]]])) 545 return Bbox([ll, [lr[0], ul[1]]]) 546 547 def inverse_transformed(self, transform): 548 """ 549 Return a new :class:`Bbox` object, statically transformed by 550 the inverse of the given transform. 551 """ 552 return self.transformed(transform.inverted()) 553 554 coefs = {'C': (0.5, 0.5), 555 'SW': (0, 0), 556 'S': (0.5, 0), 557 'SE': (1.0, 0), 558 'E': (1.0, 0.5), 559 'NE': (1.0, 1.0), 560 'N': (0.5, 1.0), 561 'NW': (0, 1.0), 562 'W': (0, 0.5)} 563 564 def anchored(self, c, container=None): 565 """ 566 Return a copy of the :class:`Bbox`, shifted to position *c* 567 within a container. 568 569 Parameters 570 ---------- 571 c : 572 May be either: 573 574 * A sequence (*cx*, *cy*) where *cx* and *cy* range from 0 575 to 1, where 0 is left or bottom and 1 is right or top 576 577 * a string: 578 - 'C' for centered 579 - 'S' for bottom-center 580 - 'SE' for bottom-left 581 - 'E' for left 582 - etc. 583 584 container : Bbox, optional 585 The box within which the :class:`Bbox` is positioned; it defaults 586 to the initial :class:`Bbox`. 587 """ 588 if container is None: 589 container = self 590 l, b, w, h = container.bounds 591 if isinstance(c, six.string_types): 592 cx, cy = self.coefs[c] 593 else: 594 cx, cy = c 595 L, B, W, H = self.bounds 596 return Bbox(self._points + 597 [(l + cx * (w - W)) - L, 598 (b + cy * (h - H)) - B]) 599 600 def shrunk(self, mx, my): 601 """ 602 Return a copy of the :class:`Bbox`, shrunk by the factor *mx* 603 in the *x* direction and the factor *my* in the *y* direction. 604 The lower left corner of the box remains unchanged. Normally 605 *mx* and *my* will be less than 1, but this is not enforced. 606 """ 607 w, h = self.size 608 return Bbox([self._points[0], 609 self._points[0] + [mx * w, my * h]]) 610 611 def shrunk_to_aspect(self, box_aspect, container=None, fig_aspect=1.0): 612 """ 613 Return a copy of the :class:`Bbox`, shrunk so that it is as 614 large as it can be while having the desired aspect ratio, 615 *box_aspect*. If the box coordinates are relative---that 616 is, fractions of a larger box such as a figure---then the 617 physical aspect ratio of that figure is specified with 618 *fig_aspect*, so that *box_aspect* can also be given as a 619 ratio of the absolute dimensions, not the relative dimensions. 620 """ 621 if box_aspect <= 0 or fig_aspect <= 0: 622 raise ValueError("'box_aspect' and 'fig_aspect' must be positive") 623 if container is None: 624 container = self 625 w, h = container.size 626 H = w * box_aspect / fig_aspect 627 if H <= h: 628 W = w 629 else: 630 W = h * fig_aspect / box_aspect 631 H = h 632 return Bbox([self._points[0], 633 self._points[0] + (W, H)]) 634 635 def splitx(self, *args): 636 """ 637 e.g., ``bbox.splitx(f1, f2, ...)`` 638 639 Returns a list of new :class:`Bbox` objects formed by 640 splitting the original one with vertical lines at fractional 641 positions *f1*, *f2*, ... 642 """ 643 xf = [0] + list(args) + [1] 644 x0, y0, x1, y1 = self.extents 645 w = x1 - x0 646 return [Bbox([[x0 + xf0 * w, y0], [x0 + xf1 * w, y1]]) 647 for xf0, xf1 in zip(xf[:-1], xf[1:])] 648 649 def splity(self, *args): 650 """ 651 e.g., ``bbox.splitx(f1, f2, ...)`` 652 653 Returns a list of new :class:`Bbox` objects formed by 654 splitting the original one with horizontal lines at fractional 655 positions *f1*, *f2*, ... 656 """ 657 yf = [0] + list(args) + [1] 658 x0, y0, x1, y1 = self.extents 659 h = y1 - y0 660 return [Bbox([[x0, y0 + yf0 * h], [x1, y0 + yf1 * h]]) 661 for yf0, yf1 in zip(yf[:-1], yf[1:])] 662 663 def count_contains(self, vertices): 664 """ 665 Count the number of vertices contained in the :class:`Bbox`. 666 Any vertices with a non-finite x or y value are ignored. 667 668 Parameters 669 ---------- 670 vertices : Nx2 Numpy array. 671 """ 672 if len(vertices) == 0: 673 return 0 674 vertices = np.asarray(vertices) 675 with np.errstate(invalid='ignore'): 676 return (((self.min < vertices) & 677 (vertices < self.max)).all(axis=1).sum()) 678 679 def count_overlaps(self, bboxes): 680 """ 681 Count the number of bounding boxes that overlap this one. 682 683 Parameters 684 ---------- 685 bboxes : sequence of :class:`BboxBase` objects 686 """ 687 return count_bboxes_overlapping_bbox( 688 self, np.atleast_3d([np.array(x) for x in bboxes])) 689 690 def expanded(self, sw, sh): 691 """ 692 Return a new :class:`Bbox` which is this :class:`Bbox` 693 expanded around its center by the given factors *sw* and 694 *sh*. 695 """ 696 width = self.width 697 height = self.height 698 deltaw = (sw * width - width) / 2.0 699 deltah = (sh * height - height) / 2.0 700 a = np.array([[-deltaw, -deltah], [deltaw, deltah]]) 701 return Bbox(self._points + a) 702 703 def padded(self, p): 704 """ 705 Return a new :class:`Bbox` that is padded on all four sides by 706 the given value. 707 """ 708 points = self.get_points() 709 return Bbox(points + [[-p, -p], [p, p]]) 710 711 def translated(self, tx, ty): 712 """ 713 Return a copy of the :class:`Bbox`, statically translated by 714 *tx* and *ty*. 715 """ 716 return Bbox(self._points + (tx, ty)) 717 718 def corners(self): 719 """ 720 Return an array of points which are the four corners of this 721 rectangle. For example, if this :class:`Bbox` is defined by 722 the points (*a*, *b*) and (*c*, *d*), :meth:`corners` returns 723 (*a*, *b*), (*a*, *d*), (*c*, *b*) and (*c*, *d*). 724 """ 725 l, b, r, t = self.get_points().flatten() 726 return np.array([[l, b], [l, t], [r, b], [r, t]]) 727 728 def rotated(self, radians): 729 """ 730 Return a new bounding box that bounds a rotated version of 731 this bounding box by the given radians. The new bounding box 732 is still aligned with the axes, of course. 733 """ 734 corners = self.corners() 735 corners_rotated = Affine2D().rotate(radians).transform(corners) 736 bbox = Bbox.unit() 737 bbox.update_from_data_xy(corners_rotated, ignore=True) 738 return bbox 739 740 @staticmethod 741 def union(bboxes): 742 """ 743 Return a :class:`Bbox` that contains all of the given bboxes. 744 """ 745 if not len(bboxes): 746 raise ValueError("'bboxes' cannot be empty") 747 x0 = np.min([bbox.xmin for bbox in bboxes]) 748 x1 = np.max([bbox.xmax for bbox in bboxes]) 749 y0 = np.min([bbox.ymin for bbox in bboxes]) 750 y1 = np.max([bbox.ymax for bbox in bboxes]) 751 return Bbox([[x0, y0], [x1, y1]]) 752 753 @staticmethod 754 def intersection(bbox1, bbox2): 755 """ 756 Return the intersection of the two bboxes or None 757 if they do not intersect. 758 """ 759 x0 = np.maximum(bbox1.xmin, bbox2.xmin) 760 x1 = np.minimum(bbox1.xmax, bbox2.xmax) 761 y0 = np.maximum(bbox1.ymin, bbox2.ymin) 762 y1 = np.minimum(bbox1.ymax, bbox2.ymax) 763 return Bbox([[x0, y0], [x1, y1]]) if x0 <= x1 and y0 <= y1 else None 764 765 766class Bbox(BboxBase): 767 """ 768 A mutable bounding box. 769 """ 770 771 def __init__(self, points, **kwargs): 772 """ 773 Parameters 774 ---------- 775 points : ndarray 776 A 2x2 numpy array of the form ``[[x0, y0], [x1, y1]]``. 777 778 Notes 779 ----- 780 If you need to create a :class:`Bbox` object from another form 781 of data, consider the static methods :meth:`unit`, 782 :meth:`from_bounds` and :meth:`from_extents`. 783 """ 784 BboxBase.__init__(self, **kwargs) 785 points = np.asarray(points, float) 786 if points.shape != (2, 2): 787 raise ValueError('Bbox points must be of the form ' 788 '"[[x0, y0], [x1, y1]]".') 789 self._points = points 790 self._minpos = np.array([np.inf, np.inf]) 791 self._ignore = True 792 # it is helpful in some contexts to know if the bbox is a 793 # default or has been mutated; we store the orig points to 794 # support the mutated methods 795 self._points_orig = self._points.copy() 796 if DEBUG: 797 ___init__ = __init__ 798 799 def __init__(self, points, **kwargs): 800 self._check(points) 801 self.___init__(points, **kwargs) 802 803 def invalidate(self): 804 self._check(self._points) 805 TransformNode.invalidate(self) 806 807 @staticmethod 808 def unit(): 809 """ 810 (staticmethod) Create a new unit :class:`Bbox` from (0, 0) to 811 (1, 1). 812 """ 813 return Bbox(np.array([[0.0, 0.0], [1.0, 1.0]], float)) 814 815 @staticmethod 816 def null(): 817 """ 818 (staticmethod) Create a new null :class:`Bbox` from (inf, inf) to 819 (-inf, -inf). 820 """ 821 return Bbox(np.array([[np.inf, np.inf], [-np.inf, -np.inf]], float)) 822 823 @staticmethod 824 def from_bounds(x0, y0, width, height): 825 """ 826 (staticmethod) Create a new :class:`Bbox` from *x0*, *y0*, 827 *width* and *height*. 828 829 *width* and *height* may be negative. 830 """ 831 return Bbox.from_extents(x0, y0, x0 + width, y0 + height) 832 833 @staticmethod 834 def from_extents(*args): 835 """ 836 (staticmethod) Create a new Bbox from *left*, *bottom*, 837 *right* and *top*. 838 839 The *y*-axis increases upwards. 840 """ 841 points = np.array(args, dtype=float).reshape(2, 2) 842 return Bbox(points) 843 844 def __format__(self, fmt): 845 return ( 846 'Bbox(x0={0.x0:{1}}, y0={0.y0:{1}}, x1={0.x1:{1}}, y1={0.y1:{1}})'. 847 format(self, fmt)) 848 849 def __str__(self): 850 return format(self, '') 851 852 def __repr__(self): 853 return 'Bbox([[{0.x0}, {0.y0}], [{0.x1}, {0.y1}]])'.format(self) 854 855 def ignore(self, value): 856 """ 857 Set whether the existing bounds of the box should be ignored 858 by subsequent calls to :meth:`update_from_data_xy`. 859 860 value : bool 861 - When ``True``, subsequent calls to :meth:`update_from_data_xy` 862 will ignore the existing bounds of the :class:`Bbox`. 863 864 - When ``False``, subsequent calls to :meth:`update_from_data_xy` 865 will include the existing bounds of the :class:`Bbox`. 866 """ 867 self._ignore = value 868 869 def update_from_path(self, path, ignore=None, updatex=True, updatey=True): 870 """ 871 Update the bounds of the :class:`Bbox` based on the passed in 872 data. After updating, the bounds will have positive *width* 873 and *height*; *x0* and *y0* will be the minimal values. 874 875 Parameters 876 ---------- 877 path : :class:`~matplotlib.path.Path` 878 879 ignore : bool, optional 880 - when ``True``, ignore the existing bounds of the :class:`Bbox`. 881 - when ``False``, include the existing bounds of the :class:`Bbox`. 882 - when ``None``, use the last value passed to :meth:`ignore`. 883 884 updatex, updatey : bool, optional 885 When ``True``, update the x/y values. 886 """ 887 if ignore is None: 888 ignore = self._ignore 889 890 if path.vertices.size == 0: 891 return 892 893 points, minpos, changed = update_path_extents( 894 path, None, self._points, self._minpos, ignore) 895 896 if changed: 897 self.invalidate() 898 if updatex: 899 self._points[:, 0] = points[:, 0] 900 self._minpos[0] = minpos[0] 901 if updatey: 902 self._points[:, 1] = points[:, 1] 903 self._minpos[1] = minpos[1] 904 905 def update_from_data_xy(self, xy, ignore=None, updatex=True, updatey=True): 906 """ 907 Update the bounds of the :class:`Bbox` based on the passed in 908 data. After updating, the bounds will have positive *width* 909 and *height*; *x0* and *y0* will be the minimal values. 910 911 Parameters 912 ---------- 913 xy : ndarray 914 A numpy array of 2D points. 915 916 ignore : bool, optional 917 - When ``True``, ignore the existing bounds of the :class:`Bbox`. 918 - When ``False``, include the existing bounds of the :class:`Bbox`. 919 - When ``None``, use the last value passed to :meth:`ignore`. 920 921 updatex, updatey : bool, optional 922 When ``True``, update the x/y values. 923 """ 924 if len(xy) == 0: 925 return 926 927 path = Path(xy) 928 self.update_from_path(path, ignore=ignore, 929 updatex=updatex, updatey=updatey) 930 931 @BboxBase.x0.setter 932 def x0(self, val): 933 self._points[0, 0] = val 934 self.invalidate() 935 936 @BboxBase.y0.setter 937 def y0(self, val): 938 self._points[0, 1] = val 939 self.invalidate() 940 941 @BboxBase.x1.setter 942 def x1(self, val): 943 self._points[1, 0] = val 944 self.invalidate() 945 946 @BboxBase.y1.setter 947 def y1(self, val): 948 self._points[1, 1] = val 949 self.invalidate() 950 951 @BboxBase.p0.setter 952 def p0(self, val): 953 self._points[0] = val 954 self.invalidate() 955 956 @BboxBase.p1.setter 957 def p1(self, val): 958 self._points[1] = val 959 self.invalidate() 960 961 @BboxBase.intervalx.setter 962 def intervalx(self, interval): 963 self._points[:, 0] = interval 964 self.invalidate() 965 966 @BboxBase.intervaly.setter 967 def intervaly(self, interval): 968 self._points[:, 1] = interval 969 self.invalidate() 970 971 @BboxBase.bounds.setter 972 def bounds(self, bounds): 973 l, b, w, h = bounds 974 points = np.array([[l, b], [l + w, b + h]], float) 975 if np.any(self._points != points): 976 self._points = points 977 self.invalidate() 978 979 @property 980 def minpos(self): 981 return self._minpos 982 983 @property 984 def minposx(self): 985 return self._minpos[0] 986 987 @property 988 def minposy(self): 989 return self._minpos[1] 990 991 def get_points(self): 992 """ 993 Get the points of the bounding box directly as a numpy array 994 of the form: ``[[x0, y0], [x1, y1]]``. 995 """ 996 self._invalid = 0 997 return self._points 998 999 def set_points(self, points): 1000 """ 1001 Set the points of the bounding box directly from a numpy array 1002 of the form: ``[[x0, y0], [x1, y1]]``. No error checking is 1003 performed, as this method is mainly for internal use. 1004 """ 1005 if np.any(self._points != points): 1006 self._points = points 1007 self.invalidate() 1008 1009 def set(self, other): 1010 """ 1011 Set this bounding box from the "frozen" bounds of another 1012 :class:`Bbox`. 1013 """ 1014 if np.any(self._points != other.get_points()): 1015 self._points = other.get_points() 1016 self.invalidate() 1017 1018 def mutated(self): 1019 'Return whether the bbox has changed since init.' 1020 return self.mutatedx() or self.mutatedy() 1021 1022 def mutatedx(self): 1023 'Return whether the x-limits have changed since init.' 1024 return (self._points[0, 0] != self._points_orig[0, 0] or 1025 self._points[1, 0] != self._points_orig[1, 0]) 1026 1027 def mutatedy(self): 1028 'Return whether the y-limits have changed since init.' 1029 return (self._points[0, 1] != self._points_orig[0, 1] or 1030 self._points[1, 1] != self._points_orig[1, 1]) 1031 1032 1033class TransformedBbox(BboxBase): 1034 """ 1035 A :class:`Bbox` that is automatically transformed by a given 1036 transform. When either the child bounding box or transform 1037 changes, the bounds of this bbox will update accordingly. 1038 """ 1039 def __init__(self, bbox, transform, **kwargs): 1040 """ 1041 Parameters 1042 ---------- 1043 bbox : :class:`Bbox` 1044 1045 transform : :class:`Transform` 1046 """ 1047 if not bbox.is_bbox: 1048 raise ValueError("'bbox' is not a bbox") 1049 if not isinstance(transform, Transform): 1050 raise ValueError("'transform' must be an instance of " 1051 "'matplotlib.transform.Transform'") 1052 if transform.input_dims != 2 or transform.output_dims != 2: 1053 raise ValueError( 1054 "The input and output dimensions of 'transform' must be 2") 1055 1056 BboxBase.__init__(self, **kwargs) 1057 self._bbox = bbox 1058 self._transform = transform 1059 self.set_children(bbox, transform) 1060 self._points = None 1061 1062 def __str__(self): 1063 return ("{}(\n" 1064 "{},\n" 1065 "{})" 1066 .format(type(self).__name__, 1067 _indent_str(self._bbox), 1068 _indent_str(self._transform))) 1069 1070 def get_points(self): 1071 if self._invalid: 1072 p = self._bbox.get_points() 1073 # Transform all four points, then make a new bounding box 1074 # from the result, taking care to make the orientation the 1075 # same. 1076 points = self._transform.transform( 1077 [[p[0, 0], p[0, 1]], 1078 [p[1, 0], p[0, 1]], 1079 [p[0, 0], p[1, 1]], 1080 [p[1, 0], p[1, 1]]]) 1081 points = np.ma.filled(points, 0.0) 1082 1083 xs = min(points[:, 0]), max(points[:, 0]) 1084 if p[0, 0] > p[1, 0]: 1085 xs = xs[::-1] 1086 1087 ys = min(points[:, 1]), max(points[:, 1]) 1088 if p[0, 1] > p[1, 1]: 1089 ys = ys[::-1] 1090 1091 self._points = np.array([ 1092 [xs[0], ys[0]], 1093 [xs[1], ys[1]] 1094 ]) 1095 1096 self._invalid = 0 1097 return self._points 1098 get_points.__doc__ = Bbox.get_points.__doc__ 1099 1100 if DEBUG: 1101 _get_points = get_points 1102 1103 def get_points(self): 1104 points = self._get_points() 1105 self._check(points) 1106 return points 1107 1108 1109class LockableBbox(BboxBase): 1110 """ 1111 A :class:`Bbox` where some elements may be locked at certain values. 1112 1113 When the child bounding box changes, the bounds of this bbox will update 1114 accordingly with the exception of the locked elements. 1115 """ 1116 def __init__(self, bbox, x0=None, y0=None, x1=None, y1=None, **kwargs): 1117 """ 1118 Parameters 1119 ---------- 1120 bbox : Bbox 1121 The child bounding box to wrap. 1122 1123 x0 : float or None 1124 The locked value for x0, or None to leave unlocked. 1125 1126 y0 : float or None 1127 The locked value for y0, or None to leave unlocked. 1128 1129 x1 : float or None 1130 The locked value for x1, or None to leave unlocked. 1131 1132 y1 : float or None 1133 The locked value for y1, or None to leave unlocked. 1134 1135 """ 1136 if not bbox.is_bbox: 1137 raise ValueError("'bbox' is not a bbox") 1138 1139 BboxBase.__init__(self, **kwargs) 1140 self._bbox = bbox 1141 self.set_children(bbox) 1142 self._points = None 1143 fp = [x0, y0, x1, y1] 1144 mask = [val is None for val in fp] 1145 self._locked_points = np.ma.array(fp, float, mask=mask).reshape((2, 2)) 1146 1147 def __str__(self): 1148 return ("{}(\n" 1149 "{},\n" 1150 "{})" 1151 .format(type(self).__name__, 1152 _indent_str(self._bbox), 1153 _indent_str(self._locked_points))) 1154 1155 def get_points(self): 1156 if self._invalid: 1157 points = self._bbox.get_points() 1158 self._points = np.where(self._locked_points.mask, 1159 points, 1160 self._locked_points) 1161 self._invalid = 0 1162 return self._points 1163 get_points.__doc__ = Bbox.get_points.__doc__ 1164 1165 if DEBUG: 1166 _get_points = get_points 1167 1168 def get_points(self): 1169 points = self._get_points() 1170 self._check(points) 1171 return points 1172 1173 @property 1174 def locked_x0(self): 1175 """ 1176 float or None: The value used for the locked x0. 1177 """ 1178 if self._locked_points.mask[0, 0]: 1179 return None 1180 else: 1181 return self._locked_points[0, 0] 1182 1183 @locked_x0.setter 1184 def locked_x0(self, x0): 1185 self._locked_points.mask[0, 0] = x0 is None 1186 self._locked_points.data[0, 0] = x0 1187 self.invalidate() 1188 1189 @property 1190 def locked_y0(self): 1191 """ 1192 float or None: The value used for the locked y0. 1193 """ 1194 if self._locked_points.mask[0, 1]: 1195 return None 1196 else: 1197 return self._locked_points[0, 1] 1198 1199 @locked_y0.setter 1200 def locked_y0(self, y0): 1201 self._locked_points.mask[0, 1] = y0 is None 1202 self._locked_points.data[0, 1] = y0 1203 self.invalidate() 1204 1205 @property 1206 def locked_x1(self): 1207 """ 1208 float or None: The value used for the locked x1. 1209 """ 1210 if self._locked_points.mask[1, 0]: 1211 return None 1212 else: 1213 return self._locked_points[1, 0] 1214 1215 @locked_x1.setter 1216 def locked_x1(self, x1): 1217 self._locked_points.mask[1, 0] = x1 is None 1218 self._locked_points.data[1, 0] = x1 1219 self.invalidate() 1220 1221 @property 1222 def locked_y1(self): 1223 """ 1224 float or None: The value used for the locked y1. 1225 """ 1226 if self._locked_points.mask[1, 1]: 1227 return None 1228 else: 1229 return self._locked_points[1, 1] 1230 1231 @locked_y1.setter 1232 def locked_y1(self, y1): 1233 self._locked_points.mask[1, 1] = y1 is None 1234 self._locked_points.data[1, 1] = y1 1235 self.invalidate() 1236 1237 1238class Transform(TransformNode): 1239 """ 1240 The base class of all :class:`TransformNode` instances that 1241 actually perform a transformation. 1242 1243 All non-affine transformations should be subclasses of this class. 1244 New affine transformations should be subclasses of 1245 :class:`Affine2D`. 1246 1247 Subclasses of this class should override the following members (at 1248 minimum): 1249 1250 - :attr:`input_dims` 1251 - :attr:`output_dims` 1252 - :meth:`transform` 1253 - :attr:`is_separable` 1254 - :attr:`has_inverse` 1255 - :meth:`inverted` (if :attr:`has_inverse` is True) 1256 1257 If the transform needs to do something non-standard with 1258 :class:`matplotlib.path.Path` objects, such as adding curves 1259 where there were once line segments, it should override: 1260 1261 - :meth:`transform_path` 1262 """ 1263 input_dims = None 1264 """ 1265 The number of input dimensions of this transform. 1266 Must be overridden (with integers) in the subclass. 1267 """ 1268 1269 output_dims = None 1270 """ 1271 The number of output dimensions of this transform. 1272 Must be overridden (with integers) in the subclass. 1273 """ 1274 1275 has_inverse = False 1276 """True if this transform has a corresponding inverse transform.""" 1277 1278 is_separable = False 1279 """True if this transform is separable in the x- and y- dimensions.""" 1280 1281 def __add__(self, other): 1282 """ 1283 Composes two transforms together such that *self* is followed 1284 by *other*. 1285 """ 1286 if isinstance(other, Transform): 1287 return composite_transform_factory(self, other) 1288 raise TypeError( 1289 "Can not add Transform to object of type '%s'" % type(other)) 1290 1291 def __radd__(self, other): 1292 """ 1293 Composes two transforms together such that *self* is followed 1294 by *other*. 1295 """ 1296 if isinstance(other, Transform): 1297 return composite_transform_factory(other, self) 1298 raise TypeError( 1299 "Can not add Transform to object of type '%s'" % type(other)) 1300 1301 # Equality is based on object identity for `Transform`s (so we don't 1302 # override `__eq__`), but some subclasses, such as TransformWrapper & 1303 # AffineBase, override this behavior. 1304 1305 if six.PY2: 1306 def __ne__(self, other): 1307 return not (self == other) 1308 1309 def _iter_break_from_left_to_right(self): 1310 """ 1311 Returns an iterator breaking down this transform stack from left to 1312 right recursively. If self == ((A, N), A) then the result will be an 1313 iterator which yields I : ((A, N), A), followed by A : (N, A), 1314 followed by (A, N) : (A), but not ((A, N), A) : I. 1315 1316 This is equivalent to flattening the stack then yielding 1317 ``flat_stack[:i], flat_stack[i:]`` where i=0..(n-1). 1318 1319 """ 1320 yield IdentityTransform(), self 1321 1322 @property 1323 def depth(self): 1324 """ 1325 Returns the number of transforms which have been chained 1326 together to form this Transform instance. 1327 1328 .. note:: 1329 1330 For the special case of a Composite transform, the maximum depth 1331 of the two is returned. 1332 1333 """ 1334 return 1 1335 1336 def contains_branch(self, other): 1337 """ 1338 Return whether the given transform is a sub-tree of this transform. 1339 1340 This routine uses transform equality to identify sub-trees, therefore 1341 in many situations it is object id which will be used. 1342 1343 For the case where the given transform represents the whole 1344 of this transform, returns True. 1345 1346 """ 1347 if self.depth < other.depth: 1348 return False 1349 1350 # check that a subtree is equal to other (starting from self) 1351 for _, sub_tree in self._iter_break_from_left_to_right(): 1352 if sub_tree == other: 1353 return True 1354 return False 1355 1356 def contains_branch_seperately(self, other_transform): 1357 """ 1358 Returns whether the given branch is a sub-tree of this transform on 1359 each separate dimension. 1360 1361 A common use for this method is to identify if a transform is a blended 1362 transform containing an axes' data transform. e.g.:: 1363 1364 x_isdata, y_isdata = trans.contains_branch_seperately(ax.transData) 1365 1366 """ 1367 if self.output_dims != 2: 1368 raise ValueError('contains_branch_seperately only supports ' 1369 'transforms with 2 output dimensions') 1370 # for a non-blended transform each separate dimension is the same, so 1371 # just return the appropriate shape. 1372 return [self.contains_branch(other_transform)] * 2 1373 1374 def __sub__(self, other): 1375 """ 1376 Returns a transform stack which goes all the way down self's transform 1377 stack, and then ascends back up other's stack. If it can, this is 1378 optimised:: 1379 1380 # normally 1381 A - B == a + b.inverted() 1382 1383 # sometimes, when A contains the tree B there is no need to 1384 # descend all the way down to the base of A (via B), instead we 1385 # can just stop at B. 1386 1387 (A + B) - (B)^-1 == A 1388 1389 # similarly, when B contains tree A, we can avoid decending A at 1390 # all, basically: 1391 A - (A + B) == ((B + A) - A).inverted() or B^-1 1392 1393 For clarity, the result of ``(A + B) - B + B == (A + B)``. 1394 1395 """ 1396 # we only know how to do this operation if other is a Transform. 1397 if not isinstance(other, Transform): 1398 return NotImplemented 1399 1400 for remainder, sub_tree in self._iter_break_from_left_to_right(): 1401 if sub_tree == other: 1402 return remainder 1403 1404 for remainder, sub_tree in other._iter_break_from_left_to_right(): 1405 if sub_tree == self: 1406 if not remainder.has_inverse: 1407 raise ValueError("The shortcut cannot be computed since " 1408 "other's transform includes a non-invertable component.") 1409 return remainder.inverted() 1410 1411 # if we have got this far, then there was no shortcut possible 1412 if other.has_inverse: 1413 return self + other.inverted() 1414 else: 1415 raise ValueError('It is not possible to compute transA - transB ' 1416 'since transB cannot be inverted and there is no ' 1417 'shortcut possible.') 1418 1419 def __array__(self, *args, **kwargs): 1420 """ 1421 Array interface to get at this Transform's affine matrix. 1422 """ 1423 return self.get_affine().get_matrix() 1424 1425 def transform(self, values): 1426 """ 1427 Performs the transformation on the given array of values. 1428 1429 Accepts a numpy array of shape (N x :attr:`input_dims`) and 1430 returns a numpy array of shape (N x :attr:`output_dims`). 1431 1432 Alternatively, accepts a numpy array of length :attr:`input_dims` 1433 and returns a numpy array of length :attr:`output_dims`. 1434 """ 1435 # Ensure that values is a 2d array (but remember whether 1436 # we started with a 1d or 2d array). 1437 values = np.asanyarray(values) 1438 ndim = values.ndim 1439 values = values.reshape((-1, self.input_dims)) 1440 1441 # Transform the values 1442 res = self.transform_affine(self.transform_non_affine(values)) 1443 1444 # Convert the result back to the shape of the input values. 1445 if ndim == 0: 1446 assert not np.ma.is_masked(res) # just to be on the safe side 1447 return res[0, 0] 1448 if ndim == 1: 1449 return res.reshape(-1) 1450 elif ndim == 2: 1451 return res 1452 raise ValueError( 1453 "Input values must have shape (N x {dims}) " 1454 "or ({dims}).".format(dims=self.input_dims)) 1455 1456 def transform_affine(self, values): 1457 """ 1458 Performs only the affine part of this transformation on the 1459 given array of values. 1460 1461 ``transform(values)`` is always equivalent to 1462 ``transform_affine(transform_non_affine(values))``. 1463 1464 In non-affine transformations, this is generally a no-op. In 1465 affine transformations, this is equivalent to 1466 ``transform(values)``. 1467 1468 Accepts a numpy array of shape (N x :attr:`input_dims`) and 1469 returns a numpy array of shape (N x :attr:`output_dims`). 1470 1471 Alternatively, accepts a numpy array of length :attr:`input_dims` 1472 and returns a numpy array of length :attr:`output_dims`. 1473 """ 1474 return self.get_affine().transform(values) 1475 1476 def transform_non_affine(self, values): 1477 """ 1478 Performs only the non-affine part of the transformation. 1479 1480 ``transform(values)`` is always equivalent to 1481 ``transform_affine(transform_non_affine(values))``. 1482 1483 In non-affine transformations, this is generally equivalent to 1484 ``transform(values)``. In affine transformations, this is 1485 always a no-op. 1486 1487 Accepts a numpy array of shape (N x :attr:`input_dims`) and 1488 returns a numpy array of shape (N x :attr:`output_dims`). 1489 1490 Alternatively, accepts a numpy array of length :attr:`input_dims` 1491 and returns a numpy array of length :attr:`output_dims`. 1492 """ 1493 return values 1494 1495 def transform_bbox(self, bbox): 1496 """ 1497 Transform the given bounding box. 1498 1499 Note, for smarter transforms including caching (a common 1500 requirement for matplotlib figures), see :class:`TransformedBbox`. 1501 """ 1502 return Bbox(self.transform(bbox.get_points())) 1503 1504 def get_affine(self): 1505 """ 1506 Get the affine part of this transform. 1507 """ 1508 return IdentityTransform() 1509 1510 def get_matrix(self): 1511 """ 1512 Get the Affine transformation array for the affine part 1513 of this transform. 1514 1515 """ 1516 return self.get_affine().get_matrix() 1517 1518 def transform_point(self, point): 1519 """ 1520 A convenience function that returns the transformed copy of a 1521 single point. 1522 1523 The point is given as a sequence of length :attr:`input_dims`. 1524 The transformed point is returned as a sequence of length 1525 :attr:`output_dims`. 1526 """ 1527 if len(point) != self.input_dims: 1528 raise ValueError("The length of 'point' must be 'self.input_dims'") 1529 return self.transform(np.asarray([point]))[0] 1530 1531 def transform_path(self, path): 1532 """ 1533 Returns a transformed path. 1534 1535 *path*: a :class:`~matplotlib.path.Path` instance. 1536 1537 In some cases, this transform may insert curves into the path 1538 that began as line segments. 1539 """ 1540 return self.transform_path_affine(self.transform_path_non_affine(path)) 1541 1542 def transform_path_affine(self, path): 1543 """ 1544 Returns a path, transformed only by the affine part of 1545 this transform. 1546 1547 *path*: a :class:`~matplotlib.path.Path` instance. 1548 1549 ``transform_path(path)`` is equivalent to 1550 ``transform_path_affine(transform_path_non_affine(values))``. 1551 """ 1552 return self.get_affine().transform_path_affine(path) 1553 1554 def transform_path_non_affine(self, path): 1555 """ 1556 Returns a path, transformed only by the non-affine 1557 part of this transform. 1558 1559 *path*: a :class:`~matplotlib.path.Path` instance. 1560 1561 ``transform_path(path)`` is equivalent to 1562 ``transform_path_affine(transform_path_non_affine(values))``. 1563 """ 1564 x = self.transform_non_affine(path.vertices) 1565 return Path._fast_from_codes_and_verts(x, path.codes, 1566 {'interpolation_steps': path._interpolation_steps, 1567 'should_simplify': path.should_simplify}) 1568 1569 def transform_angles(self, angles, pts, radians=False, pushoff=1e-5): 1570 """ 1571 Performs transformation on a set of angles anchored at 1572 specific locations. 1573 1574 The *angles* must be a column vector (i.e., numpy array). 1575 1576 The *pts* must be a two-column numpy array of x,y positions 1577 (angle transforms currently only work in 2D). This array must 1578 have the same number of rows as *angles*. 1579 1580 *radians* indicates whether or not input angles are given in 1581 radians (True) or degrees (False; the default). 1582 1583 *pushoff* is the distance to move away from *pts* for 1584 determining transformed angles (see discussion of method 1585 below). 1586 1587 The transformed angles are returned in an array with the same 1588 size as *angles*. 1589 1590 The generic version of this method uses a very generic 1591 algorithm that transforms *pts*, as well as locations very 1592 close to *pts*, to find the angle in the transformed system. 1593 """ 1594 # Must be 2D 1595 if self.input_dims != 2 or self.output_dims != 2: 1596 raise NotImplementedError('Only defined in 2D') 1597 1598 if pts.shape[1] != 2: 1599 raise ValueError("'pts' must be array with 2 columns for x,y") 1600 1601 if angles.ndim != 1 or angles.shape[0] != pts.shape[0]: 1602 raise ValueError("'angles' must be a column vector and have same " 1603 "number of rows as 'pts'") 1604 1605 # Convert to radians if desired 1606 if not radians: 1607 angles = angles / 180.0 * np.pi 1608 1609 # Move a short distance away 1610 pts2 = pts + pushoff * np.c_[np.cos(angles), np.sin(angles)] 1611 1612 # Transform both sets of points 1613 tpts = self.transform(pts) 1614 tpts2 = self.transform(pts2) 1615 1616 # Calculate transformed angles 1617 d = tpts2 - tpts 1618 a = np.arctan2(d[:, 1], d[:, 0]) 1619 1620 # Convert back to degrees if desired 1621 if not radians: 1622 a = np.rad2deg(a) 1623 1624 return a 1625 1626 def inverted(self): 1627 """ 1628 Return the corresponding inverse transformation. 1629 1630 The return value of this method should be treated as 1631 temporary. An update to *self* does not cause a corresponding 1632 update to its inverted copy. 1633 1634 ``x === self.inverted().transform(self.transform(x))`` 1635 """ 1636 raise NotImplementedError() 1637 1638 1639class TransformWrapper(Transform): 1640 """ 1641 A helper class that holds a single child transform and acts 1642 equivalently to it. 1643 1644 This is useful if a node of the transform tree must be replaced at 1645 run time with a transform of a different type. This class allows 1646 that replacement to correctly trigger invalidation. 1647 1648 Note that :class:`TransformWrapper` instances must have the same 1649 input and output dimensions during their entire lifetime, so the 1650 child transform may only be replaced with another child transform 1651 of the same dimensions. 1652 """ 1653 pass_through = True 1654 1655 def __init__(self, child): 1656 """ 1657 *child*: A class:`Transform` instance. This child may later 1658 be replaced with :meth:`set`. 1659 """ 1660 if not isinstance(child, Transform): 1661 raise ValueError("'child' must be an instance of " 1662 "'matplotlib.transform.Transform'") 1663 self._init(child) 1664 self.set_children(child) 1665 1666 def _init(self, child): 1667 Transform.__init__(self) 1668 self.input_dims = child.input_dims 1669 self.output_dims = child.output_dims 1670 self._set(child) 1671 self._invalid = 0 1672 1673 def __eq__(self, other): 1674 return self._child.__eq__(other) 1675 1676 # NOTE: Transform.__[gs]etstate__ should be sufficient when using only 1677 # Python 3.4+. 1678 def __getstate__(self): 1679 # only store the child information and parents 1680 return { 1681 'child': self._child, 1682 'input_dims': self.input_dims, 1683 'output_dims': self.output_dims, 1684 # turn the weak-values dictionary into a normal dictionary 1685 'parents': dict((k, v()) for (k, v) in 1686 six.iteritems(self._parents)) 1687 } 1688 1689 def __setstate__(self, state): 1690 # re-initialise the TransformWrapper with the state's child 1691 self._init(state['child']) 1692 # The child may not be unpickled yet, so restore its information. 1693 self.input_dims = state['input_dims'] 1694 self.output_dims = state['output_dims'] 1695 # turn the normal dictionary back into a dictionary with weak 1696 # values 1697 self._parents = dict((k, weakref.ref(v)) for (k, v) in 1698 six.iteritems(state['parents']) if v is not None) 1699 1700 def __str__(self): 1701 return ("{}(\n" 1702 "{})" 1703 .format(type(self).__name__, 1704 _indent_str(self._child))) 1705 1706 def frozen(self): 1707 return self._child.frozen() 1708 frozen.__doc__ = Transform.frozen.__doc__ 1709 1710 def _set(self, child): 1711 self._child = child 1712 1713 self.transform = child.transform 1714 self.transform_affine = child.transform_affine 1715 self.transform_non_affine = child.transform_non_affine 1716 self.transform_path = child.transform_path 1717 self.transform_path_affine = child.transform_path_affine 1718 self.transform_path_non_affine = child.transform_path_non_affine 1719 self.get_affine = child.get_affine 1720 self.inverted = child.inverted 1721 self.get_matrix = child.get_matrix 1722 1723 # note we do not wrap other properties here since the transform's 1724 # child can be changed with WrappedTransform.set and so checking 1725 # is_affine and other such properties may be dangerous. 1726 1727 def set(self, child): 1728 """ 1729 Replace the current child of this transform with another one. 1730 1731 The new child must have the same number of input and output 1732 dimensions as the current child. 1733 """ 1734 if (child.input_dims != self.input_dims or 1735 child.output_dims != self.output_dims): 1736 raise ValueError( 1737 "The new child must have the same number of input and output " 1738 "dimensions as the current child") 1739 1740 self.set_children(child) 1741 self._set(child) 1742 1743 self._invalid = 0 1744 self.invalidate() 1745 self._invalid = 0 1746 1747 def _get_is_affine(self): 1748 return self._child.is_affine 1749 is_affine = property(_get_is_affine) 1750 1751 def _get_is_separable(self): 1752 return self._child.is_separable 1753 is_separable = property(_get_is_separable) 1754 1755 def _get_has_inverse(self): 1756 return self._child.has_inverse 1757 has_inverse = property(_get_has_inverse) 1758 1759 1760class AffineBase(Transform): 1761 """ 1762 The base class of all affine transformations of any number of 1763 dimensions. 1764 """ 1765 is_affine = True 1766 1767 def __init__(self, *args, **kwargs): 1768 Transform.__init__(self, *args, **kwargs) 1769 self._inverted = None 1770 1771 def __array__(self, *args, **kwargs): 1772 # optimises the access of the transform matrix vs the superclass 1773 return self.get_matrix() 1774 1775 @staticmethod 1776 def _concat(a, b): 1777 """ 1778 Concatenates two transformation matrices (represented as numpy 1779 arrays) together. 1780 """ 1781 return np.dot(b, a) 1782 1783 def __eq__(self, other): 1784 if getattr(other, "is_affine", False): 1785 return np.all(self.get_matrix() == other.get_matrix()) 1786 return NotImplemented 1787 1788 def transform(self, values): 1789 return self.transform_affine(values) 1790 transform.__doc__ = Transform.transform.__doc__ 1791 1792 def transform_affine(self, values): 1793 raise NotImplementedError('Affine subclasses should override this ' 1794 'method.') 1795 transform_affine.__doc__ = Transform.transform_affine.__doc__ 1796 1797 def transform_non_affine(self, points): 1798 return points 1799 transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ 1800 1801 def transform_path(self, path): 1802 return self.transform_path_affine(path) 1803 transform_path.__doc__ = Transform.transform_path.__doc__ 1804 1805 def transform_path_affine(self, path): 1806 return Path(self.transform_affine(path.vertices), 1807 path.codes, path._interpolation_steps) 1808 transform_path_affine.__doc__ = Transform.transform_path_affine.__doc__ 1809 1810 def transform_path_non_affine(self, path): 1811 return path 1812 transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ 1813 1814 def get_affine(self): 1815 return self 1816 get_affine.__doc__ = Transform.get_affine.__doc__ 1817 1818 1819class Affine2DBase(AffineBase): 1820 """ 1821 The base class of all 2D affine transformations. 1822 1823 2D affine transformations are performed using a 3x3 numpy array:: 1824 1825 a c e 1826 b d f 1827 0 0 1 1828 1829 This class provides the read-only interface. For a mutable 2D 1830 affine transformation, use :class:`Affine2D`. 1831 1832 Subclasses of this class will generally only need to override a 1833 constructor and :meth:`get_matrix` that generates a custom 3x3 matrix. 1834 """ 1835 has_inverse = True 1836 1837 input_dims = 2 1838 output_dims = 2 1839 1840 def frozen(self): 1841 return Affine2D(self.get_matrix().copy()) 1842 frozen.__doc__ = AffineBase.frozen.__doc__ 1843 1844 def _get_is_separable(self): 1845 mtx = self.get_matrix() 1846 return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 1847 is_separable = property(_get_is_separable) 1848 1849 def to_values(self): 1850 """ 1851 Return the values of the matrix as a sequence (a,b,c,d,e,f) 1852 """ 1853 mtx = self.get_matrix() 1854 return tuple(mtx[:2].swapaxes(0, 1).flatten()) 1855 1856 @staticmethod 1857 def matrix_from_values(a, b, c, d, e, f): 1858 """ 1859 (staticmethod) Create a new transformation matrix as a 3x3 1860 numpy array of the form:: 1861 1862 a c e 1863 b d f 1864 0 0 1 1865 """ 1866 return np.array([[a, c, e], [b, d, f], [0.0, 0.0, 1.0]], float) 1867 1868 def transform_affine(self, points): 1869 mtx = self.get_matrix() 1870 if isinstance(points, np.ma.MaskedArray): 1871 tpoints = affine_transform(points.data, mtx) 1872 return np.ma.MaskedArray(tpoints, mask=np.ma.getmask(points)) 1873 return affine_transform(points, mtx) 1874 1875 def transform_point(self, point): 1876 mtx = self.get_matrix() 1877 return affine_transform([point], mtx)[0] 1878 transform_point.__doc__ = AffineBase.transform_point.__doc__ 1879 1880 if DEBUG: 1881 _transform_affine = transform_affine 1882 1883 def transform_affine(self, points): 1884 # The major speed trap here is just converting to the 1885 # points to an array in the first place. If we can use 1886 # more arrays upstream, that should help here. 1887 if not isinstance(points, (np.ma.MaskedArray, np.ndarray)): 1888 warnings.warn( 1889 ('A non-numpy array of type %s was passed in for ' + 1890 'transformation. Please correct this.') 1891 % type(points)) 1892 return self._transform_affine(points) 1893 transform_affine.__doc__ = AffineBase.transform_affine.__doc__ 1894 1895 def inverted(self): 1896 if self._inverted is None or self._invalid: 1897 mtx = self.get_matrix() 1898 shorthand_name = None 1899 if self._shorthand_name: 1900 shorthand_name = '(%s)-1' % self._shorthand_name 1901 self._inverted = Affine2D(inv(mtx), shorthand_name=shorthand_name) 1902 self._invalid = 0 1903 return self._inverted 1904 inverted.__doc__ = AffineBase.inverted.__doc__ 1905 1906 1907class Affine2D(Affine2DBase): 1908 """ 1909 A mutable 2D affine transformation. 1910 """ 1911 1912 def __init__(self, matrix=None, **kwargs): 1913 """ 1914 Initialize an Affine transform from a 3x3 numpy float array:: 1915 1916 a c e 1917 b d f 1918 0 0 1 1919 1920 If *matrix* is None, initialize with the identity transform. 1921 """ 1922 Affine2DBase.__init__(self, **kwargs) 1923 if matrix is None: 1924 # A bit faster than np.identity(3). 1925 matrix = IdentityTransform._mtx.copy() 1926 self._mtx = matrix 1927 self._invalid = 0 1928 1929 def __str__(self): 1930 return ("{}(\n" 1931 "{})" 1932 .format(type(self).__name__, 1933 _indent_str(self._mtx))) 1934 1935 @staticmethod 1936 def from_values(a, b, c, d, e, f): 1937 """ 1938 (staticmethod) Create a new Affine2D instance from the given 1939 values:: 1940 1941 a c e 1942 b d f 1943 0 0 1 1944 1945 . 1946 """ 1947 return Affine2D( 1948 np.array([a, c, e, b, d, f, 0.0, 0.0, 1.0], float).reshape((3, 3))) 1949 1950 def get_matrix(self): 1951 """ 1952 Get the underlying transformation matrix as a 3x3 numpy array:: 1953 1954 a c e 1955 b d f 1956 0 0 1 1957 1958 . 1959 """ 1960 self._invalid = 0 1961 return self._mtx 1962 1963 def set_matrix(self, mtx): 1964 """ 1965 Set the underlying transformation matrix from a 3x3 numpy array:: 1966 1967 a c e 1968 b d f 1969 0 0 1 1970 1971 . 1972 """ 1973 self._mtx = mtx 1974 self.invalidate() 1975 1976 def set(self, other): 1977 """ 1978 Set this transformation from the frozen copy of another 1979 :class:`Affine2DBase` object. 1980 """ 1981 if not isinstance(other, Affine2DBase): 1982 raise ValueError("'other' must be an instance of " 1983 "'matplotlib.transform.Affine2DBase'") 1984 self._mtx = other.get_matrix() 1985 self.invalidate() 1986 1987 @staticmethod 1988 def identity(): 1989 """ 1990 (staticmethod) Return a new :class:`Affine2D` object that is 1991 the identity transform. 1992 1993 Unless this transform will be mutated later on, consider using 1994 the faster :class:`IdentityTransform` class instead. 1995 """ 1996 return Affine2D() 1997 1998 def clear(self): 1999 """ 2000 Reset the underlying matrix to the identity transform. 2001 """ 2002 # A bit faster than np.identity(3). 2003 self._mtx = IdentityTransform._mtx.copy() 2004 self.invalidate() 2005 return self 2006 2007 def rotate(self, theta): 2008 """ 2009 Add a rotation (in radians) to this transform in place. 2010 2011 Returns *self*, so this method can easily be chained with more 2012 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2013 and :meth:`scale`. 2014 """ 2015 a = np.cos(theta) 2016 b = np.sin(theta) 2017 rotate_mtx = np.array([[a, -b, 0.0], [b, a, 0.0], [0.0, 0.0, 1.0]], 2018 float) 2019 self._mtx = np.dot(rotate_mtx, self._mtx) 2020 self.invalidate() 2021 return self 2022 2023 def rotate_deg(self, degrees): 2024 """ 2025 Add a rotation (in degrees) to this transform in place. 2026 2027 Returns *self*, so this method can easily be chained with more 2028 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2029 and :meth:`scale`. 2030 """ 2031 return self.rotate(np.deg2rad(degrees)) 2032 2033 def rotate_around(self, x, y, theta): 2034 """ 2035 Add a rotation (in radians) around the point (x, y) in place. 2036 2037 Returns *self*, so this method can easily be chained with more 2038 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2039 and :meth:`scale`. 2040 """ 2041 return self.translate(-x, -y).rotate(theta).translate(x, y) 2042 2043 def rotate_deg_around(self, x, y, degrees): 2044 """ 2045 Add a rotation (in degrees) around the point (x, y) in place. 2046 2047 Returns *self*, so this method can easily be chained with more 2048 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2049 and :meth:`scale`. 2050 """ 2051 # Cast to float to avoid wraparound issues with uint8's 2052 x, y = float(x), float(y) 2053 return self.translate(-x, -y).rotate_deg(degrees).translate(x, y) 2054 2055 def translate(self, tx, ty): 2056 """ 2057 Adds a translation in place. 2058 2059 Returns *self*, so this method can easily be chained with more 2060 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2061 and :meth:`scale`. 2062 """ 2063 translate_mtx = np.array( 2064 [[1.0, 0.0, tx], [0.0, 1.0, ty], [0.0, 0.0, 1.0]], float) 2065 self._mtx = np.dot(translate_mtx, self._mtx) 2066 self.invalidate() 2067 return self 2068 2069 def scale(self, sx, sy=None): 2070 """ 2071 Adds a scale in place. 2072 2073 If *sy* is None, the same scale is applied in both the *x*- and 2074 *y*-directions. 2075 2076 Returns *self*, so this method can easily be chained with more 2077 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2078 and :meth:`scale`. 2079 """ 2080 if sy is None: 2081 sy = sx 2082 scale_mtx = np.array( 2083 [[sx, 0.0, 0.0], [0.0, sy, 0.0], [0.0, 0.0, 1.0]], float) 2084 self._mtx = np.dot(scale_mtx, self._mtx) 2085 self.invalidate() 2086 return self 2087 2088 def skew(self, xShear, yShear): 2089 """ 2090 Adds a skew in place. 2091 2092 *xShear* and *yShear* are the shear angles along the *x*- and 2093 *y*-axes, respectively, in radians. 2094 2095 Returns *self*, so this method can easily be chained with more 2096 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2097 and :meth:`scale`. 2098 """ 2099 rotX = np.tan(xShear) 2100 rotY = np.tan(yShear) 2101 skew_mtx = np.array( 2102 [[1.0, rotX, 0.0], [rotY, 1.0, 0.0], [0.0, 0.0, 1.0]], float) 2103 self._mtx = np.dot(skew_mtx, self._mtx) 2104 self.invalidate() 2105 return self 2106 2107 def skew_deg(self, xShear, yShear): 2108 """ 2109 Adds a skew in place. 2110 2111 *xShear* and *yShear* are the shear angles along the *x*- and 2112 *y*-axes, respectively, in degrees. 2113 2114 Returns *self*, so this method can easily be chained with more 2115 calls to :meth:`rotate`, :meth:`rotate_deg`, :meth:`translate` 2116 and :meth:`scale`. 2117 """ 2118 return self.skew(np.deg2rad(xShear), np.deg2rad(yShear)) 2119 2120 def _get_is_separable(self): 2121 mtx = self.get_matrix() 2122 return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 2123 is_separable = property(_get_is_separable) 2124 2125 2126class IdentityTransform(Affine2DBase): 2127 """ 2128 A special class that does one thing, the identity transform, in a 2129 fast way. 2130 """ 2131 _mtx = np.identity(3) 2132 2133 def frozen(self): 2134 return self 2135 frozen.__doc__ = Affine2DBase.frozen.__doc__ 2136 2137 def __str__(self): 2138 return ("{}()" 2139 .format(type(self).__name__)) 2140 2141 def get_matrix(self): 2142 return self._mtx 2143 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2144 2145 def transform(self, points): 2146 return np.asanyarray(points) 2147 transform.__doc__ = Affine2DBase.transform.__doc__ 2148 2149 transform_affine = transform 2150 transform_affine.__doc__ = Affine2DBase.transform_affine.__doc__ 2151 2152 transform_non_affine = transform 2153 transform_non_affine.__doc__ = Affine2DBase.transform_non_affine.__doc__ 2154 2155 def transform_path(self, path): 2156 return path 2157 transform_path.__doc__ = Affine2DBase.transform_path.__doc__ 2158 2159 transform_path_affine = transform_path 2160 transform_path_affine.__doc__ = Affine2DBase.transform_path_affine.__doc__ 2161 2162 transform_path_non_affine = transform_path 2163 transform_path_non_affine.__doc__ = Affine2DBase.transform_path_non_affine.__doc__ 2164 2165 def get_affine(self): 2166 return self 2167 get_affine.__doc__ = Affine2DBase.get_affine.__doc__ 2168 2169 inverted = get_affine 2170 inverted.__doc__ = Affine2DBase.inverted.__doc__ 2171 2172 2173class BlendedGenericTransform(Transform): 2174 """ 2175 A "blended" transform uses one transform for the *x*-direction, and 2176 another transform for the *y*-direction. 2177 2178 This "generic" version can handle any given child transform in the 2179 *x*- and *y*-directions. 2180 """ 2181 input_dims = 2 2182 output_dims = 2 2183 is_separable = True 2184 pass_through = True 2185 2186 def __init__(self, x_transform, y_transform, **kwargs): 2187 """ 2188 Create a new "blended" transform using *x_transform* to 2189 transform the *x*-axis and *y_transform* to transform the 2190 *y*-axis. 2191 2192 You will generally not call this constructor directly but use 2193 the :func:`blended_transform_factory` function instead, which 2194 can determine automatically which kind of blended transform to 2195 create. 2196 """ 2197 # Here we ask: "Does it blend?" 2198 2199 Transform.__init__(self, **kwargs) 2200 self._x = x_transform 2201 self._y = y_transform 2202 self.set_children(x_transform, y_transform) 2203 self._affine = None 2204 2205 def __eq__(self, other): 2206 # Note, this is an exact copy of BlendedAffine2D.__eq__ 2207 if isinstance(other, (BlendedAffine2D, BlendedGenericTransform)): 2208 return (self._x == other._x) and (self._y == other._y) 2209 elif self._x == self._y: 2210 return self._x == other 2211 else: 2212 return NotImplemented 2213 2214 def contains_branch_seperately(self, transform): 2215 # Note, this is an exact copy of BlendedAffine2D.contains_branch_seperately 2216 return self._x.contains_branch(transform), self._y.contains_branch(transform) 2217 2218 @property 2219 def depth(self): 2220 return max(self._x.depth, self._y.depth) 2221 2222 def contains_branch(self, other): 2223 # a blended transform cannot possibly contain a branch from two different transforms. 2224 return False 2225 2226 def _get_is_affine(self): 2227 return self._x.is_affine and self._y.is_affine 2228 is_affine = property(_get_is_affine) 2229 2230 def _get_has_inverse(self): 2231 return self._x.has_inverse and self._y.has_inverse 2232 has_inverse = property(_get_has_inverse) 2233 2234 def frozen(self): 2235 return blended_transform_factory(self._x.frozen(), self._y.frozen()) 2236 frozen.__doc__ = Transform.frozen.__doc__ 2237 2238 def __str__(self): 2239 return ("{}(\n" 2240 "{},\n" 2241 "{})" 2242 .format(type(self).__name__, 2243 _indent_str(self._x), 2244 _indent_str(self._y))) 2245 2246 def transform_non_affine(self, points): 2247 if self._x.is_affine and self._y.is_affine: 2248 return points 2249 x = self._x 2250 y = self._y 2251 2252 if x == y and x.input_dims == 2: 2253 return x.transform_non_affine(points) 2254 2255 if x.input_dims == 2: 2256 x_points = x.transform_non_affine(points)[:, 0:1] 2257 else: 2258 x_points = x.transform_non_affine(points[:, 0]) 2259 x_points = x_points.reshape((len(x_points), 1)) 2260 2261 if y.input_dims == 2: 2262 y_points = y.transform_non_affine(points)[:, 1:] 2263 else: 2264 y_points = y.transform_non_affine(points[:, 1]) 2265 y_points = y_points.reshape((len(y_points), 1)) 2266 2267 if (isinstance(x_points, np.ma.MaskedArray) or 2268 isinstance(y_points, np.ma.MaskedArray)): 2269 return np.ma.concatenate((x_points, y_points), 1) 2270 else: 2271 return np.concatenate((x_points, y_points), 1) 2272 transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ 2273 2274 def inverted(self): 2275 return BlendedGenericTransform(self._x.inverted(), self._y.inverted()) 2276 inverted.__doc__ = Transform.inverted.__doc__ 2277 2278 def get_affine(self): 2279 if self._invalid or self._affine is None: 2280 if self._x == self._y: 2281 self._affine = self._x.get_affine() 2282 else: 2283 x_mtx = self._x.get_affine().get_matrix() 2284 y_mtx = self._y.get_affine().get_matrix() 2285 # This works because we already know the transforms are 2286 # separable, though normally one would want to set b and 2287 # c to zero. 2288 mtx = np.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) 2289 self._affine = Affine2D(mtx) 2290 self._invalid = 0 2291 return self._affine 2292 get_affine.__doc__ = Transform.get_affine.__doc__ 2293 2294 2295class BlendedAffine2D(Affine2DBase): 2296 """ 2297 A "blended" transform uses one transform for the *x*-direction, and 2298 another transform for the *y*-direction. 2299 2300 This version is an optimization for the case where both child 2301 transforms are of type :class:`Affine2DBase`. 2302 """ 2303 is_separable = True 2304 2305 def __init__(self, x_transform, y_transform, **kwargs): 2306 """ 2307 Create a new "blended" transform using *x_transform* to 2308 transform the *x*-axis and *y_transform* to transform the 2309 *y*-axis. 2310 2311 Both *x_transform* and *y_transform* must be 2D affine 2312 transforms. 2313 2314 You will generally not call this constructor directly but use 2315 the :func:`blended_transform_factory` function instead, which 2316 can determine automatically which kind of blended transform to 2317 create. 2318 """ 2319 is_affine = x_transform.is_affine and y_transform.is_affine 2320 is_separable = x_transform.is_separable and y_transform.is_separable 2321 is_correct = is_affine and is_separable 2322 if not is_correct: 2323 raise ValueError("Both *x_transform* and *y_transform* must be 2D " 2324 "affine transforms") 2325 2326 Transform.__init__(self, **kwargs) 2327 self._x = x_transform 2328 self._y = y_transform 2329 self.set_children(x_transform, y_transform) 2330 2331 Affine2DBase.__init__(self) 2332 self._mtx = None 2333 2334 def __eq__(self, other): 2335 # Note, this is an exact copy of BlendedGenericTransform.__eq__ 2336 if isinstance(other, (BlendedAffine2D, BlendedGenericTransform)): 2337 return (self._x == other._x) and (self._y == other._y) 2338 elif self._x == self._y: 2339 return self._x == other 2340 else: 2341 return NotImplemented 2342 2343 def contains_branch_seperately(self, transform): 2344 # Note, this is an exact copy of BlendedTransform.contains_branch_seperately 2345 return self._x.contains_branch(transform), self._y.contains_branch(transform) 2346 2347 def __str__(self): 2348 return ("{}(\n" 2349 "{},\n" 2350 "{})" 2351 .format(type(self).__name__, 2352 _indent_str(self._x), 2353 _indent_str(self._y))) 2354 2355 def get_matrix(self): 2356 if self._invalid: 2357 if self._x == self._y: 2358 self._mtx = self._x.get_matrix() 2359 else: 2360 x_mtx = self._x.get_matrix() 2361 y_mtx = self._y.get_matrix() 2362 # This works because we already know the transforms are 2363 # separable, though normally one would want to set b and 2364 # c to zero. 2365 self._mtx = np.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) 2366 self._inverted = None 2367 self._invalid = 0 2368 return self._mtx 2369 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2370 2371 2372def blended_transform_factory(x_transform, y_transform): 2373 """ 2374 Create a new "blended" transform using *x_transform* to transform 2375 the *x*-axis and *y_transform* to transform the *y*-axis. 2376 2377 A faster version of the blended transform is returned for the case 2378 where both child transforms are affine. 2379 """ 2380 if (isinstance(x_transform, Affine2DBase) 2381 and isinstance(y_transform, Affine2DBase)): 2382 return BlendedAffine2D(x_transform, y_transform) 2383 return BlendedGenericTransform(x_transform, y_transform) 2384 2385 2386class CompositeGenericTransform(Transform): 2387 """ 2388 A composite transform formed by applying transform *a* then 2389 transform *b*. 2390 2391 This "generic" version can handle any two arbitrary 2392 transformations. 2393 """ 2394 pass_through = True 2395 2396 def __init__(self, a, b, **kwargs): 2397 """ 2398 Create a new composite transform that is the result of 2399 applying transform *a* then transform *b*. 2400 2401 You will generally not call this constructor directly but use 2402 the :func:`composite_transform_factory` function instead, 2403 which can automatically choose the best kind of composite 2404 transform instance to create. 2405 """ 2406 if a.output_dims != b.input_dims: 2407 raise ValueError("The output dimension of 'a' must be equal to " 2408 "the input dimensions of 'b'") 2409 self.input_dims = a.input_dims 2410 self.output_dims = b.output_dims 2411 2412 Transform.__init__(self, **kwargs) 2413 self._a = a 2414 self._b = b 2415 self.set_children(a, b) 2416 2417 is_affine = property(lambda self: self._a.is_affine and self._b.is_affine) 2418 2419 def frozen(self): 2420 self._invalid = 0 2421 frozen = composite_transform_factory(self._a.frozen(), self._b.frozen()) 2422 if not isinstance(frozen, CompositeGenericTransform): 2423 return frozen.frozen() 2424 return frozen 2425 frozen.__doc__ = Transform.frozen.__doc__ 2426 2427 def _invalidate_internal(self, value, invalidating_node): 2428 # In some cases for a composite transform, an invalidating call to AFFINE_ONLY needs 2429 # to be extended to invalidate the NON_AFFINE part too. These cases are when the right 2430 # hand transform is non-affine and either: 2431 # (a) the left hand transform is non affine 2432 # (b) it is the left hand node which has triggered the invalidation 2433 if value == Transform.INVALID_AFFINE \ 2434 and not self._b.is_affine \ 2435 and (not self._a.is_affine or invalidating_node is self._a): 2436 2437 value = Transform.INVALID 2438 2439 Transform._invalidate_internal(self, value=value, 2440 invalidating_node=invalidating_node) 2441 2442 def __eq__(self, other): 2443 if isinstance(other, (CompositeGenericTransform, CompositeAffine2D)): 2444 return self is other or (self._a == other._a and self._b == other._b) 2445 else: 2446 return False 2447 2448 def _iter_break_from_left_to_right(self): 2449 for lh_compliment, rh_compliment in self._a._iter_break_from_left_to_right(): 2450 yield lh_compliment, rh_compliment + self._b 2451 for lh_compliment, rh_compliment in self._b._iter_break_from_left_to_right(): 2452 yield self._a + lh_compliment, rh_compliment 2453 2454 @property 2455 def depth(self): 2456 return self._a.depth + self._b.depth 2457 2458 def _get_is_affine(self): 2459 return self._a.is_affine and self._b.is_affine 2460 is_affine = property(_get_is_affine) 2461 2462 def _get_is_separable(self): 2463 return self._a.is_separable and self._b.is_separable 2464 is_separable = property(_get_is_separable) 2465 2466 def __str__(self): 2467 return ("{}(\n" 2468 "{},\n" 2469 "{})" 2470 .format(type(self).__name__, 2471 _indent_str(self._a), 2472 _indent_str(self._b))) 2473 2474 def transform_affine(self, points): 2475 return self.get_affine().transform(points) 2476 transform_affine.__doc__ = Transform.transform_affine.__doc__ 2477 2478 def transform_non_affine(self, points): 2479 if self._a.is_affine and self._b.is_affine: 2480 return points 2481 elif not self._a.is_affine and self._b.is_affine: 2482 return self._a.transform_non_affine(points) 2483 else: 2484 return self._b.transform_non_affine( 2485 self._a.transform(points)) 2486 transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ 2487 2488 def transform_path_non_affine(self, path): 2489 if self._a.is_affine and self._b.is_affine: 2490 return path 2491 elif not self._a.is_affine and self._b.is_affine: 2492 return self._a.transform_path_non_affine(path) 2493 else: 2494 return self._b.transform_path_non_affine( 2495 self._a.transform_path(path)) 2496 transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ 2497 2498 def get_affine(self): 2499 if not self._b.is_affine: 2500 return self._b.get_affine() 2501 else: 2502 return Affine2D(np.dot(self._b.get_affine().get_matrix(), 2503 self._a.get_affine().get_matrix())) 2504 get_affine.__doc__ = Transform.get_affine.__doc__ 2505 2506 def inverted(self): 2507 return CompositeGenericTransform(self._b.inverted(), self._a.inverted()) 2508 inverted.__doc__ = Transform.inverted.__doc__ 2509 2510 def _get_has_inverse(self): 2511 return self._a.has_inverse and self._b.has_inverse 2512 has_inverse = property(_get_has_inverse) 2513 2514 2515class CompositeAffine2D(Affine2DBase): 2516 """ 2517 A composite transform formed by applying transform *a* then transform *b*. 2518 2519 This version is an optimization that handles the case where both *a* 2520 and *b* are 2D affines. 2521 """ 2522 def __init__(self, a, b, **kwargs): 2523 """ 2524 Create a new composite transform that is the result of 2525 applying transform *a* then transform *b*. 2526 2527 Both *a* and *b* must be instances of :class:`Affine2DBase`. 2528 2529 You will generally not call this constructor directly but use 2530 the :func:`composite_transform_factory` function instead, 2531 which can automatically choose the best kind of composite 2532 transform instance to create. 2533 """ 2534 if not a.is_affine or not b.is_affine: 2535 raise ValueError("'a' and 'b' must be affine transforms") 2536 if a.output_dims != b.input_dims: 2537 raise ValueError("The output dimension of 'a' must be equal to " 2538 "the input dimensions of 'b'") 2539 self.input_dims = a.input_dims 2540 self.output_dims = b.output_dims 2541 2542 Affine2DBase.__init__(self, **kwargs) 2543 self._a = a 2544 self._b = b 2545 self.set_children(a, b) 2546 self._mtx = None 2547 2548 @property 2549 def depth(self): 2550 return self._a.depth + self._b.depth 2551 2552 def _iter_break_from_left_to_right(self): 2553 for lh_compliment, rh_compliment in self._a._iter_break_from_left_to_right(): 2554 yield lh_compliment, rh_compliment + self._b 2555 for lh_compliment, rh_compliment in self._b._iter_break_from_left_to_right(): 2556 yield self._a + lh_compliment, rh_compliment 2557 2558 def __str__(self): 2559 return ("{}(\n" 2560 "{},\n" 2561 "{})" 2562 .format(type(self).__name__, 2563 _indent_str(self._a), 2564 _indent_str(self._b))) 2565 2566 def get_matrix(self): 2567 if self._invalid: 2568 self._mtx = np.dot( 2569 self._b.get_matrix(), 2570 self._a.get_matrix()) 2571 self._inverted = None 2572 self._invalid = 0 2573 return self._mtx 2574 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2575 2576 2577def composite_transform_factory(a, b): 2578 """ 2579 Create a new composite transform that is the result of applying 2580 transform a then transform b. 2581 2582 Shortcut versions of the blended transform are provided for the 2583 case where both child transforms are affine, or one or the other 2584 is the identity transform. 2585 2586 Composite transforms may also be created using the '+' operator, 2587 e.g.:: 2588 2589 c = a + b 2590 """ 2591 # check to see if any of a or b are IdentityTransforms. We use 2592 # isinstance here to guarantee that the transforms will *always* 2593 # be IdentityTransforms. Since TransformWrappers are mutable, 2594 # use of equality here would be wrong. 2595 if isinstance(a, IdentityTransform): 2596 return b 2597 elif isinstance(b, IdentityTransform): 2598 return a 2599 elif isinstance(a, Affine2D) and isinstance(b, Affine2D): 2600 return CompositeAffine2D(a, b) 2601 return CompositeGenericTransform(a, b) 2602 2603 2604class BboxTransform(Affine2DBase): 2605 """ 2606 :class:`BboxTransform` linearly transforms points from one 2607 :class:`Bbox` to another :class:`Bbox`. 2608 """ 2609 is_separable = True 2610 2611 def __init__(self, boxin, boxout, **kwargs): 2612 """ 2613 Create a new :class:`BboxTransform` that linearly transforms 2614 points from *boxin* to *boxout*. 2615 """ 2616 if not boxin.is_bbox or not boxout.is_bbox: 2617 raise ValueError("'boxin' and 'boxout' must be bbox") 2618 2619 Affine2DBase.__init__(self, **kwargs) 2620 self._boxin = boxin 2621 self._boxout = boxout 2622 self.set_children(boxin, boxout) 2623 self._mtx = None 2624 self._inverted = None 2625 2626 def __str__(self): 2627 return ("{}(\n" 2628 "{},\n" 2629 "{})" 2630 .format(type(self).__name__, 2631 _indent_str(self._boxin), 2632 _indent_str(self._boxout))) 2633 2634 def get_matrix(self): 2635 if self._invalid: 2636 inl, inb, inw, inh = self._boxin.bounds 2637 outl, outb, outw, outh = self._boxout.bounds 2638 x_scale = outw / inw 2639 y_scale = outh / inh 2640 if DEBUG and (x_scale == 0 or y_scale == 0): 2641 raise ValueError("Transforming from or to a singular bounding box.") 2642 self._mtx = np.array([[x_scale, 0.0 , (-inl*x_scale+outl)], 2643 [0.0 , y_scale, (-inb*y_scale+outb)], 2644 [0.0 , 0.0 , 1.0 ]], 2645 float) 2646 self._inverted = None 2647 self._invalid = 0 2648 return self._mtx 2649 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2650 2651 2652class BboxTransformTo(Affine2DBase): 2653 """ 2654 :class:`BboxTransformTo` is a transformation that linearly 2655 transforms points from the unit bounding box to a given 2656 :class:`Bbox`. 2657 """ 2658 is_separable = True 2659 2660 def __init__(self, boxout, **kwargs): 2661 """ 2662 Create a new :class:`BboxTransformTo` that linearly transforms 2663 points from the unit bounding box to *boxout*. 2664 """ 2665 if not boxout.is_bbox: 2666 raise ValueError("'boxout' must be bbox") 2667 2668 Affine2DBase.__init__(self, **kwargs) 2669 self._boxout = boxout 2670 self.set_children(boxout) 2671 self._mtx = None 2672 self._inverted = None 2673 2674 def __str__(self): 2675 return ("{}(\n" 2676 "{})" 2677 .format(type(self).__name__, 2678 _indent_str(self._boxout))) 2679 2680 def get_matrix(self): 2681 if self._invalid: 2682 outl, outb, outw, outh = self._boxout.bounds 2683 if DEBUG and (outw == 0 or outh == 0): 2684 raise ValueError("Transforming to a singular bounding box.") 2685 self._mtx = np.array([[outw, 0.0, outl], 2686 [ 0.0, outh, outb], 2687 [ 0.0, 0.0, 1.0]], 2688 float) 2689 self._inverted = None 2690 self._invalid = 0 2691 return self._mtx 2692 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2693 2694 2695class BboxTransformToMaxOnly(BboxTransformTo): 2696 """ 2697 :class:`BboxTransformTo` is a transformation that linearly 2698 transforms points from the unit bounding box to a given 2699 :class:`Bbox` with a fixed upper left of (0, 0). 2700 """ 2701 def get_matrix(self): 2702 if self._invalid: 2703 xmax, ymax = self._boxout.max 2704 if DEBUG and (xmax == 0 or ymax == 0): 2705 raise ValueError("Transforming to a singular bounding box.") 2706 self._mtx = np.array([[xmax, 0.0, 0.0], 2707 [ 0.0, ymax, 0.0], 2708 [ 0.0, 0.0, 1.0]], 2709 float) 2710 self._inverted = None 2711 self._invalid = 0 2712 return self._mtx 2713 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2714 2715 2716class BboxTransformFrom(Affine2DBase): 2717 """ 2718 :class:`BboxTransformFrom` linearly transforms points from a given 2719 :class:`Bbox` to the unit bounding box. 2720 """ 2721 is_separable = True 2722 2723 def __init__(self, boxin, **kwargs): 2724 if not boxin.is_bbox: 2725 raise ValueError("'boxin' must be bbox") 2726 2727 Affine2DBase.__init__(self, **kwargs) 2728 self._boxin = boxin 2729 self.set_children(boxin) 2730 self._mtx = None 2731 self._inverted = None 2732 2733 def __str__(self): 2734 return ("{}(\n" 2735 "{})" 2736 .format(type(self).__name__, 2737 _indent_str(self._boxin))) 2738 2739 def get_matrix(self): 2740 if self._invalid: 2741 inl, inb, inw, inh = self._boxin.bounds 2742 if DEBUG and (inw == 0 or inh == 0): 2743 raise ValueError("Transforming from a singular bounding box.") 2744 x_scale = 1.0 / inw 2745 y_scale = 1.0 / inh 2746 self._mtx = np.array([[x_scale, 0.0 , (-inl*x_scale)], 2747 [0.0 , y_scale, (-inb*y_scale)], 2748 [0.0 , 0.0 , 1.0 ]], 2749 float) 2750 self._inverted = None 2751 self._invalid = 0 2752 return self._mtx 2753 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2754 2755 2756class ScaledTranslation(Affine2DBase): 2757 """ 2758 A transformation that translates by *xt* and *yt*, after *xt* and *yt* 2759 have been transformad by the given transform *scale_trans*. 2760 """ 2761 def __init__(self, xt, yt, scale_trans, **kwargs): 2762 Affine2DBase.__init__(self, **kwargs) 2763 self._t = (xt, yt) 2764 self._scale_trans = scale_trans 2765 self.set_children(scale_trans) 2766 self._mtx = None 2767 self._inverted = None 2768 2769 def __str__(self): 2770 return ("{}(\n" 2771 "{})" 2772 .format(type(self).__name__, 2773 _indent_str(self._t))) 2774 2775 def get_matrix(self): 2776 if self._invalid: 2777 xt, yt = self._scale_trans.transform_point(self._t) 2778 self._mtx = np.array([[1.0, 0.0, xt], 2779 [0.0, 1.0, yt], 2780 [0.0, 0.0, 1.0]], 2781 float) 2782 self._invalid = 0 2783 self._inverted = None 2784 return self._mtx 2785 get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ 2786 2787 2788class TransformedPath(TransformNode): 2789 """ 2790 A :class:`TransformedPath` caches a non-affine transformed copy of 2791 the :class:`~matplotlib.path.Path`. This cached copy is 2792 automatically updated when the non-affine part of the transform 2793 changes. 2794 2795 .. note:: 2796 2797 Paths are considered immutable by this class. Any update to the 2798 path's vertices/codes will not trigger a transform recomputation. 2799 2800 """ 2801 def __init__(self, path, transform): 2802 """ 2803 Create a new :class:`TransformedPath` from the given 2804 :class:`~matplotlib.path.Path` and :class:`Transform`. 2805 """ 2806 if not isinstance(transform, Transform): 2807 raise ValueError("'transform' must be an instance of " 2808 "'matplotlib.transform.Transform'") 2809 TransformNode.__init__(self) 2810 2811 self._path = path 2812 self._transform = transform 2813 self.set_children(transform) 2814 self._transformed_path = None 2815 self._transformed_points = None 2816 2817 def _revalidate(self): 2818 # only recompute if the invalidation includes the non_affine part of the transform 2819 if ((self._invalid & self.INVALID_NON_AFFINE == self.INVALID_NON_AFFINE) 2820 or self._transformed_path is None): 2821 self._transformed_path = \ 2822 self._transform.transform_path_non_affine(self._path) 2823 self._transformed_points = \ 2824 Path._fast_from_codes_and_verts( 2825 self._transform.transform_non_affine(self._path.vertices), 2826 None, 2827 {'interpolation_steps': self._path._interpolation_steps, 2828 'should_simplify': self._path.should_simplify}) 2829 self._invalid = 0 2830 2831 def get_transformed_points_and_affine(self): 2832 """ 2833 Return a copy of the child path, with the non-affine part of 2834 the transform already applied, along with the affine part of 2835 the path necessary to complete the transformation. Unlike 2836 :meth:`get_transformed_path_and_affine`, no interpolation will 2837 be performed. 2838 """ 2839 self._revalidate() 2840 return self._transformed_points, self.get_affine() 2841 2842 def get_transformed_path_and_affine(self): 2843 """ 2844 Return a copy of the child path, with the non-affine part of 2845 the transform already applied, along with the affine part of 2846 the path necessary to complete the transformation. 2847 """ 2848 self._revalidate() 2849 return self._transformed_path, self.get_affine() 2850 2851 def get_fully_transformed_path(self): 2852 """ 2853 Return a fully-transformed copy of the child path. 2854 """ 2855 self._revalidate() 2856 return self._transform.transform_path_affine(self._transformed_path) 2857 2858 def get_affine(self): 2859 return self._transform.get_affine() 2860 2861 2862class TransformedPatchPath(TransformedPath): 2863 """ 2864 A :class:`TransformedPatchPath` caches a non-affine transformed copy of 2865 the :class:`~matplotlib.path.Patch`. This cached copy is automatically 2866 updated when the non-affine part of the transform or the patch changes. 2867 """ 2868 def __init__(self, patch): 2869 """ 2870 Create a new :class:`TransformedPatchPath` from the given 2871 :class:`~matplotlib.path.Patch`. 2872 """ 2873 TransformNode.__init__(self) 2874 2875 transform = patch.get_transform() 2876 self._patch = patch 2877 self._transform = transform 2878 self.set_children(transform) 2879 self._path = patch.get_path() 2880 self._transformed_path = None 2881 self._transformed_points = None 2882 2883 def _revalidate(self): 2884 patch_path = self._patch.get_path() 2885 # Only recompute if the invalidation includes the non_affine part of 2886 # the transform, or the Patch's Path has changed. 2887 if (self._transformed_path is None or self._path != patch_path or 2888 (self._invalid & self.INVALID_NON_AFFINE == 2889 self.INVALID_NON_AFFINE)): 2890 self._path = patch_path 2891 self._transformed_path = \ 2892 self._transform.transform_path_non_affine(patch_path) 2893 self._transformed_points = \ 2894 Path._fast_from_codes_and_verts( 2895 self._transform.transform_non_affine(patch_path.vertices), 2896 None, 2897 {'interpolation_steps': patch_path._interpolation_steps, 2898 'should_simplify': patch_path.should_simplify}) 2899 self._invalid = 0 2900 2901 2902def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): 2903 """ 2904 Modify the endpoints of a range as needed to avoid singularities. 2905 2906 Parameters 2907 ---------- 2908 vmin, vmax : float 2909 The initial endpoints. 2910 expander : float, optional, default: 0.001 2911 Fractional amount by which *vmin* and *vmax* are expanded if 2912 the original interval is too small, based on *tiny*. 2913 tiny : float, optional, default: 1e-15 2914 Threshold for the ratio of the interval to the maximum absolute 2915 value of its endpoints. If the interval is smaller than 2916 this, it will be expanded. This value should be around 2917 1e-15 or larger; otherwise the interval will be approaching 2918 the double precision resolution limit. 2919 increasing : bool, optional, default: True 2920 If True, swap *vmin*, *vmax* if *vmin* > *vmax*. 2921 2922 Returns 2923 ------- 2924 vmin, vmax : float 2925 Endpoints, expanded and/or swapped if necessary. 2926 If either input is inf or NaN, or if both inputs are 0 or very 2927 close to zero, it returns -*expander*, *expander*. 2928 """ 2929 2930 if (not np.isfinite(vmin)) or (not np.isfinite(vmax)): 2931 return -expander, expander 2932 2933 swapped = False 2934 if vmax < vmin: 2935 vmin, vmax = vmax, vmin 2936 swapped = True 2937 2938 maxabsvalue = max(abs(vmin), abs(vmax)) 2939 if maxabsvalue < (1e6 / tiny) * np.finfo(float).tiny: 2940 vmin = -expander 2941 vmax = expander 2942 2943 elif vmax - vmin <= maxabsvalue * tiny: 2944 if vmax == 0 and vmin == 0: 2945 vmin = -expander 2946 vmax = expander 2947 else: 2948 vmin -= expander*abs(vmin) 2949 vmax += expander*abs(vmax) 2950 2951 if swapped and not increasing: 2952 vmin, vmax = vmax, vmin 2953 return vmin, vmax 2954 2955 2956def interval_contains(interval, val): 2957 """ 2958 Check, inclusively, whether an interval includes a given value. 2959 2960 Parameters 2961 ---------- 2962 interval : sequence of scalar 2963 A 2-length sequence, endpoints that define the interval. 2964 val : scalar 2965 Value to check is within interval. 2966 2967 Returns 2968 ------- 2969 bool 2970 Returns true if given val is within the interval. 2971 """ 2972 a, b = interval 2973 return a <= val <= b or a >= val >= b 2974 2975 2976def interval_contains_open(interval, val): 2977 """ 2978 Check, excluding endpoints, whether an interval includes a given value. 2979 2980 Parameters 2981 ---------- 2982 interval : sequence of scalar 2983 A 2-length sequence, endpoints that define the interval. 2984 val : scalar 2985 Value to check is within interval. 2986 2987 Returns 2988 ------- 2989 bool 2990 Returns true if given val is within the interval. 2991 """ 2992 a, b = interval 2993 return a < val < b or a > val > b 2994 2995 2996def offset_copy(trans, fig=None, x=0.0, y=0.0, units='inches'): 2997 """ 2998 Return a new transform with an added offset. 2999 3000 Parameters 3001 ---------- 3002 trans : :class:`Transform` instance 3003 Any transform, to which offset will be applied. 3004 fig : :class:`~matplotlib.figure.Figure`, optional, default: None 3005 Current figure. It can be None if *units* are 'dots'. 3006 x, y : float, optional, default: 0.0 3007 Specifies the offset to apply. 3008 units : {'inches', 'points', 'dots'}, optional 3009 Units of the offset. 3010 3011 Returns 3012 ------- 3013 trans : :class:`Transform` instance 3014 Transform with applied offset. 3015 """ 3016 if units == 'dots': 3017 return trans + Affine2D().translate(x, y) 3018 if fig is None: 3019 raise ValueError('For units of inches or points a fig kwarg is needed') 3020 if units == 'points': 3021 x /= 72.0 3022 y /= 72.0 3023 elif not units == 'inches': 3024 raise ValueError('units must be dots, points, or inches') 3025 return trans + ScaledTranslation(x, y, fig.dpi_scale_trans) 3026