Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import contextlib 

2import functools 

3import inspect 

4import math 

5from numbers import Number 

6import textwrap 

7 

8import numpy as np 

9 

10import matplotlib as mpl 

11from . import artist, cbook, colors, docstring, lines as mlines, transforms 

12from .bezier import ( 

13 NonIntersectingPathException, concatenate_paths, get_cos_sin, 

14 get_intersection, get_parallels, inside_circle, make_path_regular, 

15 make_wedged_bezier2, split_bezier_intersecting_with_closedpath, 

16 split_path_inout) 

17from .path import Path 

18 

19 

20@cbook._define_aliases({ 

21 "antialiased": ["aa"], 

22 "edgecolor": ["ec"], 

23 "facecolor": ["fc"], 

24 "linestyle": ["ls"], 

25 "linewidth": ["lw"], 

26}) 

27class Patch(artist.Artist): 

28 """ 

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

30 

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

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

33 """ 

34 zorder = 1 

35 validCap = ('butt', 'round', 'projecting') 

36 validJoin = ('miter', 'round', 'bevel') 

37 

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

39 # subclass-by-subclass basis. 

40 _edge_default = False 

41 

42 def __init__(self, 

43 edgecolor=None, 

44 facecolor=None, 

45 color=None, 

46 linewidth=None, 

47 linestyle=None, 

48 antialiased=None, 

49 hatch=None, 

50 fill=True, 

51 capstyle=None, 

52 joinstyle=None, 

53 **kwargs): 

54 """ 

55 The following kwarg properties are supported 

56 

57 %(Patch)s 

58 """ 

59 artist.Artist.__init__(self) 

60 

61 if linewidth is None: 

62 linewidth = mpl.rcParams['patch.linewidth'] 

63 if linestyle is None: 

64 linestyle = "solid" 

65 if capstyle is None: 

66 capstyle = 'butt' 

67 if joinstyle is None: 

68 joinstyle = 'miter' 

69 if antialiased is None: 

70 antialiased = mpl.rcParams['patch.antialiased'] 

71 

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

73 self._fill = True # needed for set_facecolor call 

74 if color is not None: 

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

76 cbook._warn_external( 

77 "Setting the 'color' property will override " 

78 "the edgecolor or facecolor properties.") 

79 self.set_color(color) 

80 else: 

81 self.set_edgecolor(edgecolor) 

82 self.set_facecolor(facecolor) 

83 # unscaled dashes. Needed to scale dash patterns by lw 

84 self._us_dashes = None 

85 self._linewidth = 0 

86 

87 self.set_fill(fill) 

88 self.set_linestyle(linestyle) 

89 self.set_linewidth(linewidth) 

90 self.set_antialiased(antialiased) 

91 self.set_hatch(hatch) 

92 self.set_capstyle(capstyle) 

93 self.set_joinstyle(joinstyle) 

94 

95 if len(kwargs): 

96 self.update(kwargs) 

97 

98 def get_verts(self): 

99 """ 

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

101 

102 If the patch contains Bezier curves, the curves will be 

103 interpolated by line segments. To access the curves as 

104 curves, use :meth:`get_path`. 

105 """ 

106 trans = self.get_transform() 

107 path = self.get_path() 

108 polygons = path.to_polygons(trans) 

109 if len(polygons): 

110 return polygons[0] 

111 return [] 

112 

113 def _process_radius(self, radius): 

114 if radius is not None: 

115 return radius 

116 if isinstance(self._picker, Number): 

117 _radius = self._picker 

118 else: 

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

120 _radius = 0 

121 else: 

122 _radius = self.get_linewidth() 

123 return _radius 

124 

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

126 """ 

127 Test whether the mouse event occurred in the patch. 

128 

129 Returns 

130 ------- 

131 (bool, empty dict) 

132 """ 

133 inside, info = self._default_contains(mouseevent) 

134 if inside is not None: 

135 return inside, info 

136 radius = self._process_radius(radius) 

137 codes = self.get_path().codes 

138 if codes is not None: 

139 vertices = self.get_path().vertices 

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

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

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

143 # Don't split before the first MOVETO. 

144 idxs = idxs[1:] 

145 subpaths = map( 

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

147 else: 

148 subpaths = [self.get_path()] 

149 inside = any( 

150 subpath.contains_point( 

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

152 for subpath in subpaths) 

153 return inside, {} 

154 

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

156 """ 

157 Return whether the given point is inside the patch. 

158 

159 Parameters 

160 ---------- 

161 point : (float, float) 

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

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

164 that are added to a figure or axes. 

165 radius : float, optional 

166 Add an additional margin on the patch in target coordinates of 

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

168 details. 

169 

170 Returns 

171 ------- 

172 bool 

173 

174 Notes 

175 ----- 

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

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

178 creation coordinates and the point coordinates match. The following 

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

180 

181 >>> center = 0, 0 

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

183 >>> c.contains_point(center) 

184 True 

185 

186 The convention of checking against the transformed patch stems from 

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

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

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

190 transform them first: 

191 

192 >>> center = 0, 0 

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

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

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

196 >>> c.contains_point(transformed_center) 

197 True 

198 

199 """ 

200 radius = self._process_radius(radius) 

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

202 self.get_transform(), 

203 radius) 

204 

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

206 """ 

207 Return whether the given points are inside the patch. 

208 

209 Parameters 

210 ---------- 

211 points : (N, 2) array 

212 The points to check, in target coordinates of 

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

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

215 radius : float, optional 

216 Add an additional margin on the patch in target coordinates of 

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

218 details. 

219 

220 Returns 

221 ------- 

222 length-N bool array 

223 

224 Notes 

225 ----- 

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

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

228 """ 

229 radius = self._process_radius(radius) 

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

231 self.get_transform(), 

232 radius) 

233 

234 def update_from(self, other): 

235 """Updates this `.Patch` from the properties of *other*.""" 

236 artist.Artist.update_from(self, other) 

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

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

239 self._edgecolor = other._edgecolor 

240 self._facecolor = other._facecolor 

241 self._original_edgecolor = other._original_edgecolor 

242 self._original_facecolor = other._original_facecolor 

243 self._fill = other._fill 

244 self._hatch = other._hatch 

245 self._hatch_color = other._hatch_color 

246 # copy the unscaled dash pattern 

247 self._us_dashes = other._us_dashes 

248 self.set_linewidth(other._linewidth) # also sets dash properties 

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 :class:`~matplotlib.transforms.Transform` instance which 

267 maps data coordinates to physical coordinates. 

268 """ 

269 return artist.Artist.get_transform(self) 

270 

271 def get_patch_transform(self): 

272 """ 

273 Return the :class:`~matplotlib.transforms.Transform` instance which 

274 takes patch coordinates 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 b : 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 or 'auto' 

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 if w is None: 

393 w = mpl.rcParams['axes.linewidth'] 

394 

395 self._linewidth = float(w) 

396 # scale the dash pattern by the linewidth 

397 offset, ls = self._us_dashes 

398 self._dashoffset, self._dashes = mlines._scale_dashes( 

399 offset, ls, self._linewidth) 

400 self.stale = True 

401 

402 def set_linestyle(self, ls): 

403 """ 

404 Set the patch linestyle. 

405 

406 =========================== ================= 

407 linestyle description 

408 =========================== ================= 

409 ``'-'`` or ``'solid'`` solid line 

410 ``'--'`` or ``'dashed'`` dashed line 

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

412 ``':'`` or ``'dotted'`` dotted line 

413 =========================== ================= 

414 

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

416 

417 (offset, onoffseq) 

418 

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

420 

421 Parameters 

422 ---------- 

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

424 The line style. 

425 """ 

426 if ls is None: 

427 ls = "solid" 

428 self._linestyle = ls 

429 # get the unscaled dash pattern 

430 offset, ls = self._us_dashes = mlines._get_dash_pattern(ls) 

431 # scale the dash pattern by the linewidth 

432 self._dashoffset, self._dashes = mlines._scale_dashes( 

433 offset, ls, self._linewidth) 

434 self.stale = True 

435 

436 def set_fill(self, b): 

437 """ 

438 Set whether to fill the patch. 

439 

440 Parameters 

441 ---------- 

442 b : bool 

443 """ 

444 self._fill = bool(b) 

445 self._set_facecolor(self._original_facecolor) 

446 self._set_edgecolor(self._original_edgecolor) 

447 self.stale = True 

448 

449 def get_fill(self): 

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

451 return self._fill 

452 

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

454 # but somewhat inconsistent behavior in which fill was an 

455 # attribute. 

456 fill = property(get_fill, set_fill) 

457 

458 def set_capstyle(self, s): 

459 """ 

460 Set the capstyle. 

461 

462 Parameters 

463 ---------- 

464 s : {'butt', 'round', 'projecting'} 

465 """ 

466 s = s.lower() 

467 cbook._check_in_list(self.validCap, capstyle=s) 

468 self._capstyle = s 

469 self.stale = True 

470 

471 def get_capstyle(self): 

472 """Return the capstyle.""" 

473 return self._capstyle 

474 

475 def set_joinstyle(self, s): 

476 """Set the joinstyle. 

477 

478 Parameters 

479 ---------- 

480 s : {'miter', 'round', 'bevel'} 

481 """ 

482 s = s.lower() 

483 cbook._check_in_list(self.validJoin, joinstyle=s) 

484 self._joinstyle = s 

485 self.stale = True 

486 

487 def get_joinstyle(self): 

488 """Return the joinstyle.""" 

489 return self._joinstyle 

490 

491 def set_hatch(self, hatch): 

492 r""" 

493 Set the hatching pattern. 

494 

495 *hatch* can be one of:: 

496 

497 / - diagonal hatching 

498 \ - back diagonal 

499 | - vertical 

500 - - horizontal 

501 + - crossed 

502 x - crossed diagonal 

503 o - small circle 

504 O - large circle 

505 . - dots 

506 * - stars 

507 

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

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

510 density of hatching of that pattern. 

511 

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

513 backends only. 

514 

515 Parameters 

516 ---------- 

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

518 """ 

519 self._hatch = hatch 

520 self.stale = True 

521 

522 def get_hatch(self): 

523 """Return the hatching pattern.""" 

524 return self._hatch 

525 

526 @contextlib.contextmanager 

527 def _bind_draw_path_function(self, renderer): 

528 """ 

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

530 

531 Yields a callable ``dp`` such that calling ``dp(*args, **kwargs)`` is 

532 equivalent to calling ``renderer1.draw_path(gc, *args, **kwargs)`` 

533 where ``renderer1`` and ``gc`` have been suitably set from ``renderer`` 

534 and the artist's properties. 

535 """ 

536 

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

538 gc = renderer.new_gc() 

539 

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

541 

542 lw = self._linewidth 

543 if self._edgecolor[3] == 0: 

544 lw = 0 

545 gc.set_linewidth(lw) 

546 gc.set_dashes(self._dashoffset, self._dashes) 

547 gc.set_capstyle(self._capstyle) 

548 gc.set_joinstyle(self._joinstyle) 

549 

550 gc.set_antialiased(self._antialiased) 

551 self._set_gc_clip(gc) 

552 gc.set_url(self._url) 

553 gc.set_snap(self.get_snap()) 

554 

555 gc.set_alpha(self._alpha) 

556 

557 if self._hatch: 

558 gc.set_hatch(self._hatch) 

559 try: 

560 gc.set_hatch_color(self._hatch_color) 

561 except AttributeError: 

562 # if we end up with a GC that does not have this method 

563 cbook.warn_deprecated( 

564 "3.1", message="Your backend does not support setting the " 

565 "hatch color; such backends will become unsupported in " 

566 "Matplotlib 3.3.") 

567 

568 if self.get_sketch_params() is not None: 

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

570 

571 if self.get_path_effects(): 

572 from matplotlib.patheffects import PathEffectRenderer 

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

574 

575 # In `with _bind_draw_path_function(renderer) as draw_path: ...` 

576 # (in the implementations of `draw()` below), calls to `draw_path(...)` 

577 # will occur as if they took place here with `gc` inserted as 

578 # additional first argument. 

579 yield functools.partial(renderer.draw_path, gc) 

580 

581 gc.restore() 

582 renderer.close_group('patch') 

583 self.stale = False 

584 

585 @artist.allow_rasterization 

586 def draw(self, renderer): 

587 """Draw to the given *renderer*.""" 

588 if not self.get_visible(): 

589 return 

590 

591 # Patch has traditionally ignored the dashoffset. 

592 with cbook._setattr_cm(self, _dashoffset=0), \ 

593 self._bind_draw_path_function(renderer) as draw_path: 

594 path = self.get_path() 

595 transform = self.get_transform() 

596 tpath = transform.transform_path_non_affine(path) 

597 affine = transform.get_affine() 

598 draw_path(tpath, affine, 

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

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

601 # transparent, but do if it is None. 

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

603 

604 def get_path(self): 

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

606 raise NotImplementedError('Derived must override') 

607 

608 def get_window_extent(self, renderer=None): 

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

610 

611 def _convert_xy_units(self, xy): 

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

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

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

615 return x, y 

616 

617 

618patchdoc = artist.kwdoc(Patch) 

619for k in ['Rectangle', 'Circle', 'RegularPolygon', 'Polygon', 'Wedge', 'Arrow', 

620 'FancyArrow', 'CirclePolygon', 'Ellipse', 'Arc', 'FancyBboxPatch', 

621 'Patch']: 

622 docstring.interpd.update({k: patchdoc}) 

623 

624# define Patch.__init__ docstring after the class has been added to interpd 

625docstring.dedent_interpd(Patch.__init__) 

626 

627 

628class Shadow(Patch): 

629 def __str__(self): 

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

631 

632 @docstring.dedent_interpd 

633 def __init__(self, patch, ox, oy, props=None, **kwargs): 

634 """ 

635 Create a shadow of the given *patch* offset by *ox*, *oy*. 

636 *props*, if not *None*, is a patch property update dictionary. 

637 If *None*, the shadow will have have the same color as the face, 

638 but darkened. 

639 

640 Valid keyword arguments are: 

641 

642 %(Patch)s 

643 """ 

644 Patch.__init__(self) 

645 self.patch = patch 

646 self.props = props 

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

648 self._shadow_transform = transforms.Affine2D() 

649 self._update() 

650 

651 def _update(self): 

652 self.update_from(self.patch) 

653 

654 # Place the shadow patch directly behind the inherited patch. 

655 self.set_zorder(np.nextafter(self.patch.zorder, -np.inf)) 

656 

657 if self.props is not None: 

658 self.update(self.props) 

659 else: 

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

661 self.set_facecolor(color) 

662 self.set_edgecolor(color) 

663 self.set_alpha(0.5) 

664 

665 def _update_transform(self, renderer): 

666 ox = renderer.points_to_pixels(self._ox) 

667 oy = renderer.points_to_pixels(self._oy) 

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

669 

670 def _get_ox(self): 

671 return self._ox 

672 

673 def _set_ox(self, ox): 

674 self._ox = ox 

675 

676 def _get_oy(self): 

677 return self._oy 

678 

679 def _set_oy(self, oy): 

680 self._oy = oy 

681 

682 def get_path(self): 

683 return self.patch.get_path() 

684 

685 def get_patch_transform(self): 

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

687 

688 def draw(self, renderer): 

689 self._update_transform(renderer) 

690 Patch.draw(self, renderer) 

691 

692 

693class Rectangle(Patch): 

694 """ 

695 A rectangle with lower left at *xy* = (*x*, *y*) with 

696 specified *width*, *height* and rotation *angle*. 

697 """ 

698 

699 def __str__(self): 

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

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

702 return fmt % pars 

703 

704 @docstring.dedent_interpd 

705 def __init__(self, xy, width, height, angle=0.0, **kwargs): 

706 """ 

707 Parameters 

708 ---------- 

709 xy : (float, float) 

710 The bottom and left rectangle coordinates 

711 width : float 

712 Rectangle width 

713 height : float 

714 Rectangle height 

715 angle : float, optional 

716 rotation in degrees anti-clockwise about *xy* (default is 0.0) 

717 fill : bool, optional 

718 Whether to fill the rectangle (default is ``True``) 

719 

720 Notes 

721 ----- 

722 Valid keyword arguments are: 

723 

724 %(Patch)s 

725 """ 

726 

727 Patch.__init__(self, **kwargs) 

728 

729 self._x0 = xy[0] 

730 self._y0 = xy[1] 

731 

732 self._width = width 

733 self._height = height 

734 

735 self._x1 = self._x0 + self._width 

736 self._y1 = self._y0 + self._height 

737 

738 self.angle = float(angle) 

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

740 self._rect_transform = transforms.IdentityTransform() 

741 

742 def get_path(self): 

743 """Return the vertices of the rectangle.""" 

744 return Path.unit_rectangle() 

745 

746 def _update_patch_transform(self): 

747 """ 

748 Notes 

749 ----- 

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

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

752 call the accessor method and not directly access the transformation 

753 member variable. 

754 """ 

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

756 bbox = transforms.Bbox.from_extents(x0, y0, x1, y1) 

757 rot_trans = transforms.Affine2D() 

758 rot_trans.rotate_deg_around(x0, y0, self.angle) 

759 self._rect_transform = transforms.BboxTransformTo(bbox) 

760 self._rect_transform += rot_trans 

761 

762 def _update_x1(self): 

763 self._x1 = self._x0 + self._width 

764 

765 def _update_y1(self): 

766 self._y1 = self._y0 + self._height 

767 

768 def _convert_units(self): 

769 """Convert bounds of the rectangle.""" 

770 x0 = self.convert_xunits(self._x0) 

771 y0 = self.convert_yunits(self._y0) 

772 x1 = self.convert_xunits(self._x1) 

773 y1 = self.convert_yunits(self._y1) 

774 return x0, y0, x1, y1 

775 

776 def get_patch_transform(self): 

777 self._update_patch_transform() 

778 return self._rect_transform 

779 

780 def get_x(self): 

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

782 return self._x0 

783 

784 def get_y(self): 

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

786 return self._y0 

787 

788 def get_xy(self): 

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

790 return self._x0, self._y0 

791 

792 def get_width(self): 

793 """Return the width of the rectangle.""" 

794 return self._width 

795 

796 def get_height(self): 

797 """Return the height of the rectangle.""" 

798 return self._height 

799 

800 def set_x(self, x): 

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

802 self._x0 = x 

803 self._update_x1() 

804 self.stale = True 

805 

806 def set_y(self, y): 

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

808 self._y0 = y 

809 self._update_y1() 

810 self.stale = True 

811 

812 def set_xy(self, xy): 

813 """ 

814 Set the left and bottom coordinates of the rectangle. 

815 

816 Parameters 

817 ---------- 

818 xy : (float, float) 

819 """ 

820 self._x0, self._y0 = xy 

821 self._update_x1() 

822 self._update_y1() 

823 self.stale = True 

824 

825 def set_width(self, w): 

826 """Set the width of the rectangle.""" 

827 self._width = w 

828 self._update_x1() 

829 self.stale = True 

830 

831 def set_height(self, h): 

832 """Set the height of the rectangle.""" 

833 self._height = h 

834 self._update_y1() 

835 self.stale = True 

836 

837 def set_bounds(self, *args): 

838 """ 

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

840 

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

842 

843 set_bounds(left, bottom, width, height) 

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

845 

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

847 """ 

848 if len(args) == 1: 

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

850 else: 

851 l, b, w, h = args 

852 self._x0 = l 

853 self._y0 = b 

854 self._width = w 

855 self._height = h 

856 self._update_x1() 

857 self._update_y1() 

858 self.stale = True 

859 

860 def get_bbox(self): 

861 """Return the `.Bbox`.""" 

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

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

864 

865 xy = property(get_xy, set_xy) 

866 

867 

868class RegularPolygon(Patch): 

869 """ 

870 A regular polygon patch. 

871 """ 

872 def __str__(self): 

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

874 return s % (self._xy[0], self._xy[1], self._numVertices, self._radius, 

875 self._orientation) 

876 

877 @docstring.dedent_interpd 

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

879 **kwargs): 

