Coverage for /usr/lib/python3/dist-packages/matplotlib/patches.py: 37%

1765 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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 

9import textwrap 

10from collections import namedtuple 

11 

12import numpy as np 

13 

14import matplotlib as mpl 

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

16 lines as mlines, transforms) 

17from .bezier import ( 

18 NonIntersectingPathException, get_cos_sin, get_intersection, 

19 get_parallels, inside_circle, make_wedged_bezier2, 

20 split_bezier_intersecting_with_closedpath, split_path_inout) 

21from .path import Path 

22from ._enums import JoinStyle, CapStyle 

23 

24 

25@_docstring.interpd 

26@_api.define_aliases({ 

27 "antialiased": ["aa"], 

28 "edgecolor": ["ec"], 

29 "facecolor": ["fc"], 

30 "linestyle": ["ls"], 

31 "linewidth": ["lw"], 

32}) 

33class Patch(artist.Artist): 

34 """ 

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

36 

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

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

39 """ 

40 zorder = 1 

41 

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

43 # subclass-by-subclass basis. 

44 _edge_default = False 

45 

46 @_api.make_keyword_only("3.6", name="edgecolor") 

47 def __init__(self, 

48 edgecolor=None, 

49 facecolor=None, 

50 color=None, 

51 linewidth=None, 

52 linestyle=None, 

53 antialiased=None, 

54 hatch=None, 

55 fill=True, 

56 capstyle=None, 

57 joinstyle=None, 

58 **kwargs): 

59 """ 

60 The following kwarg properties are supported 

61 

62 %(Patch:kwdoc)s 

63 """ 

64 super().__init__() 

65 

66 if linestyle is None: 

67 linestyle = "solid" 

68 if capstyle is None: 

69 capstyle = CapStyle.butt 

70 if joinstyle is None: 

71 joinstyle = JoinStyle.miter 

72 

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

74 self._fill = True # needed for set_facecolor call 

75 if color is not None: 

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

77 _api.warn_external( 

78 "Setting the 'color' property will override " 

79 "the edgecolor or facecolor properties.") 

80 self.set_color(color) 

81 else: 

82 self.set_edgecolor(edgecolor) 

83 self.set_facecolor(facecolor) 

84 

85 self._linewidth = 0 

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

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

88 

89 self.set_fill(fill) 

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 Returns 

131 ------- 

132 (bool, empty dict) 

133 """ 

134 inside, info = self._default_contains(mouseevent) 

135 if inside is not None: 

136 return inside, info 

137 radius = self._process_radius(radius) 

138 codes = self.get_path().codes 

139 if codes is not None: 

140 vertices = self.get_path().vertices 

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

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

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

144 # Don't split before the first MOVETO. 

145 idxs = idxs[1:] 

146 subpaths = map( 

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

148 else: 

149 subpaths = [self.get_path()] 

150 inside = any( 

151 subpath.contains_point( 

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

153 for subpath in subpaths) 

154 return inside, {} 

155 

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

157 """ 

158 Return whether the given point is inside the patch. 

159 

160 Parameters 

161 ---------- 

162 point : (float, float) 

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

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

165 that are added to a figure or axes. 

166 radius : float, optional 

167 Additional margin on the patch in target coordinates of 

168 ``self.get_transform()``. See `.Path.contains_point` for further 

169 details. 

170 

171 Returns 

172 ------- 

173 bool 

174 

175 Notes 

176 ----- 

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

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

179 creation coordinates and the point coordinates match. The following 

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

181 

182 >>> center = 0, 0 

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

184 >>> c.contains_point(center) 

185 True 

186 

187 The convention of checking against the transformed patch stems from 

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

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

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

191 transform them first: 

192 

193 >>> center = 0, 0 

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

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

196 >>> transformed_center = c.get_transform().transform(center) 

197 >>> c.contains_point(transformed_center) 

198 True 

199 

200 """ 

201 radius = self._process_radius(radius) 

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

203 self.get_transform(), 

204 radius) 

205 

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

207 """ 

208 Return whether the given points are inside the patch. 

209 

210 Parameters 

211 ---------- 

212 points : (N, 2) array 

213 The points to check, in target coordinates of 

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

215 that are added to a figure or axes. Columns contain x and y values. 

216 radius : float, optional 

217 Additional margin on the patch in target coordinates of 

218 ``self.get_transform()``. See `.Path.contains_point` for further 

219 details. 

220 

221 Returns 

222 ------- 

223 length-N bool array 

224 

225 Notes 

226 ----- 

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

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

229 """ 

230 radius = self._process_radius(radius) 

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

232 self.get_transform(), 

233 radius) 

234 

235 def update_from(self, other): 

236 # docstring inherited. 

237 super().update_from(other) 

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

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

240 self._edgecolor = other._edgecolor 

241 self._facecolor = other._facecolor 

242 self._original_edgecolor = other._original_edgecolor 

243 self._original_facecolor = other._original_facecolor 

244 self._fill = other._fill 

245 self._hatch = other._hatch 

246 self._hatch_color = other._hatch_color 

247 self._unscaled_dash_pattern = other._unscaled_dash_pattern 

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

249 self.set_transform(other.get_data_transform()) 

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

251 # be the case for this artist too. 

252 self._transformSet = other.is_transform_set() 

253 

254 def get_extents(self): 

255 """ 

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

257 """ 

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

259 

260 def get_transform(self): 

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

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

263 

264 def get_data_transform(self): 

265 """ 

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

267 physical coordinates. 

268 """ 

269 return artist.Artist.get_transform(self) 

270 

271 def get_patch_transform(self): 

272 """ 

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

274 to data coordinates. 

275 

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

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

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

279 """ 

280 return transforms.IdentityTransform() 

281 

282 def get_antialiased(self): 

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

284 return self._antialiased 

285 

286 def get_edgecolor(self): 

287 """Return the edge color.""" 

288 return self._edgecolor 

289 

290 def get_facecolor(self): 

291 """Return the face color.""" 

292 return self._facecolor 

293 

294 def get_linewidth(self): 

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

296 return self._linewidth 

297 

298 def get_linestyle(self): 

299 """Return the linestyle.""" 

300 return self._linestyle 

301 

302 def set_antialiased(self, aa): 

303 """ 

304 Set whether to use antialiased rendering. 

305 

306 Parameters 

307 ---------- 

308 aa : bool or None 

309 """ 

310 if aa is None: 

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

312 self._antialiased = aa 

313 self.stale = True 

314 

315 def _set_edgecolor(self, color): 

316 set_hatch_color = True 

317 if color is None: 

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

319 not self._fill or self._edge_default): 

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

321 else: 

322 color = 'none' 

323 set_hatch_color = False 

324 

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

326 if set_hatch_color: 

327 self._hatch_color = self._edgecolor 

328 self.stale = True 

329 

330 def set_edgecolor(self, color): 

331 """ 

332 Set the patch edge color. 

333 

334 Parameters 

335 ---------- 

336 color : color or None 

337 """ 

338 self._original_edgecolor = color 

339 self._set_edgecolor(color) 

340 

341 def _set_facecolor(self, color): 

342 if color is None: 

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

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

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

346 self.stale = True 

347 

348 def set_facecolor(self, color): 

349 """ 

350 Set the patch face color. 

351 

352 Parameters 

353 ---------- 

354 color : color or None 

355 """ 

356 self._original_facecolor = color 

357 self._set_facecolor(color) 

358 

359 def set_color(self, c): 

360 """ 

361 Set both the edgecolor and the facecolor. 

362 

363 Parameters 

364 ---------- 

365 c : color 

366 

367 See Also 

368 -------- 

369 Patch.set_facecolor, Patch.set_edgecolor 

370 For setting the edge or face color individually. 

371 """ 

372 self.set_facecolor(c) 

373 self.set_edgecolor(c) 

374 

375 def set_alpha(self, alpha): 

376 # docstring inherited 

377 super().set_alpha(alpha) 

378 self._set_facecolor(self._original_facecolor) 

379 self._set_edgecolor(self._original_edgecolor) 

380 # stale is already True 

381 

382 def set_linewidth(self, w): 

383 """ 

384 Set the patch linewidth in points. 

385 

386 Parameters 

387 ---------- 

388 w : float or None 

389 """ 

390 if w is None: 

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

392 self._linewidth = float(w) 

393 self._dash_pattern = mlines._scale_dashes( 

394 *self._unscaled_dash_pattern, w) 

395 self.stale = True 

396 

397 def set_linestyle(self, ls): 

398 """ 

399 Set the patch linestyle. 

400 

401 ========================================== ================= 

402 linestyle description 

403 ========================================== ================= 

404 ``'-'`` or ``'solid'`` solid line 

405 ``'--'`` or ``'dashed'`` dashed line 

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

407 ``':'`` or ``'dotted'`` dotted line 

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

409 ========================================== ================= 

410 

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

412 

413 (offset, onoffseq) 

414 

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

416 

417 Parameters 

418 ---------- 

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

420 The line style. 

421 """ 

422 if ls is None: 

423 ls = "solid" 

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

425 ls = 'None' 

426 self._linestyle = ls 

427 self._unscaled_dash_pattern = mlines._get_dash_pattern(ls) 

428 self._dash_pattern = mlines._scale_dashes( 

429 *self._unscaled_dash_pattern, self._linewidth) 

430 self.stale = True 

431 

432 def set_fill(self, b): 

433 """ 

434 Set whether to fill the patch. 

435 

436 Parameters 

437 ---------- 

438 b : bool 

439 """ 

440 self._fill = bool(b) 

441 self._set_facecolor(self._original_facecolor) 

442 self._set_edgecolor(self._original_edgecolor) 

443 self.stale = True 

444 

445 def get_fill(self): 

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

447 return self._fill 

448 

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

450 # but somewhat inconsistent behavior in which fill was an 

451 # attribute. 

452 fill = property(get_fill, set_fill) 

453 

454 @_docstring.interpd 

455 def set_capstyle(self, s): 

456 """ 

457 Set the `.CapStyle`. 

458 

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

460 all other patches. 

461 

462 Parameters 

463 ---------- 

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

465 """ 

466 cs = CapStyle(s) 

467 self._capstyle = cs 

468 self.stale = True 

469 

470 def get_capstyle(self): 

471 """Return the capstyle.""" 

472 return self._capstyle.name 

473 

474 @_docstring.interpd 

475 def set_joinstyle(self, s): 

476 """ 

477 Set the `.JoinStyle`. 

478 

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

480 all other patches. 

481 

482 Parameters 

483 ---------- 

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

485 """ 

486 js = JoinStyle(s) 

487 self._joinstyle = js 

488 self.stale = True 

489 

490 def get_joinstyle(self): 

491 """Return the joinstyle.""" 

492 return self._joinstyle.name 

493 

494 def set_hatch(self, hatch): 

495 r""" 

496 Set the hatching pattern. 

497 

498 *hatch* can be one of:: 

499 

500 / - diagonal hatching 

501 \ - back diagonal 

502 | - vertical 

503 - - horizontal 

504 + - crossed 

505 x - crossed diagonal 

506 o - small circle 

507 O - large circle 

508 . - dots 

509 * - stars 

510 

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

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

513 density of hatching of that pattern. 

514 

515 Hatching is supported in the PostScript, PDF, SVG and Agg 

516 backends only. 

517 

518 Parameters 

519 ---------- 

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

521 """ 

522 # Use validate_hatch(list) after deprecation. 

523 mhatch._validate_hatch_pattern(hatch) 

524 self._hatch = hatch 

525 self.stale = True 

526 

527 def get_hatch(self): 

528 """Return the hatching pattern.""" 

529 return self._hatch 

530 

531 def _draw_paths_with_artist_properties( 

532 self, renderer, draw_path_args_list): 

533 """ 

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

535 

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

537 from the artist properties, then repeatedly call 

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

539 *draw_path_args* in *draw_path_args_list*. 

540 """ 

541 

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

543 gc = renderer.new_gc() 

544 

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

546 

547 lw = self._linewidth 

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

549 lw = 0 

550 gc.set_linewidth(lw) 

551 gc.set_dashes(*self._dash_pattern) 

552 gc.set_capstyle(self._capstyle) 

553 gc.set_joinstyle(self._joinstyle) 

554 

555 gc.set_antialiased(self._antialiased) 

556 self._set_gc_clip(gc) 

557 gc.set_url(self._url) 

558 gc.set_snap(self.get_snap()) 

559 

560 gc.set_alpha(self._alpha) 

561 

562 if self._hatch: 

563 gc.set_hatch(self._hatch) 

564 gc.set_hatch_color(self._hatch_color) 

565 

566 if self.get_sketch_params() is not None: 

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

568 

569 if self.get_path_effects(): 

570 from matplotlib.patheffects import PathEffectRenderer 

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

572 

573 for draw_path_args in draw_path_args_list: 

574 renderer.draw_path(gc, *draw_path_args) 

575 

576 gc.restore() 

577 renderer.close_group('patch') 

578 self.stale = False 

579 

580 @artist.allow_rasterization 

581 def draw(self, renderer): 

582 # docstring inherited 

583 if not self.get_visible(): 

584 return 

585 path = self.get_path() 

586 transform = self.get_transform() 

587 tpath = transform.transform_path_non_affine(path) 

588 affine = transform.get_affine() 

589 self._draw_paths_with_artist_properties( 

590 renderer, 

591 [(tpath, affine, 

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

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

594 # transparent, but do if it is None. 

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

596 

597 def get_path(self): 

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

599 raise NotImplementedError('Derived must override') 

600 

601 def get_window_extent(self, renderer=None): 

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

603 

604 def _convert_xy_units(self, xy): 

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

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

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

608 return x, y 

609 

610 

611class Shadow(Patch): 

612 def __str__(self): 

613 return "Shadow(%s)" % (str(self.patch)) 

614 

615 @_docstring.dedent_interpd 

616 def __init__(self, patch, ox, oy, **kwargs): 

617 """ 

618 Create a shadow of the given *patch*. 

619 

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

621 but darkened. 

622 

623 Parameters 

624 ---------- 

625 patch : `.Patch` 

626 The patch to create the shadow for. 

627 ox, oy : float 

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

629 of dpi/72. 

630 **kwargs 

631 Properties of the shadow patch. Supported keys are: 

632 

633 %(Patch:kwdoc)s 

634 """ 

635 super().__init__() 

636 self.patch = patch 

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

638 self._shadow_transform = transforms.Affine2D() 

639 

640 self.update_from(self.patch) 

641 color = .3 * np.asarray(colors.to_rgb(self.patch.get_facecolor())) 

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

643 # Place shadow patch directly behind the inherited patch. 

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

645 **kwargs}) 

646 

647 def _update_transform(self, renderer): 

648 ox = renderer.points_to_pixels(self._ox) 

649 oy = renderer.points_to_pixels(self._oy) 

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

651 

652 def get_path(self): 

653 return self.patch.get_path() 

654 

655 def get_patch_transform(self): 

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

657 

658 def draw(self, renderer): 

659 self._update_transform(renderer) 

660 super().draw(renderer) 

661 

662 

663class Rectangle(Patch): 

664 """ 

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

666 

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

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

669 

670 : +------------------+ 

671 : | | 

672 : height | 

673 : | | 

674 : (xy)---- width -----+ 

675 

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

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

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

679 was inverted or if *width* was negative. 

680 """ 

681 

682 def __str__(self): 

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

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

685 return fmt % pars 

686 

687 @_docstring.dedent_interpd 

688 @_api.make_keyword_only("3.6", name="angle") 

689 def __init__(self, xy, width, height, angle=0.0, *, 

690 rotation_point='xy', **kwargs): 

691 """ 

692 Parameters 

693 ---------- 

694 xy : (float, float) 

695 The anchor point. 

696 width : float 

697 Rectangle width. 

698 height : float 

699 Rectangle height. 

700 angle : float, default: 0 

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

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

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

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

705 coordinate. 

706 

707 Other Parameters 

708 ---------------- 

709 **kwargs : `.Patch` properties 

710 %(Patch:kwdoc)s 

