Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/patches.py: 25%

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

1705 statements  

1r""" 

2Patches are `.Artist`\s with a face color and an edge color. 

3""" 

4 

5import functools 

6import inspect 

7import math 

8from numbers import Number, Real 

9import textwrap 

10from types import SimpleNamespace 

11from collections import namedtuple 

12from matplotlib.transforms import Affine2D 

13 

14import numpy as np 

15 

16import matplotlib as mpl 

17from . import (_api, artist, cbook, colors, _docstring, hatch as mhatch, 

18 lines as mlines, transforms) 

19from .bezier import ( 

20 NonIntersectingPathException, get_cos_sin, get_intersection, 

21 get_parallels, inside_circle, make_wedged_bezier2, 

22 split_bezier_intersecting_with_closedpath, split_path_inout) 

23from .path import Path 

24from ._enums import JoinStyle, CapStyle 

25 

26 

27@_docstring.interpd 

28@_api.define_aliases({ 

29 "antialiased": ["aa"], 

30 "edgecolor": ["ec"], 

31 "facecolor": ["fc"], 

32 "linestyle": ["ls"], 

33 "linewidth": ["lw"], 

34}) 

35class Patch(artist.Artist): 

36 """ 

37 A patch is a 2D artist with a face color and an edge color. 

38 

39 If any of *edgecolor*, *facecolor*, *linewidth*, or *antialiased* 

40 are *None*, they default to their rc params setting. 

41 """ 

42 zorder = 1 

43 

44 # Whether to draw an edge by default. Set on a 

45 # subclass-by-subclass basis. 

46 _edge_default = False 

47 

48 def __init__(self, *, 

49 edgecolor=None, 

50 facecolor=None, 

51 color=None, 

52 linewidth=None, 

53 linestyle=None, 

54 antialiased=None, 

55 hatch=None, 

56 fill=True, 

57 capstyle=None, 

58 joinstyle=None, 

59 **kwargs): 

60 """ 

61 The following kwarg properties are supported 

62 

63 %(Patch:kwdoc)s 

64 """ 

65 super().__init__() 

66 

67 if linestyle is None: 

68 linestyle = "solid" 

69 if capstyle is None: 

70 capstyle = CapStyle.butt 

71 if joinstyle is None: 

72 joinstyle = JoinStyle.miter 

73 

74 self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color']) 

75 self._fill = bool(fill) # needed for set_facecolor call 

76 if color is not None: 

77 if edgecolor is not None or facecolor is not None: 

78 _api.warn_external( 

79 "Setting the 'color' property will override " 

80 "the edgecolor or facecolor properties.") 

81 self.set_color(color) 

82 else: 

83 self.set_edgecolor(edgecolor) 

84 self.set_facecolor(facecolor) 

85 

86 self._linewidth = 0 

87 self._unscaled_dash_pattern = (0, None) # offset, dash 

88 self._dash_pattern = (0, None) # offset, dash (scaled by linewidth) 

89 

90 self.set_linestyle(linestyle) 

91 self.set_linewidth(linewidth) 

92 self.set_antialiased(antialiased) 

93 self.set_hatch(hatch) 

94 self.set_capstyle(capstyle) 

95 self.set_joinstyle(joinstyle) 

96 

97 if len(kwargs): 

98 self._internal_update(kwargs) 

99 

100 def get_verts(self): 

101 """ 

102 Return a copy of the vertices used in this patch. 

103 

104 If the patch contains Bézier curves, the curves will be interpolated by 

105 line segments. To access the curves as curves, use `get_path`. 

106 """ 

107 trans = self.get_transform() 

108 path = self.get_path() 

109 polygons = path.to_polygons(trans) 

110 if len(polygons): 

111 return polygons[0] 

112 return [] 

113 

114 def _process_radius(self, radius): 

115 if radius is not None: 

116 return radius 

117 if isinstance(self._picker, Number): 

118 _radius = self._picker 

119 else: 

120 if self.get_edgecolor()[3] == 0: 

121 _radius = 0 

122 else: 

123 _radius = self.get_linewidth() 

124 return _radius 

125 

126 def contains(self, mouseevent, radius=None): 

127 """ 

128 Test whether the mouse event occurred in the patch. 

129 

130 Parameters 

131 ---------- 

132 mouseevent : `~matplotlib.backend_bases.MouseEvent` 

133 Where the user clicked. 

134 

135 radius : float, optional 

136 Additional margin on the patch in target coordinates of 

137 `.Patch.get_transform`. See `.Path.contains_point` for further 

138 details. 

139 

140 If `None`, the default value depends on the state of the object: 

141 

142 - If `.Artist.get_picker` is a number, the default 

143 is that value. This is so that picking works as expected. 

144 - Otherwise if the edge color has a non-zero alpha, the default 

145 is half of the linewidth. This is so that all the colored 

146 pixels are "in" the patch. 

147 - Finally, if the edge has 0 alpha, the default is 0. This is 

148 so that patches without a stroked edge do not have points 

149 outside of the filled region report as "in" due to an 

150 invisible edge. 

151 

152 

153 Returns 

154 ------- 

155 (bool, empty dict) 

156 """ 

157 if self._different_canvas(mouseevent): 

158 return False, {} 

159 radius = self._process_radius(radius) 

160 codes = self.get_path().codes 

161 if codes is not None: 

162 vertices = self.get_path().vertices 

163 # if the current path is concatenated by multiple sub paths. 

164 # get the indexes of the starting code(MOVETO) of all sub paths 

165 idxs, = np.where(codes == Path.MOVETO) 

166 # Don't split before the first MOVETO. 

167 idxs = idxs[1:] 

168 subpaths = map( 

169 Path, np.split(vertices, idxs), np.split(codes, idxs)) 

170 else: 

171 subpaths = [self.get_path()] 

172 inside = any( 

173 subpath.contains_point( 

174 (mouseevent.x, mouseevent.y), self.get_transform(), radius) 

175 for subpath in subpaths) 

176 return inside, {} 

177 

178 def contains_point(self, point, radius=None): 

179 """ 

180 Return whether the given point is inside the patch. 

181 

182 Parameters 

183 ---------- 

184 point : (float, float) 

185 The point (x, y) to check, in target coordinates of 

186 ``.Patch.get_transform()``. These are display coordinates for patches 

187 that are added to a figure or Axes. 

188 radius : float, optional 

189 Additional margin on the patch in target coordinates of 

190 `.Patch.get_transform`. See `.Path.contains_point` for further 

191 details. 

192 

193 If `None`, the default value depends on the state of the object: 

194 

195 - If `.Artist.get_picker` is a number, the default 

196 is that value. This is so that picking works as expected. 

197 - Otherwise if the edge color has a non-zero alpha, the default 

198 is half of the linewidth. This is so that all the colored 

199 pixels are "in" the patch. 

200 - Finally, if the edge has 0 alpha, the default is 0. This is 

201 so that patches without a stroked edge do not have points 

202 outside of the filled region report as "in" due to an 

203 invisible edge. 

204 

205 Returns 

206 ------- 

207 bool 

208 

209 Notes 

210 ----- 

211 The proper use of this method depends on the transform of the patch. 

212 Isolated patches do not have a transform. In this case, the patch 

213 creation coordinates and the point coordinates match. The following 

214 example checks that the center of a circle is within the circle 

215 

216 >>> center = 0, 0 

217 >>> c = Circle(center, radius=1) 

218 >>> c.contains_point(center) 

219 True 

220 

221 The convention of checking against the transformed patch stems from 

222 the fact that this method is predominantly used to check if display 

223 coordinates (e.g. from mouse events) are within the patch. If you want 

224 to do the above check with data coordinates, you have to properly 

225 transform them first: 

226 

227 >>> center = 0, 0 

228 >>> c = Circle(center, radius=3) 

229 >>> plt.gca().add_patch(c) 

230 >>> transformed_interior_point = c.get_data_transform().transform((0, 2)) 

231 >>> c.contains_point(transformed_interior_point) 

232 True 

233 

234 """ 

235 radius = self._process_radius(radius) 

236 return self.get_path().contains_point(point, 

237 self.get_transform(), 

238 radius) 

239 

240 def contains_points(self, points, radius=None): 

241 """ 

242 Return whether the given points are inside the patch. 

243 

244 Parameters 

245 ---------- 

246 points : (N, 2) array 

247 The points to check, in target coordinates of 

248 ``self.get_transform()``. These are display coordinates for patches 

249 that are added to a figure or Axes. Columns contain x and y values. 

250 radius : float, optional 

251 Additional margin on the patch in target coordinates of 

252 `.Patch.get_transform`. See `.Path.contains_point` for further 

253 details. 

254 

255 If `None`, the default value depends on the state of the object: 

256 

257 - If `.Artist.get_picker` is a number, the default 

258 is that value. This is so that picking works as expected. 

259 - Otherwise if the edge color has a non-zero alpha, the default 

260 is half of the linewidth. This is so that all the colored 

261 pixels are "in" the patch. 

262 - Finally, if the edge has 0 alpha, the default is 0. This is 

263 so that patches without a stroked edge do not have points 

264 outside of the filled region report as "in" due to an 

265 invisible edge. 

266 

267 Returns 

268 ------- 

269 length-N bool array 

270 

271 Notes 

272 ----- 

273 The proper use of this method depends on the transform of the patch. 

274 See the notes on `.Patch.contains_point`. 

275 """ 

276 radius = self._process_radius(radius) 

277 return self.get_path().contains_points(points, 

278 self.get_transform(), 

279 radius) 

280 

281 def update_from(self, other): 

282 # docstring inherited. 

283 super().update_from(other) 

284 # For some properties we don't need or don't want to go through the 

285 # getters/setters, so we just copy them directly. 

286 self._edgecolor = other._edgecolor 

287 self._facecolor = other._facecolor 

288 self._original_edgecolor = other._original_edgecolor 

289 self._original_facecolor = other._original_facecolor 

290 self._fill = other._fill 

291 self._hatch = other._hatch 

292 self._hatch_color = other._hatch_color 

293 self._unscaled_dash_pattern = other._unscaled_dash_pattern 

294 self.set_linewidth(other._linewidth) # also sets scaled dashes 

295 self.set_transform(other.get_data_transform()) 

296 # If the transform of other needs further initialization, then it will 

297 # be the case for this artist too. 

298 self._transformSet = other.is_transform_set() 

299 

300 def get_extents(self): 

301 """ 

302 Return the `Patch`'s axis-aligned extents as a `~.transforms.Bbox`. 

303 """ 

304 return self.get_path().get_extents(self.get_transform()) 

305 

306 def get_transform(self): 

307 """Return the `~.transforms.Transform` applied to the `Patch`.""" 

308 return self.get_patch_transform() + artist.Artist.get_transform(self) 

309 

310 def get_data_transform(self): 

311 """ 

312 Return the `~.transforms.Transform` mapping data coordinates to 

313 physical coordinates. 

314 """ 

315 return artist.Artist.get_transform(self) 

316 

317 def get_patch_transform(self): 

318 """ 

319 Return the `~.transforms.Transform` instance mapping patch coordinates 

320 to data coordinates. 

321 

322 For example, one may define a patch of a circle which represents a 

323 radius of 5 by providing coordinates for a unit circle, and a 

324 transform which scales the coordinates (the patch coordinate) by 5. 

325 """ 

326 return transforms.IdentityTransform() 

327 

328 def get_antialiased(self): 

329 """Return whether antialiasing is used for drawing.""" 

330 return self._antialiased 

331 

332 def get_edgecolor(self): 

333 """Return the edge color.""" 

334 return self._edgecolor 

335 

336 def get_facecolor(self): 

337 """Return the face color.""" 

338 return self._facecolor 

339 

340 def get_linewidth(self): 

341 """Return the line width in points.""" 

342 return self._linewidth 

343 

344 def get_linestyle(self): 

345 """Return the linestyle.""" 

346 return self._linestyle 

347 

348 def set_antialiased(self, aa): 

349 """ 

350 Set whether to use antialiased rendering. 

351 

352 Parameters 

353 ---------- 

354 aa : bool or None 

355 """ 

356 if aa is None: 

357 aa = mpl.rcParams['patch.antialiased'] 

358 self._antialiased = aa 

359 self.stale = True 

360 

361 def _set_edgecolor(self, color): 

362 set_hatch_color = True 

363 if color is None: 

364 if (mpl.rcParams['patch.force_edgecolor'] or 

365 not self._fill or self._edge_default): 

366 color = mpl.rcParams['patch.edgecolor'] 

367 else: 

368 color = 'none' 

369 set_hatch_color = False 

370 

371 self._edgecolor = colors.to_rgba(color, self._alpha) 

372 if set_hatch_color: 

373 self._hatch_color = self._edgecolor 

374 self.stale = True 

375 

376 def set_edgecolor(self, color): 

377 """ 

378 Set the patch edge color. 

379 

380 Parameters 

381 ---------- 

382 color : :mpltype:`color` or None 

383 """ 

384 self._original_edgecolor = color 

385 self._set_edgecolor(color) 

386 

387 def _set_facecolor(self, color): 

388 if color is None: 

389 color = mpl.rcParams['patch.facecolor'] 

390 alpha = self._alpha if self._fill else 0 

391 self._facecolor = colors.to_rgba(color, alpha) 

392 self.stale = True 

393 

394 def set_facecolor(self, color): 

395 """ 

396 Set the patch face color. 

397 

398 Parameters 

399 ---------- 

400 color : :mpltype:`color` or None 

401 """ 

402 self._original_facecolor = color 

403 self._set_facecolor(color) 

404 

405 def set_color(self, c): 

406 """ 

407 Set both the edgecolor and the facecolor. 

408 

409 Parameters 

410 ---------- 

411 c : :mpltype:`color` 

412 

413 See Also 

414 -------- 

415 Patch.set_facecolor, Patch.set_edgecolor 

416 For setting the edge or face color individually. 

417 """ 

418 self.set_facecolor(c) 

419 self.set_edgecolor(c) 

420 

421 def set_alpha(self, alpha): 

422 # docstring inherited 

423 super().set_alpha(alpha) 

424 self._set_facecolor(self._original_facecolor) 

425 self._set_edgecolor(self._original_edgecolor) 

426 # stale is already True 

427 

428 def set_linewidth(self, w): 

429 """ 

430 Set the patch linewidth in points. 

431 

432 Parameters 

433 ---------- 

434 w : float or None 

435 """ 

436 if w is None: 

437 w = mpl.rcParams['patch.linewidth'] 

438 self._linewidth = float(w) 

439 self._dash_pattern = mlines._scale_dashes( 

440 *self._unscaled_dash_pattern, w) 

441 self.stale = True 

442 

443 def set_linestyle(self, ls): 

444 """ 

445 Set the patch linestyle. 

446 

447 ========================================== ================= 

448 linestyle description 

449 ========================================== ================= 

450 ``'-'`` or ``'solid'`` solid line 

451 ``'--'`` or ``'dashed'`` dashed line 

452 ``'-.'`` or ``'dashdot'`` dash-dotted line 

453 ``':'`` or ``'dotted'`` dotted line 

454 ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing 

455 ========================================== ================= 

456 

457 Alternatively a dash tuple of the following form can be provided:: 

458 

459 (offset, onoffseq) 

460 

461 where ``onoffseq`` is an even length tuple of on and off ink in points. 

462 

463 Parameters 

464 ---------- 

465 ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} 

466 The line style. 

467 """ 

468 if ls is None: 

469 ls = "solid" 

470 if ls in [' ', '', 'none']: 

471 ls = 'None' 

472 self._linestyle = ls 

473 self._unscaled_dash_pattern = mlines._get_dash_pattern(ls) 

474 self._dash_pattern = mlines._scale_dashes( 

475 *self._unscaled_dash_pattern, self._linewidth) 

476 self.stale = True 

477 

478 def set_fill(self, b): 

479 """ 

480 Set whether to fill the patch. 

481 

482 Parameters 

483 ---------- 

484 b : bool 

485 """ 

486 self._fill = bool(b) 

487 self._set_facecolor(self._original_facecolor) 

488 self._set_edgecolor(self._original_edgecolor) 

489 self.stale = True 

490 

491 def get_fill(self): 

492 """Return whether the patch is filled.""" 

493 return self._fill 

494 

495 # Make fill a property so as to preserve the long-standing 

496 # but somewhat inconsistent behavior in which fill was an 

497 # attribute. 

498 fill = property(get_fill, set_fill) 

499 

500 @_docstring.interpd 

501 def set_capstyle(self, s): 

502 """ 

503 Set the `.CapStyle`. 

504 

505 The default capstyle is 'round' for `.FancyArrowPatch` and 'butt' for 

506 all other patches. 

507 

508 Parameters 

509 ---------- 

510 s : `.CapStyle` or %(CapStyle)s 

511 """ 

512 cs = CapStyle(s) 

513 self._capstyle = cs 

514 self.stale = True 

515 

516 def get_capstyle(self): 

517 """Return the capstyle.""" 

518 return self._capstyle.name 

519 

520 @_docstring.interpd 

521 def set_joinstyle(self, s): 

522 """ 

523 Set the `.JoinStyle`. 

524 

525 The default joinstyle is 'round' for `.FancyArrowPatch` and 'miter' for 

526 all other patches. 

527 

528 Parameters 

529 ---------- 

530 s : `.JoinStyle` or %(JoinStyle)s 

531 """ 

532 js = JoinStyle(s) 

533 self._joinstyle = js 

534 self.stale = True 

535 

536 def get_joinstyle(self): 

537 """Return the joinstyle.""" 

538 return self._joinstyle.name 

539 

540 def set_hatch(self, hatch): 

541 r""" 

542 Set the hatching pattern. 

543 

544 *hatch* can be one of:: 

545 

546 / - diagonal hatching 

547 \ - back diagonal 

548 | - vertical 

549 - - horizontal 

550 + - crossed 

551 x - crossed diagonal 

552 o - small circle 

553 O - large circle 

554 . - dots 

555 * - stars 

556 

557 Letters can be combined, in which case all the specified 

558 hatchings are done. If same letter repeats, it increases the 

559 density of hatching of that pattern. 

560 

561 Parameters 

562 ---------- 

563 hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} 

564 """ 

565 # Use validate_hatch(list) after deprecation. 

566 mhatch._validate_hatch_pattern(hatch) 

567 self._hatch = hatch 

568 self.stale = True 

569 

570 def get_hatch(self): 

571 """Return the hatching pattern.""" 

572 return self._hatch 

573 

574 def _draw_paths_with_artist_properties( 

575 self, renderer, draw_path_args_list): 

576 """ 

577 ``draw()`` helper factored out for sharing with `FancyArrowPatch`. 

578 

579 Configure *renderer* and the associated graphics context *gc* 

580 from the artist properties, then repeatedly call 

581 ``renderer.draw_path(gc, *draw_path_args)`` for each tuple 

582 *draw_path_args* in *draw_path_args_list*. 

583 """ 

584 

585 renderer.open_group('patch', self.get_gid()) 

586 gc = renderer.new_gc() 

587 

588 gc.set_foreground(self._edgecolor, isRGBA=True) 

589 

590 lw = self._linewidth 

591 if self._edgecolor[3] == 0 or self._linestyle == 'None': 

592 lw = 0 

593 gc.set_linewidth(lw) 

594 gc.set_dashes(*self._dash_pattern) 

595 gc.set_capstyle(self._capstyle) 

596 gc.set_joinstyle(self._joinstyle) 

597 

598 gc.set_antialiased(self._antialiased) 

599 self._set_gc_clip(gc) 

600 gc.set_url(self._url) 

601 gc.set_snap(self.get_snap()) 

602 

603 gc.set_alpha(self._alpha) 

604 

605 if self._hatch: 

606 gc.set_hatch(self._hatch) 

607 gc.set_hatch_color(self._hatch_color) 

608 

609 if self.get_sketch_params() is not None: 

610 gc.set_sketch_params(*self.get_sketch_params()) 

611 

612 if self.get_path_effects(): 

613 from matplotlib.patheffects import PathEffectRenderer 

614 renderer = PathEffectRenderer(self.get_path_effects(), renderer) 

615 

616 for draw_path_args in draw_path_args_list: 

617 renderer.draw_path(gc, *draw_path_args) 

618 

619 gc.restore() 

620 renderer.close_group('patch') 

621 self.stale = False 

622 

623 @artist.allow_rasterization 

624 def draw(self, renderer): 

625 # docstring inherited 

626 if not self.get_visible(): 

627 return 

628 path = self.get_path() 

629 transform = self.get_transform() 

630 tpath = transform.transform_path_non_affine(path) 

631 affine = transform.get_affine() 

632 self._draw_paths_with_artist_properties( 

633 renderer, 

634 [(tpath, affine, 

635 # Work around a bug in the PDF and SVG renderers, which 

636 # do not draw the hatches if the facecolor is fully 

637 # transparent, but do if it is None. 

638 self._facecolor if self._facecolor[3] else None)]) 

639 

640 def get_path(self): 

641 """Return the path of this patch.""" 

642 raise NotImplementedError('Derived must override') 

643 

644 def get_window_extent(self, renderer=None): 

645 return self.get_path().get_extents(self.get_transform()) 

646 

647 def _convert_xy_units(self, xy): 

648 """Convert x and y units for a tuple (x, y).""" 

649 x = self.convert_xunits(xy[0]) 

650 y = self.convert_yunits(xy[1]) 

651 return x, y 

652 

653 

654class Shadow(Patch): 

655 def __str__(self): 

656 return f"Shadow({self.patch})" 