880 """ 

881 Constructor arguments: 

882 

883 *xy* 

884 A length 2 tuple (*x*, *y*) of the center. 

885 

886 *numVertices* 

887 the number of vertices. 

888 

889 *radius* 

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

891 

892 *orientation* 

893 rotates the polygon (in radians). 

894 

895 Valid keyword arguments are: 

896 

897 %(Patch)s 

898 """ 

899 self._xy = xy 

900 self._numVertices = numVertices 

901 self._orientation = orientation 

902 self._radius = radius 

903 self._path = Path.unit_regular_polygon(numVertices) 

904 self._poly_transform = transforms.Affine2D() 

905 self._update_transform() 

906 

907 Patch.__init__(self, **kwargs) 

908 

909 def _update_transform(self): 

910 self._poly_transform.clear() \ 

911 .scale(self.radius) \ 

912 .rotate(self.orientation) \ 

913 .translate(*self.xy) 

914 

915 @property 

916 def xy(self): 

917 return self._xy 

918 

919 @xy.setter 

920 def xy(self, xy): 

921 self._xy = xy 

922 self._update_transform() 

923 

924 @property 

925 def orientation(self): 

926 return self._orientation 

927 

928 @orientation.setter 

929 def orientation(self, orientation): 

930 self._orientation = orientation 

931 self._update_transform() 

932 

933 @property 

934 def radius(self): 

935 return self._radius 

936 

937 @radius.setter 

938 def radius(self, radius): 

939 self._radius = radius 

940 self._update_transform() 

941 

942 @property 

943 def numvertices(self): 

944 return self._numVertices 

945 

946 @numvertices.setter 

947 def numvertices(self, numVertices): 

948 self._numVertices = numVertices 

949 

950 def get_path(self): 

951 return self._path 

952 

953 def get_patch_transform(self): 

954 self._update_transform() 

955 return self._poly_transform 

956 

957 

958class PathPatch(Patch): 

959 """ 

960 A general polycurve path patch. 

961 """ 

962 _edge_default = True 

963 

964 def __str__(self): 

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

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

967 

968 @docstring.dedent_interpd 

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

970 """ 

971 *path* is a :class:`matplotlib.path.Path` object. 

972 

973 Valid keyword arguments are: 

974 

975 %(Patch)s 

976 """ 

977 Patch.__init__(self, **kwargs) 

978 self._path = path 

979 

980 def get_path(self): 

981 return self._path 

982 

983 def set_path(self, path): 

984 self._path = path 

985 

986 

987class Polygon(Patch): 

988 """ 

989 A general polygon patch. 

990 """ 

991 def __str__(self): 

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

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

994 

995 @docstring.dedent_interpd 

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

997 """ 

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

999 

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

1001 starting and ending points are the same. 

1002 

1003 Valid keyword arguments are: 

1004 

1005 %(Patch)s 

1006 """ 

1007 Patch.__init__(self, **kwargs) 

1008 self._closed = closed 

1009 self.set_xy(xy) 

1010 

1011 def get_path(self): 

1012 """ 

1013 Get the path of the polygon 

1014 

1015 Returns 

1016 ------- 

1017 path : Path 

1018 The `~.path.Path` object for the polygon. 

1019 """ 

1020 return self._path 

1021 

1022 def get_closed(self): 

1023 """ 

1024 Returns if the polygon is closed 

1025 

1026 Returns 

1027 ------- 

1028 closed : bool 

1029 If the path is closed 

1030 """ 

1031 return self._closed 

1032 

1033 def set_closed(self, closed): 

1034 """ 

1035 Set if the polygon is closed 

1036 

1037 Parameters 

1038 ---------- 

1039 closed : bool 

1040 True if the polygon is closed 

1041 """ 

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

1043 return 

1044 self._closed = bool(closed) 

1045 self.set_xy(self.get_xy()) 

1046 self.stale = True 

1047 

1048 def get_xy(self): 

1049 """ 

1050 Get the vertices of the path. 

1051 

1052 Returns 

1053 ------- 

1054 vertices : (N, 2) numpy array 

1055 The coordinates of the vertices. 

1056 """ 

1057 return self._path.vertices 

1058 

1059 def set_xy(self, xy): 

1060 """ 

1061 Set the vertices of the polygon. 

1062 

1063 Parameters 

1064 ---------- 

1065 xy : (N, 2) array-like 

1066 The coordinates of the vertices. 

1067 """ 

1068 xy = np.asarray(xy) 

1069 if self._closed: 

1070 if len(xy) and (xy[0] != xy[-1]).any(): 

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

1072 else: 

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

1074 xy = xy[:-1] 

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

1076 self.stale = True 

1077 

1078 _get_xy = get_xy 

1079 _set_xy = set_xy 

1080 xy = property(get_xy, set_xy, 

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

1082 

1083 

1084class Wedge(Patch): 

1085 """ 

1086 Wedge shaped patch. 

1087 """ 

1088 def __str__(self): 

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

1090 self.theta1, self.theta2, self.width) 

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

1092 return fmt % pars 

1093 

1094 @docstring.dedent_interpd 

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

1096 """ 

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

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

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

1100 to outer radius *r*. 

1101 

1102 Valid keyword arguments are: 

1103 

1104 %(Patch)s 

1105 """ 

1106 Patch.__init__(self, **kwargs) 

1107 self.center = center 

1108 self.r, self.width = r, width 

1109 self.theta1, self.theta2 = theta1, theta2 

1110 self._patch_transform = transforms.IdentityTransform() 

1111 self._recompute_path() 

1112 

1113 def _recompute_path(self): 

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

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

1116 theta1, theta2 = 0, 360 

1117 connector = Path.MOVETO 

1118 else: 

1119 theta1, theta2 = self.theta1, self.theta2 

1120 connector = Path.LINETO 

1121 

1122 # Form the outer ring 

1123 arc = Path.arc(theta1, theta2) 

1124 

1125 if self.width is not None: 

1126 # Partial annulus needs to draw the outer ring 

1127 # followed by a reversed and scaled inner ring 

1128 v1 = arc.vertices 

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

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

1131 c = np.hstack([arc.codes, arc.codes, connector, Path.CLOSEPOLY]) 

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

1133 else: 

1134 # Wedge doesn't need an inner ring 

1135 v = np.vstack([arc.vertices, [(0, 0), arc.vertices[0, :], (0, 0)]]) 

1136 c = np.hstack([arc.codes, [connector, connector, Path.CLOSEPOLY]]) 

1137 

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

1139 v *= self.r 

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

1141 self._path = Path(v, c) 

1142 

1143 def set_center(self, center): 

1144 self._path = None 

1145 self.center = center 

1146 self.stale = True 

1147 

1148 def set_radius(self, radius): 

1149 self._path = None 

1150 self.r = radius 

1151 self.stale = True 

1152 

1153 def set_theta1(self, theta1): 

1154 self._path = None 

1155 self.theta1 = theta1 

1156 self.stale = True 

1157 

1158 def set_theta2(self, theta2): 

1159 self._path = None 

1160 self.theta2 = theta2 

1161 self.stale = True 

1162 

1163 def set_width(self, width): 

1164 self._path = None 

1165 self.width = width 

1166 self.stale = True 

1167 

1168 def get_path(self): 

1169 if self._path is None: 

1170 self._recompute_path() 

1171 return self._path 

1172 

1173 

1174# COVERAGE NOTE: Not used internally or from examples 

1175class Arrow(Patch): 

1176 """ 

1177 An arrow patch. 

1178 """ 

1179 def __str__(self): 

1180 return "Arrow()" 

1181 

1182 _path = Path([[0.0, 0.1], [0.0, -0.1], 

1183 [0.8, -0.1], [0.8, -0.3], 

1184 [1.0, 0.0], [0.8, 0.3], 

1185 [0.8, 0.1], [0.0, 0.1]], 

1186 closed=True) 

1187 

1188 @docstring.dedent_interpd 

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

1190 """ 

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

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

1193 

1194 Parameters 

1195 ---------- 

1196 x : scalar 

1197 x coordinate of the arrow tail 

1198 y : scalar 

1199 y coordinate of the arrow tail 

1200 dx : scalar 

1201 Arrow length in the x direction 

1202 dy : scalar 

1203 Arrow length in the y direction 

1204 width : scalar, optional (default: 1) 

1205 Scale factor for the width of the arrow. With a default value of 

1206 1, the tail width is 0.2 and head width is 0.6. 

1207 **kwargs 

1208 Keyword arguments control the `Patch` properties: 

1209 

1210 %(Patch)s 

1211 

1212 See Also 

1213 -------- 

1214 :class:`FancyArrow` : 

1215 Patch that allows independent control of the head and tail 

1216 properties 

1217 """ 

1218 super().__init__(**kwargs) 

1219 self._patch_transform = ( 

1220 transforms.Affine2D() 

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

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

1223 .translate(x, y) 

1224 .frozen()) 

1225 

1226 def get_path(self): 

1227 return self._path 

1228 

1229 def get_patch_transform(self): 

1230 return self._patch_transform 

1231 

1232 

1233class FancyArrow(Polygon): 

1234 """ 

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

1236 """ 

1237 

1238 _edge_default = True 

1239 

1240 def __str__(self): 

1241 return "FancyArrow()" 

1242 

1243 @docstring.dedent_interpd 

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

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

1246 head_starts_at_zero=False, **kwargs): 

1247 """ 

1248 Constructor arguments 

1249 *width*: float (default: 0.001) 

1250 width of full arrow tail 

1251 

1252 *length_includes_head*: bool (default: False) 

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

1254 

1255 *head_width*: float or None (default: 3*width) 

1256 total width of the full arrow head 

1257 

1258 *head_length*: float or None (default: 1.5 * head_width) 

1259 length of arrow head 

1260 

1261 *shape*: ['full', 'left', 'right'] (default: 'full') 

1262 draw the left-half, right-half, or full arrow 

1263 

1264 *overhang*: float (default: 0) 

1265 fraction that the arrow is swept back (0 overhang means 

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

1267 

1268 *head_starts_at_zero*: bool (default: False) 

1269 if True, the head starts being drawn at coordinate 0 

1270 instead of ending at coordinate 0. 

1271 

1272 Other valid kwargs (inherited from :class:`Patch`) are: 

1273 

1274 %(Patch)s 

1275 """ 

1276 if head_width is None: 

1277 head_width = 3 * width 

1278 if head_length is None: 

1279 head_length = 1.5 * head_width 

1280 

1281 distance = np.hypot(dx, dy) 

1282 

1283 if length_includes_head: 

1284 length = distance 

1285 else: 

1286 length = distance + head_length 

1287 if not length: 

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

1289 else: 

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

1291 hw, hl, hs, lw = head_width, head_length, overhang, width 

1292 left_half_arrow = np.array([ 

1293 [0.0, 0.0], # tip 

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

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

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

1297 [-length, 0], 

1298 ]) 

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

1300 if not length_includes_head: 

1301 left_half_arrow += [head_length, 0] 

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

1303 if head_starts_at_zero: 

1304 left_half_arrow += [head_length / 2, 0] 

1305 # figure out the shape, and complete accordingly 

1306 if shape == 'left': 

1307 coords = left_half_arrow 

1308 else: 

1309 right_half_arrow = left_half_arrow * [1, -1] 

1310 if shape == 'right': 

1311 coords = right_half_arrow 

1312 elif shape == 'full': 

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

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

1315 # twice caused a problem with xpdf. 

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

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

1318 else: 

1319 raise ValueError("Got unknown shape: %s" % shape) 

1320 if distance != 0: 

1321 cx = dx / distance 

1322 sx = dy / distance 

1323 else: 

1324 # Account for division by zero 

1325 cx, sx = 0, 1 

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

1327 verts = np.dot(coords, M) + (x + dx, y + dy) 

1328 

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

1330 

1331 

1332docstring.interpd.update({"FancyArrow": FancyArrow.__init__.__doc__}) 

1333 

1334 

1335class CirclePolygon(RegularPolygon): 

1336 """ 

1337 A polygon-approximation of a circle patch. 

1338 """ 

1339 def __str__(self): 

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

1341 return s % (self._xy[0], self._xy[1], self._radius, self._numVertices) 

1342 

1343 @docstring.dedent_interpd 

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

1345 resolution=20, # the number of vertices 

1346 ** kwargs): 

1347 """ 

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

1349 This circle is approximated by a regular polygon with 

1350 *resolution* sides. For a smoother circle drawn with splines, 

1351 see :class:`~matplotlib.patches.Circle`. 

1352 

1353 Valid keyword arguments are: 

1354 

1355 %(Patch)s 

1356 """ 

1357 RegularPolygon.__init__(self, xy, 

1358 resolution, 

1359 radius, 

1360 orientation=0, 

1361 **kwargs) 

1362 

1363 

1364class Ellipse(Patch): 

1365 """ 

1366 A scale-free ellipse. 

1367 """ 

1368 def __str__(self): 

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

1370 self.width, self.height, self.angle) 

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

1372 return fmt % pars 

1373 

1374 @docstring.dedent_interpd 

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

1376 """ 

1377 Parameters 

1378 ---------- 

1379 xy : (float, float) 

1380 xy coordinates of ellipse centre. 

1381 width : float 

1382 Total length (diameter) of horizontal axis. 

1383 height : float 

1384 Total length (diameter) of vertical axis. 

1385 angle : scalar, optional 

1386 Rotation in degrees anti-clockwise. 

1387 

1388 Notes 

1389 ----- 

1390 Valid keyword arguments are: 

1391 

1392 %(Patch)s 

1393 """ 

