test_path.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. import platform
  2. import re
  3. import numpy as np
  4. from numpy.testing import assert_array_equal
  5. import pytest
  6. from matplotlib import patches
  7. from matplotlib.path import Path
  8. from matplotlib.patches import Polygon
  9. from matplotlib.testing.decorators import image_comparison
  10. import matplotlib.pyplot as plt
  11. from matplotlib import transforms
  12. from matplotlib.backend_bases import MouseEvent
  13. def test_empty_closed_path():
  14. path = Path(np.zeros((0, 2)), closed=True)
  15. assert path.vertices.shape == (0, 2)
  16. assert path.codes is None
  17. assert_array_equal(path.get_extents().extents,
  18. transforms.Bbox.null().extents)
  19. def test_readonly_path():
  20. path = Path.unit_circle()
  21. def modify_vertices():
  22. path.vertices = path.vertices * 2.0
  23. with pytest.raises(AttributeError):
  24. modify_vertices()
  25. def test_path_exceptions():
  26. bad_verts1 = np.arange(12).reshape(4, 3)
  27. with pytest.raises(ValueError,
  28. match=re.escape(f'has shape {bad_verts1.shape}')):
  29. Path(bad_verts1)
  30. bad_verts2 = np.arange(12).reshape(2, 3, 2)
  31. with pytest.raises(ValueError,
  32. match=re.escape(f'has shape {bad_verts2.shape}')):
  33. Path(bad_verts2)
  34. good_verts = np.arange(12).reshape(6, 2)
  35. bad_codes = np.arange(2)
  36. msg = re.escape(f"Your vertices have shape {good_verts.shape} "
  37. f"but your codes have shape {bad_codes.shape}")
  38. with pytest.raises(ValueError, match=msg):
  39. Path(good_verts, bad_codes)
  40. def test_point_in_path():
  41. # Test #1787
  42. path = Path._create_closed([(0, 0), (0, 1), (1, 1), (1, 0)])
  43. points = [(0.5, 0.5), (1.5, 0.5)]
  44. ret = path.contains_points(points)
  45. assert ret.dtype == 'bool'
  46. np.testing.assert_equal(ret, [True, False])
  47. @pytest.mark.parametrize(
  48. "other_path, inside, inverted_inside",
  49. [(Path([(0.25, 0.25), (0.25, 0.75), (0.75, 0.75), (0.75, 0.25), (0.25, 0.25)],
  50. closed=True), True, False),
  51. (Path([(-0.25, -0.25), (-0.25, 1.75), (1.75, 1.75), (1.75, -0.25), (-0.25, -0.25)],
  52. closed=True), False, True),
  53. (Path([(-0.25, -0.25), (-0.25, 1.75), (0.5, 0.5),
  54. (1.75, 1.75), (1.75, -0.25), (-0.25, -0.25)],
  55. closed=True), False, False),
  56. (Path([(0.25, 0.25), (0.25, 1.25), (1.25, 1.25), (1.25, 0.25), (0.25, 0.25)],
  57. closed=True), False, False),
  58. (Path([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], closed=True), False, False),
  59. (Path([(2, 2), (2, 3), (3, 3), (3, 2), (2, 2)], closed=True), False, False)])
  60. def test_contains_path(other_path, inside, inverted_inside):
  61. path = Path([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], closed=True)
  62. assert path.contains_path(other_path) is inside
  63. assert other_path.contains_path(path) is inverted_inside
  64. def test_contains_points_negative_radius():
  65. path = Path.unit_circle()
  66. points = [(0.0, 0.0), (1.25, 0.0), (0.9, 0.9)]
  67. result = path.contains_points(points, radius=-0.5)
  68. np.testing.assert_equal(result, [True, False, False])
  69. _test_paths = [
  70. # interior extrema determine extents and degenerate derivative
  71. Path([[0, 0], [1, 0], [1, 1], [0, 1]],
  72. [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]),
  73. # a quadratic curve
  74. Path([[0, 0], [0, 1], [1, 0]], [Path.MOVETO, Path.CURVE3, Path.CURVE3]),
  75. # a linear curve, degenerate vertically
  76. Path([[0, 1], [1, 1]], [Path.MOVETO, Path.LINETO]),
  77. # a point
  78. Path([[1, 2]], [Path.MOVETO]),
  79. ]
  80. _test_path_extents = [(0., 0., 0.75, 1.), (0., 0., 1., 0.5), (0., 1., 1., 1.),
  81. (1., 2., 1., 2.)]
  82. @pytest.mark.parametrize('path, extents', zip(_test_paths, _test_path_extents))
  83. def test_exact_extents(path, extents):
  84. # notice that if we just looked at the control points to get the bounding
  85. # box of each curve, we would get the wrong answers. For example, for
  86. # hard_curve = Path([[0, 0], [1, 0], [1, 1], [0, 1]],
  87. # [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4])
  88. # we would get that the extents area (0, 0, 1, 1). This code takes into
  89. # account the curved part of the path, which does not typically extend all
  90. # the way out to the control points.
  91. # Note that counterintuitively, path.get_extents() returns a Bbox, so we
  92. # have to get that Bbox's `.extents`.
  93. assert np.all(path.get_extents().extents == extents)
  94. @pytest.mark.parametrize('ignored_code', [Path.CLOSEPOLY, Path.STOP])
  95. def test_extents_with_ignored_codes(ignored_code):
  96. # Check that STOP and CLOSEPOLY points are ignored when calculating extents
  97. # of a path with only straight lines
  98. path = Path([[0, 0],
  99. [1, 1],
  100. [2, 2]], [Path.MOVETO, Path.MOVETO, ignored_code])
  101. assert np.all(path.get_extents().extents == (0., 0., 1., 1.))
  102. def test_point_in_path_nan():
  103. box = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
  104. p = Path(box)
  105. test = np.array([[np.nan, 0.5]])
  106. contains = p.contains_points(test)
  107. assert len(contains) == 1
  108. assert not contains[0]
  109. def test_nonlinear_containment():
  110. fig, ax = plt.subplots()
  111. ax.set(xscale="log", ylim=(0, 1))
  112. polygon = ax.axvspan(1, 10)
  113. assert polygon.get_path().contains_point(
  114. ax.transData.transform((5, .5)), polygon.get_transform())
  115. assert not polygon.get_path().contains_point(
  116. ax.transData.transform((.5, .5)), polygon.get_transform())
  117. assert not polygon.get_path().contains_point(
  118. ax.transData.transform((50, .5)), polygon.get_transform())
  119. @image_comparison(['arrow_contains_point.png'], remove_text=True, style='mpl20',
  120. tol=0 if platform.machine() == 'x86_64' else 0.027)
  121. def test_arrow_contains_point():
  122. # fix bug (#8384)
  123. fig, ax = plt.subplots()
  124. ax.set_xlim((0, 2))
  125. ax.set_ylim((0, 2))
  126. # create an arrow with Curve style
  127. arrow = patches.FancyArrowPatch((0.5, 0.25), (1.5, 0.75),
  128. arrowstyle='->',
  129. mutation_scale=40)
  130. ax.add_patch(arrow)
  131. # create an arrow with Bracket style
  132. arrow1 = patches.FancyArrowPatch((0.5, 1), (1.5, 1.25),
  133. arrowstyle=']-[',
  134. mutation_scale=40)
  135. ax.add_patch(arrow1)
  136. # create an arrow with other arrow style
  137. arrow2 = patches.FancyArrowPatch((0.5, 1.5), (1.5, 1.75),
  138. arrowstyle='fancy',
  139. fill=False,
  140. mutation_scale=40)
  141. ax.add_patch(arrow2)
  142. patches_list = [arrow, arrow1, arrow2]
  143. # generate some points
  144. X, Y = np.meshgrid(np.arange(0, 2, 0.1),
  145. np.arange(0, 2, 0.1))
  146. for k, (x, y) in enumerate(zip(X.ravel(), Y.ravel())):
  147. xdisp, ydisp = ax.transData.transform([x, y])
  148. event = MouseEvent('button_press_event', fig.canvas, xdisp, ydisp)
  149. for m, patch in enumerate(patches_list):
  150. # set the points to red only if the arrow contains the point
  151. inside, res = patch.contains(event)
  152. if inside:
  153. ax.scatter(x, y, s=5, c="r")
  154. @image_comparison(['path_clipping.svg'], remove_text=True)
  155. def test_path_clipping():
  156. fig = plt.figure(figsize=(6.0, 6.2))
  157. for i, xy in enumerate([
  158. [(200, 200), (200, 350), (400, 350), (400, 200)],
  159. [(200, 200), (200, 350), (400, 350), (400, 100)],
  160. [(200, 100), (200, 350), (400, 350), (400, 100)],
  161. [(200, 100), (200, 415), (400, 350), (400, 100)],
  162. [(200, 100), (200, 415), (400, 415), (400, 100)],
  163. [(200, 415), (400, 415), (400, 100), (200, 100)],
  164. [(400, 415), (400, 100), (200, 100), (200, 415)]]):
  165. ax = fig.add_subplot(4, 2, i+1)
  166. bbox = [0, 140, 640, 260]
  167. ax.set_xlim(bbox[0], bbox[0] + bbox[2])
  168. ax.set_ylim(bbox[1], bbox[1] + bbox[3])
  169. ax.add_patch(Polygon(
  170. xy, facecolor='none', edgecolor='red', closed=True))
  171. @image_comparison(['semi_log_with_zero.png'], style='mpl20')
  172. def test_log_transform_with_zero():
  173. x = np.arange(-10, 10)
  174. y = (1.0 - 1.0/(x**2+1))**20
  175. fig, ax = plt.subplots()
  176. ax.semilogy(x, y, "-o", lw=15, markeredgecolor='k')
  177. ax.set_ylim(1e-7, 1)
  178. ax.grid(True)
  179. def test_make_compound_path_empty():
  180. # We should be able to make a compound path with no arguments.
  181. # This makes it easier to write generic path based code.
  182. empty = Path.make_compound_path()
  183. assert empty.vertices.shape == (0, 2)
  184. r2 = Path.make_compound_path(empty, empty)
  185. assert r2.vertices.shape == (0, 2)
  186. assert r2.codes.shape == (0,)
  187. r3 = Path.make_compound_path(Path([(0, 0)]), empty)
  188. assert r3.vertices.shape == (1, 2)
  189. assert r3.codes.shape == (1,)
  190. def test_make_compound_path_stops():
  191. zero = [0, 0]
  192. paths = 3*[Path([zero, zero], [Path.MOVETO, Path.STOP])]
  193. compound_path = Path.make_compound_path(*paths)
  194. # the choice to not preserve the terminal STOP is arbitrary, but
  195. # documented, so we test that it is in fact respected here
  196. assert np.sum(compound_path.codes == Path.STOP) == 0
  197. @image_comparison(['xkcd.png'], remove_text=True)
  198. def test_xkcd():
  199. np.random.seed(0)
  200. x = np.linspace(0, 2 * np.pi, 100)
  201. y = np.sin(x)
  202. with plt.xkcd():
  203. fig, ax = plt.subplots()
  204. ax.plot(x, y)
  205. @image_comparison(['xkcd_marker.png'], remove_text=True)
  206. def test_xkcd_marker():
  207. np.random.seed(0)
  208. x = np.linspace(0, 5, 8)
  209. y1 = x
  210. y2 = 5 - x
  211. y3 = 2.5 * np.ones(8)
  212. with plt.xkcd():
  213. fig, ax = plt.subplots()
  214. ax.plot(x, y1, '+', ms=10)
  215. ax.plot(x, y2, 'o', ms=10)
  216. ax.plot(x, y3, '^', ms=10)
  217. @image_comparison(['marker_paths.pdf'], remove_text=True)
  218. def test_marker_paths_pdf():
  219. N = 7
  220. plt.errorbar(np.arange(N),
  221. np.ones(N) + 4,
  222. np.ones(N))
  223. plt.xlim(-1, N)
  224. plt.ylim(-1, 7)
  225. @image_comparison(['nan_path'], style='default', remove_text=True,
  226. extensions=['pdf', 'svg', 'eps', 'png'],
  227. tol=0 if platform.machine() == 'x86_64' else 0.009)
  228. def test_nan_isolated_points():
  229. y0 = [0, np.nan, 2, np.nan, 4, 5, 6]
  230. y1 = [np.nan, 7, np.nan, 9, 10, np.nan, 12]
  231. fig, ax = plt.subplots()
  232. ax.plot(y0, '-o')
  233. ax.plot(y1, '-o')
  234. def test_path_no_doubled_point_in_to_polygon():
  235. hand = np.array(
  236. [[1.64516129, 1.16145833],
  237. [1.64516129, 1.59375],
  238. [1.35080645, 1.921875],
  239. [1.375, 2.18229167],
  240. [1.68548387, 1.9375],
  241. [1.60887097, 2.55208333],
  242. [1.68548387, 2.69791667],
  243. [1.76209677, 2.56770833],
  244. [1.83064516, 1.97395833],
  245. [1.89516129, 2.75],
  246. [1.9516129, 2.84895833],
  247. [2.01209677, 2.76041667],
  248. [1.99193548, 1.99479167],
  249. [2.11290323, 2.63020833],
  250. [2.2016129, 2.734375],
  251. [2.25403226, 2.60416667],
  252. [2.14919355, 1.953125],
  253. [2.30645161, 2.36979167],
  254. [2.39112903, 2.36979167],
  255. [2.41532258, 2.1875],
  256. [2.1733871, 1.703125],
  257. [2.07782258, 1.16666667]])
  258. (r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
  259. poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True)
  260. clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
  261. poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
  262. assert np.all(poly_clipped[-2] != poly_clipped[-1])
  263. assert np.all(poly_clipped[-1] == poly_clipped[0])
  264. def test_path_to_polygons():
  265. data = [[10, 10], [20, 20]]
  266. p = Path(data)
  267. assert_array_equal(p.to_polygons(width=40, height=40), [])
  268. assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
  269. [data])
  270. assert_array_equal(p.to_polygons(), [])
  271. assert_array_equal(p.to_polygons(closed_only=False), [data])
  272. data = [[10, 10], [20, 20], [30, 30]]
  273. closed_data = [[10, 10], [20, 20], [30, 30], [10, 10]]
  274. p = Path(data)
  275. assert_array_equal(p.to_polygons(width=40, height=40), [closed_data])
  276. assert_array_equal(p.to_polygons(width=40, height=40, closed_only=False),
  277. [data])
  278. assert_array_equal(p.to_polygons(), [closed_data])
  279. assert_array_equal(p.to_polygons(closed_only=False), [data])
  280. def test_path_deepcopy():
  281. # Should not raise any error
  282. verts = [[0, 0], [1, 1]]
  283. codes = [Path.MOVETO, Path.LINETO]
  284. path1 = Path(verts, readonly=True)
  285. path2 = Path(verts, codes, readonly=True)
  286. path1_copy = path1.deepcopy()
  287. path2_copy = path2.deepcopy()
  288. assert path1 is not path1_copy
  289. assert path1.vertices is not path1_copy.vertices
  290. assert_array_equal(path1.vertices, path1_copy.vertices)
  291. assert path1.readonly
  292. assert not path1_copy.readonly
  293. assert path2 is not path2_copy
  294. assert path2.vertices is not path2_copy.vertices
  295. assert_array_equal(path2.vertices, path2_copy.vertices)
  296. assert path2.codes is not path2_copy.codes
  297. assert_array_equal(path2.codes, path2_copy.codes)
  298. assert path2.readonly
  299. assert not path2_copy.readonly
  300. def test_path_deepcopy_cycle():
  301. class PathWithCycle(Path):
  302. def __init__(self, *args, **kwargs):
  303. super().__init__(*args, **kwargs)
  304. self.x = self
  305. p = PathWithCycle([[0, 0], [1, 1]], readonly=True)
  306. p_copy = p.deepcopy()
  307. assert p_copy is not p
  308. assert p.readonly
  309. assert not p_copy.readonly
  310. assert p_copy.x is p_copy
  311. class PathWithCycle2(Path):
  312. def __init__(self, *args, **kwargs):
  313. super().__init__(*args, **kwargs)
  314. self.x = [self] * 2
  315. p2 = PathWithCycle2([[0, 0], [1, 1]], readonly=True)
  316. p2_copy = p2.deepcopy()
  317. assert p2_copy is not p2
  318. assert p2.readonly
  319. assert not p2_copy.readonly
  320. assert p2_copy.x[0] is p2_copy
  321. assert p2_copy.x[1] is p2_copy
  322. def test_path_shallowcopy():
  323. # Should not raise any error
  324. verts = [[0, 0], [1, 1]]
  325. codes = [Path.MOVETO, Path.LINETO]
  326. path1 = Path(verts)
  327. path2 = Path(verts, codes)
  328. path1_copy = path1.copy()
  329. path2_copy = path2.copy()
  330. assert path1 is not path1_copy
  331. assert path1.vertices is path1_copy.vertices
  332. assert path2 is not path2_copy
  333. assert path2.vertices is path2_copy.vertices
  334. assert path2.codes is path2_copy.codes
  335. @pytest.mark.parametrize('phi', np.concatenate([
  336. np.array([0, 15, 30, 45, 60, 75, 90, 105, 120, 135]) + delta
  337. for delta in [-1, 0, 1]]))
  338. def test_path_intersect_path(phi):
  339. # test for the range of intersection angles
  340. eps_array = [1e-5, 1e-8, 1e-10, 1e-12]
  341. transform = transforms.Affine2D().rotate(np.deg2rad(phi))
  342. # a and b intersect at angle phi
  343. a = Path([(-2, 0), (2, 0)])
  344. b = transform.transform_path(a)
  345. assert a.intersects_path(b) and b.intersects_path(a)
  346. # a and b touch at angle phi at (0, 0)
  347. a = Path([(0, 0), (2, 0)])
  348. b = transform.transform_path(a)
  349. assert a.intersects_path(b) and b.intersects_path(a)
  350. # a and b are orthogonal and intersect at (0, 3)
  351. a = transform.transform_path(Path([(0, 1), (0, 3)]))
  352. b = transform.transform_path(Path([(1, 3), (0, 3)]))
  353. assert a.intersects_path(b) and b.intersects_path(a)
  354. # a and b are collinear and intersect at (0, 3)
  355. a = transform.transform_path(Path([(0, 1), (0, 3)]))
  356. b = transform.transform_path(Path([(0, 5), (0, 3)]))
  357. assert a.intersects_path(b) and b.intersects_path(a)
  358. # self-intersect
  359. assert a.intersects_path(a)
  360. # a contains b
  361. a = transform.transform_path(Path([(0, 0), (5, 5)]))
  362. b = transform.transform_path(Path([(1, 1), (3, 3)]))
  363. assert a.intersects_path(b) and b.intersects_path(a)
  364. # a and b are collinear but do not intersect
  365. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  366. b = transform.transform_path(Path([(3, 0), (3, 3)]))
  367. assert not a.intersects_path(b) and not b.intersects_path(a)
  368. # a and b are on the same line but do not intersect
  369. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  370. b = transform.transform_path(Path([(0, 6), (0, 7)]))
  371. assert not a.intersects_path(b) and not b.intersects_path(a)
  372. # Note: 1e-13 is the absolute tolerance error used for
  373. # `isclose` function from src/_path.h
  374. # a and b are parallel but do not touch
  375. for eps in eps_array:
  376. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  377. b = transform.transform_path(Path([(0 + eps, 1), (0 + eps, 5)]))
  378. assert not a.intersects_path(b) and not b.intersects_path(a)
  379. # a and b are on the same line but do not intersect (really close)
  380. for eps in eps_array:
  381. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  382. b = transform.transform_path(Path([(0, 5 + eps), (0, 7)]))
  383. assert not a.intersects_path(b) and not b.intersects_path(a)
  384. # a and b are on the same line and intersect (really close)
  385. for eps in eps_array:
  386. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  387. b = transform.transform_path(Path([(0, 5 - eps), (0, 7)]))
  388. assert a.intersects_path(b) and b.intersects_path(a)
  389. # b is the same as a but with an extra point
  390. a = transform.transform_path(Path([(0, 1), (0, 5)]))
  391. b = transform.transform_path(Path([(0, 1), (0, 2), (0, 5)]))
  392. assert a.intersects_path(b) and b.intersects_path(a)
  393. # a and b are collinear but do not intersect
  394. a = transform.transform_path(Path([(1, -1), (0, -1)]))
  395. b = transform.transform_path(Path([(0, 1), (0.9, 1)]))
  396. assert not a.intersects_path(b) and not b.intersects_path(a)
  397. # a and b are collinear but do not intersect
  398. a = transform.transform_path(Path([(0., -5.), (1., -5.)]))
  399. b = transform.transform_path(Path([(1., 5.), (0., 5.)]))
  400. assert not a.intersects_path(b) and not b.intersects_path(a)
  401. @pytest.mark.parametrize('offset', range(-720, 361, 45))
  402. def test_full_arc(offset):
  403. low = offset
  404. high = 360 + offset
  405. path = Path.arc(low, high)
  406. mins = np.min(path.vertices, axis=0)
  407. maxs = np.max(path.vertices, axis=0)
  408. np.testing.assert_allclose(mins, -1)
  409. np.testing.assert_allclose(maxs, 1)
  410. def test_disjoint_zero_length_segment():
  411. this_path = Path(
  412. np.array([
  413. [824.85064295, 2056.26489203],
  414. [861.69033931, 2041.00539016],
  415. [868.57864109, 2057.63522175],
  416. [831.73894473, 2072.89472361],
  417. [824.85064295, 2056.26489203]]),
  418. np.array([1, 2, 2, 2, 79], dtype=Path.code_type))
  419. outline_path = Path(
  420. np.array([
  421. [859.91051028, 2165.38461538],
  422. [859.06772495, 2149.30331334],
  423. [859.06772495, 2181.46591743],
  424. [859.91051028, 2165.38461538],
  425. [859.91051028, 2165.38461538]]),
  426. np.array([1, 2, 2, 2, 2],
  427. dtype=Path.code_type))
  428. assert not outline_path.intersects_path(this_path)
  429. assert not this_path.intersects_path(outline_path)
  430. def test_intersect_zero_length_segment():
  431. this_path = Path(
  432. np.array([
  433. [0, 0],
  434. [1, 1],
  435. ]))
  436. outline_path = Path(
  437. np.array([
  438. [1, 0],
  439. [.5, .5],
  440. [.5, .5],
  441. [0, 1],
  442. ]))
  443. assert outline_path.intersects_path(this_path)
  444. assert this_path.intersects_path(outline_path)
  445. def test_cleanup_closepoly():
  446. # if the first connected component of a Path ends in a CLOSEPOLY, but that
  447. # component contains a NaN, then Path.cleaned should ignore not just the
  448. # control points but also the CLOSEPOLY, since it has nowhere valid to
  449. # point.
  450. paths = [
  451. Path([[np.nan, np.nan], [np.nan, np.nan]],
  452. [Path.MOVETO, Path.CLOSEPOLY]),
  453. # we trigger a different path in the C++ code if we don't pass any
  454. # codes explicitly, so we must also make sure that this works
  455. Path([[np.nan, np.nan], [np.nan, np.nan]]),
  456. # we should also make sure that this cleanup works if there's some
  457. # multi-vertex curves
  458. Path([[np.nan, np.nan], [np.nan, np.nan], [np.nan, np.nan],
  459. [np.nan, np.nan]],
  460. [Path.MOVETO, Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY])
  461. ]
  462. for p in paths:
  463. cleaned = p.cleaned(remove_nans=True)
  464. assert len(cleaned) == 1
  465. assert cleaned.codes[0] == Path.STOP
  466. def test_interpolated_moveto():
  467. # Initial path has two subpaths with two LINETOs each
  468. vertices = np.array([[0, 0],
  469. [0, 1],
  470. [1, 2],
  471. [4, 4],
  472. [4, 5],
  473. [5, 5]])
  474. codes = [Path.MOVETO, Path.LINETO, Path.LINETO] * 2
  475. path = Path(vertices, codes)
  476. result = path.interpolated(3)
  477. # Result should have two subpaths with six LINETOs each
  478. expected_subpath_codes = [Path.MOVETO] + [Path.LINETO] * 6
  479. np.testing.assert_array_equal(result.codes, expected_subpath_codes * 2)
  480. def test_interpolated_closepoly():
  481. codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]
  482. vertices = [(4, 3), (5, 4), (5, 3), (0, 0)]
  483. path = Path(vertices, codes)
  484. result = path.interpolated(2)
  485. expected_vertices = np.array([[4, 3],
  486. [4.5, 3.5],
  487. [5, 4],
  488. [5, 3.5],
  489. [5, 3],
  490. [4.5, 3],
  491. [4, 3]])
  492. expected_codes = [Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]
  493. np.testing.assert_allclose(result.vertices, expected_vertices)
  494. np.testing.assert_array_equal(result.codes, expected_codes)
  495. # Usually closepoly is the last vertex but does not have to be.
  496. codes += [Path.LINETO]
  497. vertices += [(2, 1)]
  498. path = Path(vertices, codes)
  499. result = path.interpolated(2)
  500. extra_expected_vertices = np.array([[3, 2],
  501. [2, 1]])
  502. expected_vertices = np.concatenate([expected_vertices, extra_expected_vertices])
  503. expected_codes += [Path.LINETO] * 2
  504. np.testing.assert_allclose(result.vertices, expected_vertices)
  505. np.testing.assert_array_equal(result.codes, expected_codes)
  506. def test_interpolated_moveto_closepoly():
  507. # Initial path has two closed subpaths
  508. codes = ([Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY]) * 2
  509. vertices = [(4, 3), (5, 4), (5, 3), (0, 0), (8, 6), (10, 8), (10, 6), (0, 0)]
  510. path = Path(vertices, codes)
  511. result = path.interpolated(2)
  512. expected_vertices1 = np.array([[4, 3],
  513. [4.5, 3.5],
  514. [5, 4],
  515. [5, 3.5],
  516. [5, 3],
  517. [4.5, 3],
  518. [4, 3]])
  519. expected_vertices = np.concatenate([expected_vertices1, expected_vertices1 * 2])
  520. expected_codes = ([Path.MOVETO] + [Path.LINETO]*5 + [Path.CLOSEPOLY]) * 2
  521. np.testing.assert_allclose(result.vertices, expected_vertices)
  522. np.testing.assert_array_equal(result.codes, expected_codes)
  523. def test_interpolated_empty_path():
  524. path = Path(np.zeros((0, 2)))
  525. assert path.interpolated(42) is path