657 

658 @_docstring.dedent_interpd 

659 def __init__(self, patch, ox, oy, *, shade=0.7, **kwargs): 

660 """ 

661 Create a shadow of the given *patch*. 

662 

663 By default, the shadow will have the same face color as the *patch*, 

664 but darkened. The darkness can be controlled by *shade*. 

665 

666 Parameters 

667 ---------- 

668 patch : `~matplotlib.patches.Patch` 

669 The patch to create the shadow for. 

670 ox, oy : float 

671 The shift of the shadow in data coordinates, scaled by a factor 

672 of dpi/72. 

673 shade : float, default: 0.7 

674 How the darkness of the shadow relates to the original color. If 1, the 

675 shadow is black, if 0, the shadow has the same color as the *patch*. 

676 

677 .. versionadded:: 3.8 

678 

679 **kwargs 

680 Properties of the shadow patch. Supported keys are: 

681 

682 %(Patch:kwdoc)s 

683 """ 

684 super().__init__() 

685 self.patch = patch 

686 self._ox, self._oy = ox, oy 

687 self._shadow_transform = transforms.Affine2D() 

688 

689 self.update_from(self.patch) 

690 if not 0 <= shade <= 1: 

691 raise ValueError("shade must be between 0 and 1.") 

692 color = (1 - shade) * np.asarray(colors.to_rgb(self.patch.get_facecolor())) 

693 self.update({'facecolor': color, 'edgecolor': color, 'alpha': 0.5, 

694 # Place shadow patch directly behind the inherited patch. 

695 'zorder': np.nextafter(self.patch.zorder, -np.inf), 

696 **kwargs}) 

697 

698 def _update_transform(self, renderer): 

699 ox = renderer.points_to_pixels(self._ox) 

700 oy = renderer.points_to_pixels(self._oy) 

701 self._shadow_transform.clear().translate(ox, oy) 

702 

703 def get_path(self): 

704 return self.patch.get_path() 

705 

706 def get_patch_transform(self): 

707 return self.patch.get_patch_transform() + self._shadow_transform 

708 

709 def draw(self, renderer): 

710 self._update_transform(renderer) 

711 super().draw(renderer) 

712 

713 

714class Rectangle(Patch): 

715 """ 

716 A rectangle defined via an anchor point *xy* and its *width* and *height*. 

717 

718 The rectangle extends from ``xy[0]`` to ``xy[0] + width`` in x-direction 

719 and from ``xy[1]`` to ``xy[1] + height`` in y-direction. :: 

720 

721 : +------------------+ 

722 : | | 

723 : height | 

724 : | | 

725 : (xy)---- width -----+ 

726 

727 One may picture *xy* as the bottom left corner, but which corner *xy* is 

728 actually depends on the direction of the axis and the sign of *width* 

729 and *height*; e.g. *xy* would be the bottom right corner if the x-axis 

730 was inverted or if *width* was negative. 

731 """ 

732 

733 def __str__(self): 

734 pars = self._x0, self._y0, self._width, self._height, self.angle 

735 fmt = "Rectangle(xy=(%g, %g), width=%g, height=%g, angle=%g)" 

736 return fmt % pars 

737 

738 @_docstring.dedent_interpd 

739 def __init__(self, xy, width, height, *, 

740 angle=0.0, rotation_point='xy', **kwargs): 

741 """ 

742 Parameters 

743 ---------- 

744 xy : (float, float) 

745 The anchor point. 

746 width : float 

747 Rectangle width. 

748 height : float 

749 Rectangle height. 

750 angle : float, default: 0 

751 Rotation in degrees anti-clockwise about the rotation point. 

752 rotation_point : {'xy', 'center', (number, number)}, default: 'xy' 

753 If ``'xy'``, rotate around the anchor point. If ``'center'`` rotate 

754 around the center. If 2-tuple of number, rotate around this 

755 coordinate. 

756 

757 Other Parameters 

758 ---------------- 

759 **kwargs : `~matplotlib.patches.Patch` properties 

760 %(Patch:kwdoc)s 

761 """ 

762 super().__init__(**kwargs) 

763 self._x0 = xy[0] 

764 self._y0 = xy[1] 

765 self._width = width 

766 self._height = height 

767 self.angle = float(angle) 

768 self.rotation_point = rotation_point 

769 # Required for RectangleSelector with axes aspect ratio != 1 

770 # The patch is defined in data coordinates and when changing the 

771 # selector with square modifier and not in data coordinates, we need 

772 # to correct for the aspect ratio difference between the data and 

773 # display coordinate systems. Its value is typically provide by 

774 # Axes._get_aspect_ratio() 

775 self._aspect_ratio_correction = 1.0 

776 self._convert_units() # Validate the inputs. 

777 

778 def get_path(self): 

779 """Return the vertices of the rectangle.""" 

780 return Path.unit_rectangle() 

781 

782 def _convert_units(self): 

783 """Convert bounds of the rectangle.""" 

784 x0 = self.convert_xunits(self._x0) 

785 y0 = self.convert_yunits(self._y0) 

786 x1 = self.convert_xunits(self._x0 + self._width) 

787 y1 = self.convert_yunits(self._y0 + self._height) 

788 return x0, y0, x1, y1 

789 

790 def get_patch_transform(self): 

791 # Note: This cannot be called until after this has been added to 

792 # an Axes, otherwise unit conversion will fail. This makes it very 

793 # important to call the accessor method and not directly access the 

794 # transformation member variable. 

795 bbox = self.get_bbox() 

796 if self.rotation_point == 'center': 

797 width, height = bbox.x1 - bbox.x0, bbox.y1 - bbox.y0 

798 rotation_point = bbox.x0 + width / 2., bbox.y0 + height / 2. 

799 elif self.rotation_point == 'xy': 

800 rotation_point = bbox.x0, bbox.y0 

801 else: 

802 rotation_point = self.rotation_point 

803 return transforms.BboxTransformTo(bbox) \ 

804 + transforms.Affine2D() \ 

805 .translate(-rotation_point[0], -rotation_point[1]) \ 

806 .scale(1, self._aspect_ratio_correction) \ 

807 .rotate_deg(self.angle) \ 

808 .scale(1, 1 / self._aspect_ratio_correction) \ 

809 .translate(*rotation_point) 

810 

811 @property 

812 def rotation_point(self): 

813 """The rotation point of the patch.""" 

814 return self._rotation_point 

815 

816 @rotation_point.setter 

817 def rotation_point(self, value): 

818 if value in ['center', 'xy'] or ( 

819 isinstance(value, tuple) and len(value) == 2 and 

820 isinstance(value[0], Real) and isinstance(value[1], Real) 

821 ): 

822 self._rotation_point = value 

823 else: 

824 raise ValueError("`rotation_point` must be one of " 

825 "{'xy', 'center', (number, number)}.") 

826 

827 def get_x(self): 

828 """Return the left coordinate of the rectangle.""" 

829 return self._x0 

830 

831 def get_y(self): 

832 """Return the bottom coordinate of the rectangle.""" 

833 return self._y0 

834 

835 def get_xy(self): 

836 """Return the left and bottom coords of the rectangle as a tuple.""" 

837 return self._x0, self._y0 

838 

839 def get_corners(self): 

840 """ 

841 Return the corners of the rectangle, moving anti-clockwise from 

842 (x0, y0). 

843 """ 

844 return self.get_patch_transform().transform( 

845 [(0, 0), (1, 0), (1, 1), (0, 1)]) 

846 

847 def get_center(self): 

848 """Return the centre of the rectangle.""" 

849 return self.get_patch_transform().transform((0.5, 0.5)) 

850 

851 def get_width(self): 

852 """Return the width of the rectangle.""" 

853 return self._width 

854 

855 def get_height(self): 

856 """Return the height of the rectangle.""" 

857 return self._height 

858 

859 def get_angle(self): 

860 """Get the rotation angle in degrees.""" 

861 return self.angle 

862 

863 def set_x(self, x): 

864 """Set the left coordinate of the rectangle.""" 

865 self._x0 = x 

866 self.stale = True 

867 

868 def set_y(self, y): 

869 """Set the bottom coordinate of the rectangle.""" 

870 self._y0 = y 

871 self.stale = True 

872 

873 def set_angle(self, angle): 

874 """ 

875 Set the rotation angle in degrees. 

876 

877 The rotation is performed anti-clockwise around *xy*. 

878 """ 

879 self.angle = angle 

880 self.stale = True 

881 

882 def set_xy(self, xy): 

883 """ 

884 Set the left and bottom coordinates of the rectangle. 

885 

886 Parameters 

887 ---------- 

888 xy : (float, float) 

889 """ 

890 self._x0, self._y0 = xy 

891 self.stale = True 

892 

893 def set_width(self, w): 

894 """Set the width of the rectangle.""" 

895 self._width = w 

896 self.stale = True 

897 

898 def set_height(self, h): 

899 """Set the height of the rectangle.""" 

900 self._height = h 

901 self.stale = True 

902 

903 def set_bounds(self, *args): 

904 """ 

905 Set the bounds of the rectangle as *left*, *bottom*, *width*, *height*. 

906 

907 The values may be passed as separate parameters or as a tuple:: 

908 

909 set_bounds(left, bottom, width, height) 

910 set_bounds((left, bottom, width, height)) 

911 

912 .. ACCEPTS: (left, bottom, width, height) 

913 """ 

914 if len(args) == 1: 

915 l, b, w, h = args[0] 

916 else: 

917 l, b, w, h = args 

918 self._x0 = l 

919 self._y0 = b 

920 self._width = w 

921 self._height = h 

922 self.stale = True 

923 

924 def get_bbox(self): 

925 """Return the `.Bbox`.""" 

926 return transforms.Bbox.from_extents(*self._convert_units()) 

927 

928 xy = property(get_xy, set_xy) 

929 

930 

931class RegularPolygon(Patch): 

932 """A regular polygon patch.""" 

933 

934 def __str__(self): 

935 s = "RegularPolygon((%g, %g), %d, radius=%g, orientation=%g)" 

936 return s % (self.xy[0], self.xy[1], self.numvertices, self.radius, 

937 self.orientation) 

938 

939 @_docstring.dedent_interpd 

940 def __init__(self, xy, numVertices, *, 

941 radius=5, orientation=0, **kwargs): 

942 """ 

943 Parameters 

944 ---------- 

945 xy : (float, float) 

946 The center position. 

947 

948 numVertices : int 

949 The number of vertices. 

950 

951 radius : float 

952 The distance from the center to each of the vertices. 

953 

954 orientation : float 

955 The polygon rotation angle (in radians). 

956 

957 **kwargs 

958 `Patch` properties: 

959 

960 %(Patch:kwdoc)s 

961 """ 

962 self.xy = xy 

963 self.numvertices = numVertices 

964 self.orientation = orientation 

965 self.radius = radius 

966 self._path = Path.unit_regular_polygon(numVertices) 

967 self._patch_transform = transforms.Affine2D() 

968 super().__init__(**kwargs) 

969 

970 def get_path(self): 

971 return self._path 

972 

973 def get_patch_transform(self): 

974 return self._patch_transform.clear() \ 

975 .scale(self.radius) \ 

976 .rotate(self.orientation) \ 

977 .translate(*self.xy) 

978 

979 

980class PathPatch(Patch): 

981 """A general polycurve path patch.""" 

982 

983 _edge_default = True 

984 

985 def __str__(self): 

986 s = "PathPatch%d((%g, %g) ...)" 

987 return s % (len(self._path.vertices), *tuple(self._path.vertices[0])) 

988 

989 @_docstring.dedent_interpd 

990 def __init__(self, path, **kwargs): 

991 """ 

992 *path* is a `.Path` object. 

993 

994 Valid keyword arguments are: 

995 

996 %(Patch:kwdoc)s 

997 """ 

998 super().__init__(**kwargs) 

999 self._path = path 

1000 

1001 def get_path(self): 

1002 return self._path 

1003 

1004 def set_path(self, path): 

1005 self._path = path 

1006 

1007 

1008class StepPatch(PathPatch): 

1009 """ 

1010 A path patch describing a stepwise constant function. 

1011 

1012 By default, the path is not closed and starts and stops at 

1013 baseline value. 

1014 """ 

1015 

1016 _edge_default = False 

1017 

1018 @_docstring.dedent_interpd 

1019 def __init__(self, values, edges, *, 

1020 orientation='vertical', baseline=0, **kwargs): 

1021 """ 

1022 Parameters 

1023 ---------- 

1024 values : array-like 

1025 The step heights. 

1026 

1027 edges : array-like 

1028 The edge positions, with ``len(edges) == len(vals) + 1``, 

1029 between which the curve takes on vals values. 

1030 

1031 orientation : {'vertical', 'horizontal'}, default: 'vertical' 

1032 The direction of the steps. Vertical means that *values* are 

1033 along the y-axis, and edges are along the x-axis. 

1034 

1035 baseline : float, array-like or None, default: 0 

1036 The bottom value of the bounding edges or when 

1037 ``fill=True``, position of lower edge. If *fill* is 

1038 True or an array is passed to *baseline*, a closed 

1039 path is drawn. 

1040 

1041 **kwargs 

1042 `Patch` properties: 

1043 

1044 %(Patch:kwdoc)s 

1045 """ 

1046 self.orientation = orientation 

1047 self._edges = np.asarray(edges) 

1048 self._values = np.asarray(values) 

1049 self._baseline = np.asarray(baseline) if baseline is not None else None 

1050 self._update_path() 

1051 super().__init__(self._path, **kwargs) 

1052 

1053 def _update_path(self): 

1054 if np.isnan(np.sum(self._edges)): 

1055 raise ValueError('Nan values in "edges" are disallowed') 

1056 if self._edges.size - 1 != self._values.size: 

1057 raise ValueError('Size mismatch between "values" and "edges". ' 

1058 "Expected `len(values) + 1 == len(edges)`, but " 

1059 f"`len(values) = {self._values.size}` and " 

1060 f"`len(edges) = {self._edges.size}`.") 

1061 # Initializing with empty arrays allows supporting empty stairs. 

1062 verts, codes = [np.empty((0, 2))], [np.empty(0, dtype=Path.code_type)] 

1063 

1064 _nan_mask = np.isnan(self._values) 

1065 if self._baseline is not None: 

1066 _nan_mask |= np.isnan(self._baseline) 

1067 for idx0, idx1 in cbook.contiguous_regions(~_nan_mask): 

1068 x = np.repeat(self._edges[idx0:idx1+1], 2) 

1069 y = np.repeat(self._values[idx0:idx1], 2) 

1070 if self._baseline is None: 

1071 y = np.concatenate([y[:1], y, y[-1:]]) 

1072 elif self._baseline.ndim == 0: # single baseline value 

1073 y = np.concatenate([[self._baseline], y, [self._baseline]]) 

1074 elif self._baseline.ndim == 1: # baseline array 

1075 base = np.repeat(self._baseline[idx0:idx1], 2)[::-1] 

1076 x = np.concatenate([x, x[::-1]]) 

1077 y = np.concatenate([base[-1:], y, base[:1], 

1078 base[:1], base, base[-1:]]) 

1079 else: # no baseline 

1080 raise ValueError('Invalid `baseline` specified') 

1081 if self.orientation == 'vertical': 

1082 xy = np.column_stack([x, y]) 

1083 else: 

1084 xy = np.column_stack([y, x]) 

1085 verts.append(xy) 

1086 codes.append([Path.MOVETO] + [Path.LINETO]*(len(xy)-1)) 

1087 self._path = Path(np.concatenate(verts), np.concatenate(codes)) 

1088 

1089 def get_data(self): 

1090 """Get `.StepPatch` values, edges and baseline as namedtuple.""" 

1091 StairData = namedtuple('StairData', 'values edges baseline') 

1092 return StairData(self._values, self._edges, self._baseline) 

1093 

1094 def set_data(self, values=None, edges=None, baseline=None): 

1095 """ 

1096 Set `.StepPatch` values, edges and baseline. 

1097 

1098 Parameters 

1099 ---------- 

1100 values : 1D array-like or None 

1101 Will not update values, if passing None 

1102 edges : 1D array-like, optional 

1103 baseline : float, 1D array-like or None 

1104 """ 

1105 if values is None and edges is None and baseline is None: 

1106 raise ValueError("Must set *values*, *edges* or *baseline*.") 

1107 if values is not None: 

1108 self._values = np.asarray(values) 

1109 if edges is not None: 

1110 self._edges = np.asarray(edges) 

1111 if baseline is not None: 

1112 self._baseline = np.asarray(baseline) 

1113 self._update_path() 

1114 self.stale = True 

1115 

1116 

1117class Polygon(Patch): 

1118 """A general polygon patch.""" 

1119 

1120 def __str__(self): 

1121 if len(self._path.vertices): 

1122 s = "Polygon%d((%g, %g) ...)" 

1123 return s % (len(self._path.vertices), *self._path.vertices[0]) 

1124 else: 

1125 return "Polygon0()" 

1126 

1127 @_docstring.dedent_interpd 

1128 def __init__(self, xy, *, closed=True, **kwargs): 

1129 """ 

1130 Parameters 

1131 ---------- 

1132 xy : (N, 2) array 

1133 

1134 closed : bool, default: True 

1135 Whether the polygon is closed (i.e., has identical start and end 

1136 points). 

1137 

1138 **kwargs 

1139 %(Patch:kwdoc)s 

1140 """ 

1141 super().__init__(**kwargs) 

1142 self._closed = closed 

1143 self.set_xy(xy) 

1144 

1145 def get_path(self): 

1146 """Get the `.Path` of the polygon.""" 

1147 return self._path 

1148 

1149 def get_closed(self): 

1150 """Return whether the polygon is closed.""" 

1151 return self._closed 

1152 

1153 def set_closed(self, closed): 

1154 """ 

1155 Set whether the polygon is closed. 

1156 

1157 Parameters 

1158 ---------- 

1159 closed : bool 

1160 True if the polygon is closed 

1161 """ 

1162 if self._closed == bool(closed): 

1163 return 

1164 self._closed = bool(closed) 

1165 self.set_xy(self.get_xy()) 

1166 self.stale = True 

1167 

1168 def get_xy(self): 

1169 """ 

1170 Get the vertices of the path. 

1171 

1172 Returns 

1173 ------- 

1174 (N, 2) array 

1175 The coordinates of the vertices. 

1176 """ 

1177 return self._path.vertices 

1178 

1179 def set_xy(self, xy): 

1180 """ 

1181 Set the vertices of the polygon. 

1182 

1183 Parameters 

1184 ---------- 

1185 xy : (N, 2) array-like 

1186 The coordinates of the vertices. 

1187 

1188 Notes 

1189 ----- 

1190 Unlike `.Path`, we do not ignore the last input vertex. If the 

1191 polygon is meant to be closed, and the last point of the polygon is not 

1192 equal to the first, we assume that the user has not explicitly passed a 

1193 ``CLOSEPOLY`` vertex, and add it ourselves. 

1194 """ 

1195 xy = np.asarray(xy) 

1196 nverts, _ = xy.shape 

1197 if self._closed: 

1198 # if the first and last vertex are the "same", then we assume that 

1199 # the user explicitly passed the CLOSEPOLY vertex. Otherwise, we 

1200 # have to append one since the last vertex will be "ignored" by 

1201 # Path 

1202 if nverts == 1 or nverts > 1 and (xy[0] != xy[-1]).any(): 

1203 xy = np.concatenate([xy, [xy[0]]]) 

1204 else: 

1205 # if we aren't closed, and the last vertex matches the first, then 

1206 # we assume we have an unnecessary CLOSEPOLY vertex and remove it 

1207 if nverts > 2 and (xy[0] == xy[-1]).all(): 

1208 xy = xy[:-1] 

1209 self._path = Path(xy, closed=self._closed) 

1210 self.stale = True 

1211 

1212 xy = property(get_xy, set_xy, 

1213 doc='The vertices of the path as a (N, 2) array.') 

1214 

1215 

1216class Wedge(Patch): 

1217 """Wedge shaped patch.""" 

1218 

1219 def __str__(self): 

1220 pars = (self.center[0], self.center[1], self.r, 

1221 self.theta1, self.theta2, self.width) 

1222 fmt = "Wedge(center=(%g, %g), r=%g, theta1=%g, theta2=%g, width=%s)" 

1223 return fmt % pars 

1224 

1225 @_docstring.dedent_interpd 

1226 def __init__(self, center, r, theta1, theta2, *, width=None, **kwargs): 

1227 """ 

1228 A wedge centered at *x*, *y* center with radius *r* that 

1229 sweeps *theta1* to *theta2* (in degrees). If *width* is given, 

1230 then a partial wedge is drawn from inner radius *r* - *width* 

1231 to outer radius *r*. 

1232 

1233 Valid keyword arguments are: 

1234 

1235 %(Patch:kwdoc)s 

1236 """ 

1237 super().__init__(**kwargs) 

1238 self.center = center 

1239 self.r, self.width = r, width 

1240 self.theta1, self.theta2 = theta1, theta2 

1241 self._patch_transform = transforms.IdentityTransform() 

1242 self._recompute_path() 

1243 

1244 def _recompute_path(self): 

1245 # Inner and outer rings are connected unless the annulus is complete 

1246 if abs((self.theta2 - self.theta1) - 360) <= 1e-12: 

1247 theta1, theta2 = 0, 360 

1248 connector = Path.MOVETO 

1249 else: 

1250 theta1, theta2 = self.theta1, self.theta2 

1251 connector = Path.LINETO 

