markers.py 31 KB

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