711 """ 

712 super().__init__(**kwargs) 

713 self._x0 = xy[0] 

714 self._y0 = xy[1] 

715 self._width = width 

716 self._height = height 

717 self.angle = float(angle) 

718 self.rotation_point = rotation_point 

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

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

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

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

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

724 # Axes._get_aspect_ratio() 

725 self._aspect_ratio_correction = 1.0 

726 self._convert_units() # Validate the inputs. 

727 

728 def get_path(self): 

729 """Return the vertices of the rectangle.""" 

730 return Path.unit_rectangle() 

731 

732 def _convert_units(self): 

733 """Convert bounds of the rectangle.""" 

734 x0 = self.convert_xunits(self._x0) 

735 y0 = self.convert_yunits(self._y0) 

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

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

738 return x0, y0, x1, y1 

739 

740 def get_patch_transform(self): 

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

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

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

744 # transformation member variable. 

745 bbox = self.get_bbox() 

746 if self.rotation_point == 'center': 

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

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

749 elif self.rotation_point == 'xy': 

750 rotation_point = bbox.x0, bbox.y0 

751 else: 

752 rotation_point = self.rotation_point 

753 return transforms.BboxTransformTo(bbox) \ 

754 + transforms.Affine2D() \ 

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

756 .scale(1, self._aspect_ratio_correction) \ 

757 .rotate_deg(self.angle) \ 

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

759 .translate(*rotation_point) 

760 

761 @property 

762 def rotation_point(self): 

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

764 return self._rotation_point 

765 

766 @rotation_point.setter 

767 def rotation_point(self, value): 

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

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

770 isinstance(value[0], Number) and isinstance(value[1], Number) 

771 ): 

772 self._rotation_point = value 

773 else: 

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

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

776 

777 def get_x(self): 

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

779 return self._x0 

780 

781 def get_y(self): 

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

783 return self._y0 

784 

785 def get_xy(self): 

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

787 return self._x0, self._y0 

788 

789 def get_corners(self): 

790 """ 

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

792 (x0, y0). 

793 """ 

794 return self.get_patch_transform().transform( 

795 [(0, 0), (1, 0), (1, 1), (0, 1)]) 

796 

797 def get_center(self): 

798 """Return the centre of the rectangle.""" 

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

800 

801 def get_width(self): 

802 """Return the width of the rectangle.""" 

803 return self._width 

804 

805 def get_height(self): 

806 """Return the height of the rectangle.""" 

807 return self._height 

808 

809 def get_angle(self): 

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

811 return self.angle 

812 

813 def set_x(self, x): 

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

815 self._x0 = x 

816 self.stale = True 

817 

818 def set_y(self, y): 

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

820 self._y0 = y 

821 self.stale = True 

822 

823 def set_angle(self, angle): 

824 """ 

825 Set the rotation angle in degrees. 

826 

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

828 """ 

829 self.angle = angle 

830 self.stale = True 

831 

832 def set_xy(self, xy): 

833 """ 

834 Set the left and bottom coordinates of the rectangle. 

835 

836 Parameters 

837 ---------- 

838 xy : (float, float) 

839 """ 

840 self._x0, self._y0 = xy 

841 self.stale = True 

842 

843 def set_width(self, w): 

844 """Set the width of the rectangle.""" 

845 self._width = w 

846 self.stale = True 

847 

848 def set_height(self, h): 

849 """Set the height of the rectangle.""" 

850 self._height = h 

851 self.stale = True 

852 

853 def set_bounds(self, *args): 

854 """ 

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

856 

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

858 

859 set_bounds(left, bottom, width, height) 

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

861 

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

863 """ 

864 if len(args) == 1: 

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

866 else: 

867 l, b, w, h = args 

868 self._x0 = l 

869 self._y0 = b 

870 self._width = w 

871 self._height = h 

872 self.stale = True 

873 

874 def get_bbox(self): 

875 """Return the `.Bbox`.""" 

876 x0, y0, x1, y1 = self._convert_units() 

877 return transforms.Bbox.from_extents(x0, y0, x1, y1) 

878 

879 xy = property(get_xy, set_xy) 

880 

881 

882class RegularPolygon(Patch): 

883 """A regular polygon patch.""" 

884 

885 def __str__(self): 

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

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

888 self.orientation) 

889 

890 @_docstring.dedent_interpd 

891 @_api.make_keyword_only("3.6", name="radius") 

892 def __init__(self, xy, numVertices, radius=5, orientation=0, 

893 **kwargs): 

894 """ 

895 Parameters 

896 ---------- 

897 xy : (float, float) 

898 The center position. 

899 

900 numVertices : int 

901 The number of vertices. 

902 

903 radius : float 

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

905 

906 orientation : float 

907 The polygon rotation angle (in radians). 

908 

909 **kwargs 

910 `Patch` properties: 

911 

912 %(Patch:kwdoc)s 

913 """ 

914 self.xy = xy 

915 self.numvertices = numVertices 

916 self.orientation = orientation 

917 self.radius = radius 

918 self._path = Path.unit_regular_polygon(numVertices) 

919 self._patch_transform = transforms.Affine2D() 

920 super().__init__(**kwargs) 

921 

922 def get_path(self): 

923 return self._path 

924 

925 def get_patch_transform(self): 

926 return self._patch_transform.clear() \ 

927 .scale(self.radius) \ 

928 .rotate(self.orientation) \ 

929 .translate(*self.xy) 

930 

931 

932class PathPatch(Patch): 

933 """A general polycurve path patch.""" 

934 

935 _edge_default = True 

936 

937 def __str__(self): 

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

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

940 

941 @_docstring.dedent_interpd 

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

943 """ 

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

945 

946 Valid keyword arguments are: 

947 

948 %(Patch:kwdoc)s 

949 """ 

950 super().__init__(**kwargs) 

951 self._path = path 

952 

953 def get_path(self): 

954 return self._path 

955 

956 def set_path(self, path): 

957 self._path = path 

958 

959 

960class StepPatch(PathPatch): 

961 """ 

962 A path patch describing a stepwise constant function. 

963 

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

965 baseline value. 

966 """ 

967 

968 _edge_default = False 

969 

970 @_docstring.dedent_interpd 

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

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

973 """ 

974 Parameters 

975 ---------- 

976 values : array-like 

977 The step heights. 

978 

979 edges : array-like 

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

981 between which the curve takes on vals values. 

982 

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

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

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

986 

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

988 The bottom value of the bounding edges or when 

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

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

991 path is drawn. 

992 

993 Other valid keyword arguments are: 

994 

995 %(Patch:kwdoc)s 

996 """ 

997 self.orientation = orientation 

998 self._edges = np.asarray(edges) 

999 self._values = np.asarray(values) 

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

1001 self._update_path() 

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

1003 

1004 def _update_path(self): 

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

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

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

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

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

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

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

1012 # Initializing with empty arrays allows supporting empty stairs. 

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

1014 

1015 _nan_mask = np.isnan(self._values) 

1016 if self._baseline is not None: 

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

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

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

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

1021 if self._baseline is None: 

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

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

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

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

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

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

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

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

1030 else: # no baseline 

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

1032 if self.orientation == 'vertical': 

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

1034 else: 

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

1036 verts.append(xy) 

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

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

1039 

1040 def get_data(self): 

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

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

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

1044 

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

1046 """ 

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

1048 

1049 Parameters 

1050 ---------- 

1051 values : 1D array-like or None 

1052 Will not update values, if passing None 

1053 edges : 1D array-like, optional 

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

1055 """ 

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

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

1058 if values is not None: 

1059 self._values = np.asarray(values) 

1060 if edges is not None: 

1061 self._edges = np.asarray(edges) 

1062 if baseline is not None: 

1063 self._baseline = np.asarray(baseline) 

1064 self._update_path() 

1065 self.stale = True 

1066 

1067 

1068class Polygon(Patch): 

1069 """A general polygon patch.""" 

1070 

1071 def __str__(self): 

1072 if len(self._path.vertices): 

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

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

1075 else: 

1076 return "Polygon0()" 

1077 

1078 @_docstring.dedent_interpd 

1079 @_api.make_keyword_only("3.6", name="closed") 

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

1081 """ 

1082 *xy* is a numpy array with shape Nx2. 

1083 

1084 If *closed* is *True*, the polygon will be closed so the 

1085 starting and ending points are the same. 

1086 

1087 Valid keyword arguments are: 

1088 

1089 %(Patch:kwdoc)s 

1090 """ 

1091 super().__init__(**kwargs) 

1092 self._closed = closed 

1093 self.set_xy(xy) 

1094 

1095 def get_path(self): 

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

1097 return self._path 

1098 

1099 def get_closed(self): 

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

1101 return self._closed 

1102 

1103 def set_closed(self, closed): 

1104 """ 

1105 Set whether the polygon is closed. 

1106 

1107 Parameters 

1108 ---------- 

1109 closed : bool 

1110 True if the polygon is closed 

1111 """ 

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

1113 return 

1114 self._closed = bool(closed) 

1115 self.set_xy(self.get_xy()) 

1116 self.stale = True 

1117 

1118 def get_xy(self): 

1119 """ 

1120 Get the vertices of the path. 

1121 

1122 Returns 

1123 ------- 

1124 (N, 2) numpy array 

1125 The coordinates of the vertices. 

1126 """ 

1127 return self._path.vertices 

1128 

1129 def set_xy(self, xy): 

1130 """ 

1131 Set the vertices of the polygon. 

1132 

1133 Parameters 

1134 ---------- 

1135 xy : (N, 2) array-like 

1136 The coordinates of the vertices. 

1137 

1138 Notes 

1139 ----- 

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

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

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

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

1144 """ 

1145 xy = np.asarray(xy) 

1146 nverts, _ = xy.shape 

1147 if self._closed: 

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

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

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

1151 # Path 

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

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

1154 else: 

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

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

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

1158 xy = xy[:-1] 

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

1160 self.stale = True 

1161 

1162 xy = property(get_xy, set_xy, 

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

1164 

1165 

1166class Wedge(Patch): 

1167 """Wedge shaped patch.""" 

1168 

1169 def __str__(self): 

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

1171 self.theta1, self.theta2, self.width) 

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

1173 return fmt % pars 

1174 

1175 @_docstring.dedent_interpd 

1176 @_api.make_keyword_only("3.6", name="width") 

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

1178 """ 

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

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

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

1182 to outer radius *r*. 

1183 

1184 Valid keyword arguments are: 

1185 

1186 %(Patch:kwdoc)s 

1187 """ 

1188 super().__init__(**kwargs) 

1189 self.center = center 

1190 self.r, self.width = r, width 

1191 self.theta1, self.theta2 = theta1, theta2 

1192 self._patch_transform = transforms.IdentityTransform() 

1193 self._recompute_path() 

1194 

1195 def _recompute_path(self): 

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

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

1198 theta1, theta2 = 0, 360 

1199 connector = Path.MOVETO 

1200 else: 

1201 theta1, theta2 = self.theta1, self.theta2 

1202 connector = Path.LINETO 

1203 

1204 # Form the outer ring 

1205 arc = Path.arc(theta1, theta2) 

1206 

1207 if self.width is not None: 

1208 # Partial annulus needs to draw the outer ring 

1209 # followed by a reversed and scaled inner ring 

1210 v1 = arc.vertices 

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

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

1213 c = np.concatenate([ 

1214 arc.codes, arc.codes, [connector, Path.CLOSEPOLY]]) 

1215 c[len(arc.codes)] = connector 

1216 else: 

1217 # Wedge doesn't need an inner ring 

1218 v = np.concatenate([ 

1219 arc.vertices, [(0, 0), arc.vertices[0, :], (0, 0)]]) 

1220 c = np.concatenate([ 

1221 arc.codes, [connector, connector, Path.CLOSEPOLY]]) 

1222 

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

1224 v *= self.r 

1225 v += np.asarray(self.center) 

1226 self._path = Path(v, c) 

1227 

1228 def set_center(self, center): 

1229 self._path = None 

1230 self.center = center 

1231 self.stale = True 

1232 

1233 def set_radius(self, radius): 

1234 self._path = None 

1235 self.r = radius 

1236 self.stale = True 

1237 

1238 def set_theta1(self, theta1): 

1239 self._path = None 

1240 self.theta1 = theta1 

1241 self.stale = True 

1242 

1243 def set_theta2(self, theta2): 

1244 self._path = None 

1245 self.theta2 = theta2 

1246 self.stale = True 

1247 

1248 def set_width(self, width): 

1249 self._path = None 

1250 self.width = width 

1251 self.stale = True 

1252 

1253 def get_path(self): 

1254 if self._path is None: 

1255 self._recompute_path() 

1256 return self._path 

1257 

1258 

1259# COVERAGE NOTE: Not used internally or from examples 

1260class Arrow(Patch): 

1261 """An arrow patch.""" 

1262 

1263 def __str__(self): 

1264 return "Arrow()" 

1265 

1266 _path = Path._create_closed([ 

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

1268 [0.8, 0.3], [0.8, 0.1]]) 

1269 

1270 @_docstring.dedent_interpd 

1271 @_api.make_keyword_only("3.6", name="width") 

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

1273 """ 

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

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

1276 

1277 Parameters 

1278 ---------- 

1279 x : float 

1280 x coordinate of the arrow tail. 

1281 y : float 

1282 y coordinate of the arrow tail. 

1283 dx : float 

1284 Arrow length in the x direction. 

1285 dy : float 

1286 Arrow length in the y direction. 

1287 width : float, default: 1 

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

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

1290 **kwargs 

1291 Keyword arguments control the `Patch` properties: 

1292 

1293 %(Patch:kwdoc)s 

1294 

1295 See Also 

1296 -------- 

1297 FancyArrow 

1298 Patch that allows independent control of the head and tail 

1299 properties. 

1300 """ 

1301 super().__init__(**kwargs) 

1302 self._patch_transform = ( 

1303 transforms.Affine2D() 

1304 .scale(np.hypot(dx, dy), width) 

1305 .rotate(np.arctan2(dy, dx)) 

1306 .translate(x, y) 

1307 .frozen()) 

1308 

1309 def get_path(self): 

1310 return self._path 

1311 

1312 def get_patch_transform(self): 

1313 return self._patch_transform 

1314 

1315 

1316class FancyArrow(Polygon): 

1317 """ 

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

1319 """ 

1320 

1321 _edge_default = True 

1322 

1323 def __str__(self): 

1324 return "FancyArrow()" 

1325 

1326 @_docstring.dedent_interpd 

1327 @_api.make_keyword_only("3.6", name="width") 

1328 def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False, 

1329 head_width=None, head_length=None, shape='full', overhang=0, 

1330 head_starts_at_zero=False, **kwargs): 

1331 """ 

1332 Parameters 

1333 ---------- 

1334 x, y : float 

1335 The x and y coordinates of the arrow base. 

1336 

1337 dx, dy : float 

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

1339 

1340 width : float, default: 0.001 

1341 Width of full arrow tail. 

1342 

1343 length_includes_head : bool, default: False 

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

1345 

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

1347 Total width of the full arrow head. 

1348 

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

1350 Length of arrow head. 

1351 

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

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

1354 

1355 overhang : float, default: 0 

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

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

1358 

1359 head_starts_at_zero : bool, default: False 

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

1361 instead of ending at coordinate 0. 

1362 

1363 **kwargs 

1364 `.Patch` properties: 

1365 

1366 %(Patch:kwdoc)s 

1367 """ 

1368 self._x = x 

1369 self._y = y 

1370 self._dx = dx 

1371 self._dy = dy 

1372 self._width = width 

1373 self._length_includes_head = length_includes_head 

1374 self._head_width = head_width 

1375 self._head_length = head_length 

1376 self._shape = shape 

1377 self._overhang = overhang 

1378 self._head_starts_at_zero = head_starts_at_zero 

1379 self._make_verts() 

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

1381 

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

1383 head_width=None, head_length=None): 

1384 """ 

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

1386 Values left as None will not be updated. 

1387 

1388 Parameters 

1389 ---------- 

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

1391 The x and y coordinates of the arrow base. 

1392 

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

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

1395 

1396 width : float or None, default: None 

1397 Width of full arrow tail. 

1398 

1399 head_width : float or None, default: None 

1400 Total width of the full arrow head. 

1401 

1402 head_length : float or None, default: None 

1403 Length of arrow head. 