1252 

1253 # Form the outer ring 

1254 arc = Path.arc(theta1, theta2) 

1255 

1256 if self.width is not None: 

1257 # Partial annulus needs to draw the outer ring 

1258 # followed by a reversed and scaled inner ring 

1259 v1 = arc.vertices 

1260 v2 = arc.vertices[::-1] * (self.r - self.width) / self.r 

1261 v = np.concatenate([v1, v2, [(0, 0)]]) 

1262 c = [*arc.codes, connector, *arc.codes[1:], Path.CLOSEPOLY] 

1263 else: 

1264 # Wedge doesn't need an inner ring 

1265 v = np.concatenate([arc.vertices, [(0, 0), (0, 0)]]) 

1266 c = [*arc.codes, connector, Path.CLOSEPOLY] 

1267 

1268 # Shift and scale the wedge to the final location. 

1269 self._path = Path(v * self.r + self.center, c) 

1270 

1271 def set_center(self, center): 

1272 self._path = None 

1273 self.center = center 

1274 self.stale = True 

1275 

1276 def set_radius(self, radius): 

1277 self._path = None 

1278 self.r = radius 

1279 self.stale = True 

1280 

1281 def set_theta1(self, theta1): 

1282 self._path = None 

1283 self.theta1 = theta1 

1284 self.stale = True 

1285 

1286 def set_theta2(self, theta2): 

1287 self._path = None 

1288 self.theta2 = theta2 

1289 self.stale = True 

1290 

1291 def set_width(self, width): 

1292 self._path = None 

1293 self.width = width 

1294 self.stale = True 

1295 

1296 def get_path(self): 

1297 if self._path is None: 

1298 self._recompute_path() 

1299 return self._path 

1300 

1301 

1302# COVERAGE NOTE: Not used internally or from examples 

1303class Arrow(Patch): 

1304 """An arrow patch.""" 

1305 

1306 def __str__(self): 

1307 return "Arrow()" 

1308 

1309 _path = Path._create_closed([ 

1310 [0.0, 0.1], [0.0, -0.1], [0.8, -0.1], [0.8, -0.3], [1.0, 0.0], 

1311 [0.8, 0.3], [0.8, 0.1]]) 

1312 

1313 @_docstring.dedent_interpd 

1314 def __init__(self, x, y, dx, dy, *, width=1.0, **kwargs): 

1315 """ 

1316 Draws an arrow from (*x*, *y*) to (*x* + *dx*, *y* + *dy*). 

1317 The width of the arrow is scaled by *width*. 

1318 

1319 Parameters 

1320 ---------- 

1321 x : float 

1322 x coordinate of the arrow tail. 

1323 y : float 

1324 y coordinate of the arrow tail. 

1325 dx : float 

1326 Arrow length in the x direction. 

1327 dy : float 

1328 Arrow length in the y direction. 

1329 width : float, default: 1 

1330 Scale factor for the width of the arrow. With a default value of 1, 

1331 the tail width is 0.2 and head width is 0.6. 

1332 **kwargs 

1333 Keyword arguments control the `Patch` properties: 

1334 

1335 %(Patch:kwdoc)s 

1336 

1337 See Also 

1338 -------- 

1339 FancyArrow 

1340 Patch that allows independent control of the head and tail 

1341 properties. 

1342 """ 

1343 super().__init__(**kwargs) 

1344 self.set_data(x, y, dx, dy, width) 

1345 

1346 def get_path(self): 

1347 return self._path 

1348 

1349 def get_patch_transform(self): 

1350 return self._patch_transform 

1351 

1352 def set_data(self, x=None, y=None, dx=None, dy=None, width=None): 

1353 """ 

1354 Set `.Arrow` x, y, dx, dy and width. 

1355 Values left as None will not be updated. 

1356 

1357 Parameters 

1358 ---------- 

1359 x, y : float or None, default: None 

1360 The x and y coordinates of the arrow base. 

1361 

1362 dx, dy : float or None, default: None 

1363 The length of the arrow along x and y direction. 

1364 

1365 width : float or None, default: None 

1366 Width of full arrow tail. 

1367 """ 

1368 if x is not None: 

1369 self._x = x 

1370 if y is not None: 

1371 self._y = y 

1372 if dx is not None: 

1373 self._dx = dx 

1374 if dy is not None: 

1375 self._dy = dy 

1376 if width is not None: 

1377 self._width = width 

1378 self._patch_transform = ( 

1379 transforms.Affine2D() 

1380 .scale(np.hypot(self._dx, self._dy), self._width) 

1381 .rotate(np.arctan2(self._dy, self._dx)) 

1382 .translate(self._x, self._y) 

1383 .frozen()) 

1384 

1385 

1386class FancyArrow(Polygon): 

1387 """ 

1388 Like Arrow, but lets you set head width and head height independently. 

1389 """ 

1390 

1391 _edge_default = True 

1392 

1393 def __str__(self): 

1394 return "FancyArrow()" 

1395 

1396 @_docstring.dedent_interpd 

1397 def __init__(self, x, y, dx, dy, *, 

1398 width=0.001, length_includes_head=False, head_width=None, 

1399 head_length=None, shape='full', overhang=0, 

1400 head_starts_at_zero=False, **kwargs): 

1401 """ 

1402 Parameters 

1403 ---------- 

1404 x, y : float 

1405 The x and y coordinates of the arrow base. 

1406 

1407 dx, dy : float 

1408 The length of the arrow along x and y direction. 

1409 

1410 width : float, default: 0.001 

1411 Width of full arrow tail. 

1412 

1413 length_includes_head : bool, default: False 

1414 True if head is to be counted in calculating the length. 

1415 

1416 head_width : float or None, default: 3*width 

1417 Total width of the full arrow head. 

1418 

1419 head_length : float or None, default: 1.5*head_width 

1420 Length of arrow head. 

1421 

1422 shape : {'full', 'left', 'right'}, default: 'full' 

1423 Draw the left-half, right-half, or full arrow. 

1424 

1425 overhang : float, default: 0 

1426 Fraction that the arrow is swept back (0 overhang means 

1427 triangular shape). Can be negative or greater than one. 

1428 

1429 head_starts_at_zero : bool, default: False 

1430 If True, the head starts being drawn at coordinate 0 

1431 instead of ending at coordinate 0. 

1432 

1433 **kwargs 

1434 `.Patch` properties: 

1435 

1436 %(Patch:kwdoc)s 

1437 """ 

1438 self._x = x 

1439 self._y = y 

1440 self._dx = dx 

1441 self._dy = dy 

1442 self._width = width 

1443 self._length_includes_head = length_includes_head 

1444 self._head_width = head_width 

1445 self._head_length = head_length 

1446 self._shape = shape 

1447 self._overhang = overhang 

1448 self._head_starts_at_zero = head_starts_at_zero 

1449 self._make_verts() 

1450 super().__init__(self.verts, closed=True, **kwargs) 

1451 

1452 def set_data(self, *, x=None, y=None, dx=None, dy=None, width=None, 

1453 head_width=None, head_length=None): 

1454 """ 

1455 Set `.FancyArrow` x, y, dx, dy, width, head_with, and head_length. 

1456 Values left as None will not be updated. 

1457 

1458 Parameters 

1459 ---------- 

1460 x, y : float or None, default: None 

1461 The x and y coordinates of the arrow base. 

1462 

1463 dx, dy : float or None, default: None 

1464 The length of the arrow along x and y direction. 

1465 

1466 width : float or None, default: None 

1467 Width of full arrow tail. 

1468 

1469 head_width : float or None, default: None 

1470 Total width of the full arrow head. 

1471 

1472 head_length : float or None, default: None 

1473 Length of arrow head. 

1474 """ 

1475 if x is not None: 

1476 self._x = x 

1477 if y is not None: 

1478 self._y = y 

1479 if dx is not None: 

1480 self._dx = dx 

1481 if dy is not None: 

1482 self._dy = dy 

1483 if width is not None: 

1484 self._width = width 

1485 if head_width is not None: 

1486 self._head_width = head_width 

1487 if head_length is not None: 

1488 self._head_length = head_length 

1489 self._make_verts() 

1490 self.set_xy(self.verts) 

1491 

1492 def _make_verts(self): 

1493 if self._head_width is None: 

1494 head_width = 3 * self._width 

1495 else: 

1496 head_width = self._head_width 

1497 if self._head_length is None: 

1498 head_length = 1.5 * head_width 

1499 else: 

1500 head_length = self._head_length 

1501 

1502 distance = np.hypot(self._dx, self._dy) 

1503 

1504 if self._length_includes_head: 

1505 length = distance 

1506 else: 

1507 length = distance + head_length 

1508 if not length: 

1509 self.verts = np.empty([0, 2]) # display nothing if empty 

1510 else: 

1511 # start by drawing horizontal arrow, point at (0, 0) 

1512 hw, hl = head_width, head_length 

1513 hs, lw = self._overhang, self._width 

1514 left_half_arrow = np.array([ 

1515 [0.0, 0.0], # tip 

1516 [-hl, -hw / 2], # leftmost 

1517 [-hl * (1 - hs), -lw / 2], # meets stem 

1518 [-length, -lw / 2], # bottom left 

1519 [-length, 0], 

1520 ]) 

1521 # if we're not including the head, shift up by head length 

1522 if not self._length_includes_head: 

1523 left_half_arrow += [head_length, 0] 

1524 # if the head starts at 0, shift up by another head length 

1525 if self._head_starts_at_zero: 

1526 left_half_arrow += [head_length / 2, 0] 

1527 # figure out the shape, and complete accordingly 

1528 if self._shape == 'left': 

1529 coords = left_half_arrow 

1530 else: 

1531 right_half_arrow = left_half_arrow * [1, -1] 

1532 if self._shape == 'right': 

1533 coords = right_half_arrow 

1534 elif self._shape == 'full': 

1535 # The half-arrows contain the midpoint of the stem, 

1536 # which we can omit from the full arrow. Including it 

1537 # twice caused a problem with xpdf. 

1538 coords = np.concatenate([left_half_arrow[:-1], 

1539 right_half_arrow[-2::-1]]) 

1540 else: 

1541 raise ValueError(f"Got unknown shape: {self._shape!r}") 

1542 if distance != 0: 

1543 cx = self._dx / distance 

1544 sx = self._dy / distance 

1545 else: 

1546 # Account for division by zero 

1547 cx, sx = 0, 1 

1548 M = [[cx, sx], [-sx, cx]] 

1549 self.verts = np.dot(coords, M) + [ 

1550 self._x + self._dx, 

1551 self._y + self._dy, 

1552 ] 

1553 

1554 

1555_docstring.interpd.update( 

1556 FancyArrow="\n".join( 

1557 (inspect.getdoc(FancyArrow.__init__) or "").splitlines()[2:])) 

1558 

1559 

1560class CirclePolygon(RegularPolygon): 

1561 """A polygon-approximation of a circle patch.""" 

1562 

1563 def __str__(self): 

1564 s = "CirclePolygon((%g, %g), radius=%g, resolution=%d)" 

1565 return s % (self.xy[0], self.xy[1], self.radius, self.numvertices) 

1566 

1567 @_docstring.dedent_interpd 

1568 def __init__(self, xy, radius=5, *, 

1569 resolution=20, # the number of vertices 

1570 ** kwargs): 

1571 """ 

1572 Create a circle at *xy* = (*x*, *y*) with given *radius*. 

1573 

1574 This circle is approximated by a regular polygon with *resolution* 

1575 sides. For a smoother circle drawn with splines, see `Circle`. 

1576 

1577 Valid keyword arguments are: 

1578 

1579 %(Patch:kwdoc)s 

1580 """ 

1581 super().__init__( 

1582 xy, resolution, radius=radius, orientation=0, **kwargs) 

1583 

1584 

1585class Ellipse(Patch): 

1586 """A scale-free ellipse.""" 

1587 

1588 def __str__(self): 

1589 pars = (self._center[0], self._center[1], 

1590 self.width, self.height, self.angle) 

1591 fmt = "Ellipse(xy=(%s, %s), width=%s, height=%s, angle=%s)" 

1592 return fmt % pars 

1593 

1594 @_docstring.dedent_interpd 

1595 def __init__(self, xy, width, height, *, angle=0, **kwargs): 

1596 """ 

1597 Parameters 

1598 ---------- 

1599 xy : (float, float) 

1600 xy coordinates of ellipse centre. 

1601 width : float 

1602 Total length (diameter) of horizontal axis. 

1603 height : float 

1604 Total length (diameter) of vertical axis. 

1605 angle : float, default: 0 

1606 Rotation in degrees anti-clockwise. 

1607 

1608 Notes 

1609 ----- 

1610 Valid keyword arguments are: 

1611 

1612 %(Patch:kwdoc)s 

1613 """ 

1614 super().__init__(**kwargs) 

1615 

1616 self._center = xy 

1617 self._width, self._height = width, height 

1618 self._angle = angle 

1619 self._path = Path.unit_circle() 

1620 # Required for EllipseSelector with axes aspect ratio != 1 

1621 # The patch is defined in data coordinates and when changing the 

1622 # selector with square modifier and not in data coordinates, we need 

1623 # to correct for the aspect ratio difference between the data and 

1624 # display coordinate systems. 

1625 self._aspect_ratio_correction = 1.0 

1626 # Note: This cannot be calculated until this is added to an Axes 

1627 self._patch_transform = transforms.IdentityTransform() 

1628 

1629 def _recompute_transform(self): 

1630 """ 

1631 Notes 

1632 ----- 

1633 This cannot be called until after this has been added to an Axes, 

1634 otherwise unit conversion will fail. This makes it very important to 

1635 call the accessor method and not directly access the transformation 

1636 member variable. 

1637 """ 

1638 center = (self.convert_xunits(self._center[0]), 

1639 self.convert_yunits(self._center[1])) 

1640 width = self.convert_xunits(self._width) 

1641 height = self.convert_yunits(self._height) 

1642 self._patch_transform = transforms.Affine2D() \ 

1643 .scale(width * 0.5, height * 0.5 * self._aspect_ratio_correction) \ 

1644 .rotate_deg(self.angle) \ 

1645 .scale(1, 1 / self._aspect_ratio_correction) \ 

1646 .translate(*center) 

1647 

1648 def get_path(self): 

1649 """Return the path of the ellipse.""" 

1650 return self._path 

1651 

1652 def get_patch_transform(self): 

1653 self._recompute_transform() 

1654 return self._patch_transform 

1655 

1656 def set_center(self, xy): 

1657 """ 

1658 Set the center of the ellipse. 

1659 

1660 Parameters 

1661 ---------- 

1662 xy : (float, float) 

1663 """ 

1664 self._center = xy 

1665 self.stale = True 

1666 

1667 def get_center(self): 

1668 """Return the center of the ellipse.""" 

1669 return self._center 

1670 

1671 center = property(get_center, set_center) 

1672 

1673 def set_width(self, width): 

1674 """ 

1675 Set the width of the ellipse. 

1676 

1677 Parameters 

1678 ---------- 

1679 width : float 

1680 """ 

1681 self._width = width 

1682 self.stale = True 

1683 

1684 def get_width(self): 

1685 """ 

1686 Return the width of the ellipse. 

1687 """ 

1688 return self._width 

1689 

1690 width = property(get_width, set_width) 

1691 

1692 def set_height(self, height): 

1693 """ 

1694 Set the height of the ellipse. 

1695 

1696 Parameters 

1697 ---------- 

1698 height : float 

1699 """ 

1700 self._height = height 

1701 self.stale = True 

1702 

1703 def get_height(self): 

1704 """Return the height of the ellipse.""" 

1705 return self._height 

1706 

1707 height = property(get_height, set_height) 

1708 

1709 def set_angle(self, angle): 

1710 """ 

1711 Set the angle of the ellipse. 

1712 

1713 Parameters 

1714 ---------- 

1715 angle : float 

1716 """ 

1717 self._angle = angle 

1718 self.stale = True 

1719 

1720 def get_angle(self): 

1721 """Return the angle of the ellipse.""" 

1722 return self._angle 

1723 

1724 angle = property(get_angle, set_angle) 

1725 

1726 def get_corners(self): 

1727 """ 

1728 Return the corners of the ellipse bounding box. 

1729 

1730 The bounding box orientation is moving anti-clockwise from the 

1731 lower left corner defined before rotation. 

1732 """ 

1733 return self.get_patch_transform().transform( 

1734 [(-1, -1), (1, -1), (1, 1), (-1, 1)]) 

1735 

1736 def get_vertices(self): 

1737 """ 

1738 Return the vertices coordinates of the ellipse. 

1739 

1740 The definition can be found `here <https://en.wikipedia.org/wiki/Ellipse>`_ 

1741 

1742 .. versionadded:: 3.8 

1743 """ 

1744 if self.width < self.height: 

1745 ret = self.get_patch_transform().transform([(0, 1), (0, -1)]) 

1746 else: 

1747 ret = self.get_patch_transform().transform([(1, 0), (-1, 0)]) 

1748 return [tuple(x) for x in ret] 

1749 

1750 def get_co_vertices(self): 

1751 """ 

1752 Return the co-vertices coordinates of the ellipse. 

1753 

1754 The definition can be found `here <https://en.wikipedia.org/wiki/Ellipse>`_ 

1755 

1756 .. versionadded:: 3.8 

1757 """ 

1758 if self.width < self.height: 

1759 ret = self.get_patch_transform().transform([(1, 0), (-1, 0)]) 

1760 else: 

1761 ret = self.get_patch_transform().transform([(0, 1), (0, -1)]) 

1762 return [tuple(x) for x in ret] 

1763 

1764 

1765class Annulus(Patch): 

1766 """ 

1767 An elliptical annulus. 

1768 """ 

1769 

1770 @_docstring.dedent_interpd 

1771 def __init__(self, xy, r, width, angle=0.0, **kwargs): 

1772 """ 

1773 Parameters 

1774 ---------- 

1775 xy : (float, float) 

1776 xy coordinates of annulus centre. 

1777 r : float or (float, float) 

1778 The radius, or semi-axes: 

1779 

1780 - If float: radius of the outer circle. 

1781 - If two floats: semi-major and -minor axes of outer ellipse. 

1782 width : float 

1783 Width (thickness) of the annular ring. The width is measured inward 

1784 from the outer ellipse so that for the inner ellipse the semi-axes 

1785 are given by ``r - width``. *width* must be less than or equal to 

1786 the semi-minor axis. 

1787 angle : float, default: 0 

1788 Rotation angle in degrees (anti-clockwise from the positive 

1789 x-axis). Ignored for circular annuli (i.e., if *r* is a scalar). 

1790 **kwargs 

1791 Keyword arguments control the `Patch` properties: 

1792 

1793 %(Patch:kwdoc)s 

1794 """ 

1795 super().__init__(**kwargs) 

1796 

1797 self.set_radii(r) 

1798 self.center = xy 

1799 self.width = width 

1800 self.angle = angle 

1801 self._path = None 

1802 

1803 def __str__(self): 

1804 if self.a == self.b: 

1805 r = self.a 

1806 else: 

1807 r = (self.a, self.b) 

1808 

1809 return "Annulus(xy=(%s, %s), r=%s, width=%s, angle=%s)" % \ 

1810 (*self.center, r, self.width, self.angle) 

1811 

1812 def set_center(self, xy): 

1813 """ 

1814 Set the center of the annulus. 

1815 

1816 Parameters 

1817 ---------- 

1818 xy : (float, float) 

1819 """ 

1820 self._center = xy 

1821 self._path = None 

1822 self.stale = True 

1823 

1824 def get_center(self): 

1825 """Return the center of the annulus.""" 

1826 return self._center 

1827 

1828 center = property(get_center, set_center) 

1829 

1830 def set_width(self, width): 

1831 """ 

1832 Set the width (thickness) of the annulus ring. 

1833 

1834 The width is measured inwards from the outer ellipse. 

1835 

1836 Parameters 

1837 ---------- 

1838 width : float 

1839 """ 

1840 if width > min(self.a, self.b): 

1841 raise ValueError( 

1842 'Width of annulus must be less than or equal to semi-minor axis') 

1843 

1844 self._width = width 

1845 self._path = None 

1846 self.stale = True 

1847 

1848 def get_width(self): 

1849 """Return the width (thickness) of the annulus ring.""" 

1850 return self._width 

1851 

1852 width = property(get_width, set_width) 

1853 

1854 def set_angle(self, angle): 

1855 """ 

1856 Set the tilt angle of the annulus. 

1857 

1858 Parameters 

1859 ---------- 

1860 angle : float 

1861 """ 

1862 self._angle = angle 

1863 self._path = None 

1864 self.stale = True 

1865 

1866 def get_angle(self): 

1867 """Return the angle of the annulus.""" 

1868 return self._angle 

1869 

1870 angle = property(get_angle, set_angle) 

1871 

1872 def set_semimajor(self, a): 

1873 """ 

1874 Set the semi-major axis *a* of the annulus. 

1875 

1876 Parameters 

1877 ---------- 

1878 a : float 

1879 """ 

1880 self.a = float(a) 

1881 self._path = None 

1882 self.stale = True 

1883 

1884 def set_semiminor(self, b): 

1885 """ 

1886 Set the semi-minor axis *b* of the annulus. 

1887 

1888 Parameters 

1889 ---------- 

1890 b : float 

1891 """ 

1892 self.b = float(b) 

1893 self._path = None 

1894 self.stale = True 

1895 

1896 def set_radii(self, r): 

