You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1009 lines
36 KiB

4 years ago
  1. r"""
  2. A module for dealing with the polylines used throughout Matplotlib.
  3. The primary class for polyline handling in Matplotlib is `Path`. Almost all
  4. vector drawing makes use of `Path`\s somewhere in the drawing pipeline.
  5. Whilst a `Path` instance itself cannot be drawn, some `.Artist` subclasses,
  6. such as `.PathPatch` and `.PathCollection`, can be used for convenient `Path`
  7. visualisation.
  8. """
  9. from functools import lru_cache
  10. from weakref import WeakValueDictionary
  11. import numpy as np
  12. from . import _path, rcParams
  13. from .cbook import _to_unmasked_float_array, simple_linear_interpolation
  14. class Path(object):
  15. """
  16. :class:`Path` represents a series of possibly disconnected,
  17. possibly closed, line and curve segments.
  18. The underlying storage is made up of two parallel numpy arrays:
  19. - *vertices*: an Nx2 float array of vertices
  20. - *codes*: an N-length uint8 array of vertex types
  21. These two arrays always have the same length in the first
  22. dimension. For example, to represent a cubic curve, you must
  23. provide three vertices as well as three codes ``CURVE3``.
  24. The code types are:
  25. - ``STOP`` : 1 vertex (ignored)
  26. A marker for the end of the entire path (currently not
  27. required and ignored)
  28. - ``MOVETO`` : 1 vertex
  29. Pick up the pen and move to the given vertex.
  30. - ``LINETO`` : 1 vertex
  31. Draw a line from the current position to the given vertex.
  32. - ``CURVE3`` : 1 control point, 1 endpoint
  33. Draw a quadratic Bezier curve from the current position,
  34. with the given control point, to the given end point.
  35. - ``CURVE4`` : 2 control points, 1 endpoint
  36. Draw a cubic Bezier curve from the current position, with
  37. the given control points, to the given end point.
  38. - ``CLOSEPOLY`` : 1 vertex (ignored)
  39. Draw a line segment to the start point of the current
  40. polyline.
  41. Users of Path objects should not access the vertices and codes
  42. arrays directly. Instead, they should use :meth:`iter_segments`
  43. or :meth:`cleaned` to get the vertex/code pairs. This is important,
  44. since many :class:`Path` objects, as an optimization, do not store a
  45. *codes* at all, but have a default one provided for them by
  46. :meth:`iter_segments`.
  47. Some behavior of Path objects can be controlled by rcParams. See
  48. the rcParams whose keys contain 'path.'.
  49. .. note::
  50. The vertices and codes arrays should be treated as
  51. immutable -- there are a number of optimizations and assumptions
  52. made up front in the constructor that will not change when the
  53. data changes.
  54. """
  55. # Path codes
  56. STOP = 0 # 1 vertex
  57. MOVETO = 1 # 1 vertex
  58. LINETO = 2 # 1 vertex
  59. CURVE3 = 3 # 2 vertices
  60. CURVE4 = 4 # 3 vertices
  61. CLOSEPOLY = 79 # 1 vertex
  62. #: A dictionary mapping Path codes to the number of vertices that the
  63. #: code expects.
  64. NUM_VERTICES_FOR_CODE = {STOP: 1,
  65. MOVETO: 1,
  66. LINETO: 1,
  67. CURVE3: 2,
  68. CURVE4: 3,
  69. CLOSEPOLY: 1}
  70. code_type = np.uint8
  71. def __init__(self, vertices, codes=None, _interpolation_steps=1,
  72. closed=False, readonly=False):
  73. """
  74. Create a new path with the given vertices and codes.
  75. Parameters
  76. ----------
  77. vertices : array_like
  78. The ``(n, 2)`` float array, masked array or sequence of pairs
  79. representing the vertices of the path.
  80. If *vertices* contains masked values, they will be converted
  81. to NaNs which are then handled correctly by the Agg
  82. PathIterator and other consumers of path data, such as
  83. :meth:`iter_segments`.
  84. codes : {None, array_like}, optional
  85. n-length array integers representing the codes of the path.
  86. If not None, codes must be the same length as vertices.
  87. If None, *vertices* will be treated as a series of line segments.
  88. _interpolation_steps : int, optional
  89. Used as a hint to certain projections, such as Polar, that this
  90. path should be linearly interpolated immediately before drawing.
  91. This attribute is primarily an implementation detail and is not
  92. intended for public use.
  93. closed : bool, optional
  94. If *codes* is None and closed is True, vertices will be treated as
  95. line segments of a closed polygon.
  96. readonly : bool, optional
  97. Makes the path behave in an immutable way and sets the vertices
  98. and codes as read-only arrays.
  99. """
  100. vertices = _to_unmasked_float_array(vertices)
  101. if vertices.ndim != 2 or vertices.shape[1] != 2:
  102. raise ValueError(
  103. "'vertices' must be a 2D list or array with shape Nx2")
  104. if codes is not None:
  105. codes = np.asarray(codes, self.code_type)
  106. if codes.ndim != 1 or len(codes) != len(vertices):
  107. raise ValueError("'codes' must be a 1D list or array with the "
  108. "same length of 'vertices'")
  109. if len(codes) and codes[0] != self.MOVETO:
  110. raise ValueError("The first element of 'code' must be equal "
  111. "to 'MOVETO' ({})".format(self.MOVETO))
  112. elif closed and len(vertices):
  113. codes = np.empty(len(vertices), dtype=self.code_type)
  114. codes[0] = self.MOVETO
  115. codes[1:-1] = self.LINETO
  116. codes[-1] = self.CLOSEPOLY
  117. self._vertices = vertices
  118. self._codes = codes
  119. self._interpolation_steps = _interpolation_steps
  120. self._update_values()
  121. if readonly:
  122. self._vertices.flags.writeable = False
  123. if self._codes is not None:
  124. self._codes.flags.writeable = False
  125. self._readonly = True
  126. else:
  127. self._readonly = False
  128. @classmethod
  129. def _fast_from_codes_and_verts(cls, verts, codes, internals=None):
  130. """
  131. Creates a Path instance without the expense of calling the constructor
  132. Parameters
  133. ----------
  134. verts : numpy array
  135. codes : numpy array
  136. internals : dict or None
  137. The attributes that the resulting path should have.
  138. Allowed keys are ``readonly``, ``should_simplify``,
  139. ``simplify_threshold``, ``has_nonfinite`` and
  140. ``interpolation_steps``.
  141. """
  142. internals = internals or {}
  143. pth = cls.__new__(cls)
  144. pth._vertices = _to_unmasked_float_array(verts)
  145. pth._codes = codes
  146. pth._readonly = internals.pop('readonly', False)
  147. pth.should_simplify = internals.pop('should_simplify', True)
  148. pth.simplify_threshold = (
  149. internals.pop('simplify_threshold',
  150. rcParams['path.simplify_threshold'])
  151. )
  152. pth._has_nonfinite = internals.pop('has_nonfinite', False)
  153. pth._interpolation_steps = internals.pop('interpolation_steps', 1)
  154. if internals:
  155. raise ValueError('Unexpected internals provided to '
  156. '_fast_from_codes_and_verts: '
  157. '{0}'.format('\n *'.join(internals)))
  158. return pth
  159. def _update_values(self):
  160. self._simplify_threshold = rcParams['path.simplify_threshold']
  161. self._should_simplify = (
  162. self._simplify_threshold > 0 and
  163. rcParams['path.simplify'] and
  164. len(self._vertices) >= 128 and
  165. (self._codes is None or np.all(self._codes <= Path.LINETO))
  166. )
  167. self._has_nonfinite = not np.isfinite(self._vertices).all()
  168. @property
  169. def vertices(self):
  170. """
  171. The list of vertices in the `Path` as an Nx2 numpy array.
  172. """
  173. return self._vertices
  174. @vertices.setter
  175. def vertices(self, vertices):
  176. if self._readonly:
  177. raise AttributeError("Can't set vertices on a readonly Path")
  178. self._vertices = vertices
  179. self._update_values()
  180. @property
  181. def codes(self):
  182. """
  183. The list of codes in the `Path` as a 1-D numpy array. Each
  184. code is one of `STOP`, `MOVETO`, `LINETO`, `CURVE3`, `CURVE4`
  185. or `CLOSEPOLY`. For codes that correspond to more than one
  186. vertex (`CURVE3` and `CURVE4`), that code will be repeated so
  187. that the length of `self.vertices` and `self.codes` is always
  188. the same.
  189. """
  190. return self._codes
  191. @codes.setter
  192. def codes(self, codes):
  193. if self._readonly:
  194. raise AttributeError("Can't set codes on a readonly Path")
  195. self._codes = codes
  196. self._update_values()
  197. @property
  198. def simplify_threshold(self):
  199. """
  200. The fraction of a pixel difference below which vertices will
  201. be simplified out.
  202. """
  203. return self._simplify_threshold
  204. @simplify_threshold.setter
  205. def simplify_threshold(self, threshold):
  206. self._simplify_threshold = threshold
  207. @property
  208. def has_nonfinite(self):
  209. """
  210. `True` if the vertices array has nonfinite values.
  211. """
  212. return self._has_nonfinite
  213. @property
  214. def should_simplify(self):
  215. """
  216. `True` if the vertices array should be simplified.
  217. """
  218. return self._should_simplify
  219. @should_simplify.setter
  220. def should_simplify(self, should_simplify):
  221. self._should_simplify = should_simplify
  222. @property
  223. def readonly(self):
  224. """
  225. `True` if the `Path` is read-only.
  226. """
  227. return self._readonly
  228. def __copy__(self):
  229. """
  230. Returns a shallow copy of the `Path`, which will share the
  231. vertices and codes with the source `Path`.
  232. """
  233. import copy
  234. return copy.copy(self)
  235. copy = __copy__
  236. def __deepcopy__(self, memo=None):
  237. """
  238. Returns a deepcopy of the `Path`. The `Path` will not be
  239. readonly, even if the source `Path` is.
  240. """
  241. try:
  242. codes = self.codes.copy()
  243. except AttributeError:
  244. codes = None
  245. return self.__class__(
  246. self.vertices.copy(), codes,
  247. _interpolation_steps=self._interpolation_steps)
  248. deepcopy = __deepcopy__
  249. @classmethod
  250. def make_compound_path_from_polys(cls, XY):
  251. """
  252. Make a compound path object to draw a number
  253. of polygons with equal numbers of sides XY is a (numpolys x
  254. numsides x 2) numpy array of vertices. Return object is a
  255. :class:`Path`
  256. .. plot:: gallery/misc/histogram_path.py
  257. """
  258. # for each poly: 1 for the MOVETO, (numsides-1) for the LINETO, 1 for
  259. # the CLOSEPOLY; the vert for the closepoly is ignored but we still
  260. # need it to keep the codes aligned with the vertices
  261. numpolys, numsides, two = XY.shape
  262. if two != 2:
  263. raise ValueError("The third dimension of 'XY' must be 2")
  264. stride = numsides + 1
  265. nverts = numpolys * stride
  266. verts = np.zeros((nverts, 2))
  267. codes = np.ones(nverts, int) * cls.LINETO
  268. codes[0::stride] = cls.MOVETO
  269. codes[numsides::stride] = cls.CLOSEPOLY
  270. for i in range(numsides):
  271. verts[i::stride] = XY[:, i]
  272. return cls(verts, codes)
  273. @classmethod
  274. def make_compound_path(cls, *args):
  275. """Make a compound path from a list of Path objects."""
  276. # Handle an empty list in args (i.e. no args).
  277. if not args:
  278. return Path(np.empty([0, 2], dtype=np.float32))
  279. lengths = [len(x) for x in args]
  280. total_length = sum(lengths)
  281. vertices = np.vstack([x.vertices for x in args])
  282. vertices.reshape((total_length, 2))
  283. codes = np.empty(total_length, dtype=cls.code_type)
  284. i = 0
  285. for path in args:
  286. if path.codes is None:
  287. codes[i] = cls.MOVETO
  288. codes[i + 1:i + len(path.vertices)] = cls.LINETO
  289. else:
  290. codes[i:i + len(path.codes)] = path.codes
  291. i += len(path.vertices)
  292. return cls(vertices, codes)
  293. def __repr__(self):
  294. return "Path(%r, %r)" % (self.vertices, self.codes)
  295. def __len__(self):
  296. return len(self.vertices)
  297. def iter_segments(self, transform=None, remove_nans=True, clip=None,
  298. snap=False, stroke_width=1.0, simplify=None,
  299. curves=True, sketch=None):
  300. """
  301. Iterates over all of the curve segments in the path. Each
  302. iteration returns a 2-tuple (*vertices*, *code*), where
  303. *vertices* is a sequence of 1 - 3 coordinate pairs, and *code* is
  304. one of the :class:`Path` codes.
  305. Additionally, this method can provide a number of standard
  306. cleanups and conversions to the path.
  307. Parameters
  308. ----------
  309. transform : None or :class:`~matplotlib.transforms.Transform` instance
  310. If not None, the given affine transformation will
  311. be applied to the path.
  312. remove_nans : {False, True}, optional
  313. If True, will remove all NaNs from the path and
  314. insert MOVETO commands to skip over them.
  315. clip : None or sequence, optional
  316. If not None, must be a four-tuple (x1, y1, x2, y2)
  317. defining a rectangle in which to clip the path.
  318. snap : None or bool, optional
  319. If None, auto-snap to pixels, to reduce
  320. fuzziness of rectilinear lines. If True, force snapping, and
  321. if False, don't snap.
  322. stroke_width : float, optional
  323. The width of the stroke being drawn. Needed
  324. as a hint for the snapping algorithm.
  325. simplify : None or bool, optional
  326. If True, perform simplification, to remove
  327. vertices that do not affect the appearance of the path. If
  328. False, perform no simplification. If None, use the
  329. should_simplify member variable. See also the rcParams
  330. path.simplify and path.simplify_threshold.
  331. curves : {True, False}, optional
  332. If True, curve segments will be returned as curve
  333. segments. If False, all curves will be converted to line
  334. segments.
  335. sketch : None or sequence, optional
  336. If not None, must be a 3-tuple of the form
  337. (scale, length, randomness), representing the sketch
  338. parameters.
  339. """
  340. if not len(self):
  341. return
  342. cleaned = self.cleaned(transform=transform,
  343. remove_nans=remove_nans, clip=clip,
  344. snap=snap, stroke_width=stroke_width,
  345. simplify=simplify, curves=curves,
  346. sketch=sketch)
  347. vertices = cleaned.vertices
  348. codes = cleaned.codes
  349. len_vertices = vertices.shape[0]
  350. # Cache these object lookups for performance in the loop.
  351. NUM_VERTICES_FOR_CODE = self.NUM_VERTICES_FOR_CODE
  352. STOP = self.STOP
  353. i = 0
  354. while i < len_vertices:
  355. code = codes[i]
  356. if code == STOP:
  357. return
  358. else:
  359. num_vertices = NUM_VERTICES_FOR_CODE[code]
  360. curr_vertices = vertices[i:i+num_vertices].flatten()
  361. yield curr_vertices, code
  362. i += num_vertices
  363. def cleaned(self, transform=None, remove_nans=False, clip=None,
  364. quantize=False, simplify=False, curves=False,
  365. stroke_width=1.0, snap=False, sketch=None):
  366. """
  367. Cleans up the path according to the parameters returning a new
  368. Path instance.
  369. .. seealso::
  370. See :meth:`iter_segments` for details of the keyword arguments.
  371. Returns
  372. -------
  373. Path instance with cleaned up vertices and codes.
  374. """
  375. vertices, codes = _path.cleanup_path(self, transform,
  376. remove_nans, clip,
  377. snap, stroke_width,
  378. simplify, curves, sketch)
  379. internals = {'should_simplify': self.should_simplify and not simplify,
  380. 'has_nonfinite': self.has_nonfinite and not remove_nans,
  381. 'simplify_threshold': self.simplify_threshold,
  382. 'interpolation_steps': self._interpolation_steps}
  383. return Path._fast_from_codes_and_verts(vertices, codes, internals)
  384. def transformed(self, transform):
  385. """
  386. Return a transformed copy of the path.
  387. .. seealso::
  388. :class:`matplotlib.transforms.TransformedPath`
  389. A specialized path class that will cache the
  390. transformed result and automatically update when the
  391. transform changes.
  392. """
  393. return Path(transform.transform(self.vertices), self.codes,
  394. self._interpolation_steps)
  395. def contains_point(self, point, transform=None, radius=0.0):
  396. """
  397. Returns whether the (closed) path contains the given point.
  398. If *transform* is not ``None``, the path will be transformed before
  399. performing the test.
  400. *radius* allows the path to be made slightly larger or smaller.
  401. """
  402. if transform is not None:
  403. transform = transform.frozen()
  404. # `point_in_path` does not handle nonlinear transforms, so we
  405. # transform the path ourselves. If `transform` is affine, letting
  406. # `point_in_path` handle the transform avoids allocating an extra
  407. # buffer.
  408. if transform and not transform.is_affine:
  409. self = transform.transform_path(self)
  410. transform = None
  411. return _path.point_in_path(point[0], point[1], radius, self, transform)
  412. def contains_points(self, points, transform=None, radius=0.0):
  413. """
  414. Returns a bool array which is ``True`` if the (closed) path contains
  415. the corresponding point.
  416. If *transform* is not ``None``, the path will be transformed before
  417. performing the test.
  418. *radius* allows the path to be made slightly larger or smaller.
  419. """
  420. if transform is not None:
  421. transform = transform.frozen()
  422. result = _path.points_in_path(points, radius, self, transform)
  423. return result.astype('bool')
  424. def contains_path(self, path, transform=None):
  425. """
  426. Returns whether this (closed) path completely contains the given path.
  427. If *transform* is not ``None``, the path will be transformed before
  428. performing the test.
  429. """
  430. if transform is not None:
  431. transform = transform.frozen()
  432. return _path.path_in_path(self, None, path, transform)
  433. def get_extents(self, transform=None):
  434. """
  435. Returns the extents (*xmin*, *ymin*, *xmax*, *ymax*) of the
  436. path.
  437. Unlike computing the extents on the *vertices* alone, this
  438. algorithm will take into account the curves and deal with
  439. control points appropriately.
  440. """
  441. from .transforms import Bbox
  442. path = self
  443. if transform is not None:
  444. transform = transform.frozen()
  445. if not transform.is_affine:
  446. path = self.transformed(transform)
  447. transform = None
  448. return Bbox(_path.get_path_extents(path, transform))
  449. def intersects_path(self, other, filled=True):
  450. """
  451. Returns *True* if this path intersects another given path.
  452. *filled*, when True, treats the paths as if they were filled.
  453. That is, if one path completely encloses the other,
  454. :meth:`intersects_path` will return True.
  455. """
  456. return _path.path_intersects_path(self, other, filled)
  457. def intersects_bbox(self, bbox, filled=True):
  458. """
  459. Returns *True* if this path intersects a given
  460. :class:`~matplotlib.transforms.Bbox`.
  461. *filled*, when True, treats the path as if it was filled.
  462. That is, if the path completely encloses the bounding box,
  463. :meth:`intersects_bbox` will return True.
  464. The bounding box is always considered filled.
  465. """
  466. return _path.path_intersects_rectangle(self,
  467. bbox.x0, bbox.y0, bbox.x1, bbox.y1, filled)
  468. def interpolated(self, steps):
  469. """
  470. Returns a new path resampled to length N x steps. Does not
  471. currently handle interpolating curves.
  472. """
  473. if steps == 1:
  474. return self
  475. vertices = simple_linear_interpolation(self.vertices, steps)
  476. codes = self.codes
  477. if codes is not None:
  478. new_codes = Path.LINETO * np.ones(((len(codes) - 1) * steps + 1, ))
  479. new_codes[0::steps] = codes
  480. else:
  481. new_codes = None
  482. return Path(vertices, new_codes)
  483. def to_polygons(self, transform=None, width=0, height=0, closed_only=True):
  484. """
  485. Convert this path to a list of polygons or polylines. Each
  486. polygon/polyline is an Nx2 array of vertices. In other words,
  487. each polygon has no ``MOVETO`` instructions or curves. This
  488. is useful for displaying in backends that do not support
  489. compound paths or Bezier curves.
  490. If *width* and *height* are both non-zero then the lines will
  491. be simplified so that vertices outside of (0, 0), (width,
  492. height) will be clipped.
  493. If *closed_only* is `True` (default), only closed polygons,
  494. with the last point being the same as the first point, will be
  495. returned. Any unclosed polylines in the path will be
  496. explicitly closed. If *closed_only* is `False`, any unclosed
  497. polygons in the path will be returned as unclosed polygons,
  498. and the closed polygons will be returned explicitly closed by
  499. setting the last point to the same as the first point.
  500. """
  501. if len(self.vertices) == 0:
  502. return []
  503. if transform is not None:
  504. transform = transform.frozen()
  505. if self.codes is None and (width == 0 or height == 0):
  506. vertices = self.vertices
  507. if closed_only:
  508. if len(vertices) < 3:
  509. return []
  510. elif np.any(vertices[0] != vertices[-1]):
  511. vertices = [*vertices, vertices[0]]
  512. if transform is None:
  513. return [vertices]
  514. else:
  515. return [transform.transform(vertices)]
  516. # Deal with the case where there are curves and/or multiple
  517. # subpaths (using extension code)
  518. return _path.convert_path_to_polygons(
  519. self, transform, width, height, closed_only)
  520. _unit_rectangle = None
  521. @classmethod
  522. def unit_rectangle(cls):
  523. """
  524. Return a :class:`Path` instance of the unit rectangle
  525. from (0, 0) to (1, 1).
  526. """
  527. if cls._unit_rectangle is None:
  528. cls._unit_rectangle = \
  529. cls([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
  530. [0.0, 0.0]],
  531. [cls.MOVETO, cls.LINETO, cls.LINETO, cls.LINETO,
  532. cls.CLOSEPOLY],
  533. readonly=True)
  534. return cls._unit_rectangle
  535. _unit_regular_polygons = WeakValueDictionary()
  536. @classmethod
  537. def unit_regular_polygon(cls, numVertices):
  538. """
  539. Return a :class:`Path` instance for a unit regular polygon with the
  540. given *numVertices* and radius of 1.0, centered at (0, 0).
  541. """
  542. if numVertices <= 16:
  543. path = cls._unit_regular_polygons.get(numVertices)
  544. else:
  545. path = None
  546. if path is None:
  547. theta = ((2 * np.pi / numVertices) * np.arange(numVertices + 1)
  548. # This initial rotation is to make sure the polygon always
  549. # "points-up".
  550. + np.pi / 2)
  551. verts = np.column_stack((np.cos(theta), np.sin(theta)))
  552. codes = np.empty(numVertices + 1)
  553. codes[0] = cls.MOVETO
  554. codes[1:-1] = cls.LINETO
  555. codes[-1] = cls.CLOSEPOLY
  556. path = cls(verts, codes, readonly=True)
  557. if numVertices <= 16:
  558. cls._unit_regular_polygons[numVertices] = path
  559. return path
  560. _unit_regular_stars = WeakValueDictionary()
  561. @classmethod
  562. def unit_regular_star(cls, numVertices, innerCircle=0.5):
  563. """
  564. Return a :class:`Path` for a unit regular star with the given
  565. numVertices and radius of 1.0, centered at (0, 0).
  566. """
  567. if numVertices <= 16:
  568. path = cls._unit_regular_stars.get((numVertices, innerCircle))
  569. else:
  570. path = None
  571. if path is None:
  572. ns2 = numVertices * 2
  573. theta = (2*np.pi/ns2 * np.arange(ns2 + 1))
  574. # This initial rotation is to make sure the polygon always
  575. # "points-up"
  576. theta += np.pi / 2.0
  577. r = np.ones(ns2 + 1)
  578. r[1::2] = innerCircle
  579. verts = np.vstack((r*np.cos(theta), r*np.sin(theta))).transpose()
  580. codes = np.empty((ns2 + 1,))
  581. codes[0] = cls.MOVETO
  582. codes[1:-1] = cls.LINETO
  583. codes[-1] = cls.CLOSEPOLY
  584. path = cls(verts, codes, readonly=True)
  585. if numVertices <= 16:
  586. cls._unit_regular_stars[(numVertices, innerCircle)] = path
  587. return path
  588. @classmethod
  589. def unit_regular_asterisk(cls, numVertices):
  590. """
  591. Return a :class:`Path` for a unit regular asterisk with the given
  592. numVertices and radius of 1.0, centered at (0, 0).
  593. """
  594. return cls.unit_regular_star(numVertices, 0.0)
  595. _unit_circle = None
  596. @classmethod
  597. def unit_circle(cls):
  598. """
  599. Return the readonly :class:`Path` of the unit circle.
  600. For most cases, :func:`Path.circle` will be what you want.
  601. """
  602. if cls._unit_circle is None:
  603. cls._unit_circle = cls.circle(center=(0, 0), radius=1,
  604. readonly=True)
  605. return cls._unit_circle
  606. @classmethod
  607. def circle(cls, center=(0., 0.), radius=1., readonly=False):
  608. """
  609. Return a Path representing a circle of a given radius and center.
  610. Parameters
  611. ----------
  612. center : pair of floats
  613. The center of the circle. Default ``(0, 0)``.
  614. radius : float
  615. The radius of the circle. Default is 1.
  616. readonly : bool
  617. Whether the created path should have the "readonly" argument
  618. set when creating the Path instance.
  619. Notes
  620. -----
  621. The circle is approximated using cubic Bezier curves. This
  622. uses 8 splines around the circle using the approach presented
  623. here:
  624. Lancaster, Don. `Approximating a Circle or an Ellipse Using Four
  625. Bezier Cubic Splines <http://www.tinaja.com/glib/ellipse4.pdf>`_.
  626. """
  627. MAGIC = 0.2652031
  628. SQRTHALF = np.sqrt(0.5)
  629. MAGIC45 = SQRTHALF * MAGIC
  630. vertices = np.array([[0.0, -1.0],
  631. [MAGIC, -1.0],
  632. [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
  633. [SQRTHALF, -SQRTHALF],
  634. [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
  635. [1.0, -MAGIC],
  636. [1.0, 0.0],
  637. [1.0, MAGIC],
  638. [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
  639. [SQRTHALF, SQRTHALF],
  640. [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
  641. [MAGIC, 1.0],
  642. [0.0, 1.0],
  643. [-MAGIC, 1.0],
  644. [-SQRTHALF+MAGIC45, SQRTHALF+MAGIC45],
  645. [-SQRTHALF, SQRTHALF],
  646. [-SQRTHALF-MAGIC45, SQRTHALF-MAGIC45],
  647. [-1.0, MAGIC],
  648. [-1.0, 0.0],
  649. [-1.0, -MAGIC],
  650. [-SQRTHALF-MAGIC45, -SQRTHALF+MAGIC45],
  651. [-SQRTHALF, -SQRTHALF],
  652. [-SQRTHALF+MAGIC45, -SQRTHALF-MAGIC45],
  653. [-MAGIC, -1.0],
  654. [0.0, -1.0],
  655. [0.0, -1.0]],
  656. dtype=float)
  657. codes = [cls.CURVE4] * 26
  658. codes[0] = cls.MOVETO
  659. codes[-1] = cls.CLOSEPOLY
  660. return Path(vertices * radius + center, codes, readonly=readonly)
  661. _unit_circle_righthalf = None
  662. @classmethod
  663. def unit_circle_righthalf(cls):
  664. """
  665. Return a :class:`Path` of the right half
  666. of a unit circle. The circle is approximated using cubic Bezier
  667. curves. This uses 4 splines around the circle using the approach
  668. presented here:
  669. Lancaster, Don. `Approximating a Circle or an Ellipse Using Four
  670. Bezier Cubic Splines <http://www.tinaja.com/glib/ellipse4.pdf>`_.
  671. """
  672. if cls._unit_circle_righthalf is None:
  673. MAGIC = 0.2652031
  674. SQRTHALF = np.sqrt(0.5)
  675. MAGIC45 = SQRTHALF * MAGIC
  676. vertices = np.array(
  677. [[0.0, -1.0],
  678. [MAGIC, -1.0],
  679. [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
  680. [SQRTHALF, -SQRTHALF],
  681. [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
  682. [1.0, -MAGIC],
  683. [1.0, 0.0],
  684. [1.0, MAGIC],
  685. [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
  686. [SQRTHALF, SQRTHALF],
  687. [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
  688. [MAGIC, 1.0],
  689. [0.0, 1.0],
  690. [0.0, -1.0]],
  691. float)
  692. codes = cls.CURVE4 * np.ones(14)
  693. codes[0] = cls.MOVETO
  694. codes[-1] = cls.CLOSEPOLY
  695. cls._unit_circle_righthalf = cls(vertices, codes, readonly=True)
  696. return cls._unit_circle_righthalf
  697. @classmethod
  698. def arc(cls, theta1, theta2, n=None, is_wedge=False):
  699. """
  700. Return an arc on the unit circle from angle
  701. *theta1* to angle *theta2* (in degrees).
  702. *theta2* is unwrapped to produce the shortest arc within 360 degrees.
  703. That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to
  704. *theta2* - 360 and not a full circle plus some extra overlap.
  705. If *n* is provided, it is the number of spline segments to make.
  706. If *n* is not provided, the number of spline segments is
  707. determined based on the delta between *theta1* and *theta2*.
  708. Masionobe, L. 2003. `Drawing an elliptical arc using
  709. polylines, quadratic or cubic Bezier curves
  710. <http://www.spaceroots.org/documents/ellipse/index.html>`_.
  711. """
  712. halfpi = np.pi * 0.5
  713. eta1 = theta1
  714. eta2 = theta2 - 360 * np.floor((theta2 - theta1) / 360)
  715. # Ensure 2pi range is not flattened to 0 due to floating-point errors,
  716. # but don't try to expand existing 0 range.
  717. if theta2 != theta1 and eta2 <= eta1:
  718. eta2 += 360
  719. eta1, eta2 = np.deg2rad([eta1, eta2])
  720. # number of curve segments to make
  721. if n is None:
  722. n = int(2 ** np.ceil((eta2 - eta1) / halfpi))
  723. if n < 1:
  724. raise ValueError("n must be >= 1 or None")
  725. deta = (eta2 - eta1) / n
  726. t = np.tan(0.5 * deta)
  727. alpha = np.sin(deta) * (np.sqrt(4.0 + 3.0 * t * t) - 1) / 3.0
  728. steps = np.linspace(eta1, eta2, n + 1, True)
  729. cos_eta = np.cos(steps)
  730. sin_eta = np.sin(steps)
  731. xA = cos_eta[:-1]
  732. yA = sin_eta[:-1]
  733. xA_dot = -yA
  734. yA_dot = xA
  735. xB = cos_eta[1:]
  736. yB = sin_eta[1:]
  737. xB_dot = -yB
  738. yB_dot = xB
  739. if is_wedge:
  740. length = n * 3 + 4
  741. vertices = np.zeros((length, 2), float)
  742. codes = cls.CURVE4 * np.ones((length, ), cls.code_type)
  743. vertices[1] = [xA[0], yA[0]]
  744. codes[0:2] = [cls.MOVETO, cls.LINETO]
  745. codes[-2:] = [cls.LINETO, cls.CLOSEPOLY]
  746. vertex_offset = 2
  747. end = length - 2
  748. else:
  749. length = n * 3 + 1
  750. vertices = np.empty((length, 2), float)
  751. codes = cls.CURVE4 * np.ones((length, ), cls.code_type)
  752. vertices[0] = [xA[0], yA[0]]
  753. codes[0] = cls.MOVETO
  754. vertex_offset = 1
  755. end = length
  756. vertices[vertex_offset:end:3, 0] = xA + alpha * xA_dot
  757. vertices[vertex_offset:end:3, 1] = yA + alpha * yA_dot
  758. vertices[vertex_offset+1:end:3, 0] = xB - alpha * xB_dot
  759. vertices[vertex_offset+1:end:3, 1] = yB - alpha * yB_dot
  760. vertices[vertex_offset+2:end:3, 0] = xB
  761. vertices[vertex_offset+2:end:3, 1] = yB
  762. return cls(vertices, codes, readonly=True)
  763. @classmethod
  764. def wedge(cls, theta1, theta2, n=None):
  765. """
  766. Return a wedge of the unit circle from angle
  767. *theta1* to angle *theta2* (in degrees).
  768. *theta2* is unwrapped to produce the shortest wedge within 360 degrees.
  769. That is, if *theta2* > *theta1* + 360, the wedge will be from *theta1*
  770. to *theta2* - 360 and not a full circle plus some extra overlap.
  771. If *n* is provided, it is the number of spline segments to make.
  772. If *n* is not provided, the number of spline segments is
  773. determined based on the delta between *theta1* and *theta2*.
  774. """
  775. return cls.arc(theta1, theta2, n, True)
  776. @staticmethod
  777. @lru_cache(8)
  778. def hatch(hatchpattern, density=6):
  779. """
  780. Given a hatch specifier, *hatchpattern*, generates a Path that
  781. can be used in a repeated hatching pattern. *density* is the
  782. number of lines per unit square.
  783. """
  784. from matplotlib.hatch import get_path
  785. return (get_path(hatchpattern, density)
  786. if hatchpattern is not None else None)
  787. def clip_to_bbox(self, bbox, inside=True):
  788. """
  789. Clip the path to the given bounding box.
  790. The path must be made up of one or more closed polygons. This
  791. algorithm will not behave correctly for unclosed paths.
  792. If *inside* is `True`, clip to the inside of the box, otherwise
  793. to the outside of the box.
  794. """
  795. # Use make_compound_path_from_polys
  796. verts = _path.clip_path_to_rect(self, bbox, inside)
  797. paths = [Path(poly) for poly in verts]
  798. return self.make_compound_path(*paths)
  799. def get_path_collection_extents(
  800. master_transform, paths, transforms, offsets, offset_transform):
  801. """
  802. Given a sequence of :class:`Path` objects,
  803. :class:`~matplotlib.transforms.Transform` objects and offsets, as
  804. found in a :class:`~matplotlib.collections.PathCollection`,
  805. returns the bounding box that encapsulates all of them.
  806. *master_transform* is a global transformation to apply to all paths
  807. *paths* is a sequence of :class:`Path` instances.
  808. *transforms* is a sequence of
  809. :class:`~matplotlib.transforms.Affine2D` instances.
  810. *offsets* is a sequence of (x, y) offsets (or an Nx2 array)
  811. *offset_transform* is a :class:`~matplotlib.transforms.Affine2D`
  812. to apply to the offsets before applying the offset to the path.
  813. The way that *paths*, *transforms* and *offsets* are combined
  814. follows the same method as for collections. Each is iterated over
  815. independently, so if you have 3 paths, 2 transforms and 1 offset,
  816. their combinations are as follows:
  817. (A, A, A), (B, B, A), (C, A, A)
  818. """
  819. from .transforms import Bbox
  820. if len(paths) == 0:
  821. raise ValueError("No paths provided")
  822. return Bbox.from_extents(*_path.get_path_collection_extents(
  823. master_transform, paths, np.atleast_3d(transforms),
  824. offsets, offset_transform))
  825. def get_paths_extents(paths, transforms=[]):
  826. """
  827. Given a sequence of :class:`Path` objects and optional
  828. :class:`~matplotlib.transforms.Transform` objects, returns the
  829. bounding box that encapsulates all of them.
  830. *paths* is a sequence of :class:`Path` instances.
  831. *transforms* is an optional sequence of
  832. :class:`~matplotlib.transforms.Affine2D` instances to apply to
  833. each path.
  834. """
  835. from .transforms import Bbox, Affine2D
  836. if len(paths) == 0:
  837. raise ValueError("No paths provided")
  838. return Bbox.from_extents(*_path.get_path_collection_extents(
  839. Affine2D(), paths, transforms, [], Affine2D()))