1394 Patch.__init__(self, **kwargs) 

1395 

1396 self._center = xy 

1397 self.width, self.height = width, height 

1398 self.angle = angle 

1399 self._path = Path.unit_circle() 

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

1401 self._patch_transform = transforms.IdentityTransform() 

1402 

1403 def _recompute_transform(self): 

1404 """ 

1405 Notes 

1406 ----- 

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

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

1409 call the accessor method and not directly access the transformation 

1410 member variable. 

1411 """ 

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

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

1414 width = self.convert_xunits(self.width) 

1415 height = self.convert_yunits(self.height) 

1416 self._patch_transform = transforms.Affine2D() \ 

1417 .scale(width * 0.5, height * 0.5) \ 

1418 .rotate_deg(self.angle) \ 

1419 .translate(*center) 

1420 

1421 def get_path(self): 

1422 """ 

1423 Return the path of the ellipse 

1424 """ 

1425 return self._path 

1426 

1427 def get_patch_transform(self): 

1428 self._recompute_transform() 

1429 return self._patch_transform 

1430 

1431 def set_center(self, xy): 

1432 """ 

1433 Set the center of the ellipse. 

1434 

1435 Parameters 

1436 ---------- 

1437 xy : (float, float) 

1438 """ 

1439 self._center = xy 

1440 self.stale = True 

1441 

1442 def get_center(self): 

1443 """ 

1444 Return the center of the ellipse 

1445 """ 

1446 return self._center 

1447 

1448 center = property(get_center, set_center) 

1449 

1450 

1451class Circle(Ellipse): 

1452 """ 

1453 A circle patch. 

1454 """ 

1455 def __str__(self): 

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

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

1458 return fmt % pars 

1459 

1460 @docstring.dedent_interpd 

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

1462 """ 

1463 Create true circle at center *xy* = (*x*, *y*) with given 

1464 *radius*. Unlike :class:`~matplotlib.patches.CirclePolygon` 

1465 which is a polygonal approximation, this uses Bezier splines 

1466 and is much closer to a scale-free circle. 

1467 

1468 Valid keyword arguments are: 

1469 

1470 %(Patch)s 

1471 """ 

1472 Ellipse.__init__(self, xy, radius * 2, radius * 2, **kwargs) 

1473 self.radius = radius 

1474 

1475 def set_radius(self, radius): 

1476 """ 

1477 Set the radius of the circle 

1478 

1479 Parameters 

1480 ---------- 

1481 radius : float 

1482 """ 

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

1484 self.stale = True 

1485 

1486 def get_radius(self): 

1487 """ 

1488 Return the radius of the circle 

1489 """ 

1490 return self.width / 2. 

1491 

1492 radius = property(get_radius, set_radius) 

1493 

1494 

1495class Arc(Ellipse): 

1496 """ 

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

1498 

1499 Due to internal optimizations, there are certain restrictions on using Arc: 

1500 

1501 - The arc cannot be filled. 

1502 

1503 - The arc must be used in an :class:`~.axes.Axes` instance---it can not be 

1504 added directly to a `.Figure`---because it is optimized to only render 

1505 the segments that are inside the axes bounding box with high resolution. 

1506 """ 

1507 def __str__(self): 

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

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

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

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

1512 return fmt % pars 

1513 

1514 @docstring.dedent_interpd 

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

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

1517 """ 

1518 Parameters 

1519 ---------- 

1520 xy : (float, float) 

1521 The center of the ellipse. 

1522 

1523 width : float 

1524 The length of the horizontal axis. 

1525 

1526 height : float 

1527 The length of the vertical axis. 

1528 

1529 angle : float 

1530 Rotation of the ellipse in degrees (counterclockwise). 

1531 

1532 theta1, theta2 : float, optional 

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

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

1535 the absolute starting angle is 135. 

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

1537 The arc is drawn in the counterclockwise direction. 

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

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

1540 taking the input value mod 360. 

1541 

1542 Other Parameters 

1543 ---------------- 

1544 **kwargs : `.Patch` properties 

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

1546 with the exception of *fill* and *facecolor* because filling is 

1547 not supported. 

1548 

1549 %(Patch)s 

1550 """ 

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

1552 if fill: 

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

1554 

1555 Ellipse.__init__(self, xy, width, height, angle, **kwargs) 

1556 

1557 self.theta1 = theta1 

1558 self.theta2 = theta2 

1559 

1560 @artist.allow_rasterization 

1561 def draw(self, renderer): 

1562 """ 

1563 Draw the arc to the given *renderer*. 

1564 

1565 Notes 

1566 ----- 

1567 Ellipses are normally drawn using an approximation that uses 

1568 eight cubic Bezier splines. The error of this approximation 

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

1570 

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

1572 Four Bezier Cubic Splines.* 

1573 

1574 http://www.tinaja.com/glib/ellipse4.pdf 

1575 

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

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

1578 entire ellipse with enough segments (either splines or line 

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

1580 ellipse is large enough that the error of the spline 

1581 approximation will be visible (greater than one pixel offset 

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

1583 

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

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

1586 (8). The algorithm proceeds as follows: 

1587 

1588 1. The points where the ellipse intersects the axes bounding 

1589 box are located. (This is done be performing an inverse 

1590 transformation on the axes bbox such that it is relative 

1591 to the unit circle -- this makes the intersection 

1592 calculation much easier than doing rotated ellipse 

1593 intersection directly). 

1594 

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

1596 

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

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

1599 

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

1601 

1602 3. Proceeding counterclockwise starting in the positive 

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

1604 pairs of vertices are drawn using the Bezier arc 

1605 approximation technique implemented in 

1606 :meth:`matplotlib.path.Path.arc`. 

1607 """ 

1608 if not hasattr(self, 'axes'): 

1609 raise RuntimeError('Arcs can only be used in Axes instances') 

1610 if not self.get_visible(): 

1611 return 

1612 

1613 self._recompute_transform() 

1614 

1615 width = self.convert_xunits(self.width) 

1616 height = self.convert_yunits(self.height) 

1617 

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

1619 # stretching when calculating angles to draw between 

1620 def theta_stretch(theta, scale): 

1621 theta = np.deg2rad(theta) 

1622 x = np.cos(theta) 

1623 y = np.sin(theta) 

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

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

1626 return (stheta + 360) % 360 

1627 

1628 theta1 = self.theta1 

1629 theta2 = self.theta2 

1630 

1631 if ( 

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

1633 width != height 

1634 # and we are not doing a full circle. 

1635 # 

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

1637 # stretching (due to both float precision limitations and 

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

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

1640 and not (theta1 != theta2 and theta1 % 360 == theta2 % 360) 

1641 ): 

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

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

1644 

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

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

1647 # because we want the transform from dataspace to the 

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

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

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

1651 # space to screen space). 

1652 data_to_screen_trans = self.get_data_transform() 

1653 pwidth, pheight = (data_to_screen_trans.transform((width, height)) - 

1654 data_to_screen_trans.transform((0, 0))) 

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

1656 

1657 if pwidth < inv_error and pheight < inv_error: 

1658 self._path = Path.arc(theta1, theta2) 

1659 return Patch.draw(self, renderer) 

1660 

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

1662 dx = x1 - x0 

1663 dy = y1 - y0 

1664 dr2 = dx * dx + dy * dy 

1665 D = x0 * y1 - x1 * y0 

1666 D2 = D * D 

1667 discrim = dr2 - D2 

1668 if discrim >= 0.0: 

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

1670 sqrt_discrim = np.sqrt(discrim) 

1671 return np.array( 

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

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

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

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

1676 else: 

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

1678 

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

1680 epsilon = 1e-9 

1681 if x1 < x0: 

1682 x0e, x1e = x1, x0 

1683 else: 

1684 x0e, x1e = x0, x1 

1685 if y1 < y0: 

1686 y0e, y1e = y1, y0 

1687 else: 

1688 y0e, y1e = y0, y1 

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

1690 xs, ys = xys.T 

1691 return xys[ 

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

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

1694 ] 

1695 

1696 # Transforms the axes box_path so that it is relative to the unit 

1697 # circle in the same way that it is relative to the desired ellipse. 

1698 box_path_transform = (transforms.BboxTransformTo(self.axes.bbox) 

1699 + self.get_transform().inverted()) 

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

1701 

1702 thetas = set() 

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

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

1705 xy = segment_circle_intersect(*p0, *p1) 

1706 x, y = xy.T 

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

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

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

1710 thetas.update(theta[(theta1 < theta) & (theta < theta2)]) 

1711 thetas = sorted(thetas) + [theta2] 

1712 last_theta = theta1 

1713 theta1_rad = np.deg2rad(theta1) 

1714 inside = box_path.contains_point( 

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

1716 ) 

1717 

1718 # save original path 

1719 path_original = self._path 

1720 for theta in thetas: 

1721 if inside: 

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

1723 Patch.draw(self, renderer) 

1724 inside = False 

1725 else: 

1726 inside = True 

1727 last_theta = theta 

1728 

1729 # restore original path 

1730 self._path = path_original 

1731 

1732 

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

1734 """ 

1735 This is a debug function to draw a rectangle around the bounding 

1736 box returned by 

1737 :meth:`~matplotlib.artist.Artist.get_window_extent` of an artist, 

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

1739 

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

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

1742 """ 

1743 if props is None: 

1744 props = {} 

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

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

1747 pad = renderer.points_to_pixels(pad) 

1748 bbox = artist.get_window_extent(renderer) 

1749 l, b, w, h = bbox.bounds 

1750 l -= pad / 2. 

1751 b -= pad / 2. 

1752 w += pad 

1753 h += pad 

1754 r = Rectangle(xy=(l, b), 

1755 width=w, 

1756 height=h, 

1757 fill=fill, 

1758 ) 

1759 r.set_transform(transforms.IdentityTransform()) 

1760 r.set_clip_on(False) 

1761 r.update(props) 

1762 r.draw(renderer) 

1763 

1764 

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

1766 """ 

1767 This is a debug function to draw a rectangle around the bounding 

1768 box returned by 

1769 :meth:`~matplotlib.artist.Artist.get_window_extent` of an artist, 

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

1771 """ 

1772 

1773 l, b, w, h = bbox.bounds 

1774 r = Rectangle(xy=(l, b), 

1775 width=w, 

1776 height=h, 

1777 edgecolor=color, 

1778 fill=False, 

1779 ) 

1780 if trans is not None: 

1781 r.set_transform(trans) 

1782 r.set_clip_on(False) 

1783 r.draw(renderer) 

1784 

1785 

1786def _pprint_styles(_styles): 

1787 """ 

1788 A helper function for the _Style class. Given the dictionary of 

1789 {stylename: styleclass}, return a formatted string listing all the 

1790 styles. Used to update the documentation. 

1791 """ 

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

1793 *[(cls.__name__, 

1794 # adding backquotes since - and | have special meaning in reST 

1795 f'``{name}``', 

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

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

1798 for name, cls in sorted(_styles.items())]] 

1799 # Convert to rst table. 

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

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

1802 rst_table = '\n'.join([ 

1803 '', 

1804 table_formatstr, 

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

1806 table_formatstr, 

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

1808 for row in table[1:]], 

1809 table_formatstr, 

1810 '', 

1811 ]) 

1812 return textwrap.indent(rst_table, prefix=' ' * 2) 

1813 

1814 

1815def _simpleprint_styles(_styles): 

1816 """ 

1817 A helper function for the _Style class. Given the dictionary of 

1818 {stylename: styleclass}, return a string rep of the list of keys. 

1819 Used to update the documentation. 

1820 """ 

1821 return "[{}]".format("|".join(map(" '{}' ".format, sorted(_styles)))) 

1822 

1823 

1824class _Style: 

1825 """ 

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

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

1828 provides some helper functions. 

1829 """ 

1830 def __new__(cls, stylename, **kw): 

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

1832 

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

1834 # of style names to style classes. 

1835 

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

1837 _name = _list[0].lower() 

1838 try: 

1839 _cls = cls._style_list[_name] 

1840 except KeyError: 

1841 raise ValueError("Unknown style : %s" % stylename) 

1842 

1843 try: 

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

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

1846 except ValueError: 

1847 raise ValueError("Incorrect style argument : %s" % stylename) 

1848 _args.update(kw) 

1849 

1850 return _cls(**_args) 

1851 

1852 @classmethod 

1853 def get_styles(cls): 

1854 """ 

1855 A class method which returns a dictionary of available styles. 

1856 """ 

1857 return cls._style_list 

1858 

1859 @classmethod 

1860 def pprint_styles(cls): 

1861 """ 

1862 A class method which returns a string of the available styles. 

1863 """ 

1864 return _pprint_styles(cls._style_list) 

1865 

1866 @classmethod 

1867 def register(cls, name, style): 

1868 """ 

1869 Register a new style. 

1870 """ 

1871 

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

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

1874 cls._Base)) 

1875 cls._style_list[name] = style 

1876 

1877 

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

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

1880 if cls is None: 

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

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

1883 return cls 

1884 

1885 

1886class BoxStyle(_Style): 

1887 """ 

1888 :class:`BoxStyle` is a container class which defines several 

1889 boxstyle classes, which are used for :class:`FancyBboxPatch`. 

1890 

1891 A style object can be created as:: 

1892 

1893 BoxStyle.Round(pad=0.2) 

1894 

1895 or:: 

1896 

1897 BoxStyle("Round", pad=0.2) 

1898 

1899 or:: 

1900 

1901 BoxStyle("Round, pad=0.2") 

1902 

1903 Following boxstyle classes are defined. 

1904 

1905 %(AvailableBoxstyles)s 

1906 

1907 An instance of any boxstyle class is an callable object, 

1908 whose call signature is:: 

1909 

1910 __call__(self, x0, y0, width, height, mutation_size, aspect_ratio=1.) 

1911 

1912 and returns a :class:`Path` instance. *x0*, *y0*, *width* and 

1913 *height* specify the location and size of the box to be 

1914 drawn. *mutation_scale* determines the overall size of the 

1915 mutation (by which I mean the transformation of the rectangle to 

1916 the fancy box). *mutation_aspect* determines the aspect-ratio of 

1917 the mutation. 

1918 """ 

1919 

1920 _style_list = {} 

1921 

1922 class _Base: 

1923 """ 

1924 Abstract base class for styling of `.FancyBboxPatch`. 

1925 

1926 This class is not an artist itself. The `__call__` method returns the 

1927 `~matplotlib.path.Path` for outlining the fancy box. The actual drawing 

1928 is handled in `.FancyBboxPatch`. 

1929 

1930 Subclasses may only use parameters with default values in their 

1931 ``__init__`` method because they must be able to be initialized 

1932 without arguments. 

1933 

1934 Subclasses must implement the `transmute` method. It receives the 

1935 enclosing rectangle *x0, y0, width, height* as well as the 

1936 *mutation_size*, which scales the outline properties such as padding. 

1937 It returns the outline of the fancy box as `.path.Path`. 

1938 """ 

1939 

1940 def transmute(self, x0, y0, width, height, mutation_size): 

1941 """Return the `~.path.Path` outlining the given rectangle.""" 

1942 raise NotImplementedError('Derived must override') 

1943 

1944 def __call__(self, x0, y0, width, height, mutation_size, 

1945 aspect_ratio=1.): 

1946 """ 

1947 Given the location and size of the box, return the path of 

1948 the box around it. 

1949 

1950 Parameters 

1951 ---------- 

1952 x0, y0, width, height : float 

1953 Location and size of the box. 

1954 mutation_size : float 

1955 A reference scale for the mutation. 

1956 aspect_ratio : float, default: 1 

1957 Aspect-ratio for the mutation. 

1958 

1959 Returns 

1960 ------- 

1961 path : `~matplotlib.path.Path` 

1962 """ 

1963 # The __call__ method is a thin wrapper around the transmute method 

1964 # and takes care of the aspect. 

1965 

1966 if aspect_ratio is not None: 

1967 # Squeeze the given height by the aspect_ratio 

1968 y0, height = y0 / aspect_ratio, height / aspect_ratio 

1969 # call transmute method with squeezed height. 

1970 path = self.transmute(x0, y0, width, height, mutation_size) 

1971 vertices, codes = path.vertices, path.codes 

1972 # Restore the height 

1973 vertices[:, 1] = vertices[:, 1] * aspect_ratio 

1974 return Path(vertices, codes) 

1975 else: 

1976 return self.transmute(x0, y0, width, height, mutation_size) 

1977 

1978 @_register_style(_style_list) 

1979 class Square(_Base): 

