Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/path.py : 26%

Hot-keys on this page
r m x p 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"""
12from functools import lru_cache
13from weakref import WeakValueDictionary
15import numpy as np
17from . import _path, cbook, rcParams
18from .cbook import _to_unmasked_float_array, simple_linear_interpolation
21class Path:
22 """
23 A series of possibly disconnected, possibly closed, line and curve
24 segments.
26 The underlying storage is made up of two parallel numpy arrays:
28 - *vertices*: an Nx2 float array of vertices
29 - *codes*: an N-length uint8 array of vertex types, or None
31 These two arrays always have the same length in the first
32 dimension. For example, to represent a cubic curve, you must
33 provide three vertices as well as three codes ``CURVE3``.
35 The code types are:
37 - ``STOP`` : 1 vertex (ignored)
38 A marker for the end of the entire path (currently not required and
39 ignored)
41 - ``MOVETO`` : 1 vertex
42 Pick up the pen and move to the given vertex.
44 - ``LINETO`` : 1 vertex
45 Draw a line from the current position to the given vertex.
47 - ``CURVE3`` : 1 control point, 1 endpoint
48 Draw a quadratic Bezier curve from the current position, with the given
49 control point, to the given end point.
51 - ``CURVE4`` : 2 control points, 1 endpoint
52 Draw a cubic Bezier curve from the current position, with the given
53 control points, to the given end point.
55 - ``CLOSEPOLY`` : 1 vertex (ignored)
56 Draw a line segment to the start point of the current polyline.
58 If *codes* is None, it is interpreted as a ``MOVETO`` followed by a series
59 of ``LINETO``.
61 Users of Path objects should not access the vertices and codes arrays
62 directly. Instead, they should use `iter_segments` or `cleaned` to get the
63 vertex/code pairs. This helps, in particular, to consistently handle the
64 case of *codes* being None.
66 Some behavior of Path objects can be controlled by rcParams. See the
67 rcParams whose keys start with 'path.'.
69 .. note::
71 The vertices and codes arrays should be treated as
72 immutable -- there are a number of optimizations and assumptions
73 made up front in the constructor that will not change when the
74 data changes.
75 """
77 code_type = np.uint8
79 # Path codes
80 STOP = code_type(0) # 1 vertex
81 MOVETO = code_type(1) # 1 vertex
82 LINETO = code_type(2) # 1 vertex
83 CURVE3 = code_type(3) # 2 vertices
84 CURVE4 = code_type(4) # 3 vertices
85 CLOSEPOLY = code_type(79) # 1 vertex
87 #: A dictionary mapping Path codes to the number of vertices that the
88 #: code expects.
89 NUM_VERTICES_FOR_CODE = {STOP: 1,
90 MOVETO: 1,
91 LINETO: 1,
92 CURVE3: 2,
93 CURVE4: 3,
94 CLOSEPOLY: 1}
96 def __init__(self, vertices, codes=None, _interpolation_steps=1,
97 closed=False, readonly=False):
98 """
99 Create a new path with the given vertices and codes.
101 Parameters
102 ----------
103 vertices : array-like
104 The ``(N, 2)`` float array, masked array or sequence of pairs
105 representing the vertices of the path.
107 If *vertices* contains masked values, they will be converted
108 to NaNs which are then handled correctly by the Agg
109 PathIterator and other consumers of path data, such as
110 :meth:`iter_segments`.
111 codes : array-like or None, optional
112 n-length array 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.
123 readonly : bool, optional
124 Makes the path behave in an immutable way and sets the vertices
125 and codes as read-only arrays.
126 """
127 vertices = _to_unmasked_float_array(vertices)
128 if vertices.ndim != 2 or vertices.shape[1] != 2:
129 raise ValueError(
130 "'vertices' must be a 2D list or array with shape Nx2")
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 if len(codes) and codes[0] != self.MOVETO:
138 raise ValueError("The first element of 'code' must be equal "
139 "to 'MOVETO' ({})".format(self.MOVETO))
140 elif closed and len(vertices):
141 codes = np.empty(len(vertices), dtype=self.code_type)
142 codes[0] = self.MOVETO
143 codes[1:-1] = self.LINETO
144 codes[-1] = self.CLOSEPOLY
146 self._vertices = vertices
147 self._codes = codes
148 self._interpolation_steps = _interpolation_steps
149 self._update_values()
151 if readonly:
152 self._vertices.flags.writeable = False
153 if self._codes is not None:
154 self._codes.flags.writeable = False
155 self._readonly = True
156 else:
157 self._readonly = False
159 @classmethod
160 def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None):
161 """
162 Creates a Path instance without the expense of calling the constructor.
164 Parameters
165 ----------
166 verts : numpy array
167 codes : numpy array
168 internals_from : Path or None
169 If not None, another `Path` from which the attributes
170 ``should_simplify``, ``simplify_threshold``, and
171 ``interpolation_steps`` will be copied. Note that ``readonly`` is
172 never copied, and always set to ``False`` by this constructor.
173 """
174 pth = cls.__new__(cls)
175 pth._vertices = _to_unmasked_float_array(verts)
176 pth._codes = codes
177 pth._readonly = False
178 if internals_from is not None:
179 pth._should_simplify = internals_from._should_simplify
180 pth._simplify_threshold = internals_from._simplify_threshold
181 pth._interpolation_steps = internals_from._interpolation_steps
182 else:
183 pth._should_simplify = True
184 pth._simplify_threshold = rcParams['path.simplify_threshold']
185 pth._interpolation_steps = 1
186 return pth
188 def _update_values(self):
189 self._simplify_threshold = rcParams['path.simplify_threshold']
190 self._should_simplify = (
191 self._simplify_threshold > 0 and
192 rcParams['path.simplify'] and
193 len(self._vertices) >= 128 and
194 (self._codes is None or np.all(self._codes <= Path.LINETO))
195 )
197 @property
198 def vertices(self):
199 """
200 The list of vertices in the `Path` as an Nx2 numpy array.
201 """
202 return self._vertices
204 @vertices.setter
205 def vertices(self, vertices):
206 if self._readonly:
207 raise AttributeError("Can't set vertices on a readonly Path")
208 self._vertices = vertices
209 self._update_values()
211 @property
212 def codes(self):
213 """
214 The list of codes in the `Path` as a 1-D numpy array. Each
215 code is one of `STOP`, `MOVETO`, `LINETO`, `CURVE3`, `CURVE4`
216 or `CLOSEPOLY`. For codes that correspond to more than one
217 vertex (`CURVE3` and `CURVE4`), that code will be repeated so
218 that the length of `self.vertices` and `self.codes` is always
219 the same.
220 """
221 return self._codes
223 @codes.setter
224 def codes(self, codes):
225 if self._readonly:
226 raise AttributeError("Can't set codes on a readonly Path")
227 self._codes = codes
228 self._update_values()
230 @property
231 def simplify_threshold(self):
232 """
233 The fraction of a pixel difference below which vertices will
234 be simplified out.
235 """
236 return self._simplify_threshold
238 @simplify_threshold.setter
239 def simplify_threshold(self, threshold):
240 self._simplify_threshold = threshold
242 @cbook.deprecated(
243 "3.1", alternative="not np.isfinite(self.vertices).all()")
244 @property
245 def has_nonfinite(self):
246 """
247 `True` if the vertices array has nonfinite values.
248 """
249 return not np.isfinite(self._vertices).all()
251 @property
252 def should_simplify(self):
253 """
254 `True` if the vertices array should be simplified.
255 """
256 return self._should_simplify
258 @should_simplify.setter
259 def should_simplify(self, should_simplify):
260 self._should_simplify = should_simplify
262 @property
263 def readonly(self):
264 """
265 `True` if the `Path` is read-only.
266 """
267 return self._readonly
269 def __copy__(self):
270 """
271 Returns a shallow copy of the `Path`, which will share the
272 vertices and codes with the source `Path`.
273 """
274 import copy
275 return copy.copy(self)
277 copy = __copy__
279 def __deepcopy__(self, memo=None):
280 """
281 Returns a deepcopy of the `Path`. The `Path` will not be
282 readonly, even if the source `Path` is.
283 """
284 try:
285 codes = self.codes.copy()
286 except AttributeError:
287 codes = None
288 return self.__class__(
289 self.vertices.copy(), codes,
290 _interpolation_steps=self._interpolation_steps)
292 deepcopy = __deepcopy__
294 @classmethod
295 def make_compound_path_from_polys(cls, XY):
296 """
297 Make a compound path object to draw a number
298 of polygons with equal numbers of sides XY is a (numpolys x
299 numsides x 2) numpy array of vertices. Return object is a
300 :class:`Path`
302 .. plot:: gallery/misc/histogram_path.py
304 """
306 # for each poly: 1 for the MOVETO, (numsides-1) for the LINETO, 1 for
307 # the CLOSEPOLY; the vert for the closepoly is ignored but we still
308 # need it to keep the codes aligned with the vertices
309 numpolys, numsides, two = XY.shape
310 if two != 2:
311 raise ValueError("The third dimension of 'XY' must be 2")
312 stride = numsides + 1
313 nverts = numpolys * stride
314 verts = np.zeros((nverts, 2))
315 codes = np.full(nverts, cls.LINETO, dtype=cls.code_type)
316 codes[0::stride] = cls.MOVETO
317 codes[numsides::stride] = cls.CLOSEPOLY
318 for i in range(numsides):
319 verts[i::stride] = XY[:, i]
321 return cls(verts, codes)
323 @classmethod
324 def make_compound_path(cls, *args):
325 """Make a compound path from a list of Path objects."""
326 # Handle an empty list in args (i.e. no args).
327 if not args:
328 return Path(np.empty([0, 2], dtype=np.float32))
330 lengths = [len(x) for x in args]
331 total_length = sum(lengths)
333 vertices = np.vstack([x.vertices for x in args])
334 vertices.reshape((total_length, 2))
336 codes = np.empty(total_length, dtype=cls.code_type)
337 i = 0
338 for path in args:
339 if path.codes is None:
340 codes[i] = cls.MOVETO
341 codes[i + 1:i + len(path.vertices)] = cls.LINETO
342 else:
343 codes[i:i + len(path.codes)] = path.codes
344 i += len(path.vertices)
346 return cls(vertices, codes)
348 def __repr__(self):
349 return "Path(%r, %r)" % (self.vertices, self.codes)
351 def __len__(self):
352 return len(self.vertices)
354 def iter_segments(self, transform=None, remove_nans=True, clip=None,
355 snap=False, stroke_width=1.0, simplify=None,
356 curves=True, sketch=None):
357 """
358 Iterates over all of the curve segments in the path. Each iteration
359 returns a 2-tuple ``(vertices, code)``, where ``vertices`` is a
360 sequence of 1-3 coordinate pairs, and ``code`` is a `Path` code.
362 Additionally, this method can provide a number of standard cleanups and
363 conversions to the path.
365 Parameters
366 ----------
367 transform : None or :class:`~matplotlib.transforms.Transform`
368 If not None, the given affine transformation will be applied to the
369 path.
370 remove_nans : bool, optional
371 Whether to remove all NaNs from the path and skip over them using
372 MOVETO commands.
373 clip : None or (float, float, float, float), optional
374 If not None, must be a four-tuple (x1, y1, x2, y2)
375 defining a rectangle in which to clip the path.
376 snap : None or bool, optional
377 If True, snap all nodes to pixels; if False, don't snap them.
378 If None, perform snapping if the path contains only segments
379 parallel to the x or y axes, and no more than 1024 of them.
380 stroke_width : float, optional
381 The width of the stroke being drawn (used for path snapping).
382 simplify : None or bool, optional
383 Whether to simplify the path by removing vertices
384 that do not affect its appearance. If None, use the
385 :attr:`should_simplify` attribute. See also :rc:`path.simplify`
386 and :rc:`path.simplify_threshold`.
387 curves : bool, optional
388 If True, curve segments will be returned as curve segments.
389 If False, all curves will be converted to line segments.
390 sketch : None or sequence, optional
391 If not None, must be a 3-tuple of the form
392 (scale, length, randomness), representing the sketch parameters.
393 """
394 if not len(self):
395 return
397 cleaned = self.cleaned(transform=transform,
398 remove_nans=remove_nans, clip=clip,
399 snap=snap, stroke_width=stroke_width,
400 simplify=simplify, curves=curves,
401 sketch=sketch)
403 # Cache these object lookups for performance in the loop.
404 NUM_VERTICES_FOR_CODE = self.NUM_VERTICES_FOR_CODE
405 STOP = self.STOP
407 vertices = iter(cleaned.vertices)
408 codes = iter(cleaned.codes)
409 for curr_vertices, code in zip(vertices, codes):
410 if code == STOP:
411 break
412 extra_vertices = NUM_VERTICES_FOR_CODE[code] - 1
413 if extra_vertices:
414 for i in range(extra_vertices):
415 next(codes)
416 curr_vertices = np.append(curr_vertices, next(vertices))
417 yield curr_vertices, code
419 def cleaned(self, transform=None, remove_nans=False, clip=None,
420 quantize=False, simplify=False, curves=False,
421 stroke_width=1.0, snap=False, sketch=None):
422 """
423 Return a new Path with vertices and codes cleaned according to the
424 parameters.
426 See Also
427 --------
428 Path.iter_segments : for details of the keyword arguments.
429 """
430 vertices, codes = _path.cleanup_path(
431 self, transform, remove_nans, clip, snap, stroke_width, simplify,
432 curves, sketch)
433 pth = Path._fast_from_codes_and_verts(vertices, codes, self)
434 if not simplify:
435 pth._should_simplify = False
436 return pth
438 def transformed(self, transform):
439 """
440 Return a transformed copy of the path.
442 See Also
443 --------
444 matplotlib.transforms.TransformedPath
445 A specialized path class that will cache the transformed result and
446 automatically update when the transform changes.
447 """
448 return Path(transform.transform(self.vertices), self.codes,
449 self._interpolation_steps)
451 def contains_point(self, point, transform=None, radius=0.0):
452 """
453 Return whether the (closed) path contains the given point.
455 Parameters
456 ----------
457 point : (float, float)
458 The point (x, y) to check.
459 transform : `matplotlib.transforms.Transform`, optional
460 If not ``None``, *point* will be compared to ``self`` transformed
461 by *transform*; i.e. for a correct check, *transform* should
462 transform the path into the coordinate system of *point*.
463 radius : float, default: 0
464 Add an additional margin on the path in coordinates of *point*.
465 The path is extended tangentially by *radius/2*; i.e. if you would
466 draw the path with a linewidth of *radius*, all points on the line
467 would still be considered to be contained in the area. Conversely,
468 negative values shrink the area: Points on the imaginary line
469 will be considered outside the area.
471 Returns
472 -------
473 bool
474 """
475 if transform is not None:
476 transform = transform.frozen()
477 # `point_in_path` does not handle nonlinear transforms, so we
478 # transform the path ourselves. If *transform* is affine, letting
479 # `point_in_path` handle the transform avoids allocating an extra
480 # buffer.
481 if transform and not transform.is_affine:
482 self = transform.transform_path(self)
483 transform = None
484 return _path.point_in_path(point[0], point[1], radius, self, transform)
486 def contains_points(self, points, transform=None, radius=0.0):
487 """
488 Return whether the (closed) path contains the given point.
490 Parameters
491 ----------
492 points : (N, 2) array
493 The points to check. Columns contain x and y values.
494 transform : `matplotlib.transforms.Transform`, optional
495 If not ``None``, *points* will be compared to ``self`` transformed
496 by *transform*; i.e. for a correct check, *transform* should
497 transform the path into the coordinate system of *points*.
498 radius : float, default: 0.
499 Add an additional margin on the path in coordinates of *points*.
500 The path is extended tangentially by *radius/2*; i.e. if you would
501 draw the path with a linewidth of *radius*, all points on the line
502 would still be considered to be contained in the area. Conversely,
503 negative values shrink the area: Points on the imaginary line
504 will be considered outside the area.
506 Returns
507 -------
508 length-N bool array
509 """
510 if transform is not None:
511 transform = transform.frozen()
512 result = _path.points_in_path(points, radius, self, transform)
513 return result.astype('bool')
515 def contains_path(self, path, transform=None):
516 """
517 Returns whether this (closed) path completely contains the given path.
519 If *transform* is not ``None``, the path will be transformed before
520 performing the test.
521 """
522 if transform is not None:
523 transform = transform.frozen()
524 return _path.path_in_path(self, None, path, transform)
526 def get_extents(self, transform=None):
527 """
528 Returns the extents (*xmin*, *ymin*, *xmax*, *ymax*) of the path.
530 Unlike computing the extents on the *vertices* alone, this
531 algorithm will take into account the curves and deal with
532 control points appropriately.
533 """
534 from .transforms import Bbox
535 path = self
536 if transform is not None:
537 transform = transform.frozen()
538 if not transform.is_affine:
539 path = self.transformed(transform)
540 transform = None
541 return Bbox(_path.get_path_extents(path, transform))
543 def intersects_path(self, other, filled=True):
544 """
545 Returns *True* if this path intersects another given path.
547 *filled*, when True, treats the paths as if they were filled.
548 That is, if one path completely encloses the other,
549 :meth:`intersects_path` will return True.
550 """
551 return _path.path_intersects_path(self, other, filled)
553 def intersects_bbox(self, bbox, filled=True):
554 """
555 Returns whether this path intersects a given `~.transforms.Bbox`.
557 *filled*, when True, treats the path as if it was filled.
558 That is, if the path completely encloses the bounding box,
559 :meth:`intersects_bbox` will return True.
561 The bounding box is always considered filled.
562 """
563 return _path.path_intersects_rectangle(self,
564 bbox.x0, bbox.y0, bbox.x1, bbox.y1, filled)
566 def interpolated(self, steps):
567 """
568 Returns a new path resampled to length N x steps. Does not
569 currently handle interpolating curves.
570 """
571 if steps == 1:
572 return self
574 vertices = simple_linear_interpolation(self.vertices, steps)
575 codes = self.codes
576 if codes is not None:
577 new_codes = np.full((len(codes) - 1) * steps + 1, Path.LINETO,
578 dtype=self.code_type)
579 new_codes[0::steps] = codes
580 else:
581 new_codes = None
582 return Path(vertices, new_codes)
584 def to_polygons(self, transform=None, width=0, height=0, closed_only=True):
585 """
586 Convert this path to a list of polygons or polylines. Each
587 polygon/polyline is an Nx2 array of vertices. In other words,
588 each polygon has no ``MOVETO`` instructions or curves. This
589 is useful for displaying in backends that do not support
590 compound paths or Bezier curves.
592 If *width* and *height* are both non-zero then the lines will
593 be simplified so that vertices outside of (0, 0), (width,
594 height) will be clipped.
596 If *closed_only* is `True` (default), only closed polygons,
597 with the last point being the same as the first point, will be
598 returned. Any unclosed polylines in the path will be
599 explicitly closed. If *closed_only* is `False`, any unclosed
600 polygons in the path will be returned as unclosed polygons,
601 and the closed polygons will be returned explicitly closed by
602 setting the last point to the same as the first point.
603 """
604 if len(self.vertices) == 0:
605 return []
607 if transform is not None:
608 transform = transform.frozen()
610 if self.codes is None and (width == 0 or height == 0):
611 vertices = self.vertices
612 if closed_only:
613 if len(vertices) < 3:
614 return []
615 elif np.any(vertices[0] != vertices[-1]):
616 vertices = [*vertices, vertices[0]]
618 if transform is None:
619 return [vertices]
620 else:
621 return [transform.transform(vertices)]
623 # Deal with the case where there are curves and/or multiple
624 # subpaths (using extension code)
625 return _path.convert_path_to_polygons(
626 self, transform, width, height, closed_only)
628 _unit_rectangle = None
630 @classmethod
631 def unit_rectangle(cls):
632 """
633 Return a `Path` instance of the unit rectangle from (0, 0) to (1, 1).
634 """
635 if cls._unit_rectangle is None:
636 cls._unit_rectangle = \
637 cls([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
638 [0.0, 0.0]],
639 [cls.MOVETO, cls.LINETO, cls.LINETO, cls.LINETO,
640 cls.CLOSEPOLY],
641 readonly=True)
642 return cls._unit_rectangle
644 _unit_regular_polygons = WeakValueDictionary()
646 @classmethod
647 def unit_regular_polygon(cls, numVertices):
648 """
649 Return a :class:`Path` instance for a unit regular polygon with the
650 given *numVertices* and radius of 1.0, centered at (0, 0).
651 """
652 if numVertices <= 16:
653 path = cls._unit_regular_polygons.get(numVertices)
654 else:
655 path = None
656 if path is None:
657 theta = ((2 * np.pi / numVertices) * np.arange(numVertices + 1)
658 # This initial rotation is to make sure the polygon always
659 # "points-up".
660 + np.pi / 2)
661 verts = np.column_stack((np.cos(theta), np.sin(theta)))
662 codes = np.empty(numVertices + 1)
663 codes[0] = cls.MOVETO
664 codes[1:-1] = cls.LINETO
665 codes[-1] = cls.CLOSEPOLY
666 path = cls(verts, codes, readonly=True)
667 if numVertices <= 16:
668 cls._unit_regular_polygons[numVertices] = path
669 return path
671 _unit_regular_stars = WeakValueDictionary()
673 @classmethod
674 def unit_regular_star(cls, numVertices, innerCircle=0.5):
675 """
676 Return a :class:`Path` for a unit regular star with the given
677 numVertices and radius of 1.0, centered at (0, 0).
678 """
679 if numVertices <= 16:
680 path = cls._unit_regular_stars.get((numVertices, innerCircle))
681 else:
682 path = None
683 if path is None:
684 ns2 = numVertices * 2
685 theta = (2*np.pi/ns2 * np.arange(ns2 + 1))
686 # This initial rotation is to make sure the polygon always
687 # "points-up"
688 theta += np.pi / 2.0
689 r = np.ones(ns2 + 1)
690 r[1::2] = innerCircle
691 verts = np.vstack((r*np.cos(theta), r*np.sin(theta))).transpose()
692 codes = np.empty((ns2 + 1,))
693 codes[0] = cls.MOVETO
694 codes[1:-1] = cls.LINETO
695 codes[-1] = cls.CLOSEPOLY
696 path = cls(verts, codes, readonly=True)
697 if numVertices <= 16:
698 cls._unit_regular_stars[(numVertices, innerCircle)] = path
699 return path
701 @classmethod
702 def unit_regular_asterisk(cls, numVertices):
703 """
704 Return a :class:`Path` for a unit regular asterisk with the given
705 numVertices and radius of 1.0, centered at (0, 0).
706 """
707 return cls.unit_regular_star(numVertices, 0.0)
709 _unit_circle = None
711 @classmethod
712 def unit_circle(cls):
713 """
714 Return the readonly :class:`Path` of the unit circle.
716 For most cases, :func:`Path.circle` will be what you want.
717 """
718 if cls._unit_circle is None:
719 cls._unit_circle = cls.circle(center=(0, 0), radius=1,
720 readonly=True)
721 return cls._unit_circle
723 @classmethod
724 def circle(cls, center=(0., 0.), radius=1., readonly=False):
725 """
726 Return a `Path` representing a circle of a given radius and center.
728 Parameters
729 ----------
730 center : pair of floats
731 The center of the circle. Default ``(0, 0)``.
732 radius : float
733 The radius of the circle. Default is 1.
734 readonly : bool
735 Whether the created path should have the "readonly" argument
736 set when creating the Path instance.
738 Notes
739 -----
740 The circle is approximated using 8 cubic Bezier curves, as described in
742 Lancaster, Don. `Approximating a Circle or an Ellipse Using Four
743 Bezier Cubic Splines <http://www.tinaja.com/glib/ellipse4.pdf>`_.
744 """
745 MAGIC = 0.2652031
746 SQRTHALF = np.sqrt(0.5)
747 MAGIC45 = SQRTHALF * MAGIC
749 vertices = np.array([[0.0, -1.0],
751 [MAGIC, -1.0],
752 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
753 [SQRTHALF, -SQRTHALF],
755 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
756 [1.0, -MAGIC],
757 [1.0, 0.0],
759 [1.0, MAGIC],
760 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
761 [SQRTHALF, SQRTHALF],
763 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
764 [MAGIC, 1.0],
765 [0.0, 1.0],
767 [-MAGIC, 1.0],
768 [-SQRTHALF+MAGIC45, SQRTHALF+MAGIC45],
769 [-SQRTHALF, SQRTHALF],
771 [-SQRTHALF-MAGIC45, SQRTHALF-MAGIC45],
772 [-1.0, MAGIC],
773 [-1.0, 0.0],
775 [-1.0, -MAGIC],
776 [-SQRTHALF-MAGIC45, -SQRTHALF+MAGIC45],
777 [-SQRTHALF, -SQRTHALF],
779 [-SQRTHALF+MAGIC45, -SQRTHALF-MAGIC45],
780 [-MAGIC, -1.0],
781 [0.0, -1.0],
783 [0.0, -1.0]],
784 dtype=float)
786 codes = [cls.CURVE4] * 26
787 codes[0] = cls.MOVETO
788 codes[-1] = cls.CLOSEPOLY
789 return Path(vertices * radius + center, codes, readonly=readonly)
791 _unit_circle_righthalf = None
793 @classmethod
794 def unit_circle_righthalf(cls):
795 """
796 Return a `Path` of the right half of a unit circle.
798 See `Path.circle` for the reference on the approximation used.
799 """
800 if cls._unit_circle_righthalf is None:
801 MAGIC = 0.2652031
802 SQRTHALF = np.sqrt(0.5)
803 MAGIC45 = SQRTHALF * MAGIC
805 vertices = np.array(
806 [[0.0, -1.0],
808 [MAGIC, -1.0],
809 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45],
810 [SQRTHALF, -SQRTHALF],
812 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45],
813 [1.0, -MAGIC],
814 [1.0, 0.0],
816 [1.0, MAGIC],
817 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45],
818 [SQRTHALF, SQRTHALF],
820 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45],
821 [MAGIC, 1.0],
822 [0.0, 1.0],
824 [0.0, -1.0]],
826 float)
828 codes = np.full(14, cls.CURVE4, dtype=cls.code_type)
829 codes[0] = cls.MOVETO
830 codes[-1] = cls.CLOSEPOLY
832 cls._unit_circle_righthalf = cls(vertices, codes, readonly=True)
833 return cls._unit_circle_righthalf
835 @classmethod
836 def arc(cls, theta1, theta2, n=None, is_wedge=False):
837 """
838 Return the unit circle arc from angles *theta1* to *theta2* (in
839 degrees).
841 *theta2* is unwrapped to produce the shortest arc within 360 degrees.
842 That is, if *theta2* > *theta1* + 360, the arc will be from *theta1* to
843 *theta2* - 360 and not a full circle plus some extra overlap.
845 If *n* is provided, it is the number of spline segments to make.
846 If *n* is not provided, the number of spline segments is
847 determined based on the delta between *theta1* and *theta2*.
849 Masionobe, L. 2003. `Drawing an elliptical arc using
850 polylines, quadratic or cubic Bezier curves
851 <http://www.spaceroots.org/documents/ellipse/index.html>`_.
852 """
853 halfpi = np.pi * 0.5
855 eta1 = theta1
856 eta2 = theta2 - 360 * np.floor((theta2 - theta1) / 360)
857 # Ensure 2pi range is not flattened to 0 due to floating-point errors,
858 # but don't try to expand existing 0 range.
859 if theta2 != theta1 and eta2 <= eta1:
860 eta2 += 360
861 eta1, eta2 = np.deg2rad([eta1, eta2])
863 # number of curve segments to make
864 if n is None:
865 n = int(2 ** np.ceil((eta2 - eta1) / halfpi))
866 if n < 1:
867 raise ValueError("n must be >= 1 or None")
869 deta = (eta2 - eta1) / n
870 t = np.tan(0.5 * deta)
871 alpha = np.sin(deta) * (np.sqrt(4.0 + 3.0 * t * t) - 1) / 3.0
873 steps = np.linspace(eta1, eta2, n + 1, True)
874 cos_eta = np.cos(steps)
875 sin_eta = np.sin(steps)
877 xA = cos_eta[:-1]
878 yA = sin_eta[:-1]
879 xA_dot = -yA
880 yA_dot = xA
882 xB = cos_eta[1:]
883 yB = sin_eta[1:]
884 xB_dot = -yB
885 yB_dot = xB
887 if is_wedge:
888 length = n * 3 + 4
889 vertices = np.zeros((length, 2), float)
890 codes = np.full(length, cls.CURVE4, dtype=cls.code_type)
891 vertices[1] = [xA[0], yA[0]]
892 codes[0:2] = [cls.MOVETO, cls.LINETO]
893 codes[-2:] = [cls.LINETO, cls.CLOSEPOLY]
894 vertex_offset = 2
895 end = length - 2
896 else:
897 length = n * 3 + 1
898 vertices = np.empty((length, 2), float)
899 codes = np.full(length, cls.CURVE4, dtype=cls.code_type)
900 vertices[0] = [xA[0], yA[0]]
901 codes[0] = cls.MOVETO
902 vertex_offset = 1
903 end = length
905 vertices[vertex_offset:end:3, 0] = xA + alpha * xA_dot
906 vertices[vertex_offset:end:3, 1] = yA + alpha * yA_dot
907 vertices[vertex_offset+1:end:3, 0] = xB - alpha * xB_dot
908 vertices[vertex_offset+1:end:3, 1] = yB - alpha * yB_dot
909 vertices[vertex_offset+2:end:3, 0] = xB
910 vertices[vertex_offset+2:end:3, 1] = yB
912 return cls(vertices, codes, readonly=True)
914 @classmethod
915 def wedge(cls, theta1, theta2, n=None):
916 """
917 Return the unit circle wedge from angles *theta1* to *theta2* (in
918 degrees).
920 *theta2* is unwrapped to produce the shortest wedge within 360 degrees.
921 That is, if *theta2* > *theta1* + 360, the wedge will be from *theta1*
922 to *theta2* - 360 and not a full circle plus some extra overlap.
924 If *n* is provided, it is the number of spline segments to make.
925 If *n* is not provided, the number of spline segments is
926 determined based on the delta between *theta1* and *theta2*.
928 See `Path.arc` for the reference on the approximation used.
929 """
930 return cls.arc(theta1, theta2, n, True)
932 @staticmethod
933 @lru_cache(8)
934 def hatch(hatchpattern, density=6):
935 """
936 Given a hatch specifier, *hatchpattern*, generates a Path that
937 can be used in a repeated hatching pattern. *density* is the
938 number of lines per unit square.
939 """
940 from matplotlib.hatch import get_path
941 return (get_path(hatchpattern, density)
942 if hatchpattern is not None else None)
944 def clip_to_bbox(self, bbox, inside=True):
945 """
946 Clip the path to the given bounding box.
948 The path must be made up of one or more closed polygons. This
949 algorithm will not behave correctly for unclosed paths.
951 If *inside* is `True`, clip to the inside of the box, otherwise
952 to the outside of the box.
953 """
954 # Use make_compound_path_from_polys
955 verts = _path.clip_path_to_rect(self, bbox, inside)
956 paths = [Path(poly) for poly in verts]
957 return self.make_compound_path(*paths)
960def get_path_collection_extents(
961 master_transform, paths, transforms, offsets, offset_transform):
962 r"""
963 Given a sequence of `Path`\s, `~.Transform`\s objects, and offsets, as
964 found in a `~.PathCollection`, returns the bounding box that encapsulates
965 all of them.
967 Parameters
968 ----------
969 master_transform : `~.Transform`
970 Global transformation applied to all paths.
971 paths : list of `Path`
972 transform : list of `~.Affine2D`
973 offsets : (N, 2) array-like
974 offset_transform : `~.Affine2D`
975 Transform applied to the offsets before offsetting the path.
977 Notes
978 -----
979 The way that *paths*, *transforms* and *offsets* are combined
980 follows the same method as for collections: Each is iterated over
981 independently, so if you have 3 paths, 2 transforms and 1 offset,
982 their combinations are as follows:
984 (A, A, A), (B, B, A), (C, A, A)
985 """
986 from .transforms import Bbox
987 if len(paths) == 0:
988 raise ValueError("No paths provided")
989 return Bbox.from_extents(*_path.get_path_collection_extents(
990 master_transform, paths, np.atleast_3d(transforms),
991 offsets, offset_transform))
994@cbook.deprecated("3.1", alternative="get_paths_collection_extents")
995def get_paths_extents(paths, transforms=[]):
996 """
997 Given a sequence of :class:`Path` objects and optional
998 :class:`~matplotlib.transforms.Transform` objects, returns the
999 bounding box that encapsulates all of them.
1001 *paths* is a sequence of :class:`Path` instances.
1003 *transforms* is an optional sequence of
1004 :class:`~matplotlib.transforms.Affine2D` instances to apply to
1005 each path.
1006 """
1007 from .transforms import Bbox, Affine2D
1008 if len(paths) == 0:
1009 raise ValueError("No paths provided")
1010 return Bbox.from_extents(*_path.get_path_collection_extents(
1011 Affine2D(), paths, transforms, [], Affine2D()))