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

389 statements  

1r""" 

2A module for dealing with the polylines used throughout Matplotlib. 

3 

4The primary class for polyline handling in Matplotlib is `Path`. Almost all 

5vector drawing makes use of `Path`\s somewhere in the drawing pipeline. 

6 

7Whilst a `Path` instance itself cannot be drawn, some `.Artist` subclasses, 

8such as `.PathPatch` and `.PathCollection`, can be used for convenient `Path` 

9visualisation. 

10""" 

11 

12import copy 

13from functools import lru_cache 

14from weakref import WeakValueDictionary 

15 

16import numpy as np 

17 

18import matplotlib as mpl 

19from . import _api, _path 

20from .cbook import _to_unmasked_float_array, simple_linear_interpolation 

21from .bezier import BezierSegment 

22 

23 

24class Path: 

25 """ 

26 A series of possibly disconnected, possibly closed, line and curve 

27 segments. 

28 

29 The underlying storage is made up of two parallel numpy arrays: 

30 

31 - *vertices*: an (N, 2) float array of vertices 

32 - *codes*: an N-length `numpy.uint8` array of path codes, or None 

33 

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. 

37 

38 The code types are: 

39 

40 - `STOP` : 1 vertex (ignored) 

41 A marker for the end of the entire path (currently not required and 

42 ignored) 

43 

44 - `MOVETO` : 1 vertex 

45 Pick up the pen and move to the given vertex. 

46 

47 - `LINETO` : 1 vertex 

48 Draw a line from the current position to the given vertex. 

49 

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. 

53 

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. 

57 

58 - `CLOSEPOLY` : 1 vertex (ignored) 

59 Draw a line segment to the start point of the current polyline. 

60 

61 If *codes* is None, it is interpreted as a `MOVETO` followed by a series 

62 of `LINETO`. 

63 

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. 

68 

69 Some behavior of Path objects can be controlled by rcParams. See the 

70 rcParams whose keys start with 'path.'. 

71 

72 .. note:: 

73 

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 """ 

79 

80 code_type = np.uint8 

81 

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 

89 

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} 

98 

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. 

103 

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) 

131 

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 

148 

149 self._vertices = vertices 

150 self._codes = codes 

151 self._interpolation_steps = _interpolation_steps 

152 self._update_values() 

153 

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 

161 

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. 

166 

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 

190 

191 @classmethod 

192 def _create_closed(cls, vertices): 

193 """ 

194 Create a closed polygonal path going through *vertices*. 

195 

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) 

201 

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 ) 

210 

211 @property 

212 def vertices(self): 

213 """The vertices of the `Path` as an (N, 2) array.""" 

214 return self._vertices 

215 

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() 

222 

223 @property 

224 def codes(self): 

225 """ 

226 The list of codes in the `Path` as a 1D array. 

227 

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 

234 

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() 

241 

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 

249 

250 @simplify_threshold.setter 

251 def simplify_threshold(self, threshold): 

252 self._simplify_threshold = threshold 

253 

254 @property 

255 def should_simplify(self): 

256 """ 

257 `True` if the vertices array should be simplified. 

258 """ 

259 return self._should_simplify 

260 

261 @should_simplify.setter 

262 def should_simplify(self, should_simplify): 

263 self._should_simplify = should_simplify 

264 

265 @property 

266 def readonly(self): 

267 """ 

268 `True` if the `Path` is read-only. 

269 """ 

270 return self._readonly 

271 

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) 

278 

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 

288 

289 deepcopy = __deepcopy__ 

290 

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. 

296 

297 .. plot:: gallery/misc/histogram_path.py 

298 

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) 

318 

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]) 

340 

341 def __repr__(self): 

342 return f"Path({self.vertices!r}, {self.codes!r})" 

343 

344 def __len__(self): 

345 return len(self.vertices) 

346 

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. 

352 

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. 

355 

356 Additionally, this method can provide a number of standard cleanups and 

357 conversions to the path. 

358 

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 

390 

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) 

396 

397 # Cache these object lookups for performance in the loop. 

398 NUM_VERTICES_FOR_CODE = self.NUM_VERTICES_FOR_CODE 

399 STOP = self.STOP 

400 

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 

412 

413 def iter_bezier(self, **kwargs): 

414 """ 

415 Iterate over each Bézier curve (lines included) in a `Path`. 

416 

417 Parameters 

418 ---------- 

419 **kwargs 

420 Forwarded to `.iter_segments`. 

421 

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:] 

459 

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) 

469 

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. 

476 

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 

488 

489 def transformed(self, transform): 

490 """ 

491 Return a transformed copy of the path. 

492 

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) 

501 

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. 

505 

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. 

509 

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. 

525 

526 Returns 

527 ------- 

528 bool 

529 

530 Notes 

531 ----- 

532 The current algorithm has some limitations: 

533 

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) 

551 

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. 

555 

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. 

559 

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. 

575 

576 Returns 

577 ------- 

578 length-N bool array 

579 

580 Notes 

581 ----- 

582 The current algorithm has some limitations: 

583 

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') 

595 

596 def contains_path(self, path, transform=None): 

597 """ 

598 Return whether this (closed) path completely contains the given path. 

599 

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) 

606 

607 def get_extents(self, transform=None, **kwargs): 

608 """ 

609 Get Bbox of the path. 