1404 """ 

1405 if x is not None: 

1406 self._x = x 

1407 if y is not None: 

1408 self._y = y 

1409 if dx is not None: 

1410 self._dx = dx 

1411 if dy is not None: 

1412 self._dy = dy 

1413 if width is not None: 

1414 self._width = width 

1415 if head_width is not None: 

1416 self._head_width = head_width 

1417 if head_length is not None: 

1418 self._head_length = head_length 

1419 self._make_verts() 

1420 self.set_xy(self.verts) 

1421 

1422 def _make_verts(self): 

1423 if self._head_width is None: 

1424 head_width = 3 * self._width 

1425 else: 

1426 head_width = self._head_width 

1427 if self._head_length is None: 

1428 head_length = 1.5 * head_width 

1429 else: 

1430 head_length = self._head_length 

1431 

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

1433 

1434 if self._length_includes_head: 

1435 length = distance 

1436 else: 

1437 length = distance + head_length 

1438 if not length: 

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

1440 else: 

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

1442 hw, hl = head_width, head_length 

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

1444 left_half_arrow = np.array([ 

1445 [0.0, 0.0], # tip 

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

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

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

1449 [-length, 0], 

1450 ]) 

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

1452 if not self._length_includes_head: 

1453 left_half_arrow += [head_length, 0] 

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

1455 if self._head_starts_at_zero: 

1456 left_half_arrow += [head_length / 2, 0] 

1457 # figure out the shape, and complete accordingly 

1458 if self._shape == 'left': 

1459 coords = left_half_arrow 

1460 else: 

1461 right_half_arrow = left_half_arrow * [1, -1] 

1462 if self._shape == 'right': 

1463 coords = right_half_arrow 

1464 elif self._shape == 'full': 

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

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

1467 # twice caused a problem with xpdf. 

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

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

1470 else: 

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

1472 if distance != 0: 

1473 cx = self._dx / distance 

1474 sx = self._dy / distance 

1475 else: 

1476 # Account for division by zero 

1477 cx, sx = 0, 1 

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

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

1480 self._x + self._dx, 

1481 self._y + self._dy, 

1482 ] 

1483 

1484 

1485_docstring.interpd.update( 

1486 FancyArrow="\n".join( 

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

1488 

1489 

1490class CirclePolygon(RegularPolygon): 

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

1492 

1493 def __str__(self): 

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

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

1496 

1497 @_docstring.dedent_interpd 

1498 @_api.make_keyword_only("3.6", name="resolution") 

1499 def __init__(self, xy, radius=5, 

1500 resolution=20, # the number of vertices 

1501 ** kwargs): 

1502 """ 

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

1504 

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

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

1507 

1508 Valid keyword arguments are: 

1509 

1510 %(Patch:kwdoc)s 

1511 """ 

1512 super().__init__( 

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

1514 

1515 

1516class Ellipse(Patch): 

1517 """A scale-free ellipse.""" 

1518 

1519 def __str__(self): 

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

1521 self.width, self.height, self.angle) 

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

1523 return fmt % pars 

1524 

1525 @_docstring.dedent_interpd 

1526 @_api.make_keyword_only("3.6", name="angle") 

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

1528 """ 

1529 Parameters 

1530 ---------- 

1531 xy : (float, float) 

1532 xy coordinates of ellipse centre. 

1533 width : float 

1534 Total length (diameter) of horizontal axis. 

1535 height : float 

1536 Total length (diameter) of vertical axis. 

1537 angle : float, default: 0 

1538 Rotation in degrees anti-clockwise. 

1539 

1540 Notes 

1541 ----- 

1542 Valid keyword arguments are: 

1543 

1544 %(Patch:kwdoc)s 

1545 """ 

1546 super().__init__(**kwargs) 

1547 

1548 self._center = xy 

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

1550 self._angle = angle 

1551 self._path = Path.unit_circle() 

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

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

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

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

1556 # display coordinate systems. 

1557 self._aspect_ratio_correction = 1.0 

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

1559 self._patch_transform = transforms.IdentityTransform() 

1560 

1561 def _recompute_transform(self): 

1562 """ 

1563 Notes 

1564 ----- 

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

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

1567 call the accessor method and not directly access the transformation 

1568 member variable. 

1569 """ 

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

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

1572 width = self.convert_xunits(self._width) 

1573 height = self.convert_yunits(self._height) 

1574 self._patch_transform = transforms.Affine2D() \ 

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

1576 .rotate_deg(self.angle) \ 

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

1578 .translate(*center) 

1579 

1580 def get_path(self): 

1581 """Return the path of the ellipse.""" 

1582 return self._path 

1583 

1584 def get_patch_transform(self): 

1585 self._recompute_transform() 

1586 return self._patch_transform 

1587 

1588 def set_center(self, xy): 

1589 """ 

1590 Set the center of the ellipse. 

1591 

1592 Parameters 

1593 ---------- 

1594 xy : (float, float) 

1595 """ 

1596 self._center = xy 

1597 self.stale = True 

1598 

1599 def get_center(self): 

1600 """Return the center of the ellipse.""" 

1601 return self._center 

1602 

1603 center = property(get_center, set_center) 

1604 

1605 def set_width(self, width): 

1606 """ 

1607 Set the width of the ellipse. 

1608 

1609 Parameters 

1610 ---------- 

1611 width : float 

1612 """ 

1613 self._width = width 

1614 self.stale = True 

1615 

1616 def get_width(self): 

1617 """ 

1618 Return the width of the ellipse. 

1619 """ 

1620 return self._width 

1621 

1622 width = property(get_width, set_width) 

1623 

1624 def set_height(self, height): 

1625 """ 

1626 Set the height of the ellipse. 

1627 

1628 Parameters 

1629 ---------- 

1630 height : float 

1631 """ 

1632 self._height = height 

1633 self.stale = True 

1634 

1635 def get_height(self): 

1636 """Return the height of the ellipse.""" 

1637 return self._height 

1638 

1639 height = property(get_height, set_height) 

1640 

1641 def set_angle(self, angle): 

1642 """ 

1643 Set the angle of the ellipse. 

1644 

1645 Parameters 

1646 ---------- 

1647 angle : float 

1648 """ 

1649 self._angle = angle 

1650 self.stale = True 

1651 

1652 def get_angle(self): 

1653 """Return the angle of the ellipse.""" 

1654 return self._angle 

1655 

1656 angle = property(get_angle, set_angle) 

1657 

1658 def get_corners(self): 

1659 """ 

1660 Return the corners of the ellipse bounding box. 

1661 

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

1663 lower left corner defined before rotation. 

1664 """ 

1665 return self.get_patch_transform().transform( 

1666 [(-1, -1), (1, -1), (1, 1), (-1, 1)]) 

1667 

1668 

1669class Annulus(Patch): 

1670 """ 

1671 An elliptical annulus. 

1672 """ 

1673 

1674 @_docstring.dedent_interpd 

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

1676 """ 

1677 Parameters 

1678 ---------- 

1679 xy : (float, float) 

1680 xy coordinates of annulus centre. 

1681 r : float or (float, float) 

1682 The radius, or semi-axes: 

1683 

1684 - If float: radius of the outer circle. 

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

1686 width : float 

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

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

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

1690 the semi-minor axis. 

1691 angle : float, default: 0 

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

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

1694 **kwargs 

1695 Keyword arguments control the `Patch` properties: 

1696 

1697 %(Patch:kwdoc)s 

1698 """ 

1699 super().__init__(**kwargs) 

1700 

1701 self.set_radii(r) 

1702 self.center = xy 

1703 self.width = width 

1704 self.angle = angle 

1705 self._path = None 

1706 

1707 def __str__(self): 

1708 if self.a == self.b: 

1709 r = self.a 

1710 else: 

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

1712 

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

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

1715 

1716 def set_center(self, xy): 

1717 """ 

1718 Set the center of the annulus. 

1719 

1720 Parameters 

1721 ---------- 

1722 xy : (float, float) 

1723 """ 

1724 self._center = xy 

1725 self._path = None 

1726 self.stale = True 

1727 

1728 def get_center(self): 

1729 """Return the center of the annulus.""" 

1730 return self._center 

1731 

1732 center = property(get_center, set_center) 

1733 

1734 def set_width(self, width): 

1735 """ 

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

1737 

1738 The width is measured inwards from the outer ellipse. 

1739 

1740 Parameters 

1741 ---------- 

1742 width : float 

1743 """ 

1744 if min(self.a, self.b) <= width: 

1745 raise ValueError( 

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

1747 

1748 self._width = width 

1749 self._path = None 

1750 self.stale = True 

1751 

1752 def get_width(self): 

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

1754 return self._width 

1755 

1756 width = property(get_width, set_width) 

1757 

1758 def set_angle(self, angle): 

1759 """ 

1760 Set the tilt angle of the annulus. 

1761 

1762 Parameters 

1763 ---------- 

1764 angle : float 

1765 """ 

1766 self._angle = angle 

1767 self._path = None 

1768 self.stale = True 

1769 

1770 def get_angle(self): 

1771 """Return the angle of the annulus.""" 

1772 return self._angle 

1773 

1774 angle = property(get_angle, set_angle) 

1775 

1776 def set_semimajor(self, a): 

1777 """ 

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

1779 

1780 Parameters 

1781 ---------- 

1782 a : float 

1783 """ 

1784 self.a = float(a) 

1785 self._path = None 

1786 self.stale = True 

1787 

1788 def set_semiminor(self, b): 

1789 """ 

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

1791 

1792 Parameters 

1793 ---------- 

1794 b : float 

1795 """ 

1796 self.b = float(b) 

1797 self._path = None 

1798 self.stale = True 

1799 

1800 def set_radii(self, r): 

1801 """ 

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

1803 

1804 Parameters 

1805 ---------- 

1806 r : float or (float, float) 

1807 The radius, or semi-axes: 

1808 

1809 - If float: radius of the outer circle. 

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

1811 """ 

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

1813 self.a, self.b = r 

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

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

1816 else: 

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

1818 

1819 self._path = None 

1820 self.stale = True 

1821 

1822 def get_radii(self): 

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

1824 return self.a, self.b 

1825 

1826 radii = property(get_radii, set_radii) 

1827 

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

1829 return transforms.Affine2D() \ 

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

1831 .rotate_deg(self.angle) \ 

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

1833 .transform(verts) 

1834 

1835 def _recompute_path(self): 

1836 # circular arc 

1837 arc = Path.arc(0, 360) 

1838 

1839 # annulus needs to draw an outer ring 

1840 # followed by a reversed and scaled inner ring 

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

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

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

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

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

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

1847 Path.CLOSEPOLY]) 

1848 self._path = Path(v, c) 

1849 

1850 def get_path(self): 

1851 if self._path is None: 

1852 self._recompute_path() 

1853 return self._path 

1854 

1855 

1856class Circle(Ellipse): 

1857 """ 

1858 A circle patch. 

1859 """ 

1860 def __str__(self): 

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

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

1863 return fmt % pars 

1864 

1865 @_docstring.dedent_interpd 

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

1867 """ 

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

1869 

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

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

1872 

1873 Valid keyword arguments are: 

1874 

1875 %(Patch:kwdoc)s 

1876 """ 

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

1878 self.radius = radius 

1879 

1880 def set_radius(self, radius): 

1881 """ 

1882 Set the radius of the circle. 

1883 

1884 Parameters 

1885 ---------- 

1886 radius : float 

1887 """ 

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

1889 self.stale = True 

1890 

1891 def get_radius(self): 

1892 """Return the radius of the circle.""" 

1893 return self.width / 2. 

1894 

1895 radius = property(get_radius, set_radius) 

1896 

1897 

1898class Arc(Ellipse): 

1899 """ 

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

1901 

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

1903 """ 

1904 

1905 def __str__(self): 

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

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

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

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

1910 return fmt % pars 

1911 

1912 @_docstring.dedent_interpd 

1913 @_api.make_keyword_only("3.6", name="angle") 

1914 def __init__(self, xy, width, height, angle=0.0, 

1915 theta1=0.0, theta2=360.0, **kwargs): 

1916 """ 

1917 Parameters 

1918 ---------- 

1919 xy : (float, float) 

1920 The center of the ellipse. 

1921 

1922 width : float 

1923 The length of the horizontal axis. 

1924 

1925 height : float 

1926 The length of the vertical axis. 

1927 

1928 angle : float 

1929 Rotation of the ellipse in degrees (counterclockwise). 

1930 

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

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

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

1934 the absolute starting angle is 135. 

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

1936 The arc is drawn in the counterclockwise direction. 

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

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

1939 taking the input value mod 360. 

1940 

1941 Other Parameters 

1942 ---------------- 

1943 **kwargs : `.Patch` properties 

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

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

1946 

1947 %(Patch:kwdoc)s 

1948 """ 

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

1950 if fill: 

1951 raise ValueError("Arc objects can not be filled") 

1952 

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

1954 

1955 self.theta1 = theta1 

1956 self.theta2 = theta2 

1957 (self._theta1, self._theta2, self._stretched_width, 

1958 self._stretched_height) = self._theta_stretch() 

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

1960 

1961 @artist.allow_rasterization 

1962 def draw(self, renderer): 

1963 """ 

1964 Draw the arc to the given *renderer*. 

1965 

1966 Notes 

1967 ----- 

1968 Ellipses are normally drawn using an approximation that uses 

1969 eight cubic Bezier splines. The error of this approximation 

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

1971 

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

1973 Four Bezier Cubic Splines.* 

1974 

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

1976 

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

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

