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