Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/path.py: 29%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1r"""
2A module for dealing with the polylines used throughout Matplotlib.
4The primary class for polyline handling in Matplotlib is `Path`. Almost all
5vector drawing makes use of `Path`\s somewhere in the drawing pipeline.
7Whilst a `Path` instance itself cannot be drawn, some `.Artist` subclasses,
8such as `.PathPatch` and `.PathCollection`, can be used for convenient `Path`
9visualisation.
10"""
12import copy
13from functools import lru_cache
14from weakref import WeakValueDictionary
16import numpy as np
18import matplotlib as mpl
19from . import _api, _path
20from .cbook import _to_unmasked_float_array, simple_linear_interpolation
21from .bezier import BezierSegment
24class Path:
25 """
26 A series of possibly disconnected, possibly closed, line and curve
27 segments.
29 The underlying storage is made up of two parallel numpy arrays:
31 - *vertices*: an (N, 2) float array of vertices
32 - *codes*: an N-length `numpy.uint8` array of path codes, or None
34 These two arrays always have the same length in the first
35 dimension. For example, to represent a cubic curve, you must
36 provide three vertices and three `CURVE4` codes.
38 The code types are:
40 - `STOP` : 1 vertex (ignored)
41 A marker for the end of the entire path (currently not required and
42 ignored)
44 - `MOVETO` : 1 vertex
45 Pick up the pen and move to the given vertex.
47 - `LINETO` : 1 vertex
48 Draw a line from the current position to the given vertex.
50 - `CURVE3` : 1 control point, 1 endpoint
51 Draw a quadratic Bézier curve from the current position, with the given
52 control point, to the given end point.
54 - `CURVE4` : 2 control points, 1 endpoint
55 Draw a cubic Bézier curve from the current position, with the given
56 control points, to the given end point.
58 - `CLOSEPOLY` : 1 vertex (ignored)
59 Draw a line segment to the start point of the current polyline.
61 If *codes* is None, it is interpreted as a `MOVETO` followed by a series
62 of `LINETO`.
64 Users of Path objects should not access the vertices and codes arrays
65 directly. Instead, they should use `iter_segments` or `cleaned` to get the
66 vertex/code pairs. This helps, in particular, to consistently handle the
67 case of *codes* being None.
69 Some behavior of Path objects can be controlled by rcParams. See the
70 rcParams whose keys start with 'path.'.
72 .. note::
74 The vertices and codes arrays should be treated as
75 immutable -- there are a number of optimizations and assumptions
76 made up front in the constructor that will not change when the
77 data changes.
78 """
80 code_type = np.uint8
82 # Path codes
83 STOP = code_type(0) # 1 vertex
84 MOVETO = code_type(1) # 1 vertex
85 LINETO = code_type(2) # 1 vertex
86 CURVE3 = code_type(3) # 2 vertices
87 CURVE4 = code_type(4) # 3 vertices
88 CLOSEPOLY = code_type(79) # 1 vertex
90 #: A dictionary mapping Path codes to the number of vertices that the
91 #: code expects.
92 NUM_VERTICES_FOR_CODE = {STOP: 1,
93 MOVETO: 1,
94 LINETO: 1,
95 CURVE3: 2,
96 CURVE4: 3,
97 CLOSEPOLY: 1}
99 def __init__(self, vertices, codes=None, _interpolation_steps=1,
100 closed=False, readonly=False):
101 """
102 Create a new path with the given vertices and codes.
104 Parameters
105 ----------
106 vertices : (N, 2) array-like
107 The path vertices, as an array, masked array or sequence of pairs.
108 Masked values, if any, will be converted to NaNs, which are then
109 handled correctly by the Agg PathIterator and other consumers of
110 path data, such as :meth:`iter_segments`.
111 codes : array-like or None, optional
112 N-length array of integers representing the codes of the path.
113 If not None, codes must be the same length as vertices.
114 If None, *vertices* will be treated as a series of line segments.
115 _interpolation_steps : int, optional
116 Used as a hint to certain projections, such as Polar, that this
117 path should be linearly interpolated immediately before drawing.
118 This attribute is primarily an implementation detail and is not
119 intended for public use.
120 closed : bool, optional
121 If *codes* is None and closed is True, vertices will be treated as
122 line segments of a closed polygon. Note that the last vertex will
123 then be ignored (as the corresponding code will be set to
124 `CLOSEPOLY`).
125 readonly : bool, optional
126 Makes the path behave in an immutable way and sets the vertices
127 and codes as read-only arrays.
128 """
129 vertices = _to_unmasked_float_array(vertices)
130 _api.check_shape((None, 2), vertices=vertices)
132 if codes is not None:
133 codes = np.asarray(codes, self.code_type)
134 if codes.ndim != 1 or len(codes) != len(vertices):
135 raise ValueError("'codes' must be a 1D list or array with the "
136 "same length of 'vertices'. "
137 f"Your vertices have shape {vertices.shape} "
138 f"but your codes have shape {codes.shape}")
139 if len(codes) and codes[0] != self.MOVETO:
140 raise ValueError("The first element of 'code' must be equal "
141 f"to 'MOVETO' ({self.MOVETO}). "
142 f"Your first code is {codes[0]}")
143 elif closed and len(vertices):
144 codes = np.empty(len(vertices), dtype=self.code_type)
145 codes[0] = self.MOVETO
146 codes[1:-1] = self.LINETO
147 codes[-1] = self.CLOSEPOLY
149 self._vertices = vertices
150 self._codes = codes
151 self._interpolation_steps = _interpolation_steps
152 self._update_values()
154 if readonly:
155 self._vertices.flags.writeable = False
156 if self._codes is not None:
157 self._codes.flags.writeable = False
158 self._readonly = True
159 else:
160 self._readonly = False
162 @classmethod
163 def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None):
164 """
165 Create a Path instance without the expense of calling the constructor.
167 Parameters
168 ----------
169 verts : array-like
170 codes : array
171 internals_from : Path or None
172 If not None, another `Path` from which the attributes
173 ``should_simplify``, ``simplify_threshold``, and
174 ``interpolation_steps`` will be copied. Note that ``readonly`` is
175 never copied, and always set to ``False`` by this constructor.
176 """
177 pth = cls.__new__(cls)
178 pth._vertices = _to_unmasked_float_array(verts)
179 pth._codes = codes
180 pth._readonly = False
181 if internals_from is not None:
182 pth._should_simplify = internals_from._should_simplify
183 pth._simplify_threshold = internals_from._simplify_threshold
184 pth._interpolation_steps = internals_from._interpolation_steps
185 else:
186 pth._should_simplify = True
187 pth._simplify_threshold = mpl.rcParams['path.simplify_threshold']
188 pth._interpolation_steps = 1
189 return pth
191 @classmethod
192 def _create_closed(cls, vertices):
193 """
194 Create a closed polygonal path going through *vertices*.
196 Unlike ``Path(..., closed=True)``, *vertices* should **not** end with
197 an entry for the CLOSEPATH; this entry is added by `._create_closed`.
198 """
199 v = _to_unmasked_float_array(vertices)
200 return cls(np.concatenate([v, v[:1]]), closed=True)
202 def _update_values(self):
203 self._simplify_threshold = mpl.rcParams['path.simplify_threshold']
204 self._should_simplify = (
205 self._simplify_threshold > 0 and
206 mpl.rcParams['path.simplify'] and
207 len(self._vertices) >= 128 and
208 (self._codes is None or np.all(self._codes <= Path.LINETO))
209 )
211 @property
212 def vertices(self):
213 """The vertices of the `Path` as an (N, 2) array."""
214 return self._vertices
216 @vertices.setter
217 def vertices(self, vertices):
218 if self._readonly:
219 raise AttributeError("Can't set vertices on a readonly Path")
220 self._vertices = vertices
221 self._update_values()
223 @property
224 def codes(self):
225 """
226 The list of codes in the `Path` as a 1D array.
228 Each code is one of `STOP`, `MOVETO`, `LINETO`, `CURVE3`, `CURVE4` or
229 `CLOSEPOLY`. For codes that correspond to more than one vertex
230 (`CURVE3` and `CURVE4`), that code will be repeated so that the length
231 of `vertices` and `codes` is always the same.
232 """
233 return self._codes
235 @codes.setter
236 def codes(self, codes):
237 if self._readonly:
238 raise AttributeError("Can't set codes on a readonly Path")
239 self._codes = codes
240 self._update_values()
242 @property
243 def simplify_threshold(self):
244 """
245 The fraction of a pixel difference below which vertices will
246 be simplified out.
247 """
248 return self._simplify_threshold
250 @simplify_threshold.setter
251 def simplify_threshold(self, threshold):
252 self._simplify_threshold = threshold
254 @property
255 def should_simplify(self):
256 """
257 `True` if the vertices array should be simplified.
258 """
259 return self._should_simplify
261 @should_simplify.setter
262 def should_simplify(self, should_simplify):
263 self._should_simplify = should_simplify
265 @property
266 def readonly(self):
267 """
268 `True` if the `Path` is read-only.
269 """
270 return self._readonly
272 def copy(self):
273 """
274 Return a shallow copy of the `Path`, which will share the
275 vertices and codes with the source `Path`.
276 """
277 return copy.copy(self)
279 def __deepcopy__(self, memo=None):
280 """
281 Return a deepcopy of the `Path`. The `Path` will not be
282 readonly, even if the source `Path` is.
283 """
284 # Deepcopying arrays (vertices, codes) strips the writeable=False flag.
285 p = copy.deepcopy(super(), memo)
286 p._readonly = False
287 return p
289 deepcopy = __deepcopy__
291 @classmethod
292 def make_compound_path_from_polys(cls, XY):
293 """
294 Make a compound `Path` object to draw a number of polygons with equal
295 numbers of sides.
297 .. plot:: gallery/misc/histogram_path.py
299 Parameters
300 ----------
301 XY : (numpolys, numsides, 2) array
302 """
303 # for each poly: 1 for the MOVETO, (numsides-1) for the LINETO, 1 for
304 # the CLOSEPOLY; the vert for the closepoly is ignored but we still
305 # need it to keep the codes aligned with the vertices
306 numpolys, numsides, two = XY.shape
307 if two != 2:
308 raise ValueError("The third dimension of 'XY' must be 2")
309 stride = numsides + 1
310 nverts = numpolys * stride
311 verts = np.zeros((nverts, 2))
312 codes = np.full(nverts, cls.LINETO, dtype=cls.code_type)
313 codes[0::stride] = cls.MOVETO
314 codes[numsides::stride] = cls.CLOSEPOLY
315 for i in range(numsides):
316 verts[i::stride] = XY[:, i]
317 return cls(verts, codes)
319 @classmethod
320 def make_compound_path(cls, *args):
321 r"""
322 Concatenate a list of `Path`\s into a single `Path`, removing all `STOP`\s.
323 """
324 if not args:
325 return Path(np.empty([0, 2], dtype=np.float32))
326 vertices = np.concatenate([path.vertices for path in args])
327 codes = np.empty(len(vertices), dtype=cls.code_type)
328 i = 0
329 for path in args:
330 size = len(path.vertices)
331 if path.codes is None:
332 if size:
333 codes[i] = cls.MOVETO
334 codes[i+1:i+size] = cls.LINETO
335 else:
336 codes[i:i+size] = path.codes
337 i += size
338 not_stop_mask = codes != cls.STOP # Remove STOPs, as internal STOPs are a bug.
339 return cls(vertices[not_stop_mask], codes[not_stop_mask])
341 def __repr__(self):
342 return f"Path({self.vertices!r}, {self.codes!r})"
344 def __len__(self):
345 return len(self.vertices)
347 def iter_segments(self, transform=None, remove_nans=True, clip=None,
348 snap=False, stroke_width=1.0, simplify=None,
349 curves=True, sketch=None):
350 """
351 Iterate over all curve segments in the path.
353 Each iteration returns a pair ``(vertices, code)``, where ``vertices``
354 is a sequence of 1-3 coordinate pairs, and ``code`` is a `Path` code.
356 Additionally, this method can provide a number of standard cleanups and
357 conversions to the path.
359 Parameters
360 ----------
361 transform : None or :class:`~matplotlib.transforms.Transform`
362 If not None, the given affine transformation will be applied to the
363 path.
364 remove_nans : bool, optional
365 Whether to remove all NaNs from the path and skip over them using
366 MOVETO commands.
367 clip : None or (float, float, float, float), optional
368 If not None, must be a four-tuple (x1, y1, x2, y2)
369 defining a rectangle in which to clip the path.
370 snap : None or bool, optional
371 If True, snap all nodes to pixels; if False, don't snap them.
372 If None, snap if the path contains only segments
373 parallel to the x or y axes, and no more than 1024 of them.
374 stroke_width : float, optional
375 The width of the stroke being drawn (used for path snapping).
376 simplify : None or bool, optional
377 Whether to simplify the path by removing vertices
378 that do not affect its appearance. If None, use the
379 :attr:`should_simplify` attribute. See also :rc:`path.simplify`
380 and :rc:`path.simplify_threshold`.
381 curves : bool, optional
382 If True, curve segments will be returned as curve segments.
383 If False, all curves will be converted to line segments.
384 sketch : None or sequence, optional
385 If not None, must be a 3-tuple of the form
386 (scale, length, randomness), representing the sketch parameters.
387 """
388 if not len(self):
389 return
391 cleaned = self.cleaned(transform=transform,
392 remove_nans=remove_nans, clip=clip,
393 snap=snap, stroke_width=stroke_width,
394 simplify=simplify, curves=curves,
395 sketch=sketch)
397 # Cache these object lookups for performance in the loop.
398 NUM_VERTICES_FOR_CODE = self.NUM_VERTICES_FOR_CODE
399 STOP = self.STOP
401 vertices = iter(cleaned.vertices)
402 codes = iter(cleaned.codes)
403 for curr_vertices, code in zip(vertices, codes):
404 if code == STOP:
405 break
406 extra_vertices = NUM_VERTICES_FOR_CODE[code] - 1
407 if extra_vertices:
408 for i in range(extra_vertices):
409 next(codes)
410 curr_vertices = np.append(curr_vertices, next(vertices))
411 yield curr_vertices, code
413 def iter_bezier(self, **kwargs):
414 """
415 Iterate over each Bézier curve (lines included) in a `Path`.
417 Parameters
418 ----------
419 **kwargs
420 Forwarded to `.iter_segments`.
422 Yields
423 ------
424 B : `~matplotlib.bezier.BezierSegment`
425 The Bézier curves that make up the current path. Note in particular
426 that freestanding points are Bézier curves of order 0, and lines
427 are Bézier curves of order 1 (with two control points).
428 code : `~matplotlib.path.Path.code_type`
429 The code describing what kind of curve is being returned.
430 `MOVETO`, `LINETO`, `CURVE3`, and `CURVE4` correspond to
431 Bézier curves with 1, 2, 3, and 4 control points (respectively).
432 `CLOSEPOLY` is a `LINETO` with the control points correctly
433 chosen based on the start/end points of the current stroke.
434 """
435 first_vert = None
436 prev_vert = None
437 for verts, code in self.iter_segments(**kwargs):
438 if first_vert is None:
439 if code != Path.MOVETO:
440 raise ValueError("Malformed path, must start with MOVETO.")
441 if code == Path.MOVETO: # a point is like "CURVE1"
442 first_vert = verts
443 yield BezierSegment(np.array([first_vert])), code
444 elif code == Path.LINETO: # "CURVE2"
445 yield BezierSegment(np.array([prev_vert, verts])), code
446 elif code == Path.CURVE3:
447 yield BezierSegment(np.array([prev_vert, verts[:2],
448 verts[2:]])), code
449 elif code == Path.CURVE4:
450 yield BezierSegment(np.array([prev_vert, verts[:2],
451 verts[2:4], verts[4:]])), code
452 elif code == Path.CLOSEPOLY:
453 yield BezierSegment(np.array([prev_vert, first_vert])), code
454 elif code == Path.STOP:
455 return
456 else:
457 raise ValueError(f"Invalid Path.code_type: {code}")
458 prev_vert = verts[-2:]
460 def _iter_connected_components(self):
461 """Return subpaths split at MOVETOs."""
462 if self.codes is None:
463 yield self
464 else:
465 idxs = np.append((self.codes == Path.MOVETO).nonzero()[0], len(self.codes))
466 for sl in map(slice, idxs, idxs[1:]):
467 yield Path._fast_from_codes_and_verts(
468 self.vertices[sl], self.codes[sl], self)
470 def cleaned(self, transform=None, remove_nans=False, clip=None,
471 *, simplify=False, curves=False,
472 stroke_width=1.0, snap=False, sketch=None):
473 """
474 Return a new `Path` with vertices and codes cleaned according to the
475 parameters.
477 See Also
478 --------
479 Path.iter_segments : for details of the keyword arguments.
480 """
481 vertices, codes = _path.cleanup_path(
482 self, transform, remove_nans, clip, snap, stroke_width, simplify,
483 curves, sketch)
484 pth = Path._fast_from_codes_and_verts(vertices, codes, self)
485 if not simplify:
486 pth._should_simplify = False
487 return pth
489 def transformed(self, transform):
490 """
491 Return a transformed copy of the path.
493 See Also
494 --------
495 matplotlib.transforms.TransformedPath
496 A specialized path class that will cache the transformed result and
497 automatically update when the transform changes.
498 """
499 return Path(transform.transform(self.vertices), self.codes,
500 self._interpolation_steps)
502 def contains_point(self, point, transform=None, radius=0.0):
503 """
504 Return whether the area enclosed by the path contains the given point.
506 The path is always treated as closed; i.e. if the last code is not
507 `CLOSEPOLY` an implicit segment connecting the last vertex to the first
508 vertex is assumed.
510 Parameters
511 ----------
512 point : (float, float)
513 The point (x, y) to check.
514 transform : `~matplotlib.transforms.Transform`, optional
515 If not ``None``, *point* will be compared to ``self`` transformed
516 by *transform*; i.e. for a correct check, *transform* should
517 transform the path into the coordinate system of *point*.
518 radius : float, default: 0
519 Additional margin on the path in coordinates of *point*.
520 The path is extended tangentially by *radius/2*; i.e. if you would
521 draw the path with a linewidth of *radius*, all points on the line
522 would still be considered to be contained in the area. Conversely,
523 negative values shrink the area: Points on the imaginary line
524 will be considered outside the area.
526 Returns
527 -------
528 bool
530 Notes
531 -----
532 The current algorithm has some limitations:
534 - The result is undefined for points exactly at the boundary
535 (i.e. at the path shifted by *radius/2*).
536 - The result is undefined if there is no enclosed area, i.e. all
537 vertices are on a straight line.
538 - If bounding lines start to cross each other due to *radius* shift,
539 the result is not guaranteed to be correct.
540 """
541 if transform is not None:
542 transform = transform.frozen()
543 # `point_in_path` does not handle nonlinear transforms, so we
544 # transform the path ourselves. If *transform* is affine, letting
545 # `point_in_path` handle the transform avoids allocating an extra
546 # buffer.
547 if transform and not transform.is_affine:
548 self = transform.transform_path(self)
549 transform = None
550 return _path.point_in_path(point[0], point[1], radius, self, transform)
552 def contains_points(self, points, transform=None, radius=0.0):
553 """
554 Return whether the area enclosed by the path contains the given points.
556 The path is always treated as closed; i.e. if the last code is not
557 `CLOSEPOLY` an implicit segment connecting the last vertex to the first
558 vertex is assumed.
560 Parameters
561 ----------
562 points : (N, 2) array
563 The points to check. Columns contain x and y values.
564 transform : `~matplotlib.transforms.Transform`, optional
565 If not ``None``, *points* will be compared to ``self`` transformed
566 by *transform*; i.e. for a correct check, *transform* should
567 transform the path into the coordinate system of *points*.
568 radius : float, default: 0
569 Additional margin on the path in coordinates of *points*.
570 The path is extended tangentially by *radius/2*; i.e. if you would
571 draw the path with a linewidth of *radius*, all points on the line
572 would still be considered to be contained in the area. Conversely,
573 negative values shrink the area: Points on the imaginary line
574 will be considered outside the area.
576 Returns
577 -------
578 length-N bool array
580 Notes
581 -----
582 The current algorithm has some limitations:
584 - The result is undefined for points exactly at the boundary
585 (i.e. at the path shifted by *radius/2*).
586 - The result is undefined if there is no enclosed area, i.e. all
587 vertices are on a straight line.
588 - If bounding lines start to cross each other due to *radius* shift,
589 the result is not guaranteed to be correct.
590 """
591 if transform is not None:
592 transform = transform.frozen()
593 result = _path.points_in_path(points, radius, self, transform)
594 return result.astype('bool')
596 def contains_path(self, path, transform=None):
597 """
598 Return whether this (closed) path completely contains the given path.
600 If *transform* is not ``None``, the path will be transformed before
601 checking for containment.
602 """
603 if transform is not None:
604 transform = transform.frozen()
605 return _path.path_in_path(self, None, path, transform)
607 def get_extents(self, transform=None, **kwargs):
608 """
609 Get Bbox of the path.
611 Parameters
612 ----------
613 transform : `~matplotlib.transforms.Transform`, optional
614 Transform to apply to path before computing extents, if any.
615 **kwargs
616 Forwarded to `.iter_bezier`.
618 Returns
619 -------
620 matplotlib.transforms.Bbox
621 The extents of the path Bbox([[xmin, ymin], [xmax, ymax]])
622 """
623 from .transforms import Bbox
624 if transform is not None:
625 self = transform.transform_path(self)
626 if self.codes is None:
627 xys = self.vertices
628 elif len(np.intersect1d(self.codes, [Path.CURVE3, Path.CURVE4])) == 0:
629 # Optimization for the straight line case.
630 # Instead of iterating through each curve, consider
631 # each line segment's end-points
632 # (recall that STOP and CLOSEPOLY vertices are ignored)
633 xys = self.vertices[np.isin(self.codes,
634 [Path.MOVETO, Path.LINETO])]
635 else:
636 xys = []
637 for curve, code in self.iter_bezier(**kwargs):
638 # places where the derivative is zero can be extrema
639 _, dzeros = curve.axis_aligned_extrema()
640 # as can the ends of the curve
641 xys.append(curve([0, *dzeros, 1]))
642 xys = np.concatenate(xys)
643 if len(xys):
644 return Bbox([xys.min(axis=0), xys.max(axis=0)])
645 else:
646 return Bbox.null()
648 def intersects_path(self, other, filled=True):
649 """
650 Return whether if this path intersects another given path.
652 If *filled* is True, then this also returns True if one path completely
653 encloses the other (i.e., the paths are treated as filled).
654 """
655 return _path.path_intersects_path(self, other, filled)
657 def intersects_bbox(self, bbox, filled=True):
658 """
659 Return whether this path intersects a given `~.transforms.Bbox`.
661 If *filled* is True, then this also returns True if the path completely
662 encloses the `.Bbox` (i.e., the path is treated as filled).
664 The bounding box is always considered filled.
665 """
666 return _path.path_intersects_rectangle(
667 self, bbox.x0, bbox.y0, bbox.x1, bbox.y1, filled)
669 def interpolated(self, steps):
670 """
671 Return a new path resampled to length N x *steps*.
673 Codes other than `LINETO` are not handled correctly.
674 """
675 if steps == 1:
676 return self
678 vertices = simple_linear_interpolation(self.vertices, steps)
679 codes = self.codes
680 if codes is not None:
681 new_codes = np.full((len(codes) - 1) * steps + 1, Path.LINETO,
682 dtype=self.code_type)
683 new_codes[0::steps] = codes
684 else:
685 new_codes = None
686 return Path(vertices, new_codes)
688 def to_polygons(self, transform=None, width=0, height=0, closed_only=True):
689 """
690 Convert this path to a list of polygons or polylines. Each
691 polygon/polyline is an (N, 2) array of vertices. In other words,
692 each polygon has no `MOVETO` instructions or curves. This
693 is useful for displaying in backends that do not support
694 compound paths or Bézier curves.
696 If *width* and *height* are both non-zero then the lines will
697 be simplified so that vertices outside of (0, 0), (width,
698 height) will be clipped.
700 The resulting polygons will be simplified if the
701 :attr:`Path.should_simplify` attribute of the path is `True`.
703 If *closed_only* is `True` (default), only closed polygons,
704 with the last point being the same as the first point, will be
705 returned. Any unclosed polylines in the path will be
706 explicitly closed. If *closed_only* is `False`, any unclosed
707 polygons in the path will be returned as unclosed polygons,
708 and the closed polygons will be returned explicitly closed by
709 setting the last point to the same as the first point.
710 """
711 if len(self.vertices) == 0:
712 return []
714 if transform is not None:
715 transform = transform.frozen()
717 if self.codes is None and (width == 0 or height == 0):
718 vertices = self.vertices
719 if closed_only:
720 if len(vertices) < 3:
721 return []
722 elif np.any(vertices[0] != vertices[-1]):
723 vertices = [*vertices, vertices[0]]
725 if transform is None:
726 return [vertices]
727 else:
728 return [transform.transform(vertices)]
730 # Deal with the case where there are curves and/or multiple
731 # subpaths (using extension code)
732 return _path.convert_path_to_polygons(
733 self, transform, width, height, closed_only)
735 _unit_rectangle = None
737 @classmethod
738 def unit_rectangle(cls):
739 """
740 Return a `Path` instance of the unit rectangle from (0, 0) to (1, 1).
741 """
742 if cls._unit_rectangle is None:
743 cls._unit_rectangle = cls([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]],
744 closed=True, readonly=True)
745 return cls._unit_rectangle
747 _unit_regular_polygons = WeakValueDictionary()
749 @classmethod
750 def unit_regular_polygon(cls, numVertices):
751 """
752 Return a :class:`Path` instance for a unit regular polygon with the
753 given *numVertices* such that the circumscribing circle has radius 1.0,
754 centered at (0, 0).
755 """
756 if numVertices <= 16:
757 path = cls._unit_regular_polygons.get(numVertices)
758 else:
759 path = None
760 if path is None:
761 theta = ((2 * np.pi / numVertices) * np.arange(numVertices + 1)
762 # This initial rotation is to make sure the polygon always
763 # "points-up".
764 + np.pi / 2)
765 verts = np.column_stack((np.cos(theta), np.sin(theta)))
766 path = cls(verts, closed=True, readonly=True)
767 if numVertices <= 16:
768 cls._unit_regular_polygons[numVertices] = path
769 return path
771 _unit_regular_stars = WeakValueDictionary()
773 @classmethod
774 def unit_regular_star(cls, numVertices, innerCircle=0.5):
775 """
776 Return a :class:`Path` for a unit regular star with the given
777 numVertices and radius of 1.0, centered at (0, 0).
778 """
779 if numVertices <= 16:
780 path = cls._unit_regular_stars.get((numVertices, innerCircle))
781 else:
782 path = None
783 if path is None:
784 ns2 = numVertices * 2
785 theta = (2*np.pi/ns2 * np.arange(ns2 + 1))
786 # This initial rotation is to make sure the polygon always
787 # "points-up"
788 theta += np.pi / 2.0
789 r = np.ones(ns2 + 1)
790 r[1::2] = innerCircle
791 verts = (r * np.vstack((np.cos(theta), np.sin(theta)))).T
792 path = cls(verts, closed=True, readonly=True)
793 if numVertices <= 16:
794 cls._unit_regular_stars[(numVertices, innerCircle)] = path
795 return path
797 @classmethod
798 def unit_regular_asterisk(cls, numVertices):
799 """
800 Return a :class:`Path` for a unit regular asterisk with the given
801 numVertices and radius of 1.0, centered at (0, 0).
802 """
803 return cls.unit_regular_star(numVertices, 0.0)
805 _unit_circle = None
807 @classmethod
808 def unit_circle(cls):
809 """
810 Return the readonly :class:`Path` of the unit circle.
812 For most cases, :func:`Path.circle` will be what you want.
813 """
814 if cls._unit_circle is None:
815 cls._unit_circle = cls.circle(center=(0, 0), radius=1,
816 readonly=True)
817 return cls._unit_circle
819 @classmethod
820 def circle(cls, center=(0., 0.), radius=1., readonly=False):
821 """
822 Return a `Path` representing a circle of a given radius and center.
824 Parameters
825 ----------
826 center : (float, float), default: (0, 0)
827 The center of the circle.
828 radius : float, default: 1
829 The radius of the circle.
830 readonly : bool
831 Whether the created path should have the "readonly" argument
832 set when creating the Path instance.
834 Notes
835 -----
836 The circle is approximated using 8 cubic Bézier curves, as described in
838 Lancaster, Don. `Approximating a Circle or an Ellipse Using Four
839 Bezier Cubic Splines <https://www.tinaja.com/glib/ellipse4.pdf>`_.
840 """
841 MAGIC = 0.2652031
842 SQRTHALF = np.sqrt(0.5)
843 MAGIC45 = SQRTHALF * MAGIC
845 vertices = np.array([[0.0, -1.0],
847 [MAGIC, -1.0],
848 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
849 [SQRTHALF, -SQRTHALF],
851 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
852 [1.0, -MAGIC],
853 [1.0, 0.0],
855 [1.0, MAGIC],
856 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
857 [SQRTHALF, SQRTHALF],
859 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
860 [MAGIC, 1.0],
861 [0.0, 1.0],
863 [-MAGIC, 1.0],
864 [-SQRTHALF+MAGIC45, SQRTHALF+MAGIC45],
865 [-SQRTHALF, SQRTHALF],
867 [-SQRTHALF-MAGIC45, SQRTHALF-MAGIC45],
868 [-1.0, MAGIC],
869 [-1.0, 0.0],
871 [-1.0, -MAGIC],
872 [-SQRTHALF-MAGIC45, -SQRTHALF+MAGIC45],
873 [-SQRTHALF, -SQRTHALF],
875 [-SQRTHALF+MAGIC45, -SQRTHALF-MAGIC45],
876 [-MAGIC, -1.0],
877 [0.0, -1.0],
879 [0.0, -1.0]],
880 dtype=float)
882 codes = [cls.CURVE4] * 26
883 codes[0] = cls.MOVETO
884 codes[-1] = cls.CLOSEPOLY
885 return Path(vertices * radius + center, codes, readonly=readonly)
887 _unit_circle_righthalf = None
889 @classmethod
890 def unit_circle_righthalf(cls):
891 """
892 Return a `Path` of the right half of a unit circle.
894 See `Path.circle` for the reference on the approximation used.
895 """
896 if cls._unit_circle_righthalf is None:
897 MAGIC = 0.2652031
898 SQRTHALF = np.sqrt(0.5)
899 MAGIC45 = SQRTHALF * MAGIC
901 vertices = np.array(
902 [[0.0, -1.0],
904 [MAGIC, -1.0],
905 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
906 [SQRTHALF, -SQRTHALF],
908 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
909 [1.0, -MAGIC],
910 [1.0, 0.0],
912 [1.0, MAGIC],
913 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
914 [SQRTHALF, SQRTHALF],
916 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
917 [MAGIC, 1.0],
918 [0.0, 1.0],
920 [0.0, -1.0]],
922 float)
924 codes = np.full(14, cls.CURVE4, dtype=cls.code_type)
925 codes[0] = cls.MOVETO
926 codes[-1] = cls.CLOSEPOLY
928 cls._unit_circle_righthalf = cls(vertices, codes, readonly=True)
929 return cls._unit_circle_righthalf
931 @classmethod
932 def arc(cls, theta1, theta2, n=None, is_wedge=False):
933 """
934 Return a `Path` for the unit circle arc from angles *theta1* to
935 *theta2* (in degrees).
937 *theta2* is unwrapped to produce the shortest arc within 360 degrees.
938 That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to
939 *theta2* - 360 and not a full circle plus some extra overlap.
941 If *n* is provided, it is the number of spline segments to make.
942 If *n* is not provided, the number of spline segments is
943 determined based on the delta between *theta1* and *theta2*.
945 Masionobe, L. 2003. `Drawing an elliptical arc using
946 polylines, quadratic or cubic Bezier curves
947 <https://web.archive.org/web/20190318044212/http://www.spaceroots.org/documents/ellipse/index.html>`_.
948 """
949 halfpi = np.pi * 0.5
951 eta1 = theta1
952 eta2 = theta2 - 360 * np.floor((theta2 - theta1) / 360)
953 # Ensure 2pi range is not flattened to 0 due to floating-point errors,
954 # but don't try to expand existing 0 range.
955 if theta2 != theta1 and eta2 <= eta1:
956 eta2 += 360
957 eta1, eta2 = np.deg2rad([eta1, eta2])
959 # number of curve segments to make
960 if n is None:
961 n = int(2 ** np.ceil((eta2 - eta1) / halfpi))
962 if n < 1:
963 raise ValueError("n must be >= 1 or None")
965 deta = (eta2 - eta1) / n
966 t = np.tan(0.5 * deta)
967 alpha = np.sin(deta) * (np.sqrt(4.0 + 3.0 * t * t) - 1) / 3.0
969 steps = np.linspace(eta1, eta2, n + 1, True)
970 cos_eta = np.cos(steps)
971 sin_eta = np.sin(steps)
973 xA = cos_eta[:-1]
974 yA = sin_eta[:-1]
975 xA_dot = -yA
976 yA_dot = xA
978 xB = cos_eta[1:]
979 yB = sin_eta[1:]
980 xB_dot = -yB
981 yB_dot = xB
983 if is_wedge:
984 length = n * 3 + 4
985 vertices = np.zeros((length, 2), float)
986 codes = np.full(length, cls.CURVE4, dtype=cls.code_type)
987 vertices[1] = [xA[0], yA[0]]
988 codes[0:2] = [cls.MOVETO, cls.LINETO]
989 codes[-2:] = [cls.LINETO, cls.CLOSEPOLY]
990 vertex_offset = 2
991 end = length - 2
992 else:
993 length = n * 3 + 1
994 vertices = np.empty((length, 2), float)
995 codes = np.full(length, cls.CURVE4, dtype=cls.code_type)
996 vertices[0] = [xA[0], yA[0]]
997 codes[0] = cls.MOVETO
998 vertex_offset = 1
999 end = length
1001 vertices[vertex_offset:end:3, 0] = xA + alpha * xA_dot
1002 vertices[vertex_offset:end:3, 1] = yA + alpha * yA_dot
1003 vertices[vertex_offset+1:end:3, 0] = xB - alpha * xB_dot
1004 vertices[vertex_offset+1:end:3, 1] = yB - alpha * yB_dot
1005 vertices[vertex_offset+2:end:3, 0] = xB
1006 vertices[vertex_offset+2:end:3, 1] = yB
1008 return cls(vertices, codes, readonly=True)
1010 @classmethod
1011 def wedge(cls, theta1, theta2, n=None):
1012 """
1013 Return a `Path` for the unit circle wedge from angles *theta1* to
1014 *theta2* (in degrees).
1016 *theta2* is unwrapped to produce the shortest wedge within 360 degrees.
1017 That is, if *theta2* > *theta1* + 360, the wedge will be from *theta1*
1018 to *theta2* - 360 and not a full circle plus some extra overlap.
1020 If *n* is provided, it is the number of spline segments to make.
1021 If *n* is not provided, the number of spline segments is
1022 determined based on the delta between *theta1* and *theta2*.
1024 See `Path.arc` for the reference on the approximation used.
1025 """
1026 return cls.arc(theta1, theta2, n, True)
1028 @staticmethod
1029 @lru_cache(8)
1030 def hatch(hatchpattern, density=6):
1031 """
1032 Given a hatch specifier, *hatchpattern*, generates a `Path` that
1033 can be used in a repeated hatching pattern. *density* is the
1034 number of lines per unit square.
1035 """
1036 from matplotlib.hatch import get_path
1037 return (get_path(hatchpattern, density)
1038 if hatchpattern is not None else None)
1040 def clip_to_bbox(self, bbox, inside=True):
1041 """
1042 Clip the path to the given bounding box.
1044 The path must be made up of one or more closed polygons. This
1045 algorithm will not behave correctly for unclosed paths.
1047 If *inside* is `True`, clip to the inside of the box, otherwise
1048 to the outside of the box.
1049 """
1050 verts = _path.clip_path_to_rect(self, bbox, inside)
1051 paths = [Path(poly) for poly in verts]
1052 return self.make_compound_path(*paths)
1055def get_path_collection_extents(
1056 master_transform, paths, transforms, offsets, offset_transform):
1057 r"""
1058 Get bounding box of a `.PathCollection`\s internal objects.
1060 That is, given a sequence of `Path`\s, `.Transform`\s objects, and offsets, as found
1061 in a `.PathCollection`, return the bounding box that encapsulates all of them.
1063 Parameters
1064 ----------
1065 master_transform : `~matplotlib.transforms.Transform`
1066 Global transformation applied to all paths.
1067 paths : list of `Path`
1068 transforms : list of `~matplotlib.transforms.Affine2DBase`
1069 If non-empty, this overrides *master_transform*.
1070 offsets : (N, 2) array-like
1071 offset_transform : `~matplotlib.transforms.Affine2DBase`
1072 Transform applied to the offsets before offsetting the path.
1074 Notes
1075 -----
1076 The way that *paths*, *transforms* and *offsets* are combined follows the same
1077 method as for collections: each is iterated over independently, so if you have 3
1078 paths (A, B, C), 2 transforms (α, β) and 1 offset (O), their combinations are as
1079 follows:
1081 - (A, α, O)
1082 - (B, β, O)
1083 - (C, α, O)
1084 """
1085 from .transforms import Bbox
1086 if len(paths) == 0:
1087 raise ValueError("No paths provided")
1088 if len(offsets) == 0:
1089 _api.warn_deprecated(
1090 "3.8", message="Calling get_path_collection_extents() with an"
1091 " empty offsets list is deprecated since %(since)s. Support will"
1092 " be removed %(removal)s.")
1093 extents, minpos = _path.get_path_collection_extents(
1094 master_transform, paths, np.atleast_3d(transforms),
1095 offsets, offset_transform)
1096 return Bbox.from_extents(*extents, minpos=minpos)