1979 entire ellipse with enough segments (either splines or line 

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

1981 ellipse is large enough that the error of the spline 

1982 approximation will be visible (greater than one pixel offset 

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

1984 

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

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

1987 (8). The algorithm proceeds as follows: 

1988 

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

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

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

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

1993 doing rotated ellipse intersection directly.) 

1994 

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

1996 

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

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

1999 

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

2001 

2002 3. Proceeding counterclockwise starting in the positive 

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

2004 pairs of vertices are drawn using the Bezier arc 

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

2006 """ 

2007 if not self.get_visible(): 

2008 return 

2009 

2010 self._recompute_transform() 

2011 

2012 self._update_path() 

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

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

2015 # because we want the transform from dataspace to the 

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

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

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

2019 # space to screen space). 

2020 data_to_screen_trans = self.get_data_transform() 

2021 pwidth, pheight = ( 

2022 data_to_screen_trans.transform((self._stretched_width, 

2023 self._stretched_height)) - 

2024 data_to_screen_trans.transform((0, 0))) 

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

2026 

2027 if pwidth < inv_error and pheight < inv_error: 

2028 return Patch.draw(self, renderer) 

2029 

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

2031 dx = x1 - x0 

2032 dy = y1 - y0 

2033 dr2 = dx * dx + dy * dy 

2034 D = x0 * y1 - x1 * y0 

2035 D2 = D * D 

2036 discrim = dr2 - D2 

2037 if discrim >= 0.0: 

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

2039 sqrt_discrim = np.sqrt(discrim) 

2040 return np.array( 

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

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

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

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

2045 else: 

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

2047 

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

2049 epsilon = 1e-9 

2050 if x1 < x0: 

2051 x0e, x1e = x1, x0 

2052 else: 

2053 x0e, x1e = x0, x1 

2054 if y1 < y0: 

2055 y0e, y1e = y1, y0 

2056 else: 

2057 y0e, y1e = y0, y1 

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

2059 xs, ys = xys.T 

2060 return xys[ 

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

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

2063 ] 

2064 

2065 # Transform the axes (or figure) box_path so that it is relative to 

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

2067 # ellipse. 

2068 box_path_transform = ( 

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

2070 - self.get_transform()) 

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

2072 

2073 thetas = set() 

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

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

2076 xy = segment_circle_intersect(*p0, *p1) 

2077 x, y = xy.T 

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

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

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

2081 thetas.update( 

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

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

2084 last_theta = self._theta1 

2085 theta1_rad = np.deg2rad(self._theta1) 

2086 inside = box_path.contains_point( 

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

2088 ) 

2089 

2090 # save original path 

2091 path_original = self._path 

2092 for theta in thetas: 

2093 if inside: 

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

2095 Patch.draw(self, renderer) 

2096 inside = False 

2097 else: 

2098 inside = True 

2099 last_theta = theta 

2100 

2101 # restore original path 

2102 self._path = path_original 

2103 

2104 def _update_path(self): 

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

2106 stretched = self._theta_stretch() 

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

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

2109 self._stretched_height))): 

2110 (self._theta1, self._theta2, self._stretched_width, 

2111 self._stretched_height) = stretched 

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

2113 

2114 def _theta_stretch(self): 

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

2116 # stretching when calculating angles to draw between 

2117 def theta_stretch(theta, scale): 

2118 theta = np.deg2rad(theta) 

2119 x = np.cos(theta) 

2120 y = np.sin(theta) 

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

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

2123 return (stheta + 360) % 360 

2124 

2125 width = self.convert_xunits(self.width) 

2126 height = self.convert_yunits(self.height) 

2127 if ( 

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

2129 width != height 

2130 # and we are not doing a full circle. 

2131 # 

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

2133 # stretching (due to both float precision limitations and 

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

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

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

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

2138 ): 

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

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

2141 return theta1, theta2, width, height 

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

2143 

2144 

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

2146 """ 

2147 A debug function to draw a rectangle around the bounding 

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

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

2150 

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

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

2153 """ 

2154 if props is None: 

2155 props = {} 

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

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

2158 pad = renderer.points_to_pixels(pad) 

2159 bbox = artist.get_window_extent(renderer) 

2160 r = Rectangle( 

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

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

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

2164 r.update(props) 

2165 r.draw(renderer) 

2166 

2167 

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

2169 """ 

2170 A debug function to draw a rectangle around the bounding 

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

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

2173 """ 

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

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

2176 if trans is not None: 

2177 r.set_transform(trans) 

2178 r.draw(renderer) 

2179 

2180 

2181class _Style: 

2182 """ 

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

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

2185 provides some helper functions. 

2186 """ 

2187 

2188 def __init_subclass__(cls): 

2189 # Automatically perform docstring interpolation on the subclasses: 

2190 # This allows listing the supported styles via 

2191 # - %(BoxStyle:table)s 

2192 # - %(ConnectionStyle:table)s 

2193 # - %(ArrowStyle:table)s 

2194 # and additionally adding .. ACCEPTS: blocks via 

2195 # - %(BoxStyle:table_and_accepts)s 

2196 # - %(ConnectionStyle:table_and_accepts)s 

2197 # - %(ArrowStyle:table_and_accepts)s 

2198 _docstring.interpd.update({ 

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

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

2201 cls.pprint_styles() 

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

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

2204 + "]") 

2205 }) 

2206 

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

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

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

2210 # of style names to style classes. 

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

2212 _name = _list[0].lower() 

2213 try: 

2214 _cls = cls._style_list[_name] 

2215 except KeyError as err: 

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

2217 try: 

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

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

2220 except ValueError as err: 

2221 raise ValueError( 

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

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

2224 

2225 @classmethod 

2226 def get_styles(cls): 

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

2228 return cls._style_list 

2229 

2230 @classmethod 

2231 def pprint_styles(cls): 

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

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

2234 *[(cls.__name__, 

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

2236 f'``{name}``', 

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

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

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

2240 # Convert to rst table. 

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

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

2243 rst_table = '\n'.join([ 

2244 '', 

2245 table_formatstr, 

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

2247 table_formatstr, 

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

2249 for row in table[1:]], 

2250 table_formatstr, 

2251 ]) 

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

2253 

2254 @classmethod 

2255 def register(cls, name, style): 

2256 """Register a new style.""" 

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

2258 raise ValueError("%s must be a subclass of %s" % (style, 

2259 cls._Base)) 

2260 cls._style_list[name] = style 

2261 

2262 

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

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

2265 if cls is None: 

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

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

2268 return cls 

2269 

2270 

2271@_docstring.dedent_interpd 

2272class BoxStyle(_Style): 

2273 """ 

2274 `BoxStyle` is a container class which defines several 

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

2276 

2277 A style object can be created as:: 

2278 

2279 BoxStyle.Round(pad=0.2) 

2280 

2281 or:: 

2282 

2283 BoxStyle("Round", pad=0.2) 

2284 

2285 or:: 

2286 

2287 BoxStyle("Round, pad=0.2") 

2288 

2289 The following boxstyle classes are defined. 

2290 

2291 %(BoxStyle:table)s 

2292 

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

2294 

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

2296 

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

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

2299 """ 

2300 

2301 _style_list = {} 

2302 

2303 @_register_style(_style_list) 

2304 class Square: 

2305 """A square box.""" 

2306 

2307 def __init__(self, pad=0.3): 

2308 """ 

2309 Parameters 

2310 ---------- 

2311 pad : float, default: 0.3 

2312 The amount of padding around the original box. 

2313 """ 

2314 self.pad = pad 

2315 

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

2317 pad = mutation_size * self.pad 

2318 # width and height with padding added. 

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

2320 # boundary of the padded box 

2321 x0, y0 = x0 - pad, y0 - pad 

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

2323 return Path._create_closed( 

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

2325 

2326 @_register_style(_style_list) 

2327 class Circle: 

2328 """A circular box.""" 

2329 

2330 def __init__(self, pad=0.3): 

2331 """ 

2332 Parameters 

2333 ---------- 

2334 pad : float, default: 0.3 

2335 The amount of padding around the original box. 

2336 """ 

2337 self.pad = pad 

2338 

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

2340 pad = mutation_size * self.pad 

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

2342 # boundary of the padded box 

2343 x0, y0 = x0 - pad, y0 - pad 

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

2345 max(width, height) / 2) 

2346 

2347 @_register_style(_style_list) 

2348 class LArrow: 

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

2350 

2351 def __init__(self, pad=0.3): 

2352 """ 

2353 Parameters 

2354 ---------- 

2355 pad : float, default: 0.3 

2356 The amount of padding around the original box. 

2357 """ 

2358 self.pad = pad 

2359 

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

2361 # padding 

2362 pad = mutation_size * self.pad 

2363 # width and height with padding added. 

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

2365 # boundary of the padded box 

2366 x0, y0 = x0 - pad, y0 - pad, 

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

2368 

2369 dx = (y1 - y0) / 2 

2370 dxx = dx / 2 

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

2372 

2373 return Path._create_closed( 

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

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

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

2377 (x0 + dxx, y0)]) 

2378 

2379 @_register_style(_style_list) 

2380 class RArrow(LArrow): 

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

2382 

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

2384 p = BoxStyle.LArrow.__call__( 

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

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

2387 return p 

2388 

2389 @_register_style(_style_list) 

2390 class DArrow: 

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

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

2393 

2394 def __init__(self, pad=0.3): 

2395 """ 

2396 Parameters 

2397 ---------- 

2398 pad : float, default: 0.3 

2399 The amount of padding around the original box. 

2400 """ 

2401 self.pad = pad 

2402 

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

2404 # padding 

2405 pad = mutation_size * self.pad 

2406 # width and height with padding added. 

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

2408 height = height + 2 * pad 

2409 # boundary of the padded box 

2410 x0, y0 = x0 - pad, y0 - pad 

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

2412 

2413 dx = (y1 - y0) / 2 

2414 dxx = dx / 2 

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

2416 

2417 return Path._create_closed([ 

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

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

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

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

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

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

2424 (x0 + dxx, y0)]) 

2425 

2426 @_register_style(_style_list) 

2427 class Round: 

2428 """A box with round corners.""" 

2429 

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

2431 """ 

2432 Parameters 

2433 ---------- 

2434 pad : float, default: 0.3 

2435 The amount of padding around the original box. 

2436 rounding_size : float, default: *pad* 

2437 Radius of the corners. 

2438 """ 

2439 self.pad = pad 

2440 self.rounding_size = rounding_size 

2441 

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

2443 

2444 # padding 

2445 pad = mutation_size * self.pad 

2446 

2447 # size of the rounding corner 

2448 if self.rounding_size: 

2449 dr = mutation_size * self.rounding_size 

2450 else: 

2451 dr = pad 

2452 

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

2454 

2455 x0, y0 = x0 - pad, y0 - pad, 

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

2457 

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

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

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

2461 (x1 - dr, y0), 

2462 (x1, y0), (x1, y0 + dr), 

2463 (x1, y1 - dr), 

2464 (x1, y1), (x1 - dr, y1), 

2465 (x0 + dr, y1), 

2466 (x0, y1), (x0, y1 - dr), 

2467 (x0, y0 + dr), 

2468 (x0, y0), (x0 + dr, y0), 

2469 (x0 + dr, y0)] 

2470 

2471 com = [Path.MOVETO, 

2472 Path.LINETO, 

2473 Path.CURVE3, Path.CURVE3, 

2474 Path.LINETO, 

2475 Path.CURVE3, Path.CURVE3, 

2476 Path.LINETO, 

2477 Path.CURVE3, Path.CURVE3, 

2478 Path.LINETO, 

2479 Path.CURVE3, Path.CURVE3, 

2480 Path.CLOSEPOLY] 

2481 

2482 path = Path(cp, com) 

2483 

2484 return path 

2485 

2486 @_register_style(_style_list) 

2487 class Round4: 

2488 """A box with rounded edges.""" 

2489 

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

2491 """ 

2492 Parameters 

2493 ---------- 

2494 pad : float, default: 0.3 

2495 The amount of padding around the original box. 

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

2497 Rounding of edges. 

2498 """ 

2499 self.pad = pad 

2500 self.rounding_size = rounding_size 

2501 

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

2503 

2504 # padding 

2505 pad = mutation_size * self.pad 

2506 

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

2508 if self.rounding_size: 

2509 dr = mutation_size * self.rounding_size 

2510 else: 

2511 dr = pad / 2. 

2512 

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

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

2515 

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

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

2518 

2519 cp = [(x0, y0), 

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

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

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

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

2524 (x0, y0)] 

2525 

2526 com = [Path.MOVETO, 

2527 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2528 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2529 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2530 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2531 Path.CLOSEPOLY] 

2532 

2533 path = Path(cp, com) 

2534 

2535 return path 

2536 

2537 @_register_style(_style_list) 

2538 class Sawtooth: 

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

2540 

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

2542 """ 

2543 Parameters 

2544 ---------- 

2545 pad : float, default: 0.3 

2546 The amount of padding around the original box. 

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

2548 Size of the sawtooth. 

2549 """ 

2550 self.pad = pad 

2551 self.tooth_size = tooth_size 

2552 

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

2554 

2555 # padding 

2556 pad = mutation_size * self.pad 

2557 

2558 # size of sawtooth 

2559 if self.tooth_size is None: 

2560 tooth_size = self.pad * .5 * mutation_size 

2561 else: 

2562 tooth_size = self.tooth_size * mutation_size 

2563 

2564 tooth_size2 = tooth_size / 2 

2565 width = width + 2 * pad - tooth_size 

2566 height = height + 2 * pad - tooth_size 

2567 

2568 # the sizes of the vertical and horizontal sawtooth are 

2569 # separately adjusted to fit the given box size. 

2570 dsx_n = int(round((width - tooth_size) / (tooth_size * 2))) * 2 

2571 dsx = (width - tooth_size) / dsx_n 

2572 dsy_n = int(round((height - tooth_size) / (tooth_size * 2))) * 2 

2573 dsy = (height - tooth_size) / dsy_n 

2574 

2575 x0, y0 = x0 - pad + tooth_size2, y0 - pad + tooth_size2 

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

2577 

2578 bottom_saw_x = [ 

2579 x0, 

2580 *(x0 + tooth_size2 + dsx * .5 * np.arange(dsx_n * 2)), 

2581 x1 - tooth_size2, 

2582 ] 

2583 bottom_saw_y = [ 

2584 y0, 

2585 *([y0 - tooth_size2, y0, y0 + tooth_size2, y0] * dsx_n), 

2586 y0 - tooth_size2, 

2587 ] 

2588 right_saw_x = [ 

2589 x1, 

2590 *([x1 + tooth_size2, x1, x1 - tooth_size2, x1] * dsx_n), 

2591 x1 + tooth_size2, 

2592 ] 

2593 right_saw_y = [ 

2594 y0, 

2595 *(y0 + tooth_size2 + dsy * .5 * np.arange(dsy_n * 2)), 

2596 y1 - tooth_size2, 

2597 ] 

2598 top_saw_x = [ 

2599 x1, 

2600 *(x1 - tooth_size2 - dsx * .5 * np.arange(dsx_n * 2)), 

2601 x0 + tooth_size2, 

2602 ] 

2603 top_saw_y = [ 

2604 y1, 

2605 *([y1 + tooth_size2, y1, y1 - tooth_size2, y1] * dsx_n), 

2606 y1 + tooth_size2, 

2607 ] 

2608 left_saw_x = [ 

2609 x0, 

2610 *([x0 - tooth_size2, x0, x0 + tooth_size2, x0] * dsy_n), 

2611 x0 - tooth_size2, 

2612 ] 

2613 left_saw_y = [ 

2614 y1, 

2615 *(y1 - tooth_size2 - dsy * .5 * np.arange(dsy_n * 2)), 

2616 y0 + tooth_size2, 

2617 ] 

2618 

2619 saw_vertices = [*zip(bottom_saw_x, bottom_saw_y), 

2620 *zip(right_saw_x, right_saw_y), 

2621 *zip(top_saw_x, top_saw_y), 

2622 *zip(left_saw_x, left_saw_y), 

2623 (bottom_saw_x[0], bottom_saw_y[0])] 

2624 

2625 return saw_vertices 

2626 

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

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

2629 height, mutation_size) 

2630 path = Path(saw_vertices, closed=True) 

2631 return path 

2632 

2633 @_register_style(_style_list) 

2634 class Roundtooth(Sawtooth): 

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

2636 

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

2638 saw_vertices = self._get_sawtooth_vertices(x0, y0, 

2639 width, height, 

2640 mutation_size) 

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

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

2643 codes = ([Path.MOVETO] + 

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

2645 [Path.CLOSEPOLY]) 

2646 return Path(saw_vertices, codes) 

2647 

2648 

2649@_docstring.dedent_interpd 

2650class ConnectionStyle(_Style): 

2651 """ 

2652 `ConnectionStyle` is a container class which defines 

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

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

2655 

2656 A connectionstyle object can be either created as:: 

2657 

2658 ConnectionStyle.Arc3(rad=0.2) 

2659 

2660 or:: 

2661 

2662 ConnectionStyle("Arc3", rad=0.2) 

2663 

2664 or:: 

2665 

2666 ConnectionStyle("Arc3, rad=0.2") 

2667 

2668 The following classes are defined 

2669 

2670 %(ConnectionStyle:table)s 

2671 

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

2673 whose call signature is:: 

2674 

2675 __call__(self, posA, posB, 

2676 patchA=None, patchB=None, 

2677 shrinkA=2., shrinkB=2.) 

2678 

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

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

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

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

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

2684 which is given in points. 

2685 """ 

2686 

2687 _style_list = {} 

2688 

2689 class _Base: 

2690 """ 

2691 A base class for connectionstyle classes. The subclass needs 

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

2693 

2694 connect(posA, posB) 

2695 

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

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

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

2699 helper methods. 

2700 """ 

2701 

2702 class SimpleEvent: 

2703 def __init__(self, xy): 

2704 self.x, self.y = xy 

2705 

2706 def _clip(self, path, patchA, patchB): 

2707 """ 

2708 Clip the path to the boundary of the patchA and patchB. 

2709 The starting point of the path needed to be inside of the 

2710 patchA and the end point inside the patch B. The *contains* 

2711 methods of each patch object is utilized to test if the point 

2712 is inside the path. 

2713 """ 

2714 

2715 if patchA: 

2716 def insideA(xy_display): 

2717 xy_event = ConnectionStyle._Base.SimpleEvent(xy_display) 

2718 return patchA.contains(xy_event)[0] 

2719 

2720 try: 

2721 left, right = split_path_inout(path, insideA) 

2722 except ValueError: 

2723 right = path 

2724 

2725 path = right 

2726 

2727 if patchB: 

2728 def insideB(xy_display): 

2729 xy_event = ConnectionStyle._Base.SimpleEvent(xy_display) 

2730 return patchB.contains(xy_event)[0] 

2731 

2732 try: 

2733 left, right = split_path_inout(path, insideB) 

2734 except ValueError: 

2735 left = path 

2736 

2737 path = left 

2738 

2739 return path 

2740 

2741 def _shrink(self, path, shrinkA, shrinkB): 

2742 """ 

2743 Shrink the path by fixed size (in points) with shrinkA and shrinkB. 

2744 """ 

2745 if shrinkA: 

2746 insideA = inside_circle(*path.vertices[0], shrinkA) 

2747 try: 

2748 left, path = split_path_inout(path, insideA) 

2749 except ValueError: 

2750 pass 

2751 if shrinkB: 

2752 insideB = inside_circle(*path.vertices[-1], shrinkB) 

2753 try: 

2754 path, right = split_path_inout(path, insideB) 

2755 except ValueError: 

2756 pass 

2757 return path 

2758 

2759 def __call__(self, posA, posB, 

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

2761 """ 

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

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

2764 """ 

2765 path = self.connect(posA, posB) 

2766 clipped_path = self._clip(path, patchA, patchB) 

2767 shrunk_path = self._shrink(clipped_path, shrinkA, shrinkB) 

2768 return shrunk_path 

2769 

2770 @_register_style(_style_list) 

2771 class Arc3(_Base): 

2772 """ 

2773 Creates a simple quadratic Bézier curve between two 

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

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

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

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

2778 """ 

2779 

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

2781 """ 

2782 Parameters 

2783 ---------- 

2784 rad : float 

2785 Curvature of the curve. 

2786 """ 

2787 self.rad = rad 

2788 

2789 def connect(self, posA, posB): 

2790 x1, y1 = posA 

2791 x2, y2 = posB 

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

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

2794 

2795 f = self.rad 

2796 

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

2798 

2799 vertices = [(x1, y1), 

2800 (cx, cy), 

2801 (x2, y2)] 

2802 codes = [Path.MOVETO, 

2803 Path.CURVE3, 

2804 Path.CURVE3] 

2805 

2806 return Path(vertices, codes) 

2807 

2808 @_register_style(_style_list) 

2809 class Angle3(_Base): 

2810 """ 

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

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

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

2814 *angleB*, respectively. 

2815 """ 

2816 

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

2818 """ 

2819 Parameters 

2820 ---------- 

2821 angleA : float 

2822 Starting angle of the path. 

2823 

2824 angleB : float 

2825 Ending angle of the path. 

2826 """ 

2827 

2828 self.angleA = angleA 

2829 self.angleB = angleB 

2830 

2831 def connect(self, posA, posB): 

2832 x1, y1 = posA 

2833 x2, y2 = posB 

2834 

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

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

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

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

2839 

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

2841 x2, y2, cosB, sinB) 

2842 

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

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

2845 

2846 return Path(vertices, codes) 

2847 

2848 @_register_style(_style_list) 

2849 class Angle(_Base): 

2850 """ 

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

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

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

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

2855 The connecting edges are rounded with *rad*. 

2856 """ 

2857 

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

2859 """ 

2860 Parameters 

2861 ---------- 

2862 angleA : float 

2863 Starting angle of the path. 

2864 

2865 angleB : float 

2866 Ending angle of the path. 

2867 

2868 rad : float 

2869 Rounding radius of the edge. 

2870 """ 

2871 

2872 self.angleA = angleA 

2873 self.angleB = angleB 

2874 

2875 self.rad = rad 

2876 

2877 def connect(self, posA, posB): 

2878 x1, y1 = posA 

2879 x2, y2 = posB 

2880 

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

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

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

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

2885 

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

2887 x2, y2, cosB, sinB) 

2888 

2889 vertices = [(x1, y1)] 

2890 codes = [Path.MOVETO] 

2891 

2892 if self.rad == 0.: 

2893 vertices.append((cx, cy)) 

2894 codes.append(Path.LINETO) 

2895 else: 

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

2897 d1 = np.hypot(dx1, dy1) 

2898 f1 = self.rad / d1 

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

2900 d2 = np.hypot(dx2, dy2) 

2901 f2 = self.rad / d2 

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

2903 (cx, cy), 

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

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

2906 

2907 vertices.append((x2, y2)) 

2908 codes.append(Path.LINETO) 

2909 

2910 return Path(vertices, codes) 

2911 

2912 @_register_style(_style_list) 

2913 class Arc(_Base): 

2914 """ 

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

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

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

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

2919 rounded with *rad*. 

2920 """ 

2921 

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

2923 """ 

2924 Parameters 

2925 ---------- 

2926 angleA : float 

2927 Starting angle of the path. 

2928 

2929 angleB : float 

2930 Ending angle of the path. 

2931 

2932 armA : float or None 

2933 Length of the starting arm. 

2934 

2935 armB : float or None 

2936 Length of the ending arm. 

2937 

2938 rad : float 

2939 Rounding radius of the edges. 

2940 """ 

2941 

2942 self.angleA = angleA 

2943 self.angleB = angleB 

2944 self.armA = armA 

2945 self.armB = armB 

2946 

2947 self.rad = rad 

2948 

2949 def connect(self, posA, posB): 

2950 x1, y1 = posA 

2951 x2, y2 = posB 

2952 

2953 vertices = [(x1, y1)] 

2954 rounded = [] 

2955 codes = [Path.MOVETO] 

2956 

2957 if self.armA: 

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

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

2960 # x_armA, y_armB 

2961 d = self.armA - self.rad 

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

2963 d = self.armA 

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

2965 

2966 if self.armB: 

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

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

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

2970 

2971 if rounded: 

2972 xp, yp = rounded[-1] 

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

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

2975 

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

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

2978 vertices.extend(rounded) 

2979 codes.extend([Path.LINETO, 

2980 Path.CURVE3, 

2981 Path.CURVE3]) 

2982 else: 

2983 xp, yp = vertices[-1] 

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

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

2986 

2987 d = dd - self.rad 

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

2989 (x_armB, y_armB)] 

2990 

2991 if rounded: 

2992 xp, yp = rounded[-1] 

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

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

2995 

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

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

2998 vertices.extend(rounded) 

2999 codes.extend([Path.LINETO, 

3000 Path.CURVE3, 

3001 Path.CURVE3]) 

3002 

3003 vertices.append((x2, y2)) 

3004 codes.append(Path.LINETO) 

3005 

3006 return Path(vertices, codes) 

3007 

3008 @_register_style(_style_list) 

3009 class Bar(_Base): 

3010 """ 

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

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

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

3014 Same for *armB*. 

3015 """ 

3016 

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

3018 """ 

3019 Parameters 

3020 ---------- 

3021 armA : float 

3022 Minimum length of armA. 

3023 

3024 armB : float 

3025 Minimum length of armB. 

3026 

3027 fraction : float 

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

3029 added to armA and armB. 

3030 

3031 angle : float or None 

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

3033 """ 

3034 self.armA = armA 

3035 self.armB = armB 

3036 self.fraction = fraction 

3037 self.angle = angle 

3038 

3039 def connect(self, posA, posB): 

3040 x1, y1 = posA 

3041 x20, y20 = x2, y2 = posB 

3042 

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

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

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

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

3047 

3048 armA, armB = self.armA, self.armB 

3049 

3050 if self.angle is not None: 

3051 theta0 = np.deg2rad(self.angle) 

3052 dtheta = theta1 - theta0 

3053 dl = dd * math.sin(dtheta) 

3054 dL = dd * math.cos(dtheta) 

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

3056 armB = armB - dl 

3057 

3058 # update 

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

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

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

3062 

3063 arm = max(armA, armB) 

3064 f = self.fraction * dd + arm 

3065 

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

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

3068 

3069 vertices = [(x1, y1), 

3070 (cx1, cy1), 

3071 (cx2, cy2), 

3072 (x20, y20)] 

3073 codes = [Path.MOVETO, 

3074 Path.LINETO, 

3075 Path.LINETO, 

3076 Path.LINETO] 

3077 

3078 return Path(vertices, codes) 

3079 

3080 

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

3082 """ 

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

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

3085 """ 

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

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

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

3089 

3090 return x2, y2 

3091 

3092 

3093@_docstring.dedent_interpd 

3094class ArrowStyle(_Style): 

3095 """ 

3096 `ArrowStyle` is a container class which defines several 

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

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

3099 

3100 An arrowstyle object can be either created as:: 

3101 

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

3103 

3104 or:: 

3105 

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

3107 

3108 or:: 

3109 

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

3111 

3112 The following classes are defined 

3113 

3114 %(ArrowStyle:table)s 

3115 

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

3117 whose call signature is:: 

3118 

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

3120 

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

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

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

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

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

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

3127 classes support it. 

3128 

3129 Notes 

3130 ----- 

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

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

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

3134 

3135 .. plot:: gallery/text_labels_and_annotations/angles_on_bracket_arrows.py 

3136 """ 

3137 

3138 _style_list = {} 

3139 

3140 class _Base: 

3141 """ 

3142 Arrow Transmuter Base class 

3143 

3144 ArrowTransmuterBase and its derivatives are used to make a fancy 

3145 arrow around a given path. The __call__ method returns a path 

3146 (which will be used to create a PathPatch instance) and a boolean 

3147 value indicating the path is open therefore is not fillable. This 

3148 class is not an artist and actual drawing of the fancy arrow is 

3149 done by the FancyArrowPatch class. 

3150 """ 

3151 

3152 # The derived classes are required to be able to be initialized 

3153 # w/o arguments, i.e., all its argument (except self) must have 

3154 # the default values. 

3155 

3156 @staticmethod 

3157 def ensure_quadratic_bezier(path): 

3158 """ 

3159 Some ArrowStyle classes only works with a simple quadratic 

3160 Bézier curve (created with `.ConnectionStyle.Arc3` or 

3161 `.ConnectionStyle.Angle3`). This static method checks if the 

3162 provided path is a simple quadratic Bézier curve and returns its 

3163 control points if true. 

3164 """ 

3165 segments = list(path.iter_segments()) 

3166 if (len(segments) != 2 or segments[0][1] != Path.MOVETO or 

3167 segments[1][1] != Path.CURVE3): 

3168 raise ValueError( 

3169 "'path' is not a valid quadratic Bezier curve") 

3170 return [*segments[0][0], *segments[1][0]] 

3171 

3172 def transmute(self, path, mutation_size, linewidth): 

3173 """ 

3174 The transmute method is the very core of the ArrowStyle class and 

3175 must be overridden in the subclasses. It receives the *path* 

3176 object along which the arrow will be drawn, and the 

3177 *mutation_size*, with which the arrow head etc. will be scaled. 

3178 The *linewidth* may be used to adjust the path so that it does not 

3179 pass beyond the given points. It returns a tuple of a `.Path` 

3180 instance and a boolean. The boolean value indicate whether the 

3181 path can be filled or not. The return value can also be a list of 

3182 paths and list of booleans of the same length. 

3183 """ 

3184 raise NotImplementedError('Derived must override') 

3185 

3186 def __call__(self, path, mutation_size, linewidth, 

3187 aspect_ratio=1.): 

3188 """ 

3189 The __call__ method is a thin wrapper around the transmute method 

3190 and takes care of the aspect ratio. 

3191 """ 

3192 

3193 if aspect_ratio is not None: 

3194 # Squeeze the given height by the aspect_ratio 

3195 vertices = path.vertices / [1, aspect_ratio] 

3196 path_shrunk = Path(vertices, path.codes) 

3197 # call transmute method with squeezed height. 

3198 path_mutated, fillable = self.transmute(path_shrunk, 

3199 mutation_size, 

3200 linewidth) 

3201 if np.iterable(fillable): 

3202 # Restore the height 

3203 path_list = [Path(p.vertices * [1, aspect_ratio], p.codes) 

3204 for p in path_mutated] 

3205 return path_list, fillable 

3206 else: 

3207 return path_mutated, fillable 

3208 else: 

3209 return self.transmute(path, mutation_size, linewidth) 

3210 

3211 class _Curve(_Base): 

3212 """ 

3213 A simple arrow which will work with any path instance. The 

3214 returned path is the concatenation of the original path, and at 

3215 most two paths representing the arrow head or bracket at the start 

3216 point and at the end point. The arrow heads can be either open 

3217 or closed. 

3218 """ 

3219 

3220 beginarrow = endarrow = None # Whether arrows are drawn. 

3221 arrow = "-" 

3222 fillbegin = fillend = False # Whether arrows are filled. 

3223 

3224 def __init__(self, head_length=.4, head_width=.2, widthA=1., widthB=1., 

3225 lengthA=0.2, lengthB=0.2, angleA=0, angleB=0, scaleA=None, 

3226 scaleB=None): 

3227 """ 

3228 Parameters 

3229 ---------- 

3230 head_length : float, default: 0.4 

3231 Length of the arrow head, relative to *mutation_scale*. 

3232 head_width : float, default: 0.2 

3233 Width of the arrow head, relative to *mutation_scale*. 

3234 widthA : float, default: 1.0 

3235 Width of the bracket at the beginning of the arrow 

3236 widthB : float, default: 1.0 

3237 Width of the bracket at the end of the arrow 

3238 lengthA : float, default: 0.2 

3239 Length of the bracket at the beginning of the arrow 

3240 lengthB : float, default: 0.2 

3241 Length of the bracket at the end of the arrow 

3242 angleA : float, default 0 

3243 Orientation of the bracket at the beginning, as a 

3244 counterclockwise angle. 0 degrees means perpendicular 

3245 to the line. 

3246 angleB : float, default 0 

3247 Orientation of the bracket at the beginning, as a 

3248 counterclockwise angle. 0 degrees means perpendicular 

3249 to the line. 

3250 scaleA : float, default *mutation_size* 

3251 The mutation_size for the beginning bracket 

3252 scaleB : float, default *mutation_size* 

3253 The mutation_size for the end bracket 

3254 """ 

3255 

3256 self.head_length, self.head_width = head_length, head_width 

3257 self.widthA, self.widthB = widthA, widthB 

3258 self.lengthA, self.lengthB = lengthA, lengthB 

3259 self.angleA, self.angleB = angleA, angleB 

3260 self.scaleA, self.scaleB = scaleA, scaleB 

3261 

3262 self._beginarrow_head = False 

3263 self._beginarrow_bracket = False 

3264 self._endarrow_head = False 

3265 self._endarrow_bracket = False 

3266 

3267 if "-" not in self.arrow: 

3268 raise ValueError("arrow must have the '-' between " 

3269 "the two heads") 

3270 

3271 beginarrow, endarrow = self.arrow.split("-", 1) 

3272 

3273 if beginarrow == "<": 

3274 self._beginarrow_head = True 

3275 self._beginarrow_bracket = False 

3276 elif beginarrow == "<|": 

3277 self._beginarrow_head = True 

3278 self._beginarrow_bracket = False 

3279 self.fillbegin = True 

3280 elif beginarrow in ("]", "|"): 

3281 self._beginarrow_head = False 

3282 self._beginarrow_bracket = True 

3283 elif self.beginarrow is True: 

3284 self._beginarrow_head = True 

3285 self._beginarrow_bracket = False 

3286 

3287 _api.warn_deprecated('3.5', name="beginarrow", 

3288 alternative="arrow") 

3289 elif self.beginarrow is False: 

3290 self._beginarrow_head = False 

3291 self._beginarrow_bracket = False 

3292 

3293 _api.warn_deprecated('3.5', name="beginarrow", 

3294 alternative="arrow") 

3295 

3296 if endarrow == ">": 

3297 self._endarrow_head = True 

3298 self._endarrow_bracket = False 

3299 elif endarrow == "|>": 

3300 self._endarrow_head = True 

3301 self._endarrow_bracket = False 

3302 self.fillend = True 

3303 elif endarrow in ("[", "|"): 

3304 self._endarrow_head = False 

3305 self._endarrow_bracket = True 

3306 elif self.endarrow is True: 

3307 self._endarrow_head = True 

3308 self._endarrow_bracket = False 

3309 

3310 _api.warn_deprecated('3.5', name="endarrow", 

3311 alternative="arrow") 

3312 elif self.endarrow is False: 

3313 self._endarrow_head = False 

3314 self._endarrow_bracket = False 

3315 

3316 _api.warn_deprecated('3.5', name="endarrow", 

3317 alternative="arrow") 

3318 

3319 super().__init__() 

3320 

3321 def _get_arrow_wedge(self, x0, y0, x1, y1, 

3322 head_dist, cos_t, sin_t, linewidth): 

3323 """ 

3324 Return the paths for arrow heads. Since arrow lines are 

3325 drawn with capstyle=projected, The arrow goes beyond the 

3326 desired point. This method also returns the amount of the path 

3327 to be shrunken so that it does not overshoot. 

3328 """ 

3329 

3330 # arrow from x0, y0 to x1, y1 

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

3332 

3333 cp_distance = np.hypot(dx, dy) 

3334 

3335 # pad_projected : amount of pad to account the 

3336 # overshooting of the projection of the wedge 

3337 pad_projected = (.5 * linewidth / sin_t) 

3338 

3339 # Account for division by zero 

3340 if cp_distance == 0: 

3341 cp_distance = 1 

3342 

3343 # apply pad for projected edge 

3344 ddx = pad_projected * dx / cp_distance 

3345 ddy = pad_projected * dy / cp_distance 

3346 

3347 # offset for arrow wedge 

3348 dx = dx / cp_distance * head_dist 

3349 dy = dy / cp_distance * head_dist 

3350 

3351 dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy 

3352 dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy 

3353 

3354 vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1), 

3355 (x1 + ddx, y1 + ddy), 

3356 (x1 + ddx + dx2, y1 + ddy + dy2)] 

3357 codes_arrow = [Path.MOVETO, 

3358 Path.LINETO, 

3359 Path.LINETO] 

3360 

3361 return vertices_arrow, codes_arrow, ddx, ddy 

3362 

3363 def _get_bracket(self, x0, y0, 

3364 x1, y1, width, length, angle): 

3365 

3366 cos_t, sin_t = get_cos_sin(x1, y1, x0, y0) 

3367 

3368 # arrow from x0, y0 to x1, y1 

3369 from matplotlib.bezier import get_normal_points 

3370 x1, y1, x2, y2 = get_normal_points(x0, y0, cos_t, sin_t, width) 

3371 

3372 dx, dy = length * cos_t, length * sin_t 

3373 

3374 vertices_arrow = [(x1 + dx, y1 + dy), 

3375 (x1, y1), 

3376 (x2, y2), 

3377 (x2 + dx, y2 + dy)] 

3378 codes_arrow = [Path.MOVETO, 

3379 Path.LINETO, 

3380 Path.LINETO, 

3381 Path.LINETO] 

3382 

3383 if angle: 

3384 trans = transforms.Affine2D().rotate_deg_around(x0, y0, angle) 

3385 vertices_arrow = trans.transform(vertices_arrow) 

3386 

3387 return vertices_arrow, codes_arrow 

3388 

3389 def transmute(self, path, mutation_size, linewidth): 

3390 # Doc-string inherited 

3391 if self._beginarrow_head or self._endarrow_head: 

3392 head_length = self.head_length * mutation_size 

3393 head_width = self.head_width * mutation_size 

3394 head_dist = np.hypot(head_length, head_width) 

3395 cos_t, sin_t = head_length / head_dist, head_width / head_dist 

3396 

3397 scaleA = mutation_size if self.scaleA is None else self.scaleA 

3398 scaleB = mutation_size if self.scaleB is None else self.scaleB 

3399 

3400 # begin arrow 

3401 x0, y0 = path.vertices[0] 

3402 x1, y1 = path.vertices[1] 

3403 

3404 # If there is no room for an arrow and a line, then skip the arrow 

3405 has_begin_arrow = self._beginarrow_head and (x0, y0) != (x1, y1) 

3406 verticesA, codesA, ddxA, ddyA = ( 

3407 self._get_arrow_wedge(x1, y1, x0, y0, 

3408 head_dist, cos_t, sin_t, linewidth) 

3409 if has_begin_arrow 

3410 else ([], [], 0, 0) 

3411 ) 

3412 

3413 # end arrow 

3414 x2, y2 = path.vertices[-2] 

3415 x3, y3 = path.vertices[-1] 

3416 

3417 # If there is no room for an arrow and a line, then skip the arrow 

3418 has_end_arrow = self._endarrow_head and (x2, y2) != (x3, y3) 

3419 verticesB, codesB, ddxB, ddyB = ( 

3420 self._get_arrow_wedge(x2, y2, x3, y3, 

3421 head_dist, cos_t, sin_t, linewidth) 

3422 if has_end_arrow 

3423 else ([], [], 0, 0) 

3424 ) 

3425 

3426 # This simple code will not work if ddx, ddy is greater than the 

3427 # separation between vertices. 

3428 _path = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)], 

3429 path.vertices[1:-1], 

3430 [(x3 + ddxB, y3 + ddyB)]]), 

3431 path.codes)] 

3432 _fillable = [False] 

3433 

3434 if has_begin_arrow: 

3435 if self.fillbegin: 

3436 p = np.concatenate([verticesA, [verticesA[0], 

3437 verticesA[0]], ]) 

3438 c = np.concatenate([codesA, [Path.LINETO, Path.CLOSEPOLY]]) 

3439 _path.append(Path(p, c)) 

3440 _fillable.append(True) 

3441 else: 

3442 _path.append(Path(verticesA, codesA)) 

3443 _fillable.append(False) 

3444 elif self._beginarrow_bracket: 

3445 x0, y0 = path.vertices[0] 

3446 x1, y1 = path.vertices[1] 

3447 verticesA, codesA = self._get_bracket(x0, y0, x1, y1, 

3448 self.widthA * scaleA, 

3449 self.lengthA * scaleA, 

3450 self.angleA) 

3451 

3452 _path.append(Path(verticesA, codesA)) 

3453 _fillable.append(False) 

3454 

3455 if has_end_arrow: 

3456 if self.fillend: 

3457 _fillable.append(True) 

3458 p = np.concatenate([verticesB, [verticesB[0], 

3459 verticesB[0]], ]) 

3460 c = np.concatenate([codesB, [Path.LINETO, Path.CLOSEPOLY]]) 

3461 _path.append(Path(p, c)) 

3462 else: 

3463 _fillable.append(False) 

3464 _path.append(Path(verticesB, codesB)) 

3465 elif self._endarrow_bracket: 

3466 x0, y0 = path.vertices[-1] 

3467 x1, y1 = path.vertices[-2] 

3468 verticesB, codesB = self._get_bracket(x0, y0, x1, y1, 

3469 self.widthB * scaleB, 

3470 self.lengthB * scaleB, 

3471 self.angleB) 

3472 

3473 _path.append(Path(verticesB, codesB)) 

3474 _fillable.append(False) 

3475 

3476 return _path, _fillable 

3477 

3478 @_register_style(_style_list, name="-") 

3479 class Curve(_Curve): 

3480 """A simple curve without any arrow head.""" 

3481 

3482 def __init__(self): # hide head_length, head_width 

3483 # These attributes (whose values come from backcompat) only matter 

3484 # if someone modifies beginarrow/etc. on an ArrowStyle instance. 

3485 super().__init__(head_length=.2, head_width=.1) 

3486 

3487 @_register_style(_style_list, name="<-") 

3488 class CurveA(_Curve): 

3489 """An arrow with a head at its start point.""" 

3490 arrow = "<-" 

3491 

3492 @_register_style(_style_list, name="->") 

3493 class CurveB(_Curve): 

3494 """An arrow with a head at its end point.""" 

3495 arrow = "->" 

3496 

3497 @_register_style(_style_list, name="<->") 

3498 class CurveAB(_Curve): 

3499 """An arrow with heads both at the start and the end point.""" 

3500 arrow = "<->" 

3501 

3502 @_register_style(_style_list, name="<|-") 

3503 class CurveFilledA(_Curve): 

3504 """An arrow with filled triangle head at the start.""" 

3505 arrow = "<|-" 

3506 

3507 @_register_style(_style_list, name="-|>") 

3508 class CurveFilledB(_Curve): 

3509 """An arrow with filled triangle head at the end.""" 

3510 arrow = "-|>" 

3511 

3512 @_register_style(_style_list, name="<|-|>") 

3513 class CurveFilledAB(_Curve): 

3514 """An arrow with filled triangle heads at both ends.""" 

3515 arrow = "<|-|>" 

3516 

3517 @_register_style(_style_list, name="]-") 

3518 class BracketA(_Curve): 

3519 """An arrow with an outward square bracket at its start.""" 

3520 arrow = "]-" 

3521 

3522 def __init__(self, widthA=1., lengthA=0.2, angleA=0): 

3523 """ 

3524 Parameters 

3525 ---------- 

3526 widthA : float, default: 1.0 

3527 Width of the bracket. 

3528 lengthA : float, default: 0.2 

3529 Length of the bracket. 

3530 angleA : float, default: 0 degrees 

3531 Orientation of the bracket, as a counterclockwise angle. 

3532 0 degrees means perpendicular to the line. 

3533 """ 

3534 super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA) 

3535 

3536 @_register_style(_style_list, name="-[") 

3537 class BracketB(_Curve): 

3538 """An arrow with an outward square bracket at its end.""" 

3539 arrow = "-[" 

3540 

3541 def __init__(self, widthB=1., lengthB=0.2, angleB=0): 

3542 """ 

3543 Parameters 

3544 ---------- 

3545 widthB : float, default: 1.0 

3546 Width of the bracket. 

3547 lengthB : float, default: 0.2 

3548 Length of the bracket. 

3549 angleB : float, default: 0 degrees 

3550 Orientation of the bracket, as a counterclockwise angle. 

3551 0 degrees means perpendicular to the line. 

3552 """ 

3553 super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB) 

3554 

3555 @_register_style(_style_list, name="]-[") 

3556 class BracketAB(_Curve): 

3557 """An arrow with outward square brackets at both ends.""" 

3558 arrow = "]-[" 

3559 

3560 def __init__(self, 

3561 widthA=1., lengthA=0.2, angleA=0, 

3562 widthB=1., lengthB=0.2, angleB=0): 

3563 """ 

3564 Parameters 

3565 ---------- 

3566 widthA, widthB : float, default: 1.0 

3567 Width of the bracket. 

3568 lengthA, lengthB : float, default: 0.2 

3569 Length of the bracket. 

3570 angleA, angleB : float, default: 0 degrees 

3571 Orientation of the bracket, as a counterclockwise angle. 

3572 0 degrees means perpendicular to the line. 

3573 """ 

3574 super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA, 

3575 widthB=widthB, lengthB=lengthB, angleB=angleB) 

3576 

3577 @_register_style(_style_list, name="|-|") 

3578 class BarAB(_Curve): 

3579 """An arrow with vertical bars ``|`` at both ends.""" 

3580 arrow = "|-|" 

3581 

3582 def __init__(self, widthA=1., angleA=0, widthB=1., angleB=0): 

3583 """ 

3584 Parameters 

3585 ---------- 

3586 widthA, widthB : float, default: 1.0 

3587 Width of the bracket. 

3588 angleA, angleB : float, default: 0 degrees 

3589 Orientation of the bracket, as a counterclockwise angle. 

3590 0 degrees means perpendicular to the line. 

3591 """ 

3592 super().__init__(widthA=widthA, lengthA=0, angleA=angleA, 

3593 widthB=widthB, lengthB=0, angleB=angleB) 

3594 

3595 @_register_style(_style_list, name=']->') 

3596 class BracketCurve(_Curve): 

3597 """ 

3598 An arrow with an outward square bracket at its start and a head at 

3599 the end. 

3600 """ 

3601 arrow = "]->" 

3602 

3603 def __init__(self, widthA=1., lengthA=0.2, angleA=None): 

3604 """ 

3605 Parameters 

3606 ---------- 

3607 widthA : float, default: 1.0 

3608 Width of the bracket. 

3609 lengthA : float, default: 0.2 

3610 Length of the bracket. 

3611 angleA : float, default: 0 degrees 

3612 Orientation of the bracket, as a counterclockwise angle. 

3613 0 degrees means perpendicular to the line. 

3614 """ 

3615 super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA) 

3616 

3617 @_register_style(_style_list, name='<-[') 

3618 class CurveBracket(_Curve): 

3619 """ 

3620 An arrow with an outward square bracket at its end and a head at 

3621 the start. 

3622 """ 

3623 arrow = "<-[" 

3624 

3625 def __init__(self, widthB=1., lengthB=0.2, angleB=None): 

3626 """ 

3627 Parameters 

3628 ---------- 

3629 widthB : float, default: 1.0 

3630 Width of the bracket. 

3631 lengthB : float, default: 0.2 

3632 Length of the bracket. 

3633 angleB : 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__(widthB=widthB, lengthB=lengthB, angleB=angleB) 

3638 

3639 @_register_style(_style_list) 

3640 class Simple(_Base): 

3641 """A simple arrow. Only works with a quadratic Bézier curve.""" 

3642 

3643 def __init__(self, head_length=.5, head_width=.5, tail_width=.2): 

3644 """ 

3645 Parameters 

3646 ---------- 

3647 head_length : float, default: 0.5 

3648 Length of the arrow head. 

3649 

3650 head_width : float, default: 0.5 

3651 Width of the arrow head. 

3652 

3653 tail_width : float, default: 0.2 

3654 Width of the arrow tail. 

3655 """ 

3656 self.head_length, self.head_width, self.tail_width = \ 

3657 head_length, head_width, tail_width 

3658 super().__init__() 

3659 

3660 def transmute(self, path, mutation_size, linewidth): 

3661 # Doc-string inherited 

3662 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3663 

3664 # divide the path into a head and a tail 

3665 head_length = self.head_length * mutation_size 

3666 in_f = inside_circle(x2, y2, head_length) 

3667 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3668 

3669 try: 

3670 arrow_out, arrow_in = \ 

3671 split_bezier_intersecting_with_closedpath( 

3672 arrow_path, in_f, tolerance=0.01) 

3673 except NonIntersectingPathException: 

3674 # if this happens, make a straight line of the head_length 

3675 # long. 

3676 x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) 

