markers.py 32 KB

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