610 

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`. 

617 

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() 

647 

648 def intersects_path(self, other, filled=True): 

649 """ 

650 Return whether if this path intersects another given path. 

651 

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) 

656 

657 def intersects_bbox(self, bbox, filled=True): 

658 """ 

659 Return whether this path intersects a given `~.transforms.Bbox`. 

660 

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). 

663 

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) 

668 

669 def interpolated(self, steps): 

670 """ 

671 Return a new path resampled to length N x *steps*. 

672 

673 Codes other than `LINETO` are not handled correctly. 

674 """ 

675 if steps == 1: 

676 return self 

677 

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) 

687 

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. 

695 

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. 

699 

700 The resulting polygons will be simplified if the 

701 :attr:`Path.should_simplify` attribute of the path is `True`. 

702 

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 [] 

713 

714 if transform is not None: 

715 transform = transform.frozen() 

716 

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]] 

724 

725 if transform is None: 

726 return [vertices] 

727 else: 

728 return [transform.transform(vertices)] 

729 

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) 

734 

735 _unit_rectangle = None 

736 

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 

746 

747 _unit_regular_polygons = WeakValueDictionary() 

748 

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 

770 

771 _unit_regular_stars = WeakValueDictionary() 

772 

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 

796 

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) 

804 

805 _unit_circle = None 

806 

807 @classmethod 

808 def unit_circle(cls): 

809 """ 

810 Return the readonly :class:`Path` of the unit circle. 

811 

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 

818 

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. 

823 

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. 

833 

834 Notes 

835 ----- 

836 The circle is approximated using 8 cubic Bézier curves, as described in 

837 

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 

844 

845 vertices = np.array([[0.0, -1.0], 

846 

847 [MAGIC, -1.0], 

848 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45], 

849 [SQRTHALF, -SQRTHALF], 

850 

851 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45], 

852 [1.0, -MAGIC], 

853 [1.0, 0.0], 

854 

855 [1.0, MAGIC], 

856 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45], 

857 [SQRTHALF, SQRTHALF], 

858 

859 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45], 

860 [MAGIC, 1.0], 

861 [0.0, 1.0], 

862 

863 [-MAGIC, 1.0], 

864 [-SQRTHALF+MAGIC45, SQRTHALF+MAGIC45], 

865 [-SQRTHALF, SQRTHALF], 

866 

867 [-SQRTHALF-MAGIC45, SQRTHALF-MAGIC45], 

868 [-1.0, MAGIC], 

869 [-1.0, 0.0], 

870 

871 [-1.0, -MAGIC], 

872 [-SQRTHALF-MAGIC45, -SQRTHALF+MAGIC45], 

873 [-SQRTHALF, -SQRTHALF], 

874 

875 [-SQRTHALF+MAGIC45, -SQRTHALF-MAGIC45], 

876 [-MAGIC, -1.0], 

877 [0.0, -1.0], 

878 

879 [0.0, -1.0]], 

880 dtype=float) 

881 

882 codes = [cls.CURVE4] * 26 

883 codes[0] = cls.MOVETO 

884 codes[-1] = cls.CLOSEPOLY 

885 return Path(vertices * radius + center, codes, readonly=readonly) 

886 

887 _unit_circle_righthalf = None 

888 

889 @classmethod 

890 def unit_circle_righthalf(cls): 

891 """ 

892 Return a `Path` of the right half of a unit circle. 

893 

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 

900 

901 vertices = np.array( 

902 [[0.0, -1.0], 

903 

904 [MAGIC, -1.0], 

905 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45], 

906 [SQRTHALF, -SQRTHALF], 

907 

908 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45], 

909 [1.0, -MAGIC], 

910 [1.0, 0.0], 

911 

912 [1.0, MAGIC], 

913 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45], 

914 [SQRTHALF, SQRTHALF], 

915 

916 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45], 

917 [MAGIC, 1.0], 

918 [0.0, 1.0], 

919 

920 [0.0, -1.0]], 

921 

922 float) 

923 

924 codes = np.full(14, cls.CURVE4, dtype=cls.code_type) 

925 codes[0] = cls.MOVETO 

926 codes[-1] = cls.CLOSEPOLY 

927 

928 cls._unit_circle_righthalf = cls(vertices, codes, readonly=True) 

929 return cls._unit_circle_righthalf 

930 

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). 

936 

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. 

940 

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*. 

944 

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 

950 

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]) 

958 

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") 

964 

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 

968 

969 steps = np.linspace(eta1, eta2, n + 1, True) 

970 cos_eta = np.cos(steps) 

971 sin_eta = np.sin(steps) 

972 

973 xA = cos_eta[:-1] 

974 yA = sin_eta[:-1] 

975 xA_dot = -yA 

976 yA_dot = xA 

977 

978 xB = cos_eta[1:] 

979 yB = sin_eta[1:] 

980 xB_dot = -yB 

981 yB_dot = xB 

982 

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 

1000 

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 

1007 

1008 return cls(vertices, codes, readonly=True) 

1009 

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). 

1015 

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. 

1019 

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*. 

1023 

1024 See `Path.arc` for the reference on the approximation used. 

1025 """ 

1026 return cls.arc(theta1, theta2, n, True) 

1027 

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) 

1039 

1040 def clip_to_bbox(self, bbox, inside=True): 

1041 """ 

1042 Clip the path to the given bounding box. 

1043 

1044 The path must be made up of one or more closed polygons. This 

1045 algorithm will not behave correctly for unclosed paths. 

1046 

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) 

1053 

1054 

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. 

1059 

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. 

1062 

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. 

1073 

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: 

1080 

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)