1980 """ 

1981 A square box. 

1982 

1983 Parameters 

1984 ---------- 

1985 pad : float, default: 0.3 

1986 The amount of padding around the original box. 

1987 """ 

1988 def __init__(self, pad=0.3): 

1989 self.pad = pad 

1990 super().__init__() 

1991 

1992 def transmute(self, x0, y0, width, height, mutation_size): 

1993 pad = mutation_size * self.pad 

1994 

1995 # width and height with padding added. 

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

1997 

1998 # boundary of the padded box 

1999 x0, y0 = x0 - pad, y0 - pad, 

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

2001 

2002 vertices = [(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)] 

2003 codes = [Path.MOVETO] + [Path.LINETO] * 3 + [Path.CLOSEPOLY] 

2004 return Path(vertices, codes) 

2005 

2006 @_register_style(_style_list) 

2007 class Circle(_Base): 

2008 """ 

2009 A circular box. 

2010 

2011 Parameters 

2012 ---------- 

2013 pad : float, default: 0.3 

2014 The amount of padding around the original box. 

2015 """ 

2016 def __init__(self, pad=0.3): 

2017 self.pad = pad 

2018 super().__init__() 

2019 

2020 def transmute(self, x0, y0, width, height, mutation_size): 

2021 pad = mutation_size * self.pad 

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

2023 

2024 # boundary of the padded box 

2025 x0, y0 = x0 - pad, y0 - pad, 

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

2027 max(width, height) / 2) 

2028 

2029 @_register_style(_style_list) 

2030 class LArrow(_Base): 

2031 """ 

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

2033 

2034 Parameters 

2035 ---------- 

2036 pad : float, default: 0.3 

2037 The amount of padding around the original box. 

2038 """ 

2039 def __init__(self, pad=0.3): 

2040 self.pad = pad 

2041 super().__init__() 

2042 

2043 def transmute(self, x0, y0, width, height, mutation_size): 

2044 # padding 

2045 pad = mutation_size * self.pad 

2046 

2047 # width and height with padding added. 

2048 width, height = width + 2. * pad, height + 2. * pad 

2049 

2050 # boundary of the padded box 

2051 x0, y0 = x0 - pad, y0 - pad, 

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

2053 

2054 dx = (y1 - y0) / 2. 

2055 dxx = dx * .5 

2056 # adjust x0. 1.4 <- sqrt(2) 

2057 x0 = x0 + pad / 1.4 

2058 

2059 cp = [(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1), 

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

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

2062 (x0 + dxx, y0), (x0 + dxx, y0)] 

2063 

2064 com = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, 

2065 Path.LINETO, Path.LINETO, Path.LINETO, 

2066 Path.LINETO, Path.CLOSEPOLY] 

2067 

2068 path = Path(cp, com) 

2069 

2070 return path 

2071 

2072 @_register_style(_style_list) 

2073 class RArrow(LArrow): 

2074 """ 

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

2076 

2077 Parameters 

2078 ---------- 

2079 pad : float, default: 0.3 

2080 The amount of padding around the original box. 

2081 """ 

2082 def __init__(self, pad=0.3): 

2083 super().__init__(pad) 

2084 

2085 def transmute(self, x0, y0, width, height, mutation_size): 

2086 p = BoxStyle.LArrow.transmute(self, x0, y0, 

2087 width, height, mutation_size) 

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

2089 return p 

2090 

2091 @_register_style(_style_list) 

2092 class DArrow(_Base): 

2093 """ 

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

2095 

2096 Parameters 

2097 ---------- 

2098 pad : float, default: 0.3 

2099 The amount of padding around the original box. 

2100 """ 

2101 # This source is copied from LArrow, 

2102 # modified to add a right arrow to the bbox. 

2103 

2104 def __init__(self, pad=0.3): 

2105 self.pad = pad 

2106 super().__init__() 

2107 

2108 def transmute(self, x0, y0, width, height, mutation_size): 

2109 

2110 # padding 

2111 pad = mutation_size * self.pad 

2112 

2113 # width and height with padding added. 

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

2115 height = height + 2. * pad 

2116 

2117 # boundary of the padded box 

2118 x0, y0 = x0 - pad, y0 - pad 

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

2120 

2121 dx = (y1 - y0) / 2 

2122 dxx = dx * .5 

2123 # adjust x0. 1.4 <- sqrt(2) 

2124 x0 = x0 + pad / 1.4 

2125 

2126 cp = [(x0 + dxx, y0), (x1, y0), # bot-segment 

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

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

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

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

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

2132 (x0 + dxx, y0), (x0 + dxx, y0)] # close-poly 

2133 

2134 com = [Path.MOVETO, Path.LINETO, 

2135 Path.LINETO, Path.LINETO, 

2136 Path.LINETO, 

2137 Path.LINETO, Path.LINETO, 

2138 Path.LINETO, Path.LINETO, 

2139 Path.LINETO, 

2140 Path.LINETO, Path.CLOSEPOLY] 

2141 

2142 path = Path(cp, com) 

2143 

2144 return path 

2145 

2146 @_register_style(_style_list) 

2147 class Round(_Base): 

2148 """ 

2149 A box with round corners. 

2150 

2151 Parameters 

2152 ---------- 

2153 pad : float, default: 0.3 

2154 The amount of padding around the original box. 

2155 rounding_size : float, default: *pad* 

2156 Radius of the corners. 

2157 """ 

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

2159 self.pad = pad 

2160 self.rounding_size = rounding_size 

2161 super().__init__() 

2162 

2163 def transmute(self, x0, y0, width, height, mutation_size): 

2164 

2165 # padding 

2166 pad = mutation_size * self.pad 

2167 

2168 # size of the rounding corner 

2169 if self.rounding_size: 

2170 dr = mutation_size * self.rounding_size 

2171 else: 

2172 dr = pad 

2173 

2174 width, height = width + 2. * pad, height + 2. * pad 

2175 

2176 x0, y0 = x0 - pad, y0 - pad, 

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

2178 

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

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

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

2182 (x1 - dr, y0), 

2183 (x1, y0), (x1, y0 + dr), 

2184 (x1, y1 - dr), 

2185 (x1, y1), (x1 - dr, y1), 

2186 (x0 + dr, y1), 

2187 (x0, y1), (x0, y1 - dr), 

2188 (x0, y0 + dr), 

2189 (x0, y0), (x0 + dr, y0), 

2190 (x0 + dr, y0)] 

2191 

2192 com = [Path.MOVETO, 

2193 Path.LINETO, 

2194 Path.CURVE3, Path.CURVE3, 

2195 Path.LINETO, 

2196 Path.CURVE3, Path.CURVE3, 

2197 Path.LINETO, 

2198 Path.CURVE3, Path.CURVE3, 

2199 Path.LINETO, 

2200 Path.CURVE3, Path.CURVE3, 

2201 Path.CLOSEPOLY] 

2202 

2203 path = Path(cp, com) 

2204 

2205 return path 

2206 

2207 @_register_style(_style_list) 

2208 class Round4(_Base): 

2209 """ 

2210 A box with rounded edges. 

2211 

2212 Parameters 

2213 ---------- 

2214 pad : float, default: 0.3 

2215 The amount of padding around the original box. 

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

2217 Rounding of edges. 

2218 """ 

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

2220 self.pad = pad 

2221 self.rounding_size = rounding_size 

2222 super().__init__() 

2223 

2224 def transmute(self, x0, y0, width, height, mutation_size): 

2225 

2226 # padding 

2227 pad = mutation_size * self.pad 

2228 

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

2230 if self.rounding_size: 

2231 dr = mutation_size * self.rounding_size 

2232 else: 

2233 dr = pad / 2. 

2234 

2235 width, height = (width + 2. * pad - 2 * dr, 

2236 height + 2. * pad - 2 * dr) 

2237 

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

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

2240 

2241 cp = [(x0, y0), 

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

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

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

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

2246 (x0, y0)] 

2247 

2248 com = [Path.MOVETO, 

2249 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2250 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2251 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2252 Path.CURVE4, Path.CURVE4, Path.CURVE4, 

2253 Path.CLOSEPOLY] 

2254 

2255 path = Path(cp, com) 

2256 

2257 return path 

2258 

2259 @_register_style(_style_list) 

2260 class Sawtooth(_Base): 

2261 """ 

2262 A box with a sawtooth outline. 

2263 

2264 Parameters 

2265 ---------- 

2266 pad : float, default: 0.3 

2267 The amount of padding around the original box. 

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

2269 Size of the sawtooth. 

2270 """ 

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

2272 self.pad = pad 

2273 self.tooth_size = tooth_size 

2274 super().__init__() 

2275 

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

2277 

2278 # padding 

2279 pad = mutation_size * self.pad 

2280 

2281 # size of sawtooth 

2282 if self.tooth_size is None: 

2283 tooth_size = self.pad * .5 * mutation_size 

2284 else: 

2285 tooth_size = self.tooth_size * mutation_size 

2286 

2287 tooth_size2 = tooth_size / 2. 

2288 width, height = (width + 2. * pad - tooth_size, 

2289 height + 2. * pad - tooth_size) 

2290 

2291 # the sizes of the vertical and horizontal sawtooth are 

2292 # separately adjusted to fit the given box size. 

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

2294 dsx = (width - tooth_size) / dsx_n 

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

2296 dsy = (height - tooth_size) / dsy_n 

2297 

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

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

2300 

2301 bottom_saw_x = [ 

2302 x0, 

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

2304 x1 - tooth_size2, 

2305 ] 

2306 bottom_saw_y = [ 

2307 y0, 

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

2309 y0 - tooth_size2, 

2310 ] 

2311 right_saw_x = [ 

2312 x1, 

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

2314 x1 + tooth_size2, 

2315 ] 

2316 right_saw_y = [ 

2317 y0, 

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

2319 y1 - tooth_size2, 

2320 ] 

2321 top_saw_x = [ 

2322 x1, 

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

2324 x0 + tooth_size2, 

2325 ] 

2326 top_saw_y = [ 

2327 y1, 

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

2329 y1 + tooth_size2, 

2330 ] 

2331 left_saw_x = [ 

2332 x0, 

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

2334 x0 - tooth_size2, 

2335 ] 

2336 left_saw_y = [ 

2337 y1, 

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

2339 y0 + tooth_size2, 

2340 ] 

2341 

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

2343 *zip(right_saw_x, right_saw_y), 

2344 *zip(top_saw_x, top_saw_y), 

2345 *zip(left_saw_x, left_saw_y), 

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

2347 

2348 return saw_vertices 

2349 

2350 def transmute(self, x0, y0, width, height, mutation_size): 

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

2352 height, mutation_size) 

2353 path = Path(saw_vertices, closed=True) 

2354 return path 

2355 

2356 @_register_style(_style_list) 

2357 class Roundtooth(Sawtooth): 

2358 """ 

2359 A box with a rounded sawtooth outline. 

2360 

2361 Parameters 

2362 ---------- 

2363 pad : float, default: 0.3 

2364 The amount of padding around the original box. 

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

2366 Size of the sawtooth. 

2367 """ 

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

2369 super().__init__(pad, tooth_size) 

2370 

2371 def transmute(self, x0, y0, width, height, mutation_size): 

2372 saw_vertices = self._get_sawtooth_vertices(x0, y0, 

2373 width, height, 

2374 mutation_size) 

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

2376 saw_vertices = np.concatenate([np.array(saw_vertices), 

2377 [saw_vertices[0]]], axis=0) 

2378 codes = ([Path.MOVETO] + 

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

2380 [Path.CLOSEPOLY]) 

2381 return Path(saw_vertices, codes) 

2382 

2383 if __doc__: # __doc__ could be None if -OO optimization is enabled 

2384 __doc__ = inspect.cleandoc(__doc__) % { 

2385 "AvailableBoxstyles": _pprint_styles(_style_list)} 

2386 

2387docstring.interpd.update( 

2388 AvailableBoxstyles=_pprint_styles(BoxStyle._style_list), 

2389 ListBoxstyles=_simpleprint_styles(BoxStyle._style_list)) 

2390 

2391 

2392class FancyBboxPatch(Patch): 

2393 """ 

2394 A fancy box around a rectangle with lower left at *xy* = (*x*, *y*) 

2395 with specified width and height. 

2396 

2397 `.FancyBboxPatch` is similar to `.Rectangle`, but it draws a fancy box 

2398 around the rectangle. The transformation of the rectangle box to the 

2399 fancy box is delegated to the style classes defined in `.BoxStyle`. 

2400 """ 

2401 

2402 _edge_default = True 

2403 

2404 def __str__(self): 

2405 s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)" 

2406 return s % (self._x, self._y, self._width, self._height) 

2407 

2408 @docstring.dedent_interpd 

2409 def __init__(self, xy, width, height, 

2410 boxstyle="round", 

2411 bbox_transmuter=None, 

2412 mutation_scale=1., 

2413 mutation_aspect=None, 

2414 **kwargs): 

2415 """ 

2416 Parameters 

2417 ---------- 

2418 xy : float, float 

2419 The lower left corner of the box. 

2420 

2421 width : float 

2422 The width of the box. 

2423 

2424 height : float 

2425 The height of the box. 

2426 

2427 boxstyle : str or `matplotlib.patches.BoxStyle` 

2428 The style of the fancy box. This can either be a `.BoxStyle` 

2429 instance or a string of the style name and optionally comma 

2430 seprarated attributes (e.g. "Round, pad=0.2"). This string is 

2431 passed to `.BoxStyle` to construct a `.BoxStyle` object. See 

2432 there for a full documentation. 

2433 

2434 The following box styles are available: 

2435 

2436 %(AvailableBoxstyles)s 

2437 

2438 mutation_scale : float, optional, default: 1 

2439 Scaling factor applied to the attributes of the box style 

2440 (e.g. pad or rounding_size). 

2441 

2442 mutation_aspect : float, optional 

2443 The height of the rectangle will be squeezed by this value before 

2444 the mutation and the mutated box will be stretched by the inverse 

2445 of it. For example, this allows different horizontal and vertical 

2446 padding. 

2447 

2448 Other Parameters 

2449 ---------------- 

2450 **kwargs : `.Patch` properties 

2451 

2452 %(Patch)s 

2453 """ 

2454 

2455 Patch.__init__(self, **kwargs) 

2456 

2457 self._x = xy[0] 

2458 self._y = xy[1] 

2459 self._width = width 

2460 self._height = height 

2461 

2462 if boxstyle == "custom": 

2463 if bbox_transmuter is None: 

2464 raise ValueError("bbox_transmuter argument is needed with " 

2465 "custom boxstyle") 

2466 self._bbox_transmuter = bbox_transmuter 

2467 else: 

2468 self.set_boxstyle(boxstyle) 

2469 

2470 self._mutation_scale = mutation_scale 

2471 self._mutation_aspect = mutation_aspect 

2472 

2473 self.stale = True 

2474 

2475 @docstring.dedent_interpd 

2476 def set_boxstyle(self, boxstyle=None, **kwargs): 

2477 """ 

2478 Set the box style. 

2479 

2480 Most box styles can be further configured using attributes. 

2481 Attributes from the previous box style are not reused. 

2482 

2483 Without argument (or with ``boxstyle=None``), the available box styles 

2484 are returned as a human-readable string. 

2485 

2486 Parameters 

2487 ---------- 

2488 boxstyle : str 

2489 The name of the box style. Optionally, followed by a comma and a 

2490 comma-separated list of attributes. The attributes may 

2491 alternatively be passed separately as keyword arguments. 

2492 

2493 The following box styles are available: 

2494 

2495 %(AvailableBoxstyles)s 

2496 

2497 .. ACCEPTS: %(ListBoxstyles)s 

2498 

2499 **kwargs 

2500 Additional attributes for the box style. See the table above for 

2501 supported parameters. 

2502 

2503 Examples 

2504 -------- 

2505 :: 

2506 

2507 set_boxstyle("round,pad=0.2") 

2508 set_boxstyle("round", pad=0.2) 

2509 

2510 """ 

2511 if boxstyle is None: 

2512 return BoxStyle.pprint_styles() 

2513 

2514 if isinstance(boxstyle, BoxStyle._Base) or callable(boxstyle): 

2515 self._bbox_transmuter = boxstyle 

2516 else: 

2517 self._bbox_transmuter = BoxStyle(boxstyle, **kwargs) 

2518 self.stale = True 

2519 

2520 def set_mutation_scale(self, scale): 

2521 """ 

2522 Set the mutation scale. 

2523 

2524 Parameters 

2525 ---------- 

2526 scale : float 

2527 """ 

2528 self._mutation_scale = scale 

2529 self.stale = True 

2530 

2531 def get_mutation_scale(self): 

2532 """Return the mutation scale.""" 

2533 return self._mutation_scale 

2534 

2535 def set_mutation_aspect(self, aspect): 