1897 """ 

1898 Set the semi-major (*a*) and semi-minor radii (*b*) of the annulus. 

1899 

1900 Parameters 

1901 ---------- 

1902 r : float or (float, float) 

1903 The radius, or semi-axes: 

1904 

1905 - If float: radius of the outer circle. 

1906 - If two floats: semi-major and -minor axes of outer ellipse. 

1907 """ 

1908 if np.shape(r) == (2,): 

1909 self.a, self.b = r 

1910 elif np.shape(r) == (): 

1911 self.a = self.b = float(r) 

1912 else: 

1913 raise ValueError("Parameter 'r' must be one or two floats.") 

1914 

1915 self._path = None 

1916 self.stale = True 

1917 

1918 def get_radii(self): 

1919 """Return the semi-major and semi-minor radii of the annulus.""" 

1920 return self.a, self.b 

1921 

1922 radii = property(get_radii, set_radii) 

1923 

1924 def _transform_verts(self, verts, a, b): 

1925 return transforms.Affine2D() \ 

1926 .scale(*self._convert_xy_units((a, b))) \ 

1927 .rotate_deg(self.angle) \ 

1928 .translate(*self._convert_xy_units(self.center)) \ 

1929 .transform(verts) 

1930 

1931 def _recompute_path(self): 

1932 # circular arc 

1933 arc = Path.arc(0, 360) 

1934 

1935 # annulus needs to draw an outer ring 

1936 # followed by a reversed and scaled inner ring 

1937 a, b, w = self.a, self.b, self.width 

1938 v1 = self._transform_verts(arc.vertices, a, b) 

1939 v2 = self._transform_verts(arc.vertices[::-1], a - w, b - w) 

1940 v = np.vstack([v1, v2, v1[0, :], (0, 0)]) 

1941 c = np.hstack([arc.codes, Path.MOVETO, 

1942 arc.codes[1:], Path.MOVETO, 

1943 Path.CLOSEPOLY]) 

1944 self._path = Path(v, c) 

1945 

1946 def get_path(self): 

1947 if self._path is None: 

1948 self._recompute_path() 

1949 return self._path 

1950 

1951 

1952class Circle(Ellipse): 

1953 """ 

1954 A circle patch. 

1955 """ 

1956 def __str__(self): 

1957 pars = self.center[0], self.center[1], self.radius 

1958 fmt = "Circle(xy=(%g, %g), radius=%g)" 

1959 return fmt % pars 

1960 

1961 @_docstring.dedent_interpd 

1962 def __init__(self, xy, radius=5, **kwargs): 

1963 """ 

1964 Create a true circle at center *xy* = (*x*, *y*) with given *radius*. 

1965 

1966 Unlike `CirclePolygon` which is a polygonal approximation, this uses 

1967 Bezier splines and is much closer to a scale-free circle. 

1968 

1969 Valid keyword arguments are: 

1970 

1971 %(Patch:kwdoc)s 

1972 """ 

1973 super().__init__(xy, radius * 2, radius * 2, **kwargs) 

1974 self.radius = radius 

1975 

1976 def set_radius(self, radius): 

1977 """ 

1978 Set the radius of the circle. 

1979 

1980 Parameters 

1981 ---------- 

1982 radius : float 

1983 """ 

1984 self.width = self.height = 2 * radius 

1985 self.stale = True 

1986 

1987 def get_radius(self): 

1988 """Return the radius of the circle.""" 

1989 return self.width / 2. 

1990 

1991 radius = property(get_radius, set_radius) 

1992 

1993 

1994class Arc(Ellipse): 

1995 """ 

1996 An elliptical arc, i.e. a segment of an ellipse. 

1997 

1998 Due to internal optimizations, the arc cannot be filled. 

1999 """ 

2000 

2001 def __str__(self): 

2002 pars = (self.center[0], self.center[1], self.width, 

2003 self.height, self.angle, self.theta1, self.theta2) 

2004 fmt = ("Arc(xy=(%g, %g), width=%g, " 

2005 "height=%g, angle=%g, theta1=%g, theta2=%g)") 

2006 return fmt % pars 

2007 

2008 @_docstring.dedent_interpd 

2009 def __init__(self, xy, width, height, *, 

2010 angle=0.0, theta1=0.0, theta2=360.0, **kwargs): 

2011 """ 

2012 Parameters 

2013 ---------- 

2014 xy : (float, float) 

2015 The center of the ellipse. 

2016 

2017 width : float 

2018 The length of the horizontal axis. 

2019 

2020 height : float 

2021 The length of the vertical axis. 

2022 

2023 angle : float 

2024 Rotation of the ellipse in degrees (counterclockwise). 

2025 

2026 theta1, theta2 : float, default: 0, 360 

2027 Starting and ending angles of the arc in degrees. These values 

2028 are relative to *angle*, e.g. if *angle* = 45 and *theta1* = 90 

2029 the absolute starting angle is 135. 

2030 Default *theta1* = 0, *theta2* = 360, i.e. a complete ellipse. 

2031 The arc is drawn in the counterclockwise direction. 

2032 Angles greater than or equal to 360, or smaller than 0, are 

2033 represented by an equivalent angle in the range [0, 360), by 

2034 taking the input value mod 360. 

2035 

2036 Other Parameters 

2037 ---------------- 

2038 **kwargs : `~matplotlib.patches.Patch` properties 

2039 Most `.Patch` properties are supported as keyword arguments, 

2040 except *fill* and *facecolor* because filling is not supported. 

2041 

2042 %(Patch:kwdoc)s 

2043 """ 

2044 fill = kwargs.setdefault('fill', False) 

2045 if fill: 

2046 raise ValueError("Arc objects cannot be filled") 

2047 

2048 super().__init__(xy, width, height, angle=angle, **kwargs) 

2049 

2050 self.theta1 = theta1 

2051 self.theta2 = theta2 

2052 (self._theta1, self._theta2, self._stretched_width, 

2053 self._stretched_height) = self._theta_stretch() 

2054 self._path = Path.arc(self._theta1, self._theta2) 

2055 

2056 @artist.allow_rasterization 

2057 def draw(self, renderer): 

2058 """ 

2059 Draw the arc to the given *renderer*. 

2060 

2061 Notes 

2062 ----- 

2063 Ellipses are normally drawn using an approximation that uses 

2064 eight cubic Bezier splines. The error of this approximation 

2065 is 1.89818e-6, according to this unverified source: 

2066 

2067 Lancaster, Don. *Approximating a Circle or an Ellipse Using 

2068 Four Bezier Cubic Splines.* 

2069 

2070 https://www.tinaja.com/glib/ellipse4.pdf 

2071 

2072 There is a use case where very large ellipses must be drawn 

2073 with very high accuracy, and it is too expensive to render the 

2074 entire ellipse with enough segments (either splines or line 

2075 segments). Therefore, in the case where either radius of the 

2076 ellipse is large enough that the error of the spline 

2077 approximation will be visible (greater than one pixel offset 

2078 from the ideal), a different technique is used. 

2079 

2080 In that case, only the visible parts of the ellipse are drawn, 

2081 with each visible arc using a fixed number of spline segments 

2082 (8). The algorithm proceeds as follows: 

2083 

2084 1. The points where the ellipse intersects the axes (or figure) 

2085 bounding box are located. (This is done by performing an inverse 

2086 transformation on the bbox such that it is relative to the unit 

2087 circle -- this makes the intersection calculation much easier than 

2088 doing rotated ellipse intersection directly.) 

2089 

2090 This uses the "line intersecting a circle" algorithm from: 

2091 

2092 Vince, John. *Geometry for Computer Graphics: Formulae, 

2093 Examples & Proofs.* London: Springer-Verlag, 2005. 

2094 

2095 2. The angles of each of the intersection points are calculated. 

2096 

2097 3. Proceeding counterclockwise starting in the positive 

2098 x-direction, each of the visible arc-segments between the 

2099 pairs of vertices are drawn using the Bezier arc 

2100 approximation technique implemented in `.Path.arc`. 

2101 """ 

2102 if not self.get_visible(): 

2103 return 

2104 

2105 self._recompute_transform() 

2106 

2107 self._update_path() 

2108 # Get width and height in pixels we need to use 

2109 # `self.get_data_transform` rather than `self.get_transform` 

2110 # because we want the transform from dataspace to the 

2111 # screen space to estimate how big the arc will be in physical 

2112 # units when rendered (the transform that we get via 

2113 # `self.get_transform()` goes from an idealized unit-radius 

2114 # space to screen space). 

2115 data_to_screen_trans = self.get_data_transform() 

2116 pwidth, pheight = ( 

2117 data_to_screen_trans.transform((self._stretched_width, 

2118 self._stretched_height)) - 

2119 data_to_screen_trans.transform((0, 0))) 

2120 inv_error = (1.0 / 1.89818e-6) * 0.5 

2121 

2122 if pwidth < inv_error and pheight < inv_error: 

2123 return Patch.draw(self, renderer) 

2124 

2125 def line_circle_intersect(x0, y0, x1, y1): 

2126 dx = x1 - x0 

2127 dy = y1 - y0 

2128 dr2 = dx * dx + dy * dy 

2129 D = x0 * y1 - x1 * y0 

2130 D2 = D * D 

2131 discrim = dr2 - D2 

2132 if discrim >= 0.0: 

2133 sign_dy = np.copysign(1, dy) # +/-1, never 0. 

2134 sqrt_discrim = np.sqrt(discrim) 

2135 return np.array( 

2136 [[(D * dy + sign_dy * dx * sqrt_discrim) / dr2, 

2137 (-D * dx + abs(dy) * sqrt_discrim) / dr2], 

2138 [(D * dy - sign_dy * dx * sqrt_discrim) / dr2, 

2139 (-D * dx - abs(dy) * sqrt_discrim) / dr2]]) 

2140 else: 

2141 return np.empty((0, 2)) 

2142 

2143 def segment_circle_intersect(x0, y0, x1, y1): 

2144 epsilon = 1e-9 

2145 if x1 < x0: 

2146 x0e, x1e = x1, x0 

2147 else: 

2148 x0e, x1e = x0, x1 

2149 if y1 < y0: 

2150 y0e, y1e = y1, y0 

2151 else: 

2152 y0e, y1e = y0, y1 

2153 xys = line_circle_intersect(x0, y0, x1, y1) 

2154 xs, ys = xys.T 

2155 return xys[ 

2156 (x0e - epsilon < xs) & (xs < x1e + epsilon) 

2157 & (y0e - epsilon < ys) & (ys < y1e + epsilon) 

2158 ] 

2159 

2160 # Transform the Axes (or figure) box_path so that it is relative to 

2161 # the unit circle in the same way that it is relative to the desired 

2162 # ellipse. 

2163 box_path_transform = ( 

2164 transforms.BboxTransformTo((self.axes or self.figure).bbox) 

2165 - self.get_transform()) 

2166 box_path = Path.unit_rectangle().transformed(box_path_transform) 

2167 

2168 thetas = set() 

2169 # For each of the point pairs, there is a line segment 

2170 for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]): 

2171 xy = segment_circle_intersect(*p0, *p1) 

2172 x, y = xy.T 

2173 # arctan2 return [-pi, pi), the rest of our angles are in 

2174 # [0, 360], adjust as needed. 

2175 theta = (np.rad2deg(np.arctan2(y, x)) + 360) % 360 

2176 thetas.update( 

2177 theta[(self._theta1 < theta) & (theta < self._theta2)]) 

2178 thetas = sorted(thetas) + [self._theta2] 

2179 last_theta = self._theta1 

2180 theta1_rad = np.deg2rad(self._theta1) 

2181 inside = box_path.contains_point( 

2182 (np.cos(theta1_rad), np.sin(theta1_rad)) 

2183 ) 

2184 

2185 # save original path 

2186 path_original = self._path 

2187 for theta in thetas: 

2188 if inside: 

2189 self._path = Path.arc(last_theta, theta, 8) 

2190 Patch.draw(self, renderer) 

2191 inside = False 

2192 else: 

2193 inside = True 

2194 last_theta = theta 

2195 

2196 # restore original path 

2197 self._path = path_original 

2198 

2199 def _update_path(self): 

2200 # Compute new values and update and set new _path if any value changed 

2201 stretched = self._theta_stretch() 

2202 if any(a != b for a, b in zip( 

2203 stretched, (self._theta1, self._theta2, self._stretched_width, 

2204 self._stretched_height))): 

2205 (self._theta1, self._theta2, self._stretched_width, 

2206 self._stretched_height) = stretched 

2207 self._path = Path.arc(self._theta1, self._theta2) 

2208 

2209 def _theta_stretch(self): 

2210 # If the width and height of ellipse are not equal, take into account 

2211 # stretching when calculating angles to draw between 

2212 def theta_stretch(theta, scale): 

2213 theta = np.deg2rad(theta) 

2214 x = np.cos(theta) 

2215 y = np.sin(theta) 

2216 stheta = np.rad2deg(np.arctan2(scale * y, x)) 

2217 # arctan2 has the range [-pi, pi], we expect [0, 2*pi] 

2218 return (stheta + 360) % 360 

2219 

2220 width = self.convert_xunits(self.width) 

2221 height = self.convert_yunits(self.height) 

2222 if ( 

2223 # if we need to stretch the angles because we are distorted 

2224 width != height 

2225 # and we are not doing a full circle. 

2226 # 

2227 # 0 and 360 do not exactly round-trip through the angle 

2228 # stretching (due to both float precision limitations and 

2229 # the difference between the range of arctan2 [-pi, pi] and 

2230 # this method [0, 360]) so avoid doing it if we don't have to. 

2231 and not (self.theta1 != self.theta2 and 

2232 self.theta1 % 360 == self.theta2 % 360) 

2233 ): 

2234 theta1 = theta_stretch(self.theta1, width / height) 

2235 theta2 = theta_stretch(self.theta2, width / height) 

2236 return theta1, theta2, width, height 

2237 return self.theta1, self.theta2, width, height 

2238 

2239 

2240def bbox_artist(artist, renderer, props=None, fill=True): 

2241 """ 

2242 A debug function to draw a rectangle around the bounding 

2243 box returned by an artist's `.Artist.get_window_extent` 

2244 to test whether the artist is returning the correct bbox. 

2245 

2246 *props* is a dict of rectangle props with the additional property 

2247 'pad' that sets the padding around the bbox in points. 

2248 """ 

2249 if props is None: 

2250 props = {} 

2251 props = props.copy() # don't want to alter the pad externally 

2252 pad = props.pop('pad', 4) 

2253 pad = renderer.points_to_pixels(pad) 

2254 bbox = artist.get_window_extent(renderer) 

2255 r = Rectangle( 

2256 xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2), 

2257 width=bbox.width + pad, height=bbox.height + pad, 

2258 fill=fill, transform=transforms.IdentityTransform(), clip_on=False) 

2259 r.update(props) 

2260 r.draw(renderer) 

2261 

2262 

2263def draw_bbox(bbox, renderer, color='k', trans=None): 

2264 """ 

2265 A debug function to draw a rectangle around the bounding 

2266 box returned by an artist's `.Artist.get_window_extent` 

2267 to test whether the artist is returning the correct bbox. 

2268 """ 

2269 r = Rectangle(xy=bbox.p0, width=bbox.width, height=bbox.height, 

2270 edgecolor=color, fill=False, clip_on=False) 

2271 if trans is not None: 

2272 r.set_transform(trans) 

2273 r.draw(renderer) 

2274 

2275 

2276class _Style: 

2277 """ 

2278 A base class for the Styles. It is meant to be a container class, 

2279 where actual styles are declared as subclass of it, and it 

2280 provides some helper functions. 

2281 """ 

2282 

2283 def __init_subclass__(cls): 

2284 # Automatically perform docstring interpolation on the subclasses: 

2285 # This allows listing the supported styles via 

2286 # - %(BoxStyle:table)s 

2287 # - %(ConnectionStyle:table)s 

2288 # - %(ArrowStyle:table)s 

2289 # and additionally adding .. ACCEPTS: blocks via 

2290 # - %(BoxStyle:table_and_accepts)s 

2291 # - %(ConnectionStyle:table_and_accepts)s 

2292 # - %(ArrowStyle:table_and_accepts)s 

2293 _docstring.interpd.update({ 

2294 f"{cls.__name__}:table": cls.pprint_styles(), 

2295 f"{cls.__name__}:table_and_accepts": ( 

2296 cls.pprint_styles() 

2297 + "\n\n .. ACCEPTS: [" 

2298 + "|".join(map(" '{}' ".format, cls._style_list)) 

2299 + "]") 

2300 }) 

2301 

2302 def __new__(cls, stylename, **kwargs): 

2303 """Return the instance of the subclass with the given style name.""" 

2304 # The "class" should have the _style_list attribute, which is a mapping 

2305 # of style names to style classes. 

2306 _list = stylename.replace(" ", "").split(",") 

2307 _name = _list[0].lower() 

2308 try: 

2309 _cls = cls._style_list[_name] 

2310 except KeyError as err: 

2311 raise ValueError(f"Unknown style: {stylename!r}") from err 

2312 try: 

2313 _args_pair = [cs.split("=") for cs in _list[1:]] 

2314 _args = {k: float(v) for k, v in _args_pair} 

2315 except ValueError as err: 

2316 raise ValueError( 

2317 f"Incorrect style argument: {stylename!r}") from err 

2318 return _cls(**{**_args, **kwargs}) 

2319 

2320 @classmethod 

2321 def get_styles(cls): 

2322 """Return a dictionary of available styles.""" 

2323 return cls._style_list 

2324 

2325 @classmethod 

2326 def pprint_styles(cls): 

2327 """Return the available styles as pretty-printed string.""" 

2328 table = [('Class', 'Name', 'Attrs'), 

2329 *[(cls.__name__, 

2330 # Add backquotes, as - and | have special meaning in reST. 

2331 f'``{name}``', 

2332 # [1:-1] drops the surrounding parentheses. 

2333 str(inspect.signature(cls))[1:-1] or 'None') 

2334 for name, cls in cls._style_list.items()]] 

2335 # Convert to rst table. 

2336 col_len = [max(len(cell) for cell in column) for column in zip(*table)] 

2337 table_formatstr = ' '.join('=' * cl for cl in col_len) 

2338 rst_table = '\n'.join([ 

2339 '', 

2340 table_formatstr, 

2341 ' '.join(cell.ljust(cl) for cell, cl in zip(table[0], col_len)), 

2342 table_formatstr, 

2343 *[' '.join(cell.ljust(cl) for cell, cl in zip(row, col_len)) 

2344 for row in table[1:]], 

2345 table_formatstr, 

2346 ]) 

2347 return textwrap.indent(rst_table, prefix=' ' * 4) 

2348 

2349 @classmethod 

2350 def register(cls, name, style): 

2351 """Register a new style.""" 

2352 if not issubclass(style, cls._Base): 

2353 raise ValueError(f"{style} must be a subclass of {cls._Base}") 

2354 cls._style_list[name] = style 

2355 

2356 

2357def _register_style(style_list, cls=None, *, name=None): 

2358 """Class decorator that stashes a class in a (style) dictionary.""" 

2359 if cls is None: 

2360 return functools.partial(_register_style, style_list, name=name) 

2361 style_list[name or cls.__name__.lower()] = cls 

2362 return cls 

2363 

2364 

2365@_docstring.dedent_interpd 

2366class BoxStyle(_Style): 

2367 """ 

2368 `BoxStyle` is a container class which defines several 

2369 boxstyle classes, which are used for `FancyBboxPatch`. 

2370 

2371 A style object can be created as:: 

2372 

2373 BoxStyle.Round(pad=0.2) 

2374 

2375 or:: 

2376 

2377 BoxStyle("Round", pad=0.2) 

2378 

2379 or:: 

2380 

2381 BoxStyle("Round, pad=0.2") 

2382 

2383 The following boxstyle classes are defined. 

2384 

2385 %(BoxStyle:table)s 

2386 

2387 An instance of a boxstyle class is a callable object, with the signature :: 

2388 

2389 __call__(self, x0, y0, width, height, mutation_size) -> Path 

2390 

2391 *x0*, *y0*, *width* and *height* specify the location and size of the box 

2392 to be drawn; *mutation_size* scales the outline properties such as padding. 

2393 """ 

2394 

2395 _style_list = {} 

2396 

2397 @_register_style(_style_list) 

2398 class Square: 

2399 """A square box.""" 

2400 

2401 def __init__(self, pad=0.3): 

2402 """ 

2403 Parameters 

2404 ---------- 

2405 pad : float, default: 0.3 

2406 The amount of padding around the original box. 

2407 """ 

2408 self.pad = pad 

2409 

2410 def __call__(self, x0, y0, width, height, mutation_size): 

2411 pad = mutation_size * self.pad 

2412 # width and height with padding added. 

2413 width, height = width + 2 * pad, height + 2 * pad 

2414 # boundary of the padded box 

2415 x0, y0 = x0 - pad, y0 - pad 

2416 x1, y1 = x0 + width, y0 + height 