3677 x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2) 

3678 arrow_in = [(x0, y0), (x1n, y1n), (x2, y2)] 

3679 arrow_out = None 

3680 

3681 # head 

3682 head_width = self.head_width * mutation_size 

3683 head_left, head_right = make_wedged_bezier2(arrow_in, 

3684 head_width / 2., wm=.5) 

3685 

3686 # tail 

3687 if arrow_out is not None: 

3688 tail_width = self.tail_width * mutation_size 

3689 tail_left, tail_right = get_parallels(arrow_out, 

3690 tail_width / 2.) 

3691 

3692 patch_path = [(Path.MOVETO, tail_right[0]), 

3693 (Path.CURVE3, tail_right[1]), 

3694 (Path.CURVE3, tail_right[2]), 

3695 (Path.LINETO, head_right[0]), 

3696 (Path.CURVE3, head_right[1]), 

3697 (Path.CURVE3, head_right[2]), 

3698 (Path.CURVE3, head_left[1]), 

3699 (Path.CURVE3, head_left[0]), 

3700 (Path.LINETO, tail_left[2]), 

3701 (Path.CURVE3, tail_left[1]), 

3702 (Path.CURVE3, tail_left[0]), 

3703 (Path.LINETO, tail_right[0]), 

3704 (Path.CLOSEPOLY, tail_right[0]), 

3705 ] 

