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