2536 """ 

2537 Set the aspect ratio of the bbox mutation. 

2538 

2539 Parameters 

2540 ---------- 

2541 aspect : float 

2542 """ 

2543 self._mutation_aspect = aspect 

2544 self.stale = True 

2545 

2546 def get_mutation_aspect(self): 

2547 """Return the aspect ratio of the bbox mutation.""" 

2548 return self._mutation_aspect 

2549 

2550 def get_boxstyle(self): 

2551 """Return the boxstyle object.""" 

2552 return self._bbox_transmuter 

2553 

2554 def get_path(self): 

2555 """Return the mutated path of the rectangle.""" 

2556 _path = self.get_boxstyle()(self._x, self._y, 

2557 self._width, self._height, 

2558 self.get_mutation_scale(), 

2559 self.get_mutation_aspect()) 

2560 return _path 

2561 

2562 # Following methods are borrowed from the Rectangle class. 

2563 

2564 def get_x(self): 

2565 """Return the left coord of the rectangle.""" 

2566 return self._x 

2567 

2568 def get_y(self): 

2569 """Return the bottom coord of the rectangle.""" 

2570 return self._y 

2571 

2572 def get_width(self): 

2573 """Return the width of the rectangle.""" 

2574 return self._width 

2575 

2576 def get_height(self): 

2577 """Return the height of the rectangle.""" 

2578 return self._height 

2579 

2580 def set_x(self, x): 

2581 """ 

2582 Set the left coord of the rectangle. 

2583 

2584 Parameters 

2585 ---------- 

2586 x : float 

2587 """ 

2588 self._x = x 

2589 self.stale = True 

2590 

2591 def set_y(self, y): 

2592 """ 

2593 Set the bottom coord of the rectangle. 

2594 

2595 Parameters 

2596 ---------- 

2597 y : float 

2598 """ 

2599 self._y = y 

2600 self.stale = True 

2601 

2602 def set_width(self, w): 

2603 """ 

2604 Set the rectangle width. 

2605 

2606 Parameters 

2607 ---------- 

2608 w : float 

2609 """ 

2610 self._width = w 

2611 self.stale = True 

2612 

2613 def set_height(self, h): 

2614 """ 

2615 Set the rectangle height. 

2616 

2617 Parameters 

2618 ---------- 

2619 h : float 

2620 """ 

2621 self._height = h 

2622 self.stale = True 

2623 

2624 def set_bounds(self, *args): 

2625 """ 

2626 Set the bounds of the rectangle. 

2627 

2628 Call signatures:: 

2629 

2630 set_bounds(left, bottom, width, height) 

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

2632 

2633 Parameters 

2634 ---------- 

2635 left, bottom : float 

2636 The coordinates of the bottom left corner of the rectangle. 

2637 width, height : float 

2638 The width/height of the rectangle. 

2639 """ 

2640 if len(args) == 1: 

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

2642 else: 

2643 l, b, w, h = args 

2644 self._x = l 

2645 self._y = b 

2646 self._width = w 

2647 self._height = h 

2648 self.stale = True 

2649 

2650 def get_bbox(self): 

2651 """Return the `.Bbox`.""" 

2652 return transforms.Bbox.from_bounds(self._x, self._y, 

2653 self._width, self._height) 

2654 

2655 

2656class ConnectionStyle(_Style): 

2657 """ 

2658 :class:`ConnectionStyle` is a container class which defines 

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

2660 between two points. These are mainly used with 

2661 :class:`FancyArrowPatch`. 

2662 

2663 A connectionstyle object can be either created as:: 

2664 

2665 ConnectionStyle.Arc3(rad=0.2) 

2666 

2667 or:: 

2668 

2669 ConnectionStyle("Arc3", rad=0.2) 

2670 

2671 or:: 

2672 

2673 ConnectionStyle("Arc3, rad=0.2") 

2674 

2675 The following classes are defined 

2676 

2677 %(AvailableConnectorstyles)s 

2678 

2679 An instance of any connection style class is an callable object, 

2680 whose call signature is:: 

2681 

2682 __call__(self, posA, posB, 

2683 patchA=None, patchB=None, 

2684 shrinkA=2., shrinkB=2.) 

2685 

2686 and it returns a :class:`Path` instance. *posA* and *posB* are 

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

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

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

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

2691 which is given in points. 

2692 """ 

2693 

2694 _style_list = {} 

2695 

2696 class _Base: 

2697 """ 

2698 A base class for connectionstyle classes. The subclass needs 

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

2700 

2701 connect(posA, posB) 

2702 

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

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

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

2706 helper methods. 

2707 """ 

2708 

2709 class SimpleEvent: 

2710 def __init__(self, xy): 

2711 self.x, self.y = xy 

2712 

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

2714 """ 

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

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

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

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

2719 is inside the path. 

2720 """ 

2721 

2722 if patchA: 

2723 def insideA(xy_display): 

2724 xy_event = ConnectionStyle._Base.SimpleEvent(xy_display) 

2725 return patchA.contains(xy_event)[0] 

2726 

2727 try: 

2728 left, right = split_path_inout(path, insideA) 

2729 except ValueError: 

2730 right = path 

2731 

2732 path = right 

2733 

2734 if patchB: 

2735 def insideB(xy_display): 

2736 xy_event = ConnectionStyle._Base.SimpleEvent(xy_display) 

2737 return patchB.contains(xy_event)[0] 

2738 

2739 try: 

2740 left, right = split_path_inout(path, insideB) 

2741 except ValueError: 

2742 left = path 

2743 

2744 path = left 

2745 

2746 return path 

2747 

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

2749 """ 

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

2751 """ 

2752 if shrinkA: 

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

2754 try: 

2755 left, path = split_path_inout(path, insideA) 

2756 except ValueError: 

2757 pass 

2758 if shrinkB: 

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

2760 try: 

2761 path, right = split_path_inout(path, insideB) 

2762 except ValueError: 

2763 pass 

2764 return path 

2765 

2766 def __call__(self, posA, posB, 

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

2768 """ 

2769 Calls the *connect* method to create a path between *posA* 

2770 and *posB*. The path is clipped and shrunken. 

2771 """ 

2772 

2773 path = self.connect(posA, posB) 

2774 

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

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

2777 

2778 return shrunk_path 

2779 

2780 @_register_style(_style_list) 

2781 class Arc3(_Base): 

2782 """ 

2783 Creates a simple quadratic Bezier curve between two 

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

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

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

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

2788 """ 

2789 

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

2791 """ 

2792 *rad* 

2793 curvature of the curve. 

2794 """ 

2795 self.rad = rad 

2796 

2797 def connect(self, posA, posB): 

2798 x1, y1 = posA 

2799 x2, y2 = posB 

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

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

2802 

2803 f = self.rad 

2804 

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

2806 

2807 vertices = [(x1, y1), 

2808 (cx, cy), 

2809 (x2, y2)] 

2810 codes = [Path.MOVETO, 

2811 Path.CURVE3, 

2812 Path.CURVE3] 

2813 

2814 return Path(vertices, codes) 

2815 

2816 @_register_style(_style_list) 

2817 class Angle3(_Base): 

2818 """ 

2819 Creates a simple quadratic Bezier curve between two 

2820 points. The middle control points is placed at the 

2821 intersecting point of two lines which cross the start and 

2822 end point, and have a slope of angleA and angleB, respectively. 

2823 """ 

2824 

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

2826 """ 

2827 *angleA* 

2828 starting angle of the path 

2829 

2830 *angleB* 

2831 ending angle of the path 

2832 """ 

2833 

2834 self.angleA = angleA 

2835 self.angleB = angleB 

2836 

2837 def connect(self, posA, posB): 

2838 x1, y1 = posA 

2839 x2, y2 = posB 

2840 

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

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

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

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

2845 

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

2847 x2, y2, cosB, sinB) 

2848 

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

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

2851 

2852 return Path(vertices, codes) 

2853 

2854 @_register_style(_style_list) 

2855 class Angle(_Base): 

2856 """ 

2857 Creates a piecewise continuous quadratic Bezier path between 

2858 two points. The path has a one passing-through point placed at 

2859 the intersecting point of two lines which cross the start 

2860 and end point, and have a slope of angleA and angleB, respectively. 

2861 The connecting edges are rounded with *rad*. 

2862 """ 

2863 

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

2865 """ 

2866 *angleA* 

2867 starting angle of the path 

2868 

2869 *angleB* 

2870 ending angle of the path 

2871 

2872 *rad* 

2873 rounding radius of the edge 

2874 """ 

2875 

2876 self.angleA = angleA 

2877 self.angleB = angleB 

2878 

2879 self.rad = rad 

2880 

2881 def connect(self, posA, posB): 

2882 x1, y1 = posA 

2883 x2, y2 = posB 

2884 

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

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

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

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

2889 

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

2891 x2, y2, cosB, sinB) 

2892 

2893 vertices = [(x1, y1)] 

2894 codes = [Path.MOVETO] 

2895 

2896 if self.rad == 0.: 

2897 vertices.append((cx, cy)) 

2898 codes.append(Path.LINETO) 

2899 else: 

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

2901 d1 = np.hypot(dx1, dy1) 

2902 f1 = self.rad / d1 

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

2904 d2 = np.hypot(dx2, dy2) 

2905 f2 = self.rad / d2 

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

2907 (cx, cy), 

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

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

2910 

2911 vertices.append((x2, y2)) 

2912 codes.append(Path.LINETO) 

2913 

2914 return Path(vertices, codes) 

2915 

2916 @_register_style(_style_list) 

2917 class Arc(_Base): 

2918 """ 

2919 Creates a piecewise continuous quadratic Bezier path between 

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

2921 point placed at the distance of armA and angle of angleA from 

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

2923 rounded with *rad*. 

2924 """ 

2925 

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

2927 """ 

2928 *angleA* : 

2929 starting angle of the path 

2930 

2931 *angleB* : 

2932 ending angle of the path 

2933 

2934 *armA* : 

2935 length of the starting arm 

2936 

2937 *armB* : 

2938 length of the ending arm 

2939 

2940 *rad* : 

2941 rounding radius of the edges 

2942 """ 

2943 

2944 self.angleA = angleA 

2945 self.angleB = angleB 

2946 self.armA = armA 

2947 self.armB = armB 

2948 

2949 self.rad = rad 

2950 

2951 def connect(self, posA, posB): 

2952 x1, y1 = posA 

2953 x2, y2 = posB 

2954 

2955 vertices = [(x1, y1)] 

2956 rounded = [] 

2957 codes = [Path.MOVETO] 

2958 

2959 if self.armA: 

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

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

2962 # x_armA, y_armB 

2963 d = self.armA - self.rad 

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

2965 d = self.armA 

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

2967 

2968 if self.armB: 

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

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

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

2972 

2973 if rounded: 

2974 xp, yp = rounded[-1] 

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

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

2977 

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

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

2980 vertices.extend(rounded) 

2981 codes.extend([Path.LINETO, 

2982 Path.CURVE3, 

2983 Path.CURVE3]) 

2984 else: 

2985 xp, yp = vertices[-1] 

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

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

2988 

2989 d = dd - self.rad 

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

2991 (x_armB, y_armB)] 

2992 

2993 if rounded: 

2994 xp, yp = rounded[-1] 

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

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

2997 

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

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

3000 vertices.extend(rounded) 

3001 codes.extend([Path.LINETO, 

3002 Path.CURVE3, 

3003 Path.CURVE3]) 

3004 

3005 vertices.append((x2, y2)) 

3006 codes.append(Path.LINETO) 

3007 

3008 return Path(vertices, codes) 

3009 

3010 @_register_style(_style_list) 

3011 class Bar(_Base): 

3012 """ 

3013 A line with *angle* between A and B with *armA* and 

3014 *armB*. One of the arms is extended so that they are connected in 

3015 a right angle. The length of armA is determined by (*armA* 

3016 + *fraction* x AB distance). Same for armB. 

3017 """ 

3018 

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

3020 """ 

3021 Parameters 

3022 ---------- 

3023 armA : float 

3024 minimum length of armA 

3025 

3026 armB : float 

3027 minimum length of armB 

3028 

3029 fraction : float 

3030 a fraction of the distance between two points that 

3031 will be added to armA and armB. 

3032 

3033 angle : float or None 

3034 angle of the connecting line (if None, parallel 

3035 to A and B) 

3036 """ 

3037 self.armA = armA 

3038 self.armB = armB 

3039 self.fraction = fraction 

3040 self.angle = angle 

3041 

3042 def connect(self, posA, posB): 

3043 x1, y1 = posA 

3044 x20, y20 = x2, y2 = posB 

3045 

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

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

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

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

3050 

3051 armA, armB = self.armA, self.armB 

3052 

3053 if self.angle is not None: 

3054 theta0 = np.deg2rad(self.angle) 

3055 dtheta = theta1 - theta0 

3056 dl = dd * math.sin(dtheta) 

3057 dL = dd * math.cos(dtheta) 

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

3059 armB = armB - dl 

3060 

3061 # update 

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

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

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

3065 

3066 arm = max(armA, armB) 

3067 f = self.fraction * dd + arm 

3068 

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

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

3071 

3072 vertices = [(x1, y1), 

3073 (cx1, cy1), 

3074 (cx2, cy2), 

3075 (x20, y20)] 

3076 codes = [Path.MOVETO, 

3077 Path.LINETO, 

3078 Path.LINETO, 

3079 Path.LINETO] 

3080 

3081 return Path(vertices, codes) 

3082 

3083 if __doc__: 

3084 __doc__ = inspect.cleandoc(__doc__) % { 

3085 "AvailableConnectorstyles": _pprint_styles(_style_list)} 

3086 

3087 

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

3089 """ 

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

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

3092 """ 

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

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

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

3096 

3097 return x2, y2 

3098 

3099 

3100class ArrowStyle(_Style): 

3101 """ 

3102 :class:`ArrowStyle` is a container class which defines several 

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

3104 given path. These are mainly used with :class:`FancyArrowPatch`. 

3105 

3106 A arrowstyle object can be either created as:: 

3107 

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

3109 

3110 or:: 

3111 

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

3113 

3114 or:: 

3115 

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

3117 

3118 The following classes are defined 

3119 

3120 %(AvailableArrowstyles)s 

3121 

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

3123 whose call signature is:: 

3124 

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

3126 

3127 and it returns a tuple of a :class:`Path` instance and a boolean 

3128 value. *path* is a :class:`Path` instance along which the arrow 

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

3130 meaning as in :class:`BoxStyle`. *linewidth* is a line width to be 

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

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

3133 classes support it. 

3134 """ 

3135 

3136 _style_list = {} 

3137 

3138 class _Base: 

3139 """ 

3140 Arrow Transmuter Base class 

3141 

3142 ArrowTransmuterBase and its derivatives are used to make a fancy 

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

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

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

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

3147 done by the FancyArrowPatch class. 

3148 

3149 """ 

3150 

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

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

3153 # the default values. 

3154 

3155 @staticmethod 

3156 def ensure_quadratic_bezier(path): 

3157 """ 

3158 Some ArrowStyle class only works with a simple quadratic Bezier 

3159 curve (created with Arc3Connection or Angle3Connector). This static 

3160 method is to check if the provided path is a simple quadratic 

3161 Bezier curve and returns its control points if true. 

3162 """ 

3163 segments = list(path.iter_segments()) 

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

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

3166 raise ValueError( 

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

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

3169 

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

3171 """ 

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

3173 must be overridden in the subclasses. It receives the path object 

3174 along which the arrow will be drawn, and the mutation_size, with 

3175 which the arrow head etc. will be scaled. The linewidth may be 

3176 used to adjust the path so that it does not pass beyond the given 

3177 points. It returns a tuple of a Path instance and a boolean. The 

3178 boolean value indicate whether the path can be filled or not. The 

3179 return value can also be a list of paths and list of booleans of a 

3180 same length. 

3181 """ 

3182 raise NotImplementedError('Derived must override') 

3183 

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

3185 aspect_ratio=1.): 

3186 """ 

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

3188 and takes care of the aspect ratio. 

3189 """ 

3190 

3191 path = make_path_regular(path) 

3192 

3193 if aspect_ratio is not None: 

3194 # Squeeze the given height by the aspect_ratio 

3195 

3196 vertices, codes = path.vertices[:], path.codes[:] 

3197 # Squeeze the height 

3198 vertices[:, 1] = vertices[:, 1] / aspect_ratio 

3199 path_shrunk = Path(vertices, codes) 

3200 # call transmute method with squeezed height. 

3201 path_mutated, fillable = self.transmute(path_shrunk, 

3202 linewidth, 

3203 mutation_size) 

3204 if np.iterable(fillable): 

3205 path_list = [] 

3206 for p in zip(path_mutated): 

