test_transforms.py 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131
  1. import copy
  2. import numpy as np
  3. from numpy.testing import (assert_allclose, assert_almost_equal,
  4. assert_array_equal, assert_array_almost_equal)
  5. import pytest
  6. from matplotlib import scale
  7. import matplotlib.pyplot as plt
  8. import matplotlib.patches as mpatches
  9. import matplotlib.transforms as mtransforms
  10. from matplotlib.transforms import Affine2D, Bbox, TransformedBbox, _ScaledRotation
  11. from matplotlib.path import Path
  12. from matplotlib.testing.decorators import image_comparison, check_figures_equal
  13. from unittest.mock import MagicMock
  14. class TestAffine2D:
  15. single_point = [1.0, 1.0]
  16. multiple_points = [[0.0, 2.0], [3.0, 3.0], [4.0, 0.0]]
  17. pivot = single_point
  18. def test_init(self):
  19. Affine2D([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
  20. Affine2D(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], int))
  21. Affine2D(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], float))
  22. def test_values(self):
  23. np.random.seed(19680801)
  24. values = np.random.random(6)
  25. assert_array_equal(Affine2D.from_values(*values).to_values(), values)
  26. def test_modify_inplace(self):
  27. # Some polar transforms require modifying the matrix in place.
  28. trans = Affine2D()
  29. mtx = trans.get_matrix()
  30. mtx[0, 0] = 42
  31. assert_array_equal(trans.get_matrix(), [[42, 0, 0], [0, 1, 0], [0, 0, 1]])
  32. def test_clear(self):
  33. a = Affine2D(np.random.rand(3, 3) + 5) # Anything non-identity.
  34. a.clear()
  35. assert_array_equal(a.get_matrix(), [[1, 0, 0], [0, 1, 0], [0, 0, 1]])
  36. def test_rotate(self):
  37. r_pi_2 = Affine2D().rotate(np.pi / 2)
  38. r90 = Affine2D().rotate_deg(90)
  39. assert_array_equal(r_pi_2.get_matrix(), r90.get_matrix())
  40. assert_array_almost_equal(r90.transform(self.single_point), [-1, 1])
  41. assert_array_almost_equal(r90.transform(self.multiple_points),
  42. [[-2, 0], [-3, 3], [0, 4]])
  43. r_pi = Affine2D().rotate(np.pi)
  44. r180 = Affine2D().rotate_deg(180)
  45. assert_array_equal(r_pi.get_matrix(), r180.get_matrix())
  46. assert_array_almost_equal(r180.transform(self.single_point), [-1, -1])
  47. assert_array_almost_equal(r180.transform(self.multiple_points),
  48. [[0, -2], [-3, -3], [-4, 0]])
  49. r_pi_3_2 = Affine2D().rotate(3 * np.pi / 2)
  50. r270 = Affine2D().rotate_deg(270)
  51. assert_array_equal(r_pi_3_2.get_matrix(), r270.get_matrix())
  52. assert_array_almost_equal(r270.transform(self.single_point), [1, -1])
  53. assert_array_almost_equal(r270.transform(self.multiple_points),
  54. [[2, 0], [3, -3], [0, -4]])
  55. assert_array_equal((r90 + r90).get_matrix(), r180.get_matrix())
  56. assert_array_equal((r90 + r180).get_matrix(), r270.get_matrix())
  57. def test_rotate_around(self):
  58. r_pi_2 = Affine2D().rotate_around(*self.pivot, np.pi / 2)
  59. r90 = Affine2D().rotate_deg_around(*self.pivot, 90)
  60. assert_array_equal(r_pi_2.get_matrix(), r90.get_matrix())
  61. assert_array_almost_equal(r90.transform(self.single_point), [1, 1])
  62. assert_array_almost_equal(r90.transform(self.multiple_points),
  63. [[0, 0], [-1, 3], [2, 4]])
  64. r_pi = Affine2D().rotate_around(*self.pivot, np.pi)
  65. r180 = Affine2D().rotate_deg_around(*self.pivot, 180)
  66. assert_array_equal(r_pi.get_matrix(), r180.get_matrix())
  67. assert_array_almost_equal(r180.transform(self.single_point), [1, 1])
  68. assert_array_almost_equal(r180.transform(self.multiple_points),
  69. [[2, 0], [-1, -1], [-2, 2]])
  70. r_pi_3_2 = Affine2D().rotate_around(*self.pivot, 3 * np.pi / 2)
  71. r270 = Affine2D().rotate_deg_around(*self.pivot, 270)
  72. assert_array_equal(r_pi_3_2.get_matrix(), r270.get_matrix())
  73. assert_array_almost_equal(r270.transform(self.single_point), [1, 1])
  74. assert_array_almost_equal(r270.transform(self.multiple_points),
  75. [[2, 2], [3, -1], [0, -2]])
  76. assert_array_almost_equal((r90 + r90).get_matrix(), r180.get_matrix())
  77. assert_array_almost_equal((r90 + r180).get_matrix(), r270.get_matrix())
  78. def test_scale(self):
  79. sx = Affine2D().scale(3, 1)
  80. sy = Affine2D().scale(1, -2)
  81. trans = Affine2D().scale(3, -2)
  82. assert_array_equal((sx + sy).get_matrix(), trans.get_matrix())
  83. assert_array_equal(trans.transform(self.single_point), [3, -2])
  84. assert_array_equal(trans.transform(self.multiple_points),
  85. [[0, -4], [9, -6], [12, 0]])
  86. def test_skew(self):
  87. trans_rad = Affine2D().skew(np.pi / 8, np.pi / 12)
  88. trans_deg = Affine2D().skew_deg(22.5, 15)
  89. assert_array_equal(trans_rad.get_matrix(), trans_deg.get_matrix())
  90. # Using ~atan(0.5), ~atan(0.25) produces roundish numbers on output.
  91. trans = Affine2D().skew_deg(26.5650512, 14.0362435)
  92. assert_array_almost_equal(trans.transform(self.single_point), [1.5, 1.25])
  93. assert_array_almost_equal(trans.transform(self.multiple_points),
  94. [[1, 2], [4.5, 3.75], [4, 1]])
  95. def test_translate(self):
  96. tx = Affine2D().translate(23, 0)
  97. ty = Affine2D().translate(0, 42)
  98. trans = Affine2D().translate(23, 42)
  99. assert_array_equal((tx + ty).get_matrix(), trans.get_matrix())
  100. assert_array_equal(trans.transform(self.single_point), [24, 43])
  101. assert_array_equal(trans.transform(self.multiple_points),
  102. [[23, 44], [26, 45], [27, 42]])
  103. def test_rotate_plus_other(self):
  104. trans = Affine2D().rotate_deg(90).rotate_deg_around(*self.pivot, 180)
  105. trans_added = (Affine2D().rotate_deg(90) +
  106. Affine2D().rotate_deg_around(*self.pivot, 180))
  107. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  108. assert_array_almost_equal(trans.transform(self.single_point), [3, 1])
  109. assert_array_almost_equal(trans.transform(self.multiple_points),
  110. [[4, 2], [5, -1], [2, -2]])
  111. trans = Affine2D().rotate_deg(90).scale(3, -2)
  112. trans_added = Affine2D().rotate_deg(90) + Affine2D().scale(3, -2)
  113. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  114. assert_array_almost_equal(trans.transform(self.single_point), [-3, -2])
  115. assert_array_almost_equal(trans.transform(self.multiple_points),
  116. [[-6, -0], [-9, -6], [0, -8]])
  117. trans = (Affine2D().rotate_deg(90)
  118. .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
  119. trans_added = (Affine2D().rotate_deg(90) +
  120. Affine2D().skew_deg(26.5650512, 14.0362435))
  121. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  122. assert_array_almost_equal(trans.transform(self.single_point), [-0.5, 0.75])
  123. assert_array_almost_equal(trans.transform(self.multiple_points),
  124. [[-2, -0.5], [-1.5, 2.25], [2, 4]])
  125. trans = Affine2D().rotate_deg(90).translate(23, 42)
  126. trans_added = Affine2D().rotate_deg(90) + Affine2D().translate(23, 42)
  127. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  128. assert_array_almost_equal(trans.transform(self.single_point), [22, 43])
  129. assert_array_almost_equal(trans.transform(self.multiple_points),
  130. [[21, 42], [20, 45], [23, 46]])
  131. def test_rotate_around_plus_other(self):
  132. trans = Affine2D().rotate_deg_around(*self.pivot, 90).rotate_deg(180)
  133. trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
  134. Affine2D().rotate_deg(180))
  135. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  136. assert_array_almost_equal(trans.transform(self.single_point), [-1, -1])
  137. assert_array_almost_equal(trans.transform(self.multiple_points),
  138. [[0, 0], [1, -3], [-2, -4]])
  139. trans = Affine2D().rotate_deg_around(*self.pivot, 90).scale(3, -2)
  140. trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
  141. Affine2D().scale(3, -2))
  142. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  143. assert_array_almost_equal(trans.transform(self.single_point), [3, -2])
  144. assert_array_almost_equal(trans.transform(self.multiple_points),
  145. [[0, 0], [-3, -6], [6, -8]])
  146. trans = (Affine2D().rotate_deg_around(*self.pivot, 90)
  147. .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
  148. trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
  149. Affine2D().skew_deg(26.5650512, 14.0362435))
  150. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  151. assert_array_almost_equal(trans.transform(self.single_point), [1.5, 1.25])
  152. assert_array_almost_equal(trans.transform(self.multiple_points),
  153. [[0, 0], [0.5, 2.75], [4, 4.5]])
  154. trans = Affine2D().rotate_deg_around(*self.pivot, 90).translate(23, 42)
  155. trans_added = (Affine2D().rotate_deg_around(*self.pivot, 90) +
  156. Affine2D().translate(23, 42))
  157. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  158. assert_array_almost_equal(trans.transform(self.single_point), [24, 43])
  159. assert_array_almost_equal(trans.transform(self.multiple_points),
  160. [[23, 42], [22, 45], [25, 46]])
  161. def test_scale_plus_other(self):
  162. trans = Affine2D().scale(3, -2).rotate_deg(90)
  163. trans_added = Affine2D().scale(3, -2) + Affine2D().rotate_deg(90)
  164. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  165. assert_array_equal(trans.transform(self.single_point), [2, 3])
  166. assert_array_almost_equal(trans.transform(self.multiple_points),
  167. [[4, 0], [6, 9], [0, 12]])
  168. trans = Affine2D().scale(3, -2).rotate_deg_around(*self.pivot, 90)
  169. trans_added = (Affine2D().scale(3, -2) +
  170. Affine2D().rotate_deg_around(*self.pivot, 90))
  171. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  172. assert_array_equal(trans.transform(self.single_point), [4, 3])
  173. assert_array_almost_equal(trans.transform(self.multiple_points),
  174. [[6, 0], [8, 9], [2, 12]])
  175. trans = (Affine2D().scale(3, -2)
  176. .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
  177. trans_added = (Affine2D().scale(3, -2) +
  178. Affine2D().skew_deg(26.5650512, 14.0362435))
  179. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  180. assert_array_almost_equal(trans.transform(self.single_point), [2, -1.25])
  181. assert_array_almost_equal(trans.transform(self.multiple_points),
  182. [[-2, -4], [6, -3.75], [12, 3]])
  183. trans = Affine2D().scale(3, -2).translate(23, 42)
  184. trans_added = Affine2D().scale(3, -2) + Affine2D().translate(23, 42)
  185. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  186. assert_array_equal(trans.transform(self.single_point), [26, 40])
  187. assert_array_equal(trans.transform(self.multiple_points),
  188. [[23, 38], [32, 36], [35, 42]])
  189. def test_skew_plus_other(self):
  190. # Using ~atan(0.5), ~atan(0.25) produces roundish numbers on output.
  191. trans = Affine2D().skew_deg(26.5650512, 14.0362435).rotate_deg(90)
  192. trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
  193. Affine2D().rotate_deg(90))
  194. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  195. assert_array_almost_equal(trans.transform(self.single_point), [-1.25, 1.5])
  196. assert_array_almost_equal(trans.transform(self.multiple_points),
  197. [[-2, 1], [-3.75, 4.5], [-1, 4]])
  198. trans = (Affine2D().skew_deg(26.5650512, 14.0362435)
  199. .rotate_deg_around(*self.pivot, 90))
  200. trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
  201. Affine2D().rotate_deg_around(*self.pivot, 90))
  202. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  203. assert_array_almost_equal(trans.transform(self.single_point), [0.75, 1.5])
  204. assert_array_almost_equal(trans.transform(self.multiple_points),
  205. [[0, 1], [-1.75, 4.5], [1, 4]])
  206. trans = Affine2D().skew_deg(26.5650512, 14.0362435).scale(3, -2)
  207. trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
  208. Affine2D().scale(3, -2))
  209. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  210. assert_array_almost_equal(trans.transform(self.single_point), [4.5, -2.5])
  211. assert_array_almost_equal(trans.transform(self.multiple_points),
  212. [[3, -4], [13.5, -7.5], [12, -2]])
  213. trans = Affine2D().skew_deg(26.5650512, 14.0362435).translate(23, 42)
  214. trans_added = (Affine2D().skew_deg(26.5650512, 14.0362435) +
  215. Affine2D().translate(23, 42))
  216. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  217. assert_array_almost_equal(trans.transform(self.single_point), [24.5, 43.25])
  218. assert_array_almost_equal(trans.transform(self.multiple_points),
  219. [[24, 44], [27.5, 45.75], [27, 43]])
  220. def test_translate_plus_other(self):
  221. trans = Affine2D().translate(23, 42).rotate_deg(90)
  222. trans_added = Affine2D().translate(23, 42) + Affine2D().rotate_deg(90)
  223. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  224. assert_array_almost_equal(trans.transform(self.single_point), [-43, 24])
  225. assert_array_almost_equal(trans.transform(self.multiple_points),
  226. [[-44, 23], [-45, 26], [-42, 27]])
  227. trans = Affine2D().translate(23, 42).rotate_deg_around(*self.pivot, 90)
  228. trans_added = (Affine2D().translate(23, 42) +
  229. Affine2D().rotate_deg_around(*self.pivot, 90))
  230. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  231. assert_array_almost_equal(trans.transform(self.single_point), [-41, 24])
  232. assert_array_almost_equal(trans.transform(self.multiple_points),
  233. [[-42, 23], [-43, 26], [-40, 27]])
  234. trans = Affine2D().translate(23, 42).scale(3, -2)
  235. trans_added = Affine2D().translate(23, 42) + Affine2D().scale(3, -2)
  236. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  237. assert_array_almost_equal(trans.transform(self.single_point), [72, -86])
  238. assert_array_almost_equal(trans.transform(self.multiple_points),
  239. [[69, -88], [78, -90], [81, -84]])
  240. trans = (Affine2D().translate(23, 42)
  241. .skew_deg(26.5650512, 14.0362435)) # ~atan(0.5), ~atan(0.25)
  242. trans_added = (Affine2D().translate(23, 42) +
  243. Affine2D().skew_deg(26.5650512, 14.0362435))
  244. assert_array_equal(trans.get_matrix(), trans_added.get_matrix())
  245. assert_array_almost_equal(trans.transform(self.single_point), [45.5, 49])
  246. assert_array_almost_equal(trans.transform(self.multiple_points),
  247. [[45, 49.75], [48.5, 51.5], [48, 48.75]])
  248. def test_invalid_transform(self):
  249. t = mtransforms.Affine2D()
  250. # There are two different exceptions, since the wrong number of
  251. # dimensions is caught when constructing an array_view, and that
  252. # raises a ValueError, and a wrong shape with a possible number
  253. # of dimensions is caught by our CALL_CPP macro, which always
  254. # raises the less precise RuntimeError.
  255. with pytest.raises(ValueError):
  256. t.transform(1)
  257. with pytest.raises(ValueError):
  258. t.transform([[[1]]])
  259. with pytest.raises(RuntimeError):
  260. t.transform([])
  261. with pytest.raises(RuntimeError):
  262. t.transform([1])
  263. with pytest.raises(ValueError):
  264. t.transform([[1]])
  265. with pytest.raises(ValueError):
  266. t.transform([[1, 2, 3]])
  267. def test_copy(self):
  268. a = mtransforms.Affine2D()
  269. b = mtransforms.Affine2D()
  270. s = a + b
  271. # Updating a dependee should invalidate a copy of the dependent.
  272. s.get_matrix() # resolve it.
  273. s1 = copy.copy(s)
  274. assert not s._invalid and not s1._invalid
  275. a.translate(1, 2)
  276. assert s._invalid and s1._invalid
  277. assert (s1.get_matrix() == a.get_matrix()).all()
  278. # Updating a copy of a dependee shouldn't invalidate a dependent.
  279. s.get_matrix() # resolve it.
  280. b1 = copy.copy(b)
  281. b1.translate(3, 4)
  282. assert not s._invalid
  283. assert_array_equal(s.get_matrix(), a.get_matrix())
  284. def test_deepcopy(self):
  285. a = mtransforms.Affine2D()
  286. b = mtransforms.Affine2D()
  287. s = a + b
  288. # Updating a dependee shouldn't invalidate a deepcopy of the dependent.
  289. s.get_matrix() # resolve it.
  290. s1 = copy.deepcopy(s)
  291. assert not s._invalid and not s1._invalid
  292. a.translate(1, 2)
  293. assert s._invalid and not s1._invalid
  294. assert_array_equal(s1.get_matrix(), mtransforms.Affine2D().get_matrix())
  295. # Updating a deepcopy of a dependee shouldn't invalidate a dependent.
  296. s.get_matrix() # resolve it.
  297. b1 = copy.deepcopy(b)
  298. b1.translate(3, 4)
  299. assert not s._invalid
  300. assert_array_equal(s.get_matrix(), a.get_matrix())
  301. class TestAffineDeltaTransform:
  302. def test_invalidate(self):
  303. before = np.array([[1.0, 4.0, 0.0],
  304. [5.0, 1.0, 0.0],
  305. [0.0, 0.0, 1.0]])
  306. after = np.array([[1.0, 3.0, 0.0],
  307. [5.0, 1.0, 0.0],
  308. [0.0, 0.0, 1.0]])
  309. # Translation and skew present
  310. base = mtransforms.Affine2D.from_values(1, 5, 4, 1, 2, 3)
  311. t = mtransforms.AffineDeltaTransform(base)
  312. assert_array_equal(t.get_matrix(), before)
  313. # Mess with the internal structure of `base` without invalidating
  314. # This should not affect this transform because it's a passthrough:
  315. # it's always invalid
  316. base.get_matrix()[0, 1:] = 3
  317. assert_array_equal(t.get_matrix(), after)
  318. # Invalidate the base
  319. base.invalidate()
  320. assert_array_equal(t.get_matrix(), after)
  321. def test_non_affine_caching():
  322. class AssertingNonAffineTransform(mtransforms.Transform):
  323. """
  324. This transform raises an assertion error when called when it
  325. shouldn't be and ``self.raise_on_transform`` is True.
  326. """
  327. input_dims = output_dims = 2
  328. is_affine = False
  329. def __init__(self, *args, **kwargs):
  330. super().__init__(*args, **kwargs)
  331. self.raise_on_transform = False
  332. self.underlying_transform = mtransforms.Affine2D().scale(10, 10)
  333. def transform_path_non_affine(self, path):
  334. assert not self.raise_on_transform, \
  335. 'Invalidated affine part of transform unnecessarily.'
  336. return self.underlying_transform.transform_path(path)
  337. transform_path = transform_path_non_affine
  338. def transform_non_affine(self, path):
  339. assert not self.raise_on_transform, \
  340. 'Invalidated affine part of transform unnecessarily.'
  341. return self.underlying_transform.transform(path)
  342. transform = transform_non_affine
  343. my_trans = AssertingNonAffineTransform()
  344. ax = plt.axes()
  345. plt.plot(np.arange(10), transform=my_trans + ax.transData)
  346. plt.draw()
  347. # enable the transform to raise an exception if it's non-affine transform
  348. # method is triggered again.
  349. my_trans.raise_on_transform = True
  350. ax.transAxes.invalidate()
  351. plt.draw()
  352. def test_external_transform_api():
  353. class ScaledBy:
  354. def __init__(self, scale_factor):
  355. self._scale_factor = scale_factor
  356. def _as_mpl_transform(self, axes):
  357. return (mtransforms.Affine2D().scale(self._scale_factor)
  358. + axes.transData)
  359. ax = plt.axes()
  360. line, = plt.plot(np.arange(10), transform=ScaledBy(10))
  361. ax.set_xlim(0, 100)
  362. ax.set_ylim(0, 100)
  363. # assert that the top transform of the line is the scale transform.
  364. assert_allclose(line.get_transform()._a.get_matrix(),
  365. mtransforms.Affine2D().scale(10).get_matrix())
  366. @image_comparison(['pre_transform_data'], remove_text=True, style='mpl20',
  367. tol=0.05)
  368. def test_pre_transform_plotting():
  369. # a catch-all for as many as possible plot layouts which handle
  370. # pre-transforming the data NOTE: The axis range is important in this
  371. # plot. It should be x10 what the data suggests it should be
  372. ax = plt.axes()
  373. times10 = mtransforms.Affine2D().scale(10)
  374. ax.contourf(np.arange(48).reshape(6, 8), transform=times10 + ax.transData)
  375. ax.pcolormesh(np.linspace(0, 4, 7),
  376. np.linspace(5.5, 8, 9),
  377. np.arange(48).reshape(8, 6),
  378. transform=times10 + ax.transData)
  379. ax.scatter(np.linspace(0, 10), np.linspace(10, 0),
  380. transform=times10 + ax.transData)
  381. x = np.linspace(8, 10, 20)
  382. y = np.linspace(1, 5, 20)
  383. u = 2*np.sin(x) + np.cos(y[:, np.newaxis])
  384. v = np.sin(x) - np.cos(y[:, np.newaxis])
  385. ax.streamplot(x, y, u, v, transform=times10 + ax.transData,
  386. linewidth=np.hypot(u, v))
  387. # reduce the vector data down a bit for barb and quiver plotting
  388. x, y = x[::3], y[::3]
  389. u, v = u[::3, ::3], v[::3, ::3]
  390. ax.quiver(x, y + 5, u, v, transform=times10 + ax.transData)
  391. ax.barbs(x - 3, y + 5, u**2, v**2, transform=times10 + ax.transData)
  392. def test_contour_pre_transform_limits():
  393. ax = plt.axes()
  394. xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
  395. ax.contourf(xs, ys, np.log(xs * ys),
  396. transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
  397. expected = np.array([[1.5, 1.24],
  398. [2., 1.25]])
  399. assert_almost_equal(expected, ax.dataLim.get_points())
  400. def test_pcolor_pre_transform_limits():
  401. # Based on test_contour_pre_transform_limits()
  402. ax = plt.axes()
  403. xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
  404. ax.pcolor(xs, ys, np.log(xs * ys)[:-1, :-1],
  405. transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
  406. expected = np.array([[1.5, 1.24],
  407. [2., 1.25]])
  408. assert_almost_equal(expected, ax.dataLim.get_points())
  409. def test_pcolormesh_pre_transform_limits():
  410. # Based on test_contour_pre_transform_limits()
  411. ax = plt.axes()
  412. xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20))
  413. ax.pcolormesh(xs, ys, np.log(xs * ys)[:-1, :-1],
  414. transform=mtransforms.Affine2D().scale(0.1) + ax.transData)
  415. expected = np.array([[1.5, 1.24],
  416. [2., 1.25]])
  417. assert_almost_equal(expected, ax.dataLim.get_points())
  418. def test_pcolormesh_gouraud_nans():
  419. np.random.seed(19680801)
  420. values = np.linspace(0, 180, 3)
  421. radii = np.linspace(100, 1000, 10)
  422. z, y = np.meshgrid(values, radii)
  423. x = np.radians(np.random.rand(*z.shape) * 100)
  424. fig = plt.figure()
  425. ax = fig.add_subplot(111, projection="polar")
  426. # Setting the limit to cause clipping of the r values causes NaN to be
  427. # introduced; these should not crash but be ignored as in other path
  428. # operations.
  429. ax.set_rlim(101, 1000)
  430. ax.pcolormesh(x, y, z, shading="gouraud")
  431. fig.canvas.draw()
  432. def test_Affine2D_from_values():
  433. points = np.array([[0, 0],
  434. [10, 20],
  435. [-1, 0],
  436. ])
  437. t = mtransforms.Affine2D.from_values(1, 0, 0, 0, 0, 0)
  438. actual = t.transform(points)
  439. expected = np.array([[0, 0], [10, 0], [-1, 0]])
  440. assert_almost_equal(actual, expected)
  441. t = mtransforms.Affine2D.from_values(0, 2, 0, 0, 0, 0)
  442. actual = t.transform(points)
  443. expected = np.array([[0, 0], [0, 20], [0, -2]])
  444. assert_almost_equal(actual, expected)
  445. t = mtransforms.Affine2D.from_values(0, 0, 3, 0, 0, 0)
  446. actual = t.transform(points)
  447. expected = np.array([[0, 0], [60, 0], [0, 0]])
  448. assert_almost_equal(actual, expected)
  449. t = mtransforms.Affine2D.from_values(0, 0, 0, 4, 0, 0)
  450. actual = t.transform(points)
  451. expected = np.array([[0, 0], [0, 80], [0, 0]])
  452. assert_almost_equal(actual, expected)
  453. t = mtransforms.Affine2D.from_values(0, 0, 0, 0, 5, 0)
  454. actual = t.transform(points)
  455. expected = np.array([[5, 0], [5, 0], [5, 0]])
  456. assert_almost_equal(actual, expected)
  457. t = mtransforms.Affine2D.from_values(0, 0, 0, 0, 0, 6)
  458. actual = t.transform(points)
  459. expected = np.array([[0, 6], [0, 6], [0, 6]])
  460. assert_almost_equal(actual, expected)
  461. def test_affine_inverted_invalidated():
  462. # Ensure that the an affine transform is not declared valid on access
  463. point = [1.0, 1.0]
  464. t = mtransforms.Affine2D()
  465. assert_almost_equal(point, t.transform(t.inverted().transform(point)))
  466. # Change and access the transform
  467. t.translate(1.0, 1.0).get_matrix()
  468. assert_almost_equal(point, t.transform(t.inverted().transform(point)))
  469. def test_clipping_of_log():
  470. # issue 804
  471. path = Path._create_closed([(0.2, -99), (0.4, -99), (0.4, 20), (0.2, 20)])
  472. # something like this happens in plotting logarithmic histograms
  473. trans = mtransforms.BlendedGenericTransform(
  474. mtransforms.Affine2D(), scale.LogTransform(10, 'clip'))
  475. tpath = trans.transform_path_non_affine(path)
  476. result = tpath.iter_segments(trans.get_affine(),
  477. clip=(0, 0, 100, 100),
  478. simplify=False)
  479. tpoints, tcodes = zip(*result)
  480. assert_allclose(tcodes, path.codes[:-1]) # No longer closed.
  481. class NonAffineForTest(mtransforms.Transform):
  482. """
  483. A class which looks like a non affine transform, but does whatever
  484. the given transform does (even if it is affine). This is very useful
  485. for testing NonAffine behaviour with a simple Affine transform.
  486. """
  487. is_affine = False
  488. output_dims = 2
  489. input_dims = 2
  490. def __init__(self, real_trans, *args, **kwargs):
  491. self.real_trans = real_trans
  492. super().__init__(*args, **kwargs)
  493. def transform_non_affine(self, values):
  494. return self.real_trans.transform(values)
  495. def transform_path_non_affine(self, path):
  496. return self.real_trans.transform_path(path)
  497. class TestBasicTransform:
  498. def setup_method(self):
  499. self.ta1 = mtransforms.Affine2D(shorthand_name='ta1').rotate(np.pi / 2)
  500. self.ta2 = mtransforms.Affine2D(shorthand_name='ta2').translate(10, 0)
  501. self.ta3 = mtransforms.Affine2D(shorthand_name='ta3').scale(1, 2)
  502. self.tn1 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
  503. shorthand_name='tn1')
  504. self.tn2 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
  505. shorthand_name='tn2')
  506. self.tn3 = NonAffineForTest(mtransforms.Affine2D().translate(1, 2),
  507. shorthand_name='tn3')
  508. # creates a transform stack which looks like ((A, (N, A)), A)
  509. self.stack1 = (self.ta1 + (self.tn1 + self.ta2)) + self.ta3
  510. # creates a transform stack which looks like (((A, N), A), A)
  511. self.stack2 = self.ta1 + self.tn1 + self.ta2 + self.ta3
  512. # creates a transform stack which is a subset of stack2
  513. self.stack2_subset = self.tn1 + self.ta2 + self.ta3
  514. # when in debug, the transform stacks can produce dot images:
  515. # self.stack1.write_graphviz(file('stack1.dot', 'w'))
  516. # self.stack2.write_graphviz(file('stack2.dot', 'w'))
  517. # self.stack2_subset.write_graphviz(file('stack2_subset.dot', 'w'))
  518. def test_transform_depth(self):
  519. assert self.stack1.depth == 4
  520. assert self.stack2.depth == 4
  521. assert self.stack2_subset.depth == 3
  522. def test_left_to_right_iteration(self):
  523. stack3 = (self.ta1 + (self.tn1 + (self.ta2 + self.tn2))) + self.ta3
  524. # stack3.write_graphviz(file('stack3.dot', 'w'))
  525. target_transforms = [stack3,
  526. (self.tn1 + (self.ta2 + self.tn2)) + self.ta3,
  527. (self.ta2 + self.tn2) + self.ta3,
  528. self.tn2 + self.ta3,
  529. self.ta3,
  530. ]
  531. r = [rh for _, rh in stack3._iter_break_from_left_to_right()]
  532. assert len(r) == len(target_transforms)
  533. for target_stack, stack in zip(target_transforms, r):
  534. assert target_stack == stack
  535. def test_transform_shortcuts(self):
  536. assert self.stack1 - self.stack2_subset == self.ta1
  537. assert self.stack2 - self.stack2_subset == self.ta1
  538. assert self.stack2_subset - self.stack2 == self.ta1.inverted()
  539. assert (self.stack2_subset - self.stack2).depth == 1
  540. with pytest.raises(ValueError):
  541. self.stack1 - self.stack2
  542. aff1 = self.ta1 + (self.ta2 + self.ta3)
  543. aff2 = self.ta2 + self.ta3
  544. assert aff1 - aff2 == self.ta1
  545. assert aff1 - self.ta2 == aff1 + self.ta2.inverted()
  546. assert self.stack1 - self.ta3 == self.ta1 + (self.tn1 + self.ta2)
  547. assert self.stack2 - self.ta3 == self.ta1 + self.tn1 + self.ta2
  548. assert ((self.ta2 + self.ta3) - self.ta3 + self.ta3 ==
  549. self.ta2 + self.ta3)
  550. def test_contains_branch(self):
  551. r1 = (self.ta2 + self.ta1)
  552. r2 = (self.ta2 + self.ta1)
  553. assert r1 == r2
  554. assert r1 != self.ta1
  555. assert r1.contains_branch(r2)
  556. assert r1.contains_branch(self.ta1)
  557. assert not r1.contains_branch(self.ta2)
  558. assert not r1.contains_branch(self.ta2 + self.ta2)
  559. assert r1 == r2
  560. assert self.stack1.contains_branch(self.ta3)
  561. assert self.stack2.contains_branch(self.ta3)
  562. assert self.stack1.contains_branch(self.stack2_subset)
  563. assert self.stack2.contains_branch(self.stack2_subset)
  564. assert not self.stack2_subset.contains_branch(self.stack1)
  565. assert not self.stack2_subset.contains_branch(self.stack2)
  566. assert self.stack1.contains_branch(self.ta2 + self.ta3)
  567. assert self.stack2.contains_branch(self.ta2 + self.ta3)
  568. assert not self.stack1.contains_branch(self.tn1 + self.ta2)
  569. blend = mtransforms.BlendedGenericTransform(self.tn2, self.stack2)
  570. x, y = blend.contains_branch_seperately(self.stack2_subset)
  571. stack_blend = self.tn3 + blend
  572. sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset)
  573. assert x is sx is False
  574. assert y is sy is True
  575. def test_affine_simplification(self):
  576. # tests that a transform stack only calls as much is absolutely
  577. # necessary "non-affine" allowing the best possible optimization with
  578. # complex transformation stacks.
  579. points = np.array([[0, 0], [10, 20], [np.nan, 1], [-1, 0]],
  580. dtype=np.float64)
  581. na_pts = self.stack1.transform_non_affine(points)
  582. all_pts = self.stack1.transform(points)
  583. na_expected = np.array([[1., 2.], [-19., 12.],
  584. [np.nan, np.nan], [1., 1.]], dtype=np.float64)
  585. all_expected = np.array([[11., 4.], [-9., 24.],
  586. [np.nan, np.nan], [11., 2.]],
  587. dtype=np.float64)
  588. # check we have the expected results from doing the affine part only
  589. assert_array_almost_equal(na_pts, na_expected)
  590. # check we have the expected results from a full transformation
  591. assert_array_almost_equal(all_pts, all_expected)
  592. # check we have the expected results from doing the transformation in
  593. # two steps
  594. assert_array_almost_equal(self.stack1.transform_affine(na_pts),
  595. all_expected)
  596. # check that getting the affine transformation first, then fully
  597. # transforming using that yields the same result as before.
  598. assert_array_almost_equal(self.stack1.get_affine().transform(na_pts),
  599. all_expected)
  600. # check that the affine part of stack1 & stack2 are equivalent
  601. # (i.e. the optimization is working)
  602. expected_result = (self.ta2 + self.ta3).get_matrix()
  603. result = self.stack1.get_affine().get_matrix()
  604. assert_array_equal(expected_result, result)
  605. result = self.stack2.get_affine().get_matrix()
  606. assert_array_equal(expected_result, result)
  607. class TestTransformPlotInterface:
  608. def test_line_extent_axes_coords(self):
  609. # a simple line in axes coordinates
  610. ax = plt.axes()
  611. ax.plot([0.1, 1.2, 0.8], [0.9, 0.5, 0.8], transform=ax.transAxes)
  612. assert_array_equal(ax.dataLim.get_points(),
  613. np.array([[np.inf, np.inf],
  614. [-np.inf, -np.inf]]))
  615. def test_line_extent_data_coords(self):
  616. # a simple line in data coordinates
  617. ax = plt.axes()
  618. ax.plot([0.1, 1.2, 0.8], [0.9, 0.5, 0.8], transform=ax.transData)
  619. assert_array_equal(ax.dataLim.get_points(),
  620. np.array([[0.1, 0.5], [1.2, 0.9]]))
  621. def test_line_extent_compound_coords1(self):
  622. # a simple line in data coordinates in the y component, and in axes
  623. # coordinates in the x
  624. ax = plt.axes()
  625. trans = mtransforms.blended_transform_factory(ax.transAxes,
  626. ax.transData)
  627. ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
  628. assert_array_equal(ax.dataLim.get_points(),
  629. np.array([[np.inf, -5.],
  630. [-np.inf, 35.]]))
  631. def test_line_extent_predata_transform_coords(self):
  632. # a simple line in (offset + data) coordinates
  633. ax = plt.axes()
  634. trans = mtransforms.Affine2D().scale(10) + ax.transData
  635. ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
  636. assert_array_equal(ax.dataLim.get_points(),
  637. np.array([[1., -50.], [12., 350.]]))
  638. def test_line_extent_compound_coords2(self):
  639. # a simple line in (offset + data) coordinates in the y component, and
  640. # in axes coordinates in the x
  641. ax = plt.axes()
  642. trans = mtransforms.blended_transform_factory(
  643. ax.transAxes, mtransforms.Affine2D().scale(10) + ax.transData)
  644. ax.plot([0.1, 1.2, 0.8], [35, -5, 18], transform=trans)
  645. assert_array_equal(ax.dataLim.get_points(),
  646. np.array([[np.inf, -50.], [-np.inf, 350.]]))
  647. def test_line_extents_affine(self):
  648. ax = plt.axes()
  649. offset = mtransforms.Affine2D().translate(10, 10)
  650. plt.plot(np.arange(10), transform=offset + ax.transData)
  651. expected_data_lim = np.array([[0., 0.], [9., 9.]]) + 10
  652. assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
  653. def test_line_extents_non_affine(self):
  654. ax = plt.axes()
  655. offset = mtransforms.Affine2D().translate(10, 10)
  656. na_offset = NonAffineForTest(mtransforms.Affine2D().translate(10, 10))
  657. plt.plot(np.arange(10), transform=offset + na_offset + ax.transData)
  658. expected_data_lim = np.array([[0., 0.], [9., 9.]]) + 20
  659. assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
  660. def test_pathc_extents_non_affine(self):
  661. ax = plt.axes()
  662. offset = mtransforms.Affine2D().translate(10, 10)
  663. na_offset = NonAffineForTest(mtransforms.Affine2D().translate(10, 10))
  664. pth = Path([[0, 0], [0, 10], [10, 10], [10, 0]])
  665. patch = mpatches.PathPatch(pth,
  666. transform=offset + na_offset + ax.transData)
  667. ax.add_patch(patch)
  668. expected_data_lim = np.array([[0., 0.], [10., 10.]]) + 20
  669. assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
  670. def test_pathc_extents_affine(self):
  671. ax = plt.axes()
  672. offset = mtransforms.Affine2D().translate(10, 10)
  673. pth = Path([[0, 0], [0, 10], [10, 10], [10, 0]])
  674. patch = mpatches.PathPatch(pth, transform=offset + ax.transData)
  675. ax.add_patch(patch)
  676. expected_data_lim = np.array([[0., 0.], [10., 10.]]) + 10
  677. assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
  678. def test_line_extents_for_non_affine_transData(self):
  679. ax = plt.axes(projection='polar')
  680. # add 10 to the radius of the data
  681. offset = mtransforms.Affine2D().translate(0, 10)
  682. plt.plot(np.arange(10), transform=offset + ax.transData)
  683. # the data lim of a polar plot is stored in coordinates
  684. # before a transData transformation, hence the data limits
  685. # are not what is being shown on the actual plot.
  686. expected_data_lim = np.array([[0., 0.], [9., 9.]]) + [0, 10]
  687. assert_array_almost_equal(ax.dataLim.get_points(), expected_data_lim)
  688. def assert_bbox_eq(bbox1, bbox2):
  689. assert_array_equal(bbox1.bounds, bbox2.bounds)
  690. def test_bbox_frozen_copies_minpos():
  691. bbox = mtransforms.Bbox.from_extents(0.0, 0.0, 1.0, 1.0, minpos=1.0)
  692. frozen = bbox.frozen()
  693. assert_array_equal(frozen.minpos, bbox.minpos)
  694. def test_bbox_intersection():
  695. bbox_from_ext = mtransforms.Bbox.from_extents
  696. inter = mtransforms.Bbox.intersection
  697. r1 = bbox_from_ext(0, 0, 1, 1)
  698. r2 = bbox_from_ext(0.5, 0.5, 1.5, 1.5)
  699. r3 = bbox_from_ext(0.5, 0, 0.75, 0.75)
  700. r4 = bbox_from_ext(0.5, 1.5, 1, 2.5)
  701. r5 = bbox_from_ext(1, 1, 2, 2)
  702. # self intersection -> no change
  703. assert_bbox_eq(inter(r1, r1), r1)
  704. # simple intersection
  705. assert_bbox_eq(inter(r1, r2), bbox_from_ext(0.5, 0.5, 1, 1))
  706. # r3 contains r2
  707. assert_bbox_eq(inter(r1, r3), r3)
  708. # no intersection
  709. assert inter(r1, r4) is None
  710. # single point
  711. assert_bbox_eq(inter(r1, r5), bbox_from_ext(1, 1, 1, 1))
  712. def test_bbox_as_strings():
  713. b = mtransforms.Bbox([[.5, 0], [.75, .75]])
  714. assert_bbox_eq(b, eval(repr(b), {'Bbox': mtransforms.Bbox}))
  715. asdict = eval(str(b), {'Bbox': dict})
  716. for k, v in asdict.items():
  717. assert getattr(b, k) == v
  718. fmt = '.1f'
  719. asdict = eval(format(b, fmt), {'Bbox': dict})
  720. for k, v in asdict.items():
  721. assert eval(format(getattr(b, k), fmt)) == v
  722. def test_str_transform():
  723. # The str here should not be considered as "absolutely stable", and may be
  724. # reformatted later; this is just a smoketest for __str__.
  725. assert str(plt.subplot(projection="polar").transData) == """\
  726. CompositeGenericTransform(
  727. CompositeGenericTransform(
  728. CompositeGenericTransform(
  729. TransformWrapper(
  730. BlendedAffine2D(
  731. IdentityTransform(),
  732. IdentityTransform())),
  733. CompositeAffine2D(
  734. Affine2D().scale(1.0),
  735. Affine2D().scale(1.0))),
  736. PolarTransform(
  737. PolarAxes(0.125,0.1;0.775x0.8),
  738. use_rmin=True,
  739. apply_theta_transforms=False)),
  740. CompositeGenericTransform(
  741. CompositeGenericTransform(
  742. PolarAffine(
  743. TransformWrapper(
  744. BlendedAffine2D(
  745. IdentityTransform(),
  746. IdentityTransform())),
  747. LockableBbox(
  748. Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0),
  749. [[-- --]
  750. [-- --]])),
  751. BboxTransformFrom(
  752. _WedgeBbox(
  753. (0.5, 0.5),
  754. TransformedBbox(
  755. Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0),
  756. CompositeAffine2D(
  757. Affine2D().scale(1.0),
  758. Affine2D().scale(1.0))),
  759. LockableBbox(
  760. Bbox(x0=0.0, y0=0.0, x1=6.283185307179586, y1=1.0),
  761. [[-- --]
  762. [-- --]])))),
  763. BboxTransformTo(
  764. TransformedBbox(
  765. Bbox(x0=0.125, y0=0.09999999999999998, x1=0.9, y1=0.9),
  766. BboxTransformTo(
  767. TransformedBbox(
  768. Bbox(x0=0.0, y0=0.0, x1=8.0, y1=6.0),
  769. Affine2D().scale(80.0)))))))"""
  770. def test_transform_single_point():
  771. t = mtransforms.Affine2D()
  772. r = t.transform_affine((1, 1))
  773. assert r.shape == (2,)
  774. def test_log_transform():
  775. # Tests that the last line runs without exception (previously the
  776. # transform would fail if one of the axes was logarithmic).
  777. fig, ax = plt.subplots()
  778. ax.set_yscale('log')
  779. ax.transData.transform((1, 1))
  780. def test_nan_overlap():
  781. a = mtransforms.Bbox([[0, 0], [1, 1]])
  782. b = mtransforms.Bbox([[0, 0], [1, np.nan]])
  783. assert not a.overlaps(b)
  784. def test_transform_angles():
  785. t = mtransforms.Affine2D() # Identity transform
  786. angles = np.array([20, 45, 60])
  787. points = np.array([[0, 0], [1, 1], [2, 2]])
  788. # Identity transform does not change angles
  789. new_angles = t.transform_angles(angles, points)
  790. assert_array_almost_equal(angles, new_angles)
  791. # points missing a 2nd dimension
  792. with pytest.raises(ValueError):
  793. t.transform_angles(angles, points[0:2, 0:1])
  794. # Number of angles != Number of points
  795. with pytest.raises(ValueError):
  796. t.transform_angles(angles, points[0:2, :])
  797. def test_nonsingular():
  798. # test for zero-expansion type cases; other cases may be added later
  799. zero_expansion = np.array([-0.001, 0.001])
  800. cases = [(0, np.nan), (0, 0), (0, 7.9e-317)]
  801. for args in cases:
  802. out = np.array(mtransforms.nonsingular(*args))
  803. assert_array_equal(out, zero_expansion)
  804. def test_transformed_path():
  805. points = [(0, 0), (1, 0), (1, 1), (0, 1)]
  806. path = Path(points, closed=True)
  807. trans = mtransforms.Affine2D()
  808. trans_path = mtransforms.TransformedPath(path, trans)
  809. assert_allclose(trans_path.get_fully_transformed_path().vertices, points)
  810. # Changing the transform should change the result.
  811. r2 = 1 / np.sqrt(2)
  812. trans.rotate(np.pi / 4)
  813. assert_allclose(trans_path.get_fully_transformed_path().vertices,
  814. [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
  815. atol=1e-15)
  816. # Changing the path does not change the result (it's cached).
  817. path.points = [(0, 0)] * 4
  818. assert_allclose(trans_path.get_fully_transformed_path().vertices,
  819. [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
  820. atol=1e-15)
  821. def test_transformed_patch_path():
  822. trans = mtransforms.Affine2D()
  823. patch = mpatches.Wedge((0, 0), 1, 45, 135, transform=trans)
  824. tpatch = mtransforms.TransformedPatchPath(patch)
  825. points = tpatch.get_fully_transformed_path().vertices
  826. # Changing the transform should change the result.
  827. trans.scale(2)
  828. assert_allclose(tpatch.get_fully_transformed_path().vertices, points * 2)
  829. # Changing the path should change the result (and cancel out the scaling
  830. # from the transform).
  831. patch.set_radius(0.5)
  832. assert_allclose(tpatch.get_fully_transformed_path().vertices, points)
  833. @pytest.mark.parametrize('locked_element', ['x0', 'y0', 'x1', 'y1'])
  834. def test_lockable_bbox(locked_element):
  835. other_elements = ['x0', 'y0', 'x1', 'y1']
  836. other_elements.remove(locked_element)
  837. orig = mtransforms.Bbox.unit()
  838. locked = mtransforms.LockableBbox(orig, **{locked_element: 2})
  839. # LockableBbox should keep its locked element as specified in __init__.
  840. assert getattr(locked, locked_element) == 2
  841. assert getattr(locked, 'locked_' + locked_element) == 2
  842. for elem in other_elements:
  843. assert getattr(locked, elem) == getattr(orig, elem)
  844. # Changing underlying Bbox should update everything but locked element.
  845. orig.set_points(orig.get_points() + 10)
  846. assert getattr(locked, locked_element) == 2
  847. assert getattr(locked, 'locked_' + locked_element) == 2
  848. for elem in other_elements:
  849. assert getattr(locked, elem) == getattr(orig, elem)
  850. # Unlocking element should revert values back to the underlying Bbox.
  851. setattr(locked, 'locked_' + locked_element, None)
  852. assert getattr(locked, 'locked_' + locked_element) is None
  853. assert np.all(orig.get_points() == locked.get_points())
  854. # Relocking an element should change its value, but not others.
  855. setattr(locked, 'locked_' + locked_element, 3)
  856. assert getattr(locked, locked_element) == 3
  857. assert getattr(locked, 'locked_' + locked_element) == 3
  858. for elem in other_elements:
  859. assert getattr(locked, elem) == getattr(orig, elem)
  860. def test_transformwrapper():
  861. t = mtransforms.TransformWrapper(mtransforms.Affine2D())
  862. with pytest.raises(ValueError, match=(
  863. r"The input and output dims of the new child \(1, 1\) "
  864. r"do not match those of current child \(2, 2\)")):
  865. t.set(scale.LogTransform(10))
  866. @check_figures_equal(extensions=["png"])
  867. def test_scale_swapping(fig_test, fig_ref):
  868. np.random.seed(19680801)
  869. samples = np.random.normal(size=10)
  870. x = np.linspace(-5, 5, 10)
  871. for fig, log_state in zip([fig_test, fig_ref], [True, False]):
  872. ax = fig.subplots()
  873. ax.hist(samples, log=log_state, density=True)
  874. ax.plot(x, np.exp(-(x**2) / 2) / np.sqrt(2 * np.pi))
  875. fig.canvas.draw()
  876. ax.set_yscale('linear')
  877. def test_offset_copy_errors():
  878. with pytest.raises(ValueError,
  879. match="'fontsize' is not a valid value for units;"
  880. " supported values are 'dots', 'points', 'inches'"):
  881. mtransforms.offset_copy(None, units='fontsize')
  882. with pytest.raises(ValueError,
  883. match='For units of inches or points a fig kwarg is needed'):
  884. mtransforms.offset_copy(None, units='inches')
  885. def test_transformedbbox_contains():
  886. bb = TransformedBbox(Bbox.unit(), Affine2D().rotate_deg(30))
  887. assert bb.contains(.8, .5)
  888. assert bb.contains(-.4, .85)
  889. assert not bb.contains(.9, .5)
  890. bb = TransformedBbox(Bbox.unit(), Affine2D().translate(.25, .5))
  891. assert bb.contains(1.25, 1.5)
  892. assert not bb.fully_contains(1.25, 1.5)
  893. assert not bb.fully_contains(.1, .1)
  894. def test_interval_contains():
  895. assert mtransforms.interval_contains((0, 1), 0.5)
  896. assert mtransforms.interval_contains((0, 1), 0)
  897. assert mtransforms.interval_contains((0, 1), 1)
  898. assert not mtransforms.interval_contains((0, 1), -1)
  899. assert not mtransforms.interval_contains((0, 1), 2)
  900. assert mtransforms.interval_contains((1, 0), 0.5)
  901. def test_interval_contains_open():
  902. assert mtransforms.interval_contains_open((0, 1), 0.5)
  903. assert not mtransforms.interval_contains_open((0, 1), 0)
  904. assert not mtransforms.interval_contains_open((0, 1), 1)
  905. assert not mtransforms.interval_contains_open((0, 1), -1)
  906. assert not mtransforms.interval_contains_open((0, 1), 2)
  907. assert mtransforms.interval_contains_open((1, 0), 0.5)
  908. def test_scaledrotation_initialization():
  909. """Test that the ScaledRotation object is initialized correctly."""
  910. theta = 1.0 # Arbitrary theta value for testing
  911. trans_shift = MagicMock() # Mock the trans_shift transformation
  912. scaled_rot = _ScaledRotation(theta, trans_shift)
  913. assert scaled_rot._theta == theta
  914. assert scaled_rot._trans_shift == trans_shift
  915. assert scaled_rot._mtx is None
  916. def test_scaledrotation_get_matrix_invalid():
  917. """Test get_matrix when the matrix is invalid and needs recalculation."""
  918. theta = np.pi / 2
  919. trans_shift = MagicMock(transform=MagicMock(return_value=[[theta, 0]]))
  920. scaled_rot = _ScaledRotation(theta, trans_shift)
  921. scaled_rot._invalid = True
  922. matrix = scaled_rot.get_matrix()
  923. trans_shift.transform.assert_called_once_with([[theta, 0]])
  924. expected_rotation = np.array([[0, -1],
  925. [1, 0]])
  926. assert matrix is not None
  927. assert_allclose(matrix[:2, :2], expected_rotation, atol=1e-15)