markers.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. r"""
  2. Functions to handle markers; used by the marker functionality of
  3. `~matplotlib.axes.Axes.plot`, `~matplotlib.axes.Axes.scatter`, and
  4. `~matplotlib.axes.Axes.errorbar`.
  5. All possible markers are defined here:
  6. ============================== ====== =========================================
  7. marker symbol description
  8. ============================== ====== =========================================
  9. ``"."`` |m00| point
  10. ``","`` |m01| pixel
  11. ``"o"`` |m02| circle
  12. ``"v"`` |m03| triangle_down
  13. ``"^"`` |m04| triangle_up
  14. ``"<"`` |m05| triangle_left
  15. ``">"`` |m06| triangle_right
  16. ``"1"`` |m07| tri_down
  17. ``"2"`` |m08| tri_up
  18. ``"3"`` |m09| tri_left
  19. ``"4"`` |m10| tri_right
  20. ``"8"`` |m11| octagon
  21. ``"s"`` |m12| square
  22. ``"p"`` |m13| pentagon
  23. ``"P"`` |m23| plus (filled)
  24. ``"*"`` |m14| star
  25. ``"h"`` |m15| hexagon1
  26. ``"H"`` |m16| hexagon2
  27. ``"+"`` |m17| plus
  28. ``"x"`` |m18| x
  29. ``"X"`` |m24| x (filled)
  30. ``"D"`` |m19| diamond
  31. ``"d"`` |m20| thin_diamond
  32. ``"|"`` |m21| vline
  33. ``"_"`` |m22| hline
  34. ``0`` (``TICKLEFT``) |m25| tickleft
  35. ``1`` (``TICKRIGHT``) |m26| tickright
  36. ``2`` (``TICKUP``) |m27| tickup
  37. ``3`` (``TICKDOWN``) |m28| tickdown
  38. ``4`` (``CARETLEFT``) |m29| caretleft
  39. ``5`` (``CARETRIGHT``) |m30| caretright
  40. ``6`` (``CARETUP``) |m31| caretup
  41. ``7`` (``CARETDOWN``) |m32| caretdown
  42. ``8`` (``CARETLEFTBASE``) |m33| caretleft (centered at base)
  43. ``9`` (``CARETRIGHTBASE``) |m34| caretright (centered at base)
  44. ``10`` (``CARETUPBASE``) |m35| caretup (centered at base)
  45. ``11`` (``CARETDOWNBASE``) |m36| caretdown (centered at base)
  46. ``"none"`` or ``"None"`` nothing
  47. ``" "`` or ``""`` nothing
  48. ``"$...$"`` |m37| Render the string using mathtext.
  49. E.g ``"$f$"`` for marker showing the
  50. letter ``f``.
  51. ``verts`` A list of (x, y) pairs used for Path
  52. vertices. The center of the marker is
  53. located at (0, 0) and the size is
  54. normalized, such that the created path
  55. is encapsulated inside the unit cell.
  56. ``path`` A `~matplotlib.path.Path` instance.
  57. ``(numsides, 0, angle)`` A regular polygon with ``numsides``
  58. sides, rotated by ``angle``.
  59. ``(numsides, 1, angle)`` A star-like symbol with ``numsides``
  60. sides, rotated by ``angle``.
  61. ``(numsides, 2, angle)`` An asterisk with ``numsides`` sides,
  62. rotated by ``angle``.
  63. ============================== ====== =========================================
  64. Note that special symbols can be defined via the
  65. :ref:`STIX math font <mathtext>`,
  66. e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer to the
  67. `STIX font table <http://www.stixfonts.org/allGlyphs.html>`_.
  68. Also see the :doc:`/gallery/text_labels_and_annotations/stix_fonts_demo`.
  69. Integer numbers from ``0`` to ``11`` create lines and triangles. Those are
  70. equally accessible via capitalized variables, like ``CARETDOWNBASE``.
  71. Hence the following are equivalent::
  72. plt.plot([1, 2, 3], marker=11)
  73. plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE)
  74. Markers join and cap styles can be customized by creating a new instance of
  75. MarkerStyle.
  76. A MarkerStyle can also have a custom `~matplotlib.transforms.Transform`
  77. allowing it to be arbitrarily rotated or offset.
  78. Examples showing the use of markers:
  79. * :doc:`/gallery/lines_bars_and_markers/marker_reference`
  80. * :doc:`/gallery/lines_bars_and_markers/scatter_star_poly`
  81. * :doc:`/gallery/lines_bars_and_markers/multivariate_marker_plot`
  82. .. |m00| image:: /_static/markers/m00.png
  83. .. |m01| image:: /_static/markers/m01.png
  84. .. |m02| image:: /_static/markers/m02.png
  85. .. |m03| image:: /_static/markers/m03.png
  86. .. |m04| image:: /_static/markers/m04.png
  87. .. |m05| image:: /_static/markers/m05.png
  88. .. |m06| image:: /_static/markers/m06.png
  89. .. |m07| image:: /_static/markers/m07.png
  90. .. |m08| image:: /_static/markers/m08.png
  91. .. |m09| image:: /_static/markers/m09.png
  92. .. |m10| image:: /_static/markers/m10.png
  93. .. |m11| image:: /_static/markers/m11.png
  94. .. |m12| image:: /_static/markers/m12.png
  95. .. |m13| image:: /_static/markers/m13.png
  96. .. |m14| image:: /_static/markers/m14.png
  97. .. |m15| image:: /_static/markers/m15.png
  98. .. |m16| image:: /_static/markers/m16.png
  99. .. |m17| image:: /_static/markers/m17.png
  100. .. |m18| image:: /_static/markers/m18.png
  101. .. |m19| image:: /_static/markers/m19.png
  102. .. |m20| image:: /_static/markers/m20.png
  103. .. |m21| image:: /_static/markers/m21.png
  104. .. |m22| image:: /_static/markers/m22.png
  105. .. |m23| image:: /_static/markers/m23.png
  106. .. |m24| image:: /_static/markers/m24.png
  107. .. |m25| image:: /_static/markers/m25.png
  108. .. |m26| image:: /_static/markers/m26.png
  109. .. |m27| image:: /_static/markers/m27.png
  110. .. |m28| image:: /_static/markers/m28.png
  111. .. |m29| image:: /_static/markers/m29.png
  112. .. |m30| image:: /_static/markers/m30.png
  113. .. |m31| image:: /_static/markers/m31.png
  114. .. |m32| image:: /_static/markers/m32.png
  115. .. |m33| image:: /_static/markers/m33.png
  116. .. |m34| image:: /_static/markers/m34.png
  117. .. |m35| image:: /_static/markers/m35.png
  118. .. |m36| image:: /_static/markers/m36.png
  119. .. |m37| image:: /_static/markers/m37.png
  120. """
  121. import copy
  122. from collections.abc import Sized
  123. import numpy as np
  124. import matplotlib as mpl
  125. from . import _api, cbook
  126. from .path import Path
  127. from .transforms import IdentityTransform, Affine2D
  128. from ._enums import JoinStyle, CapStyle
  129. # special-purpose marker identifiers:
  130. (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN,
  131. CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN,
  132. CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) = range(12)
  133. _empty_path = Path(np.empty((0, 2)))
  134. class MarkerStyle:
  135. """
  136. A class representing marker types.
  137. Instances are immutable. If you need to change anything, create a new
  138. instance.
  139. Attributes
  140. ----------
  141. markers : dict
  142. All known markers.
  143. filled_markers : tuple
  144. All known filled markers. This is a subset of *markers*.
  145. fillstyles : tuple
  146. The supported fillstyles.
  147. """
  148. markers = {
  149. '.': 'point',
  150. ',': 'pixel',
  151. 'o': 'circle',
  152. 'v': 'triangle_down',
  153. '^': 'triangle_up',
  154. '<': 'triangle_left',
  155. '>': 'triangle_right',
  156. '1': 'tri_down',
  157. '2': 'tri_up',
  158. '3': 'tri_left',
  159. '4': 'tri_right',
  160. '8': 'octagon',
  161. 's': 'square',
  162. 'p': 'pentagon',
  163. '*': 'star',
  164. 'h': 'hexagon1',
  165. 'H': 'hexagon2',
  166. '+': 'plus',
  167. 'x': 'x',
  168. 'D': 'diamond',
  169. 'd': 'thin_diamond',
  170. '|': 'vline',
  171. '_': 'hline',
  172. 'P': 'plus_filled',
  173. 'X': 'x_filled',
  174. TICKLEFT: 'tickleft',
  175. TICKRIGHT: 'tickright',
  176. TICKUP: 'tickup',
  177. TICKDOWN: 'tickdown',
  178. CARETLEFT: 'caretleft',
  179. CARETRIGHT: 'caretright',
  180. CARETUP: 'caretup',
  181. CARETDOWN: 'caretdown',
  182. CARETLEFTBASE: 'caretleftbase',
  183. CARETRIGHTBASE: 'caretrightbase',
  184. CARETUPBASE: 'caretupbase',
  185. CARETDOWNBASE: 'caretdownbase',
  186. "None": 'nothing',
  187. "none": 'nothing',
  188. ' ': 'nothing',
  189. '': 'nothing'
  190. }
  191. # Just used for informational purposes. is_filled()
  192. # is calculated in the _set_* functions.
  193. filled_markers = (
  194. '.', 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd',
  195. 'P', 'X')
  196. fillstyles = ('full', 'left', 'right', 'bottom', 'top', 'none')
  197. _half_fillstyles = ('left', 'right', 'bottom', 'top')
  198. def __init__(self, marker,
  199. fillstyle=None, transform=None, capstyle=None, joinstyle=None):
  200. """
  201. Parameters
  202. ----------
  203. marker : str, array-like, Path, MarkerStyle
  204. - Another instance of `MarkerStyle` copies the details of that *marker*.
  205. - For other possible marker values, see the module docstring
  206. `matplotlib.markers`.
  207. fillstyle : str, default: :rc:`markers.fillstyle`
  208. One of 'full', 'left', 'right', 'bottom', 'top', 'none'.
  209. transform : `~matplotlib.transforms.Transform`, optional
  210. Transform that will be combined with the native transform of the
  211. marker.
  212. capstyle : `.CapStyle` or %(CapStyle)s, optional
  213. Cap style that will override the default cap style of the marker.
  214. joinstyle : `.JoinStyle` or %(JoinStyle)s, optional
  215. Join style that will override the default join style of the marker.
  216. """
  217. self._marker_function = None
  218. self._user_transform = transform
  219. self._user_capstyle = CapStyle(capstyle) if capstyle is not None else None
  220. self._user_joinstyle = JoinStyle(joinstyle) if joinstyle is not None else None
  221. self._set_fillstyle(fillstyle)
  222. self._set_marker(marker)
  223. def _recache(self):
  224. if self._marker_function is None:
  225. return
  226. self._path = _empty_path
  227. self._transform = IdentityTransform()
  228. self._alt_path = None
  229. self._alt_transform = None
  230. self._snap_threshold = None
  231. self._joinstyle = JoinStyle.round
  232. self._capstyle = self._user_capstyle or CapStyle.butt
  233. # Initial guess: Assume the marker is filled unless the fillstyle is
  234. # set to 'none'. The marker function will override this for unfilled
  235. # markers.
  236. self._filled = self._fillstyle != 'none'
  237. self._marker_function()
  238. def __bool__(self):
  239. return bool(len(self._path.vertices))
  240. def is_filled(self):
  241. return self._filled
  242. def get_fillstyle(self):
  243. return self._fillstyle
  244. def _set_fillstyle(self, fillstyle):
  245. """
  246. Set the fillstyle.
  247. Parameters
  248. ----------
  249. fillstyle : {'full', 'left', 'right', 'bottom', 'top', 'none'}
  250. The part of the marker surface that is colored with
  251. markerfacecolor.
  252. """
  253. if fillstyle is None:
  254. fillstyle = mpl.rcParams['markers.fillstyle']
  255. _api.check_in_list(self.fillstyles, fillstyle=fillstyle)
  256. self._fillstyle = fillstyle
  257. def get_joinstyle(self):
  258. return self._joinstyle.name
  259. def get_capstyle(self):
  260. return self._capstyle.name
  261. def get_marker(self):
  262. return self._marker
  263. def _set_marker(self, marker):
  264. """
  265. Set the marker.
  266. Parameters
  267. ----------
  268. marker : str, array-like, Path, MarkerStyle
  269. - Another instance of `MarkerStyle` copies the details of that *marker*.
  270. - For other possible marker values see the module docstring
  271. `matplotlib.markers`.
  272. """
  273. if isinstance(marker, str) and cbook.is_math_text(marker):
  274. self._marker_function = self._set_mathtext_path
  275. elif isinstance(marker, (int, str)) and marker in self.markers:
  276. self._marker_function = getattr(self, '_set_' + self.markers[marker])
  277. elif (isinstance(marker, np.ndarray) and marker.ndim == 2 and
  278. marker.shape[1] == 2):
  279. self._marker_function = self._set_vertices
  280. elif isinstance(marker, Path):
  281. self._marker_function = self._set_path_marker
  282. elif (isinstance(marker, Sized) and len(marker) in (2, 3) and
  283. marker[1] in (0, 1, 2)):
  284. self._marker_function = self._set_tuple_marker
  285. elif isinstance(marker, MarkerStyle):
  286. self.__dict__ = copy.deepcopy(marker.__dict__)
  287. else:
  288. try:
  289. Path(marker)
  290. self._marker_function = self._set_vertices
  291. except ValueError as err:
  292. raise ValueError(
  293. f'Unrecognized marker style {marker!r}') from err
  294. if not isinstance(marker, MarkerStyle):
  295. self._marker = marker
  296. self._recache()
  297. def get_path(self):
  298. """
  299. Return a `.Path` for the primary part of the marker.
  300. For unfilled markers this is the whole marker, for filled markers,
  301. this is the area to be drawn with *markerfacecolor*.
  302. """
  303. return self._path
  304. def get_transform(self):
  305. """
  306. Return the transform to be applied to the `.Path` from
  307. `MarkerStyle.get_path()`.
  308. """
  309. if self._user_transform is None:
  310. return self._transform.frozen()
  311. else:
  312. return (self._transform + self._user_transform).frozen()
  313. def get_alt_path(self):
  314. """
  315. Return a `.Path` for the alternate part of the marker.
  316. For unfilled markers, this is *None*; for filled markers, this is the
  317. area to be drawn with *markerfacecoloralt*.
  318. """
  319. return self._alt_path
  320. def get_alt_transform(self):
  321. """
  322. Return the transform to be applied to the `.Path` from
  323. `MarkerStyle.get_alt_path()`.
  324. """
  325. if self._user_transform is None:
  326. return self._alt_transform.frozen()
  327. else:
  328. return (self._alt_transform + self._user_transform).frozen()
  329. def get_snap_threshold(self):
  330. return self._snap_threshold
  331. def get_user_transform(self):
  332. """Return user supplied part of marker transform."""
  333. if self._user_transform is not None:
  334. return self._user_transform.frozen()
  335. def transformed(self, transform):
  336. """
  337. Return a new version of this marker with the transform applied.
  338. Parameters
  339. ----------
  340. transform : `~matplotlib.transforms.Affine2D`
  341. Transform will be combined with current user supplied transform.
  342. """
  343. new_marker = MarkerStyle(self)
  344. if new_marker._user_transform is not None:
  345. new_marker._user_transform += transform
  346. else:
  347. new_marker._user_transform = transform
  348. return new_marker
  349. def rotated(self, *, deg=None, rad=None):
  350. """
  351. Return a new version of this marker rotated by specified angle.
  352. Parameters
  353. ----------
  354. deg : float, optional
  355. Rotation angle in degrees.
  356. rad : float, optional
  357. Rotation angle in radians.
  358. .. note:: You must specify exactly one of deg or rad.
  359. """
  360. if deg is None and rad is None:
  361. raise ValueError('One of deg or rad is required')
  362. if deg is not None and rad is not None:
  363. raise ValueError('Only one of deg and rad can be supplied')
  364. new_marker = MarkerStyle(self)
  365. if new_marker._user_transform is None:
  366. new_marker._user_transform = Affine2D()
  367. if deg is not None:
  368. new_marker._user_transform.rotate_deg(deg)
  369. if rad is not None:
  370. new_marker._user_transform.rotate(rad)
  371. return new_marker
  372. def scaled(self, sx, sy=None):
  373. """
  374. Return new marker scaled by specified scale factors.
  375. If *sy* is not given, the same scale is applied in both the *x*- and
  376. *y*-directions.
  377. Parameters
  378. ----------
  379. sx : float
  380. *X*-direction scaling factor.
  381. sy : float, optional
  382. *Y*-direction scaling factor.
  383. """
  384. if sy is None:
  385. sy = sx
  386. new_marker = MarkerStyle(self)
  387. _transform = new_marker._user_transform or Affine2D()
  388. new_marker._user_transform = _transform.scale(sx, sy)
  389. return new_marker
  390. def _set_nothing(self):
  391. self._filled = False
  392. def _set_custom_marker(self, path):
  393. rescale = np.max(np.abs(path.vertices)) # max of x's and y's.
  394. self._transform = Affine2D().scale(0.5 / rescale)
  395. self._path = path
  396. def _set_path_marker(self):
  397. self._set_custom_marker(self._marker)
  398. def _set_vertices(self):
  399. self._set_custom_marker(Path(self._marker))
  400. def _set_tuple_marker(self):
  401. marker = self._marker
  402. if len(marker) == 2:
  403. numsides, rotation = marker[0], 0.0
  404. elif len(marker) == 3:
  405. numsides, rotation = marker[0], marker[2]
  406. symstyle = marker[1]
  407. if symstyle == 0:
  408. self._path = Path.unit_regular_polygon(numsides)
  409. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  410. elif symstyle == 1:
  411. self._path = Path.unit_regular_star(numsides)
  412. self._joinstyle = self._user_joinstyle or JoinStyle.bevel
  413. elif symstyle == 2:
  414. self._path = Path.unit_regular_asterisk(numsides)
  415. self._filled = False
  416. self._joinstyle = self._user_joinstyle or JoinStyle.bevel
  417. else:
  418. raise ValueError(f"Unexpected tuple marker: {marker}")
  419. self._transform = Affine2D().scale(0.5).rotate_deg(rotation)
  420. def _set_mathtext_path(self):
  421. """
  422. Draw mathtext markers '$...$' using `.TextPath` object.
  423. Submitted by tcb
  424. """
  425. from matplotlib.text import TextPath
  426. # again, the properties could be initialised just once outside
  427. # this function
  428. text = TextPath(xy=(0, 0), s=self.get_marker(),
  429. usetex=mpl.rcParams['text.usetex'])
  430. if len(text.vertices) == 0:
  431. return
  432. bbox = text.get_extents()
  433. max_dim = max(bbox.width, bbox.height)
  434. self._transform = (
  435. Affine2D()
  436. .translate(-bbox.xmin + 0.5 * -bbox.width, -bbox.ymin + 0.5 * -bbox.height)
  437. .scale(1.0 / max_dim))
  438. self._path = text
  439. self._snap = False
  440. def _half_fill(self):
  441. return self.get_fillstyle() in self._half_fillstyles
  442. def _set_circle(self, size=1.0):
  443. self._transform = Affine2D().scale(0.5 * size)
  444. self._snap_threshold = np.inf
  445. if not self._half_fill():
  446. self._path = Path.unit_circle()
  447. else:
  448. self._path = self._alt_path = Path.unit_circle_righthalf()
  449. fs = self.get_fillstyle()
  450. self._transform.rotate_deg(
  451. {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs])
  452. self._alt_transform = self._transform.frozen().rotate_deg(180.)
  453. def _set_point(self):
  454. self._set_circle(size=0.5)
  455. def _set_pixel(self):
  456. self._path = Path.unit_rectangle()
  457. # Ideally, you'd want -0.5, -0.5 here, but then the snapping
  458. # algorithm in the Agg backend will round this to a 2x2
  459. # rectangle from (-1, -1) to (1, 1). By offsetting it
  460. # slightly, we can force it to be (0, 0) to (1, 1), which both
  461. # makes it only be a single pixel and places it correctly
  462. # aligned to 1-width stroking (i.e. the ticks). This hack is
  463. # the best of a number of bad alternatives, mainly because the
  464. # backends are not aware of what marker is actually being used
  465. # beyond just its path data.
  466. self._transform = Affine2D().translate(-0.49999, -0.49999)
  467. self._snap_threshold = None
  468. _triangle_path = Path._create_closed([[0, 1], [-1, -1], [1, -1]])
  469. # Going down halfway looks to small. Golden ratio is too far.
  470. _triangle_path_u = Path._create_closed([[0, 1], [-3/5, -1/5], [3/5, -1/5]])
  471. _triangle_path_d = Path._create_closed(
  472. [[-3/5, -1/5], [3/5, -1/5], [1, -1], [-1, -1]])
  473. _triangle_path_l = Path._create_closed([[0, 1], [0, -1], [-1, -1]])
  474. _triangle_path_r = Path._create_closed([[0, 1], [0, -1], [1, -1]])
  475. def _set_triangle(self, rot, skip):
  476. self._transform = Affine2D().scale(0.5).rotate_deg(rot)
  477. self._snap_threshold = 5.0
  478. if not self._half_fill():
  479. self._path = self._triangle_path
  480. else:
  481. mpaths = [self._triangle_path_u,
  482. self._triangle_path_l,
  483. self._triangle_path_d,
  484. self._triangle_path_r]
  485. fs = self.get_fillstyle()
  486. if fs == 'top':
  487. self._path = mpaths[(0 + skip) % 4]
  488. self._alt_path = mpaths[(2 + skip) % 4]
  489. elif fs == 'bottom':
  490. self._path = mpaths[(2 + skip) % 4]
  491. self._alt_path = mpaths[(0 + skip) % 4]
  492. elif fs == 'left':
  493. self._path = mpaths[(1 + skip) % 4]
  494. self._alt_path = mpaths[(3 + skip) % 4]
  495. else:
  496. self._path = mpaths[(3 + skip) % 4]
  497. self._alt_path = mpaths[(1 + skip) % 4]
  498. self._alt_transform = self._transform
  499. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  500. def _set_triangle_up(self):
  501. return self._set_triangle(0.0, 0)
  502. def _set_triangle_down(self):
  503. return self._set_triangle(180.0, 2)
  504. def _set_triangle_left(self):
  505. return self._set_triangle(90.0, 3)
  506. def _set_triangle_right(self):
  507. return self._set_triangle(270.0, 1)
  508. def _set_square(self):
  509. self._transform = Affine2D().translate(-0.5, -0.5)
  510. self._snap_threshold = 2.0
  511. if not self._half_fill():
  512. self._path = Path.unit_rectangle()
  513. else:
  514. # Build a bottom filled square out of two rectangles, one filled.
  515. self._path = Path([[0.0, 0.0], [1.0, 0.0], [1.0, 0.5],
  516. [0.0, 0.5], [0.0, 0.0]])
  517. self._alt_path = Path([[0.0, 0.5], [1.0, 0.5], [1.0, 1.0],
  518. [0.0, 1.0], [0.0, 0.5]])
  519. fs = self.get_fillstyle()
  520. rotate = {'bottom': 0, 'right': 90, 'top': 180, 'left': 270}[fs]
  521. self._transform.rotate_deg(rotate)
  522. self._alt_transform = self._transform
  523. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  524. def _set_diamond(self):
  525. self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45)
  526. self._snap_threshold = 5.0
  527. if not self._half_fill():
  528. self._path = Path.unit_rectangle()
  529. else:
  530. self._path = Path([[0, 0], [1, 0], [1, 1], [0, 0]])
  531. self._alt_path = Path([[0, 0], [0, 1], [1, 1], [0, 0]])
  532. fs = self.get_fillstyle()
  533. rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs]
  534. self._transform.rotate_deg(rotate)
  535. self._alt_transform = self._transform
  536. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  537. def _set_thin_diamond(self):
  538. self._set_diamond()
  539. self._transform.scale(0.6, 1.0)
  540. def _set_pentagon(self):
  541. self._transform = Affine2D().scale(0.5)
  542. self._snap_threshold = 5.0
  543. polypath = Path.unit_regular_polygon(5)
  544. if not self._half_fill():
  545. self._path = polypath
  546. else:
  547. verts = polypath.vertices
  548. y = (1 + np.sqrt(5)) / 4.
  549. top = Path(verts[[0, 1, 4, 0]])
  550. bottom = Path(verts[[1, 2, 3, 4, 1]])
  551. left = Path([verts[0], verts[1], verts[2], [0, -y], verts[0]])
  552. right = Path([verts[0], verts[4], verts[3], [0, -y], verts[0]])
  553. self._path, self._alt_path = {
  554. 'top': (top, bottom), 'bottom': (bottom, top),
  555. 'left': (left, right), 'right': (right, left),
  556. }[self.get_fillstyle()]
  557. self._alt_transform = self._transform
  558. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  559. def _set_star(self):
  560. self._transform = Affine2D().scale(0.5)
  561. self._snap_threshold = 5.0
  562. polypath = Path.unit_regular_star(5, innerCircle=0.381966)
  563. if not self._half_fill():
  564. self._path = polypath
  565. else:
  566. verts = polypath.vertices
  567. top = Path(np.concatenate([verts[0:4], verts[7:10], verts[0:1]]))
  568. bottom = Path(np.concatenate([verts[3:8], verts[3:4]]))
  569. left = Path(np.concatenate([verts[0:6], verts[0:1]]))
  570. right = Path(np.concatenate([verts[0:1], verts[5:10], verts[0:1]]))
  571. self._path, self._alt_path = {
  572. 'top': (top, bottom), 'bottom': (bottom, top),
  573. 'left': (left, right), 'right': (right, left),
  574. }[self.get_fillstyle()]
  575. self._alt_transform = self._transform
  576. self._joinstyle = self._user_joinstyle or JoinStyle.bevel
  577. def _set_hexagon1(self):
  578. self._transform = Affine2D().scale(0.5)
  579. self._snap_threshold = None
  580. polypath = Path.unit_regular_polygon(6)
  581. if not self._half_fill():
  582. self._path = polypath
  583. else:
  584. verts = polypath.vertices
  585. # not drawing inside lines
  586. x = np.abs(np.cos(5 * np.pi / 6.))
  587. top = Path(np.concatenate([[(-x, 0)], verts[[1, 0, 5]], [(x, 0)]]))
  588. bottom = Path(np.concatenate([[(-x, 0)], verts[2:5], [(x, 0)]]))
  589. left = Path(verts[0:4])
  590. right = Path(verts[[0, 5, 4, 3]])
  591. self._path, self._alt_path = {
  592. 'top': (top, bottom), 'bottom': (bottom, top),
  593. 'left': (left, right), 'right': (right, left),
  594. }[self.get_fillstyle()]
  595. self._alt_transform = self._transform
  596. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  597. def _set_hexagon2(self):
  598. self._transform = Affine2D().scale(0.5).rotate_deg(30)
  599. self._snap_threshold = None
  600. polypath = Path.unit_regular_polygon(6)
  601. if not self._half_fill():
  602. self._path = polypath
  603. else:
  604. verts = polypath.vertices
  605. # not drawing inside lines
  606. x, y = np.sqrt(3) / 4, 3 / 4.
  607. top = Path(verts[[1, 0, 5, 4, 1]])
  608. bottom = Path(verts[1:5])
  609. left = Path(np.concatenate([
  610. [(x, y)], verts[:3], [(-x, -y), (x, y)]]))
  611. right = Path(np.concatenate([
  612. [(x, y)], verts[5:2:-1], [(-x, -y)]]))
  613. self._path, self._alt_path = {
  614. 'top': (top, bottom), 'bottom': (bottom, top),
  615. 'left': (left, right), 'right': (right, left),
  616. }[self.get_fillstyle()]
  617. self._alt_transform = self._transform
  618. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  619. def _set_octagon(self):
  620. self._transform = Affine2D().scale(0.5)
  621. self._snap_threshold = 5.0
  622. polypath = Path.unit_regular_polygon(8)
  623. if not self._half_fill():
  624. self._transform.rotate_deg(22.5)
  625. self._path = polypath
  626. else:
  627. x = np.sqrt(2.) / 4.
  628. self._path = self._alt_path = Path(
  629. [[0, -1], [0, 1], [-x, 1], [-1, x],
  630. [-1, -x], [-x, -1], [0, -1]])
  631. fs = self.get_fillstyle()
  632. self._transform.rotate_deg(
  633. {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs])
  634. self._alt_transform = self._transform.frozen().rotate_deg(180.0)
  635. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  636. _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]])
  637. def _set_vline(self):
  638. self._transform = Affine2D().scale(0.5)
  639. self._snap_threshold = 1.0
  640. self._filled = False
  641. self._path = self._line_marker_path
  642. def _set_hline(self):
  643. self._set_vline()
  644. self._transform = self._transform.rotate_deg(90)
  645. _tickhoriz_path = Path([[0.0, 0.0], [1.0, 0.0]])
  646. def _set_tickleft(self):
  647. self._transform = Affine2D().scale(-1.0, 1.0)
  648. self._snap_threshold = 1.0
  649. self._filled = False
  650. self._path = self._tickhoriz_path
  651. def _set_tickright(self):
  652. self._transform = Affine2D().scale(1.0, 1.0)
  653. self._snap_threshold = 1.0
  654. self._filled = False
  655. self._path = self._tickhoriz_path
  656. _tickvert_path = Path([[-0.0, 0.0], [-0.0, 1.0]])
  657. def _set_tickup(self):
  658. self._transform = Affine2D().scale(1.0, 1.0)
  659. self._snap_threshold = 1.0
  660. self._filled = False
  661. self._path = self._tickvert_path
  662. def _set_tickdown(self):
  663. self._transform = Affine2D().scale(1.0, -1.0)
  664. self._snap_threshold = 1.0
  665. self._filled = False
  666. self._path = self._tickvert_path
  667. _tri_path = Path([[0.0, 0.0], [0.0, -1.0],
  668. [0.0, 0.0], [0.8, 0.5],
  669. [0.0, 0.0], [-0.8, 0.5]],
  670. [Path.MOVETO, Path.LINETO,
  671. Path.MOVETO, Path.LINETO,
  672. Path.MOVETO, Path.LINETO])
  673. def _set_tri_down(self):
  674. self._transform = Affine2D().scale(0.5)
  675. self._snap_threshold = 5.0
  676. self._filled = False
  677. self._path = self._tri_path
  678. def _set_tri_up(self):
  679. self._set_tri_down()
  680. self._transform = self._transform.rotate_deg(180)
  681. def _set_tri_left(self):
  682. self._set_tri_down()
  683. self._transform = self._transform.rotate_deg(270)
  684. def _set_tri_right(self):
  685. self._set_tri_down()
  686. self._transform = self._transform.rotate_deg(90)
  687. _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]])
  688. def _set_caretdown(self):
  689. self._transform = Affine2D().scale(0.5)
  690. self._snap_threshold = 3.0
  691. self._filled = False
  692. self._path = self._caret_path
  693. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  694. def _set_caretup(self):
  695. self._set_caretdown()
  696. self._transform = self._transform.rotate_deg(180)
  697. def _set_caretleft(self):
  698. self._set_caretdown()
  699. self._transform = self._transform.rotate_deg(270)
  700. def _set_caretright(self):
  701. self._set_caretdown()
  702. self._transform = self._transform.rotate_deg(90)
  703. _caret_path_base = Path([[-1.0, 0.0], [0.0, -1.5], [1.0, 0]])
  704. def _set_caretdownbase(self):
  705. self._set_caretdown()
  706. self._path = self._caret_path_base
  707. def _set_caretupbase(self):
  708. self._set_caretdownbase()
  709. self._transform = self._transform.rotate_deg(180)
  710. def _set_caretleftbase(self):
  711. self._set_caretdownbase()
  712. self._transform = self._transform.rotate_deg(270)
  713. def _set_caretrightbase(self):
  714. self._set_caretdownbase()
  715. self._transform = self._transform.rotate_deg(90)
  716. _plus_path = Path([[-1.0, 0.0], [1.0, 0.0],
  717. [0.0, -1.0], [0.0, 1.0]],
  718. [Path.MOVETO, Path.LINETO,
  719. Path.MOVETO, Path.LINETO])
  720. def _set_plus(self):
  721. self._transform = Affine2D().scale(0.5)
  722. self._snap_threshold = 1.0
  723. self._filled = False
  724. self._path = self._plus_path
  725. _x_path = Path([[-1.0, -1.0], [1.0, 1.0],
  726. [-1.0, 1.0], [1.0, -1.0]],
  727. [Path.MOVETO, Path.LINETO,
  728. Path.MOVETO, Path.LINETO])
  729. def _set_x(self):
  730. self._transform = Affine2D().scale(0.5)
  731. self._snap_threshold = 3.0
  732. self._filled = False
  733. self._path = self._x_path
  734. _plus_filled_path = Path._create_closed(np.array([
  735. (-1, -3), (+1, -3), (+1, -1), (+3, -1), (+3, +1), (+1, +1),
  736. (+1, +3), (-1, +3), (-1, +1), (-3, +1), (-3, -1), (-1, -1)]) / 6)
  737. _plus_filled_path_t = Path._create_closed(np.array([
  738. (+3, 0), (+3, +1), (+1, +1), (+1, +3),
  739. (-1, +3), (-1, +1), (-3, +1), (-3, 0)]) / 6)
  740. def _set_plus_filled(self):
  741. self._transform = Affine2D()
  742. self._snap_threshold = 5.0
  743. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  744. if not self._half_fill():
  745. self._path = self._plus_filled_path
  746. else:
  747. # Rotate top half path to support all partitions
  748. self._path = self._alt_path = self._plus_filled_path_t
  749. fs = self.get_fillstyle()
  750. self._transform.rotate_deg(
  751. {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs])
  752. self._alt_transform = self._transform.frozen().rotate_deg(180)
  753. _x_filled_path = Path._create_closed(np.array([
  754. (-1, -2), (0, -1), (+1, -2), (+2, -1), (+1, 0), (+2, +1),
  755. (+1, +2), (0, +1), (-1, +2), (-2, +1), (-1, 0), (-2, -1)]) / 4)
  756. _x_filled_path_t = Path._create_closed(np.array([
  757. (+1, 0), (+2, +1), (+1, +2), (0, +1),
  758. (-1, +2), (-2, +1), (-1, 0)]) / 4)
  759. def _set_x_filled(self):
  760. self._transform = Affine2D()
  761. self._snap_threshold = 5.0
  762. self._joinstyle = self._user_joinstyle or JoinStyle.miter
  763. if not self._half_fill():
  764. self._path = self._x_filled_path
  765. else:
  766. # Rotate top half path to support all partitions
  767. self._path = self._alt_path = self._x_filled_path_t
  768. fs = self.get_fillstyle()
  769. self._transform.rotate_deg(
  770. {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs])
  771. self._alt_transform = self._transform.frozen().rotate_deg(180)