3207 v, c = p.vertices, p.codes 

3208 # Restore the height 

3209 v[:, 1] = v[:, 1] * aspect_ratio 

3210 path_list.append(Path(v, c)) 

3211 return path_list, fillable 

3212 else: 

3213 return path_mutated, fillable 

3214 else: 

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

3216 

3217 class _Curve(_Base): 

3218 """ 

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

3220 returned path is simply concatenation of the original path + at 

3221 most two paths representing the arrow head at the begin point and the 

3222 at the end point. The arrow heads can be either open or closed. 

3223 """ 

3224 

3225 def __init__(self, beginarrow=None, endarrow=None, 

3226 fillbegin=False, fillend=False, 

3227 head_length=.2, head_width=.1): 

3228 """ 

3229 The arrows are drawn if *beginarrow* and/or *endarrow* are 

3230 true. *head_length* and *head_width* determines the size 

3231 of the arrow relative to the *mutation scale*. The 

3232 arrowhead at the begin (or end) is closed if fillbegin (or 

3233 fillend) is True. 

3234 """ 

3235 self.beginarrow, self.endarrow = beginarrow, endarrow 

3236 self.head_length, self.head_width = head_length, head_width 

3237 self.fillbegin, self.fillend = fillbegin, fillend 

3238 super().__init__() 

3239 

3240 def _get_arrow_wedge(self, x0, y0, x1, y1, 

3241 head_dist, cos_t, sin_t, linewidth): 

3242 """ 

3243 Return the paths for arrow heads. Since arrow lines are 

3244 drawn with capstyle=projected, The arrow goes beyond the 

3245 desired point. This method also returns the amount of the path 

3246 to be shrunken so that it does not overshoot. 

3247 """ 

3248 

3249 # arrow from x0, y0 to x1, y1 

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

3251 

3252 cp_distance = np.hypot(dx, dy) 

3253 

3254 # pad_projected : amount of pad to account the 

3255 # overshooting of the projection of the wedge 

3256 pad_projected = (.5 * linewidth / sin_t) 

3257 

3258 # Account for division by zero 

3259 if cp_distance == 0: 

3260 cp_distance = 1 

3261 

3262 # apply pad for projected edge 

3263 ddx = pad_projected * dx / cp_distance 

3264 ddy = pad_projected * dy / cp_distance 

3265 

3266 # offset for arrow wedge 

3267 dx = dx / cp_distance * head_dist 

3268 dy = dy / cp_distance * head_dist 

3269 

3270 dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy 

3271 dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy 

3272 

3273 vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1), 

3274 (x1 + ddx, y1 + ddy), 

3275 (x1 + ddx + dx2, y1 + ddy + dy2)] 

3276 codes_arrow = [Path.MOVETO, 

3277 Path.LINETO, 

3278 Path.LINETO] 

3279 

3280 return vertices_arrow, codes_arrow, ddx, ddy 

3281 

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

3283 

3284 head_length = self.head_length * mutation_size 

3285 head_width = self.head_width * mutation_size 

3286 head_dist = np.hypot(head_length, head_width) 

3287 cos_t, sin_t = head_length / head_dist, head_width / head_dist 

3288 

3289 # begin arrow 

3290 x0, y0 = path.vertices[0] 

3291 x1, y1 = path.vertices[1] 

3292 

3293 # If there is no room for an arrow and a line, then skip the arrow 

3294 has_begin_arrow = self.beginarrow and (x0, y0) != (x1, y1) 

3295 verticesA, codesA, ddxA, ddyA = ( 

3296 self._get_arrow_wedge(x1, y1, x0, y0, 

3297 head_dist, cos_t, sin_t, linewidth) 

3298 if has_begin_arrow 

3299 else ([], [], 0, 0) 

3300 ) 

3301 

3302 # end arrow 

3303 x2, y2 = path.vertices[-2] 

3304 x3, y3 = path.vertices[-1] 

3305 

3306 # If there is no room for an arrow and a line, then skip the arrow 

3307 has_end_arrow = self.endarrow and (x2, y2) != (x3, y3) 

3308 verticesB, codesB, ddxB, ddyB = ( 

3309 self._get_arrow_wedge(x2, y2, x3, y3, 

3310 head_dist, cos_t, sin_t, linewidth) 

3311 if has_end_arrow 

3312 else ([], [], 0, 0) 

3313 ) 

3314 

3315 # This simple code will not work if ddx, ddy is greater than the 

3316 # separation between vertices. 

3317 _path = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)], 

3318 path.vertices[1:-1], 

3319 [(x3 + ddxB, y3 + ddyB)]]), 

3320 path.codes)] 

3321 _fillable = [False] 

3322 

3323 if has_begin_arrow: 

3324 if self.fillbegin: 

3325 p = np.concatenate([verticesA, [verticesA[0], 

3326 verticesA[0]], ]) 

3327 c = np.concatenate([codesA, [Path.LINETO, Path.CLOSEPOLY]]) 

3328 _path.append(Path(p, c)) 

3329 _fillable.append(True) 

3330 else: 

3331 _path.append(Path(verticesA, codesA)) 

3332 _fillable.append(False) 

3333 

3334 if has_end_arrow: 

3335 if self.fillend: 

3336 _fillable.append(True) 

3337 p = np.concatenate([verticesB, [verticesB[0], 

3338 verticesB[0]], ]) 

3339 c = np.concatenate([codesB, [Path.LINETO, Path.CLOSEPOLY]]) 

3340 _path.append(Path(p, c)) 

3341 else: 

3342 _fillable.append(False) 

3343 _path.append(Path(verticesB, codesB)) 

3344 

3345 return _path, _fillable 

3346 

3347 @_register_style(_style_list, name="-") 

3348 class Curve(_Curve): 

3349 """ 

3350 A simple curve without any arrow head. 

3351 """ 

3352 

3353 def __init__(self): 

3354 super().__init__(beginarrow=False, endarrow=False) 

3355 

3356 @_register_style(_style_list, name="<-") 

3357 class CurveA(_Curve): 

3358 """ 

3359 An arrow with a head at its begin point. 

3360 """ 

3361 

3362 def __init__(self, head_length=.4, head_width=.2): 

3363 """ 

3364 Parameters 

3365 ---------- 

3366 head_length : float, optional, default : 0.4 

3367 Length of the arrow head 

3368 

3369 head_width : float, optional, default : 0.2 

3370 Width of the arrow head 

3371 """ 

3372 super().__init__(beginarrow=True, endarrow=False, 

3373 head_length=head_length, head_width=head_width) 

3374 

3375 @_register_style(_style_list, name="->") 

3376 class CurveB(_Curve): 

3377 """ 

3378 An arrow with a head at its end point. 

3379 """ 

3380 

3381 def __init__(self, head_length=.4, head_width=.2): 

3382 """ 

3383 Parameters 

3384 ---------- 

3385 head_length : float, optional, default : 0.4 

3386 Length of the arrow head 

3387 

3388 head_width : float, optional, default : 0.2 

3389 Width of the arrow head 

3390 """ 

3391 super().__init__(beginarrow=False, endarrow=True, 

3392 head_length=head_length, head_width=head_width) 

3393 

3394 @_register_style(_style_list, name="<->") 

3395 class CurveAB(_Curve): 

3396 """ 

3397 An arrow with heads both at the begin and the end point. 

3398 """ 

3399 

3400 def __init__(self, head_length=.4, head_width=.2): 

3401 """ 

3402 Parameters 

3403 ---------- 

3404 head_length : float, optional, default : 0.4 

3405 Length of the arrow head 

3406 

3407 head_width : float, optional, default : 0.2 

3408 Width of the arrow head 

3409 """ 

3410 super().__init__(beginarrow=True, endarrow=True, 

3411 head_length=head_length, head_width=head_width) 

3412 

3413 @_register_style(_style_list, name="<|-") 

3414 class CurveFilledA(_Curve): 

3415 """ 

3416 An arrow with filled triangle head at the begin. 

3417 """ 

3418 

3419 def __init__(self, head_length=.4, head_width=.2): 

3420 """ 

3421 Parameters 

3422 ---------- 

3423 head_length : float, optional, default : 0.4 

3424 Length of the arrow head 

3425 

3426 head_width : float, optional, default : 0.2 

3427 Width of the arrow head 

3428 """ 

3429 super().__init__(beginarrow=True, endarrow=False, 

3430 fillbegin=True, fillend=False, 

3431 head_length=head_length, head_width=head_width) 

3432 

3433 @_register_style(_style_list, name="-|>") 

3434 class CurveFilledB(_Curve): 

3435 """ 

3436 An arrow with filled triangle head at the end. 

3437 """ 

3438 

3439 def __init__(self, head_length=.4, head_width=.2): 

3440 """ 

3441 Parameters 

3442 ---------- 

3443 head_length : float, optional, default : 0.4 

3444 Length of the arrow head 

3445 

3446 head_width : float, optional, default : 0.2 

3447 Width of the arrow head 

3448 """ 

3449 super().__init__(beginarrow=False, endarrow=True, 

3450 fillbegin=False, fillend=True, 

3451 head_length=head_length, head_width=head_width) 

3452 

3453 @_register_style(_style_list, name="<|-|>") 

3454 class CurveFilledAB(_Curve): 

3455 """ 

3456 An arrow with filled triangle heads at both ends. 

3457 """ 

3458 

3459 def __init__(self, head_length=.4, head_width=.2): 

3460 """ 

3461 Parameters 

3462 ---------- 

3463 head_length : float, optional, default : 0.4 

3464 Length of the arrow head 

3465 

3466 head_width : float, optional, default : 0.2 

3467 Width of the arrow head 

3468 """ 

3469 super().__init__(beginarrow=True, endarrow=True, 

3470 fillbegin=True, fillend=True, 

3471 head_length=head_length, head_width=head_width) 

3472 

3473 class _Bracket(_Base): 

3474 

3475 def __init__(self, bracketA=None, bracketB=None, 

3476 widthA=1., widthB=1., 

3477 lengthA=0.2, lengthB=0.2, 

3478 angleA=None, angleB=None, 

3479 scaleA=None, scaleB=None): 

3480 self.bracketA, self.bracketB = bracketA, bracketB 

3481 self.widthA, self.widthB = widthA, widthB 

3482 self.lengthA, self.lengthB = lengthA, lengthB 

3483 self.angleA, self.angleB = angleA, angleB 

3484 self.scaleA, self.scaleB = scaleA, scaleB 

3485 

3486 def _get_bracket(self, x0, y0, 

3487 cos_t, sin_t, width, length): 

3488 

3489 # arrow from x0, y0 to x1, y1 

3490 from matplotlib.bezier import get_normal_points 

3491 x1, y1, x2, y2 = get_normal_points(x0, y0, cos_t, sin_t, width) 

3492 

3493 dx, dy = length * cos_t, length * sin_t 

3494 

3495 vertices_arrow = [(x1 + dx, y1 + dy), 

3496 (x1, y1), 

3497 (x2, y2), 

3498 (x2 + dx, y2 + dy)] 

3499 codes_arrow = [Path.MOVETO, 

3500 Path.LINETO, 

3501 Path.LINETO, 

3502 Path.LINETO] 

3503 

3504 return vertices_arrow, codes_arrow 

3505 

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

3507 

3508 if self.scaleA is None: 

3509 scaleA = mutation_size 

3510 else: 

3511 scaleA = self.scaleA 

3512 

3513 if self.scaleB is None: 

3514 scaleB = mutation_size 

3515 else: 

3516 scaleB = self.scaleB 

3517 

3518 vertices_list, codes_list = [], [] 

3519 

3520 if self.bracketA: 

3521 x0, y0 = path.vertices[0] 

3522 x1, y1 = path.vertices[1] 

3523 cos_t, sin_t = get_cos_sin(x1, y1, x0, y0) 

3524 verticesA, codesA = self._get_bracket(x0, y0, cos_t, sin_t, 

3525 self.widthA * scaleA, 

3526 self.lengthA * scaleA) 

3527 vertices_list.append(verticesA) 

3528 codes_list.append(codesA) 

3529 

3530 vertices_list.append(path.vertices) 

3531 codes_list.append(path.codes) 

3532 

3533 if self.bracketB: 

3534 x0, y0 = path.vertices[-1] 

3535 x1, y1 = path.vertices[-2] 

3536 cos_t, sin_t = get_cos_sin(x1, y1, x0, y0) 

3537 verticesB, codesB = self._get_bracket(x0, y0, cos_t, sin_t, 

3538 self.widthB * scaleB, 

3539 self.lengthB * scaleB) 

3540 vertices_list.append(verticesB) 

3541 codes_list.append(codesB) 

3542 

3543 vertices = np.concatenate(vertices_list) 

3544 codes = np.concatenate(codes_list) 

3545 

3546 p = Path(vertices, codes) 

3547 

3548 return p, False 

3549 

3550 @_register_style(_style_list, name="]-[") 

3551 class BracketAB(_Bracket): 

3552 """ 

3553 An arrow with a bracket(]) at both ends. 

3554 """ 

3555 

3556 def __init__(self, 

3557 widthA=1., lengthA=0.2, angleA=None, 

3558 widthB=1., lengthB=0.2, angleB=None): 

3559 """ 

3560 Parameters 

3561 ---------- 

3562 widthA : float, optional, default : 1.0 

3563 Width of the bracket 

3564 

3565 lengthA : float, optional, default : 0.2 

3566 Length of the bracket 

3567 

3568 angleA : float, optional, default : None 

3569 Angle between the bracket and the line 

3570 

3571 widthB : float, optional, default : 1.0 

3572 Width of the bracket 

3573 

3574 lengthB : float, optional, default : 0.2 

3575 Length of the bracket 

3576 

3577 angleB : float, optional, default : None 

3578 Angle between the bracket and the line 

3579 """ 

3580 super().__init__(True, True, 

3581 widthA=widthA, lengthA=lengthA, angleA=angleA, 

3582 widthB=widthB, lengthB=lengthB, angleB=angleB) 

3583 

3584 @_register_style(_style_list, name="]-") 

3585 class BracketA(_Bracket): 

3586 """ 

3587 An arrow with a bracket(]) at its end. 

3588 """ 

3589 

3590 def __init__(self, widthA=1., lengthA=0.2, angleA=None): 

3591 """ 

3592 Parameters 

3593 ---------- 

3594 widthA : float, optional, default : 1.0 

3595 Width of the bracket 

3596 

3597 lengthA : float, optional, default : 0.2 

3598 Length of the bracket 

3599 

3600 angleA : float, optional, default : None 

3601 Angle between the bracket and the line 

3602 """ 

3603 super().__init__(True, None, 

3604 widthA=widthA, lengthA=lengthA, angleA=angleA) 

3605 

3606 @_register_style(_style_list, name="-[") 

3607 class BracketB(_Bracket): 

3608 """ 

3609 An arrow with a bracket([) at its end. 

3610 """ 

3611 

3612 def __init__(self, widthB=1., lengthB=0.2, angleB=None): 

3613 """ 

3614 Parameters 

3615 ---------- 

3616 widthB : float, optional, default : 1.0 

3617 Width of the bracket 

3618 

3619 lengthB : float, optional, default : 0.2 

3620 Length of the bracket 

3621 

3622 angleB : float, optional, default : None 

3623 Angle between the bracket and the line 

3624 """ 

3625 super().__init__(None, True, 

3626 widthB=widthB, lengthB=lengthB, angleB=angleB) 

3627 

3628 @_register_style(_style_list, name="|-|") 

3629 class BarAB(_Bracket): 

3630 """ 

3631 An arrow with a bar(|) at both ends. 

3632 """ 

3633 

3634 def __init__(self, 

3635 widthA=1., angleA=None, 

3636 widthB=1., angleB=None): 

3637 """ 

3638 Parameters 

3639 ---------- 

3640 widthA : float, optional, default : 1.0 

3641 Width of the bracket 

3642 

3643 angleA : float, optional, default : None 

3644 Angle between the bracket and the line 

3645 

3646 widthB : float, optional, default : 1.0 

3647 Width of the bracket 

3648 

3649 angleB : float, optional, default : None 

3650 Angle between the bracket and the line 

3651 """ 

3652 super().__init__(True, True, 

3653 widthA=widthA, lengthA=0, angleA=angleA, 

3654 widthB=widthB, lengthB=0, angleB=angleB) 

3655 

3656 @_register_style(_style_list) 

3657 class Simple(_Base): 

3658 """ 

3659 A simple arrow. Only works with a quadratic Bezier curve. 

3660 """ 

3661 

3662 def __init__(self, head_length=.5, head_width=.5, tail_width=.2): 

