test_path.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import copy
  2. import numpy as np
  3. from numpy.testing import assert_array_equal
  4. import pytest
  5. from matplotlib import patches
  6. from matplotlib.path import Path
  7. from matplotlib.patches import Polygon
  8. from matplotlib.testing.decorators import image_comparison
  9. import matplotlib.pyplot as plt
  10. from matplotlib import transforms
  11. from matplotlib.backend_bases import MouseEvent
  12. def test_empty_closed_path():
  13. path = Path(np.zeros((0, 2)), closed=True)
  14. assert path.vertices.shape == (0, 2)
  15. assert path.codes is None
  16. def test_readonly_path():
  17. path = Path.unit_circle()
  18. def modify_vertices():
  19. path.vertices = path.vertices * 2.0
  20. with pytest.raises(AttributeError):
  21. modify_vertices()
  22. def test_point_in_path():
  23. # Test #1787
  24. verts2 = [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]
  25. path = Path(verts2, closed=True)
  26. points = [(0.5, 0.5), (1.5, 0.5)]
  27. ret = path.contains_points(points)
  28. assert ret.dtype == 'bool'
  29. np.testing.assert_equal(ret, [True, False])
  30. def test_contains_points_negative_radius():
  31. path = Path.unit_circle()
  32. points = [(0.0, 0.0), (1.25, 0.0), (0.9, 0.9)]
  33. result = path.contains_points(points, radius=-0.5)
  34. np.testing.assert_equal(result, [True, False, False])
  35. def test_point_in_path_nan():
  36. box = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
  37. p = Path(box)
  38. test = np.array([[np.nan, 0.5]])
  39. contains = p.contains_points(test)
  40. assert len(contains) == 1
  41. assert not contains[0]
  42. def test_nonlinear_containment():
  43. fig, ax = plt.subplots()
  44. ax.set(xscale="log", ylim=(0, 1))
  45. polygon = ax.axvspan(1, 10)
  46. assert polygon.get_path().contains_point(
  47. ax.transData.transform((5, .5)), ax.transData)
  48. assert not polygon.get_path().contains_point(
  49. ax.transData.transform((.5, .5)), ax.transData)
  50. assert not polygon.get_path().contains_point(
  51. ax.transData.transform((50, .5)), ax.transData)
  52. @image_comparison(['arrow_contains_point.png'],
  53. remove_text=True, style='mpl20')
  54. def test_arrow_contains_point():
  55. # fix bug (#8384)
  56. fig, ax = plt.subplots()
  57. ax.set_xlim((0, 2))
  58. ax.set_ylim((0, 2))
  59. # create an arrow with Curve style
  60. arrow = patches.FancyArrowPatch((0.5, 0.25), (1.5, 0.75),
  61. arrowstyle='->',
  62. mutation_scale=40)
  63. ax.add_patch(arrow)
  64. # create an arrow with Bracket style
  65. arrow1 = patches.FancyArrowPatch((0.5, 1), (1.5, 1.25),
  66. arrowstyle=']-[',
  67. mutation_scale=40)
  68. ax.add_patch(arrow1)
  69. # create an arrow with other arrow style
  70. arrow2 = patches.FancyArrowPatch((0.5, 1.5), (1.5, 1.75),
  71. arrowstyle='fancy',
  72. fill=False,
  73. mutation_scale=40)
  74. ax.add_patch(arrow2)
  75. patches_list = [arrow, arrow1, arrow2]
  76. # generate some points
  77. X, Y = np.meshgrid(np.arange(0, 2, 0.1),
  78. np.arange(0, 2, 0.1))
  79. for k, (x, y) in enumerate(zip(X.ravel(), Y.ravel())):
  80. xdisp, ydisp = ax.transData.transform([x, y])
  81. event = MouseEvent('button_press_event', fig.canvas, xdisp, ydisp)
  82. for m, patch in enumerate(patches_list):
  83. # set the points to red only if the arrow contains the point
  84. inside, res = patch.contains(event)
  85. if inside:
  86. ax.scatter(x, y, s=5, c="r")
  87. @image_comparison(['path_clipping.svg'], remove_text=True)
  88. def test_path_clipping():
  89. fig = plt.figure(figsize=(6.0, 6.2))
  90. for i, xy in enumerate([
  91. [(200, 200), (200, 350), (400, 350), (400, 200)],
  92. [(200, 200), (200, 350), (400, 350), (400, 100)],
  93. [(200, 100), (200, 350), (400, 350), (400, 100)],
  94. [(200, 100), (200, 415), (400, 350), (400, 100)],
  95. [(200, 100), (200, 415), (400, 415), (400, 100)],
  96. [(200, 415), (400, 415), (400, 100), (200, 100)],
  97. [(400, 415), (400, 100), (200, 100), (200, 415)]]):
  98. ax = fig.add_subplot(4, 2, i+1)
  99. bbox = [0, 140, 640, 260]
  100. ax.set_xlim(bbox[0], bbox[0] + bbox[2])
  101. ax.set_ylim(bbox[1], bbox[1] + bbox[3])
  102. ax.add_patch(Polygon(
  103. xy, facecolor='none', edgecolor='red', closed=True))
  104. @image_comparison(['semi_log_with_zero.png'], style='mpl20')
  105. def test_log_transform_with_zero():
  106. x = np.arange(-10, 10)
  107. y = (1.0 - 1.0/(x**2+1))**20
  108. fig, ax = plt.subplots()
  109. ax.semilogy(x, y, "-o", lw=15, markeredgecolor='k')
  110. ax.set_ylim(1e-7, 1)
  111. ax.grid(True)
  112. def test_make_compound_path_empty():
  113. # We should be able to make a compound path with no arguments.
  114. # This makes it easier to write generic path based code.
  115. r = Path.make_compound_path()
  116. assert r.vertices.shape == (0, 2)
  117. @image_comparison(['xkcd.png'], remove_text=True)
  118. def test_xkcd():
  119. np.random.seed(0)
  120. x = np.linspace(0, 2 * np.pi, 100)
  121. y = np.sin(x)
  122. with plt.xkcd():
  123. fig, ax = plt.subplots()
  124. ax.plot(x, y)
  125. @image_comparison(['xkcd_marker.png'], remove_text=True)
  126. def test_xkcd_marker():
  127. np.random.seed(0)
  128. x = np.linspace(0, 5, 8)
  129. y1 = x
  130. y2 = 5 - x
  131. y3 = 2.5 * np.ones(8)
  132. with plt.xkcd():
  133. fig, ax = plt.subplots()
  134. ax.plot(x, y1, '+', ms=10)
  135. ax.plot(x, y2, 'o', ms=10)
  136. ax.plot(x, y3, '^', ms=10)
  137. @image_comparison(['marker_paths.pdf'], remove_text=True)
  138. def test_marker_paths_pdf():
  139. N = 7
  140. plt.errorbar(np.arange(N),
  141. np.ones(N) + 4,
  142. np.ones(N))
  143. plt.xlim(-1, N)
  144. plt.ylim(-1, 7)
  145. @image_comparison(['nan_path'], style='default', remove_text=True,
  146. extensions=['pdf', 'svg', 'eps', 'png'])
  147. def test_nan_isolated_points():
  148. y0 = [0, np.nan, 2, np.nan, 4, 5, 6]
  149. y1 = [np.nan, 7, np.nan, 9, 10, np.nan, 12]
  150. fig, ax = plt.subplots()
  151. ax.plot(y0, '-o')
  152. ax.plot(y1, '-o')
  153. def test_path_no_doubled_point_in_to_polygon():
  154. hand = np.array(
  155. [[1.64516129, 1.16145833],
  156. [1.64516129, 1.59375],
  157. [1.35080645, 1.921875],
  158. [1.375, 2.18229167],
  159. [1.68548387, 1.9375],
  160. [1.60887097, 2.55208333],
  161. [1.68548387, 2.69791667],
  162. [1.76209677, 2.56770833],
  163. [1.83064516, 1.97395833],
  164. [1.89516129, 2.75],
  165. [1.9516129, 2.84895833],
  166. [2.01209677, 2.76041667],
  167. [1.99193548, 1.99479167],
  168. [2.11290323, 2.63020833],
  169. [2.2016129, 2.734375],
  170. [2.25403226, 2.60416667],
  171. [2.14919355, 1.953125],
  172. [2.30645161, 2.36979167],
  173. [2.39112903, 2.36979167],
  174. [2.41532258, 2.1875],
  175. [2.1733871, 1.703125],
  176. [2.07782258, 1.16666667]])
  177. (r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
  178. poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True)
  179. clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
  180. poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
  181. assert np.all(poly_clipped[-2] != poly_clipped[-1])
  182. assert np.all(poly_clipped[-1] == poly_clipped[0])
  183. def test_path_to_polygons():
  184. data = [[10, 10], [20, 20]]
  185. p = Path(data)
  186. assert_array_equal(p.to_polygons(width=40, height=40), [])
  187. assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
  188. [data])
  189. assert_array_equal(p.to_polygons(), [])
  190. assert_array_equal(p.to_polygons(closed_only=False), [data])
  191. data = [[10, 10], [20, 20], [30, 30]]
  192. closed_data = [[10, 10], [20, 20], [30, 30], [10, 10]]
  193. p = Path(data)
  194. assert_array_equal(p.to_polygons(width=40, height=40), [closed_data])
  195. assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
  196. [data])
  197. assert_array_equal(p.to_polygons(), [closed_data])
  198. assert_array_equal(p.to_polygons(closed_only=False), [data])
  199. def test_path_deepcopy():
  200. # Should not raise any error
  201. verts = [[0, 0], [1, 1]]
  202. codes = [Path.MOVETO, Path.LINETO]
  203. path1 = Path(verts)
  204. path2 = Path(verts, codes)
  205. copy.deepcopy(path1)
  206. copy.deepcopy(path2)
  207. @pytest.mark.parametrize('phi', np.concatenate([
  208. np.array([0, 15, 30, 45, 60, 75, 90, 105, 120, 135]) + delta
  209. for delta in [-1, 0, 1]]))
  210. def test_path_intersect_path(phi):
  211. # test for the range of intersection angles
  212. eps_array = [1e-5, 1e-8, 1e-10, 1e-12]
  213. transform = transforms.Affine2D().rotate(np.deg2rad(phi))
  214. # a and b intersect at angle phi
  215. a = Path([(-2, 0), (2, 0)])
  216. b = transform.transform_path(a)
  217. assert a.intersects_path(b) and b.intersects_path(a)
  218. # a and b touch at angle phi at (0, 0)
  219. a = Path([(0, 0), (2, 0)])
  220. b = transform.transform_path(a)
  221. assert a.intersects_path(b) and b.intersects_path(a)
  222. # a and b are orthogonal and intersect at (0, 3)
  223. a = transform.transform_path(Path([(0, 1), (0, 3)]))
  224. b = transform.transform_path(Path([(1, 3), (0, 3)]))
  225. assert a.intersects_path(b) and b.intersects_path(a)
  226. # a and b are collinear and intersect at (0, 3)
  227. a = transform.transform_path(Path([(0, 1), (0, 3)]))
  228. b = transform.transform_path(Path([(0, 5), (0, 3)]))
  229. assert a.intersects_path(b) and b.intersects_path(a)
  230. # self-intersect
  231. assert a.intersects_path(a)
  232. # a contains b
  233. a = transform.transform_path(Path([(0, 0), (5, 5)]))
  234. b = transform.transform_path(Path([(1, 1), (3, 3)]))
  235. assert a.intersects_path(b) and b.intersects_path(a)
  236. # a and b are collinear but do not intersect
  237. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  238. b = transform.transform_path(Path([(3, 0), (3, 3)]))
  239. assert not a.intersects_path(b) and not b.intersects_path(a)
  240. # a and b are on the same line but do not intersect
  241. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  242. b = transform.transform_path(Path([(0, 6), (0, 7)]))
  243. assert not a.intersects_path(b) and not b.intersects_path(a)
  244. # Note: 1e-13 is the absolute tolerance error used for
  245. # `isclose` function from src/_path.h
  246. # a and b are parallel but do not touch
  247. for eps in eps_array:
  248. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  249. b = transform.transform_path(Path([(0 + eps, 1), (0 + eps, 5)]))
  250. assert not a.intersects_path(b) and not b.intersects_path(a)
  251. # a and b are on the same line but do not intersect (really close)
  252. for eps in eps_array:
  253. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  254. b = transform.transform_path(Path([(0, 5 + eps), (0, 7)]))
  255. assert not a.intersects_path(b) and not b.intersects_path(a)
  256. # a and b are on the same line and intersect (really close)
  257. for eps in eps_array:
  258. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  259. b = transform.transform_path(Path([(0, 5 - eps), (0, 7)]))
  260. assert a.intersects_path(b) and b.intersects_path(a)
  261. # b is the same as a but with an extra point
  262. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  263. b = transform.transform_path(Path([(0, 1), (0, 2), (0, 5)]))
  264. assert a.intersects_path(b) and b.intersects_path(a)
  265. @pytest.mark.parametrize('offset', range(-720, 361, 45))
  266. def test_full_arc(offset):
  267. low = offset
  268. high = 360 + offset
  269. path = Path.arc(low, high)
  270. mins = np.min(path.vertices, axis=0)
  271. maxs = np.max(path.vertices, axis=0)
  272. np.testing.assert_allclose(mins, -1)
  273. np.testing.assert_allclose(maxs, 1)
  274. def test_disjoint_zero_length_segment():
  275. this_path = Path(
  276. np.array([
  277. [824.85064295, 2056.26489203],
  278. [861.69033931, 2041.00539016],
  279. [868.57864109, 2057.63522175],
  280. [831.73894473, 2072.89472361],
  281. [824.85064295, 2056.26489203]]),
  282. np.array([1, 2, 2, 2, 79], dtype=Path.code_type))
  283. outline_path = Path(
  284. np.array([
  285. [859.91051028, 2165.38461538],
  286. [859.06772495, 2149.30331334],
  287. [859.06772495, 2181.46591743],
  288. [859.91051028, 2165.38461538],
  289. [859.91051028, 2165.38461538]]),
  290. np.array([1, 2, 2, 2, 2],
  291. dtype=Path.code_type))
  292. assert not outline_path.intersects_path(this_path)
  293. assert not this_path.intersects_path(outline_path)
  294. def test_intersect_zero_length_segment():
  295. this_path = Path(
  296. np.array([
  297. [0, 0],
  298. [1, 1],
  299. ]))
  300. outline_path = Path(
  301. np.array([
  302. [1, 0],
  303. [.5, .5],
  304. [.5, .5],
  305. [0, 1],
  306. ]))
  307. assert outline_path.intersects_path(this_path)
  308. assert this_path.intersects_path(outline_path)