You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4614 lines
148 KiB

4 years ago
  1. import functools
  2. import math
  3. from numbers import Number
  4. import textwrap
  5. import warnings
  6. import numpy as np
  7. import matplotlib as mpl
  8. from . import artist, cbook, colors, docstring, lines as mlines, transforms
  9. from .bezier import (
  10. NonIntersectingPathException, concatenate_paths, get_cos_sin,
  11. get_intersection, get_parallels, inside_circle, make_path_regular,
  12. make_wedged_bezier2, split_bezier_intersecting_with_closedpath,
  13. split_path_inout)
  14. from .path import Path
  15. @cbook._define_aliases({
  16. "antialiased": ["aa"],
  17. "edgecolor": ["ec"],
  18. "facecolor": ["fc"],
  19. "linewidth": ["lw"],
  20. "linestyle": ["ls"],
  21. })
  22. class Patch(artist.Artist):
  23. """
  24. A patch is a 2D artist with a face color and an edge color.
  25. If any of *edgecolor*, *facecolor*, *linewidth*, or *antialiased*
  26. are *None*, they default to their rc params setting.
  27. """
  28. zorder = 1
  29. validCap = ('butt', 'round', 'projecting')
  30. validJoin = ('miter', 'round', 'bevel')
  31. # Whether to draw an edge by default. Set on a
  32. # subclass-by-subclass basis.
  33. _edge_default = False
  34. def __init__(self,
  35. edgecolor=None,
  36. facecolor=None,
  37. color=None,
  38. linewidth=None,
  39. linestyle=None,
  40. antialiased=None,
  41. hatch=None,
  42. fill=True,
  43. capstyle=None,
  44. joinstyle=None,
  45. **kwargs):
  46. """
  47. The following kwarg properties are supported
  48. %(Patch)s
  49. """
  50. artist.Artist.__init__(self)
  51. if linewidth is None:
  52. linewidth = mpl.rcParams['patch.linewidth']
  53. if linestyle is None:
  54. linestyle = "solid"
  55. if capstyle is None:
  56. capstyle = 'butt'
  57. if joinstyle is None:
  58. joinstyle = 'miter'
  59. if antialiased is None:
  60. antialiased = mpl.rcParams['patch.antialiased']
  61. self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color'])
  62. self._fill = True # needed for set_facecolor call
  63. if color is not None:
  64. if edgecolor is not None or facecolor is not None:
  65. warnings.warn("Setting the 'color' property will override"
  66. "the edgecolor or facecolor properties.")
  67. self.set_color(color)
  68. else:
  69. self.set_edgecolor(edgecolor)
  70. self.set_facecolor(facecolor)
  71. # unscaled dashes. Needed to scale dash patterns by lw
  72. self._us_dashes = None
  73. self._linewidth = 0
  74. self.set_fill(fill)
  75. self.set_linestyle(linestyle)
  76. self.set_linewidth(linewidth)
  77. self.set_antialiased(antialiased)
  78. self.set_hatch(hatch)
  79. self.set_capstyle(capstyle)
  80. self.set_joinstyle(joinstyle)
  81. self._combined_transform = transforms.IdentityTransform()
  82. if len(kwargs):
  83. self.update(kwargs)
  84. def get_verts(self):
  85. """
  86. Return a copy of the vertices used in this patch
  87. If the patch contains Bezier curves, the curves will be
  88. interpolated by line segments. To access the curves as
  89. curves, use :meth:`get_path`.
  90. """
  91. trans = self.get_transform()
  92. path = self.get_path()
  93. polygons = path.to_polygons(trans)
  94. if len(polygons):
  95. return polygons[0]
  96. return []
  97. def _process_radius(self, radius):
  98. if radius is not None:
  99. return radius
  100. if isinstance(self._picker, Number):
  101. _radius = self._picker
  102. else:
  103. if self.get_edgecolor()[3] == 0:
  104. _radius = 0
  105. else:
  106. _radius = self.get_linewidth()
  107. return _radius
  108. def contains(self, mouseevent, radius=None):
  109. """Test whether the mouse event occurred in the patch.
  110. Returns T/F, {}
  111. """
  112. if callable(self._contains):
  113. return self._contains(self, mouseevent)
  114. radius = self._process_radius(radius)
  115. inside = self.get_path().contains_point(
  116. (mouseevent.x, mouseevent.y), self.get_transform(), radius)
  117. return inside, {}
  118. def contains_point(self, point, radius=None):
  119. """
  120. Returns ``True`` if the given *point* is inside the path
  121. (transformed with its transform attribute).
  122. *radius* allows the path to be made slightly larger or smaller.
  123. """
  124. radius = self._process_radius(radius)
  125. return self.get_path().contains_point(point,
  126. self.get_transform(),
  127. radius)
  128. def contains_points(self, points, radius=None):
  129. """
  130. Returns a bool array which is ``True`` if the (closed) path
  131. contains the corresponding point.
  132. (transformed with its transform attribute).
  133. *points* must be Nx2 array.
  134. *radius* allows the path to be made slightly larger or smaller.
  135. """
  136. radius = self._process_radius(radius)
  137. return self.get_path().contains_points(points,
  138. self.get_transform(),
  139. radius)
  140. def update_from(self, other):
  141. """
  142. Updates this :class:`Patch` from the properties of *other*.
  143. """
  144. artist.Artist.update_from(self, other)
  145. # For some properties we don't need or don't want to go through the
  146. # getters/setters, so we just copy them directly.
  147. self._edgecolor = other._edgecolor
  148. self._facecolor = other._facecolor
  149. self._original_edgecolor = other._original_edgecolor
  150. self._original_facecolor = other._original_facecolor
  151. self._fill = other._fill
  152. self._hatch = other._hatch
  153. self._hatch_color = other._hatch_color
  154. # copy the unscaled dash pattern
  155. self._us_dashes = other._us_dashes
  156. self.set_linewidth(other._linewidth) # also sets dash properties
  157. self.set_transform(other.get_data_transform())
  158. # If the transform of other needs further initialization, then it will
  159. # be the case for this artist too.
  160. self._transformSet = other.is_transform_set()
  161. def get_extents(self):
  162. """
  163. Return a :class:`~matplotlib.transforms.Bbox` object defining
  164. the axis-aligned extents of the :class:`Patch`.
  165. """
  166. return self.get_path().get_extents(self.get_transform())
  167. def get_transform(self):
  168. """
  169. Return the :class:`~matplotlib.transforms.Transform` applied
  170. to the :class:`Patch`.
  171. """
  172. return self.get_patch_transform() + artist.Artist.get_transform(self)
  173. def get_data_transform(self):
  174. """
  175. Return the :class:`~matplotlib.transforms.Transform` instance which
  176. maps data coordinates to physical coordinates.
  177. """
  178. return artist.Artist.get_transform(self)
  179. def get_patch_transform(self):
  180. """
  181. Return the :class:`~matplotlib.transforms.Transform` instance which
  182. takes patch coordinates to data coordinates.
  183. For example, one may define a patch of a circle which represents a
  184. radius of 5 by providing coordinates for a unit circle, and a
  185. transform which scales the coordinates (the patch coordinate) by 5.
  186. """
  187. return transforms.IdentityTransform()
  188. def get_antialiased(self):
  189. """
  190. Returns True if the :class:`Patch` is to be drawn with antialiasing.
  191. """
  192. return self._antialiased
  193. def get_edgecolor(self):
  194. """
  195. Return the edge color of the :class:`Patch`.
  196. """
  197. return self._edgecolor
  198. def get_facecolor(self):
  199. """
  200. Return the face color of the :class:`Patch`.
  201. """
  202. return self._facecolor
  203. def get_linewidth(self):
  204. """
  205. Return the line width in points.
  206. """
  207. return self._linewidth
  208. def get_linestyle(self):
  209. """
  210. Return the linestyle.
  211. """
  212. return self._linestyle
  213. def set_antialiased(self, aa):
  214. """
  215. Set whether to use antialiased rendering.
  216. Parameters
  217. ----------
  218. b : bool or None
  219. """
  220. if aa is None:
  221. aa = mpl.rcParams['patch.antialiased']
  222. self._antialiased = aa
  223. self.stale = True
  224. def _set_edgecolor(self, color):
  225. set_hatch_color = True
  226. if color is None:
  227. if (mpl.rcParams['patch.force_edgecolor'] or
  228. not self._fill or self._edge_default):
  229. color = mpl.rcParams['patch.edgecolor']
  230. else:
  231. color = 'none'
  232. set_hatch_color = False
  233. self._edgecolor = colors.to_rgba(color, self._alpha)
  234. if set_hatch_color:
  235. self._hatch_color = self._edgecolor
  236. self.stale = True
  237. def set_edgecolor(self, color):
  238. """
  239. Set the patch edge color.
  240. Parameters
  241. ----------
  242. color : color or None or 'auto'
  243. """
  244. self._original_edgecolor = color
  245. self._set_edgecolor(color)
  246. def _set_facecolor(self, color):
  247. if color is None:
  248. color = mpl.rcParams['patch.facecolor']
  249. alpha = self._alpha if self._fill else 0
  250. self._facecolor = colors.to_rgba(color, alpha)
  251. self.stale = True
  252. def set_facecolor(self, color):
  253. """
  254. Set the patch face color.
  255. Parameters
  256. ----------
  257. color : color or None
  258. """
  259. self._original_facecolor = color
  260. self._set_facecolor(color)
  261. def set_color(self, c):
  262. """
  263. Set both the edgecolor and the facecolor.
  264. .. seealso::
  265. :meth:`set_facecolor`, :meth:`set_edgecolor`
  266. For setting the edge or face color individually.
  267. Parameters
  268. ----------
  269. c : color
  270. """
  271. self.set_facecolor(c)
  272. self.set_edgecolor(c)
  273. def set_alpha(self, alpha):
  274. """
  275. Set the alpha transparency of the patch.
  276. Parameters
  277. ----------
  278. alpha : float or None
  279. """
  280. if alpha is not None:
  281. try:
  282. float(alpha)
  283. except TypeError:
  284. raise TypeError('alpha must be a float or None')
  285. artist.Artist.set_alpha(self, alpha)
  286. self._set_facecolor(self._original_facecolor)
  287. self._set_edgecolor(self._original_edgecolor)
  288. # stale is already True
  289. def set_linewidth(self, w):
  290. """
  291. Set the patch linewidth in points
  292. ACCEPTS: float or None for default
  293. """
  294. if w is None:
  295. w = mpl.rcParams['patch.linewidth']
  296. if w is None:
  297. w = mpl.rcParams['axes.linewidth']
  298. self._linewidth = float(w)
  299. # scale the dash pattern by the linewidth
  300. offset, ls = self._us_dashes
  301. self._dashoffset, self._dashes = mlines._scale_dashes(
  302. offset, ls, self._linewidth)
  303. self.stale = True
  304. def set_linestyle(self, ls):
  305. """
  306. Set the patch linestyle.
  307. =========================== =================
  308. linestyle description
  309. =========================== =================
  310. ``'-'`` or ``'solid'`` solid line
  311. ``'--'`` or ``'dashed'`` dashed line
  312. ``'-.'`` or ``'dashdot'`` dash-dotted line
  313. ``':'`` or ``'dotted'`` dotted line
  314. =========================== =================
  315. Alternatively a dash tuple of the following form can be provided::
  316. (offset, onoffseq),
  317. where ``onoffseq`` is an even length tuple of on and off ink in points.
  318. Parameters
  319. ----------
  320. ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
  321. The line style.
  322. """
  323. if ls is None:
  324. ls = "solid"
  325. self._linestyle = ls
  326. # get the unscaled dash pattern
  327. offset, ls = self._us_dashes = mlines._get_dash_pattern(ls)
  328. # scale the dash pattern by the linewidth
  329. self._dashoffset, self._dashes = mlines._scale_dashes(
  330. offset, ls, self._linewidth)
  331. self.stale = True
  332. def set_fill(self, b):
  333. """
  334. Set whether to fill the patch.
  335. Parameters
  336. ----------
  337. b : bool
  338. """
  339. self._fill = bool(b)
  340. self._set_facecolor(self._original_facecolor)
  341. self._set_edgecolor(self._original_edgecolor)
  342. self.stale = True
  343. def get_fill(self):
  344. 'return whether fill is set'
  345. return self._fill
  346. # Make fill a property so as to preserve the long-standing
  347. # but somewhat inconsistent behavior in which fill was an
  348. # attribute.
  349. fill = property(get_fill, set_fill)
  350. def set_capstyle(self, s):
  351. """
  352. Set the patch capstyle
  353. Parameters
  354. ----------
  355. s : {'butt', 'round', 'projecting'}
  356. """
  357. s = s.lower()
  358. if s not in self.validCap:
  359. raise ValueError('set_capstyle passed "%s";\n' % (s,) +
  360. 'valid capstyles are %s' % (self.validCap,))
  361. self._capstyle = s
  362. self.stale = True
  363. def get_capstyle(self):
  364. "Return the current capstyle"
  365. return self._capstyle
  366. def set_joinstyle(self, s):
  367. """
  368. Set the patch joinstyle
  369. Parameters
  370. ----------
  371. s : {'miter', 'round', 'bevel'}
  372. """
  373. s = s.lower()
  374. if s not in self.validJoin:
  375. raise ValueError('set_joinstyle passed "%s";\n' % (s,) +
  376. 'valid joinstyles are %s' % (self.validJoin,))
  377. self._joinstyle = s
  378. self.stale = True
  379. def get_joinstyle(self):
  380. "Return the current joinstyle"
  381. return self._joinstyle
  382. def set_hatch(self, hatch):
  383. r"""
  384. Set the hatching pattern
  385. *hatch* can be one of::
  386. / - diagonal hatching
  387. \ - back diagonal
  388. | - vertical
  389. - - horizontal
  390. + - crossed
  391. x - crossed diagonal
  392. o - small circle
  393. O - large circle
  394. . - dots
  395. * - stars
  396. Letters can be combined, in which case all the specified
  397. hatchings are done. If same letter repeats, it increases the
  398. density of hatching of that pattern.
  399. Hatching is supported in the PostScript, PDF, SVG and Agg
  400. backends only.
  401. Parameters
  402. ----------
  403. hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'}
  404. """
  405. self._hatch = hatch
  406. self.stale = True
  407. def get_hatch(self):
  408. 'Return the current hatching pattern'
  409. return self._hatch
  410. @artist.allow_rasterization
  411. def draw(self, renderer):
  412. 'Draw the :class:`Patch` to the given *renderer*.'
  413. if not self.get_visible():
  414. return
  415. renderer.open_group('patch', self.get_gid())
  416. gc = renderer.new_gc()
  417. gc.set_foreground(self._edgecolor, isRGBA=True)
  418. lw = self._linewidth
  419. if self._edgecolor[3] == 0:
  420. lw = 0
  421. gc.set_linewidth(lw)
  422. gc.set_dashes(0, self._dashes)
  423. gc.set_capstyle(self._capstyle)
  424. gc.set_joinstyle(self._joinstyle)
  425. gc.set_antialiased(self._antialiased)
  426. self._set_gc_clip(gc)
  427. gc.set_url(self._url)
  428. gc.set_snap(self.get_snap())
  429. rgbFace = self._facecolor
  430. if rgbFace[3] == 0:
  431. rgbFace = None # (some?) renderers expect this as no-fill signal
  432. gc.set_alpha(self._alpha)
  433. if self._hatch:
  434. gc.set_hatch(self._hatch)
  435. try:
  436. gc.set_hatch_color(self._hatch_color)
  437. except AttributeError:
  438. # if we end up with a GC that does not have this method
  439. warnings.warn(
  440. "Your backend does not support setting the hatch color.")
  441. if self.get_sketch_params() is not None:
  442. gc.set_sketch_params(*self.get_sketch_params())
  443. path = self.get_path()
  444. transform = self.get_transform()
  445. tpath = transform.transform_path_non_affine(path)
  446. affine = transform.get_affine()
  447. if self.get_path_effects():
  448. from matplotlib.patheffects import PathEffectRenderer
  449. renderer = PathEffectRenderer(self.get_path_effects(), renderer)
  450. renderer.draw_path(gc, tpath, affine, rgbFace)
  451. gc.restore()
  452. renderer.close_group('patch')
  453. self.stale = False
  454. def get_path(self):
  455. """
  456. Return the path of this patch
  457. """
  458. raise NotImplementedError('Derived must override')
  459. def get_window_extent(self, renderer=None):
  460. return self.get_path().get_extents(self.get_transform())
  461. patchdoc = artist.kwdoc(Patch)
  462. for k in ('Rectangle', 'Circle', 'RegularPolygon', 'Polygon', 'Wedge', 'Arrow',
  463. 'FancyArrow', 'YAArrow', 'CirclePolygon', 'Ellipse', 'Arc',
  464. 'FancyBboxPatch', 'Patch'):
  465. docstring.interpd.update({k: patchdoc})
  466. # define Patch.__init__ docstring after the class has been added to interpd
  467. docstring.dedent_interpd(Patch.__init__)
  468. class Shadow(Patch):
  469. def __str__(self):
  470. return "Shadow(%s)" % (str(self.patch))
  471. @docstring.dedent_interpd
  472. def __init__(self, patch, ox, oy, props=None, **kwargs):
  473. """
  474. Create a shadow of the given *patch* offset by *ox*, *oy*.
  475. *props*, if not *None*, is a patch property update dictionary.
  476. If *None*, the shadow will have have the same color as the face,
  477. but darkened.
  478. kwargs are
  479. %(Patch)s
  480. """
  481. Patch.__init__(self)
  482. self.patch = patch
  483. self.props = props
  484. self._ox, self._oy = ox, oy
  485. self._shadow_transform = transforms.Affine2D()
  486. self._update()
  487. def _update(self):
  488. self.update_from(self.patch)
  489. # Place the shadow patch directly behind the inherited patch.
  490. self.set_zorder(np.nextafter(self.patch.zorder, -np.inf))
  491. if self.props is not None:
  492. self.update(self.props)
  493. else:
  494. color = .3 * np.asarray(colors.to_rgb(self.patch.get_facecolor()))
  495. self.set_facecolor(color)
  496. self.set_edgecolor(color)
  497. self.set_alpha(0.5)
  498. def _update_transform(self, renderer):
  499. ox = renderer.points_to_pixels(self._ox)
  500. oy = renderer.points_to_pixels(self._oy)
  501. self._shadow_transform.clear().translate(ox, oy)
  502. def _get_ox(self):
  503. return self._ox
  504. def _set_ox(self, ox):
  505. self._ox = ox
  506. def _get_oy(self):
  507. return self._oy
  508. def _set_oy(self, oy):
  509. self._oy = oy
  510. def get_path(self):
  511. return self.patch.get_path()
  512. def get_patch_transform(self):
  513. return self.patch.get_patch_transform() + self._shadow_transform
  514. def draw(self, renderer):
  515. self._update_transform(renderer)
  516. Patch.draw(self, renderer)
  517. class Rectangle(Patch):
  518. """
  519. Draw a rectangle with lower left at *xy* = (*x*, *y*) with
  520. specified *width*, *height* and rotation *angle*.
  521. """
  522. def __str__(self):
  523. pars = self._x0, self._y0, self._width, self._height, self.angle
  524. fmt = "Rectangle(xy=(%g, %g), width=%g, height=%g, angle=%g)"
  525. return fmt % pars
  526. @docstring.dedent_interpd
  527. def __init__(self, xy, width, height, angle=0.0, **kwargs):
  528. """
  529. Parameters
  530. ----------
  531. xy : (float, float)
  532. The bottom and left rectangle coordinates
  533. width : float
  534. Rectangle width
  535. height : float
  536. Rectangle height
  537. angle : float, optional
  538. rotation in degrees anti-clockwise about *xy* (default is 0.0)
  539. fill : bool, optional
  540. Whether to fill the rectangle (default is ``True``)
  541. Notes
  542. -----
  543. Valid kwargs are:
  544. %(Patch)s
  545. """
  546. Patch.__init__(self, **kwargs)
  547. self._x0 = xy[0]
  548. self._y0 = xy[1]
  549. self._width = width
  550. self._height = height
  551. self._x1 = self._x0 + self._width
  552. self._y1 = self._y0 + self._height
  553. self.angle = float(angle)
  554. # Note: This cannot be calculated until this is added to an Axes
  555. self._rect_transform = transforms.IdentityTransform()
  556. def get_path(self):
  557. """
  558. Return the vertices of the rectangle.
  559. """
  560. return Path.unit_rectangle()
  561. def _update_patch_transform(self):
  562. """NOTE: This cannot be called until after this has been added
  563. to an Axes, otherwise unit conversion will fail. This
  564. makes it very important to call the accessor method and
  565. not directly access the transformation member variable.
  566. """
  567. x0, y0, x1, y1 = self._convert_units()
  568. bbox = transforms.Bbox.from_extents(x0, y0, x1, y1)
  569. rot_trans = transforms.Affine2D()
  570. rot_trans.rotate_deg_around(x0, y0, self.angle)
  571. self._rect_transform = transforms.BboxTransformTo(bbox)
  572. self._rect_transform += rot_trans
  573. def _update_x1(self):
  574. self._x1 = self._x0 + self._width
  575. def _update_y1(self):
  576. self._y1 = self._y0 + self._height
  577. def _convert_units(self):
  578. """
  579. Convert bounds of the rectangle.
  580. """
  581. x0 = self.convert_xunits(self._x0)
  582. y0 = self.convert_yunits(self._y0)
  583. x1 = self.convert_xunits(self._x1)
  584. y1 = self.convert_yunits(self._y1)
  585. return x0, y0, x1, y1
  586. def get_patch_transform(self):
  587. self._update_patch_transform()
  588. return self._rect_transform
  589. def get_x(self):
  590. "Return the left coord of the rectangle."
  591. return self._x0
  592. def get_y(self):
  593. "Return the bottom coord of the rectangle."
  594. return self._y0
  595. def get_xy(self):
  596. "Return the left and bottom coords of the rectangle."
  597. return self._x0, self._y0
  598. def get_width(self):
  599. "Return the width of the rectangle."
  600. return self._width
  601. def get_height(self):
  602. "Return the height of the rectangle."
  603. return self._height
  604. def set_x(self, x):
  605. "Set the left coord of the rectangle."
  606. self._x0 = x
  607. self._update_x1()
  608. self.stale = True
  609. def set_y(self, y):
  610. "Set the bottom coord of the rectangle."
  611. self._y0 = y
  612. self._update_y1()
  613. self.stale = True
  614. def set_xy(self, xy):
  615. """
  616. Set the left and bottom coords of the rectangle.
  617. Parameters
  618. ----------
  619. xy : (float, float)
  620. """
  621. self._x0, self._y0 = xy
  622. self._update_x1()
  623. self._update_y1()
  624. self.stale = True
  625. def set_width(self, w):
  626. "Set the width of the rectangle."
  627. self._width = w
  628. self._update_x1()
  629. self.stale = True
  630. def set_height(self, h):
  631. "Set the height of the rectangle."
  632. self._height = h
  633. self._update_y1()
  634. self.stale = True
  635. def set_bounds(self, *args):
  636. """
  637. Set the bounds of the rectangle: l,b,w,h
  638. ACCEPTS: (left, bottom, width, height)
  639. """
  640. if len(args) == 1:
  641. l, b, w, h = args[0]
  642. else:
  643. l, b, w, h = args
  644. self._x0 = l
  645. self._y0 = b
  646. self._width = w
  647. self._height = h
  648. self._update_x1()
  649. self._update_y1()
  650. self.stale = True
  651. def get_bbox(self):
  652. x0, y0, x1, y1 = self._convert_units()
  653. return transforms.Bbox.from_extents(x0, y0, x1, y1)
  654. xy = property(get_xy, set_xy)
  655. class RegularPolygon(Patch):
  656. """
  657. A regular polygon patch.
  658. """
  659. def __str__(self):
  660. s = "RegularPolygon((%g, %g), %d, radius=%g, orientation=%g)"
  661. return s % (self._xy[0], self._xy[1], self._numVertices, self._radius,
  662. self._orientation)
  663. @docstring.dedent_interpd
  664. def __init__(self, xy, numVertices, radius=5, orientation=0,
  665. **kwargs):
  666. """
  667. Constructor arguments:
  668. *xy*
  669. A length 2 tuple (*x*, *y*) of the center.
  670. *numVertices*
  671. the number of vertices.
  672. *radius*
  673. The distance from the center to each of the vertices.
  674. *orientation*
  675. rotates the polygon (in radians).
  676. Valid kwargs are:
  677. %(Patch)s
  678. """
  679. self._xy = xy
  680. self._numVertices = numVertices
  681. self._orientation = orientation
  682. self._radius = radius
  683. self._path = Path.unit_regular_polygon(numVertices)
  684. self._poly_transform = transforms.Affine2D()
  685. self._update_transform()
  686. Patch.__init__(self, **kwargs)
  687. def _update_transform(self):
  688. self._poly_transform.clear() \
  689. .scale(self.radius) \
  690. .rotate(self.orientation) \
  691. .translate(*self.xy)
  692. def _get_xy(self):
  693. return self._xy
  694. def _set_xy(self, xy):
  695. self._xy = xy
  696. self._update_transform()
  697. xy = property(_get_xy, _set_xy)
  698. def _get_orientation(self):
  699. return self._orientation
  700. def _set_orientation(self, orientation):
  701. self._orientation = orientation
  702. self._update_transform()
  703. orientation = property(_get_orientation, _set_orientation)
  704. def _get_radius(self):
  705. return self._radius
  706. def _set_radius(self, radius):
  707. self._radius = radius
  708. self._update_transform()
  709. radius = property(_get_radius, _set_radius)
  710. def _get_numvertices(self):
  711. return self._numVertices
  712. def _set_numvertices(self, numVertices):
  713. self._numVertices = numVertices
  714. numvertices = property(_get_numvertices, _set_numvertices)
  715. def get_path(self):
  716. return self._path
  717. def get_patch_transform(self):
  718. self._update_transform()
  719. return self._poly_transform
  720. class PathPatch(Patch):
  721. """
  722. A general polycurve path patch.
  723. """
  724. _edge_default = True
  725. def __str__(self):
  726. s = "PathPatch%d((%g, %g) ...)"
  727. return s % (len(self._path.vertices), *tuple(self._path.vertices[0]))
  728. @docstring.dedent_interpd
  729. def __init__(self, path, **kwargs):
  730. """
  731. *path* is a :class:`matplotlib.path.Path` object.
  732. Valid kwargs are:
  733. %(Patch)s
  734. .. seealso::
  735. :class:`Patch`
  736. For additional kwargs
  737. """
  738. Patch.__init__(self, **kwargs)
  739. self._path = path
  740. def get_path(self):
  741. return self._path
  742. class Polygon(Patch):
  743. """
  744. A general polygon patch.
  745. """
  746. def __str__(self):
  747. s = "Polygon%d((%g, %g) ...)"
  748. return s % (len(self._path.vertices), *tuple(self._path.vertices[0]))
  749. @docstring.dedent_interpd
  750. def __init__(self, xy, closed=True, **kwargs):
  751. """
  752. *xy* is a numpy array with shape Nx2.
  753. If *closed* is *True*, the polygon will be closed so the
  754. starting and ending points are the same.
  755. Valid kwargs are:
  756. %(Patch)s
  757. .. seealso::
  758. :class:`Patch`
  759. For additional kwargs
  760. """
  761. Patch.__init__(self, **kwargs)
  762. self._closed = closed
  763. self.set_xy(xy)
  764. def get_path(self):
  765. """
  766. Get the path of the polygon
  767. Returns
  768. -------
  769. path : Path
  770. The :class:`~matplotlib.path.Path` object for
  771. the polygon
  772. """
  773. return self._path
  774. def get_closed(self):
  775. """
  776. Returns if the polygon is closed
  777. Returns
  778. -------
  779. closed : bool
  780. If the path is closed
  781. """
  782. return self._closed
  783. def set_closed(self, closed):
  784. """
  785. Set if the polygon is closed
  786. Parameters
  787. ----------
  788. closed : bool
  789. True if the polygon is closed
  790. """
  791. if self._closed == bool(closed):
  792. return
  793. self._closed = bool(closed)
  794. self.set_xy(self.get_xy())
  795. self.stale = True
  796. def get_xy(self):
  797. """
  798. Get the vertices of the path.
  799. Returns
  800. -------
  801. vertices : (N, 2) numpy array
  802. The coordinates of the vertices.
  803. """
  804. return self._path.vertices
  805. def set_xy(self, xy):
  806. """
  807. Set the vertices of the polygon.
  808. Parameters
  809. ----------
  810. xy : (N, 2) array-like
  811. The coordinates of the vertices.
  812. """
  813. xy = np.asarray(xy)
  814. if self._closed:
  815. if len(xy) and (xy[0] != xy[-1]).any():
  816. xy = np.concatenate([xy, [xy[0]]])
  817. else:
  818. if len(xy) > 2 and (xy[0] == xy[-1]).all():
  819. xy = xy[:-1]
  820. self._path = Path(xy, closed=self._closed)
  821. self.stale = True
  822. _get_xy = get_xy
  823. _set_xy = set_xy
  824. xy = property(get_xy, set_xy,
  825. doc='The vertices of the path as (N, 2) numpy array.')
  826. class Wedge(Patch):
  827. """
  828. Wedge shaped patch.
  829. """
  830. def __str__(self):
  831. pars = (self.center[0], self.center[1], self.r,
  832. self.theta1, self.theta2, self.width)
  833. fmt = "Wedge(center=(%g, %g), r=%g, theta1=%g, theta2=%g, width=%s)"
  834. return fmt % pars
  835. @docstring.dedent_interpd
  836. def __init__(self, center, r, theta1, theta2, width=None, **kwargs):
  837. """
  838. Draw a wedge centered at *x*, *y* center with radius *r* that
  839. sweeps *theta1* to *theta2* (in degrees). If *width* is given,
  840. then a partial wedge is drawn from inner radius *r* - *width*
  841. to outer radius *r*.
  842. Valid kwargs are:
  843. %(Patch)s
  844. """
  845. Patch.__init__(self, **kwargs)
  846. self.center = center
  847. self.r, self.width = r, width
  848. self.theta1, self.theta2 = theta1, theta2
  849. self._patch_transform = transforms.IdentityTransform()
  850. self._recompute_path()
  851. def _recompute_path(self):
  852. # Inner and outer rings are connected unless the annulus is complete
  853. if abs((self.theta2 - self.theta1) - 360) <= 1e-12:
  854. theta1, theta2 = 0, 360
  855. connector = Path.MOVETO
  856. else:
  857. theta1, theta2 = self.theta1, self.theta2
  858. connector = Path.LINETO
  859. # Form the outer ring
  860. arc = Path.arc(theta1, theta2)
  861. if self.width is not None:
  862. # Partial annulus needs to draw the outer ring
  863. # followed by a reversed and scaled inner ring
  864. v1 = arc.vertices
  865. v2 = arc.vertices[::-1] * (self.r - self.width) / self.r
  866. v = np.vstack([v1, v2, v1[0, :], (0, 0)])
  867. c = np.hstack([arc.codes, arc.codes, connector, Path.CLOSEPOLY])
  868. c[len(arc.codes)] = connector
  869. else:
  870. # Wedge doesn't need an inner ring
  871. v = np.vstack([arc.vertices, [(0, 0), arc.vertices[0, :], (0, 0)]])
  872. c = np.hstack([arc.codes, [connector, connector, Path.CLOSEPOLY]])
  873. # Shift and scale the wedge to the final location.
  874. v *= self.r
  875. v += np.asarray(self.center)
  876. self._path = Path(v, c)
  877. def set_center(self, center):
  878. self._path = None
  879. self.center = center
  880. self.stale = True
  881. def set_radius(self, radius):
  882. self._path = None
  883. self.r = radius
  884. self.stale = True
  885. def set_theta1(self, theta1):
  886. self._path = None
  887. self.theta1 = theta1
  888. self.stale = True
  889. def set_theta2(self, theta2):
  890. self._path = None
  891. self.theta2 = theta2
  892. self.stale = True
  893. def set_width(self, width):
  894. self._path = None
  895. self.width = width
  896. self.stale = True
  897. def get_path(self):
  898. if self._path is None:
  899. self._recompute_path()
  900. return self._path
  901. # COVERAGE NOTE: Not used internally or from examples
  902. class Arrow(Patch):
  903. """
  904. An arrow patch.
  905. """
  906. def __str__(self):
  907. return "Arrow()"
  908. _path = Path([[0.0, 0.1], [0.0, -0.1],
  909. [0.8, -0.1], [0.8, -0.3],
  910. [1.0, 0.0], [0.8, 0.3],
  911. [0.8, 0.1], [0.0, 0.1]],
  912. closed=True)
  913. @docstring.dedent_interpd
  914. def __init__(self, x, y, dx, dy, width=1.0, **kwargs):
  915. """
  916. Draws an arrow from (*x*, *y*) to (*x* + *dx*, *y* + *dy*).
  917. The width of the arrow is scaled by *width*.
  918. Parameters
  919. ----------
  920. x : scalar
  921. x coordinate of the arrow tail
  922. y : scalar
  923. y coordinate of the arrow tail
  924. dx : scalar
  925. Arrow length in the x direction
  926. dy : scalar
  927. Arrow length in the y direction
  928. width : scalar, optional (default: 1)
  929. Scale factor for the width of the arrow. With a default value of
  930. 1, the tail width is 0.2 and head width is 0.6.
  931. **kwargs :
  932. Keyword arguments control the :class:`~matplotlib.patches.Patch`
  933. properties:
  934. %(Patch)s
  935. See Also
  936. --------
  937. :class:`FancyArrow` :
  938. Patch that allows independent control of the head and tail
  939. properties
  940. """
  941. Patch.__init__(self, **kwargs)
  942. L = np.hypot(dx, dy)
  943. if L != 0:
  944. cx = dx / L
  945. sx = dy / L
  946. else:
  947. # Account for division by zero
  948. cx, sx = 0, 1
  949. trans1 = transforms.Affine2D().scale(L, width)
  950. trans2 = transforms.Affine2D.from_values(cx, sx, -sx, cx, 0.0, 0.0)
  951. trans3 = transforms.Affine2D().translate(x, y)
  952. trans = trans1 + trans2 + trans3
  953. self._patch_transform = trans.frozen()
  954. def get_path(self):
  955. return self._path
  956. def get_patch_transform(self):
  957. return self._patch_transform
  958. class FancyArrow(Polygon):
  959. """
  960. Like Arrow, but lets you set head width and head height independently.
  961. """
  962. _edge_default = True
  963. def __str__(self):
  964. return "FancyArrow()"
  965. @docstring.dedent_interpd
  966. def __init__(self, x, y, dx, dy, width=0.001, length_includes_head=False,
  967. head_width=None, head_length=None, shape='full', overhang=0,
  968. head_starts_at_zero=False, **kwargs):
  969. """
  970. Constructor arguments
  971. *width*: float (default: 0.001)
  972. width of full arrow tail
  973. *length_includes_head*: bool (default: False)
  974. True if head is to be counted in calculating the length.
  975. *head_width*: float or None (default: 3*width)
  976. total width of the full arrow head
  977. *head_length*: float or None (default: 1.5 * head_width)
  978. length of arrow head
  979. *shape*: ['full', 'left', 'right'] (default: 'full')
  980. draw the left-half, right-half, or full arrow
  981. *overhang*: float (default: 0)
  982. fraction that the arrow is swept back (0 overhang means
  983. triangular shape). Can be negative or greater than one.
  984. *head_starts_at_zero*: bool (default: False)
  985. if True, the head starts being drawn at coordinate 0
  986. instead of ending at coordinate 0.
  987. Other valid kwargs (inherited from :class:`Patch`) are:
  988. %(Patch)s
  989. """
  990. if head_width is None:
  991. head_width = 3 * width
  992. if head_length is None:
  993. head_length = 1.5 * head_width
  994. distance = np.hypot(dx, dy)
  995. if length_includes_head:
  996. length = distance
  997. else:
  998. length = distance + head_length
  999. if not length:
  1000. verts = [] # display nothing if empty
  1001. else:
  1002. # start by drawing horizontal arrow, point at (0,0)
  1003. hw, hl, hs, lw = head_width, head_length, overhang, width
  1004. left_half_arrow = np.array([
  1005. [0.0, 0.0], # tip
  1006. [-hl, -hw / 2], # leftmost
  1007. [-hl * (1 - hs), -lw / 2], # meets stem
  1008. [-length, -lw / 2], # bottom left
  1009. [-length, 0],
  1010. ])
  1011. # if we're not including the head, shift up by head length
  1012. if not length_includes_head:
  1013. left_half_arrow += [head_length, 0]
  1014. # if the head starts at 0, shift up by another head length
  1015. if head_starts_at_zero:
  1016. left_half_arrow += [head_length / 2, 0]
  1017. # figure out the shape, and complete accordingly
  1018. if shape == 'left':
  1019. coords = left_half_arrow
  1020. else:
  1021. right_half_arrow = left_half_arrow * [1, -1]
  1022. if shape == 'right':
  1023. coords = right_half_arrow
  1024. elif shape == 'full':
  1025. # The half-arrows contain the midpoint of the stem,
  1026. # which we can omit from the full arrow. Including it
  1027. # twice caused a problem with xpdf.
  1028. coords = np.concatenate([left_half_arrow[:-1],
  1029. right_half_arrow[-2::-1]])
  1030. else:
  1031. raise ValueError("Got unknown shape: %s" % shape)
  1032. if distance != 0:
  1033. cx = dx / distance
  1034. sx = dy / distance
  1035. else:
  1036. # Account for division by zero
  1037. cx, sx = 0, 1
  1038. M = [[cx, sx], [-sx, cx]]
  1039. verts = np.dot(coords, M) + (x + dx, y + dy)
  1040. super().__init__(verts, closed=True, **kwargs)
  1041. docstring.interpd.update({"FancyArrow": FancyArrow.__init__.__doc__})
  1042. @cbook.deprecated("3.0", alternative="FancyArrowPatch")
  1043. class YAArrow(Patch):
  1044. """
  1045. Yet another arrow class.
  1046. This is an arrow that is defined in display space and has a tip at
  1047. *x1*, *y1* and a base at *x2*, *y2*.
  1048. """
  1049. def __str__(self):
  1050. return "YAArrow()"
  1051. @docstring.dedent_interpd
  1052. def __init__(self, figure, xytip, xybase,
  1053. width=4, frac=0.1, headwidth=12, **kwargs):
  1054. """
  1055. Constructor arguments:
  1056. *xytip*
  1057. (*x*, *y*) location of arrow tip
  1058. *xybase*
  1059. (*x*, *y*) location the arrow base mid point
  1060. *figure*
  1061. The `Figure` instance (used to get the dpi setting).
  1062. *width*
  1063. The width of the arrow in points
  1064. *frac*
  1065. The fraction of the arrow length occupied by the head
  1066. *headwidth*
  1067. The width of the base of the arrow head in points
  1068. Valid kwargs are:
  1069. %(Patch)s
  1070. """
  1071. self.xytip = xytip
  1072. self.xybase = xybase
  1073. self.width = width
  1074. self.frac = frac
  1075. self.headwidth = headwidth
  1076. Patch.__init__(self, **kwargs)
  1077. # Set self.figure after Patch.__init__, since it sets self.figure to
  1078. # None
  1079. self.figure = figure
  1080. def get_path(self):
  1081. # Since this is dpi dependent, we need to recompute the path
  1082. # every time.
  1083. # the base vertices
  1084. x1, y1 = self.xytip
  1085. x2, y2 = self.xybase
  1086. k1 = self.width * self.figure.dpi / 72. / 2.
  1087. k2 = self.headwidth * self.figure.dpi / 72. / 2.
  1088. xb1, yb1, xb2, yb2 = self.getpoints(x1, y1, x2, y2, k1)
  1089. # a point on the segment 20% of the distance from the tip to the base
  1090. theta = math.atan2(y2 - y1, x2 - x1)
  1091. r = math.sqrt((y2 - y1) ** 2. + (x2 - x1) ** 2.)
  1092. xm = x1 + self.frac * r * math.cos(theta)
  1093. ym = y1 + self.frac * r * math.sin(theta)
  1094. xc1, yc1, xc2, yc2 = self.getpoints(x1, y1, xm, ym, k1)
  1095. xd1, yd1, xd2, yd2 = self.getpoints(x1, y1, xm, ym, k2)
  1096. xs = self.convert_xunits([xb1, xb2, xc2, xd2, x1, xd1, xc1, xb1])
  1097. ys = self.convert_yunits([yb1, yb2, yc2, yd2, y1, yd1, yc1, yb1])
  1098. return Path(np.column_stack([xs, ys]), closed=True)
  1099. def get_patch_transform(self):
  1100. return transforms.IdentityTransform()
  1101. def getpoints(self, x1, y1, x2, y2, k):
  1102. """
  1103. For line segment defined by (*x1*, *y1*) and (*x2*, *y2*)
  1104. return the points on the line that is perpendicular to the
  1105. line and intersects (*x2*, *y2*) and the distance from (*x2*,
  1106. *y2*) of the returned points is *k*.
  1107. """
  1108. x1, y1, x2, y2, k = map(float, (x1, y1, x2, y2, k))
  1109. if y2 - y1 == 0:
  1110. return x2, y2 + k, x2, y2 - k
  1111. elif x2 - x1 == 0:
  1112. return x2 + k, y2, x2 - k, y2
  1113. m = (y2 - y1) / (x2 - x1)
  1114. pm = -1. / m
  1115. a = 1
  1116. b = -2 * y2
  1117. c = y2 ** 2. - k ** 2. * pm ** 2. / (1. + pm ** 2.)
  1118. y3a = (-b + math.sqrt(b ** 2 - 4 * a * c)) / (2 * a)
  1119. x3a = (y3a - y2) / pm + x2
  1120. y3b = (-b - math.sqrt(b ** 2 - 4 * a * c)) / (2 * a)
  1121. x3b = (y3b - y2) / pm + x2
  1122. return x3a, y3a, x3b, y3b
  1123. class CirclePolygon(RegularPolygon):
  1124. """
  1125. A polygon-approximation of a circle patch.
  1126. """
  1127. def __str__(self):
  1128. s = "CirclePolygon((%g, %g), radius=%g, resolution=%d)"
  1129. return s % (self._xy[0], self._xy[1], self._radius, self._numVertices)
  1130. @docstring.dedent_interpd
  1131. def __init__(self, xy, radius=5,
  1132. resolution=20, # the number of vertices
  1133. ** kwargs):
  1134. """
  1135. Create a circle at *xy* = (*x*, *y*) with given *radius*.
  1136. This circle is approximated by a regular polygon with
  1137. *resolution* sides. For a smoother circle drawn with splines,
  1138. see :class:`~matplotlib.patches.Circle`.
  1139. Valid kwargs are:
  1140. %(Patch)s
  1141. """
  1142. RegularPolygon.__init__(self, xy,
  1143. resolution,
  1144. radius,
  1145. orientation=0,
  1146. **kwargs)
  1147. class Ellipse(Patch):
  1148. """
  1149. A scale-free ellipse.
  1150. """
  1151. def __str__(self):
  1152. pars = (self._center[0], self._center[1],
  1153. self.width, self.height, self.angle)
  1154. fmt = "Ellipse(xy=(%s, %s), width=%s, height=%s, angle=%s)"
  1155. return fmt % pars
  1156. @docstring.dedent_interpd
  1157. def __init__(self, xy, width, height, angle=0, **kwargs):
  1158. """
  1159. Parameters
  1160. ----------
  1161. xy : (float, float)
  1162. xy coordinates of ellipse centre.
  1163. width : float
  1164. Total length (diameter) of horizontal axis.
  1165. height : float
  1166. Total length (diameter) of vertical axis.
  1167. angle : scalar, optional
  1168. Rotation in degrees anti-clockwise.
  1169. Notes
  1170. -----
  1171. Valid keyword arguments are
  1172. %(Patch)s
  1173. """
  1174. Patch.__init__(self, **kwargs)
  1175. self._center = xy
  1176. self.width, self.height = width, height
  1177. self.angle = angle
  1178. self._path = Path.unit_circle()
  1179. # Note: This cannot be calculated until this is added to an Axes
  1180. self._patch_transform = transforms.IdentityTransform()
  1181. def _recompute_transform(self):
  1182. """NOTE: This cannot be called until after this has been added
  1183. to an Axes, otherwise unit conversion will fail. This
  1184. makes it very important to call the accessor method and
  1185. not directly access the transformation member variable.
  1186. """
  1187. center = (self.convert_xunits(self._center[0]),
  1188. self.convert_yunits(self._center[1]))
  1189. width = self.convert_xunits(self.width)
  1190. height = self.convert_yunits(self.height)
  1191. self._patch_transform = transforms.Affine2D() \
  1192. .scale(width * 0.5, height * 0.5) \
  1193. .rotate_deg(self.angle) \
  1194. .translate(*center)
  1195. def get_path(self):
  1196. """
  1197. Return the vertices of the rectangle
  1198. """
  1199. return self._path
  1200. def get_patch_transform(self):
  1201. self._recompute_transform()
  1202. return self._patch_transform
  1203. def set_center(self, xy):
  1204. """
  1205. Set the center of the ellipse.
  1206. Parameters
  1207. ----------
  1208. xy : (float, float)
  1209. """
  1210. self._center = xy
  1211. self.stale = True
  1212. def get_center(self):
  1213. """
  1214. Return the center of the ellipse
  1215. """
  1216. return self._center
  1217. center = property(get_center, set_center)
  1218. class Circle(Ellipse):
  1219. """
  1220. A circle patch.
  1221. """
  1222. def __str__(self):
  1223. pars = self.center[0], self.center[1], self.radius
  1224. fmt = "Circle(xy=(%g, %g), radius=%g)"
  1225. return fmt % pars
  1226. @docstring.dedent_interpd
  1227. def __init__(self, xy, radius=5, **kwargs):
  1228. """
  1229. Create true circle at center *xy* = (*x*, *y*) with given
  1230. *radius*. Unlike :class:`~matplotlib.patches.CirclePolygon`
  1231. which is a polygonal approximation, this uses Bezier splines
  1232. and is much closer to a scale-free circle.
  1233. Valid kwargs are:
  1234. %(Patch)s
  1235. """
  1236. Ellipse.__init__(self, xy, radius * 2, radius * 2, **kwargs)
  1237. self.radius = radius
  1238. def set_radius(self, radius):
  1239. """
  1240. Set the radius of the circle
  1241. Parameters
  1242. ----------
  1243. radius : float
  1244. """
  1245. self.width = self.height = 2 * radius
  1246. self.stale = True
  1247. def get_radius(self):
  1248. """
  1249. Return the radius of the circle
  1250. """
  1251. return self.width / 2.
  1252. radius = property(get_radius, set_radius)
  1253. class Arc(Ellipse):
  1254. """
  1255. An elliptical arc, i.e. a segment of an ellipse.
  1256. Due to internal optimizations, there are certain restrictions on using Arc:
  1257. - The arc cannot be filled.
  1258. - The arc must be used in an :class:`~.axes.Axes` instance---it can not be
  1259. added directly to a `.Figure`---because it is optimized to only render
  1260. the segments that are inside the axes bounding box with high resolution.
  1261. """
  1262. def __str__(self):
  1263. pars = (self.center[0], self.center[1], self.width,
  1264. self.height, self.angle, self.theta1, self.theta2)
  1265. fmt = ("Arc(xy=(%g, %g), width=%g, "
  1266. "height=%g, angle=%g, theta1=%g, theta2=%g)")
  1267. return fmt % pars
  1268. @docstring.dedent_interpd
  1269. def __init__(self, xy, width, height, angle=0.0,
  1270. theta1=0.0, theta2=360.0, **kwargs):
  1271. """
  1272. Parameters
  1273. ----------
  1274. xy : (float, float)
  1275. The center of the ellipse.
  1276. width : float
  1277. The length of the horizontal axis.
  1278. height : float
  1279. The length of the vertical axis.
  1280. angle : float
  1281. Rotation of the ellipse in degrees (anti-clockwise).
  1282. theta1, theta2 : float, optional
  1283. Starting and ending angles of the arc in degrees. These values
  1284. are relative to *angle*, .e.g. if *angle* = 45 and *theta1* = 90
  1285. the absolute starting angle is 135.
  1286. Default *theta1* = 0, *theta2* = 360, i.e. a complete ellipse.
  1287. Other Parameters
  1288. ----------------
  1289. **kwargs : `.Patch` properties
  1290. Most `.Patch` properties are supported as keyword arguments,
  1291. with the exception of *fill* and *facecolor* because filling is
  1292. not supported.
  1293. %(Patch)s
  1294. """
  1295. fill = kwargs.setdefault('fill', False)
  1296. if fill:
  1297. raise ValueError("Arc objects can not be filled")
  1298. Ellipse.__init__(self, xy, width, height, angle, **kwargs)
  1299. self.theta1 = theta1
  1300. self.theta2 = theta2
  1301. @artist.allow_rasterization
  1302. def draw(self, renderer):
  1303. """
  1304. Draw the arc to the given *renderer*.
  1305. Notes
  1306. -----
  1307. Ellipses are normally drawn using an approximation that uses
  1308. eight cubic Bezier splines. The error of this approximation
  1309. is 1.89818e-6, according to this unverified source:
  1310. Lancaster, Don. *Approximating a Circle or an Ellipse Using
  1311. Four Bezier Cubic Splines.*
  1312. http://www.tinaja.com/glib/ellipse4.pdf
  1313. There is a use case where very large ellipses must be drawn
  1314. with very high accuracy, and it is too expensive to render the
  1315. entire ellipse with enough segments (either splines or line
  1316. segments). Therefore, in the case where either radius of the
  1317. ellipse is large enough that the error of the spline
  1318. approximation will be visible (greater than one pixel offset
  1319. from the ideal), a different technique is used.
  1320. In that case, only the visible parts of the ellipse are drawn,
  1321. with each visible arc using a fixed number of spline segments
  1322. (8). The algorithm proceeds as follows:
  1323. 1. The points where the ellipse intersects the axes bounding
  1324. box are located. (This is done be performing an inverse
  1325. transformation on the axes bbox such that it is relative
  1326. to the unit circle -- this makes the intersection
  1327. calculation much easier than doing rotated ellipse
  1328. intersection directly).
  1329. This uses the "line intersecting a circle" algorithm
  1330. from:
  1331. Vince, John. *Geometry for Computer Graphics: Formulae,
  1332. Examples & Proofs.* London: Springer-Verlag, 2005.
  1333. 2. The angles of each of the intersection points are
  1334. calculated.
  1335. 3. Proceeding counterclockwise starting in the positive
  1336. x-direction, each of the visible arc-segments between the
  1337. pairs of vertices are drawn using the Bezier arc
  1338. approximation technique implemented in
  1339. :meth:`matplotlib.path.Path.arc`.
  1340. """
  1341. if not hasattr(self, 'axes'):
  1342. raise RuntimeError('Arcs can only be used in Axes instances')
  1343. self._recompute_transform()
  1344. width = self.convert_xunits(self.width)
  1345. height = self.convert_yunits(self.height)
  1346. # If the width and height of ellipse are not equal, take into account
  1347. # stretching when calculating angles to draw between
  1348. def theta_stretch(theta, scale):
  1349. theta = np.deg2rad(theta)
  1350. x = np.cos(theta)
  1351. y = np.sin(theta)
  1352. return np.rad2deg(np.arctan2(scale * y, x))
  1353. theta1 = theta_stretch(self.theta1, width / height)
  1354. theta2 = theta_stretch(self.theta2, width / height)
  1355. # Get width and height in pixels
  1356. width, height = self.get_transform().transform_point((width, height))
  1357. inv_error = (1.0 / 1.89818e-6) * 0.5
  1358. if width < inv_error and height < inv_error:
  1359. self._path = Path.arc(theta1, theta2)
  1360. return Patch.draw(self, renderer)
  1361. def iter_circle_intersect_on_line(x0, y0, x1, y1):
  1362. dx = x1 - x0
  1363. dy = y1 - y0
  1364. dr2 = dx * dx + dy * dy
  1365. D = x0 * y1 - x1 * y0
  1366. D2 = D * D
  1367. discrim = dr2 - D2
  1368. # Single (tangential) intersection
  1369. if discrim == 0.0:
  1370. x = (D * dy) / dr2
  1371. y = (-D * dx) / dr2
  1372. yield x, y
  1373. elif discrim > 0.0:
  1374. # The definition of "sign" here is different from
  1375. # np.sign: we never want to get 0.0
  1376. if dy < 0.0:
  1377. sign_dy = -1.0
  1378. else:
  1379. sign_dy = 1.0
  1380. sqrt_discrim = np.sqrt(discrim)
  1381. for sign in (1., -1.):
  1382. x = (D * dy + sign * sign_dy * dx * sqrt_discrim) / dr2
  1383. y = (-D * dx + sign * np.abs(dy) * sqrt_discrim) / dr2
  1384. yield x, y
  1385. def iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
  1386. epsilon = 1e-9
  1387. if x1 < x0:
  1388. x0e, x1e = x1, x0
  1389. else:
  1390. x0e, x1e = x0, x1
  1391. if y1 < y0:
  1392. y0e, y1e = y1, y0
  1393. else:
  1394. y0e, y1e = y0, y1
  1395. x0e -= epsilon
  1396. y0e -= epsilon
  1397. x1e += epsilon
  1398. y1e += epsilon
  1399. for x, y in iter_circle_intersect_on_line(x0, y0, x1, y1):
  1400. if x0e <= x <= x1e and y0e <= y <= y1e:
  1401. yield x, y
  1402. # Transforms the axes box_path so that it is relative to the unit
  1403. # circle in the same way that it is relative to the desired
  1404. # ellipse.
  1405. box_path = Path.unit_rectangle()
  1406. box_path_transform = transforms.BboxTransformTo(self.axes.bbox) + \
  1407. self.get_transform().inverted()
  1408. box_path = box_path.transformed(box_path_transform)
  1409. thetas = set()
  1410. # For each of the point pairs, there is a line segment
  1411. for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]):
  1412. x0, y0 = p0
  1413. x1, y1 = p1
  1414. for x, y in iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
  1415. theta = np.arccos(x)
  1416. if y < 0:
  1417. theta = 2 * np.pi - theta
  1418. # Convert radians to angles
  1419. theta = np.rad2deg(theta)
  1420. if theta1 < theta < theta2:
  1421. thetas.add(theta)
  1422. thetas = sorted(thetas) + [theta2]
  1423. last_theta = theta1
  1424. theta1_rad = np.deg2rad(theta1)
  1425. inside = box_path.contains_point((np.cos(theta1_rad),
  1426. np.sin(theta1_rad)))
  1427. # save original path
  1428. path_original = self._path
  1429. for theta in thetas:
  1430. if inside:
  1431. self._path = Path.arc(last_theta, theta, 8)
  1432. Patch.draw(self, renderer)
  1433. inside = False
  1434. else:
  1435. inside = True
  1436. last_theta = theta
  1437. # restore original path
  1438. self._path = path_original
  1439. def bbox_artist(artist, renderer, props=None, fill=True):
  1440. """
  1441. This is a debug function to draw a rectangle around the bounding
  1442. box returned by
  1443. :meth:`~matplotlib.artist.Artist.get_window_extent` of an artist,
  1444. to test whether the artist is returning the correct bbox.
  1445. *props* is a dict of rectangle props with the additional property
  1446. 'pad' that sets the padding around the bbox in points.
  1447. """
  1448. if props is None:
  1449. props = {}
  1450. props = props.copy() # don't want to alter the pad externally
  1451. pad = props.pop('pad', 4)
  1452. pad = renderer.points_to_pixels(pad)
  1453. bbox = artist.get_window_extent(renderer)
  1454. l, b, w, h = bbox.bounds
  1455. l -= pad / 2.
  1456. b -= pad / 2.
  1457. w += pad
  1458. h += pad
  1459. r = Rectangle(xy=(l, b),
  1460. width=w,
  1461. height=h,
  1462. fill=fill,
  1463. )
  1464. r.set_transform(transforms.IdentityTransform())
  1465. r.set_clip_on(False)
  1466. r.update(props)
  1467. r.draw(renderer)
  1468. def draw_bbox(bbox, renderer, color='k', trans=None):
  1469. """
  1470. This is a debug function to draw a rectangle around the bounding
  1471. box returned by
  1472. :meth:`~matplotlib.artist.Artist.get_window_extent` of an artist,
  1473. to test whether the artist is returning the correct bbox.
  1474. """
  1475. l, b, w, h = bbox.bounds
  1476. r = Rectangle(xy=(l, b),
  1477. width=w,
  1478. height=h,
  1479. edgecolor=color,
  1480. fill=False,
  1481. )
  1482. if trans is not None:
  1483. r.set_transform(trans)
  1484. r.set_clip_on(False)
  1485. r.draw(renderer)
  1486. def _pprint_table(table, leadingspace=2):
  1487. """
  1488. Given the list of list of strings, return a string of REST table format.
  1489. """
  1490. col_len = [max(len(cell) for cell in column) for column in zip(*table)]
  1491. table_formatstr = ' '.join('=' * cl for cl in col_len)
  1492. lines = [
  1493. '',
  1494. table_formatstr,
  1495. ' '.join(cell.ljust(cl) for cell, cl in zip(table[0], col_len)),
  1496. table_formatstr,
  1497. *[' '.join(cell.ljust(cl) for cell, cl in zip(row, col_len))
  1498. for row in table[1:]],
  1499. table_formatstr,
  1500. '',
  1501. ]
  1502. return textwrap.indent('\n'.join(lines), ' ' * leadingspace)
  1503. def _pprint_styles(_styles):
  1504. """
  1505. A helper function for the _Style class. Given the dictionary of
  1506. {stylename: styleclass}, return a formatted string listing all the
  1507. styles. Used to update the documentation.
  1508. """
  1509. import inspect
  1510. _table = [["Class", "Name", "Attrs"]]
  1511. for name, cls in sorted(_styles.items()):
  1512. spec = inspect.getfullargspec(cls.__init__)
  1513. if spec.defaults:
  1514. argstr = ", ".join(map(
  1515. "{}={}".format, spec.args[-len(spec.defaults):], spec.defaults
  1516. ))
  1517. else:
  1518. argstr = 'None'
  1519. # adding ``quotes`` since - and | have special meaning in reST
  1520. _table.append([cls.__name__, "``%s``" % name, argstr])
  1521. return _pprint_table(_table)
  1522. def _simpleprint_styles(_styles):
  1523. """
  1524. A helper function for the _Style class. Given the dictionary of
  1525. {stylename: styleclass}, return a string rep of the list of keys.
  1526. Used to update the documentation.
  1527. """
  1528. return "[{}]".format("|".join(map(" '{}' ".format, sorted(_styles))))
  1529. class _Style(object):
  1530. """
  1531. A base class for the Styles. It is meant to be a container class,
  1532. where actual styles are declared as subclass of it, and it
  1533. provides some helper functions.
  1534. """
  1535. def __new__(cls, stylename, **kw):
  1536. """
  1537. return the instance of the subclass with the given style name.
  1538. """
  1539. # The "class" should have the _style_list attribute, which is a mapping
  1540. # of style names to style classes.
  1541. _list = stylename.replace(" ", "").split(",")
  1542. _name = _list[0].lower()
  1543. try:
  1544. _cls = cls._style_list[_name]
  1545. except KeyError:
  1546. raise ValueError("Unknown style : %s" % stylename)
  1547. try:
  1548. _args_pair = [cs.split("=") for cs in _list[1:]]
  1549. _args = {k: float(v) for k, v in _args_pair}
  1550. except ValueError:
  1551. raise ValueError("Incorrect style argument : %s" % stylename)
  1552. _args.update(kw)
  1553. return _cls(**_args)
  1554. @classmethod
  1555. def get_styles(cls):
  1556. """
  1557. A class method which returns a dictionary of available styles.
  1558. """
  1559. return cls._style_list
  1560. @classmethod
  1561. def pprint_styles(cls):
  1562. """
  1563. A class method which returns a string of the available styles.
  1564. """
  1565. return _pprint_styles(cls._style_list)
  1566. @classmethod
  1567. def register(cls, name, style):
  1568. """
  1569. Register a new style.
  1570. """
  1571. if not issubclass(style, cls._Base):
  1572. raise ValueError("%s must be a subclass of %s" % (style,
  1573. cls._Base))
  1574. cls._style_list[name] = style
  1575. def _register_style(style_list, cls=None, *, name=None):
  1576. """Class decorator that stashes a class in a (style) dictionary."""
  1577. if cls is None:
  1578. return functools.partial(_register_style, style_list, name=name)
  1579. style_list[name or cls.__name__.lower()] = cls
  1580. return cls
  1581. class BoxStyle(_Style):
  1582. """
  1583. :class:`BoxStyle` is a container class which defines several
  1584. boxstyle classes, which are used for :class:`FancyBboxPatch`.
  1585. A style object can be created as::
  1586. BoxStyle.Round(pad=0.2)
  1587. or::
  1588. BoxStyle("Round", pad=0.2)
  1589. or::
  1590. BoxStyle("Round, pad=0.2")
  1591. Following boxstyle classes are defined.
  1592. %(AvailableBoxstyles)s
  1593. An instance of any boxstyle class is an callable object,
  1594. whose call signature is::
  1595. __call__(self, x0, y0, width, height, mutation_size, aspect_ratio=1.)
  1596. and returns a :class:`Path` instance. *x0*, *y0*, *width* and
  1597. *height* specify the location and size of the box to be
  1598. drawn. *mutation_scale* determines the overall size of the
  1599. mutation (by which I mean the transformation of the rectangle to
  1600. the fancy box). *mutation_aspect* determines the aspect-ratio of
  1601. the mutation.
  1602. """
  1603. _style_list = {}
  1604. class _Base(object):
  1605. """
  1606. :class:`BBoxTransmuterBase` and its derivatives are used to make a
  1607. fancy box around a given rectangle. The :meth:`__call__` method
  1608. returns the :class:`~matplotlib.path.Path` of the fancy box. This
  1609. class is not an artist and actual drawing of the fancy box is done
  1610. by the :class:`FancyBboxPatch` class.
  1611. """
  1612. # The derived classes are required to be able to be initialized
  1613. # w/o arguments, i.e., all its argument (except self) must have
  1614. # the default values.
  1615. def transmute(self, x0, y0, width, height, mutation_size):
  1616. """
  1617. The transmute method is a very core of the
  1618. :class:`BboxTransmuter` class and must be overridden in the
  1619. subclasses. It receives the location and size of the
  1620. rectangle, and the mutation_size, with which the amount of
  1621. padding and etc. will be scaled. It returns a
  1622. :class:`~matplotlib.path.Path` instance.
  1623. """
  1624. raise NotImplementedError('Derived must override')
  1625. def __call__(self, x0, y0, width, height, mutation_size,
  1626. aspect_ratio=1.):
  1627. """
  1628. Given the location and size of the box, return the path of
  1629. the box around it.
  1630. - *x0*, *y0*, *width*, *height* : location and size of the box
  1631. - *mutation_size* : a reference scale for the mutation.
  1632. - *aspect_ratio* : aspect-ration for the mutation.
  1633. """
  1634. # The __call__ method is a thin wrapper around the transmute method
  1635. # and takes care of the aspect.
  1636. if aspect_ratio is not None:
  1637. # Squeeze the given height by the aspect_ratio
  1638. y0, height = y0 / aspect_ratio, height / aspect_ratio
  1639. # call transmute method with squeezed height.
  1640. path = self.transmute(x0, y0, width, height, mutation_size)
  1641. vertices, codes = path.vertices, path.codes
  1642. # Restore the height
  1643. vertices[:, 1] = vertices[:, 1] * aspect_ratio
  1644. return Path(vertices, codes)
  1645. else:
  1646. return self.transmute(x0, y0, width, height, mutation_size)
  1647. @_register_style(_style_list)
  1648. class Square(_Base):
  1649. """
  1650. A simple square box.
  1651. """
  1652. def __init__(self, pad=0.3):
  1653. """
  1654. *pad*
  1655. amount of padding
  1656. """
  1657. self.pad = pad
  1658. super().__init__()
  1659. def transmute(self, x0, y0, width, height, mutation_size):
  1660. pad = mutation_size * self.pad
  1661. # width and height with padding added.
  1662. width, height = width + 2*pad, height + 2*pad
  1663. # boundary of the padded box
  1664. x0, y0 = x0 - pad, y0 - pad,
  1665. x1, y1 = x0 + width, y0 + height
  1666. vertices = [(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)]
  1667. codes = [Path.MOVETO] + [Path.LINETO] * 3 + [Path.CLOSEPOLY]
  1668. return Path(vertices, codes)
  1669. @_register_style(_style_list)
  1670. class Circle(_Base):
  1671. """A simple circle box."""
  1672. def __init__(self, pad=0.3):
  1673. """
  1674. Parameters
  1675. ----------
  1676. pad : float
  1677. The amount of padding around the original box.
  1678. """
  1679. self.pad = pad
  1680. super().__init__()
  1681. def transmute(self, x0, y0, width, height, mutation_size):
  1682. pad = mutation_size * self.pad
  1683. width, height = width + 2 * pad, height + 2 * pad
  1684. # boundary of the padded box
  1685. x0, y0 = x0 - pad, y0 - pad,
  1686. return Path.circle((x0 + width / 2, y0 + height / 2),
  1687. max(width, height) / 2)
  1688. @_register_style(_style_list)
  1689. class LArrow(_Base):
  1690. """
  1691. (left) Arrow Box
  1692. """
  1693. def __init__(self, pad=0.3):
  1694. self.pad = pad
  1695. super().__init__()
  1696. def transmute(self, x0, y0, width, height, mutation_size):
  1697. # padding
  1698. pad = mutation_size * self.pad
  1699. # width and height with padding added.
  1700. width, height = width + 2. * pad, height + 2. * pad
  1701. # boundary of the padded box
  1702. x0, y0 = x0 - pad, y0 - pad,
  1703. x1, y1 = x0 + width, y0 + height
  1704. dx = (y1 - y0) / 2.
  1705. dxx = dx * .5
  1706. # adjust x0. 1.4 <- sqrt(2)
  1707. x0 = x0 + pad / 1.4
  1708. cp = [(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1),
  1709. (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
  1710. (x0 + dxx, y0 - dxx), # arrow
  1711. (x0 + dxx, y0), (x0 + dxx, y0)]
  1712. com = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO,
  1713. Path.LINETO, Path.LINETO, Path.LINETO,
  1714. Path.LINETO, Path.CLOSEPOLY]
  1715. path = Path(cp, com)
  1716. return path
  1717. @_register_style(_style_list)
  1718. class RArrow(LArrow):
  1719. """
  1720. (right) Arrow Box
  1721. """
  1722. def __init__(self, pad=0.3):
  1723. super().__init__(pad)
  1724. def transmute(self, x0, y0, width, height, mutation_size):
  1725. p = BoxStyle.LArrow.transmute(self, x0, y0,
  1726. width, height, mutation_size)
  1727. p.vertices[:, 0] = 2 * x0 + width - p.vertices[:, 0]
  1728. return p
  1729. @_register_style(_style_list)
  1730. class DArrow(_Base):
  1731. """
  1732. (Double) Arrow Box
  1733. """
  1734. # This source is copied from LArrow,
  1735. # modified to add a right arrow to the bbox.
  1736. def __init__(self, pad=0.3):
  1737. self.pad = pad
  1738. super().__init__()
  1739. def transmute(self, x0, y0, width, height, mutation_size):
  1740. # padding
  1741. pad = mutation_size * self.pad
  1742. # width and height with padding added.
  1743. # The width is padded by the arrows, so we don't need to pad it.
  1744. height = height + 2. * pad
  1745. # boundary of the padded box
  1746. x0, y0 = x0 - pad, y0 - pad
  1747. x1, y1 = x0 + width, y0 + height
  1748. dx = (y1 - y0) / 2
  1749. dxx = dx * .5
  1750. # adjust x0. 1.4 <- sqrt(2)
  1751. x0 = x0 + pad / 1.4
  1752. cp = [(x0 + dxx, y0), (x1, y0), # bot-segment
  1753. (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx),
  1754. (x1, y1 + dxx), # right-arrow
  1755. (x1, y1), (x0 + dxx, y1), # top-segment
  1756. (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
  1757. (x0 + dxx, y0 - dxx), # left-arrow
  1758. (x0 + dxx, y0), (x0 + dxx, y0)] # close-poly
  1759. com = [Path.MOVETO, Path.LINETO,
  1760. Path.LINETO, Path.LINETO,
  1761. Path.LINETO,
  1762. Path.LINETO, Path.LINETO,
  1763. Path.LINETO, Path.LINETO,
  1764. Path.LINETO,
  1765. Path.LINETO, Path.CLOSEPOLY]
  1766. path = Path(cp, com)
  1767. return path
  1768. @_register_style(_style_list)
  1769. class Round(_Base):
  1770. """
  1771. A box with round corners.
  1772. """
  1773. def __init__(self, pad=0.3, rounding_size=None):
  1774. """
  1775. *pad*
  1776. amount of padding
  1777. *rounding_size*
  1778. rounding radius of corners. *pad* if None
  1779. """
  1780. self.pad = pad
  1781. self.rounding_size = rounding_size
  1782. super().__init__()
  1783. def transmute(self, x0, y0, width, height, mutation_size):
  1784. # padding
  1785. pad = mutation_size * self.pad
  1786. # size of the rounding corner
  1787. if self.rounding_size:
  1788. dr = mutation_size * self.rounding_size
  1789. else:
  1790. dr = pad
  1791. width, height = width + 2. * pad, height + 2. * pad
  1792. x0, y0 = x0 - pad, y0 - pad,
  1793. x1, y1 = x0 + width, y0 + height
  1794. # Round corners are implemented as quadratic Bezier, e.g.,
  1795. # [(x0, y0-dr), (x0, y0), (x0+dr, y0)] for lower left corner.
  1796. cp = [(x0 + dr, y0),
  1797. (x1 - dr, y0),
  1798. (x1, y0), (x1, y0 + dr),
  1799. (x1, y1 - dr),
  1800. (x1, y1), (x1 - dr, y1),
  1801. (x0 + dr, y1),
  1802. (x0, y1), (x0, y1 - dr),
  1803. (x0, y0 + dr),
  1804. (x0, y0), (x0 + dr, y0),
  1805. (x0 + dr, y0)]
  1806. com = [Path.MOVETO,
  1807. Path.LINETO,
  1808. Path.CURVE3, Path.CURVE3,
  1809. Path.LINETO,
  1810. Path.CURVE3, Path.CURVE3,
  1811. Path.LINETO,
  1812. Path.CURVE3, Path.CURVE3,
  1813. Path.LINETO,
  1814. Path.CURVE3, Path.CURVE3,
  1815. Path.CLOSEPOLY]
  1816. path = Path(cp, com)
  1817. return path
  1818. @_register_style(_style_list)
  1819. class Round4(_Base):
  1820. """
  1821. Another box with round edges.
  1822. """
  1823. def __init__(self, pad=0.3, rounding_size=None):
  1824. """
  1825. *pad*
  1826. amount of padding
  1827. *rounding_size*
  1828. rounding size of edges. *pad* if None
  1829. """
  1830. self.pad = pad
  1831. self.rounding_size = rounding_size
  1832. super().__init__()
  1833. def transmute(self, x0, y0, width, height, mutation_size):
  1834. # padding
  1835. pad = mutation_size * self.pad
  1836. # Rounding size; defaults to half of the padding.
  1837. if self.rounding_size:
  1838. dr = mutation_size * self.rounding_size
  1839. else:
  1840. dr = pad / 2.
  1841. width, height = (width + 2. * pad - 2 * dr,
  1842. height + 2. * pad - 2 * dr)
  1843. x0, y0 = x0 - pad + dr, y0 - pad + dr,
  1844. x1, y1 = x0 + width, y0 + height
  1845. cp = [(x0, y0),
  1846. (x0 + dr, y0 - dr), (x1 - dr, y0 - dr), (x1, y0),
  1847. (x1 + dr, y0 + dr), (x1 + dr, y1 - dr), (x1, y1),
  1848. (x1 - dr, y1 + dr), (x0 + dr, y1 + dr), (x0, y1),
  1849. (x0 - dr, y1 - dr), (x0 - dr, y0 + dr), (x0, y0),
  1850. (x0, y0)]
  1851. com = [Path.MOVETO,
  1852. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  1853. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  1854. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  1855. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  1856. Path.CLOSEPOLY]
  1857. path = Path(cp, com)
  1858. return path
  1859. @_register_style(_style_list)
  1860. class Sawtooth(_Base):
  1861. """
  1862. A sawtooth box.
  1863. """
  1864. def __init__(self, pad=0.3, tooth_size=None):
  1865. """
  1866. *pad*
  1867. amount of padding
  1868. *tooth_size*
  1869. size of the sawtooth. pad* if None
  1870. """
  1871. self.pad = pad
  1872. self.tooth_size = tooth_size
  1873. super().__init__()
  1874. def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size):
  1875. # padding
  1876. pad = mutation_size * self.pad
  1877. # size of sawtooth
  1878. if self.tooth_size is None:
  1879. tooth_size = self.pad * .5 * mutation_size
  1880. else:
  1881. tooth_size = self.tooth_size * mutation_size
  1882. tooth_size2 = tooth_size / 2.
  1883. width, height = (width + 2. * pad - tooth_size,
  1884. height + 2. * pad - tooth_size)
  1885. # the sizes of the vertical and horizontal sawtooth are
  1886. # separately adjusted to fit the given box size.
  1887. dsx_n = int(np.round((width - tooth_size) / (tooth_size * 2))) * 2
  1888. dsx = (width - tooth_size) / dsx_n
  1889. dsy_n = int(np.round((height - tooth_size) / (tooth_size * 2))) * 2
  1890. dsy = (height - tooth_size) / dsy_n
  1891. x0, y0 = x0 - pad + tooth_size2, y0 - pad + tooth_size2
  1892. x1, y1 = x0 + width, y0 + height
  1893. bottom_saw_x = [
  1894. x0,
  1895. *(x0 + tooth_size2 + dsx * .5 * np.arange(dsx_n * 2)),
  1896. x1 - tooth_size2,
  1897. ]
  1898. bottom_saw_y = [
  1899. y0,
  1900. *([y0 - tooth_size2, y0, y0 + tooth_size2, y0] * dsx_n),
  1901. y0 - tooth_size2,
  1902. ]
  1903. right_saw_x = [
  1904. x1,
  1905. *([x1 + tooth_size2, x1, x1 - tooth_size2, x1] * dsx_n),
  1906. x1 + tooth_size2,
  1907. ]
  1908. right_saw_y = [
  1909. y0,
  1910. *(y0 + tooth_size2 + dsy * .5 * np.arange(dsy_n * 2)),
  1911. y1 - tooth_size2,
  1912. ]
  1913. top_saw_x = [
  1914. x1,
  1915. *(x1 - tooth_size2 - dsx * .5 * np.arange(dsx_n * 2)),
  1916. x0 + tooth_size2,
  1917. ]
  1918. top_saw_y = [
  1919. y1,
  1920. *([y1 + tooth_size2, y1, y1 - tooth_size2, y1] * dsx_n),
  1921. y1 + tooth_size2,
  1922. ]
  1923. left_saw_x = [
  1924. x0,
  1925. *([x0 - tooth_size2, x0, x0 + tooth_size2, x0] * dsy_n),
  1926. x0 - tooth_size2,
  1927. ]
  1928. left_saw_y = [
  1929. y1,
  1930. *(y1 - tooth_size2 - dsy * .5 * np.arange(dsy_n * 2)),
  1931. y0 + tooth_size2,
  1932. ]
  1933. saw_vertices = [*zip(bottom_saw_x, bottom_saw_y),
  1934. *zip(right_saw_x, right_saw_y),
  1935. *zip(top_saw_x, top_saw_y),
  1936. *zip(left_saw_x, left_saw_y),
  1937. (bottom_saw_x[0], bottom_saw_y[0])]
  1938. return saw_vertices
  1939. def transmute(self, x0, y0, width, height, mutation_size):
  1940. saw_vertices = self._get_sawtooth_vertices(x0, y0, width,
  1941. height, mutation_size)
  1942. path = Path(saw_vertices, closed=True)
  1943. return path
  1944. @_register_style(_style_list)
  1945. class Roundtooth(Sawtooth):
  1946. """A rounded tooth box."""
  1947. def __init__(self, pad=0.3, tooth_size=None):
  1948. """
  1949. *pad*
  1950. amount of padding
  1951. *tooth_size*
  1952. size of the sawtooth. pad* if None
  1953. """
  1954. super().__init__(pad, tooth_size)
  1955. def transmute(self, x0, y0, width, height, mutation_size):
  1956. saw_vertices = self._get_sawtooth_vertices(x0, y0,
  1957. width, height,
  1958. mutation_size)
  1959. # Add a trailing vertex to allow us to close the polygon correctly
  1960. saw_vertices = np.concatenate([np.array(saw_vertices),
  1961. [saw_vertices[0]]], axis=0)
  1962. codes = ([Path.MOVETO] +
  1963. [Path.CURVE3, Path.CURVE3] * ((len(saw_vertices)-1)//2) +
  1964. [Path.CLOSEPOLY])
  1965. return Path(saw_vertices, codes)
  1966. if __doc__: # __doc__ could be None if -OO optimization is enabled
  1967. __doc__ = cbook.dedent(__doc__) % \
  1968. {"AvailableBoxstyles": _pprint_styles(_style_list)}
  1969. docstring.interpd.update(
  1970. AvailableBoxstyles=_pprint_styles(BoxStyle._style_list),
  1971. ListBoxstyles=_simpleprint_styles(BoxStyle._style_list))
  1972. class FancyBboxPatch(Patch):
  1973. """
  1974. Draw a fancy box around a rectangle with lower left at *xy*=(*x*,
  1975. *y*) with specified width and height.
  1976. :class:`FancyBboxPatch` class is similar to :class:`Rectangle`
  1977. class, but it draws a fancy box around the rectangle. The
  1978. transformation of the rectangle box to the fancy box is delegated
  1979. to the :class:`BoxTransmuterBase` and its derived classes.
  1980. """
  1981. _edge_default = True
  1982. def __str__(self):
  1983. s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)"
  1984. return s % (self._x, self._y, self._width, self._height)
  1985. @docstring.dedent_interpd
  1986. def __init__(self, xy, width, height,
  1987. boxstyle="round",
  1988. bbox_transmuter=None,
  1989. mutation_scale=1.,
  1990. mutation_aspect=None,
  1991. **kwargs):
  1992. """
  1993. *xy* = lower left corner
  1994. *width*, *height*
  1995. *boxstyle* determines what kind of fancy box will be drawn. It
  1996. can be a string of the style name with a comma separated
  1997. attribute, or an instance of :class:`BoxStyle`. Following box
  1998. styles are available.
  1999. %(AvailableBoxstyles)s
  2000. *mutation_scale* : a value with which attributes of boxstyle
  2001. (e.g., pad) will be scaled. default=1.
  2002. *mutation_aspect* : The height of the rectangle will be
  2003. squeezed by this value before the mutation and the mutated
  2004. box will be stretched by the inverse of it. default=None.
  2005. Valid kwargs are:
  2006. %(Patch)s
  2007. """
  2008. Patch.__init__(self, **kwargs)
  2009. self._x = xy[0]
  2010. self._y = xy[1]
  2011. self._width = width
  2012. self._height = height
  2013. if boxstyle == "custom":
  2014. if bbox_transmuter is None:
  2015. raise ValueError("bbox_transmuter argument is needed with "
  2016. "custom boxstyle")
  2017. self._bbox_transmuter = bbox_transmuter
  2018. else:
  2019. self.set_boxstyle(boxstyle)
  2020. self._mutation_scale = mutation_scale
  2021. self._mutation_aspect = mutation_aspect
  2022. self.stale = True
  2023. @docstring.dedent_interpd
  2024. def set_boxstyle(self, boxstyle=None, **kw):
  2025. """
  2026. Set the box style.
  2027. *boxstyle* can be a string with boxstyle name with optional
  2028. comma-separated attributes. Alternatively, the attrs can
  2029. be provided as keywords::
  2030. set_boxstyle("round,pad=0.2")
  2031. set_boxstyle("round", pad=0.2)
  2032. Old attrs simply are forgotten.
  2033. Without argument (or with *boxstyle* = None), it returns
  2034. available box styles.
  2035. The following boxstyles are available:
  2036. %(AvailableBoxstyles)s
  2037. ACCEPTS: %(ListBoxstyles)s
  2038. """
  2039. if boxstyle is None:
  2040. return BoxStyle.pprint_styles()
  2041. if isinstance(boxstyle, BoxStyle._Base) or callable(boxstyle):
  2042. self._bbox_transmuter = boxstyle
  2043. else:
  2044. self._bbox_transmuter = BoxStyle(boxstyle, **kw)
  2045. self.stale = True
  2046. def set_mutation_scale(self, scale):
  2047. """
  2048. Set the mutation scale.
  2049. Parameters
  2050. ----------
  2051. scale : float
  2052. """
  2053. self._mutation_scale = scale
  2054. self.stale = True
  2055. def get_mutation_scale(self):
  2056. """
  2057. Return the mutation scale.
  2058. """
  2059. return self._mutation_scale
  2060. def set_mutation_aspect(self, aspect):
  2061. """
  2062. Set the aspect ratio of the bbox mutation.
  2063. Parameters
  2064. ----------
  2065. aspect : float
  2066. """
  2067. self._mutation_aspect = aspect
  2068. self.stale = True
  2069. def get_mutation_aspect(self):
  2070. """
  2071. Return the aspect ratio of the bbox mutation.
  2072. """
  2073. return self._mutation_aspect
  2074. def get_boxstyle(self):
  2075. "Return the boxstyle object"
  2076. return self._bbox_transmuter
  2077. def get_path(self):
  2078. """
  2079. Return the mutated path of the rectangle
  2080. """
  2081. _path = self.get_boxstyle()(self._x, self._y,
  2082. self._width, self._height,
  2083. self.get_mutation_scale(),
  2084. self.get_mutation_aspect())
  2085. return _path
  2086. # Following methods are borrowed from the Rectangle class.
  2087. def get_x(self):
  2088. "Return the left coord of the rectangle"
  2089. return self._x
  2090. def get_y(self):
  2091. "Return the bottom coord of the rectangle"
  2092. return self._y
  2093. def get_width(self):
  2094. "Return the width of the rectangle"
  2095. return self._width
  2096. def get_height(self):
  2097. "Return the height of the rectangle"
  2098. return self._height
  2099. def set_x(self, x):
  2100. """
  2101. Set the left coord of the rectangle.
  2102. Parameters
  2103. ----------
  2104. x : float
  2105. """
  2106. self._x = x
  2107. self.stale = True
  2108. def set_y(self, y):
  2109. """
  2110. Set the bottom coord of the rectangle.
  2111. Parameters
  2112. ----------
  2113. y : float
  2114. """
  2115. self._y = y
  2116. self.stale = True
  2117. def set_width(self, w):
  2118. """
  2119. Set the rectangle width.
  2120. Parameters
  2121. ----------
  2122. w : float
  2123. """
  2124. self._width = w
  2125. self.stale = True
  2126. def set_height(self, h):
  2127. """
  2128. Set the rectangle height.
  2129. Parameters
  2130. ----------
  2131. h : float
  2132. """
  2133. self._height = h
  2134. self.stale = True
  2135. def set_bounds(self, *args):
  2136. """
  2137. Set the bounds of the rectangle: l,b,w,h
  2138. ACCEPTS: (left, bottom, width, height)
  2139. """
  2140. if len(args) == 1:
  2141. l, b, w, h = args[0]
  2142. else:
  2143. l, b, w, h = args
  2144. self._x = l
  2145. self._y = b
  2146. self._width = w
  2147. self._height = h
  2148. self.stale = True
  2149. def get_bbox(self):
  2150. return transforms.Bbox.from_bounds(self._x, self._y,
  2151. self._width, self._height)
  2152. class ConnectionStyle(_Style):
  2153. """
  2154. :class:`ConnectionStyle` is a container class which defines
  2155. several connectionstyle classes, which is used to create a path
  2156. between two points. These are mainly used with
  2157. :class:`FancyArrowPatch`.
  2158. A connectionstyle object can be either created as::
  2159. ConnectionStyle.Arc3(rad=0.2)
  2160. or::
  2161. ConnectionStyle("Arc3", rad=0.2)
  2162. or::
  2163. ConnectionStyle("Arc3, rad=0.2")
  2164. The following classes are defined
  2165. %(AvailableConnectorstyles)s
  2166. An instance of any connection style class is an callable object,
  2167. whose call signature is::
  2168. __call__(self, posA, posB,
  2169. patchA=None, patchB=None,
  2170. shrinkA=2., shrinkB=2.)
  2171. and it returns a :class:`Path` instance. *posA* and *posB* are
  2172. tuples of x,y coordinates of the two points to be
  2173. connected. *patchA* (or *patchB*) is given, the returned path is
  2174. clipped so that it start (or end) from the boundary of the
  2175. patch. The path is further shrunk by *shrinkA* (or *shrinkB*)
  2176. which is given in points.
  2177. """
  2178. _style_list = {}
  2179. class _Base(object):
  2180. """
  2181. A base class for connectionstyle classes. The subclass needs
  2182. to implement a *connect* method whose call signature is::
  2183. connect(posA, posB)
  2184. where posA and posB are tuples of x, y coordinates to be
  2185. connected. The method needs to return a path connecting two
  2186. points. This base class defines a __call__ method, and a few
  2187. helper methods.
  2188. """
  2189. class SimpleEvent:
  2190. def __init__(self, xy):
  2191. self.x, self.y = xy
  2192. def _clip(self, path, patchA, patchB):
  2193. """
  2194. Clip the path to the boundary of the patchA and patchB.
  2195. The starting point of the path needed to be inside of the
  2196. patchA and the end point inside the patch B. The *contains*
  2197. methods of each patch object is utilized to test if the point
  2198. is inside the path.
  2199. """
  2200. if patchA:
  2201. def insideA(xy_display):
  2202. xy_event = ConnectionStyle._Base.SimpleEvent(xy_display)
  2203. return patchA.contains(xy_event)[0]
  2204. try:
  2205. left, right = split_path_inout(path, insideA)
  2206. except ValueError:
  2207. right = path
  2208. path = right
  2209. if patchB:
  2210. def insideB(xy_display):
  2211. xy_event = ConnectionStyle._Base.SimpleEvent(xy_display)
  2212. return patchB.contains(xy_event)[0]
  2213. try:
  2214. left, right = split_path_inout(path, insideB)
  2215. except ValueError:
  2216. left = path
  2217. path = left
  2218. return path
  2219. def _shrink(self, path, shrinkA, shrinkB):
  2220. """
  2221. Shrink the path by fixed size (in points) with shrinkA and shrinkB
  2222. """
  2223. if shrinkA:
  2224. x, y = path.vertices[0]
  2225. insideA = inside_circle(x, y, shrinkA)
  2226. try:
  2227. left, right = split_path_inout(path, insideA)
  2228. path = right
  2229. except ValueError:
  2230. pass
  2231. if shrinkB:
  2232. x, y = path.vertices[-1]
  2233. insideB = inside_circle(x, y, shrinkB)
  2234. try:
  2235. left, right = split_path_inout(path, insideB)
  2236. path = left
  2237. except ValueError:
  2238. pass
  2239. return path
  2240. def __call__(self, posA, posB,
  2241. shrinkA=2., shrinkB=2., patchA=None, patchB=None):
  2242. """
  2243. Calls the *connect* method to create a path between *posA*
  2244. and *posB*. The path is clipped and shrunken.
  2245. """
  2246. path = self.connect(posA, posB)
  2247. clipped_path = self._clip(path, patchA, patchB)
  2248. shrunk_path = self._shrink(clipped_path, shrinkA, shrinkB)
  2249. return shrunk_path
  2250. @_register_style(_style_list)
  2251. class Arc3(_Base):
  2252. """
  2253. Creates a simple quadratic Bezier curve between two
  2254. points. The curve is created so that the middle control point
  2255. (C1) is located at the same distance from the start (C0) and
  2256. end points(C2) and the distance of the C1 to the line
  2257. connecting C0-C2 is *rad* times the distance of C0-C2.
  2258. """
  2259. def __init__(self, rad=0.):
  2260. """
  2261. *rad*
  2262. curvature of the curve.
  2263. """
  2264. self.rad = rad
  2265. def connect(self, posA, posB):
  2266. x1, y1 = posA
  2267. x2, y2 = posB
  2268. x12, y12 = (x1 + x2) / 2., (y1 + y2) / 2.
  2269. dx, dy = x2 - x1, y2 - y1
  2270. f = self.rad
  2271. cx, cy = x12 + f * dy, y12 - f * dx
  2272. vertices = [(x1, y1),
  2273. (cx, cy),
  2274. (x2, y2)]
  2275. codes = [Path.MOVETO,
  2276. Path.CURVE3,
  2277. Path.CURVE3]
  2278. return Path(vertices, codes)
  2279. @_register_style(_style_list)
  2280. class Angle3(_Base):
  2281. """
  2282. Creates a simple quadratic Bezier curve between two
  2283. points. The middle control points is placed at the
  2284. intersecting point of two lines which cross the start and
  2285. end point, and have a slope of angleA and angleB, respectively.
  2286. """
  2287. def __init__(self, angleA=90, angleB=0):
  2288. """
  2289. *angleA*
  2290. starting angle of the path
  2291. *angleB*
  2292. ending angle of the path
  2293. """
  2294. self.angleA = angleA
  2295. self.angleB = angleB
  2296. def connect(self, posA, posB):
  2297. x1, y1 = posA
  2298. x2, y2 = posB
  2299. cosA = math.cos(math.radians(self.angleA))
  2300. sinA = math.sin(math.radians(self.angleA))
  2301. cosB = math.cos(math.radians(self.angleB))
  2302. sinB = math.sin(math.radians(self.angleB))
  2303. cx, cy = get_intersection(x1, y1, cosA, sinA,
  2304. x2, y2, cosB, sinB)
  2305. vertices = [(x1, y1), (cx, cy), (x2, y2)]
  2306. codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
  2307. return Path(vertices, codes)
  2308. @_register_style(_style_list)
  2309. class Angle(_Base):
  2310. """
  2311. Creates a piecewise continuous quadratic Bezier path between
  2312. two points. The path has a one passing-through point placed at
  2313. the intersecting point of two lines which cross the start
  2314. and end point, and have a slope of angleA and angleB, respectively.
  2315. The connecting edges are rounded with *rad*.
  2316. """
  2317. def __init__(self, angleA=90, angleB=0, rad=0.):
  2318. """
  2319. *angleA*
  2320. starting angle of the path
  2321. *angleB*
  2322. ending angle of the path
  2323. *rad*
  2324. rounding radius of the edge
  2325. """
  2326. self.angleA = angleA
  2327. self.angleB = angleB
  2328. self.rad = rad
  2329. def connect(self, posA, posB):
  2330. x1, y1 = posA
  2331. x2, y2 = posB
  2332. cosA = math.cos(math.radians(self.angleA))
  2333. sinA = math.sin(math.radians(self.angleA))
  2334. cosB = math.cos(math.radians(self.angleB))
  2335. sinB = math.sin(math.radians(self.angleB))
  2336. cx, cy = get_intersection(x1, y1, cosA, sinA,
  2337. x2, y2, cosB, sinB)
  2338. vertices = [(x1, y1)]
  2339. codes = [Path.MOVETO]
  2340. if self.rad == 0.:
  2341. vertices.append((cx, cy))
  2342. codes.append(Path.LINETO)
  2343. else:
  2344. dx1, dy1 = x1 - cx, y1 - cy
  2345. d1 = (dx1 ** 2 + dy1 ** 2) ** .5
  2346. f1 = self.rad / d1
  2347. dx2, dy2 = x2 - cx, y2 - cy
  2348. d2 = (dx2 ** 2 + dy2 ** 2) ** .5
  2349. f2 = self.rad / d2
  2350. vertices.extend([(cx + dx1 * f1, cy + dy1 * f1),
  2351. (cx, cy),
  2352. (cx + dx2 * f2, cy + dy2 * f2)])
  2353. codes.extend([Path.LINETO, Path.CURVE3, Path.CURVE3])
  2354. vertices.append((x2, y2))
  2355. codes.append(Path.LINETO)
  2356. return Path(vertices, codes)
  2357. @_register_style(_style_list)
  2358. class Arc(_Base):
  2359. """
  2360. Creates a piecewise continuous quadratic Bezier path between
  2361. two points. The path can have two passing-through points, a
  2362. point placed at the distance of armA and angle of angleA from
  2363. point A, another point with respect to point B. The edges are
  2364. rounded with *rad*.
  2365. """
  2366. def __init__(self, angleA=0, angleB=0, armA=None, armB=None, rad=0.):
  2367. """
  2368. *angleA* :
  2369. starting angle of the path
  2370. *angleB* :
  2371. ending angle of the path
  2372. *armA* :
  2373. length of the starting arm
  2374. *armB* :
  2375. length of the ending arm
  2376. *rad* :
  2377. rounding radius of the edges
  2378. """
  2379. self.angleA = angleA
  2380. self.angleB = angleB
  2381. self.armA = armA
  2382. self.armB = armB
  2383. self.rad = rad
  2384. def connect(self, posA, posB):
  2385. x1, y1 = posA
  2386. x2, y2 = posB
  2387. vertices = [(x1, y1)]
  2388. rounded = []
  2389. codes = [Path.MOVETO]
  2390. if self.armA:
  2391. cosA = math.cos(math.radians(self.angleA))
  2392. sinA = math.sin(math.radians(self.angleA))
  2393. # x_armA, y_armB
  2394. d = self.armA - self.rad
  2395. rounded.append((x1 + d * cosA, y1 + d * sinA))
  2396. d = self.armA
  2397. rounded.append((x1 + d * cosA, y1 + d * sinA))
  2398. if self.armB:
  2399. cosB = math.cos(math.radians(self.angleB))
  2400. sinB = math.sin(math.radians(self.angleB))
  2401. x_armB, y_armB = x2 + self.armB * cosB, y2 + self.armB * sinB
  2402. if rounded:
  2403. xp, yp = rounded[-1]
  2404. dx, dy = x_armB - xp, y_armB - yp
  2405. dd = (dx * dx + dy * dy) ** .5
  2406. rounded.append((xp + self.rad * dx / dd,
  2407. yp + self.rad * dy / dd))
  2408. vertices.extend(rounded)
  2409. codes.extend([Path.LINETO,
  2410. Path.CURVE3,
  2411. Path.CURVE3])
  2412. else:
  2413. xp, yp = vertices[-1]
  2414. dx, dy = x_armB - xp, y_armB - yp
  2415. dd = (dx * dx + dy * dy) ** .5
  2416. d = dd - self.rad
  2417. rounded = [(xp + d * dx / dd, yp + d * dy / dd),
  2418. (x_armB, y_armB)]
  2419. if rounded:
  2420. xp, yp = rounded[-1]
  2421. dx, dy = x2 - xp, y2 - yp
  2422. dd = (dx * dx + dy * dy) ** .5
  2423. rounded.append((xp + self.rad * dx / dd,
  2424. yp + self.rad * dy / dd))
  2425. vertices.extend(rounded)
  2426. codes.extend([Path.LINETO,
  2427. Path.CURVE3,
  2428. Path.CURVE3])
  2429. vertices.append((x2, y2))
  2430. codes.append(Path.LINETO)
  2431. return Path(vertices, codes)
  2432. @_register_style(_style_list)
  2433. class Bar(_Base):
  2434. """
  2435. A line with *angle* between A and B with *armA* and
  2436. *armB*. One of the arms is extended so that they are connected in
  2437. a right angle. The length of armA is determined by (*armA*
  2438. + *fraction* x AB distance). Same for armB.
  2439. """
  2440. def __init__(self, armA=0., armB=0., fraction=0.3, angle=None):
  2441. """
  2442. Parameters
  2443. ----------
  2444. armA : float
  2445. minimum length of armA
  2446. armB : float
  2447. minimum length of armB
  2448. fraction : float
  2449. a fraction of the distance between two points that
  2450. will be added to armA and armB.
  2451. angle : float or None
  2452. angle of the connecting line (if None, parallel
  2453. to A and B)
  2454. """
  2455. self.armA = armA
  2456. self.armB = armB
  2457. self.fraction = fraction
  2458. self.angle = angle
  2459. def connect(self, posA, posB):
  2460. x1, y1 = posA
  2461. x20, y20 = x2, y2 = posB
  2462. theta1 = math.atan2(y2 - y1, x2 - x1)
  2463. dx, dy = x2 - x1, y2 - y1
  2464. dd = (dx * dx + dy * dy) ** .5
  2465. ddx, ddy = dx / dd, dy / dd
  2466. armA, armB = self.armA, self.armB
  2467. if self.angle is not None:
  2468. theta0 = np.deg2rad(self.angle)
  2469. dtheta = theta1 - theta0
  2470. dl = dd * math.sin(dtheta)
  2471. dL = dd * math.cos(dtheta)
  2472. x2, y2 = x1 + dL * math.cos(theta0), y1 + dL * math.sin(theta0)
  2473. armB = armB - dl
  2474. # update
  2475. dx, dy = x2 - x1, y2 - y1
  2476. dd2 = (dx * dx + dy * dy) ** .5
  2477. ddx, ddy = dx / dd2, dy / dd2
  2478. else:
  2479. dl = 0.
  2480. arm = max(armA, armB)
  2481. f = self.fraction * dd + arm
  2482. cx1, cy1 = x1 + f * ddy, y1 - f * ddx
  2483. cx2, cy2 = x2 + f * ddy, y2 - f * ddx
  2484. vertices = [(x1, y1),
  2485. (cx1, cy1),
  2486. (cx2, cy2),
  2487. (x20, y20)]
  2488. codes = [Path.MOVETO,
  2489. Path.LINETO,
  2490. Path.LINETO,
  2491. Path.LINETO]
  2492. return Path(vertices, codes)
  2493. if __doc__:
  2494. __doc__ = cbook.dedent(__doc__) % \
  2495. {"AvailableConnectorstyles": _pprint_styles(_style_list)}
  2496. def _point_along_a_line(x0, y0, x1, y1, d):
  2497. """
  2498. find a point along a line connecting (x0, y0) -- (x1, y1) whose
  2499. distance from (x0, y0) is d.
  2500. """
  2501. dx, dy = x0 - x1, y0 - y1
  2502. ff = d / (dx * dx + dy * dy) ** .5
  2503. x2, y2 = x0 - ff * dx, y0 - ff * dy
  2504. return x2, y2
  2505. class ArrowStyle(_Style):
  2506. """
  2507. :class:`ArrowStyle` is a container class which defines several
  2508. arrowstyle classes, which is used to create an arrow path along a
  2509. given path. These are mainly used with :class:`FancyArrowPatch`.
  2510. A arrowstyle object can be either created as::
  2511. ArrowStyle.Fancy(head_length=.4, head_width=.4, tail_width=.4)
  2512. or::
  2513. ArrowStyle("Fancy", head_length=.4, head_width=.4, tail_width=.4)
  2514. or::
  2515. ArrowStyle("Fancy, head_length=.4, head_width=.4, tail_width=.4")
  2516. The following classes are defined
  2517. %(AvailableArrowstyles)s
  2518. An instance of any arrow style class is a callable object,
  2519. whose call signature is::
  2520. __call__(self, path, mutation_size, linewidth, aspect_ratio=1.)
  2521. and it returns a tuple of a :class:`Path` instance and a boolean
  2522. value. *path* is a :class:`Path` instance along which the arrow
  2523. will be drawn. *mutation_size* and *aspect_ratio* have the same
  2524. meaning as in :class:`BoxStyle`. *linewidth* is a line width to be
  2525. stroked. This is meant to be used to correct the location of the
  2526. head so that it does not overshoot the destination point, but not all
  2527. classes support it.
  2528. """
  2529. _style_list = {}
  2530. class _Base(object):
  2531. """
  2532. Arrow Transmuter Base class
  2533. ArrowTransmuterBase and its derivatives are used to make a fancy
  2534. arrow around a given path. The __call__ method returns a path
  2535. (which will be used to create a PathPatch instance) and a boolean
  2536. value indicating the path is open therefore is not fillable. This
  2537. class is not an artist and actual drawing of the fancy arrow is
  2538. done by the FancyArrowPatch class.
  2539. """
  2540. # The derived classes are required to be able to be initialized
  2541. # w/o arguments, i.e., all its argument (except self) must have
  2542. # the default values.
  2543. @staticmethod
  2544. def ensure_quadratic_bezier(path):
  2545. """
  2546. Some ArrowStyle class only works with a simple quadratic Bezier
  2547. curve (created with Arc3Connetion or Angle3Connector). This static
  2548. method is to check if the provided path is a simple quadratic
  2549. Bezier curve and returns its control points if true.
  2550. """
  2551. segments = list(path.iter_segments())
  2552. if (len(segments) != 2 or segments[0][1] != Path.MOVETO or
  2553. segments[1][1] != Path.CURVE3):
  2554. raise ValueError(
  2555. "'path' is not a valid quadratic Bezier curve")
  2556. return [*segments[0][0], *segments[1][0]]
  2557. def transmute(self, path, mutation_size, linewidth):
  2558. """
  2559. The transmute method is the very core of the ArrowStyle class and
  2560. must be overridden in the subclasses. It receives the path object
  2561. along which the arrow will be drawn, and the mutation_size, with
  2562. which the arrow head etc. will be scaled. The linewidth may be
  2563. used to adjust the path so that it does not pass beyond the given
  2564. points. It returns a tuple of a Path instance and a boolean. The
  2565. boolean value indicate whether the path can be filled or not. The
  2566. return value can also be a list of paths and list of booleans of a
  2567. same length.
  2568. """
  2569. raise NotImplementedError('Derived must override')
  2570. def __call__(self, path, mutation_size, linewidth,
  2571. aspect_ratio=1.):
  2572. """
  2573. The __call__ method is a thin wrapper around the transmute method
  2574. and takes care of the aspect ratio.
  2575. """
  2576. path = make_path_regular(path)
  2577. if aspect_ratio is not None:
  2578. # Squeeze the given height by the aspect_ratio
  2579. vertices, codes = path.vertices[:], path.codes[:]
  2580. # Squeeze the height
  2581. vertices[:, 1] = vertices[:, 1] / aspect_ratio
  2582. path_shrunk = Path(vertices, codes)
  2583. # call transmute method with squeezed height.
  2584. path_mutated, fillable = self.transmute(path_shrunk,
  2585. linewidth,
  2586. mutation_size)
  2587. if cbook.iterable(fillable):
  2588. path_list = []
  2589. for p in zip(path_mutated):
  2590. v, c = p.vertices, p.codes
  2591. # Restore the height
  2592. v[:, 1] = v[:, 1] * aspect_ratio
  2593. path_list.append(Path(v, c))
  2594. return path_list, fillable
  2595. else:
  2596. return path_mutated, fillable
  2597. else:
  2598. return self.transmute(path, mutation_size, linewidth)
  2599. class _Curve(_Base):
  2600. """
  2601. A simple arrow which will work with any path instance. The
  2602. returned path is simply concatenation of the original path + at
  2603. most two paths representing the arrow head at the begin point and the
  2604. at the end point. The arrow heads can be either open or closed.
  2605. """
  2606. def __init__(self, beginarrow=None, endarrow=None,
  2607. fillbegin=False, fillend=False,
  2608. head_length=.2, head_width=.1):
  2609. """
  2610. The arrows are drawn if *beginarrow* and/or *endarrow* are
  2611. true. *head_length* and *head_width* determines the size
  2612. of the arrow relative to the *mutation scale*. The
  2613. arrowhead at the begin (or end) is closed if fillbegin (or
  2614. fillend) is True.
  2615. """
  2616. self.beginarrow, self.endarrow = beginarrow, endarrow
  2617. self.head_length, self.head_width = head_length, head_width
  2618. self.fillbegin, self.fillend = fillbegin, fillend
  2619. super().__init__()
  2620. def _get_arrow_wedge(self, x0, y0, x1, y1,
  2621. head_dist, cos_t, sin_t, linewidth
  2622. ):
  2623. """
  2624. Return the paths for arrow heads. Since arrow lines are
  2625. drawn with capstyle=projected, The arrow goes beyond the
  2626. desired point. This method also returns the amount of the path
  2627. to be shrunken so that it does not overshoot.
  2628. """
  2629. # arrow from x0, y0 to x1, y1
  2630. dx, dy = x0 - x1, y0 - y1
  2631. cp_distance = np.hypot(dx, dy)
  2632. # pad_projected : amount of pad to account the
  2633. # overshooting of the projection of the wedge
  2634. pad_projected = (.5 * linewidth / sin_t)
  2635. # Account for division by zero
  2636. if cp_distance == 0:
  2637. cp_distance = 1
  2638. # apply pad for projected edge
  2639. ddx = pad_projected * dx / cp_distance
  2640. ddy = pad_projected * dy / cp_distance
  2641. # offset for arrow wedge
  2642. dx = dx / cp_distance * head_dist
  2643. dy = dy / cp_distance * head_dist
  2644. dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy
  2645. dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy
  2646. vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1),
  2647. (x1 + ddx, y1 + ddy),
  2648. (x1 + ddx + dx2, y1 + ddy + dy2)]
  2649. codes_arrow = [Path.MOVETO,
  2650. Path.LINETO,
  2651. Path.LINETO]
  2652. return vertices_arrow, codes_arrow, ddx, ddy
  2653. def transmute(self, path, mutation_size, linewidth):
  2654. head_length = self.head_length * mutation_size
  2655. head_width = self.head_width * mutation_size
  2656. head_dist = math.sqrt(head_length ** 2 + head_width ** 2)
  2657. cos_t, sin_t = head_length / head_dist, head_width / head_dist
  2658. # begin arrow
  2659. x0, y0 = path.vertices[0]
  2660. x1, y1 = path.vertices[1]
  2661. # If there is no room for an arrow and a line, then skip the arrow
  2662. has_begin_arrow = self.beginarrow and not (x0 == x1 and y0 == y1)
  2663. if has_begin_arrow:
  2664. verticesA, codesA, ddxA, ddyA = \
  2665. self._get_arrow_wedge(x1, y1, x0, y0,
  2666. head_dist, cos_t, sin_t,
  2667. linewidth)
  2668. else:
  2669. verticesA, codesA = [], []
  2670. ddxA, ddyA = 0., 0.
  2671. # end arrow
  2672. x2, y2 = path.vertices[-2]
  2673. x3, y3 = path.vertices[-1]
  2674. # If there is no room for an arrow and a line, then skip the arrow
  2675. has_end_arrow = (self.endarrow and not (x2 == x3 and y2 == y3))
  2676. if has_end_arrow:
  2677. verticesB, codesB, ddxB, ddyB = \
  2678. self._get_arrow_wedge(x2, y2, x3, y3,
  2679. head_dist, cos_t, sin_t,
  2680. linewidth)
  2681. else:
  2682. verticesB, codesB = [], []
  2683. ddxB, ddyB = 0., 0.
  2684. # This simple code will not work if ddx, ddy is greater than the
  2685. # separation between vertices.
  2686. _path = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)],
  2687. path.vertices[1:-1],
  2688. [(x3 + ddxB, y3 + ddyB)]]),
  2689. path.codes)]
  2690. _fillable = [False]
  2691. if has_begin_arrow:
  2692. if self.fillbegin:
  2693. p = np.concatenate([verticesA, [verticesA[0],
  2694. verticesA[0]], ])
  2695. c = np.concatenate([codesA, [Path.LINETO, Path.CLOSEPOLY]])
  2696. _path.append(Path(p, c))
  2697. _fillable.append(True)
  2698. else:
  2699. _path.append(Path(verticesA, codesA))
  2700. _fillable.append(False)
  2701. if has_end_arrow:
  2702. if self.fillend:
  2703. _fillable.append(True)
  2704. p = np.concatenate([verticesB, [verticesB[0],
  2705. verticesB[0]], ])
  2706. c = np.concatenate([codesB, [Path.LINETO, Path.CLOSEPOLY]])
  2707. _path.append(Path(p, c))
  2708. else:
  2709. _fillable.append(False)
  2710. _path.append(Path(verticesB, codesB))
  2711. return _path, _fillable
  2712. @_register_style(_style_list, name="-")
  2713. class Curve(_Curve):
  2714. """
  2715. A simple curve without any arrow head.
  2716. """
  2717. def __init__(self):
  2718. super().__init__(beginarrow=False, endarrow=False)
  2719. @_register_style(_style_list, name="<-")
  2720. class CurveA(_Curve):
  2721. """
  2722. An arrow with a head at its begin point.
  2723. """
  2724. def __init__(self, head_length=.4, head_width=.2):
  2725. """
  2726. Parameters
  2727. ----------
  2728. head_length : float, optional, default : 0.4
  2729. Length of the arrow head
  2730. head_width : float, optional, default : 0.2
  2731. Width of the arrow head
  2732. """
  2733. super().__init__(beginarrow=True, endarrow=False,
  2734. head_length=head_length, head_width=head_width)
  2735. @_register_style(_style_list, name="->")
  2736. class CurveB(_Curve):
  2737. """
  2738. An arrow with a head at its end point.
  2739. """
  2740. def __init__(self, head_length=.4, head_width=.2):
  2741. """
  2742. Parameters
  2743. ----------
  2744. head_length : float, optional, default : 0.4
  2745. Length of the arrow head
  2746. head_width : float, optional, default : 0.2
  2747. Width of the arrow head
  2748. """
  2749. super().__init__(beginarrow=False, endarrow=True,
  2750. head_length=head_length, head_width=head_width)
  2751. @_register_style(_style_list, name="<->")
  2752. class CurveAB(_Curve):
  2753. """
  2754. An arrow with heads both at the begin and the end point.
  2755. """
  2756. def __init__(self, head_length=.4, head_width=.2):
  2757. """
  2758. Parameters
  2759. ----------
  2760. head_length : float, optional, default : 0.4
  2761. Length of the arrow head
  2762. head_width : float, optional, default : 0.2
  2763. Width of the arrow head
  2764. """
  2765. super().__init__(beginarrow=True, endarrow=True,
  2766. head_length=head_length, head_width=head_width)
  2767. @_register_style(_style_list, name="<|-")
  2768. class CurveFilledA(_Curve):
  2769. """
  2770. An arrow with filled triangle head at the begin.
  2771. """
  2772. def __init__(self, head_length=.4, head_width=.2):
  2773. """
  2774. Parameters
  2775. ----------
  2776. head_length : float, optional, default : 0.4
  2777. Length of the arrow head
  2778. head_width : float, optional, default : 0.2
  2779. Width of the arrow head
  2780. """
  2781. super().__init__(beginarrow=True, endarrow=False,
  2782. fillbegin=True, fillend=False,
  2783. head_length=head_length, head_width=head_width)
  2784. @_register_style(_style_list, name="-|>")
  2785. class CurveFilledB(_Curve):
  2786. """
  2787. An arrow with filled triangle head at the end.
  2788. """
  2789. def __init__(self, head_length=.4, head_width=.2):
  2790. """
  2791. Parameters
  2792. ----------
  2793. head_length : float, optional, default : 0.4
  2794. Length of the arrow head
  2795. head_width : float, optional, default : 0.2
  2796. Width of the arrow head
  2797. """
  2798. super().__init__(beginarrow=False, endarrow=True,
  2799. fillbegin=False, fillend=True,
  2800. head_length=head_length, head_width=head_width)
  2801. @_register_style(_style_list, name="<|-|>")
  2802. class CurveFilledAB(_Curve):
  2803. """
  2804. An arrow with filled triangle heads at both ends.
  2805. """
  2806. def __init__(self, head_length=.4, head_width=.2):
  2807. """
  2808. Parameters
  2809. ----------
  2810. head_length : float, optional, default : 0.4
  2811. Length of the arrow head
  2812. head_width : float, optional, default : 0.2
  2813. Width of the arrow head
  2814. """
  2815. super().__init__(beginarrow=True, endarrow=True,
  2816. fillbegin=True, fillend=True,
  2817. head_length=head_length, head_width=head_width)
  2818. class _Bracket(_Base):
  2819. def __init__(self, bracketA=None, bracketB=None,
  2820. widthA=1., widthB=1.,
  2821. lengthA=0.2, lengthB=0.2,
  2822. angleA=None, angleB=None,
  2823. scaleA=None, scaleB=None):
  2824. self.bracketA, self.bracketB = bracketA, bracketB
  2825. self.widthA, self.widthB = widthA, widthB
  2826. self.lengthA, self.lengthB = lengthA, lengthB
  2827. self.angleA, self.angleB = angleA, angleB
  2828. self.scaleA, self.scaleB = scaleA, scaleB
  2829. def _get_bracket(self, x0, y0,
  2830. cos_t, sin_t, width, length):
  2831. # arrow from x0, y0 to x1, y1
  2832. from matplotlib.bezier import get_normal_points
  2833. x1, y1, x2, y2 = get_normal_points(x0, y0, cos_t, sin_t, width)
  2834. dx, dy = length * cos_t, length * sin_t
  2835. vertices_arrow = [(x1 + dx, y1 + dy),
  2836. (x1, y1),
  2837. (x2, y2),
  2838. (x2 + dx, y2 + dy)]
  2839. codes_arrow = [Path.MOVETO,
  2840. Path.LINETO,
  2841. Path.LINETO,
  2842. Path.LINETO]
  2843. return vertices_arrow, codes_arrow
  2844. def transmute(self, path, mutation_size, linewidth):
  2845. if self.scaleA is None:
  2846. scaleA = mutation_size
  2847. else:
  2848. scaleA = self.scaleA
  2849. if self.scaleB is None:
  2850. scaleB = mutation_size
  2851. else:
  2852. scaleB = self.scaleB
  2853. vertices_list, codes_list = [], []
  2854. if self.bracketA:
  2855. x0, y0 = path.vertices[0]
  2856. x1, y1 = path.vertices[1]
  2857. cos_t, sin_t = get_cos_sin(x1, y1, x0, y0)
  2858. verticesA, codesA = self._get_bracket(x0, y0, cos_t, sin_t,
  2859. self.widthA * scaleA,
  2860. self.lengthA * scaleA)
  2861. vertices_list.append(verticesA)
  2862. codes_list.append(codesA)
  2863. vertices_list.append(path.vertices)
  2864. codes_list.append(path.codes)
  2865. if self.bracketB:
  2866. x0, y0 = path.vertices[-1]
  2867. x1, y1 = path.vertices[-2]
  2868. cos_t, sin_t = get_cos_sin(x1, y1, x0, y0)
  2869. verticesB, codesB = self._get_bracket(x0, y0, cos_t, sin_t,
  2870. self.widthB * scaleB,
  2871. self.lengthB * scaleB)
  2872. vertices_list.append(verticesB)
  2873. codes_list.append(codesB)
  2874. vertices = np.concatenate(vertices_list)
  2875. codes = np.concatenate(codes_list)
  2876. p = Path(vertices, codes)
  2877. return p, False
  2878. @_register_style(_style_list, name="]-[")
  2879. class BracketAB(_Bracket):
  2880. """
  2881. An arrow with a bracket(]) at both ends.
  2882. """
  2883. def __init__(self,
  2884. widthA=1., lengthA=0.2, angleA=None,
  2885. widthB=1., lengthB=0.2, angleB=None):
  2886. """
  2887. Parameters
  2888. ----------
  2889. widthA : float, optional, default : 1.0
  2890. Width of the bracket
  2891. lengthA : float, optional, default : 0.2
  2892. Length of the bracket
  2893. angleA : float, optional, default : None
  2894. Angle between the bracket and the line
  2895. widthB : float, optional, default : 1.0
  2896. Width of the bracket
  2897. lengthB : float, optional, default : 0.2
  2898. Length of the bracket
  2899. angleB : float, optional, default : None
  2900. Angle between the bracket and the line
  2901. """
  2902. super().__init__(True, True,
  2903. widthA=widthA, lengthA=lengthA, angleA=angleA,
  2904. widthB=widthB, lengthB=lengthB, angleB=angleB)
  2905. @_register_style(_style_list, name="]-")
  2906. class BracketA(_Bracket):
  2907. """
  2908. An arrow with a bracket(]) at its end.
  2909. """
  2910. def __init__(self, widthA=1., lengthA=0.2, angleA=None):
  2911. """
  2912. Parameters
  2913. ----------
  2914. widthA : float, optional, default : 1.0
  2915. Width of the bracket
  2916. lengthA : float, optional, default : 0.2
  2917. Length of the bracket
  2918. angleA : float, optional, default : None
  2919. Angle between the bracket and the line
  2920. """
  2921. super().__init__(True, None,
  2922. widthA=widthA, lengthA=lengthA, angleA=angleA)
  2923. @_register_style(_style_list, name="-[")
  2924. class BracketB(_Bracket):
  2925. """
  2926. An arrow with a bracket([) at its end.
  2927. """
  2928. def __init__(self, widthB=1., lengthB=0.2, angleB=None):
  2929. """
  2930. Parameters
  2931. ----------
  2932. widthB : float, optional, default : 1.0
  2933. Width of the bracket
  2934. lengthB : float, optional, default : 0.2
  2935. Length of the bracket
  2936. angleB : float, optional, default : None
  2937. Angle between the bracket and the line
  2938. """
  2939. super().__init__(None, True,
  2940. widthB=widthB, lengthB=lengthB, angleB=angleB)
  2941. @_register_style(_style_list, name="|-|")
  2942. class BarAB(_Bracket):
  2943. """
  2944. An arrow with a bar(|) at both ends.
  2945. """
  2946. def __init__(self,
  2947. widthA=1., angleA=None,
  2948. widthB=1., angleB=None):
  2949. """
  2950. Parameters
  2951. ----------
  2952. widthA : float, optional, default : 1.0
  2953. Width of the bracket
  2954. angleA : float, optional, default : None
  2955. Angle between the bracket and the line
  2956. widthB : float, optional, default : 1.0
  2957. Width of the bracket
  2958. angleB : float, optional, default : None
  2959. Angle between the bracket and the line
  2960. """
  2961. super().__init__(True, True,
  2962. widthA=widthA, lengthA=0, angleA=angleA,
  2963. widthB=widthB, lengthB=0, angleB=angleB)
  2964. @_register_style(_style_list)
  2965. class Simple(_Base):
  2966. """
  2967. A simple arrow. Only works with a quadratic Bezier curve.
  2968. """
  2969. def __init__(self, head_length=.5, head_width=.5, tail_width=.2):
  2970. """
  2971. Parameters
  2972. ----------
  2973. head_length : float, optional, default : 0.5
  2974. Length of the arrow head
  2975. head_width : float, optional, default : 0.5
  2976. Width of the arrow head
  2977. tail_width : float, optional, default : 0.2
  2978. Width of the arrow tail
  2979. """
  2980. self.head_length, self.head_width, self.tail_width = \
  2981. head_length, head_width, tail_width
  2982. super().__init__()
  2983. def transmute(self, path, mutation_size, linewidth):
  2984. x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
  2985. # divide the path into a head and a tail
  2986. head_length = self.head_length * mutation_size
  2987. in_f = inside_circle(x2, y2, head_length)
  2988. arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
  2989. try:
  2990. arrow_out, arrow_in = \
  2991. split_bezier_intersecting_with_closedpath(arrow_path,
  2992. in_f,
  2993. tolerence=0.01)
  2994. except NonIntersectingPathException:
  2995. # if this happens, make a straight line of the head_length
  2996. # long.
  2997. x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length)
  2998. x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2)
  2999. arrow_in = [(x0, y0), (x1n, y1n), (x2, y2)]
  3000. arrow_out = None
  3001. # head
  3002. head_width = self.head_width * mutation_size
  3003. head_left, head_right = make_wedged_bezier2(arrow_in,
  3004. head_width / 2., wm=.5)
  3005. # tail
  3006. if arrow_out is not None:
  3007. tail_width = self.tail_width * mutation_size
  3008. tail_left, tail_right = get_parallels(arrow_out,
  3009. tail_width / 2.)
  3010. patch_path = [(Path.MOVETO, tail_right[0]),
  3011. (Path.CURVE3, tail_right[1]),
  3012. (Path.CURVE3, tail_right[2]),
  3013. (Path.LINETO, head_right[0]),
  3014. (Path.CURVE3, head_right[1]),
  3015. (Path.CURVE3, head_right[2]),
  3016. (Path.CURVE3, head_left[1]),
  3017. (Path.CURVE3, head_left[0]),
  3018. (Path.LINETO, tail_left[2]),
  3019. (Path.CURVE3, tail_left[1]),
  3020. (Path.CURVE3, tail_left[0]),
  3021. (Path.LINETO, tail_right[0]),
  3022. (Path.CLOSEPOLY, tail_right[0]),
  3023. ]
  3024. else:
  3025. patch_path = [(Path.MOVETO, head_right[0]),
  3026. (Path.CURVE3, head_right[1]),
  3027. (Path.CURVE3, head_right[2]),
  3028. (Path.CURVE3, head_left[1]),
  3029. (Path.CURVE3, head_left[0]),
  3030. (Path.CLOSEPOLY, head_left[0]),
  3031. ]
  3032. path = Path([p for c, p in patch_path], [c for c, p in patch_path])
  3033. return path, True
  3034. @_register_style(_style_list)
  3035. class Fancy(_Base):
  3036. """
  3037. A fancy arrow. Only works with a quadratic Bezier curve.
  3038. """
  3039. def __init__(self, head_length=.4, head_width=.4, tail_width=.4):
  3040. """
  3041. Parameters
  3042. ----------
  3043. head_length : float, optional, default : 0.4
  3044. Length of the arrow head
  3045. head_width : float, optional, default : 0.4
  3046. Width of the arrow head
  3047. tail_width : float, optional, default : 0.4
  3048. Width of the arrow tail
  3049. """
  3050. self.head_length, self.head_width, self.tail_width = \
  3051. head_length, head_width, tail_width
  3052. super().__init__()
  3053. def transmute(self, path, mutation_size, linewidth):
  3054. x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
  3055. # divide the path into a head and a tail
  3056. head_length = self.head_length * mutation_size
  3057. arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
  3058. # path for head
  3059. in_f = inside_circle(x2, y2, head_length)
  3060. try:
  3061. path_out, path_in = \
  3062. split_bezier_intersecting_with_closedpath(
  3063. arrow_path,
  3064. in_f,
  3065. tolerence=0.01)
  3066. except NonIntersectingPathException:
  3067. # if this happens, make a straight line of the head_length
  3068. # long.
  3069. x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length)
  3070. x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2)
  3071. arrow_path = [(x0, y0), (x1n, y1n), (x2, y2)]
  3072. path_head = arrow_path
  3073. else:
  3074. path_head = path_in
  3075. # path for head
  3076. in_f = inside_circle(x2, y2, head_length * .8)
  3077. path_out, path_in = split_bezier_intersecting_with_closedpath(
  3078. arrow_path,
  3079. in_f,
  3080. tolerence=0.01
  3081. )
  3082. path_tail = path_out
  3083. # head
  3084. head_width = self.head_width * mutation_size
  3085. head_l, head_r = make_wedged_bezier2(path_head,
  3086. head_width / 2.,
  3087. wm=.6)
  3088. # tail
  3089. tail_width = self.tail_width * mutation_size
  3090. tail_left, tail_right = make_wedged_bezier2(path_tail,
  3091. tail_width * .5,
  3092. w1=1., wm=0.6, w2=0.3)
  3093. # path for head
  3094. in_f = inside_circle(x0, y0, tail_width * .3)
  3095. path_in, path_out = split_bezier_intersecting_with_closedpath(
  3096. arrow_path,
  3097. in_f,
  3098. tolerence=0.01
  3099. )
  3100. tail_start = path_in[-1]
  3101. head_right, head_left = head_r, head_l
  3102. patch_path = [(Path.MOVETO, tail_start),
  3103. (Path.LINETO, tail_right[0]),
  3104. (Path.CURVE3, tail_right[1]),
  3105. (Path.CURVE3, tail_right[2]),
  3106. (Path.LINETO, head_right[0]),
  3107. (Path.CURVE3, head_right[1]),
  3108. (Path.CURVE3, head_right[2]),
  3109. (Path.CURVE3, head_left[1]),
  3110. (Path.CURVE3, head_left[0]),
  3111. (Path.LINETO, tail_left[2]),
  3112. (Path.CURVE3, tail_left[1]),
  3113. (Path.CURVE3, tail_left[0]),
  3114. (Path.LINETO, tail_start),
  3115. (Path.CLOSEPOLY, tail_start),
  3116. ]
  3117. path = Path([p for c, p in patch_path], [c for c, p in patch_path])
  3118. return path, True
  3119. @_register_style(_style_list)
  3120. class Wedge(_Base):
  3121. """
  3122. Wedge(?) shape. Only works with a quadratic Bezier curve. The
  3123. begin point has a width of the tail_width and the end point has a
  3124. width of 0. At the middle, the width is shrink_factor*tail_width.
  3125. """
  3126. def __init__(self, tail_width=.3, shrink_factor=0.5):
  3127. """
  3128. Parameters
  3129. ----------
  3130. tail_width : float, optional, default : 0.3
  3131. Width of the tail
  3132. shrink_factor : float, optional, default : 0.5
  3133. Fraction of the arrow width at the middle point
  3134. """
  3135. self.tail_width = tail_width
  3136. self.shrink_factor = shrink_factor
  3137. super().__init__()
  3138. def transmute(self, path, mutation_size, linewidth):
  3139. x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
  3140. arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
  3141. b_plus, b_minus = make_wedged_bezier2(
  3142. arrow_path,
  3143. self.tail_width * mutation_size / 2.,
  3144. wm=self.shrink_factor)
  3145. patch_path = [(Path.MOVETO, b_plus[0]),
  3146. (Path.CURVE3, b_plus[1]),
  3147. (Path.CURVE3, b_plus[2]),
  3148. (Path.LINETO, b_minus[2]),
  3149. (Path.CURVE3, b_minus[1]),
  3150. (Path.CURVE3, b_minus[0]),
  3151. (Path.CLOSEPOLY, b_minus[0]),
  3152. ]
  3153. path = Path([p for c, p in patch_path], [c for c, p in patch_path])
  3154. return path, True
  3155. if __doc__:
  3156. __doc__ = cbook.dedent(__doc__) % \
  3157. {"AvailableArrowstyles": _pprint_styles(_style_list)}
  3158. docstring.interpd.update(
  3159. AvailableArrowstyles=_pprint_styles(ArrowStyle._style_list),
  3160. AvailableConnectorstyles=_pprint_styles(ConnectionStyle._style_list),
  3161. )
  3162. class FancyArrowPatch(Patch):
  3163. """
  3164. A fancy arrow patch. It draws an arrow using the :class:`ArrowStyle`.
  3165. The head and tail positions are fixed at the specified start and end points
  3166. of the arrow, but the size and shape (in display coordinates) of the arrow
  3167. does not change when the axis is moved or zoomed.
  3168. """
  3169. _edge_default = True
  3170. def __str__(self):
  3171. if self._posA_posB is not None:
  3172. (x1, y1), (x2, y2) = self._posA_posB
  3173. return self.__class__.__name__ \
  3174. + "((%g, %g)->(%g, %g))" % (x1, y1, x2, y2)
  3175. else:
  3176. return self.__class__.__name__ \
  3177. + "(%s)" % (str(self._path_original),)
  3178. @docstring.dedent_interpd
  3179. def __init__(self, posA=None, posB=None,
  3180. path=None,
  3181. arrowstyle="simple",
  3182. arrow_transmuter=None,
  3183. connectionstyle="arc3",
  3184. connector=None,
  3185. patchA=None,
  3186. patchB=None,
  3187. shrinkA=2,
  3188. shrinkB=2,
  3189. mutation_scale=1,
  3190. mutation_aspect=None,
  3191. dpi_cor=1,
  3192. **kwargs):
  3193. """
  3194. If *posA* and *posB* are given, a path connecting two points is
  3195. created according to *connectionstyle*. The path will be
  3196. clipped with *patchA* and *patchB* and further shrunken by
  3197. *shrinkA* and *shrinkB*. An arrow is drawn along this
  3198. resulting path using the *arrowstyle* parameter.
  3199. Alternatively if *path* is provided, an arrow is drawn along this path
  3200. and *patchA*, *patchB*, *shrinkA*, and *shrinkB* are ignored.
  3201. Parameters
  3202. ----------
  3203. posA, posB : None, tuple, optional (default: None)
  3204. (x,y) coordinates of arrow tail and arrow head respectively.
  3205. path : None, Path (default: None)
  3206. :class:`matplotlib.path.Path` instance. If provided, an arrow is
  3207. drawn along this path and *patchA*, *patchB*, *shrinkA*, and
  3208. *shrinkB* are ignored.
  3209. arrowstyle : str or ArrowStyle, optional (default: 'simple')
  3210. Describes how the fancy arrow will be
  3211. drawn. It can be string of the available arrowstyle names,
  3212. with optional comma-separated attributes, or an
  3213. :class:`ArrowStyle` instance. The optional attributes are meant to
  3214. be scaled with the *mutation_scale*. The following arrow styles are
  3215. available:
  3216. %(AvailableArrowstyles)s
  3217. arrow_transmuter :
  3218. Ignored
  3219. connectionstyle : str, ConnectionStyle, or None, optional
  3220. (default: 'arc3')
  3221. Describes how *posA* and *posB* are connected. It can be an
  3222. instance of the :class:`ConnectionStyle` class or a string of the
  3223. connectionstyle name, with optional comma-separated attributes. The
  3224. following connection styles are available:
  3225. %(AvailableConnectorstyles)s
  3226. connector :
  3227. Ignored
  3228. patchA, patchB : None, Patch, optional (default: None)
  3229. Head and tail patch respectively. :class:`matplotlib.patch.Patch`
  3230. instance.
  3231. shrinkA, shrinkB : scalar, optional (default: 2)
  3232. Shrinking factor of the tail and head of the arrow respectively
  3233. mutation_scale : scalar, optional (default: 1)
  3234. Value with which attributes of *arrowstyle* (e.g., *head_length*)
  3235. will be scaled.
  3236. mutation_aspect : None, scalar, optional (default: None)
  3237. The height of the rectangle will be squeezed by this value before
  3238. the mutation and the mutated box will be stretched by the inverse
  3239. of it.
  3240. dpi_cor : scalar, optional (default: 1)
  3241. dpi_cor is currently used for linewidth-related things and shrink
  3242. factor. Mutation scale is affected by this.
  3243. Notes
  3244. -----
  3245. Valid kwargs are:
  3246. %(Patch)s
  3247. """
  3248. if arrow_transmuter is not None:
  3249. cbook.warn_deprecated(
  3250. 3.0,
  3251. message=('The "arrow_transmuter" keyword argument is not used,'
  3252. ' and will be removed in Matplotlib 3.1'),
  3253. name='arrow_transmuter',
  3254. obj_type='keyword argument')
  3255. if connector is not None:
  3256. cbook.warn_deprecated(
  3257. 3.0,
  3258. message=('The "connector" keyword argument is not used,'
  3259. ' and will be removed in Matplotlib 3.1'),
  3260. name='connector',
  3261. obj_type='keyword argument')
  3262. Patch.__init__(self, **kwargs)
  3263. if posA is not None and posB is not None and path is None:
  3264. self._posA_posB = [posA, posB]
  3265. if connectionstyle is None:
  3266. connectionstyle = "arc3"
  3267. self.set_connectionstyle(connectionstyle)
  3268. elif posA is None and posB is None and path is not None:
  3269. self._posA_posB = None
  3270. self._connetors = None
  3271. else:
  3272. raise ValueError("either posA and posB, or path need to provided")
  3273. self.patchA = patchA
  3274. self.patchB = patchB
  3275. self.shrinkA = shrinkA
  3276. self.shrinkB = shrinkB
  3277. self._path_original = path
  3278. self.set_arrowstyle(arrowstyle)
  3279. self._mutation_scale = mutation_scale
  3280. self._mutation_aspect = mutation_aspect
  3281. self.set_dpi_cor(dpi_cor)
  3282. def set_dpi_cor(self, dpi_cor):
  3283. """
  3284. dpi_cor is currently used for linewidth-related things and
  3285. shrink factor. Mutation scale is affected by this.
  3286. Parameters
  3287. ----------
  3288. dpi_cor : scalar
  3289. """
  3290. self._dpi_cor = dpi_cor
  3291. self.stale = True
  3292. def get_dpi_cor(self):
  3293. """
  3294. dpi_cor is currently used for linewidth-related things and
  3295. shrink factor. Mutation scale is affected by this.
  3296. Returns
  3297. -------
  3298. dpi_cor : scalar
  3299. """
  3300. return self._dpi_cor
  3301. def set_positions(self, posA, posB):
  3302. """
  3303. Set the begin and end positions of the connecting path.
  3304. Parameters
  3305. ----------
  3306. posA, posB : None, tuple
  3307. (x,y) coordinates of arrow tail and arrow head respectively. If
  3308. `None` use current value.
  3309. """
  3310. if posA is not None:
  3311. self._posA_posB[0] = posA
  3312. if posB is not None:
  3313. self._posA_posB[1] = posB
  3314. self.stale = True
  3315. def set_patchA(self, patchA):
  3316. """
  3317. Set the tail patch.
  3318. Parameters
  3319. ----------
  3320. patchA : Patch
  3321. :class:`matplotlib.patch.Patch` instance.
  3322. """
  3323. self.patchA = patchA
  3324. self.stale = True
  3325. def set_patchB(self, patchB):
  3326. """
  3327. Set the head patch.
  3328. Parameters
  3329. ----------
  3330. patchB : Patch
  3331. :class:`matplotlib.patch.Patch` instance.
  3332. """
  3333. self.patchB = patchB
  3334. self.stale = True
  3335. def set_connectionstyle(self, connectionstyle, **kw):
  3336. """
  3337. Set the connection style. Old attributes are forgotten.
  3338. Parameters
  3339. ----------
  3340. connectionstyle : None, ConnectionStyle instance, or string
  3341. Can be a string with connectionstyle name with
  3342. optional comma-separated attributes, e.g.::
  3343. set_connectionstyle("arc,angleA=0,armA=30,rad=10")
  3344. Alternatively, the attributes can be provided as keywords, e.g.::
  3345. set_connectionstyle("arc", angleA=0,armA=30,rad=10)
  3346. Without any arguments (or with ``connectionstyle=None``), return
  3347. available styles as a list of strings.
  3348. """
  3349. if connectionstyle is None:
  3350. return ConnectionStyle.pprint_styles()
  3351. if (isinstance(connectionstyle, ConnectionStyle._Base) or
  3352. callable(connectionstyle)):
  3353. self._connector = connectionstyle
  3354. else:
  3355. self._connector = ConnectionStyle(connectionstyle, **kw)
  3356. self.stale = True
  3357. def get_connectionstyle(self):
  3358. """
  3359. Return the :class:`ConnectionStyle` instance.
  3360. """
  3361. return self._connector
  3362. def set_arrowstyle(self, arrowstyle=None, **kw):
  3363. """
  3364. Set the arrow style. Old attributes are forgotten. Without arguments
  3365. (or with ``arrowstyle=None``) returns available box styles as a list of
  3366. strings.
  3367. Parameters
  3368. ----------
  3369. arrowstyle : None, ArrowStyle, str, optional (default: None)
  3370. Can be a string with arrowstyle name with optional comma-separated
  3371. attributes, e.g.::
  3372. set_arrowstyle("Fancy,head_length=0.2")
  3373. Alternatively attributes can be provided as keywords, e.g.::
  3374. set_arrowstyle("fancy", head_length=0.2)
  3375. """
  3376. if arrowstyle is None:
  3377. return ArrowStyle.pprint_styles()
  3378. if isinstance(arrowstyle, ArrowStyle._Base):
  3379. self._arrow_transmuter = arrowstyle
  3380. else:
  3381. self._arrow_transmuter = ArrowStyle(arrowstyle, **kw)
  3382. self.stale = True
  3383. def get_arrowstyle(self):
  3384. """
  3385. Return the arrowstyle object.
  3386. """
  3387. return self._arrow_transmuter
  3388. def set_mutation_scale(self, scale):
  3389. """
  3390. Set the mutation scale.
  3391. Parameters
  3392. ----------
  3393. scale : scalar
  3394. """
  3395. self._mutation_scale = scale
  3396. self.stale = True
  3397. def get_mutation_scale(self):
  3398. """
  3399. Return the mutation scale.
  3400. Returns
  3401. -------
  3402. scale : scalar
  3403. """
  3404. return self._mutation_scale
  3405. def set_mutation_aspect(self, aspect):
  3406. """
  3407. Set the aspect ratio of the bbox mutation.
  3408. Parameters
  3409. ----------
  3410. aspect : scalar
  3411. """
  3412. self._mutation_aspect = aspect
  3413. self.stale = True
  3414. def get_mutation_aspect(self):
  3415. """
  3416. Return the aspect ratio of the bbox mutation.
  3417. """
  3418. return self._mutation_aspect
  3419. def get_path(self):
  3420. """
  3421. Return the path of the arrow in the data coordinates. Use
  3422. get_path_in_displaycoord() method to retrieve the arrow path
  3423. in display coordinates.
  3424. """
  3425. _path, fillable = self.get_path_in_displaycoord()
  3426. if cbook.iterable(fillable):
  3427. _path = concatenate_paths(_path)
  3428. return self.get_transform().inverted().transform_path(_path)
  3429. def get_path_in_displaycoord(self):
  3430. """
  3431. Return the mutated path of the arrow in display coordinates.
  3432. """
  3433. dpi_cor = self.get_dpi_cor()
  3434. if self._posA_posB is not None:
  3435. posA = self.get_transform().transform_point(self._posA_posB[0])
  3436. posB = self.get_transform().transform_point(self._posA_posB[1])
  3437. _path = self.get_connectionstyle()(posA, posB,
  3438. patchA=self.patchA,
  3439. patchB=self.patchB,
  3440. shrinkA=self.shrinkA * dpi_cor,
  3441. shrinkB=self.shrinkB * dpi_cor
  3442. )
  3443. else:
  3444. _path = self.get_transform().transform_path(self._path_original)
  3445. _path, fillable = self.get_arrowstyle()(
  3446. _path,
  3447. self.get_mutation_scale() * dpi_cor,
  3448. self.get_linewidth() * dpi_cor,
  3449. self.get_mutation_aspect())
  3450. # if not fillable:
  3451. # self._fill = False
  3452. return _path, fillable
  3453. def draw(self, renderer):
  3454. if not self.get_visible():
  3455. return
  3456. renderer.open_group('patch', self.get_gid())
  3457. gc = renderer.new_gc()
  3458. gc.set_foreground(self._edgecolor, isRGBA=True)
  3459. lw = self._linewidth
  3460. if self._edgecolor[3] == 0:
  3461. lw = 0
  3462. gc.set_linewidth(lw)
  3463. gc.set_dashes(self._dashoffset, self._dashes)
  3464. gc.set_antialiased(self._antialiased)
  3465. self._set_gc_clip(gc)
  3466. gc.set_capstyle('round')
  3467. gc.set_snap(self.get_snap())
  3468. rgbFace = self._facecolor
  3469. if rgbFace[3] == 0:
  3470. rgbFace = None # (some?) renderers expect this as no-fill signal
  3471. gc.set_alpha(self._alpha)
  3472. if self._hatch:
  3473. gc.set_hatch(self._hatch)
  3474. if self._hatch_color is not None:
  3475. try:
  3476. gc.set_hatch_color(self._hatch_color)
  3477. except AttributeError:
  3478. # if we end up with a GC that does not have this method
  3479. warnings.warn("Your backend does not support setting the "
  3480. "hatch color.")
  3481. if self.get_sketch_params() is not None:
  3482. gc.set_sketch_params(*self.get_sketch_params())
  3483. # FIXME : dpi_cor is for the dpi-dependecy of the
  3484. # linewidth. There could be room for improvement.
  3485. #
  3486. # dpi_cor = renderer.points_to_pixels(1.)
  3487. self.set_dpi_cor(renderer.points_to_pixels(1.))
  3488. path, fillable = self.get_path_in_displaycoord()
  3489. if not cbook.iterable(fillable):
  3490. path = [path]
  3491. fillable = [fillable]
  3492. affine = transforms.IdentityTransform()
  3493. if self.get_path_effects():
  3494. from matplotlib.patheffects import PathEffectRenderer
  3495. renderer = PathEffectRenderer(self.get_path_effects(), renderer)
  3496. for p, f in zip(path, fillable):
  3497. if f:
  3498. renderer.draw_path(gc, p, affine, rgbFace)
  3499. else:
  3500. renderer.draw_path(gc, p, affine, None)
  3501. gc.restore()
  3502. renderer.close_group('patch')
  3503. self.stale = False
  3504. class ConnectionPatch(FancyArrowPatch):
  3505. """
  3506. A :class:`~matplotlib.patches.ConnectionPatch` class is to make
  3507. connecting lines between two points (possibly in different axes).
  3508. """
  3509. def __str__(self):
  3510. return "ConnectionPatch((%g, %g), (%g, %g))" % \
  3511. (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1])
  3512. @docstring.dedent_interpd
  3513. def __init__(self, xyA, xyB, coordsA, coordsB=None,
  3514. axesA=None, axesB=None,
  3515. arrowstyle="-",
  3516. arrow_transmuter=None,
  3517. connectionstyle="arc3",
  3518. connector=None,
  3519. patchA=None,
  3520. patchB=None,
  3521. shrinkA=0.,
  3522. shrinkB=0.,
  3523. mutation_scale=10.,
  3524. mutation_aspect=None,
  3525. clip_on=False,
  3526. dpi_cor=1.,
  3527. **kwargs):
  3528. """
  3529. Connect point *xyA* in *coordsA* with point *xyB* in *coordsB*
  3530. Valid keys are
  3531. =============== ======================================================
  3532. Key Description
  3533. =============== ======================================================
  3534. arrowstyle the arrow style
  3535. connectionstyle the connection style
  3536. relpos default is (0.5, 0.5)
  3537. patchA default is bounding box of the text
  3538. patchB default is None
  3539. shrinkA default is 2 points
  3540. shrinkB default is 2 points
  3541. mutation_scale default is text size (in points)
  3542. mutation_aspect default is 1.
  3543. ? any key for :class:`matplotlib.patches.PathPatch`
  3544. =============== ======================================================
  3545. *coordsA* and *coordsB* are strings that indicate the
  3546. coordinates of *xyA* and *xyB*.
  3547. ================= ===================================================
  3548. Property Description
  3549. ================= ===================================================
  3550. 'figure points' points from the lower left corner of the figure
  3551. 'figure pixels' pixels from the lower left corner of the figure
  3552. 'figure fraction' 0,0 is lower left of figure and 1,1 is upper, right
  3553. 'axes points' points from lower left corner of axes
  3554. 'axes pixels' pixels from lower left corner of axes
  3555. 'axes fraction' 0,1 is lower left of axes and 1,1 is upper right
  3556. 'data' use the coordinate system of the object being
  3557. annotated (default)
  3558. 'offset points' Specify an offset (in points) from the *xy* value
  3559. 'polar' you can specify *theta*, *r* for the annotation,
  3560. even in cartesian plots. Note that if you
  3561. are using a polar axes, you do not need
  3562. to specify polar for the coordinate
  3563. system since that is the native "data" coordinate
  3564. system.
  3565. ================= ===================================================
  3566. """
  3567. if coordsB is None:
  3568. coordsB = coordsA
  3569. # we'll draw ourself after the artist we annotate by default
  3570. self.xy1 = xyA
  3571. self.xy2 = xyB
  3572. self.coords1 = coordsA
  3573. self.coords2 = coordsB
  3574. self.axesA = axesA
  3575. self.axesB = axesB
  3576. FancyArrowPatch.__init__(self,
  3577. posA=(0, 0), posB=(1, 1),
  3578. arrowstyle=arrowstyle,
  3579. arrow_transmuter=arrow_transmuter,
  3580. connectionstyle=connectionstyle,
  3581. connector=connector,
  3582. patchA=patchA,
  3583. patchB=patchB,
  3584. shrinkA=shrinkA,
  3585. shrinkB=shrinkB,
  3586. mutation_scale=mutation_scale,
  3587. mutation_aspect=mutation_aspect,
  3588. clip_on=clip_on,
  3589. dpi_cor=dpi_cor,
  3590. **kwargs)
  3591. # if True, draw annotation only if self.xy is inside the axes
  3592. self._annotation_clip = None
  3593. def _get_xy(self, x, y, s, axes=None):
  3594. """
  3595. calculate the pixel position of given point
  3596. """
  3597. if axes is None:
  3598. axes = self.axes
  3599. if s == 'data':
  3600. trans = axes.transData
  3601. x = float(self.convert_xunits(x))
  3602. y = float(self.convert_yunits(y))
  3603. return trans.transform_point((x, y))
  3604. elif s == 'offset points':
  3605. # convert the data point
  3606. dx, dy = self.xy
  3607. # prevent recursion
  3608. if self.xycoords == 'offset points':
  3609. return self._get_xy(dx, dy, 'data')
  3610. dx, dy = self._get_xy(dx, dy, self.xycoords)
  3611. # convert the offset
  3612. dpi = self.figure.get_dpi()
  3613. x *= dpi / 72.
  3614. y *= dpi / 72.
  3615. # add the offset to the data point
  3616. x += dx
  3617. y += dy
  3618. return x, y
  3619. elif s == 'polar':
  3620. theta, r = x, y
  3621. x = r * np.cos(theta)
  3622. y = r * np.sin(theta)
  3623. trans = axes.transData
  3624. return trans.transform_point((x, y))
  3625. elif s == 'figure points':
  3626. # points from the lower left corner of the figure
  3627. dpi = self.figure.dpi
  3628. l, b, w, h = self.figure.bbox.bounds
  3629. r = l + w
  3630. t = b + h
  3631. x *= dpi / 72.
  3632. y *= dpi / 72.
  3633. if x < 0:
  3634. x = r + x
  3635. if y < 0:
  3636. y = t + y
  3637. return x, y
  3638. elif s == 'figure pixels':
  3639. # pixels from the lower left corner of the figure
  3640. l, b, w, h = self.figure.bbox.bounds
  3641. r = l + w
  3642. t = b + h
  3643. if x < 0:
  3644. x = r + x
  3645. if y < 0:
  3646. y = t + y
  3647. return x, y
  3648. elif s == 'figure fraction':
  3649. # (0,0) is lower left, (1,1) is upper right of figure
  3650. trans = self.figure.transFigure
  3651. return trans.transform_point((x, y))
  3652. elif s == 'axes points':
  3653. # points from the lower left corner of the axes
  3654. dpi = self.figure.dpi
  3655. l, b, w, h = axes.bbox.bounds
  3656. r = l + w
  3657. t = b + h
  3658. if x < 0:
  3659. x = r + x * dpi / 72.
  3660. else:
  3661. x = l + x * dpi / 72.
  3662. if y < 0:
  3663. y = t + y * dpi / 72.
  3664. else:
  3665. y = b + y * dpi / 72.
  3666. return x, y
  3667. elif s == 'axes pixels':
  3668. #pixels from the lower left corner of the axes
  3669. l, b, w, h = axes.bbox.bounds
  3670. r = l + w
  3671. t = b + h
  3672. if x < 0:
  3673. x = r + x
  3674. else:
  3675. x = l + x
  3676. if y < 0:
  3677. y = t + y
  3678. else:
  3679. y = b + y
  3680. return x, y
  3681. elif s == 'axes fraction':
  3682. #(0,0) is lower left, (1,1) is upper right of axes
  3683. trans = axes.transAxes
  3684. return trans.transform_point((x, y))
  3685. def set_annotation_clip(self, b):
  3686. """
  3687. set *annotation_clip* attribute.
  3688. * True: the annotation will only be drawn when self.xy is inside the
  3689. axes.
  3690. * False: the annotation will always be drawn regardless of its
  3691. position.
  3692. * None: the self.xy will be checked only if *xycoords* is "data"
  3693. """
  3694. self._annotation_clip = b
  3695. self.stale = True
  3696. def get_annotation_clip(self):
  3697. """
  3698. Return *annotation_clip* attribute.
  3699. See :meth:`set_annotation_clip` for the meaning of return values.
  3700. """
  3701. return self._annotation_clip
  3702. def get_path_in_displaycoord(self):
  3703. """
  3704. Return the mutated path of the arrow in the display coord
  3705. """
  3706. dpi_cor = self.get_dpi_cor()
  3707. x, y = self.xy1
  3708. posA = self._get_xy(x, y, self.coords1, self.axesA)
  3709. x, y = self.xy2
  3710. posB = self._get_xy(x, y, self.coords2, self.axesB)
  3711. _path = self.get_connectionstyle()(posA, posB,
  3712. patchA=self.patchA,
  3713. patchB=self.patchB,
  3714. shrinkA=self.shrinkA * dpi_cor,
  3715. shrinkB=self.shrinkB * dpi_cor
  3716. )
  3717. _path, fillable = self.get_arrowstyle()(
  3718. _path,
  3719. self.get_mutation_scale() * dpi_cor,
  3720. self.get_linewidth() * dpi_cor,
  3721. self.get_mutation_aspect()
  3722. )
  3723. return _path, fillable
  3724. def _check_xy(self, renderer):
  3725. """Check whether the annotation needs to be drawn."""
  3726. b = self.get_annotation_clip()
  3727. if b or (b is None and self.coords1 == "data"):
  3728. x, y = self.xy1
  3729. xy_pixel = self._get_xy(x, y, self.coords1, self.axesA)
  3730. if not self.axes.contains_point(xy_pixel):
  3731. return False
  3732. if b or (b is None and self.coords2 == "data"):
  3733. x, y = self.xy2
  3734. xy_pixel = self._get_xy(x, y, self.coords2, self.axesB)
  3735. if self.axesB is None:
  3736. axes = self.axes
  3737. else:
  3738. axes = self.axesB
  3739. if not axes.contains_point(xy_pixel):
  3740. return False
  3741. return True
  3742. def draw(self, renderer):
  3743. if renderer is not None:
  3744. self._renderer = renderer
  3745. if not self.get_visible() or not self._check_xy(renderer):
  3746. return
  3747. FancyArrowPatch.draw(self, renderer)