3663 """ 

3664 Parameters 

3665 ---------- 

3666 head_length : float, optional, default : 0.5 

3667 Length of the arrow head 

3668 

3669 head_width : float, optional, default : 0.5 

3670 Width of the arrow head 

3671 

3672 tail_width : float, optional, default : 0.2 

3673 Width of the arrow tail 

3674 """ 

3675 self.head_length, self.head_width, self.tail_width = \ 

3676 head_length, head_width, tail_width 

3677 super().__init__() 

3678 

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

3680 

3681 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3682 

3683 # divide the path into a head and a tail 

3684 head_length = self.head_length * mutation_size 

3685 in_f = inside_circle(x2, y2, head_length) 

3686 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3687 

3688 try: 

3689 arrow_out, arrow_in = \ 

3690 split_bezier_intersecting_with_closedpath( 

3691 arrow_path, in_f, tolerance=0.01) 

3692 except NonIntersectingPathException: 

3693 # if this happens, make a straight line of the head_length 

3694 # long. 

3695 x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) 

3696 x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2) 

3697 arrow_in = [(x0, y0), (x1n, y1n), (x2, y2)] 

3698 arrow_out = None 

3699 

3700 # head 

3701 head_width = self.head_width * mutation_size 

3702 head_left, head_right = make_wedged_bezier2(arrow_in, 

3703 head_width / 2., wm=.5) 

3704 

3705 # tail 

3706 if arrow_out is not None: 

3707 tail_width = self.tail_width * mutation_size 

3708 tail_left, tail_right = get_parallels(arrow_out, 

3709 tail_width / 2.) 

3710 

3711 patch_path = [(Path.MOVETO, tail_right[0]), 

3712 (Path.CURVE3, tail_right[1]), 

3713 (Path.CURVE3, tail_right[2]), 

3714 (Path.LINETO, head_right[0]), 

3715 (Path.CURVE3, head_right[1]), 

3716 (Path.CURVE3, head_right[2]), 

3717 (Path.CURVE3, head_left[1]), 

3718 (Path.CURVE3, head_left[0]), 

3719 (Path.LINETO, tail_left[2]), 

3720 (Path.CURVE3, tail_left[1]), 

3721 (Path.CURVE3, tail_left[0]), 

3722 (Path.LINETO, tail_right[0]), 

3723 (Path.CLOSEPOLY, tail_right[0]), 

3724 ] 

3725 else: 

3726 patch_path = [(Path.MOVETO, head_right[0]), 

3727 (Path.CURVE3, head_right[1]), 

3728 (Path.CURVE3, head_right[2]), 

3729 (Path.CURVE3, head_left[1]), 

3730 (Path.CURVE3, head_left[0]), 

3731 (Path.CLOSEPOLY, head_left[0]), 

3732 ] 

3733 

3734 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3735 

3736 return path, True 

3737 

3738 @_register_style(_style_list) 

3739 class Fancy(_Base): 

3740 """ 

3741 A fancy arrow. Only works with a quadratic Bezier curve. 

3742 """ 

3743 

3744 def __init__(self, head_length=.4, head_width=.4, tail_width=.4): 

3745 """ 

3746 Parameters 

3747 ---------- 

3748 head_length : float, optional, default : 0.4 

3749 Length of the arrow head 

3750 

3751 head_width : float, optional, default : 0.4 

3752 Width of the arrow head 

3753 

3754 tail_width : float, optional, default : 0.4 

3755 Width of the arrow tail 

3756 """ 

3757 self.head_length, self.head_width, self.tail_width = \ 

3758 head_length, head_width, tail_width 

3759 super().__init__() 

3760 

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

3762 

3763 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3764 

3765 # divide the path into a head and a tail 

3766 head_length = self.head_length * mutation_size 

3767 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3768 

3769 # path for head 

3770 in_f = inside_circle(x2, y2, head_length) 

3771 try: 

3772 path_out, path_in = split_bezier_intersecting_with_closedpath( 

3773 arrow_path, in_f, tolerance=0.01) 

3774 except NonIntersectingPathException: 

3775 # if this happens, make a straight line of the head_length 

3776 # long. 

3777 x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length) 

3778 x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2) 

3779 arrow_path = [(x0, y0), (x1n, y1n), (x2, y2)] 

3780 path_head = arrow_path 

3781 else: 

3782 path_head = path_in 

3783 

3784 # path for head 

3785 in_f = inside_circle(x2, y2, head_length * .8) 

3786 path_out, path_in = split_bezier_intersecting_with_closedpath( 

3787 arrow_path, in_f, tolerance=0.01) 

3788 path_tail = path_out 

3789 

3790 # head 

3791 head_width = self.head_width * mutation_size 

3792 head_l, head_r = make_wedged_bezier2(path_head, 

3793 head_width / 2., 

3794 wm=.6) 

3795 

3796 # tail 

3797 tail_width = self.tail_width * mutation_size 

3798 tail_left, tail_right = make_wedged_bezier2(path_tail, 

3799 tail_width * .5, 

3800 w1=1., wm=0.6, w2=0.3) 

3801 

3802 # path for head 

3803 in_f = inside_circle(x0, y0, tail_width * .3) 

3804 path_in, path_out = split_bezier_intersecting_with_closedpath( 

3805 arrow_path, in_f, tolerance=0.01) 

3806 tail_start = path_in[-1] 

3807 

3808 head_right, head_left = head_r, head_l 

3809 patch_path = [(Path.MOVETO, tail_start), 

3810 (Path.LINETO, tail_right[0]), 

3811 (Path.CURVE3, tail_right[1]), 

3812 (Path.CURVE3, tail_right[2]), 

3813 (Path.LINETO, head_right[0]), 

3814 (Path.CURVE3, head_right[1]), 

3815 (Path.CURVE3, head_right[2]), 

3816 (Path.CURVE3, head_left[1]), 

3817 (Path.CURVE3, head_left[0]), 

3818 (Path.LINETO, tail_left[2]), 

3819 (Path.CURVE3, tail_left[1]), 

3820 (Path.CURVE3, tail_left[0]), 

3821 (Path.LINETO, tail_start), 

3822 (Path.CLOSEPOLY, tail_start), 

3823 ] 

3824 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3825 

3826 return path, True 

3827 

3828 @_register_style(_style_list) 

3829 class Wedge(_Base): 

3830 """ 

3831 Wedge(?) shape. Only works with a quadratic Bezier curve. The 

3832 begin point has a width of the tail_width and the end point has a 

3833 width of 0. At the middle, the width is shrink_factor*tail_width. 

3834 """ 

3835 

3836 def __init__(self, tail_width=.3, shrink_factor=0.5): 

3837 """ 

3838 Parameters 

3839 ---------- 

3840 tail_width : float, optional, default : 0.3 

3841 Width of the tail 

3842 

3843 shrink_factor : float, optional, default : 0.5 

3844 Fraction of the arrow width at the middle point 

3845 """ 

3846 self.tail_width = tail_width 

3847 self.shrink_factor = shrink_factor 

3848 super().__init__() 

3849 

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

3851 

3852 x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path) 

3853 

3854 arrow_path = [(x0, y0), (x1, y1), (x2, y2)] 

3855 b_plus, b_minus = make_wedged_bezier2( 

3856 arrow_path, 

3857 self.tail_width * mutation_size / 2., 

3858 wm=self.shrink_factor) 

3859 

3860 patch_path = [(Path.MOVETO, b_plus[0]), 

3861 (Path.CURVE3, b_plus[1]), 

3862 (Path.CURVE3, b_plus[2]), 

3863 (Path.LINETO, b_minus[2]), 

3864 (Path.CURVE3, b_minus[1]), 

3865 (Path.CURVE3, b_minus[0]), 

3866 (Path.CLOSEPOLY, b_minus[0]), 

3867 ] 

3868 path = Path([p for c, p in patch_path], [c for c, p in patch_path]) 

3869 

3870 return path, True 

3871 

3872 if __doc__: 

3873 __doc__ = inspect.cleandoc(__doc__) % { 

3874 "AvailableArrowstyles": _pprint_styles(_style_list)} 

3875 

3876 

3877docstring.interpd.update( 

3878 AvailableArrowstyles=_pprint_styles(ArrowStyle._style_list), 

3879 AvailableConnectorstyles=_pprint_styles(ConnectionStyle._style_list), 

3880) 

3881 

3882 

3883class FancyArrowPatch(Patch): 

3884 """ 

3885 A fancy arrow patch. It draws an arrow using the :class:`ArrowStyle`. 

3886 

3887 The head and tail positions are fixed at the specified start and end points 

3888 of the arrow, but the size and shape (in display coordinates) of the arrow 

3889 does not change when the axis is moved or zoomed. 

3890 """ 

3891 _edge_default = True 

3892 

3893 def __str__(self): 

3894 

3895 if self._posA_posB is not None: 

3896 (x1, y1), (x2, y2) = self._posA_posB 

3897 return self.__class__.__name__ \ 

3898 + "((%g, %g)->(%g, %g))" % (x1, y1, x2, y2) 

3899 else: 

3900 return self.__class__.__name__ \ 

3901 + "(%s)" % (str(self._path_original),) 

3902 

3903 @docstring.dedent_interpd 

3904 def __init__(self, posA=None, posB=None, 

3905 path=None, 

3906 arrowstyle="simple", 

3907 arrow_transmuter=None, 

3908 connectionstyle="arc3", 

3909 connector=None, 

3910 patchA=None, 

3911 patchB=None, 

3912 shrinkA=2, 

3913 shrinkB=2, 

3914 mutation_scale=1, 

3915 mutation_aspect=None, 

3916 dpi_cor=1, 

3917 **kwargs): 

3918 """ 

3919 There are two ways for defining an arrow: 

3920 

3921 - If *posA* and *posB* are given, a path connecting two points is 

3922 created according to *connectionstyle*. The path will be 

3923 clipped with *patchA* and *patchB* and further shrunken by 

3924 *shrinkA* and *shrinkB*. An arrow is drawn along this 

3925 resulting path using the *arrowstyle* parameter. 

3926 

3927 - Alternatively if *path* is provided, an arrow is drawn along this 

3928 path and *patchA*, *patchB*, *shrinkA*, and *shrinkB* are ignored. 

3929 

3930 Parameters 

3931 ---------- 

3932 posA, posB : (float, float), optional (default: None) 

3933 (x, y) coordinates of arrow tail and arrow head respectively. 

3934 

3935 path : `~matplotlib.path.Path`, optional (default: None) 

3936 If provided, an arrow is drawn along this path and *patchA*, 

3937 *patchB*, *shrinkA*, and *shrinkB* are ignored. 

3938 

3939 arrowstyle : str or `.ArrowStyle`, optional (default: 'simple') 

3940 Describes how the fancy arrow will be 

3941 drawn. It can be string of the available arrowstyle names, 

3942 with optional comma-separated attributes, or an 

3943 :class:`ArrowStyle` instance. The optional attributes are meant to 

3944 be scaled with the *mutation_scale*. The following arrow styles are 

3945 available: 

3946 

3947 %(AvailableArrowstyles)s 

3948 

3949 arrow_transmuter 

3950 Ignored. 

3951 

3952 connectionstyle : str or `.ConnectionStyle` or None, optional \ 

3953(default: 'arc3') 

3954 Describes how *posA* and *posB* are connected. It can be an 

3955 instance of the :class:`ConnectionStyle` class or a string of the 

3956 connectionstyle name, with optional comma-separated attributes. The 

3957 following connection styles are available: 

3958 

3959 %(AvailableConnectorstyles)s 

3960 

3961 connector 

3962 Ignored. 

3963 

3964 patchA, patchB : `.Patch`, optional (default: None) 

3965 Head and tail patch respectively. :class:`matplotlib.patch.Patch` 

3966 instance. 

3967 

3968 shrinkA, shrinkB : float, optional (default: 2) 

3969 Shrinking factor of the tail and head of the arrow respectively. 

3970 

3971 mutation_scale : float, optional (default: 1) 

3972 Value with which attributes of *arrowstyle* (e.g., *head_length*) 

3973 will be scaled. 

3974 

3975 mutation_aspect : None or float, optional (default: None) 

3976 The height of the rectangle will be squeezed by this value before 

3977 the mutation and the mutated box will be stretched by the inverse 

3978 of it. 

3979 

3980 dpi_cor : float, optional (default: 1) 

3981 dpi_cor is currently used for linewidth-related things and shrink 

3982 factor. Mutation scale is affected by this. 

3983 

3984 Other Parameters 

3985 ---------------- 

3986 **kwargs : `.Patch` properties, optional 

3987 Here is a list of available `.Patch` properties: 

3988 

3989 %(Patch)s 

3990 

3991 In contrast to other patches, the default ``capstyle`` and 

3992 ``joinstyle`` for `FancyArrowPatch` are set to ``"round"``. 

3993 """ 

3994 if arrow_transmuter is not None: 

3995 cbook.warn_deprecated( 

3996 3.0, 

3997 message=('The "arrow_transmuter" keyword argument is not used,' 

3998 ' and will be removed in Matplotlib 3.1'), 

3999 name='arrow_transmuter', 

4000 obj_type='keyword argument') 

4001 if connector is not None: 

4002 cbook.warn_deprecated( 

4003 3.0, 

4004 message=('The "connector" keyword argument is not used,' 

4005 ' and will be removed in Matplotlib 3.1'), 

4006 name='connector', 

4007 obj_type='keyword argument') 

4008 # Traditionally, the cap- and joinstyle for FancyArrowPatch are round 

4009 kwargs.setdefault("joinstyle", "round") 

4010 kwargs.setdefault("capstyle", "round") 

4011 

4012 Patch.__init__(self, **kwargs) 

4013 

4014 if posA is not None and posB is not None and path is None: 

4015 self._posA_posB = [posA, posB] 

4016 

4017 if connectionstyle is None: 

4018 connectionstyle = "arc3" 

4019 self.set_connectionstyle(connectionstyle) 

4020 

4021 elif posA is None and posB is None and path is not None: 

4022 self._posA_posB = None 

4023 else: 

4024 raise ValueError("either posA and posB, or path need to provided") 

4025 

4026 self.patchA = patchA 

4027 self.patchB = patchB 

4028 self.shrinkA = shrinkA 

4029 self.shrinkB = shrinkB 

4030 

4031 self._path_original = path 

4032 

4033 self.set_arrowstyle(arrowstyle) 

4034 

4035 self._mutation_scale = mutation_scale 

4036 self._mutation_aspect = mutation_aspect 

4037 

4038 self.set_dpi_cor(dpi_cor) 

4039 

4040 def set_dpi_cor(self, dpi_cor): 

4041 """ 

4042 dpi_cor is currently used for linewidth-related things and 

4043 shrink factor. Mutation scale is affected by this. 

4044 

4045 Parameters 

4046 ---------- 

4047 dpi_cor : scalar 

4048 """ 

4049 self._dpi_cor = dpi_cor 

4050 self.stale = True 

4051 

4052 def get_dpi_cor(self): 

4053 """ 

4054 dpi_cor is currently used for linewidth-related things and 

4055 shrink factor. Mutation scale is affected by this. 

4056 

4057 Returns 

4058 ------- 

4059 dpi_cor : scalar 

4060 """ 

4061 return self._dpi_cor 

4062 

4063 def set_positions(self, posA, posB): 

4064 """ 

4065 Set the begin and end positions of the connecting path. 

4066 

4067 Parameters 

4068 ---------- 

4069 posA, posB : None, tuple 

4070 (x, y) coordinates of arrow tail and arrow head respectively. If 

4071 `None` use current value. 

4072 """ 

4073 if posA is not None: 

4074 self._posA_posB[0] = posA 

4075 if posB is not None: 

4076 self._posA_posB[1] = posB 

4077 self.stale = True 

4078 

4079 def set_patchA(self, patchA): 

4080 """ 

4081 Set the tail patch. 

4082 

4083 Parameters 

4084 ---------- 

4085 patchA : Patch 

4086 :class:`matplotlib.patch.Patch` instance. 

4087 """ 

4088 self.patchA = patchA 

4089 self.stale = True 

4090 

4091 def set_patchB(self, patchB): 

4092 """ 

4093 Set the head patch. 

4094 

4095 Parameters 

4096 ---------- 

4097 patchB : Patch 

4098 :class:`matplotlib.patch.Patch` instance. 

4099 """ 

4100 self.patchB = patchB 

4101 self.stale = True 

4102 

4103 def set_connectionstyle(self, connectionstyle, **kw): 

4104 """ 

4105 Set the connection style. Old attributes are forgotten. 

4106 

4107 Parameters 

4108 ---------- 

4109 connectionstyle : str or `.ConnectionStyle` or None, optional 

4110 Can be a string with connectionstyle name with 

4111 optional comma-separated attributes, e.g.:: 

4112 

4113 set_connectionstyle("arc,angleA=0,armA=30,rad=10") 

4114 

4115 Alternatively, the attributes can be provided as keywords, e.g.:: 

4116 

4117 set_connectionstyle("arc", angleA=0,armA=30,rad=10) 

4118 

4119 Without any arguments (or with ``connectionstyle=None``), return 

4120 available styles as a list of strings. 

4121 """ 

