Hide keyboard shortcuts

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. 

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 

12from functools import lru_cache 

13from weakref import WeakValueDictionary 

14 

15import numpy as np 

16 

17from . import _path, cbook, rcParams 

18from .cbook import _to_unmasked_float_array, simple_linear_interpolation 

19 

20 

21class Path: 

22 """ 

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

24 segments. 

25 

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

27 

28 - *vertices*: an Nx2 float array of vertices 

29 - *codes*: an N-length uint8 array of vertex types, or None 

30 

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

34 

35 The code types are: 

36 

37 - ``STOP`` : 1 vertex (ignored) 

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

39 ignored) 

40 

41 - ``MOVETO`` : 1 vertex 

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

43 

44 - ``LINETO`` : 1 vertex 

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

46 

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. 

50 

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. 

54 

55 - ``CLOSEPOLY`` : 1 vertex (ignored) 

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

57 

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

59 of ``LINETO``. 

60 

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. 

65 

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

67 rcParams whose keys start with 'path.'. 

68 

69 .. note:: 

70 

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

76 

77 code_type = np.uint8 

78 

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 

86 

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} 

95 

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. 

100 

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. 

106 

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

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

145 

146 self._vertices = vertices 

147 self._codes = codes 

148 self._interpolation_steps = _interpolation_steps 

149 self._update_values() 

150 

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 

158 

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. 

163 

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 

187 

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 ) 

196 

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 

203 

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

210 

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 

222 

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

229 

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 

237 

238 @simplify_threshold.setter 

239 def simplify_threshold(self, threshold): 

240 self._simplify_threshold = threshold 

241 

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

250 

251 @property 

252 def should_simplify(self): 

253 """ 

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

255 """ 

256 return self._should_simplify 

257 

258 @should_simplify.setter 

259 def should_simplify(self, should_simplify): 

260 self._should_simplify = should_simplify 

261 

262 @property 

263 def readonly(self): 

264 """ 

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

266 """ 

267 return self._readonly 

268 

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) 

276 

277 copy = __copy__ 

278 

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) 

291 

292 deepcopy = __deepcopy__ 

293 

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` 

301 

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

303 

304 """ 

305 

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] 

320 

321 return cls(verts, codes) 

322 

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

329 

330 lengths = [len(x) for x in args] 

331 total_length = sum(lengths) 

332 

333 vertices = np.vstack([x.vertices for x in args]) 

334 vertices.reshape((total_length, 2)) 

335 

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) 

345 

346 return cls(vertices, codes) 

347 

348 def __repr__(self): 

349 return "Path(%r, %r)" % (self.vertices, self.codes) 

350 

351 def __len__(self): 

352 return len(self.vertices) 

353 

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. 

361 

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

363 conversions to the path. 

364 

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 

396 

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) 

402 

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

404 NUM_VERTICES_FOR_CODE = self.NUM_VERTICES_FOR_CODE 

405 STOP = self.STOP 

406 

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 

418 

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. 

425 

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 

437 

438 def transformed(self, transform): 

439 """ 

440 Return a transformed copy of the path. 

441 

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) 

450 

451 def contains_point(self, point, transform=None, radius=0.0): 

452 """ 

453 Return whether the (closed) path contains the given point. 

454 

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. 

470 

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) 

485 

486 def contains_points(self, points, transform=None, radius=0.0): 

487 """ 

488 Return whether the (closed) path contains the given point. 

489 

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. 

505 

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

514 

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

516 """ 

517 Returns whether this (closed) path completely contains the given path. 

518 

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) 

525 

526 def get_extents(self, transform=None): 

527 """ 

528 Returns the extents (*xmin*, *ymin*, *xmax*, *ymax*) of the path. 

529 

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

542 

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

544 """ 

545 Returns *True* if this path intersects another given path. 

546 

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) 

552 

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