3706 else: 

3707 patch_path = [(Path.MOVETO, head_right[0]), 

3708 (Path.CURVE3, head_right[1]), 

3709 (Path.CURVE3, head_right[2]), 

3710 (Path.CURVE3, head_left[1]), 

3711 (Path.CURVE3, head_left[0]), 

3712 (Path.CLOSEPOLY, head_left[0]), 

3713 ] 

3714 

3715 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3716 

3717 return path, True 

3718 

3719 @_register_style(_style_list) 

3720 class Fancy(_Base): 

3721 """A fancy arrow. Only works with a quadratic Bézier curve.""" 

3722 

3723 def __init__(self, head_length=.4, head_width=.4, tail_width=.4): 

3724 """ 

3725 Parameters 

3726 ---------- 

3727 head_length : float, default: 0.4 

3728 Length of the arrow head. 

3729 

3730 head_width : float, default: 0.4 

3731 Width of the arrow head. 

3732 

3733 tail_width : float, default: 0.4 

3734 Width of the arrow tail. 

3735 """ 

3736 self.head_length, self.head_width, self.tail_width = \ 

3737 head_length, head_width, tail_width 

3738 super().__init__() 

3739 

3740 def transmute(self, path, mutation_size, linewidth): 

3741 # Doc-string inherited 

3742 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3743 

3744 # divide the path into a head and a tail 

3745 head_length = self.head_length * mutation_size 

3746 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3747 

3748 # path for head 

3749 in_f = inside_circle(x2, y2, head_length) 

3750 try: 

3751 path_out, path_in = split_bezier_intersecting_with_closedpath( 

3752 arrow_path, in_f, tolerance=0.01) 

3753 except NonIntersectingPathException: 

3754 # if this happens, make a straight line of the head_length 

3755 # long. 

3756 x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) 

3757 x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2) 

3758 arrow_path = [(x0, y0), (x1n, y1n), (x2, y2)] 

3759 path_head = arrow_path 

3760 else: 

3761 path_head = path_in 

3762 

3763 # path for head 

3764 in_f = inside_circle(x2, y2, head_length * .8) 

3765 path_out, path_in = split_bezier_intersecting_with_closedpath( 

3766 arrow_path, in_f, tolerance=0.01) 

3767 path_tail = path_out 

3768 

3769 # head 

3770 head_width = self.head_width * mutation_size 

3771 head_l, head_r = make_wedged_bezier2(path_head, 

3772 head_width / 2., 

3773 wm=.6) 

3774 

3775 # tail 

3776 tail_width = self.tail_width * mutation_size 

3777 tail_left, tail_right = make_wedged_bezier2(path_tail, 

3778 tail_width * .5, 

3779 w1=1., wm=0.6, w2=0.3) 

3780 

3781 # path for head 

3782 in_f = inside_circle(x0, y0, tail_width * .3) 

3783 path_in, path_out = split_bezier_intersecting_with_closedpath( 

3784 arrow_path, in_f, tolerance=0.01) 

3785 tail_start = path_in[-1] 

3786 

3787 head_right, head_left = head_r, head_l 

3788 patch_path = [(Path.MOVETO, tail_start), 

3789 (Path.LINETO, tail_right[0]), 

3790 (Path.CURVE3, tail_right[1]), 

3791 (Path.CURVE3, tail_right[2]), 

3792 (Path.LINETO, head_right[0]), 

3793 (Path.CURVE3, head_right[1]), 

3794 (Path.CURVE3, head_right[2]), 

3795 (Path.CURVE3, head_left[1]), 

3796 (Path.CURVE3, head_left[0]), 

3797 (Path.LINETO, tail_left[2]), 

3798 (Path.CURVE3, tail_left[1]), 

3799 (Path.CURVE3, tail_left[0]), 

3800 (Path.LINETO, tail_start), 

3801 (Path.CLOSEPOLY, tail_start), 

3802 ] 

3803 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3804 

3805 return path, True 

3806 

3807 @_register_style(_style_list) 

3808 class Wedge(_Base): 

3809 """ 

3810 Wedge(?) shape. Only works with a quadratic Bézier curve. The 

3811 start point has a width of the *tail_width* and the end point has a 

3812 width of 0. At the middle, the width is *shrink_factor*x*tail_width*. 

3813 """ 

3814 

3815 def __init__(self, tail_width=.3, shrink_factor=0.5): 