2417 return Path._create_closed( 

2418 [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) 

2419 

2420 @_register_style(_style_list) 

2421 class Circle: 

2422 """A circular box.""" 

2423 

2424 def __init__(self, pad=0.3): 

2425 """ 

2426 Parameters 

2427 ---------- 

2428 pad : float, default: 0.3 

2429 The amount of padding around the original box. 

2430 """ 

2431 self.pad = pad 

2432 

2433 def __call__(self, x0, y0, width, height, mutation_size): 

2434 pad = mutation_size * self.pad 

2435 width, height = width + 2 * pad, height + 2 * pad 

2436 # boundary of the padded box 

2437 x0, y0 = x0 - pad, y0 - pad 

2438 return Path.circle((x0 + width / 2, y0 + height / 2), 

2439 max(width, height) / 2) 

2440 

2441 @_register_style(_style_list) 

2442 class Ellipse: 

2443 """ 

2444 An elliptical box. 

2445 

2446 .. versionadded:: 3.7 

2447 """ 

2448 

2449 def __init__(self, pad=0.3): 

2450 """ 

2451 Parameters 

2452 ---------- 

2453 pad : float, default: 0.3 

2454 The amount of padding around the original box. 

2455 """ 

2456 self.pad = pad 

2457 

2458 def __call__(self, x0, y0, width, height, mutation_size): 

2459 pad = mutation_size * self.pad 

2460 width, height = width + 2 * pad, height + 2 * pad 

2461 # boundary of the padded box 

2462 x0, y0 = x0 - pad, y0 - pad 

2463 a = width / math.sqrt(2) 

2464 b = height / math.sqrt(2) 

2465 trans = Affine2D().scale(a, b).translate(x0 + width / 2, 

2466 y0 + height / 2) 

2467 return trans.transform_path(Path.unit_circle()) 

2468 

2469 @_register_style(_style_list) 

2470 class LArrow: 

2471 """A box in the shape of a left-pointing arrow.""" 

2472 

2473 def __init__(self, pad=0.3): 

2474 """ 

2475 Parameters 

2476 ---------- 

2477 pad : float, default: 0.3 

2478 The amount of padding around the original box. 

2479 """ 

2480 self.pad = pad 

2481 

2482 def __call__(self, x0, y0, width, height, mutation_size): 

2483 # padding 

2484 pad = mutation_size * self.pad 

2485 # width and height with padding added. 

2486 width, height = width + 2 * pad, height + 2 * pad 

2487 # boundary of the padded box 

2488 x0, y0 = x0 - pad, y0 - pad, 

2489 x1, y1 = x0 + width, y0 + height 

2490 

2491 dx = (y1 - y0) / 2 

2492 dxx = dx / 2 

2493 x0 = x0 + pad / 1.4 # adjust by ~sqrt(2) 

2494 

2495 return Path._create_closed( 

2496 [(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1), 

2497 (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), 

2498 (x0 + dxx, y0 - dxx), # arrow 

2499 (x0 + dxx, y0)]) 

2500 

2501 @_register_style(_style_list) 

2502 class RArrow(LArrow): 

2503 """A box in the shape of a right-pointing arrow.""" 

2504 

2505 def __call__(self, x0, y0, width, height, mutation_size): 

2506 p = BoxStyle.LArrow.__call__( 

2507 self, x0, y0, width, height, mutation_size) 

2508 p.vertices[:, 0] = 2 * x0 + width - p.vertices[:, 0] 

2509 return p 

2510 

2511 @_register_style(_style_list) 

2512 class DArrow: 

2513 """A box in the shape of a two-way arrow.""" 

2514 # Modified from LArrow to add a right arrow to the bbox. 

2515 

2516 def __init__(self, pad=0.3): 

2517 """ 

2518 Parameters 

2519 ---------- 

2520 pad : float, default: 0.3 

2521 The amount of padding around the original box. 

2522 """ 

2523 self.pad = pad 

2524 

2525 def __call__(self, x0, y0, width, height, mutation_size): 

2526 # padding 

2527 pad = mutation_size * self.pad 

2528 # width and height with padding added. 

2529 # The width is padded by the arrows, so we don't need to pad it. 

2530 height = height + 2 * pad 

2531 # boundary of the padded box 

2532 x0, y0 = x0 - pad, y0 - pad 

2533 x1, y1 = x0 + width, y0 + height 

2534 

2535 dx = (y1 - y0) / 2 

2536 dxx = dx / 2 

2537 x0 = x0 + pad / 1.4 # adjust by ~sqrt(2) 

2538 

2539 return Path._create_closed([ 

2540 (x0 + dxx, y0), (x1, y0), # bot-segment 

2541 (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx), 

2542 (x1, y1 + dxx), # right-arrow 

2543 (x1, y1), (x0 + dxx, y1), # top-segment 

2544 (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx), 

2545 (x0 + dxx, y0 - dxx), # left-arrow 

2546 (x0 + dxx, y0)]) 

2547 

2548 @_register_style(_style_list) 

2549 class Round: 

2550 """A box with round corners.""" 

2551 

2552 def __init__(self, pad=0.3, rounding_size=None): 

2553 """ 

2554 Parameters 

2555 ---------- 

2556 pad : float, default: 0.3 

2557 The amount of padding around the original box. 

2558 rounding_size : float, default: *pad* 

2559 Radius of the corners. 

2560 """ 

2561 self.pad = pad 

2562 self.rounding_size = rounding_size 

2563 

2564 def __call__(self, x0, y0, width, height, mutation_size): 

2565 

2566 # padding 

2567 pad = mutation_size * self.pad 

2568 

2569 # size of the rounding corner 

2570 if self.rounding_size: 

2571 dr = mutation_size * self.rounding_size 

2572 else: 

2573 dr = pad 

2574 

2575 width, height = width + 2 * pad, height + 2 * pad 

2576 

2577 x0, y0 = x0 - pad, y0 - pad, 

2578 x1, y1 = x0 + width, y0 + height 

2579 

2580 # Round corners are implemented as quadratic Bezier, e.g., 

2581 # [(x0, y0-dr), (x0, y0), (x0+dr, y0)] for lower left corner. 

2582 cp = [(x0 + dr, y0), 

2583 (x1 - dr, y0), 

2584 (x1, y0), (x1, y0 + dr), 

2585 (x1, y1 - dr), 

2586 (x1, y1), (x1 - dr, y1), 

2587 (x0 + dr, y1), 

2588 (x0, y1), (x0, y1 - dr), 

2589 (x0, y0 + dr), 

2590 (x0, y0), (x0 + dr, y0), 

2591 (x0 + dr, y0)] 

2592 

2593 com = [Path.MOVETO, 

2594 Path.LINETO, 

2595 Path.CURVE3, Path.CURVE3, 

2596 Path.LINETO, 

2597 Path.CURVE3, Path.CURVE3, 

2598 Path.LINETO, 

2599 Path.CURVE3, Path.CURVE3, 

2600 Path.LINETO, 

2601 Path.CURVE3, Path.CURVE3, 

2602 Path.CLOSEPOLY] 

2603 

2604 return Path(cp, com) 

2605 

2606 @_register_style(_style_list) 

2607 class Round4: 

2608 """A box with rounded edges.""" 

2609 

2610 def __init__(self, pad=0.3, rounding_size=None): 

2611 """ 

2612 Parameters 

2613 ---------- 

2614 pad : float, default: 0.3 

2615 The amount of padding around the original box. 

2616 rounding_size : float, default: *pad*/2 

2617 Rounding of edges. 

2618 """ 

2619 self.pad = pad 

2620 self.rounding_size = rounding_size 

2621 

2622 def __call__(self, x0, y0, width, height, mutation_size): 

2623 

2624 # padding 

2625 pad = mutation_size * self.pad 

2626 

2627 # Rounding size; defaults to half of the padding. 

2628 if self.rounding_size: 

2629 dr = mutation_size * self.rounding_size 

2630 else: 

2631 dr = pad / 2. 

2632 

2633 width = width + 2 * pad - 2 * dr 

2634 height = height + 2 * pad - 2 * dr 

2635 

2636 x0, y0 = x0 - pad + dr, y0 - pad + dr, 

2637 x1, y1 = x0 + width, y0 + height 

2638 

2639 cp = [(x0, y0), 

2640 (x0 + dr, y0 - dr), (x1 - dr, y0 - dr), (x1, y0), 

2641 (x1 + dr, y0 + dr), (x1 + dr, y1 - dr), (x1, y1), 

2642 (x1 - dr, y1 + dr), (x0 + dr, y1 + dr), (x0, y1), 

2643 (x0 - dr, y1 - dr), (x0 - dr, y0 + dr), (x0, y0), 

2644 (x0, y0)] 

2645 

2646 com = [Path.MOVETO, 

2647 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2648 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2649 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2650 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2651 Path.CLOSEPOLY] 

2652 

2653 return Path(cp, com) 

2654 

2655 @_register_style(_style_list) 

2656 class Sawtooth: 

2657 """A box with a sawtooth outline.""" 

2658 

2659 def __init__(self, pad=0.3, tooth_size=None): 

2660 """ 

2661 Parameters 

2662 ---------- 

2663 pad : float, default: 0.3 

2664 The amount of padding around the original box. 

2665 tooth_size : float, default: *pad*/2 

2666 Size of the sawtooth. 

2667 """ 

2668 self.pad = pad 

2669 self.tooth_size = tooth_size 

2670 

2671 def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size): 

2672 

2673 # padding 

2674 pad = mutation_size * self.pad 

2675 

2676 # size of sawtooth 

2677 if self.tooth_size is None: 

2678 tooth_size = self.pad * .5 * mutation_size 

2679 else: 

2680 tooth_size = self.tooth_size * mutation_size 

2681 

2682 hsz = tooth_size / 2 

2683 width = width + 2 * pad - tooth_size 

2684 height = height + 2 * pad - tooth_size 

2685 

2686 # the sizes of the vertical and horizontal sawtooth are 

2687 # separately adjusted to fit the given box size. 

2688 dsx_n = round((width - tooth_size) / (tooth_size * 2)) * 2 

2689 dsy_n = round((height - tooth_size) / (tooth_size * 2)) * 2 

2690 

2691 x0, y0 = x0 - pad + hsz, y0 - pad + hsz 

2692 x1, y1 = x0 + width, y0 + height 

2693 

2694 xs = [ 

2695 x0, *np.linspace(x0 + hsz, x1 - hsz, 2 * dsx_n + 1), # bottom 

2696 *([x1, x1 + hsz, x1, x1 - hsz] * dsy_n)[:2*dsy_n+2], # right 

2697 x1, *np.linspace(x1 - hsz, x0 + hsz, 2 * dsx_n + 1), # top 

2698 *([x0, x0 - hsz, x0, x0 + hsz] * dsy_n)[:2*dsy_n+2], # left 

2699 ] 

2700 ys = [ 

2701 *([y0, y0 - hsz, y0, y0 + hsz] * dsx_n)[:2*dsx_n+2], # bottom 

2702 y0, *np.linspace(y0 + hsz, y1 - hsz, 2 * dsy_n + 1), # right 

2703 *([y1, y1 + hsz, y1, y1 - hsz] * dsx_n)[:2*dsx_n+2], # top 

2704 y1, *np.linspace(y1 - hsz, y0 + hsz, 2 * dsy_n + 1), # left 

2705 ] 

2706 

2707 return [*zip(xs, ys), (xs[0], ys[0])] 

2708 

2709 def __call__(self, x0, y0, width, height, mutation_size): 

2710 saw_vertices = self._get_sawtooth_vertices(x0, y0, width, 

2711 height, mutation_size) 

2712 return Path(saw_vertices, closed=True) 

2713 

2714 @_register_style(_style_list) 

2715 class Roundtooth(Sawtooth): 

2716 """A box with a rounded sawtooth outline.""" 

2717 

2718 def __call__(self, x0, y0, width, height, mutation_size): 

2719 saw_vertices = self._get_sawtooth_vertices(x0, y0, 

2720 width, height, 

2721 mutation_size) 

2722 # Add a trailing vertex to allow us to close the polygon correctly 

2723 saw_vertices = np.concatenate([saw_vertices, [saw_vertices[0]]]) 

2724 codes = ([Path.MOVETO] + 

2725 [Path.CURVE3, Path.CURVE3] * ((len(saw_vertices)-1)//2) + 

2726 [Path.CLOSEPOLY]) 

2727 return Path(saw_vertices, codes) 

2728 

2729 

2730@_docstring.dedent_interpd 

2731class ConnectionStyle(_Style): 

2732 """ 

2733 `ConnectionStyle` is a container class which defines 

2734 several connectionstyle classes, which is used to create a path 

2735 between two points. These are mainly used with `FancyArrowPatch`. 

2736 

2737 A connectionstyle object can be either created as:: 

2738 

2739 ConnectionStyle.Arc3(rad=0.2) 

2740 

2741 or:: 

2742 

2743 ConnectionStyle("Arc3", rad=0.2) 

2744 

2745 or:: 

2746 

2747 ConnectionStyle("Arc3, rad=0.2") 

2748 

2749 The following classes are defined 

2750 

2751 %(ConnectionStyle:table)s 

2752 

2753 An instance of any connection style class is a callable object, 

2754 whose call signature is:: 

2755 

2756 __call__(self, posA, posB, 

2757 patchA=None, patchB=None, 

2758 shrinkA=2., shrinkB=2.) 

2759 

2760 and it returns a `.Path` instance. *posA* and *posB* are 

2761 tuples of (x, y) coordinates of the two points to be 

2762 connected. *patchA* (or *patchB*) is given, the returned path is 

2763 clipped so that it start (or end) from the boundary of the 

2764 patch. The path is further shrunk by *shrinkA* (or *shrinkB*) 

2765 which is given in points. 

2766 """ 

2767 

2768 _style_list = {} 

2769 

2770 class _Base: 

2771 """ 

2772 A base class for connectionstyle classes. The subclass needs 

2773 to implement a *connect* method whose call signature is:: 

2774 

2775 connect(posA, posB) 

2776 

2777 where posA and posB are tuples of x, y coordinates to be 

2778 connected. The method needs to return a path connecting two 

2779 points. This base class defines a __call__ method, and a few 

2780 helper methods. 

2781 """ 

2782 def _in_patch(self, patch): 

2783 """ 

2784 Return a predicate function testing whether a point *xy* is 

2785 contained in *patch*. 

2786 """ 

2787 return lambda xy: patch.contains( 

2788 SimpleNamespace(x=xy[0], y=xy[1]))[0] 

2789 

2790 def _clip(self, path, in_start, in_stop): 

2791 """ 

2792 Clip *path* at its start by the region where *in_start* returns 

2793 True, and at its stop by the region where *in_stop* returns True. 

2794 

2795 The original path is assumed to start in the *in_start* region and 

2796 to stop in the *in_stop* region. 

2797 """ 

2798 if in_start: 

2799 try: 

2800 _, path = split_path_inout(path, in_start) 

2801 except ValueError: 

2802 pass 

2803 if in_stop: 

2804 try: 

2805 path, _ = split_path_inout(path, in_stop) 

2806 except ValueError: 

2807 pass 

2808 return path 

2809 

2810 def __call__(self, posA, posB, 

2811 shrinkA=2., shrinkB=2., patchA=None, patchB=None): 

2812 """ 

2813 Call the *connect* method to create a path between *posA* and 

2814 *posB*; then clip and shrink the path. 

2815 """ 

2816 path = self.connect(posA, posB) 

2817 path = self._clip( 

2818 path, 

2819 self._in_patch(patchA) if patchA else None, 

2820 self._in_patch(patchB) if patchB else None, 

2821 ) 

2822 path = self._clip( 

2823 path, 

2824 inside_circle(*path.vertices[0], shrinkA) if shrinkA else None, 

2825 inside_circle(*path.vertices[-1], shrinkB) if shrinkB else None 

2826 ) 

2827 return path 

2828 

2829 @_register_style(_style_list) 

2830 class Arc3(_Base): 

2831 """ 

2832 Creates a simple quadratic Bézier curve between two 

2833 points. The curve is created so that the middle control point 

2834 (C1) is located at the same distance from the start (C0) and 

2835 end points(C2) and the distance of the C1 to the line 

2836 connecting C0-C2 is *rad* times the distance of C0-C2. 

2837 """ 

2838 

2839 def __init__(self, rad=0.): 

2840 """ 

2841 Parameters 

2842 ---------- 

2843 rad : float 

2844 Curvature of the curve. 

2845 """ 

2846 self.rad = rad 

2847 

2848 def connect(self, posA, posB): 

2849 x1, y1 = posA 

2850 x2, y2 = posB 

2851 x12, y12 = (x1 + x2) / 2., (y1 + y2) / 2. 

2852 dx, dy = x2 - x1, y2 - y1 

2853 

2854 f = self.rad 

2855 

2856 cx, cy = x12 + f * dy, y12 - f * dx 

2857 

2858 vertices = [(x1, y1), 

2859 (cx, cy), 

2860 (x2, y2)] 

2861 codes = [Path.MOVETO, 

2862 Path.CURVE3, 

2863 Path.CURVE3] 

2864 

2865 return Path(vertices, codes) 

2866 

2867 @_register_style(_style_list) 

2868 class Angle3(_Base): 

2869 """ 

2870 Creates a simple quadratic Bézier curve between two points. The middle 

2871 control point is placed at the intersecting point of two lines which 

2872 cross the start and end point, and have a slope of *angleA* and 

2873 *angleB*, respectively. 

2874 """ 

2875 

2876 def __init__(self, angleA=90, angleB=0): 

2877 """ 

2878 Parameters 

2879 ---------- 

2880 angleA : float 

2881 Starting angle of the path. 

2882 

2883 angleB : float 

2884 Ending angle of the path. 

2885 """ 

2886 

2887 self.angleA = angleA 

2888 self.angleB = angleB 

2889 

2890 def connect(self, posA, posB): 

2891 x1, y1 = posA 

2892 x2, y2 = posB 

2893 

2894 cosA = math.cos(math.radians(self.angleA)) 

2895 sinA = math.sin(math.radians(self.angleA)) 

2896 cosB = math.cos(math.radians(self.angleB)) 

2897 sinB = math.sin(math.radians(self.angleB)) 

2898 

2899 cx, cy = get_intersection(x1, y1, cosA, sinA, 

2900 x2, y2, cosB, sinB) 

2901 

2902 vertices = [(x1, y1), (cx, cy), (x2, y2)] 

2903 codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3] 

2904 

2905 return Path(vertices, codes) 

2906 

2907 @_register_style(_style_list) 

2908 class Angle(_Base): 

2909 """ 

2910 Creates a piecewise continuous quadratic Bézier path between two 

2911 points. The path has a one passing-through point placed at the 

2912 intersecting point of two lines which cross the start and end point, 

2913 and have a slope of *angleA* and *angleB*, respectively. 

2914 The connecting edges are rounded with *rad*. 

2915 """ 

2916 

2917 def __init__(self, angleA=90, angleB=0, rad=0.): 

2918 """ 

2919 Parameters 

2920 ---------- 

2921 angleA : float 

2922 Starting angle of the path. 

2923 

2924 angleB : float 

2925 Ending angle of the path. 

2926 

2927 rad : float 

2928 Rounding radius of the edge. 

2929 """ 

2930 

2931 self.angleA = angleA 

2932 self.angleB = angleB 

2933 

2934 self.rad = rad 

2935 

2936 def connect(self, posA, posB): 

2937 x1, y1 = posA 

2938 x2, y2 = posB 

2939 

2940 cosA = math.cos(math.radians(self.angleA)) 

2941 sinA = math.sin(math.radians(self.angleA)) 

2942 cosB = math.cos(math.radians(self.angleB)) 

2943 sinB = math.sin(math.radians(self.angleB)) 

2944 

2945 cx, cy = get_intersection(x1, y1, cosA, sinA, 

2946 x2, y2, cosB, sinB) 

2947 

2948 vertices = [(x1, y1)] 

2949 codes = [Path.MOVETO] 

2950 

2951 if self.rad == 0.: 

2952 vertices.append((cx, cy)) 

2953 codes.append(Path.LINETO) 

2954 else: 

2955 dx1, dy1 = x1 - cx, y1 - cy 

2956 d1 = np.hypot(dx1, dy1) 

2957 f1 = self.rad / d1 

2958 dx2, dy2 = x2 - cx, y2 - cy 

2959 d2 = np.hypot(dx2, dy2) 

2960 f2 = self.rad / d2 

2961 vertices.extend([(cx + dx1 * f1, cy + dy1 * f1), 

2962 (cx, cy), 

2963 (cx + dx2 * f2, cy + dy2 * f2)]) 

2964 codes.extend([Path.LINETO, Path.CURVE3, Path.CURVE3]) 

2965 

2966 vertices.append((x2, y2)) 

2967 codes.append(Path.LINETO) 

2968 

2969 return Path(vertices, codes) 

2970 

2971 @_register_style(_style_list) 

2972 class Arc(_Base): 

2973 """ 

2974 Creates a piecewise continuous quadratic Bézier path between two 

2975 points. The path can have two passing-through points, a 

2976 point placed at the distance of *armA* and angle of *angleA* from 

2977 point A, another point with respect to point B. The edges are 

2978 rounded with *rad*. 

2979 """ 

2980 

2981 def __init__(self, angleA=0, angleB=0, armA=None, armB=None, rad=0.): 

2982 """ 

2983 Parameters 

2984 ---------- 

2985 angleA : float 

2986 Starting angle of the path. 

2987 

2988 angleB : float 

2989 Ending angle of the path. 

2990 

2991 armA : float or None 

2992 Length of the starting arm. 

2993 

2994 armB : float or None 

2995 Length of the ending arm. 

2996 

2997 rad : float 

2998 Rounding radius of the edges. 

2999 """ 

3000 

3001 self.angleA = angleA 

3002 self.angleB = angleB 

3003 self.armA = armA 

3004 self.armB = armB 

3005 

3006 self.rad = rad 

3007 

3008 def connect(self, posA, posB): 

