1
1
"""
2
2
Matplotlib includes a framework for arbitrary geometric
3
- transformations that is used determine the final position of all
3
+ transformations that is used to determine the final position of all
4
4
elements drawn on the canvas.
5
5
6
6
Transforms are composed into trees of `TransformNode` objects
7
- whose actual value depends on their children. When the contents of
8
- children change, their parents are automatically invalidated. The
7
+ whose actual value depends on their children. When the contents of
8
+ children change, their parents are automatically invalidated. The
9
9
next time an invalidated transform is accessed, it is recomputed to
10
- reflect those changes. This invalidation/caching approach prevents
10
+ reflect those changes. This invalidation/caching approach prevents
11
11
unnecessary recomputations of transforms, and contributes to better
12
12
interactive performance.
13
13
@@ -1372,7 +1372,7 @@ def _iter_break_from_left_to_right(self):
1372
1372
This is equivalent to flattening the stack then yielding
1373
1373
``flat_stack[:i], flat_stack[i:]`` where i=0..(n-1).
1374
1374
"""
1375
- yield IdentityTransform (), self
1375
+ yield IdentityTransform (dims = self . input_dims ), self
1376
1376
1377
1377
@property
1378
1378
def depth (self ):
@@ -1578,7 +1578,7 @@ def transform_bbox(self, bbox):
1578
1578
1579
1579
def get_affine (self ):
1580
1580
"""Get the affine part of this transform."""
1581
- return IdentityTransform ()
1581
+ return IdentityTransform (dims = self . input_dims )
1582
1582
1583
1583
def get_matrix (self ):
1584
1584
"""Get the matrix for the affine part of this transform."""
@@ -1821,11 +1821,13 @@ def get_affine(self):
1821
1821
return self
1822
1822
1823
1823
1824
- class Affine2DBase (AffineBase ):
1824
+ class AffineImmutable (AffineBase ):
1825
1825
"""
1826
- The base class of all 2D affine transformations.
1826
+ The base class of all affine transformations.
1827
1827
1828
- 2D affine transformations are performed using a 3x3 numpy array::
1828
+ Affine transformations for the n-th degree are performed using a
1829
+ numpy array with shape (n+1, n+1). For example, 2D affine
1830
+ transformations are performed using a 3x3 numpy array::
1829
1831
1830
1832
a c e
1831
1833
b d f
@@ -1835,34 +1837,65 @@ class Affine2DBase(AffineBase):
1835
1837
affine transformation, use `Affine2D`.
1836
1838
1837
1839
Subclasses of this class will generally only need to override a
1838
- constructor and `~.Transform.get_matrix` that generates a custom 3x3 matrix.
1840
+ constructor and `~.Transform.get_matrix` that generates a custom matrix
1841
+ with the appropriate shape.
1839
1842
"""
1840
- input_dims = 2
1841
- output_dims = 2
1843
+ def __init__ (self , * args , dims = 2 , ** kwargs ):
1844
+ self .input_dims = dims
1845
+ self .output_dims = dims
1846
+ super ().__init__ (* args , ** kwargs )
1842
1847
1843
1848
def frozen (self ):
1844
1849
# docstring inherited
1845
- return Affine2D (self .get_matrix ().copy ())
1850
+ return _affine_factory (self .get_matrix ().copy (), self . input_dims )
1846
1851
1847
1852
@property
1848
1853
def is_separable (self ):
1849
1854
mtx = self .get_matrix ()
1850
- return mtx [0 , 1 ] == mtx [1 , 0 ] == 0.0
1855
+ separable = True
1856
+ for i in range (self .input_dims ):
1857
+ for j in range (i + 1 , self .input_dims ):
1858
+ separable = separable and mtx [i , j ] == 0.0
1859
+ separable = separable and mtx [j , i ] == 0.0
1860
+ return separable
1851
1861
1852
1862
def to_values (self ):
1853
1863
"""
1854
- Return the values of the matrix as an ``(a, b, c, d, e, f)`` tuple.
1864
+ Return the values of the matrix as a tuple.
1855
1865
"""
1856
1866
mtx = self .get_matrix ()
1857
- return tuple (mtx [:2 ].swapaxes (0 , 1 ).flat )
1867
+ return tuple (mtx [:self .input_dims ].swapaxes (0 , 1 ).flat )
1868
+
1869
+ def _affine_transform (self , vertices ):
1870
+ """
1871
+ Default implementation of affine_transform if a C implementation isn't
1872
+ available
1873
+ """
1874
+ mtx = self .get_matrix ()
1875
+ values = np .asanyarray (vertices )
1876
+ # single point
1877
+ if (values .shape == (self .input_dims ,)):
1878
+ return mtx .dot (vertices + [1 ]).tolist ()[:self .input_dims ]
1879
+ # multiple points
1880
+ if (values .shape [1 ] == self .input_dims ):
1881
+ homogeneous = np .hstack ((values , np .ones ((values .shape [0 ], 1 ))))
1882
+ return np .dot (mtx , homogeneous .T ).T [:, :- 1 ]
1883
+
1884
+ raise TypeError ("Dimensions of input must match the input dimensions of "
1885
+ "the transform" )
1858
1886
1859
1887
@_api .rename_parameter ("3.8" , "points" , "values" )
1860
1888
def transform_affine (self , values ):
1861
1889
mtx = self .get_matrix ()
1890
+
1891
+ # Default to python implementation if C implementation isn't available
1892
+ transform_fn = (affine_transform if self .input_dims <= 2
1893
+ else self ._affine_transform )
1894
+
1862
1895
if isinstance (values , np .ma .MaskedArray ):
1863
- tpoints = affine_transform (values .data , mtx )
1896
+ tpoints = transform_fn (values .data , mtx )
1864
1897
return np .ma .MaskedArray (tpoints , mask = np .ma .getmask (values ))
1865
- return affine_transform (values , mtx )
1898
+ return transform_fn (values , mtx )
1866
1899
1867
1900
if DEBUG :
1868
1901
_transform_affine = transform_affine
@@ -1886,12 +1919,25 @@ def inverted(self):
1886
1919
shorthand_name = None
1887
1920
if self ._shorthand_name :
1888
1921
shorthand_name = '(%s)-1' % self ._shorthand_name
1889
- self ._inverted = Affine2D (inv (mtx ), shorthand_name = shorthand_name )
1922
+ self ._inverted = _affine_factory (inv (mtx ), self .input_dims ,
1923
+ shorthand_name = shorthand_name )
1890
1924
self ._invalid = 0
1891
1925
return self ._inverted
1892
1926
1893
1927
1894
- class Affine2D (Affine2DBase ):
1928
+ @_api .deprecated ("3.9" , alternative = "AffineImmutable" )
1929
+ class Affine2DBase (AffineImmutable ):
1930
+ pass
1931
+
1932
+
1933
+ def _affine_factory (mtx , dims , * args , ** kwargs ):
1934
+ if dims == 2 :
1935
+ return Affine2D (mtx , * args , ** kwargs )
1936
+ else :
1937
+ return NotImplemented
1938
+
1939
+
1940
+ class Affine2D (AffineImmutable ):
1895
1941
"""
1896
1942
A mutable 2D affine transformation.
1897
1943
"""
@@ -1906,10 +1952,9 @@ def __init__(self, matrix=None, **kwargs):
1906
1952
1907
1953
If *matrix* is None, initialize with the identity transform.
1908
1954
"""
1909
- super ().__init__ (** kwargs )
1955
+ super ().__init__ (dims = 2 , ** kwargs )
1910
1956
if matrix is None :
1911
- # A bit faster than np.identity(3).
1912
- matrix = IdentityTransform ._mtx
1957
+ matrix = np .identity (3 )
1913
1958
self ._mtx = matrix .copy ()
1914
1959
self ._invalid = 0
1915
1960
@@ -1967,18 +2012,20 @@ def set_matrix(self, mtx):
1967
2012
def set (self , other ):
1968
2013
"""
1969
2014
Set this transformation from the frozen copy of another
1970
- `Affine2DBase ` object.
2015
+ 2D `AffineImmutable ` object.
1971
2016
"""
1972
- _api .check_isinstance (Affine2DBase , other = other )
2017
+ _api .check_isinstance (AffineImmutable , other = other )
2018
+ if (other .input_dims != 2 ):
2019
+ raise TypeError ("Mismatch between dimensions of AffineImmutable "
2020
+ "and Affine2D" )
1973
2021
self ._mtx = other .get_matrix ()
1974
2022
self .invalidate ()
1975
2023
1976
2024
def clear (self ):
1977
2025
"""
1978
2026
Reset the underlying matrix to the identity transform.
1979
2027
"""
1980
- # A bit faster than np.identity(3).
1981
- self ._mtx = IdentityTransform ._mtx .copy ()
2028
+ self ._mtx = np .identity (3 )
1982
2029
self .invalidate ()
1983
2030
return self
1984
2031
@@ -2113,12 +2160,14 @@ def skew_deg(self, xShear, yShear):
2113
2160
return self .skew (math .radians (xShear ), math .radians (yShear ))
2114
2161
2115
2162
2116
- class IdentityTransform (Affine2DBase ):
2163
+ class IdentityTransform (AffineImmutable ):
2117
2164
"""
2118
2165
A special class that does one thing, the identity transform, in a
2119
2166
fast way.
2120
2167
"""
2121
- _mtx = np .identity (3 )
2168
+ def __init__ (self , * args , ** kwargs ):
2169
+ super ().__init__ (self , * args , ** kwargs )
2170
+ self ._mtx = np .identity (self .input_dims + 1 )
2122
2171
2123
2172
def frozen (self ):
2124
2173
# docstring inherited
@@ -2532,7 +2581,7 @@ def composite_transform_factory(a, b):
2532
2581
return CompositeGenericTransform (a , b )
2533
2582
2534
2583
2535
- class BboxTransform (Affine2DBase ):
2584
+ class BboxTransform (AffineImmutable ):
2536
2585
"""
2537
2586
`BboxTransform` linearly transforms points from one `Bbox` to another.
2538
2587
"""
@@ -2546,7 +2595,7 @@ def __init__(self, boxin, boxout, **kwargs):
2546
2595
"""
2547
2596
_api .check_isinstance (BboxBase , boxin = boxin , boxout = boxout )
2548
2597
2549
- super ().__init__ (** kwargs )
2598
+ super ().__init__ (dims = 2 , ** kwargs )
2550
2599
self ._boxin = boxin
2551
2600
self ._boxout = boxout
2552
2601
self .set_children (boxin , boxout )
@@ -2574,7 +2623,7 @@ def get_matrix(self):
2574
2623
return self ._mtx
2575
2624
2576
2625
2577
- class BboxTransformTo (Affine2DBase ):
2626
+ class BboxTransformTo (AffineImmutable ):
2578
2627
"""
2579
2628
`BboxTransformTo` is a transformation that linearly transforms points from
2580
2629
the unit bounding box to a given `Bbox`.
@@ -2589,7 +2638,7 @@ def __init__(self, boxout, **kwargs):
2589
2638
"""
2590
2639
_api .check_isinstance (BboxBase , boxout = boxout )
2591
2640
2592
- super ().__init__ (** kwargs )
2641
+ super ().__init__ (dims = 2 , ** kwargs )
2593
2642
self ._boxout = boxout
2594
2643
self .set_children (boxout )
2595
2644
self ._mtx = None
@@ -2633,7 +2682,7 @@ def get_matrix(self):
2633
2682
return self ._mtx
2634
2683
2635
2684
2636
- class BboxTransformFrom (Affine2DBase ):
2685
+ class BboxTransformFrom (AffineImmutable ):
2637
2686
"""
2638
2687
`BboxTransformFrom` linearly transforms points from a given `Bbox` to the
2639
2688
unit bounding box.
@@ -2643,7 +2692,7 @@ class BboxTransformFrom(Affine2DBase):
2643
2692
def __init__ (self , boxin , ** kwargs ):
2644
2693
_api .check_isinstance (BboxBase , boxin = boxin )
2645
2694
2646
- super ().__init__ (** kwargs )
2695
+ super ().__init__ (dims = 2 , ** kwargs )
2647
2696
self ._boxin = boxin
2648
2697
self .set_children (boxin )
2649
2698
self ._mtx = None
@@ -2668,13 +2717,13 @@ def get_matrix(self):
2668
2717
return self ._mtx
2669
2718
2670
2719
2671
- class ScaledTranslation (Affine2DBase ):
2720
+ class ScaledTranslation (AffineImmutable ):
2672
2721
"""
2673
2722
A transformation that translates by *xt* and *yt*, after *xt* and *yt*
2674
2723
have been transformed by *scale_trans*.
2675
2724
"""
2676
2725
def __init__ (self , xt , yt , scale_trans , ** kwargs ):
2677
- super ().__init__ (** kwargs )
2726
+ super ().__init__ (dims = 2 , ** kwargs )
2678
2727
self ._t = (xt , yt )
2679
2728
self ._scale_trans = scale_trans
2680
2729
self .set_children (scale_trans )
@@ -2686,15 +2735,14 @@ def __init__(self, xt, yt, scale_trans, **kwargs):
2686
2735
def get_matrix (self ):
2687
2736
# docstring inherited
2688
2737
if self ._invalid :
2689
- # A bit faster than np.identity(3).
2690
- self ._mtx = IdentityTransform ._mtx .copy ()
2738
+ self ._mtx = np .identity (3 )
2691
2739
self ._mtx [:2 , 2 ] = self ._scale_trans .transform (self ._t )
2692
2740
self ._invalid = 0
2693
2741
self ._inverted = None
2694
2742
return self ._mtx
2695
2743
2696
2744
2697
- class AffineDeltaTransform (Affine2DBase ):
2745
+ class AffineDeltaTransform (AffineImmutable ):
2698
2746
r"""
2699
2747
A transform wrapper for transforming displacements between pairs of points.
2700
2748
@@ -2712,7 +2760,7 @@ class AffineDeltaTransform(Affine2DBase):
2712
2760
"""
2713
2761
2714
2762
def __init__ (self , transform , ** kwargs ):
2715
- super ().__init__ (** kwargs )
2763
+ super ().__init__ (dims = 2 , ** kwargs )
2716
2764
self ._base_transform = transform
2717
2765
2718
2766
__str__ = _make_str_method ("_base_transform" )
0 commit comments