3816 """ 

3817 Parameters 

3818 ---------- 

3819 tail_width : float, default: 0.3 

3820 Width of the tail. 

3821 

3822 shrink_factor : float, default: 0.5 

3823 Fraction of the arrow width at the middle point. 

3824 """ 

3825 self.tail_width = tail_width 

3826 self.shrink_factor = shrink_factor 

3827 super().__init__() 

3828 

3829 def transmute(self, path, mutation_size, linewidth): 

3830 # Doc-string inherited 

3831 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3832 

3833 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3834 b_plus, b_minus = make_wedged_bezier2( 

3835 arrow_path, 

3836 self.tail_width * mutation_size / 2., 

3837 wm=self.shrink_factor) 

3838 

3839 patch_path = [(Path.MOVETO, b_plus[0]), 

3840 (Path.CURVE3, b_plus[1]), 

3841 (Path.CURVE3, b_plus[2]), 

3842 (Path.LINETO, b_minus[2]), 

3843 (Path.CURVE3, b_minus[1]), 

3844 (Path.CURVE3, b_minus[0]), 

3845 (Path.CLOSEPOLY, b_minus[0]), 

3846 ] 

3847 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3848 

3849 return path, True 

3850 

3851 

3852class FancyBboxPatch(Patch): 

3853 """ 

3854 A fancy box around a rectangle with lower left at *xy* = (*x*, *y*) 

3855 with specified width and height. 

3856 

3857 `.FancyBboxPatch` is similar to `.Rectangle`, but it draws a fancy box 

3858 around the rectangle. The transformation of the rectangle box to the 

3859 fancy box is delegated to the style classes defined in `.BoxStyle`. 

3860 """ 

3861 

3862 _edge_default = True 

3863 

3864 def __str__(self): 

3865 s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)" 

3866 return s % (self._x, self._y, self._width, self._height) 

3867 

3868 @_docstring.dedent_interpd 

3869 @_api.make_keyword_only("3.6", name="mutation_scale") 

3870 @_api.delete_parameter("3.4", "bbox_transmuter", alternative="boxstyle") 

3871 def __init__(self, xy, width, height, 

3872 boxstyle="round", bbox_transmuter=None, 

3873 mutation_scale=1, mutation_aspect=1, 

3874 **kwargs): 

3875 """ 

3876 Parameters 

3877 ---------- 

3878 xy : float, float 

3879 The lower left corner of the box. 

3880 

3881 width : float 

3882 The width of the box. 

3883 

3884 height : float 

3885 The height of the box. 

3886 

3887 boxstyle : str or `matplotlib.patches.BoxStyle` 

3888 The style of the fancy box. This can either be a `.BoxStyle` 

3889 instance or a string of the style name and optionally comma 

3890 separated attributes (e.g. "Round, pad=0.2"). This string is 

3891 passed to `.BoxStyle` to construct a `.BoxStyle` object. See 

3892 there for a full documentation. 

3893 

3894 The following box styles are available: 

3895 

3896 %(BoxStyle:table)s 

3897 

3898 mutation_scale : float, default: 1 

3899 Scaling factor applied to the attributes of the box style 

3900 (e.g. pad or rounding_size). 

3901 

3902 mutation_aspect : float, default: 1 

3903 The height of the rectangle will be squeezed by this value before 

3904 the mutation and the mutated box will be stretched by the inverse 

3905 of it. For example, this allows different horizontal and vertical 

3906 padding. 

3907 

3908 Other Parameters 

3909 ---------------- 

3910 **kwargs : `.Patch` properties 

3911 

3912 %(Patch:kwdoc)s 

3913 """ 

3914 

3915 super().__init__(**kwargs) 

3916 

3917 self._x = xy[0] 

3918 self._y = xy[1] 

3919 self._width = width 

3920 self._height = height 

3921 

3922 if boxstyle == "custom": 

3923 _api.warn_deprecated( 

3924 "3.4", message="Support for boxstyle='custom' is deprecated " 

3925 "since %(since)s and will be removed %(removal)s; directly " 

3926 "pass a boxstyle instance as the boxstyle parameter instead.") 

3927 if bbox_transmuter is None: 

3928 raise ValueError("bbox_transmuter argument is needed with " 

3929 "custom boxstyle") 

3930 self._bbox_transmuter = bbox_transmuter 

3931 else: 

3932 self.set_boxstyle(boxstyle) 

3933 

3934 self._mutation_scale = mutation_scale 

3935 self._mutation_aspect = mutation_aspect 

3936 

3937 self.stale = True 

3938 

3939 @_docstring.dedent_interpd 

3940 def set_boxstyle(self, boxstyle=None, **kwargs): 

3941 """ 

3942 Set the box style, possibly with further attributes. 

3943 

3944 Attributes from the previous box style are not reused. 

3945 

3946 Without argument (or with ``boxstyle=None``), the available box styles 

3947 are returned as a human-readable string. 

3948 

3949 Parameters 

3950 ---------- 

3951 boxstyle : str or `matplotlib.patches.BoxStyle` 

3952 The style of the box: either a `.BoxStyle` instance, or a string, 

3953 which is the style name and optionally comma separated attributes 

3954 (e.g. "Round,pad=0.2"). Such a string is used to construct a 

3955 `.BoxStyle` object, as documented in that class. 

3956 

3957 The following box styles are available: 

3958 

3959 %(BoxStyle:table_and_accepts)s 

3960 

3961 **kwargs 

3962 Additional attributes for the box style. See the table above for 

3963 supported parameters. 

3964 

3965 Examples 

3966 -------- 

3967 :: 

3968 

3969 set_boxstyle("Round,pad=0.2") 

3970 set_boxstyle("round", pad=0.2) 

3971 """ 

3972 if boxstyle is None: 

3973 return BoxStyle.pprint_styles() 

3974 self._bbox_transmuter = ( 

3975 BoxStyle(boxstyle, **kwargs) 

3976 if isinstance(boxstyle, str) else boxstyle) 

3977 self.stale = True 

3978 

3979 def get_boxstyle(self): 

3980 """Return the boxstyle object.""" 

3981 return self._bbox_transmuter 

3982 

3983 def set_mutation_scale(self, scale): 

3984 """ 

3985 Set the mutation scale. 

3986 

3987 Parameters 

3988 ---------- 

3989 scale : float 

3990 """ 

3991 self._mutation_scale = scale 

3992 self.stale = True 

3993 

3994 def get_mutation_scale(self): 

3995 """Return the mutation scale.""" 

3996 return self._mutation_scale 

3997 

3998 def set_mutation_aspect(self, aspect): 

3999 """ 

4000 Set the aspect ratio of the bbox mutation. 

4001 

4002 Parameters 

4003 ---------- 

4004 aspect : float 

4005 """ 

4006 self._mutation_aspect = aspect 

4007 self.stale = True 

4008 

4009 def get_mutation_aspect(self): 

4010 """Return the aspect ratio of the bbox mutation.""" 

4011 return (self._mutation_aspect if self._mutation_aspect is not None 

4012 else 1) # backcompat. 

4013 

4014 def get_path(self): 

4015 """Return the mutated path of the rectangle.""" 

4016 boxstyle = self.get_boxstyle() 

4017 x = self._x 

4018 y = self._y 

4019 width = self._width 

4020 height = self._height 

4021 m_scale = self.get_mutation_scale() 

4022 m_aspect = self.get_mutation_aspect() 

4023 # Squeeze the given height by the aspect_ratio. 

4024 y, height = y / m_aspect, height / m_aspect 

4025 # Call boxstyle with squeezed height. 

4026 try: 

4027 inspect.signature(boxstyle).bind(x, y, width, height, m_scale) 

4028 except TypeError: 

4029 # Don't apply aspect twice. 

4030 path = boxstyle(x, y, width, height, m_scale, 1) 

4031 _api.warn_deprecated( 

4032 "3.4", message="boxstyles must be callable without the " 

4033 "'mutation_aspect' parameter since %(since)s; support for the " 

4034 "old call signature will be removed %(removal)s.") 

4035 else: 

4036 path = boxstyle(x, y, width, height, m_scale) 

4037 vertices, codes = path.vertices, path.codes 

4038 # Restore the height. 

4039 vertices[:, 1] = vertices[:, 1] * m_aspect 

4040 return Path(vertices, codes) 

4041 

4042 # Following methods are borrowed from the Rectangle class. 

4043 

4044 def get_x(self): 

4045 """Return the left coord of the rectangle.""" 

4046 return self._x 

4047 

4048 def get_y(self): 

4049 """Return the bottom coord of the rectangle.""" 

4050 return self._y 

4051 

4052 def get_width(self): 

4053 """Return the width of the rectangle.""" 

4054 return self._width 

4055 

4056 def get_height(self): 

4057 """Return the height of the rectangle.""" 

4058 return self._height 

4059 

4060 def set_x(self, x): 

4061 """ 

4062 Set the left coord of the rectangle. 

4063 

4064 Parameters 

4065 ---------- 

4066 x : float 

4067 """ 

4068 self._x = x 

4069 self.stale = True 

4070 

4071 def set_y(self, y): 

4072 """ 

4073 Set the bottom coord of the rectangle. 

4074 

4075 Parameters 

4076 ---------- 

4077 y : float 

4078 """ 

4079 self._y = y 

4080 self.stale = True 

4081 

4082 def set_width(self, w): 

4083 """ 

4084 Set the rectangle width. 

4085 

4086 Parameters 

4087 ---------- 

4088 w : float 

4089 """ 

4090 self._width = w 

4091 self.stale = True 

4092 

4093 def set_height(self, h): 

4094 """ 

4095 Set the rectangle height. 

4096 

4097 Parameters 

4098 ---------- 

4099 h : float 

4100 """ 

4101 self._height = h 

4102 self.stale = True 

4103 

4104 def set_bounds(self, *args): 

4105 """ 

4106 Set the bounds of the rectangle. 

4107 

4108 Call signatures:: 

4109 

4110 set_bounds(left, bottom, width, height) 

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

4112 

4113 Parameters 

4114 ---------- 

4115 left, bottom : float 

4116 The coordinates of the bottom left corner of the rectangle. 

4117 width, height : float 

4118 The width/height of the rectangle. 

4119 """ 

4120 if len(args) == 1: 

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

4122 else: 

4123 l, b, w, h = args 

4124 self._x = l 

4125 self._y = b 

4126 self._width = w 

4127 self._height = h 

4128 self.stale = True 

4129 

4130 def get_bbox(self): 

4131 """Return the `.Bbox`.""" 

4132 return transforms.Bbox.from_bounds(self._x, self._y, 

4133 self._width, self._height) 

4134 

4135 

4136class FancyArrowPatch(Patch): 

4137 """ 

4138 A fancy arrow patch. It draws an arrow using the `ArrowStyle`. 

4139 

4140 The head and tail positions are fixed at the specified start and end points 

4141 of the arrow, but the size and shape (in display coordinates) of the arrow 

4142 does not change when the axis is moved or zoomed. 

4143 """ 

4144 _edge_default = True 

4145 

4146 def __str__(self): 

4147 if self._posA_posB is not None: 

4148 (x1, y1), (x2, y2) = self._posA_posB 

4149 return f"{type(self).__name__}(({x1:g}, {y1:g})->({x2:g}, {y2:g}))" 

4150 else: 

4151 return f"{type(self).__name__}({self._path_original})" 

4152 

4153 @_docstring.dedent_interpd 

4154 @_api.make_keyword_only("3.6", name="path") 

4155 def __init__(self, posA=None, posB=None, path=None, 

4156 arrowstyle="simple", connectionstyle="arc3", 

4157 patchA=None, patchB=None, 

4158 shrinkA=2, shrinkB=2, 

4159 mutation_scale=1, mutation_aspect=1, 

4160 **kwargs): 

4161 """ 

4162 There are two ways for defining an arrow: 

4163 

4164 - If *posA* and *posB* are given, a path connecting two points is 

4165 created according to *connectionstyle*. The path will be 

4166 clipped with *patchA* and *patchB* and further shrunken by 

4167 *shrinkA* and *shrinkB*. An arrow is drawn along this 

4168 resulting path using the *arrowstyle* parameter. 

4169 

4170 - Alternatively if *path* is provided, an arrow is drawn along this 

4171 path and *patchA*, *patchB*, *shrinkA*, and *shrinkB* are ignored. 

4172 

4173 Parameters 

4174 ---------- 

4175 posA, posB : (float, float), default: None 

4176 (x, y) coordinates of arrow tail and arrow head respectively. 

4177 

4178 path : `~matplotlib.path.Path`, default: None 

4179 If provided, an arrow is drawn along this path and *patchA*, 

4180 *patchB*, *shrinkA*, and *shrinkB* are ignored. 

4181 

4182 arrowstyle : str or `.ArrowStyle`, default: 'simple' 

4183 The `.ArrowStyle` with which the fancy arrow is drawn. If a 

4184 string, it should be one of the available arrowstyle names, with 

4185 optional comma-separated attributes. The optional attributes are 

4186 meant to be scaled with the *mutation_scale*. The following arrow 

4187 styles are available: 

4188 

4189 %(ArrowStyle:table)s 

4190 

4191 connectionstyle : str or `.ConnectionStyle` or None, optional, \ 

4192default: 'arc3' 

4193 The `.ConnectionStyle` with which *posA* and *posB* are connected. 

4194 If a string, it should be one of the available connectionstyle 

4195 names, with optional comma-separated attributes. The following 

4196 connection styles are available: 

4197 

4198 %(ConnectionStyle:table)s 

4199 

4200 patchA, patchB : `.Patch`, default: None 

4201 Head and tail patches, respectively. 

4202 

4203 shrinkA, shrinkB : float, default: 2 

4204 Shrinking factor of the tail and head of the arrow respectively. 

4205 

4206 mutation_scale : float, default: 1 

4207 Value with which attributes of *arrowstyle* (e.g., *head_length*) 

4208 will be scaled. 

4209 

4210 mutation_aspect : None or float, default: None 

4211 The height of the rectangle will be squeezed by this value before 

4212 the mutation and the mutated box will be stretched by the inverse 

4213 of it. 

4214 

4215 Other Parameters 

4216 ---------------- 

4217 **kwargs : `.Patch` properties, optional 

4218 Here is a list of available `.Patch` properties: 

4219 

4220 %(Patch:kwdoc)s 

4221 

4222 In contrast to other patches, the default ``capstyle`` and 

4223 ``joinstyle`` for `FancyArrowPatch` are set to ``"round"``. 

4224 """ 

4225 # Traditionally, the cap- and joinstyle for FancyArrowPatch are round 

4226 kwargs.setdefault("joinstyle", JoinStyle.round) 

4227 kwargs.setdefault("capstyle", CapStyle.round) 

4228 

4229 super().__init__(**kwargs) 

4230 

4231 if posA is not None and posB is not None and path is None: 

4232 self._posA_posB = [posA, posB] 

4233 

4234 if connectionstyle is None: 

4235 connectionstyle = "arc3" 

4236 self.set_connectionstyle(connectionstyle) 

4237 

4238 elif posA is None and posB is None and path is not None: 

4239 self._posA_posB = None 

4240 else: 

4241 raise ValueError("Either posA and posB, or path need to provided") 

4242 

4243 self.patchA = patchA 

4244 self.patchB = patchB 

4245 self.shrinkA = shrinkA 

4246 self.shrinkB = shrinkB 

4247 

4248 self._path_original = path 

4249 

4250 self.set_arrowstyle(arrowstyle) 

4251 

4252 self._mutation_scale = mutation_scale 

4253 self._mutation_aspect = mutation_aspect 

4254 

4255 self._dpi_cor = 1.0 

4256 

4257 def set_positions(self, posA, posB): 

4258 """ 

4259 Set the start and end positions of the connecting path. 

4260 

4261 Parameters 

4262 ---------- 

4263 posA, posB : None, tuple 

4264 (x, y) coordinates of arrow tail and arrow head respectively. If 

4265 `None` use current value. 

4266 """ 

4267 if posA is not None: 

4268 self._posA_posB[0] = posA 

4269 if posB is not None: 

4270 self._posA_posB[1] = posB 

4271 self.stale = True 

4272 

4273 def set_patchA(self, patchA): 

4274 """ 

4275 Set the tail patch. 

4276 

4277 Parameters 

4278 ---------- 

4279 patchA : `.patches.Patch` 

4280 """ 

4281 self.patchA = patchA 

4282 self.stale = True 

4283 

4284 def set_patchB(self, patchB): 

4285 """ 

4286 Set the head patch. 

4287 

4288 Parameters 

4289 ---------- 

4290 patchB : `.patches.Patch` 

4291 """ 

4292 self.patchB = patchB 

4293 self.stale = True 

4294 

4295 @_docstring.dedent_interpd 