554 """ 

555 Returns whether this path intersects a given `~.transforms.Bbox`. 

556 

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. 

560 

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) 

565 

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 

573 

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) 

583 

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. 

591 

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. 

595 

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

606 

607 if transform is not None: 

608 transform = transform.frozen() 

609 

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

617 

618 if transform is None: 

619 return [vertices] 

620 else: 

621 return [transform.transform(vertices)] 

622 

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) 

627 

628 _unit_rectangle = None 

629 

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 

643 

644 _unit_regular_polygons = WeakValueDictionary() 

645 

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 

670 

671 _unit_regular_stars = WeakValueDictionary() 

672 

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 

700 

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) 

708 

709 _unit_circle = None 

710 

711 @classmethod 

712 def unit_circle(cls): 

713 """ 

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

715 

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 

722 

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. 

727 

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. 

737 

738 Notes 

739 ----- 

740 The circle is approximated using 8 cubic Bezier curves, as described in 

741 

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 

748 

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

750 

751 [MAGIC, -1.0], 

752 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45], 

753 [SQRTHALF, -SQRTHALF], 

754 

755 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45], 

756 [1.0, -MAGIC], 

757 [1.0, 0.0], 

758 

759 [1.0, MAGIC], 

760 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45], 

761 [SQRTHALF, SQRTHALF], 

762 

763 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45], 

764 [MAGIC, 1.0], 

765 [0.0, 1.0], 

766 

767 [-MAGIC, 1.0], 

768 [-SQRTHALF+MAGIC45, SQRTHALF+MAGIC45], 

769 [-SQRTHALF, SQRTHALF], 

770 

771 [-SQRTHALF-MAGIC45, SQRTHALF-MAGIC45], 

772 [-1.0, MAGIC], 

773 [-1.0, 0.0], 

774 

775 [-1.0, -MAGIC], 

776 [-SQRTHALF-MAGIC45, -SQRTHALF+MAGIC45], 

777 [-SQRTHALF, -SQRTHALF], 

778 

779 [-SQRTHALF+MAGIC45, -SQRTHALF-MAGIC45], 

780 [-MAGIC, -1.0], 

781 [0.0, -1.0], 

782 

783 [0.0, -1.0]], 

784 dtype=float) 

785 

786 codes = [cls.CURVE4] * 26 

787 codes[0] = cls.MOVETO 

788 codes[-1] = cls.CLOSEPOLY 

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

790 

791 _unit_circle_righthalf = None 

792 

793 @classmethod 

794 def unit_circle_righthalf(cls): 

795 """ 

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

797 

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 

804 

805 vertices = np.array( 

806 [[0.0, -1.0], 

807 

808 [MAGIC, -1.0], 

809 [SQRTHALF-MAGIC45, -SQRTHALF-MAGIC45], 

810 [SQRTHALF, -SQRTHALF], 

811 

812 [SQRTHALF+MAGIC45, -SQRTHALF+MAGIC45], 

813 [1.0, -MAGIC], 

814 [1.0, 0.0], 

815 

816 [1.0, MAGIC], 

817 [SQRTHALF+MAGIC45, SQRTHALF-MAGIC45], 

818 [SQRTHALF, SQRTHALF], 

819 

820 [SQRTHALF-MAGIC45, SQRTHALF+MAGIC45], 

821 [MAGIC, 1.0], 

822 [0.0, 1.0], 

823 

824 [0.0, -1.0]], 

825 

826 float) 

827 

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

829 codes[0] = cls.MOVETO 

830 codes[-1] = cls.CLOSEPOLY 

831 

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

833 return cls._unit_circle_righthalf 

834 

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

840 

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. 

844 

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

848 

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 

854 

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

862 

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

868 

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 

872 

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

874 cos_eta = np.cos(steps) 

875 sin_eta = np.sin(steps) 

876 

877 xA = cos_eta[:-1] 

878 yA = sin_eta[:-1] 

879 xA_dot = -yA 

880 yA_dot = xA 

881 

882 xB = cos_eta[1:] 

883 yB = sin_eta[1:] 

884 xB_dot = -yB 

885 yB_dot = xB 

886 

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 

904 

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 

911 

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

913 

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

919 

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. 

923 

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

927 

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

929 """ 

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

931 

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) 

943 

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

945 """ 

946 Clip the path to the given bounding box. 

947 

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

949 algorithm will not behave correctly for unclosed paths. 

950 

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) 

958 

959 

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. 

966 

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. 

976 

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: 

983 

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

992 

993 

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. 

1000 

1001 *paths* is a sequence of :class:`Path` instances. 

1002 

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