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