4296 def set_connectionstyle(self, connectionstyle=None, **kwargs): 

4297 """ 

4298 Set the connection style, possibly with further attributes. 

4299 

4300 Attributes from the previous connection style are not reused. 

4301 

4302 Without argument (or with ``connectionstyle=None``), the available box 

4303 styles are returned as a human-readable string. 

4304 

4305 Parameters 

4306 ---------- 

4307 connectionstyle : str or `matplotlib.patches.ConnectionStyle` 

4308 The style of the connection: either a `.ConnectionStyle` instance, 

4309 or a string, which is the style name and optionally comma separated 

4310 attributes (e.g. "Arc,armA=30,rad=10"). Such a string is used to 

4311 construct a `.ConnectionStyle` object, as documented in that class. 

4312 

4313 The following connection styles are available: 

4314 

4315 %(ConnectionStyle:table_and_accepts)s 

4316 

4317 **kwargs 

4318 Additional attributes for the connection style. See the table above 

4319 for supported parameters. 

4320 

4321 Examples 

4322 -------- 

4323 :: 

4324 

4325 set_connectionstyle("Arc,armA=30,rad=10") 

4326 set_connectionstyle("arc", armA=30, rad=10) 

4327 """ 

4328 if connectionstyle is None: 

4329 return ConnectionStyle.pprint_styles() 

4330 self._connector = ( 

4331 ConnectionStyle(connectionstyle, **kwargs) 

4332 if isinstance(connectionstyle, str) else connectionstyle) 

4333 self.stale = True 

4334 

4335 def get_connectionstyle(self): 

4336 """Return the `ConnectionStyle` used.""" 

4337 return self._connector 

4338 

4339 def set_arrowstyle(self, arrowstyle=None, **kwargs): 

4340 """ 

4341 Set the arrow style, possibly with further attributes. 

4342 

4343 Attributes from the previous arrow style are not reused. 

4344 

4345 Without argument (or with ``arrowstyle=None``), the available box 

4346 styles are returned as a human-readable string. 

4347 

4348 Parameters 

4349 ---------- 

4350 arrowstyle : str or `matplotlib.patches.ArrowStyle` 

4351 The style of the arrow: either a `.ArrowStyle` instance, or a 

4352 string, which is the style name and optionally comma separated 

4353 attributes (e.g. "Fancy,head_length=0.2"). Such a string is used to 

4354 construct a `.ArrowStyle` object, as documented in that class. 

4355 

4356 The following arrow styles are available: 

4357 

4358 %(ArrowStyle:table_and_accepts)s 

4359 

4360 **kwargs 

4361 Additional attributes for the arrow style. See the table above for 

4362 supported parameters. 

4363 

4364 Examples 

4365 -------- 

4366 :: 

4367 

4368 set_arrowstyle("Fancy,head_length=0.2") 

4369 set_arrowstyle("fancy", head_length=0.2) 

4370 """ 

4371 if arrowstyle is None: 

4372 return ArrowStyle.pprint_styles() 

4373 self._arrow_transmuter = ( 

4374 ArrowStyle(arrowstyle, **kwargs) 

4375 if isinstance(arrowstyle, str) else arrowstyle) 

4376 self.stale = True 

4377 

4378 def get_arrowstyle(self): 

4379 """Return the arrowstyle object.""" 

4380 return self._arrow_transmuter 

4381 

4382 def set_mutation_scale(self, scale): 

4383 """ 

4384 Set the mutation scale. 

4385 

4386 Parameters 

4387 ---------- 

4388 scale : float 

4389 """ 

4390 self._mutation_scale = scale 

4391 self.stale = True 

4392 

4393 def get_mutation_scale(self): 

4394 """ 

4395 Return the mutation scale. 

4396 

4397 Returns 

4398 ------- 

4399 scalar 

4400 """ 

4401 return self._mutation_scale 

4402 

4403 def set_mutation_aspect(self, aspect): 

4404 """ 

4405 Set the aspect ratio of the bbox mutation. 

4406 

4407 Parameters 

4408 ---------- 

4409 aspect : float 

4410 """ 

4411 self._mutation_aspect = aspect 

4412 self.stale = True 

4413 

4414 def get_mutation_aspect(self): 

4415 """Return the aspect ratio of the bbox mutation.""" 

4416 return (self._mutation_aspect if self._mutation_aspect is not None 

4417 else 1) # backcompat. 

4418 

4419 def get_path(self): 

4420 """Return the path of the arrow in the data coordinates.""" 

4421 # The path is generated in display coordinates, then converted back to 

4422 # data coordinates. 

4423 _path, fillable = self._get_path_in_displaycoord() 

4424 if np.iterable(fillable): 

4425 _path = Path.make_compound_path(*_path) 

4426 return self.get_transform().inverted().transform_path(_path) 

4427 

4428 def _get_path_in_displaycoord(self): 

4429 """Return the mutated path of the arrow in display coordinates.""" 

4430 dpi_cor = self._dpi_cor 

4431 

4432 if self._posA_posB is not None: 

4433 posA = self._convert_xy_units(self._posA_posB[0]) 

4434 posB = self._convert_xy_units(self._posA_posB[1]) 

4435 (posA, posB) = self.get_transform().transform((posA, posB)) 

4436 _path = self.get_connectionstyle()(posA, posB, 

4437 patchA=self.patchA, 

4438 patchB=self.patchB, 

4439 shrinkA=self.shrinkA * dpi_cor, 

4440 shrinkB=self.shrinkB * dpi_cor 

4441 ) 

4442 else: 

4443 _path = self.get_transform().transform_path(self._path_original) 

4444 

4445 _path, fillable = self.get_arrowstyle()( 

4446 _path, 

4447 self.get_mutation_scale() * dpi_cor, 

4448 self.get_linewidth() * dpi_cor, 

4449 self.get_mutation_aspect()) 

4450 

4451 return _path, fillable 

4452 

4453 get_path_in_displaycoord = _api.deprecate_privatize_attribute( 

4454 "3.5", 

4455 alternative="self.get_transform().transform_path(self.get_path())") 

4456 

4457 def draw(self, renderer): 

4458 if not self.get_visible(): 

4459 return 

4460 

4461 # FIXME: dpi_cor is for the dpi-dependency of the linewidth. There 

4462 # could be room for improvement. Maybe _get_path_in_displaycoord could 

4463 # take a renderer argument, but get_path should be adapted too. 

4464 self._dpi_cor = renderer.points_to_pixels(1.) 

4465 path, fillable = self._get_path_in_displaycoord() 

4466 

4467 if not np.iterable(fillable): 

4468 path = [path] 

4469 fillable = [fillable] 

4470 

4471 affine = transforms.IdentityTransform() 

4472 

4473 self._draw_paths_with_artist_properties( 

4474 renderer, 

4475 [(p, affine, self._facecolor if f and self._facecolor[3] else None) 

4476 for p, f in zip(path, fillable)]) 

4477 

4478 

4479class ConnectionPatch(FancyArrowPatch): 

4480 """A patch that connects two points (possibly in different axes).""" 

4481 

4482 def __str__(self): 

4483 return "ConnectionPatch((%g, %g), (%g, %g))" % \ 

4484 (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1]) 

4485 

4486 @_docstring.dedent_interpd 

4487 @_api.make_keyword_only("3.6", name="axesA") 

4488 def __init__(self, xyA, xyB, coordsA, coordsB=None, 

4489 axesA=None, axesB=None, 

4490 arrowstyle="-", 

4491 connectionstyle="arc3", 

4492 patchA=None, 

4493 patchB=None, 

4494 shrinkA=0., 

4495 shrinkB=0., 

4496 mutation_scale=10., 

4497 mutation_aspect=None, 

4498 clip_on=False, 

4499 **kwargs): 

4500 """ 

4501 Connect point *xyA* in *coordsA* with point *xyB* in *coordsB*. 

4502 

4503 Valid keys are 

4504 

4505 =============== ====================================================== 

4506 Key Description 

4507 =============== ====================================================== 

4508 arrowstyle the arrow style 

4509 connectionstyle the connection style 

4510 relpos default is (0.5, 0.5) 

4511 patchA default is bounding box of the text 

4512 patchB default is None 

4513 shrinkA default is 2 points 

4514 shrinkB default is 2 points 

4515 mutation_scale default is text size (in points) 

4516 mutation_aspect default is 1. 

4517 ? any key for `matplotlib.patches.PathPatch` 

4518 =============== ====================================================== 

4519 

4520 *coordsA* and *coordsB* are strings that indicate the 

4521 coordinates of *xyA* and *xyB*. 

4522 

4523 ==================== ================================================== 

4524 Property Description 

4525 ==================== ================================================== 

4526 'figure points' points from the lower left corner of the figure 

4527 'figure pixels' pixels from the lower left corner of the figure 

4528 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper 

4529 right 

4530 'subfigure points' points from the lower left corner of the subfigure 

4531 'subfigure pixels' pixels from the lower left corner of the subfigure 

4532 'subfigure fraction' fraction of the subfigure, 0, 0 is lower left. 

4533 'axes points' points from lower left corner of axes 

4534 'axes pixels' pixels from lower left corner of axes 

4535 'axes fraction' 0, 0 is lower left of axes and 1, 1 is upper right 

4536 'data' use the coordinate system of the object being 

4537 annotated (default) 

4538 'offset points' offset (in points) from the *xy* value 

4539 'polar' you can specify *theta*, *r* for the annotation, 

4540 even in cartesian plots. Note that if you are 

4541 using a polar axes, you do not need to specify 

4542 polar for the coordinate system since that is the 

4543 native "data" coordinate system. 

4544 ==================== ================================================== 

4545 

4546 Alternatively they can be set to any valid 

4547 `~matplotlib.transforms.Transform`. 

4548 

4549 Note that 'subfigure pixels' and 'figure pixels' are the same 

4550 for the parent figure, so users who want code that is usable in 

4551 a subfigure can use 'subfigure pixels'. 

4552 

4553 .. note:: 

4554 

4555 Using `ConnectionPatch` across two `~.axes.Axes` instances 

4556 is not directly compatible with :doc:`constrained layout 

4557 </tutorials/intermediate/constrainedlayout_guide>`. Add the artist 

4558 directly to the `.Figure` instead of adding it to a specific Axes, 

4559 or exclude it from the layout using ``con.set_in_layout(False)``. 

4560 

4561 .. code-block:: default 

4562 

4563 fig, ax = plt.subplots(1, 2, constrained_layout=True) 

4564 con = ConnectionPatch(..., axesA=ax[0], axesB=ax[1]) 

4565 fig.add_artist(con) 

4566 

4567 """ 

4568 if coordsB is None: 

4569 coordsB = coordsA 

4570 # we'll draw ourself after the artist we annotate by default 

4571 self.xy1 = xyA 

4572 self.xy2 = xyB 

4573 self.coords1 = coordsA 

4574 self.coords2 = coordsB 

4575 

4576 self.axesA = axesA 

4577 self.axesB = axesB 

4578 

4579 super().__init__(posA=(0, 0), posB=(1, 1), 

4580 arrowstyle=arrowstyle, 

4581 connectionstyle=connectionstyle, 

4582 patchA=patchA, patchB=patchB, 

4583 shrinkA=shrinkA, shrinkB=shrinkB, 

4584 mutation_scale=mutation_scale, 

4585 mutation_aspect=mutation_aspect, 

4586 clip_on=clip_on, 

4587 **kwargs) 

4588 # if True, draw annotation only if self.xy is inside the axes 

4589 self._annotation_clip = None 

4590 

4591 def _get_xy(self, xy, s, axes=None): 

4592 """Calculate the pixel position of given point.""" 

4593 s0 = s # For the error message, if needed. 

4594 if axes is None: 

4595 axes = self.axes 

4596 xy = np.array(xy) 

4597 if s in ["figure points", "axes points"]: 

4598 xy *= self.figure.dpi / 72 

4599 s = s.replace("points", "pixels") 

4600 elif s == "figure fraction": 

4601 s = self.figure.transFigure 

4602 elif s == "subfigure fraction": 

4603 s = self.figure.transSubfigure 

4604 elif s == "axes fraction": 

4605 s = axes.transAxes 

4606 x, y = xy 

4607 

4608 if s == 'data': 

4609 trans = axes.transData 

4610 x = float(self.convert_xunits(x)) 

4611 y = float(self.convert_yunits(y)) 

4612 return trans.transform((x, y)) 

4613 elif s == 'offset points': 

4614 if self.xycoords == 'offset points': # prevent recursion 

4615 return self._get_xy(self.xy, 'data') 

4616 return ( 

4617 self._get_xy(self.xy, self.xycoords) # converted data point 

4618 + xy * self.figure.dpi / 72) # converted offset 

4619 elif s == 'polar': 

4620 theta, r = x, y 

4621 x = r * np.cos(theta) 

4622 y = r * np.sin(theta) 

4623 trans = axes.transData 

4624 return trans.transform((x, y)) 

4625 elif s == 'figure pixels': 

4626 # pixels from the lower left corner of the figure 

4627 bb = self.figure.figbbox 

4628 x = bb.x0 + x if x >= 0 else bb.x1 + x 

4629 y = bb.y0 + y if y >= 0 else bb.y1 + y 

4630 return x, y 

4631 elif s == 'subfigure pixels': 

4632 # pixels from the lower left corner of the figure 

4633 bb = self.figure.bbox 

4634 x = bb.x0 + x if x >= 0 else bb.x1 + x 

4635 y = bb.y0 + y if y >= 0 else bb.y1 + y 

4636 return x, y 

4637 elif s == 'axes pixels': 

4638 # pixels from the lower left corner of the axes 

4639 bb = axes.bbox 

4640 x = bb.x0 + x if x >= 0 else bb.x1 + x 

4641 y = bb.y0 + y if y >= 0 else bb.y1 + y 

4642 return x, y 

4643 elif isinstance(s, transforms.Transform): 

4644 return s.transform(xy) 

4645 else: 

4646 raise ValueError(f"{s0} is not a valid coordinate transformation") 

4647 

4648 def set_annotation_clip(self, b): 

4649 """ 

4650 Set the annotation's clipping behavior. 

4651 

4652 Parameters 

4653 ---------- 

4654 b : bool or None 

4655 - True: The annotation will be clipped when ``self.xy`` is 

4656 outside the axes. 

4657 - False: The annotation will always be drawn. 

4658 - None: The annotation will be clipped when ``self.xy`` is 

4659 outside the axes and ``self.xycoords == "data"``. 

4660 """ 

4661 self._annotation_clip = b 

4662 self.stale = True 

4663 

4664 def get_annotation_clip(self): 

4665 """ 

4666 Return the clipping behavior. 

4667 

4668 See `.set_annotation_clip` for the meaning of the return value. 

4669 """ 

4670 return self._annotation_clip 

4671 

4672 def _get_path_in_displaycoord(self): 

4673 """Return the mutated path of the arrow in display coordinates.""" 

4674 dpi_cor = self._dpi_cor 

4675 posA = self._get_xy(self.xy1, self.coords1, self.axesA) 

4676 posB = self._get_xy(self.xy2, self.coords2, self.axesB) 

4677 path = self.get_connectionstyle()( 

4678 posA, posB, 

4679 patchA=self.patchA, patchB=self.patchB, 

4680 shrinkA=self.shrinkA * dpi_cor, shrinkB=self.shrinkB * dpi_cor, 

4681 ) 

4682 path, fillable = self.get_arrowstyle()( 

4683 path, 

4684 self.get_mutation_scale() * dpi_cor, 

4685 self.get_linewidth() * dpi_cor, 

4686 self.get_mutation_aspect() 

4687 ) 

4688 return path, fillable 

4689 

4690 def _check_xy(self, renderer): 

4691 """Check whether the annotation needs to be drawn.""" 

4692 

4693 b = self.get_annotation_clip() 

4694 

4695 if b or (b is None and self.coords1 == "data"): 

4696 xy_pixel = self._get_xy(self.xy1, self.coords1, self.axesA) 

4697 if self.axesA is None: 

4698 axes = self.axes 

4699 else: 

4700 axes = self.axesA 

4701 if not axes.contains_point(xy_pixel): 

4702 return False 

4703 

4704 if b or (b is None and self.coords2 == "data"): 

4705 xy_pixel = self._get_xy(self.xy2, self.coords2, self.axesB) 

4706 if self.axesB is None: 

4707 axes = self.axes 

4708 else: 

4709 axes = self.axesB 

4710 if not axes.contains_point(xy_pixel): 

4711 return False 

4712 

4713 return True 

4714 

4715 def draw(self, renderer): 

4716 if renderer is not None: 

4717 self._renderer = renderer 

4718 if not self.get_visible() or not self._check_xy(renderer): 

4719 return 

4720 super().draw(renderer)