3009 x1, y1 = posA 

3010 x2, y2 = posB 

3011 

3012 vertices = [(x1, y1)] 

3013 rounded = [] 

3014 codes = [Path.MOVETO] 

3015 

3016 if self.armA: 

3017 cosA = math.cos(math.radians(self.angleA)) 

3018 sinA = math.sin(math.radians(self.angleA)) 

3019 # x_armA, y_armB 

3020 d = self.armA - self.rad 

3021 rounded.append((x1 + d * cosA, y1 + d * sinA)) 

3022 d = self.armA 

3023 rounded.append((x1 + d * cosA, y1 + d * sinA)) 

3024 

3025 if self.armB: 

3026 cosB = math.cos(math.radians(self.angleB)) 

3027 sinB = math.sin(math.radians(self.angleB)) 

3028 x_armB, y_armB = x2 + self.armB * cosB, y2 + self.armB * sinB 

3029 

3030 if rounded: 

3031 xp, yp = rounded[-1] 

3032 dx, dy = x_armB - xp, y_armB - yp 

3033 dd = (dx * dx + dy * dy) ** .5 

3034 

3035 rounded.append((xp + self.rad * dx / dd, 

3036 yp + self.rad * dy / dd)) 

3037 vertices.extend(rounded) 

3038 codes.extend([Path.LINETO, 

3039 Path.CURVE3, 

3040 Path.CURVE3]) 

3041 else: 

3042 xp, yp = vertices[-1] 

3043 dx, dy = x_armB - xp, y_armB - yp 

3044 dd = (dx * dx + dy * dy) ** .5 

3045 

3046 d = dd - self.rad 

3047 rounded = [(xp + d * dx / dd, yp + d * dy / dd), 

3048 (x_armB, y_armB)] 

3049 

3050 if rounded: 

3051 xp, yp = rounded[-1] 

3052 dx, dy = x2 - xp, y2 - yp 

3053 dd = (dx * dx + dy * dy) ** .5 

3054 

3055 rounded.append((xp + self.rad * dx / dd, 

3056 yp + self.rad * dy / dd)) 

3057 vertices.extend(rounded) 

3058 codes.extend([Path.LINETO, 

3059 Path.CURVE3, 

3060 Path.CURVE3]) 

3061 

3062 vertices.append((x2, y2)) 

3063 codes.append(Path.LINETO) 

3064 

3065 return Path(vertices, codes) 

3066 

3067 @_register_style(_style_list) 

3068 class Bar(_Base): 

3069 """ 

3070 A line with *angle* between A and B with *armA* and *armB*. One of the 

3071 arms is extended so that they are connected in a right angle. The 

3072 length of *armA* is determined by (*armA* + *fraction* x AB distance). 

3073 Same for *armB*. 

3074 """ 

3075 

3076 def __init__(self, armA=0., armB=0., fraction=0.3, angle=None): 

3077 """ 

3078 Parameters 

3079 ---------- 

3080 armA : float 

3081 Minimum length of armA. 

3082 

3083 armB : float 

3084 Minimum length of armB. 

3085 

3086 fraction : float 

3087 A fraction of the distance between two points that will be 

3088 added to armA and armB. 

3089 

3090 angle : float or None 

3091 Angle of the connecting line (if None, parallel to A and B). 

3092 """ 

3093 self.armA = armA 

3094 self.armB = armB 

3095 self.fraction = fraction 

3096 self.angle = angle 

3097 

3098 def connect(self, posA, posB): 

3099 x1, y1 = posA 

3100 x20, y20 = x2, y2 = posB 

3101 

3102 theta1 = math.atan2(y2 - y1, x2 - x1) 

3103 dx, dy = x2 - x1, y2 - y1 

3104 dd = (dx * dx + dy * dy) ** .5 

3105 ddx, ddy = dx / dd, dy / dd 

3106 

3107 armA, armB = self.armA, self.armB 

3108 

3109 if self.angle is not None: 

3110 theta0 = np.deg2rad(self.angle) 

3111 dtheta = theta1 - theta0 

3112 dl = dd * math.sin(dtheta) 

3113 dL = dd * math.cos(dtheta) 

3114 x2, y2 = x1 + dL * math.cos(theta0), y1 + dL * math.sin(theta0) 

3115 armB = armB - dl 

3116 

3117 # update 

3118 dx, dy = x2 - x1, y2 - y1 

3119 dd2 = (dx * dx + dy * dy) ** .5 

3120 ddx, ddy = dx / dd2, dy / dd2 

3121 

3122 arm = max(armA, armB) 

3123 f = self.fraction * dd + arm 

3124 

3125 cx1, cy1 = x1 + f * ddy, y1 - f * ddx 

3126 cx2, cy2 = x2 + f * ddy, y2 - f * ddx 

3127 

3128 vertices = [(x1, y1), 

3129 (cx1, cy1), 

3130 (cx2, cy2), 

3131 (x20, y20)] 

3132 codes = [Path.MOVETO, 

3133 Path.LINETO, 

3134 Path.LINETO, 

3135 Path.LINETO] 

3136 

3137 return Path(vertices, codes) 

3138 

3139 

3140def _point_along_a_line(x0, y0, x1, y1, d): 

3141 """ 

3142 Return the point on the line connecting (*x0*, *y0*) -- (*x1*, *y1*) whose 

3143 distance from (*x0*, *y0*) is *d*. 

3144 """ 

3145 dx, dy = x0 - x1, y0 - y1 

3146 ff = d / (dx * dx + dy * dy) ** .5 

3147 x2, y2 = x0 - ff * dx, y0 - ff * dy 

3148 

3149 return x2, y2 

3150 

3151 

3152@_docstring.dedent_interpd 

3153class ArrowStyle(_Style): 

3154 """ 

3155 `ArrowStyle` is a container class which defines several 

3156 arrowstyle classes, which is used to create an arrow path along a 

3157 given path. These are mainly used with `FancyArrowPatch`. 

3158 

3159 An arrowstyle object can be either created as:: 

3160 

3161 ArrowStyle.Fancy(head_length=.4, head_width=.4, tail_width=.4) 

3162 

3163 or:: 

3164 

3165 ArrowStyle("Fancy", head_length=.4, head_width=.4, tail_width=.4) 

3166 

3167 or:: 

3168 

3169 ArrowStyle("Fancy, head_length=.4, head_width=.4, tail_width=.4") 

3170 

3171 The following classes are defined 

3172 

3173 %(ArrowStyle:table)s 

3174 

3175 For an overview of the visual appearance, see 

3176 :doc:`/gallery/text_labels_and_annotations/fancyarrow_demo`. 

3177 

3178 An instance of any arrow style class is a callable object, 

3179 whose call signature is:: 

3180 

3181 __call__(self, path, mutation_size, linewidth, aspect_ratio=1.) 

3182 

3183 and it returns a tuple of a `.Path` instance and a boolean 

3184 value. *path* is a `.Path` instance along which the arrow 

3185 will be drawn. *mutation_size* and *aspect_ratio* have the same 

3186 meaning as in `BoxStyle`. *linewidth* is a line width to be 

3187 stroked. This is meant to be used to correct the location of the 

3188 head so that it does not overshoot the destination point, but not all 

3189 classes support it. 

3190 

3191 Notes 

3192 ----- 

3193 *angleA* and *angleB* specify the orientation of the bracket, as either a 

3194 clockwise or counterclockwise angle depending on the arrow type. 0 degrees 

3195 means perpendicular to the line connecting the arrow's head and tail. 

3196 

3197 .. plot:: gallery/text_labels_and_annotations/angles_on_bracket_arrows.py 

3198 """ 

3199 

3200 _style_list = {} 

3201 

3202 class _Base: 

3203 """ 

3204 Arrow Transmuter Base class 

3205 

3206 ArrowTransmuterBase and its derivatives are used to make a fancy 

3207 arrow around a given path. The __call__ method returns a path 

3208 (which will be used to create a PathPatch instance) and a boolean 

3209 value indicating the path is open therefore is not fillable. This 

3210 class is not an artist and actual drawing of the fancy arrow is 

3211 done by the FancyArrowPatch class. 

3212 """ 

3213 

3214 # The derived classes are required to be able to be initialized 

3215 # w/o arguments, i.e., all its argument (except self) must have 

3216 # the default values. 

3217 

3218 @staticmethod 

3219 def ensure_quadratic_bezier(path): 

3220 """ 

3221 Some ArrowStyle classes only works with a simple quadratic 

3222 Bézier curve (created with `.ConnectionStyle.Arc3` or 

3223 `.ConnectionStyle.Angle3`). This static method checks if the 

3224 provided path is a simple quadratic Bézier curve and returns its 

3225 control points if true. 

3226 """ 

3227 segments = list(path.iter_segments()) 

3228 if (len(segments) != 2 or segments[0][1] != Path.MOVETO or 

3229 segments[1][1] != Path.CURVE3): 

3230 raise ValueError( 

3231 "'path' is not a valid quadratic Bezier curve") 

3232 return [*segments[0][0], *segments[1][0]] 

3233 

3234 def transmute(self, path, mutation_size, linewidth): 

3235 """ 

3236 The transmute method is the very core of the ArrowStyle class and 

3237 must be overridden in the subclasses. It receives the *path* 

3238 object along which the arrow will be drawn, and the 

3239 *mutation_size*, with which the arrow head etc. will be scaled. 

3240 The *linewidth* may be used to adjust the path so that it does not 

3241 pass beyond the given points. It returns a tuple of a `.Path` 

3242 instance and a boolean. The boolean value indicate whether the 

3243 path can be filled or not. The return value can also be a list of 

3244 paths and list of booleans of the same length. 

3245 """ 

3246 raise NotImplementedError('Derived must override') 

3247 

3248 def __call__(self, path, mutation_size, linewidth, 

3249 aspect_ratio=1.): 

3250 """ 

3251 The __call__ method is a thin wrapper around the transmute method 

3252 and takes care of the aspect ratio. 

3253 """ 

3254 

3255 if aspect_ratio is not None: 

3256 # Squeeze the given height by the aspect_ratio 

3257 vertices = path.vertices / [1, aspect_ratio] 

3258 path_shrunk = Path(vertices, path.codes) 

3259 # call transmute method with squeezed height. 

3260 path_mutated, fillable = self.transmute(path_shrunk, 

3261 mutation_size, 

3262 linewidth) 

3263 if np.iterable(fillable): 

3264 # Restore the height 

3265 path_list = [Path(p.vertices * [1, aspect_ratio], p.codes) 

3266 for p in path_mutated] 

3267 return path_list, fillable 

3268 else: 

3269 return path_mutated, fillable 

3270 else: 

3271 return self.transmute(path, mutation_size, linewidth) 

3272 

3273 class _Curve(_Base): 

3274 """ 

3275 A simple arrow which will work with any path instance. The 

3276 returned path is the concatenation of the original path, and at 

3277 most two paths representing the arrow head or bracket at the start 

3278 point and at the end point. The arrow heads can be either open 

3279 or closed. 

3280 """ 

3281 

3282 arrow = "-" 

3283 fillbegin = fillend = False # Whether arrows are filled. 

3284 

3285 def __init__(self, head_length=.4, head_width=.2, widthA=1., widthB=1., 

3286 lengthA=0.2, lengthB=0.2, angleA=0, angleB=0, scaleA=None, 

3287 scaleB=None): 

3288 """ 

3289 Parameters 

3290 ---------- 

3291 head_length : float, default: 0.4 

3292 Length of the arrow head, relative to *mutation_size*. 

3293 head_width : float, default: 0.2 

3294 Width of the arrow head, relative to *mutation_size*. 

3295 widthA, widthB : float, default: 1.0 

3296 Width of the bracket. 

3297 lengthA, lengthB : float, default: 0.2 

3298 Length of the bracket. 

3299 angleA, angleB : float, default: 0 

3300 Orientation of the bracket, as a counterclockwise angle. 

3301 0 degrees means perpendicular to the line. 

3302 scaleA, scaleB : float, default: *mutation_size* 

3303 The scale of the brackets. 

3304 """ 

3305 

3306 self.head_length, self.head_width = head_length, head_width 

3307 self.widthA, self.widthB = widthA, widthB 

3308 self.lengthA, self.lengthB = lengthA, lengthB 

3309 self.angleA, self.angleB = angleA, angleB 

3310 self.scaleA, self.scaleB = scaleA, scaleB 

3311 

3312 self._beginarrow_head = False 

3313 self._beginarrow_bracket = False 

3314 self._endarrow_head = False 

3315 self._endarrow_bracket = False 

3316 

3317 if "-" not in self.arrow: 

3318 raise ValueError("arrow must have the '-' between " 

3319 "the two heads") 

3320 

3321 beginarrow, endarrow = self.arrow.split("-", 1) 

3322 

3323 if beginarrow == "<": 

3324 self._beginarrow_head = True 

3325 self._beginarrow_bracket = False 

3326 elif beginarrow == "<|": 

3327 self._beginarrow_head = True 

3328 self._beginarrow_bracket = False 

3329 self.fillbegin = True 

3330 elif beginarrow in ("]", "|"): 

3331 self._beginarrow_head = False 

3332 self._beginarrow_bracket = True 

3333 

3334 if endarrow == ">": 

3335 self._endarrow_head = True 

3336 self._endarrow_bracket = False 

3337 elif endarrow == "|>": 

3338 self._endarrow_head = True 

3339 self._endarrow_bracket = False 

3340 self.fillend = True 

3341 elif endarrow in ("[", "|"): 

3342 self._endarrow_head = False 

3343 self._endarrow_bracket = True 

3344 

3345 super().__init__() 

3346 

3347 def _get_arrow_wedge(self, x0, y0, x1, y1, 

3348 head_dist, cos_t, sin_t, linewidth): 

3349 """ 

3350 Return the paths for arrow heads. Since arrow lines are 

3351 drawn with capstyle=projected, The arrow goes beyond the 

3352 desired point. This method also returns the amount of the path 

3353 to be shrunken so that it does not overshoot. 

3354 """ 

3355 

3356 # arrow from x0, y0 to x1, y1 

3357 dx, dy = x0 - x1, y0 - y1 

3358 

3359 cp_distance = np.hypot(dx, dy) 

3360 

3361 # pad_projected : amount of pad to account the 

3362 # overshooting of the projection of the wedge 

3363 pad_projected = (.5 * linewidth / sin_t) 

3364 

3365 # Account for division by zero 

3366 if cp_distance == 0: 

3367 cp_distance = 1 

3368 

3369 # apply pad for projected edge 

3370 ddx = pad_projected * dx / cp_distance 

3371 ddy = pad_projected * dy / cp_distance 

3372 

3373 # offset for arrow wedge 

3374 dx = dx / cp_distance * head_dist 

3375 dy = dy / cp_distance * head_dist 

3376 

3377 dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy 

3378 dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy 

3379 

3380 vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1), 

3381 (x1 + ddx, y1 + ddy), 

3382 (x1 + ddx + dx2, y1 + ddy + dy2)] 

3383 codes_arrow = [Path.MOVETO, 

3384 Path.LINETO, 

3385 Path.LINETO] 

3386 

3387 return vertices_arrow, codes_arrow, ddx, ddy 

3388 

3389 def _get_bracket(self, x0, y0, 

3390 x1, y1, width, length, angle): 

3391 

3392 cos_t, sin_t = get_cos_sin(x1, y1, x0, y0) 

3393 

3394 # arrow from x0, y0 to x1, y1 

3395 from matplotlib.bezier import get_normal_points 

3396 x1, y1, x2, y2 = get_normal_points(x0, y0, cos_t, sin_t, width) 

3397 

3398 dx, dy = length * cos_t, length * sin_t 

3399 

3400 vertices_arrow = [(x1 + dx, y1 + dy), 

3401 (x1, y1), 

3402 (x2, y2), 

3403 (x2 + dx, y2 + dy)] 

3404 codes_arrow = [Path.MOVETO, 

3405 Path.LINETO, 

3406 Path.LINETO, 

3407 Path.LINETO] 

3408 

3409 if angle: 

3410 trans = transforms.Affine2D().rotate_deg_around(x0, y0, angle) 

3411 vertices_arrow = trans.transform(vertices_arrow) 

3412 

3413 return vertices_arrow, codes_arrow 

3414 

3415 def transmute(self, path, mutation_size, linewidth): 

3416 # docstring inherited 

3417 if self._beginarrow_head or self._endarrow_head: 

3418 head_length = self.head_length * mutation_size 

3419 head_width = self.head_width * mutation_size 

3420 head_dist = np.hypot(head_length, head_width) 

3421 cos_t, sin_t = head_length / head_dist, head_width / head_dist 

3422 

3423 scaleA = mutation_size if self.scaleA is None else self.scaleA 

3424 scaleB = mutation_size if self.scaleB is None else self.scaleB 

3425 

3426 # begin arrow 

3427 x0, y0 = path.vertices[0] 

3428 x1, y1 = path.vertices[1] 

3429 

3430 # If there is no room for an arrow and a line, then skip the arrow 

3431 has_begin_arrow = self._beginarrow_head and (x0, y0) != (x1, y1) 

3432 verticesA, codesA, ddxA, ddyA = ( 

3433 self._get_arrow_wedge(x1, y1, x0, y0, 

3434 head_dist, cos_t, sin_t, linewidth) 

3435 if has_begin_arrow 

3436 else ([], [], 0, 0) 

3437 ) 

3438 

3439 # end arrow 

3440 x2, y2 = path.vertices[-2] 

3441 x3, y3 = path.vertices[-1] 

3442 

3443 # If there is no room for an arrow and a line, then skip the arrow 

3444 has_end_arrow = self._endarrow_head and (x2, y2) != (x3, y3) 

3445 verticesB, codesB, ddxB, ddyB = ( 

3446 self._get_arrow_wedge(x2, y2, x3, y3, 

3447 head_dist, cos_t, sin_t, linewidth) 

3448 if has_end_arrow 

3449 else ([], [], 0, 0) 

3450 ) 

3451 

3452 # This simple code will not work if ddx, ddy is greater than the 

3453 # separation between vertices. 

3454 paths = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)], 

3455 path.vertices[1:-1], 

3456 [(x3 + ddxB, y3 + ddyB)]]), 

3457 path.codes)] 

3458 fills = [False] 

3459 

3460 if has_begin_arrow: 

3461 if self.fillbegin: 

3462 paths.append( 

3463 Path([*verticesA, (0, 0)], [*codesA, Path.CLOSEPOLY])) 

3464 fills.append(True) 

3465 else: 

3466 paths.append(Path(verticesA, codesA)) 

3467 fills.append(False) 

3468 elif self._beginarrow_bracket: 

3469 x0, y0 = path.vertices[0] 

3470 x1, y1 = path.vertices[1] 

3471 verticesA, codesA = self._get_bracket(x0, y0, x1, y1, 

3472 self.widthA * scaleA, 

3473 self.lengthA * scaleA, 

3474 self.angleA) 

3475 

3476 paths.append(Path(verticesA, codesA)) 

3477 fills.append(False) 

3478 

3479 if has_end_arrow: 

3480 if self.fillend: 

3481 fills.append(True) 

3482 paths.append( 

3483 Path([*verticesB, (0, 0)], [*codesB, Path.CLOSEPOLY])) 

3484 else: 

3485 fills.append(False) 

3486 paths.append(Path(verticesB, codesB)) 

3487 elif self._endarrow_bracket: 

3488 x0, y0 = path.vertices[-1] 

3489 x1, y1 = path.vertices[-2] 

3490 verticesB, codesB = self._get_bracket(x0, y0, x1, y1, 

3491 self.widthB * scaleB, 

3492 self.lengthB * scaleB, 

3493 self.angleB) 

3494 

3495 paths.append(Path(verticesB, codesB)) 

3496 fills.append(False) 

3497 

3498 return paths, fills 

3499 

3500 @_register_style(_style_list, name="-") 

3501 class Curve(_Curve): 

3502 """A simple curve without any arrow head.""" 

3503 

3504 def __init__(self): # hide head_length, head_width 

3505 # These attributes (whose values come from backcompat) only matter 

3506 # if someone modifies beginarrow/etc. on an ArrowStyle instance. 

3507 super().__init__(head_length=.2, head_width=.1) 

3508 

3509 @_register_style(_style_list, name="<-") 

3510 class CurveA(_Curve): 

3511 """An arrow with a head at its start point.""" 

3512 arrow = "<-" 

3513 

3514 @_register_style(_style_list, name="->") 

3515 class CurveB(_Curve): 

3516 """An arrow with a head at its end point.""" 

3517 arrow = "->" 

3518 

3519 @_register_style(_style_list, name="<->") 

3520 class CurveAB(_Curve): 

3521 """An arrow with heads both at the start and the end point.""" 

3522 arrow = "<->" 

3523 

3524 @_register_style(_style_list, name="<|-") 

3525 class CurveFilledA(_Curve): 

3526 """An arrow with filled triangle head at the start.""" 

3527 arrow = "<|-" 

3528 

3529 @_register_style(_style_list, name="-|>") 

3530 class CurveFilledB(_Curve): 

3531 """An arrow with filled triangle head at the end.""" 

3532 arrow = "-|>" 

3533 

3534 @_register_style(_style_list, name="<|-|>") 

3535 class CurveFilledAB(_Curve): 

3536 """An arrow with filled triangle heads at both ends.""" 

3537 arrow = "<|-|>" 

3538 

3539 @_register_style(_style_list, name="]-") 

3540 class BracketA(_Curve): 

3541 """An arrow with an outward square bracket at its start.""" 