4122 

4123 if connectionstyle is None: 

4124 return ConnectionStyle.pprint_styles() 

4125 

4126 if (isinstance(connectionstyle, ConnectionStyle._Base) or 

4127 callable(connectionstyle)): 

4128 self._connector = connectionstyle 

4129 else: 

4130 self._connector = ConnectionStyle(connectionstyle, **kw) 

4131 self.stale = True 

4132 

4133 def get_connectionstyle(self): 

4134 """ 

4135 Return the :class:`ConnectionStyle` instance. 

4136 """ 

4137 return self._connector 

4138 

4139 def set_arrowstyle(self, arrowstyle=None, **kw): 

4140 """ 

4141 Set the arrow style. Old attributes are forgotten. Without arguments 

4142 (or with ``arrowstyle=None``) returns available box styles as a list of 

4143 strings. 

4144 

4145 Parameters 

4146 ---------- 

4147 arrowstyle : None, ArrowStyle, str, optional (default: None) 

4148 Can be a string with arrowstyle name with optional comma-separated 

4149 attributes, e.g.:: 

4150 

4151 set_arrowstyle("Fancy,head_length=0.2") 

4152 

4153 Alternatively attributes can be provided as keywords, e.g.:: 

4154 

4155 set_arrowstyle("fancy", head_length=0.2) 

4156 

4157 """ 

4158 

4159 if arrowstyle is None: 

4160 return ArrowStyle.pprint_styles() 

4161 

4162 if isinstance(arrowstyle, ArrowStyle._Base): 

4163 self._arrow_transmuter = arrowstyle 

4164 else: 

4165 self._arrow_transmuter = ArrowStyle(arrowstyle, **kw) 

4166 self.stale = True 

4167 

4168 def get_arrowstyle(self): 

4169 """ 

4170 Return the arrowstyle object. 

4171 """ 

4172 return self._arrow_transmuter 

4173 

4174 def set_mutation_scale(self, scale): 

4175 """ 

4176 Set the mutation scale. 

4177 

4178 Parameters 

4179 ---------- 

4180 scale : scalar 

4181 """ 

4182 self._mutation_scale = scale 

4183 self.stale = True 

4184 

4185 def get_mutation_scale(self): 

4186 """ 

4187 Return the mutation scale. 

4188 

4189 Returns 

4190 ------- 

4191 scale : scalar 

4192 """ 

4193 return self._mutation_scale 

4194 

4195 def set_mutation_aspect(self, aspect): 

4196 """ 

4197 Set the aspect ratio of the bbox mutation. 

4198 

4199 Parameters 

4200 ---------- 

4201 aspect : scalar 

4202 """ 

4203 self._mutation_aspect = aspect 

4204 self.stale = True 

4205 

4206 def get_mutation_aspect(self): 

4207 """ 

4208 Return the aspect ratio of the bbox mutation. 

4209 """ 

4210 return self._mutation_aspect 

4211 

4212 def get_path(self): 

4213 """ 

4214 Return the path of the arrow in the data coordinates. Use 

4215 get_path_in_displaycoord() method to retrieve the arrow path 

4216 in display coordinates. 

4217 """ 

4218 _path, fillable = self.get_path_in_displaycoord() 

4219 if np.iterable(fillable): 

4220 _path = concatenate_paths(_path) 

4221 return self.get_transform().inverted().transform_path(_path) 

4222 

4223 def get_path_in_displaycoord(self): 

4224 """ 

4225 Return the mutated path of the arrow in display coordinates. 

4226 """ 

4227 

4228 dpi_cor = self.get_dpi_cor() 

4229 

4230 if self._posA_posB is not None: 

4231 posA = self._convert_xy_units(self._posA_posB[0]) 

4232 posB = self._convert_xy_units(self._posA_posB[1]) 

4233 (posA, posB) = self.get_transform().transform((posA, posB)) 

4234 _path = self.get_connectionstyle()(posA, posB, 

4235 patchA=self.patchA, 

4236 patchB=self.patchB, 

4237 shrinkA=self.shrinkA * dpi_cor, 

4238 shrinkB=self.shrinkB * dpi_cor 

4239 ) 

4240 else: 

4241 _path = self.get_transform().transform_path(self._path_original) 

4242 

4243 _path, fillable = self.get_arrowstyle()( 

4244 _path, 

4245 self.get_mutation_scale() * dpi_cor, 

4246 self.get_linewidth() * dpi_cor, 

4247 self.get_mutation_aspect()) 

4248 

4249 # if not fillable: 

4250 # self._fill = False 

4251 

4252 return _path, fillable 

4253 

4254 def draw(self, renderer): 

4255 if not self.get_visible(): 

4256 return 

4257 

4258 with self._bind_draw_path_function(renderer) as draw_path: 

4259 

4260 # FIXME : dpi_cor is for the dpi-dependency of the linewidth. There 

4261 # could be room for improvement. 

4262 self.set_dpi_cor(renderer.points_to_pixels(1.)) 

4263 path, fillable = self.get_path_in_displaycoord() 

4264 

4265 if not np.iterable(fillable): 

4266 path = [path] 

4267 fillable = [fillable] 

4268 

4269 affine = transforms.IdentityTransform() 

4270 

4271 for p, f in zip(path, fillable): 

4272 draw_path( 

4273 p, affine, 

4274 self._facecolor if f and self._facecolor[3] else None) 

4275 

4276 

4277class ConnectionPatch(FancyArrowPatch): 

4278 """ 

4279 A :class:`~matplotlib.patches.ConnectionPatch` class is to make 

4280 connecting lines between two points (possibly in different axes). 

4281 """ 

4282 def __str__(self): 

4283 return "ConnectionPatch((%g, %g), (%g, %g))" % \ 

4284 (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1]) 

4285 

4286 @docstring.dedent_interpd 

4287 def __init__(self, xyA, xyB, coordsA, coordsB=None, 

4288 axesA=None, axesB=None, 

4289 arrowstyle="-", 

4290 arrow_transmuter=None, 

4291 connectionstyle="arc3", 

4292 connector=None, 

4293 patchA=None, 

4294 patchB=None, 

4295 shrinkA=0., 

4296 shrinkB=0., 

4297 mutation_scale=10., 

4298 mutation_aspect=None, 

4299 clip_on=False, 

4300 dpi_cor=1., 

4301 **kwargs): 

4302 """Connect point *xyA* in *coordsA* with point *xyB* in *coordsB* 

4303 

4304 Valid keys are 

4305 

4306 =============== ====================================================== 

4307 Key Description 

4308 =============== ====================================================== 

4309 arrowstyle the arrow style 

4310 connectionstyle the connection style 

4311 relpos default is (0.5, 0.5) 

4312 patchA default is bounding box of the text 

4313 patchB default is None 

4314 shrinkA default is 2 points 

4315 shrinkB default is 2 points 

4316 mutation_scale default is text size (in points) 

4317 mutation_aspect default is 1. 

4318 ? any key for :class:`matplotlib.patches.PathPatch` 

4319 =============== ====================================================== 

4320 

4321 *coordsA* and *coordsB* are strings that indicate the 

4322 coordinates of *xyA* and *xyB*. 

4323 

4324 ================= =================================================== 

4325 Property Description 

4326 ================= =================================================== 

4327 'figure points' points from the lower left corner of the figure 

4328 'figure pixels' pixels from the lower left corner of the figure 

4329 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper right 

4330 'axes points' points from lower left corner of axes 

4331 'axes pixels' pixels from lower left corner of axes 

4332 'axes fraction' 0, 0 is lower left of axes and 1, 1 is upper right 

4333 'data' use the coordinate system of the object being 

4334 annotated (default) 

4335 'offset points' offset (in points) from the *xy* value 

4336 'polar' you can specify *theta*, *r* for the annotation, 

4337 even in cartesian plots. Note that if you are using 

4338 a polar axes, you do not need to specify polar for 

4339 the coordinate system since that is the native 

4340 "data" coordinate system. 

4341 ================= =================================================== 

4342 

4343 Alternatively they can be set to any valid 

4344 `~matplotlib.transforms.Transform`. 

4345 

4346 .. note:: 

4347 

4348 Using :class:`~matplotlib.patches.ConnectionPatch` across 

4349 two :class:`~matplotlib.axes.Axes` instances is not 

4350 directly compatible with :doc:`constrained layout 

4351 </tutorials/intermediate/constrainedlayout_guide>`. Add the 

4352 artist directly to the :class:`~matplotlib.figure.Figure` 

4353 instead of adding it to a specific Axes. 

4354 

4355 .. code-block:: default 

4356 

4357 fig, ax = plt.subplots(1, 2, constrained_layout=True) 

4358 con = ConnectionPatch(..., axesA=ax[0], axesB=ax[1]) 

4359 fig.add_artist(con) 

4360 

4361 """ 

4362 if coordsB is None: 

4363 coordsB = coordsA 

4364 # we'll draw ourself after the artist we annotate by default 

4365 self.xy1 = xyA 

4366 self.xy2 = xyB 

4367 self.coords1 = coordsA 

4368 self.coords2 = coordsB 

4369 

4370 self.axesA = axesA 

4371 self.axesB = axesB 

4372 

4373 FancyArrowPatch.__init__(self, 

4374 posA=(0, 0), posB=(1, 1), 

4375 arrowstyle=arrowstyle, 

4376 arrow_transmuter=arrow_transmuter, 

4377 connectionstyle=connectionstyle, 

4378 connector=connector, 

4379 patchA=patchA, 

4380 patchB=patchB, 

4381 shrinkA=shrinkA, 

4382 shrinkB=shrinkB, 

4383 mutation_scale=mutation_scale, 

4384 mutation_aspect=mutation_aspect, 

4385 clip_on=clip_on, 

4386 dpi_cor=dpi_cor, 

4387 **kwargs) 

4388 

4389 # if True, draw annotation only if self.xy is inside the axes 

4390 self._annotation_clip = None 

4391 

4392 def _get_xy(self, x, y, s, axes=None): 

4393 """Calculate the pixel position of given point.""" 

4394 if axes is None: 

4395 axes = self.axes 

4396 

4397 if s == 'data': 

4398 trans = axes.transData 

4399 x = float(self.convert_xunits(x)) 

4400 y = float(self.convert_yunits(y)) 

4401 return trans.transform((x, y)) 

4402 elif s == 'offset points': 

4403 # convert the data point 

4404 dx, dy = self.xy 

4405 

4406 # prevent recursion 

4407 if self.xycoords == 'offset points': 

4408 return self._get_xy(dx, dy, 'data') 

4409 

4410 dx, dy = self._get_xy(dx, dy, self.xycoords) 

4411 

4412 # convert the offset 

4413 dpi = self.figure.get_dpi() 

4414 x *= dpi / 72. 

4415 y *= dpi / 72. 

4416 

4417 # add the offset to the data point 

4418 x += dx 

4419 y += dy 

4420 

4421 return x, y 

4422 elif s == 'polar': 

4423 theta, r = x, y 

4424 x = r * np.cos(theta) 

4425 y = r * np.sin(theta) 

4426 trans = axes.transData 

4427 return trans.transform((x, y)) 

4428 elif s == 'figure points': 

4429 # points from the lower left corner of the figure 

4430 dpi = self.figure.dpi 

4431 l, b, w, h = self.figure.bbox.bounds 

4432 r = l + w 

4433 t = b + h 

4434 

4435 x *= dpi / 72. 

4436 y *= dpi / 72. 

4437 if x < 0: 

4438 x = r + x 

4439 if y < 0: 

4440 y = t + y 

4441 return x, y 

4442 elif s == 'figure pixels': 

4443 # pixels from the lower left corner of the figure 

4444 l, b, w, h = self.figure.bbox.bounds 

4445 r = l + w 

4446 t = b + h 

4447 if x < 0: 

4448 x = r + x 

4449 if y < 0: 

4450 y = t + y 

4451 return x, y 

4452 elif s == 'figure fraction': 

4453 # (0, 0) is lower left, (1, 1) is upper right of figure 

4454 trans = self.figure.transFigure 

4455 return trans.transform((x, y)) 

4456 elif s == 'axes points': 

4457 # points from the lower left corner of the axes 

4458 dpi = self.figure.dpi 

4459 l, b, w, h = axes.bbox.bounds 

4460 r = l + w 

4461 t = b + h 

4462 if x < 0: 

4463 x = r + x * dpi / 72. 

4464 else: 

4465 x = l + x * dpi / 72. 

4466 if y < 0: 

4467 y = t + y * dpi / 72. 

4468 else: 

4469 y = b + y * dpi / 72. 

4470 return x, y 

4471 elif s == 'axes pixels': 

4472 # pixels from the lower left corner of the axes 

4473 l, b, w, h = axes.bbox.bounds 

4474 r = l + w 

4475 t = b + h 

4476 if x < 0: 

4477 x = r + x 

4478 else: 

4479 x = l + x 

4480 if y < 0: 

4481 y = t + y 

4482 else: 

4483 y = b + y 

4484 return x, y 

4485 elif s == 'axes fraction': 

4486 # (0, 0) is lower left, (1, 1) is upper right of axes 

4487 trans = axes.transAxes 

4488 return trans.transform((x, y)) 

4489 elif isinstance(s, transforms.Transform): 

4490 return s.transform((x, y)) 

4491 else: 

4492 raise ValueError("{} is not a valid coordinate " 

4493 "transformation.".format(s)) 

4494 

4495 def set_annotation_clip(self, b): 

4496 """ 

4497 Set the clipping behavior. 

4498 

4499 Parameters 

4500 ---------- 

4501 b : bool or None 

4502 

4503 - *False*: The annotation will always be drawn regardless of its 

4504 position. 

4505 - *True*: The annotation will only be drawn if ``self.xy`` is 

4506 inside the axes. 

4507 - *None*: The annotation will only be drawn if ``self.xy`` is 

4508 inside the axes and ``self.xycoords == "data"``. 

4509 """ 

4510 self._annotation_clip = b 

4511 self.stale = True 

4512 

4513 def get_annotation_clip(self): 

4514 """ 

4515 Return the clipping behavior. 

4516 

4517 See `.set_annotation_clip` for the meaning of the return value. 

4518 """ 

4519 return self._annotation_clip 

4520 

4521 def get_path_in_displaycoord(self): 

4522 """Return the mutated path of the arrow in display coordinates.""" 

4523 

4524 dpi_cor = self.get_dpi_cor() 

4525 

4526 x, y = self.xy1 

4527 posA = self._get_xy(x, y, self.coords1, self.axesA) 

4528 

4529 x, y = self.xy2 

4530 posB = self._get_xy(x, y, self.coords2, self.axesB) 

4531 

4532 _path = self.get_connectionstyle()(posA, posB, 

4533 patchA=self.patchA, 

4534 patchB=self.patchB, 

4535 shrinkA=self.shrinkA * dpi_cor, 

4536 shrinkB=self.shrinkB * dpi_cor 

4537 ) 

4538 

4539 _path, fillable = self.get_arrowstyle()( 

4540 _path, 

4541 self.get_mutation_scale() * dpi_cor, 

4542 self.get_linewidth() * dpi_cor, 

4543 self.get_mutation_aspect() 

4544 ) 

4545 

4546 return _path, fillable 

4547 

4548 def _check_xy(self, renderer): 

4549 """Check whether the annotation needs to be drawn.""" 

4550 

4551 b = self.get_annotation_clip() 

4552 

4553 if b or (b is None and self.coords1 == "data"): 

4554 x, y = self.xy1 

4555 xy_pixel = self._get_xy(x, y, self.coords1, self.axesA) 

4556 if self.axesA is None: 

4557 axes = self.axes 

4558 else: 

4559 axes = self.axesA 

4560 if not axes.contains_point(xy_pixel): 

4561 return False 

4562 

4563 if b or (b is None and self.coords2 == "data"): 

4564 x, y = self.xy2 

4565 xy_pixel = self._get_xy(x, y, self.coords2, self.axesB) 

4566 if self.axesB is None: 

4567 axes = self.axes 

4568 else: 

4569 axes = self.axesB 

4570 if not axes.contains_point(xy_pixel): 

4571 return False 

4572 

4573 return True 

4574 

4575 def draw(self, renderer): 

4576 if renderer is not None: 

4577 self._renderer = renderer 

4578 if not self.get_visible() or not self._check_xy(renderer): 

4579 return 

4580 FancyArrowPatch.draw(self, renderer)