3542 arrow = "]-" 

3543 

3544 def __init__(self, widthA=1., lengthA=0.2, angleA=0): 

3545 """ 

3546 Parameters 

3547 ---------- 

3548 widthA : float, default: 1.0 

3549 Width of the bracket. 

3550 lengthA : float, default: 0.2 

3551 Length of the bracket. 

3552 angleA : float, default: 0 degrees 

3553 Orientation of the bracket, as a counterclockwise angle. 

3554 0 degrees means perpendicular to the line. 

3555 """ 

3556 super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA) 

3557 

3558 @_register_style(_style_list, name="-[") 

3559 class BracketB(_Curve): 

3560 """An arrow with an outward square bracket at its end.""" 

3561 arrow = "-[" 

3562 

3563 def __init__(self, widthB=1., lengthB=0.2, angleB=0): 

3564 """ 

3565 Parameters 

3566 ---------- 

3567 widthB : float, default: 1.0 

3568 Width of the bracket. 

3569 lengthB : float, default: 0.2 

3570 Length of the bracket. 

3571 angleB : float, default: 0 degrees 

3572 Orientation of the bracket, as a counterclockwise angle. 

3573 0 degrees means perpendicular to the line. 

3574 """ 

3575 super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB) 

3576 

3577 @_register_style(_style_list, name="]-[") 

3578 class BracketAB(_Curve): 

3579 """An arrow with outward square brackets at both ends.""" 

3580 arrow = "]-[" 

3581 

3582 def __init__(self, 

3583 widthA=1., lengthA=0.2, angleA=0, 

3584 widthB=1., lengthB=0.2, angleB=0): 

3585 """ 

3586 Parameters 

3587 ---------- 

3588 widthA, widthB : float, default: 1.0 

3589 Width of the bracket. 

3590 lengthA, lengthB : float, default: 0.2 

3591 Length of the bracket. 

3592 angleA, angleB : float, default: 0 degrees 

3593 Orientation of the bracket, as a counterclockwise angle. 

3594 0 degrees means perpendicular to the line. 

3595 """ 

3596 super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA, 

3597 widthB=widthB, lengthB=lengthB, angleB=angleB) 

3598 

3599 @_register_style(_style_list, name="|-|") 

3600 class BarAB(_Curve): 

3601 """An arrow with vertical bars ``|`` at both ends.""" 

3602 arrow = "|-|" 

3603 

3604 def __init__(self, widthA=1., angleA=0, widthB=1., angleB=0): 

3605 """ 

3606 Parameters 

3607 ---------- 

3608 widthA, widthB : float, default: 1.0 

3609 Width of the bracket. 

3610 angleA, angleB : float, default: 0 degrees 

3611 Orientation of the bracket, as a counterclockwise angle. 

3612 0 degrees means perpendicular to the line. 

3613 """ 

3614 super().__init__(widthA=widthA, lengthA=0, angleA=angleA, 

3615 widthB=widthB, lengthB=0, angleB=angleB) 

3616 

3617 @_register_style(_style_list, name=']->') 

3618 class BracketCurve(_Curve): 

3619 """ 

3620 An arrow with an outward square bracket at its start and a head at 

3621 the end. 

3622 """ 

3623 arrow = "]->" 

3624 

3625 def __init__(self, widthA=1., lengthA=0.2, angleA=None): 

3626 """ 

3627 Parameters 

3628 ---------- 

3629 widthA : float, default: 1.0 

3630 Width of the bracket. 

3631 lengthA : float, default: 0.2 

3632 Length of the bracket. 

3633 angleA : float, default: 0 degrees 

3634 Orientation of the bracket, as a counterclockwise angle. 

3635 0 degrees means perpendicular to the line. 

3636 """ 

3637 super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA) 

3638 

3639 @_register_style(_style_list, name='<-[') 

3640 class CurveBracket(_Curve): 

3641 """ 

3642 An arrow with an outward square bracket at its end and a head at 

3643 the start. 

3644 """ 

3645 arrow = "<-[" 

3646 

3647 def __init__(self, widthB=1., lengthB=0.2, angleB=None): 

3648 """ 

3649 Parameters 

3650 ---------- 

3651 widthB : float, default: 1.0 

3652 Width of the bracket. 

3653 lengthB : float, default: 0.2 

3654 Length of the bracket. 

3655 angleB : float, default: 0 degrees 

3656 Orientation of the bracket, as a counterclockwise angle. 

3657 0 degrees means perpendicular to the line. 

3658 """ 

3659 super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB) 

3660 

3661 @_register_style(_style_list) 

3662 class Simple(_Base): 

3663 """A simple arrow. Only works with a quadratic Bézier curve.""" 

3664 

3665 def __init__(self, head_length=.5, head_width=.5, tail_width=.2): 

3666 """ 

3667 Parameters 

3668 ---------- 

3669 head_length : float, default: 0.5 

3670 Length of the arrow head. 

3671 

3672 head_width : float, default: 0.5 

3673 Width of the arrow head. 

3674 

3675 tail_width : float, default: 0.2 

3676 Width of the arrow tail. 

3677 """ 

3678 self.head_length, self.head_width, self.tail_width = \ 

3679 head_length, head_width, tail_width 

3680 super().__init__() 

3681 

3682 def transmute(self, path, mutation_size, linewidth): 

3683 # docstring inherited 

3684 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3685 

3686 # divide the path into a head and a tail 

3687 head_length = self.head_length * mutation_size 

3688 in_f = inside_circle(x2, y2, head_length) 

3689 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3690 

3691 try: 

3692 arrow_out, arrow_in = \ 

3693 split_bezier_intersecting_with_closedpath(arrow_path, in_f) 

3694 except NonIntersectingPathException: 

3695 # if this happens, make a straight line of the head_length 

3696 # long. 

3697 x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) 

3698 x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2) 

3699 arrow_in = [(x0, y0), (x1n, y1n), (x2, y2)] 

3700 arrow_out = None 

3701 

3702 # head 

3703 head_width = self.head_width * mutation_size 

3704 head_left, head_right = make_wedged_bezier2(arrow_in, 

3705 head_width / 2., wm=.5) 

3706 

3707 # tail 

3708 if arrow_out is not None: 

3709 tail_width = self.tail_width * mutation_size 

3710 tail_left, tail_right = get_parallels(arrow_out, 

3711 tail_width / 2.) 

3712 

3713 patch_path = [(Path.MOVETO, tail_right[0]), 

3714 (Path.CURVE3, tail_right[1]), 

3715 (Path.CURVE3, tail_right[2]), 

3716 (Path.LINETO, head_right[0]), 

3717 (Path.CURVE3, head_right[1]), 

3718 (Path.CURVE3, head_right[2]), 

3719 (Path.CURVE3, head_left[1]), 

3720 (Path.CURVE3, head_left[0]), 

3721 (Path.LINETO, tail_left[2]), 

3722 (Path.CURVE3, tail_left[1]), 

3723 (Path.CURVE3, tail_left[0]), 

3724 (Path.LINETO, tail_right[0]), 

3725 (Path.CLOSEPOLY, tail_right[0]), 

3726 ] 

3727 else: 

3728 patch_path = [(Path.MOVETO, head_right[0]), 

3729 (Path.CURVE3, head_right[1]), 

3730 (Path.CURVE3, head_right[2]), 

3731 (Path.CURVE3, head_left[1]), 

3732 (Path.CURVE3, head_left[0]), 

3733 (Path.CLOSEPOLY, head_left[0]), 

3734 ] 

3735 

3736 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3737 

3738 return path, True 

3739 

3740 @_register_style(_style_list) 

3741 class Fancy(_Base): 

3742 """A fancy arrow. Only works with a quadratic Bézier curve.""" 

3743 

3744 def __init__(self, head_length=.4, head_width=.4, tail_width=.4): 

3745 """ 

3746 Parameters 

3747 ---------- 

3748 head_length : float, default: 0.4 

3749 Length of the arrow head. 

3750 

3751 head_width : float, default: 0.4 

3752 Width of the arrow head. 

3753 

3754 tail_width : float, default: 0.4 

3755 Width of the arrow tail. 

3756 """ 

3757 self.head_length, self.head_width, self.tail_width = \ 

3758 head_length, head_width, tail_width 

3759 super().__init__() 

3760 

3761 def transmute(self, path, mutation_size, linewidth): 

3762 # docstring inherited 

3763 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3764 

3765 # divide the path into a head and a tail 

3766 head_length = self.head_length * mutation_size 

3767 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3768 

3769 # path for head 

3770 in_f = inside_circle(x2, y2, head_length) 

3771 try: 

3772 path_out, path_in = split_bezier_intersecting_with_closedpath( 

3773 arrow_path, in_f) 

3774 except NonIntersectingPathException: 

3775 # if this happens, make a straight line of the head_length 

3776 # long. 

3777 x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) 

3778 x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2) 

3779 arrow_path = [(x0, y0), (x1n, y1n), (x2, y2)] 

3780 path_head = arrow_path 

3781 else: 

3782 path_head = path_in 

3783 

3784 # path for head 

3785 in_f = inside_circle(x2, y2, head_length * .8) 

3786 path_out, path_in = split_bezier_intersecting_with_closedpath( 

3787 arrow_path, in_f) 

3788 path_tail = path_out 

3789 

3790 # head 

3791 head_width = self.head_width * mutation_size 

3792 head_l, head_r = make_wedged_bezier2(path_head, 

3793 head_width / 2., 

3794 wm=.6) 

3795 

3796 # tail 

3797 tail_width = self.tail_width * mutation_size 

3798 tail_left, tail_right = make_wedged_bezier2(path_tail, 

3799 tail_width * .5, 

3800 w1=1., wm=0.6, w2=0.3) 

3801 

3802 # path for head 

3803 in_f = inside_circle(x0, y0, tail_width * .3) 

3804 path_in, path_out = split_bezier_intersecting_with_closedpath( 

3805 arrow_path, in_f) 

3806 tail_start = path_in[-1] 

3807 

3808 head_right, head_left = head_r, head_l 

3809 patch_path = [(Path.MOVETO, tail_start), 

3810 (Path.LINETO, tail_right[0]), 

3811 (Path.CURVE3, tail_right[1]), 

3812 (Path.CURVE3, tail_right[2]), 

3813 (Path.LINETO, head_right[0]), 

3814 (Path.CURVE3, head_right[1]), 

3815 (Path.CURVE3, head_right[2]), 

3816 (Path.CURVE3, head_left[1]), 

3817 (Path.CURVE3, head_left[0]), 

3818 (Path.LINETO, tail_left[2]), 

3819 (Path.CURVE3, tail_left[1]), 

3820 (Path.CURVE3, tail_left[0]), 

3821 (Path.LINETO, tail_start), 

3822 (Path.CLOSEPOLY, tail_start), 

3823 ] 

3824 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3825 

3826 return path, True 

3827 

3828 @_register_style(_style_list) 

3829 class Wedge(_Base): 

3830 """ 

3831 Wedge(?) shape. Only works with a quadratic Bézier curve. The 

3832 start point has a width of the *tail_width* and the end point has a 

3833 width of 0. At the middle, the width is *shrink_factor*x*tail_width*. 

3834 """ 

3835 

3836 def __init__(self, tail_width=.3, shrink_factor=0.5): 

3837 """ 

3838 Parameters 

3839 ---------- 

3840 tail_width : float, default: 0.3 

3841 Width of the tail. 

3842 

3843 shrink_factor : float, default: 0.5 

3844 Fraction of the arrow width at the middle point. 

3845 """ 

3846 self.tail_width = tail_width 

3847 self.shrink_factor = shrink_factor 

3848 super().__init__() 

3849 

3850 def transmute(self, path, mutation_size, linewidth): 

3851 # docstring inherited 

3852 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3853 

3854 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3855 b_plus, b_minus = make_wedged_bezier2( 

3856 arrow_path, 

3857 self.tail_width * mutation_size / 2., 

3858 wm=self.shrink_factor) 

3859 

3860 patch_path = [(Path.MOVETO, b_plus[0]), 

3861 (Path.CURVE3, b_plus[1]), 

3862 (Path.CURVE3, b_plus[2]), 

3863 (Path.LINETO, b_minus[2]), 

3864 (Path.CURVE3, b_minus[1]), 

3865 (Path.CURVE3, b_minus[0]), 

3866 (Path.CLOSEPOLY, b_minus[0]), 

3867 ] 

3868 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3869 

3870 return path, True 

3871 

3872 

3873class FancyBboxPatch(Patch): 

3874 """ 

3875 A fancy box around a rectangle with lower left at *xy* = (*x*, *y*) 

3876 with specified width and height. 

3877 

3878 `.FancyBboxPatch` is similar to `.Rectangle`, but it draws a fancy box 

3879 around the rectangle. The transformation of the rectangle box to the 

3880 fancy box is delegated to the style classes defined in `.BoxStyle`. 

3881 """ 

3882 

3883 _edge_default = True 

3884 

3885 def __str__(self): 

3886 s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)" 

3887 return s % (self._x, self._y, self._width, self._height) 

3888 

3889 @_docstring.dedent_interpd 

3890 def __init__(self, xy, width, height, boxstyle="round", *, 

3891 mutation_scale=1, mutation_aspect=1, **kwargs): 

3892 """ 

3893 Parameters 

3894 ---------- 

3895 xy : (float, float) 

3896 The lower left corner of the box. 

3897 

3898 width : float 

3899 The width of the box. 

3900 

3901 height : float 

3902 The height of the box. 

3903 

3904 boxstyle : str or `~matplotlib.patches.BoxStyle` 

3905 The style of the fancy box. This can either be a `.BoxStyle` 

3906 instance or a string of the style name and optionally comma 

3907 separated attributes (e.g. "Round, pad=0.2"). This string is 

3908 passed to `.BoxStyle` to construct a `.BoxStyle` object. See 

3909 there for a full documentation. 

3910 

3911 The following box styles are available: 

3912 

3913 %(BoxStyle:table)s 

3914 

3915 mutation_scale : float, default: 1 

3916 Scaling factor applied to the attributes of the box style 

3917 (e.g. pad or rounding_size). 

3918 

3919 mutation_aspect : float, default: 1 

3920 The height of the rectangle will be squeezed by this value before 

3921 the mutation and the mutated box will be stretched by the inverse 

3922 of it. For example, this allows different horizontal and vertical 

3923 padding. 

3924 

3925 Other Parameters 

3926 ---------------- 

3927 **kwargs : `~matplotlib.patches.Patch` properties 

3928 

3929 %(Patch:kwdoc)s 

3930 """ 

3931 

3932 super().__init__(**kwargs) 

3933 self._x, self._y = xy 

3934 self._width = width 

3935 self._height = height 

3936 self.set_boxstyle(boxstyle) 

3937 self._mutation_scale = mutation_scale 

3938 self._mutation_aspect = mutation_aspect 

3939 self.stale = True 

3940 

3941 @_docstring.dedent_interpd 

3942 def set_boxstyle(self, boxstyle=None, **kwargs): 

3943 """ 

3944 Set the box style, possibly with further attributes. 

3945 

3946 Attributes from the previous box style are not reused. 

3947 

3948 Without argument (or with ``boxstyle=None``), the available box styles 

3949 are returned as a human-readable string. 

3950 

3951 Parameters 

3952 ---------- 

3953 boxstyle : str or `~matplotlib.patches.BoxStyle` 

3954 The style of the box: either a `.BoxStyle` instance, or a string, 

3955 which is the style name and optionally comma separated attributes 

3956 (e.g. "Round,pad=0.2"). Such a string is used to construct a 

3957 `.BoxStyle` object, as documented in that class. 

3958 

3959 The following box styles are available: 

3960 

3961 %(BoxStyle:table_and_accepts)s 

3962 

3963 **kwargs 

3964 Additional attributes for the box style. See the table above for 

3965 supported parameters. 

3966 

3967 Examples 

3968 -------- 

3969 :: 

3970 

3971 set_boxstyle("Round,pad=0.2") 

3972 set_boxstyle("round", pad=0.2) 

3973 """ 

3974 if boxstyle is None: 

3975 return BoxStyle.pprint_styles() 

3976 self._bbox_transmuter = ( 

3977 BoxStyle(boxstyle, **kwargs) 

3978 if isinstance(boxstyle, str) else boxstyle) 

3979 self.stale = True 

3980 

3981 def get_boxstyle(self): 

3982 """Return the boxstyle object.""" 

3983 return self._bbox_transmuter 

3984 

3985 def set_mutation_scale(self, scale): 

3986 """ 

3987 Set the mutation scale. 

3988 

3989 Parameters 

3990 ---------- 

3991 scale : float 

3992 """ 

3993 self._mutation_scale = scale 

3994 self.stale = True 

3995 

3996 def get_mutation_scale(self): 

3997 """Return the mutation scale.""" 

3998 return self._mutation_scale 

3999 

4000 def set_mutation_aspect(self, aspect): 

4001 """ 

4002 Set the aspect ratio of the bbox mutation. 

4003 

4004 Parameters 

4005 ---------- 

4006 aspect : float 

4007 """ 

4008 self._mutation_aspect = aspect 

4009 self.stale = True 

4010 

4011 def get_mutation_aspect(self): 

4012 """Return the aspect ratio of the bbox mutation.""" 

4013 return (self._mutation_aspect if self._mutation_aspect is not None 

4014 else 1) # backcompat. 

4015 

4016 def get_path(self): 

4017 """Return the mutated path of the rectangle.""" 

4018 boxstyle = self.get_boxstyle() 

4019 m_aspect = self.get_mutation_aspect() 

4020 # Call boxstyle with y, height squeezed by aspect_ratio. 

4021 path = boxstyle(self._x, self._y / m_aspect, 

4022 self._width, self._height / m_aspect, 

4023 self.get_mutation_scale()) 

4024 return Path(path.vertices * [1, m_aspect], path.codes) # Unsqueeze y. 

4025 

4026 # Following methods are borrowed from the Rectangle class. 

4027 

4028 def get_x(self): 

4029 """Return the left coord of the rectangle.""" 

4030 return self._x 

4031 

4032 def get_y(self): 

4033 """Return the bottom coord of the rectangle.""" 

4034 return self._y 

4035 

4036 def get_width(self): 

4037 """Return the width of the rectangle.""" 

4038 return self._width 

4039 

4040 def get_height(self): 

4041 """Return the height of the rectangle.""" 

4042 return self._height 

4043 

4044 def set_x(self, x): 

4045 """ 

4046 Set the left coord of the rectangle. 

4047 

4048 Parameters 

4049 ---------- 

4050 x : float 

4051 """ 

4052 self._x = x 

4053 self.stale = True 

4054 

4055 def set_y(self, y): 

4056 """ 

4057 Set the bottom coord of the rectangle. 

4058 

4059 Parameters 

4060 ---------- 

4061 y : float 

4062 """ 

4063 self._y = y 

4064 self.stale = True 

4065 

4066 def set_width(self, w): 

4067 """ 

4068 Set the rectangle width. 

4069 

4070 Parameters 

4071 ---------- 

4072 w : float 

4073 """ 

4074 self._width = w 

4075 self.stale = True 

4076 

4077 def set_height(self, h): 

4078 """ 

4079 Set the rectangle height. 

4080 

4081 Parameters 

4082 ---------- 

4083 h : float 

4084 """ 

4085 self._height = h 

4086 self.stale = True 

4087 

4088 def set_bounds(self, *args): 

4089 """ 

4090 Set the bounds of the rectangle. 

4091 

4092 Call signatures:: 

4093 

4094 set_bounds(left, bottom, width, height) 

4095 set_bounds((left, bottom, width, height)) 

4096 

4097 Parameters 

4098 ---------- 

4099 left, bottom : float 

4100 The coordinates of the bottom left corner of the rectangle. 

4101 width, height : float 

4102 The width/height of the rectangle. 

4103 """ 

4104 if len(args) == 1: 

4105 l, b, w, h = args[0] 

4106 else: 

4107 l, b, w, h = args 

4108 self._x = l 

4109 self._y = b 

4110 self._width = w 

4111 self._height = h 

4112 self.stale = True 

4113 

4114 def get_bbox(self): 

4115 """Return the `.Bbox`.""" 

4116 return transforms.Bbox.from_bounds(self._x, self._y, 

4117 self._width, self._height) 

4118 

4119 

4120class FancyArrowPatch(Patch): 

4121 """ 

4122 A fancy arrow patch. 

4123 

4124 It draws an arrow using the `ArrowStyle`. It is primarily used by the 

4125 `~.axes.Axes.annotate` method. For most purposes, use the annotate method for 

4126 drawing arrows. 

4127 

4128 The head and tail positions are fixed at the specified start and end points 

4129 of the arrow, but the size and shape (in display coordinates) of the arrow 

4130 does not change when the axis is moved or zoomed. 

4131 """ 

4132 _edge_default = True 

4133 

4134 def __str__(self): 

4135 if self._posA_posB is not None: 

4136 (x1, y1), (x2, y2) = self._posA_posB 

4137 return f"{type(self).__name__}(({x1:g}, {y1:g})->({x2:g}, {y2:g}))" 

4138 else: 

4139 return f"{type(self).__name__}({self._path_original})" 

4140 

4141 @_docstring.dedent_interpd 

4142 def __init__(self, posA=None, posB=None, *, 

4143 path=None, arrowstyle="simple", connectionstyle="arc3", 

4144 patchA=None, patchB=None, shrinkA=2, shrinkB=2, 

4145 mutation_scale=1, mutation_aspect=1, **kwargs): 

4146 """ 

4147 There are two ways for defining an arrow: 

4148 

4149 - If *posA* and *posB* are given, a path connecting two points is 

4150 created according to *connectionstyle*. The path will be 

4151 clipped with *patchA* and *patchB* and further shrunken by 

4152 *shrinkA* and *shrinkB*. An arrow is drawn along this 

4153 resulting path using the *arrowstyle* parameter. 

4154 

4155 - Alternatively if *path* is provided, an arrow is drawn along this 

4156 path and *patchA*, *patchB*, *shrinkA*, and *shrinkB* are ignored. 

4157 

4158 Parameters 

4159 ---------- 

4160 posA, posB : (float, float), default: None 

4161 (x, y) coordinates of arrow tail and arrow head respectively. 

4162 

4163 path : `~matplotlib.path.Path`, default: None 

4164 If provided, an arrow is drawn along this path and *patchA*, 

4165 *patchB*, *shrinkA*, and *shrinkB* are ignored. 

4166 

4167 arrowstyle : str or `.ArrowStyle`, default: 'simple' 

4168 The `.ArrowStyle` with which the fancy arrow is drawn. If a 

4169 string, it should be one of the available arrowstyle names, with 

4170 optional comma-separated attributes. The optional attributes are 

4171 meant to be scaled with the *mutation_scale*. The following arrow 

4172 styles are available: 

4173 

4174 %(ArrowStyle:table)s 

4175 

4176 connectionstyle : str or `.ConnectionStyle` or None, optional, \ 

4177default: 'arc3' 

4178 The `.ConnectionStyle` with which *posA* and *posB* are connected. 

4179 If a string, it should be one of the available connectionstyle 

4180 names, with optional comma-separated attributes. The following 

4181 connection styles are available: 

4182 

4183 %(ConnectionStyle:table)s 

4184 

4185 patchA, patchB : `~matplotlib.patches.Patch`, default: None 

4186 Head and tail patches, respectively. 

4187 

4188 shrinkA, shrinkB : float, default: 2 

4189 Shrink amount, in points, of the tail and head of the arrow respectively. 

4190 

4191 mutation_scale : float, default: 1 

4192 Value with which attributes of *arrowstyle* (e.g., *head_length*) 

4193 will be scaled. 

4194 

4195 mutation_aspect : None or float, default: None 

4196 The height of the rectangle will be squeezed by this value before 

4197 the mutation and the mutated box will be stretched by the inverse 

4198 of it. 

4199 

4200 Other Parameters 

4201 ---------------- 

4202 **kwargs : `~matplotlib.patches.Patch` properties, optional 

4203 Here is a list of available `.Patch` properties: 

4204 

4205 %(Patch:kwdoc)s 

4206 

4207 In contrast to other patches, the default ``capstyle`` and 

4208 ``joinstyle`` for `FancyArrowPatch` are set to ``"round"``. 

4209 """ 

4210 # Traditionally, the cap- and joinstyle for FancyArrowPatch are round 

4211 kwargs.setdefault("joinstyle", JoinStyle.round) 

4212 kwargs.setdefault("capstyle", CapStyle.round) 

4213 

4214 super().__init__(**kwargs) 

4215 

4216 if posA is not None and posB is not None and path is None: 

4217 self._posA_posB = [posA, posB] 

4218 

4219 if connectionstyle is None: 

4220 connectionstyle = "arc3" 

4221 self.set_connectionstyle(connectionstyle) 

4222 

4223 elif posA is None and posB is None and path is not None: 

4224 self._posA_posB = None 

4225 else: 

4226 raise ValueError("Either posA and posB, or path need to provided") 

4227 

4228 self.patchA = patchA 

4229 self.patchB = patchB 

4230 self.shrinkA = shrinkA 

4231 self.shrinkB = shrinkB 

4232 

4233 self._path_original = path 

4234 

4235 self.set_arrowstyle(arrowstyle) 

4236 

4237 self._mutation_scale = mutation_scale 

4238 self._mutation_aspect = mutation_aspect 

4239 

4240 self._dpi_cor = 1.0 

4241 

4242 def set_positions(self, posA, posB): 

4243 """ 

4244 Set the start and end positions of the connecting path. 

4245 

4246 Parameters 

4247 ---------- 

4248 posA, posB : None, tuple 

4249 (x, y) coordinates of arrow tail and arrow head respectively. If 

4250 `None` use current value. 

4251 """ 

4252 if posA is not None: 

4253 self._posA_posB[0] = posA 

4254 if posB is not None: 

4255 self._posA_posB[1] = posB 

4256 self.stale = True 

4257 

4258 def set_patchA(self, patchA): 

4259 """ 

4260 Set the tail patch. 

4261 

4262 Parameters 

4263 ---------- 

4264 patchA : `.patches.Patch` 

4265 """ 

4266 self.patchA = patchA 

4267 self.stale = True 

4268 

4269 def set_patchB(self, patchB): 

4270 """ 

4271 Set the head patch. 

4272 

4273 Parameters 

4274 ---------- 

4275 patchB : `.patches.Patch` 

4276 """ 

4277 self.patchB = patchB 

4278 self.stale = True 

4279 

4280 @_docstring.dedent_interpd 

4281 def set_connectionstyle(self, connectionstyle=None, **kwargs): 

4282 """ 

4283 Set the connection style, possibly with further attributes. 

4284 

4285 Attributes from the previous connection style are not reused. 

4286 

4287 Without argument (or with ``connectionstyle=None``), the available box 

4288 styles are returned as a human-readable string. 

4289 

4290 Parameters 

4291 ---------- 

4292 connectionstyle : str or `~matplotlib.patches.ConnectionStyle` 

4293 The style of the connection: either a `.ConnectionStyle` instance, 

4294 or a string, which is the style name and optionally comma separated 

4295 attributes (e.g. "Arc,armA=30,rad=10"). Such a string is used to 

4296 construct a `.ConnectionStyle` object, as documented in that class. 

4297 

4298 The following connection styles are available: 

4299 

4300 %(ConnectionStyle:table_and_accepts)s 

4301 

4302 **kwargs 

4303 Additional attributes for the connection style. See the table above 

4304 for supported parameters. 

4305 

4306 Examples 

4307 -------- 

4308 :: 

4309 

4310 set_connectionstyle("Arc,armA=30,rad=10") 

4311 set_connectionstyle("arc", armA=30, rad=10) 

4312 """ 

4313 if connectionstyle is None: 

4314 return ConnectionStyle.pprint_styles() 

4315 self._connector = ( 

4316 ConnectionStyle(connectionstyle, **kwargs) 

4317 if isinstance(connectionstyle, str) else connectionstyle) 

4318 self.stale = True 

4319 

4320 def get_connectionstyle(self): 

4321 """Return the `ConnectionStyle` used.""" 

4322 return self._connector 

4323 

4324 def set_arrowstyle(self, arrowstyle=None, **kwargs): 

4325 """ 

4326 Set the arrow style, possibly with further attributes. 

4327 

4328 Attributes from the previous arrow style are not reused. 

4329 

4330 Without argument (or with ``arrowstyle=None``), the available box 

4331 styles are returned as a human-readable string. 

4332 

4333 Parameters 

4334 ---------- 

4335 arrowstyle : str or `~matplotlib.patches.ArrowStyle` 

4336 The style of the arrow: either a `.ArrowStyle` instance, or a 

4337 string, which is the style name and optionally comma separated 

4338 attributes (e.g. "Fancy,head_length=0.2"). Such a string is used to 

4339 construct a `.ArrowStyle` object, as documented in that class. 

4340 

4341 The following arrow styles are available: 

4342 

4343 %(ArrowStyle:table_and_accepts)s 

4344 

4345 **kwargs 

4346 Additional attributes for the arrow style. See the table above for 

4347 supported parameters. 

4348 

4349 Examples 

4350 -------- 

4351 :: 

4352 

4353 set_arrowstyle("Fancy,head_length=0.2") 

4354 set_arrowstyle("fancy", head_length=0.2) 

4355 """ 

4356 if arrowstyle is None: 

4357 return ArrowStyle.pprint_styles() 

4358 self._arrow_transmuter = ( 

4359 ArrowStyle(arrowstyle, **kwargs) 

4360 if isinstance(arrowstyle, str) else arrowstyle) 

4361 self.stale = True 

4362 

4363 def get_arrowstyle(self): 

4364 """Return the arrowstyle object.""" 

4365 return self._arrow_transmuter 

4366 

4367 def set_mutation_scale(self, scale): 

4368 """ 

4369 Set the mutation scale. 

4370 

4371 Parameters 

4372 ---------- 

4373 scale : float 

4374 """ 

4375 self._mutation_scale = scale 

4376 self.stale = True 

4377 

4378 def get_mutation_scale(self): 

4379 """ 

4380 Return the mutation scale. 

4381 

4382 Returns 

4383 ------- 

4384 scalar 

4385 """ 

4386 return self._mutation_scale 

4387 

4388 def set_mutation_aspect(self, aspect): 

4389 """ 

4390 Set the aspect ratio of the bbox mutation. 

4391 

4392 Parameters 

4393 ---------- 

4394 aspect : float 

4395 """ 

4396 self._mutation_aspect = aspect 

4397 self.stale = True 

4398 

4399 def get_mutation_aspect(self): 

4400 """Return the aspect ratio of the bbox mutation.""" 

4401 return (self._mutation_aspect if self._mutation_aspect is not None 

4402 else 1) # backcompat. 

4403 

4404 def get_path(self): 

4405 """Return the path of the arrow in the data coordinates.""" 

4406 # The path is generated in display coordinates, then converted back to 

4407 # data coordinates. 

4408 _path, fillable = self._get_path_in_displaycoord() 

4409 if np.iterable(fillable): 

4410 _path = Path.make_compound_path(*_path) 

4411 return self.get_transform().inverted().transform_path(_path) 

4412 

4413 def _get_path_in_displaycoord(self): 

4414 """Return the mutated path of the arrow in display coordinates.""" 

4415 dpi_cor = self._dpi_cor 

4416 

4417 if self._posA_posB is not None: 

4418 posA = self._convert_xy_units(self._posA_posB[0]) 

4419 posB = self._convert_xy_units(self._posA_posB[1]) 

4420 (posA, posB) = self.get_transform().transform((posA, posB)) 

4421 _path = self.get_connectionstyle()(posA, posB, 

4422 patchA=self.patchA, 

4423 patchB=self.patchB, 

4424 shrinkA=self.shrinkA * dpi_cor, 

4425 shrinkB=self.shrinkB * dpi_cor 

4426 ) 

4427 else: 

4428 _path = self.get_transform().transform_path(self._path_original) 

4429 

4430 _path, fillable = self.get_arrowstyle()( 

4431 _path, 

4432 self.get_mutation_scale() * dpi_cor, 

4433 self.get_linewidth() * dpi_cor, 

4434 self.get_mutation_aspect()) 

4435 

4436 return _path, fillable 

4437 

4438 def draw(self, renderer): 

4439 if not self.get_visible(): 

4440 return 

4441 

4442 # FIXME: dpi_cor is for the dpi-dependency of the linewidth. There 

4443 # could be room for improvement. Maybe _get_path_in_displaycoord could 

4444 # take a renderer argument, but get_path should be adapted too. 

4445 self._dpi_cor = renderer.points_to_pixels(1.) 

4446 path, fillable = self._get_path_in_displaycoord() 

4447 

4448 if not np.iterable(fillable): 

4449 path = [path] 

4450 fillable = [fillable] 

4451 

4452 affine = transforms.IdentityTransform() 

4453 

4454 self._draw_paths_with_artist_properties( 

4455 renderer, 

4456 [(p, affine, self._facecolor if f and self._facecolor[3] else None) 

4457 for p, f in zip(path, fillable)]) 

4458 

4459 

4460class ConnectionPatch(FancyArrowPatch): 

4461 """A patch that connects two points (possibly in different Axes).""" 

4462 

4463 def __str__(self): 

4464 return "ConnectionPatch((%g, %g), (%g, %g))" % \ 

4465 (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1]) 

4466 

4467 @_docstring.dedent_interpd 

4468 def __init__(self, xyA, xyB, coordsA, coordsB=None, *, 

4469 axesA=None, axesB=None, 

4470 arrowstyle="-", 

4471 connectionstyle="arc3", 

4472 patchA=None, 

4473 patchB=None, 

4474 shrinkA=0., 

4475 shrinkB=0., 

4476 mutation_scale=10., 

4477 mutation_aspect=None, 

4478 clip_on=False, 

4479 **kwargs): 

4480 """ 

4481 Connect point *xyA* in *coordsA* with point *xyB* in *coordsB*. 

4482 

4483 Valid keys are 

4484 

4485 =============== ====================================================== 

4486 Key Description 

4487 =============== ====================================================== 

4488 arrowstyle the arrow style 

4489 connectionstyle the connection style 

4490 relpos default is (0.5, 0.5) 

4491 patchA default is bounding box of the text 

4492 patchB default is None 

4493 shrinkA default is 2 points 

4494 shrinkB default is 2 points 

4495 mutation_scale default is text size (in points) 

4496 mutation_aspect default is 1. 

4497 ? any key for `matplotlib.patches.PathPatch` 

4498 =============== ====================================================== 

4499 

4500 *coordsA* and *coordsB* are strings that indicate the 

4501 coordinates of *xyA* and *xyB*. 

4502 

4503 ==================== ================================================== 

4504 Property Description 

4505 ==================== ================================================== 

4506 'figure points' points from the lower left corner of the figure 

4507 'figure pixels' pixels from the lower left corner of the figure 

4508 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper 

4509 right 

4510 'subfigure points' points from the lower left corner of the subfigure 

4511 'subfigure pixels' pixels from the lower left corner of the subfigure 

4512 'subfigure fraction' fraction of the subfigure, 0, 0 is lower left. 

4513 'axes points' points from lower left corner of the Axes 

4514 'axes pixels' pixels from lower left corner of the Axes 

4515 'axes fraction' 0, 0 is lower left of Axes and 1, 1 is upper right 

4516 'data' use the coordinate system of the object being 

4517 annotated (default) 

4518 'offset points' offset (in points) from the *xy* value 

4519 'polar' you can specify *theta*, *r* for the annotation, 

4520 even in cartesian plots. Note that if you are 

4521 using a polar Axes, you do not need to specify 

4522 polar for the coordinate system since that is the 

4523 native "data" coordinate system. 

4524 ==================== ================================================== 

4525 

4526 Alternatively they can be set to any valid 

4527 `~matplotlib.transforms.Transform`. 

4528 

4529 Note that 'subfigure pixels' and 'figure pixels' are the same 

4530 for the parent figure, so users who want code that is usable in 

4531 a subfigure can use 'subfigure pixels'. 

4532 

4533 .. note:: 

4534 

4535 Using `ConnectionPatch` across two `~.axes.Axes` instances 

4536 is not directly compatible with :ref:`constrained layout 

4537 <constrainedlayout_guide>`. Add the artist 

4538 directly to the `.Figure` instead of adding it to a specific Axes, 

4539 or exclude it from the layout using ``con.set_in_layout(False)``. 

4540 

4541 .. code-block:: default 

4542 

4543 fig, ax = plt.subplots(1, 2, constrained_layout=True) 

4544 con = ConnectionPatch(..., axesA=ax[0], axesB=ax[1]) 

4545 fig.add_artist(con) 

4546 

4547 """ 

4548 if coordsB is None: 

4549 coordsB = coordsA 

4550 # we'll draw ourself after the artist we annotate by default 

4551 self.xy1 = xyA 

4552 self.xy2 = xyB 

4553 self.coords1 = coordsA 

4554 self.coords2 = coordsB 

4555 

4556 self.axesA = axesA 

4557 self.axesB = axesB 

4558 

4559 super().__init__(posA=(0, 0), posB=(1, 1), 

4560 arrowstyle=arrowstyle, 

4561 connectionstyle=connectionstyle, 

4562 patchA=patchA, patchB=patchB, 

4563 shrinkA=shrinkA, shrinkB=shrinkB, 

4564 mutation_scale=mutation_scale, 

4565 mutation_aspect=mutation_aspect, 

4566 clip_on=clip_on, 

4567 **kwargs) 

4568 # if True, draw annotation only if self.xy is inside the Axes 

4569 self._annotation_clip = None 

4570 

4571 def _get_xy(self, xy, s, axes=None): 

4572 """Calculate the pixel position of given point.""" 

4573 s0 = s # For the error message, if needed. 

4574 if axes is None: 

4575 axes = self.axes 

4576 xy = np.array(xy) 

4577 if s in ["figure points", "axes points"]: 

4578 xy *= self.figure.dpi / 72 

4579 s = s.replace("points", "pixels") 

4580 elif s == "figure fraction": 

4581 s = self.figure.transFigure 

4582 elif s == "subfigure fraction": 

4583 s = self.figure.transSubfigure 

4584 elif s == "axes fraction": 

4585 s = axes.transAxes 

4586 x, y = xy 

4587 

4588 if s == 'data': 

4589 trans = axes.transData 

4590 x = float(self.convert_xunits(x)) 

4591 y = float(self.convert_yunits(y)) 

4592 return trans.transform((x, y)) 

4593 elif s == 'offset points': 

4594 if self.xycoords == 'offset points': # prevent recursion 

4595 return self._get_xy(self.xy, 'data') 

4596 return ( 

4597 self._get_xy(self.xy, self.xycoords) # converted data point 

4598 + xy * self.figure.dpi / 72) # converted offset 

4599 elif s == 'polar': 

4600 theta, r = x, y 

4601 x = r * np.cos(theta) 

4602 y = r * np.sin(theta) 

4603 trans = axes.transData 

4604 return trans.transform((x, y)) 

4605 elif s == 'figure pixels': 

4606 # pixels from the lower left corner of the figure 

4607 bb = self.figure.figbbox 

4608 x = bb.x0 + x if x >= 0 else bb.x1 + x 

4609 y = bb.y0 + y if y >= 0 else bb.y1 + y 

4610 return x, y 

4611 elif s == 'subfigure pixels': 

4612 # pixels from the lower left corner of the figure 

4613 bb = self.figure.bbox 

4614 x = bb.x0 + x if x >= 0 else bb.x1 + x 

4615 y = bb.y0 + y if y >= 0 else bb.y1 + y 

4616 return x, y 

4617 elif s == 'axes pixels': 

4618 # pixels from the lower left corner of the Axes 

4619 bb = axes.bbox 

4620 x = bb.x0 + x if x >= 0 else bb.x1 + x 

4621 y = bb.y0 + y if y >= 0 else bb.y1 + y 

4622 return x, y 

4623 elif isinstance(s, transforms.Transform): 

4624 return s.transform(xy) 

4625 else: 

4626 raise ValueError(f"{s0} is not a valid coordinate transformation") 

4627 

4628 def set_annotation_clip(self, b): 

4629 """ 

4630 Set the annotation's clipping behavior. 

4631 

4632 Parameters 

4633 ---------- 

4634 b : bool or None 

4635 - True: The annotation will be clipped when ``self.xy`` is 

4636 outside the Axes. 

4637 - False: The annotation will always be drawn. 

4638 - None: The annotation will be clipped when ``self.xy`` is 

4639 outside the Axes and ``self.xycoords == "data"``. 

4640 """ 

4641 self._annotation_clip = b 

4642 self.stale = True 

4643 

4644 def get_annotation_clip(self): 

4645 """ 

4646 Return the clipping behavior. 

4647 

4648 See `.set_annotation_clip` for the meaning of the return value. 

4649 """ 

4650 return self._annotation_clip 

4651 

4652 def _get_path_in_displaycoord(self): 

4653 """Return the mutated path of the arrow in display coordinates.""" 

4654 dpi_cor = self._dpi_cor 

4655 posA = self._get_xy(self.xy1, self.coords1, self.axesA) 

4656 posB = self._get_xy(self.xy2, self.coords2, self.axesB) 

4657 path = self.get_connectionstyle()( 

4658 posA, posB, 

4659 patchA=self.patchA, patchB=self.patchB, 

4660 shrinkA=self.shrinkA * dpi_cor, shrinkB=self.shrinkB * dpi_cor, 

4661 ) 

4662 path, fillable = self.get_arrowstyle()( 

4663 path, 

4664 self.get_mutation_scale() * dpi_cor, 

4665 self.get_linewidth() * dpi_cor, 

4666 self.get_mutation_aspect() 

4667 ) 

4668 return path, fillable 

4669 

4670 def _check_xy(self, renderer): 

4671 """Check whether the annotation needs to be drawn.""" 

4672 

4673 b = self.get_annotation_clip() 

4674 

4675 if b or (b is None and self.coords1 == "data"): 

4676 xy_pixel = self._get_xy(self.xy1, self.coords1, self.axesA) 

4677 if self.axesA is None: 

4678 axes = self.axes 

4679 else: 

4680 axes = self.axesA 

4681 if not axes.contains_point(xy_pixel): 

4682 return False 

4683 

4684 if b or (b is None and self.coords2 == "data"): 

4685 xy_pixel = self._get_xy(self.xy2, self.coords2, self.axesB) 

4686 if self.axesB is None: 

4687 axes = self.axes 

4688 else: 

4689 axes = self.axesB 

4690 if not axes.contains_point(xy_pixel): 

4691 return False 

4692 

4693 return True 

4694 

4695 def draw(self, renderer): 

4696 if not self.get_visible() or not self._check_xy(renderer): 

4697 return 

4698 super().draw(renderer)