r""" Functions to handle markers; used by the marker functionality of `~matplotlib.axes.Axes.plot`, `~matplotlib.axes.Axes.scatter`, and `~matplotlib.axes.Axes.errorbar`. All possible markers are defined here: ============================== ====== ========================================= marker symbol description ============================== ====== ========================================= ``"."`` |m00| point ``","`` |m01| pixel ``"o"`` |m02| circle ``"v"`` |m03| triangle_down ``"^"`` |m04| triangle_up ``"<"`` |m05| triangle_left ``">"`` |m06| triangle_right ``"1"`` |m07| tri_down ``"2"`` |m08| tri_up ``"3"`` |m09| tri_left ``"4"`` |m10| tri_right ``"8"`` |m11| octagon ``"s"`` |m12| square ``"p"`` |m13| pentagon ``"P"`` |m23| plus (filled) ``"*"`` |m14| star ``"h"`` |m15| hexagon1 ``"H"`` |m16| hexagon2 ``"+"`` |m17| plus ``"x"`` |m18| x ``"X"`` |m24| x (filled) ``"D"`` |m19| diamond ``"d"`` |m20| thin_diamond ``"|"`` |m21| vline ``"_"`` |m22| hline ``0`` (``TICKLEFT``) |m25| tickleft ``1`` (``TICKRIGHT``) |m26| tickright ``2`` (``TICKUP``) |m27| tickup ``3`` (``TICKDOWN``) |m28| tickdown ``4`` (``CARETLEFT``) |m29| caretleft ``5`` (``CARETRIGHT``) |m30| caretright ``6`` (``CARETUP``) |m31| caretup ``7`` (``CARETDOWN``) |m32| caretdown ``8`` (``CARETLEFTBASE``) |m33| caretleft (centered at base) ``9`` (``CARETRIGHTBASE``) |m34| caretright (centered at base) ``10`` (``CARETUPBASE``) |m35| caretup (centered at base) ``11`` (``CARETDOWNBASE``) |m36| caretdown (centered at base) ``"none"`` or ``"None"`` nothing ``" "`` or ``""`` nothing ``'$...$'`` |m37| Render the string using mathtext. E.g ``"$f$"`` for marker showing the letter ``f``. ``verts`` A list of (x, y) pairs used for Path vertices. The center of the marker is located at (0, 0) and the size is normalized, such that the created path is encapsulated inside the unit cell. path A `~matplotlib.path.Path` instance. ``(numsides, 0, angle)`` A regular polygon with ``numsides`` sides, rotated by ``angle``. ``(numsides, 1, angle)`` A star-like symbol with ``numsides`` sides, rotated by ``angle``. ``(numsides, 2, angle)`` An asterisk with ``numsides`` sides, rotated by ``angle``. ============================== ====== ========================================= As a deprecated feature, ``None`` also means 'nothing' when directly constructing a `.MarkerStyle`, but note that there are other contexts where ``marker=None`` instead means "the default marker" (e.g. :rc:`scatter.marker` for `.Axes.scatter`). Note that special symbols can be defined via the :ref:`STIX math font `, e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer to the `STIX font table `_. Also see the :doc:`/gallery/text_labels_and_annotations/stix_fonts_demo`. Integer numbers from ``0`` to ``11`` create lines and triangles. Those are equally accessible via capitalized variables, like ``CARETDOWNBASE``. Hence the following are equivalent:: plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) Markers join and cap styles can be customized by creating a new instance of MarkerStyle. A MarkerStyle can also have a custom `~matplotlib.transforms.Transform` allowing it to be arbitrarily rotated or offset. Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` * :doc:`/gallery/lines_bars_and_markers/scatter_star_poly` * :doc:`/gallery/lines_bars_and_markers/multivariate_marker_plot` .. |m00| image:: /_static/markers/m00.png .. |m01| image:: /_static/markers/m01.png .. |m02| image:: /_static/markers/m02.png .. |m03| image:: /_static/markers/m03.png .. |m04| image:: /_static/markers/m04.png .. |m05| image:: /_static/markers/m05.png .. |m06| image:: /_static/markers/m06.png .. |m07| image:: /_static/markers/m07.png .. |m08| image:: /_static/markers/m08.png .. |m09| image:: /_static/markers/m09.png .. |m10| image:: /_static/markers/m10.png .. |m11| image:: /_static/markers/m11.png .. |m12| image:: /_static/markers/m12.png .. |m13| image:: /_static/markers/m13.png .. |m14| image:: /_static/markers/m14.png .. |m15| image:: /_static/markers/m15.png .. |m16| image:: /_static/markers/m16.png .. |m17| image:: /_static/markers/m17.png .. |m18| image:: /_static/markers/m18.png .. |m19| image:: /_static/markers/m19.png .. |m20| image:: /_static/markers/m20.png .. |m21| image:: /_static/markers/m21.png .. |m22| image:: /_static/markers/m22.png .. |m23| image:: /_static/markers/m23.png .. |m24| image:: /_static/markers/m24.png .. |m25| image:: /_static/markers/m25.png .. |m26| image:: /_static/markers/m26.png .. |m27| image:: /_static/markers/m27.png .. |m28| image:: /_static/markers/m28.png .. |m29| image:: /_static/markers/m29.png .. |m30| image:: /_static/markers/m30.png .. |m31| image:: /_static/markers/m31.png .. |m32| image:: /_static/markers/m32.png .. |m33| image:: /_static/markers/m33.png .. |m34| image:: /_static/markers/m34.png .. |m35| image:: /_static/markers/m35.png .. |m36| image:: /_static/markers/m36.png .. |m37| image:: /_static/markers/m37.png """ import copy from collections.abc import Sized import numpy as np import matplotlib as mpl from . import _api, cbook from .path import Path from .transforms import IdentityTransform, Affine2D from ._enums import JoinStyle, CapStyle # special-purpose marker identifiers: (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN, CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN, CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) = range(12) _empty_path = Path(np.empty((0, 2))) class MarkerStyle: """ A class representing marker types. Instances are immutable. If you need to change anything, create a new instance. Attributes ---------- markers : dict All known markers. filled_markers : tuple All known filled markers. This is a subset of *markers*. fillstyles : tuple The supported fillstyles. """ markers = { '.': 'point', ',': 'pixel', 'o': 'circle', 'v': 'triangle_down', '^': 'triangle_up', '<': 'triangle_left', '>': 'triangle_right', '1': 'tri_down', '2': 'tri_up', '3': 'tri_left', '4': 'tri_right', '8': 'octagon', 's': 'square', 'p': 'pentagon', '*': 'star', 'h': 'hexagon1', 'H': 'hexagon2', '+': 'plus', 'x': 'x', 'D': 'diamond', 'd': 'thin_diamond', '|': 'vline', '_': 'hline', 'P': 'plus_filled', 'X': 'x_filled', TICKLEFT: 'tickleft', TICKRIGHT: 'tickright', TICKUP: 'tickup', TICKDOWN: 'tickdown', CARETLEFT: 'caretleft', CARETRIGHT: 'caretright', CARETUP: 'caretup', CARETDOWN: 'caretdown', CARETLEFTBASE: 'caretleftbase', CARETRIGHTBASE: 'caretrightbase', CARETUPBASE: 'caretupbase', CARETDOWNBASE: 'caretdownbase', "None": 'nothing', "none": 'nothing', ' ': 'nothing', '': 'nothing' } # Just used for informational purposes. is_filled() # is calculated in the _set_* functions. filled_markers = ( '.', 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X') fillstyles = ('full', 'left', 'right', 'bottom', 'top', 'none') _half_fillstyles = ('left', 'right', 'bottom', 'top') def __init__(self, marker, fillstyle=None, transform=None, capstyle=None, joinstyle=None): """ Parameters ---------- marker : str, array-like, Path, MarkerStyle, or None - Another instance of *MarkerStyle* copies the details of that ``marker``. - *None* means no marker. This is the deprecated default. - For other possible marker values, see the module docstring `matplotlib.markers`. fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. transform : transforms.Transform, default: None Transform that will be combined with the native transform of the marker. capstyle : `.CapStyle` or %(CapStyle)s, default: None Cap style that will override the default cap style of the marker. joinstyle : `.JoinStyle` or %(JoinStyle)s, default: None Join style that will override the default join style of the marker. """ self._marker_function = None self._user_transform = transform self._user_capstyle = CapStyle(capstyle) if capstyle is not None else None self._user_joinstyle = JoinStyle(joinstyle) if joinstyle is not None else None self._set_fillstyle(fillstyle) self._set_marker(marker) def _recache(self): if self._marker_function is None: return self._path = _empty_path self._transform = IdentityTransform() self._alt_path = None self._alt_transform = None self._snap_threshold = None self._joinstyle = JoinStyle.round self._capstyle = self._user_capstyle or CapStyle.butt # Initial guess: Assume the marker is filled unless the fillstyle is # set to 'none'. The marker function will override this for unfilled # markers. self._filled = self._fillstyle != 'none' self._marker_function() def __bool__(self): return bool(len(self._path.vertices)) def is_filled(self): return self._filled def get_fillstyle(self): return self._fillstyle def _set_fillstyle(self, fillstyle): """ Set the fillstyle. Parameters ---------- fillstyle : {'full', 'left', 'right', 'bottom', 'top', 'none'} The part of the marker surface that is colored with markerfacecolor. """ if fillstyle is None: fillstyle = mpl.rcParams['markers.fillstyle'] _api.check_in_list(self.fillstyles, fillstyle=fillstyle) self._fillstyle = fillstyle def get_joinstyle(self): return self._joinstyle.name def get_capstyle(self): return self._capstyle.name def get_marker(self): return self._marker def _set_marker(self, marker): """ Set the marker. Parameters ---------- marker : str, array-like, Path, MarkerStyle, or None, default: None - Another instance of *MarkerStyle* copies the details of that ``marker``. - *None* means no marker. - For other possible marker values see the module docstring `matplotlib.markers`. """ if isinstance(marker, str) and cbook.is_math_text(marker): self._marker_function = self._set_mathtext_path elif isinstance(marker, (int, str)) and marker in self.markers: self._marker_function = getattr(self, '_set_' + self.markers[marker]) elif (isinstance(marker, np.ndarray) and marker.ndim == 2 and marker.shape[1] == 2): self._marker_function = self._set_vertices elif isinstance(marker, Path): self._marker_function = self._set_path_marker elif (isinstance(marker, Sized) and len(marker) in (2, 3) and marker[1] in (0, 1, 2)): self._marker_function = self._set_tuple_marker elif isinstance(marker, MarkerStyle): self.__dict__ = copy.deepcopy(marker.__dict__) else: try: Path(marker) self._marker_function = self._set_vertices except ValueError as err: raise ValueError( f'Unrecognized marker style {marker!r}') from err if not isinstance(marker, MarkerStyle): self._marker = marker self._recache() def get_path(self): """ Return a `.Path` for the primary part of the marker. For unfilled markers this is the whole marker, for filled markers, this is the area to be drawn with *markerfacecolor*. """ return self._path def get_transform(self): """ Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ if self._user_transform is None: return self._transform.frozen() else: return (self._transform + self._user_transform).frozen() def get_alt_path(self): """ Return a `.Path` for the alternate part of the marker. For unfilled markers, this is *None*; for filled markers, this is the area to be drawn with *markerfacecoloralt*. """ return self._alt_path def get_alt_transform(self): """ Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ if self._user_transform is None: return self._alt_transform.frozen() else: return (self._alt_transform + self._user_transform).frozen() def get_snap_threshold(self): return self._snap_threshold def get_user_transform(self): """Return user supplied part of marker transform.""" if self._user_transform is not None: return self._user_transform.frozen() def transformed(self, transform: Affine2D): """ Return a new version of this marker with the transform applied. Parameters ---------- transform : `~matplotlib.transforms.Affine2D`, default: None Transform will be combined with current user supplied transform. """ new_marker = MarkerStyle(self) if new_marker._user_transform is not None: new_marker._user_transform += transform else: new_marker._user_transform = transform return new_marker def rotated(self, *, deg=None, rad=None): """ Return a new version of this marker rotated by specified angle. Parameters ---------- deg : float, default: None Rotation angle in degrees. rad : float, default: None Rotation angle in radians. .. note:: You must specify exactly one of deg or rad. """ if deg is None and rad is None: raise ValueError('One of deg or rad is required') if deg is not None and rad is not None: raise ValueError('Only one of deg and rad can be supplied') new_marker = MarkerStyle(self) if new_marker._user_transform is None: new_marker._user_transform = Affine2D() if deg is not None: new_marker._user_transform.rotate_deg(deg) if rad is not None: new_marker._user_transform.rotate(rad) return new_marker def scaled(self, sx, sy=None): """ Return new marker scaled by specified scale factors. If *sy* is None, the same scale is applied in both the *x*- and *y*-directions. Parameters ---------- sx : float *X*-direction scaling factor. sy : float, default: None *Y*-direction scaling factor. """ if sy is None: sy = sx new_marker = MarkerStyle(self) _transform = new_marker._user_transform or Affine2D() new_marker._user_transform = _transform.scale(sx, sy) return new_marker def _set_nothing(self): self._filled = False def _set_custom_marker(self, path): rescale = np.max(np.abs(path.vertices)) # max of x's and y's. self._transform = Affine2D().scale(0.5 / rescale) self._path = path def _set_path_marker(self): self._set_custom_marker(self._marker) def _set_vertices(self): self._set_custom_marker(Path(self._marker)) def _set_tuple_marker(self): marker = self._marker if len(marker) == 2: numsides, rotation = marker[0], 0.0 elif len(marker) == 3: numsides, rotation = marker[0], marker[2] symstyle = marker[1] if symstyle == 0: self._path = Path.unit_regular_polygon(numsides) self._joinstyle = self._user_joinstyle or JoinStyle.miter elif symstyle == 1: self._path = Path.unit_regular_star(numsides) self._joinstyle = self._user_joinstyle or JoinStyle.bevel elif symstyle == 2: self._path = Path.unit_regular_asterisk(numsides) self._filled = False self._joinstyle = self._user_joinstyle or JoinStyle.bevel else: raise ValueError(f"Unexpected tuple marker: {marker}") self._transform = Affine2D().scale(0.5).rotate_deg(rotation) def _set_mathtext_path(self): """ Draw mathtext markers '$...$' using `.TextPath` object. Submitted by tcb """ from matplotlib.text import TextPath # again, the properties could be initialised just once outside # this function text = TextPath(xy=(0, 0), s=self.get_marker(), usetex=mpl.rcParams['text.usetex']) if len(text.vertices) == 0: return bbox = text.get_extents() max_dim = max(bbox.width, bbox.height) self._transform = ( Affine2D() .translate(-bbox.xmin + 0.5 * -bbox.width, -bbox.ymin + 0.5 * -bbox.height) .scale(1.0 / max_dim)) self._path = text self._snap = False def _half_fill(self): return self.get_fillstyle() in self._half_fillstyles def _set_circle(self, size=1.0): self._transform = Affine2D().scale(0.5 * size) self._snap_threshold = np.inf if not self._half_fill(): self._path = Path.unit_circle() else: self._path = self._alt_path = Path.unit_circle_righthalf() fs = self.get_fillstyle() self._transform.rotate_deg( {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180.) def _set_point(self): self._set_circle(size=0.5) def _set_pixel(self): self._path = Path.unit_rectangle() # Ideally, you'd want -0.5, -0.5 here, but then the snapping # algorithm in the Agg backend will round this to a 2x2 # rectangle from (-1, -1) to (1, 1). By offsetting it # slightly, we can force it to be (0, 0) to (1, 1), which both # makes it only be a single pixel and places it correctly # aligned to 1-width stroking (i.e. the ticks). This hack is # the best of a number of bad alternatives, mainly because the # backends are not aware of what marker is actually being used # beyond just its path data. self._transform = Affine2D().translate(-0.49999, -0.49999) self._snap_threshold = None _triangle_path = Path._create_closed([[0, 1], [-1, -1], [1, -1]]) # Going down halfway looks to small. Golden ratio is too far. _triangle_path_u = Path._create_closed([[0, 1], [-3/5, -1/5], [3/5, -1/5]]) _triangle_path_d = Path._create_closed( [[-3/5, -1/5], [3/5, -1/5], [1, -1], [-1, -1]]) _triangle_path_l = Path._create_closed([[0, 1], [0, -1], [-1, -1]]) _triangle_path_r = Path._create_closed([[0, 1], [0, -1], [1, -1]]) def _set_triangle(self, rot, skip): self._transform = Affine2D().scale(0.5).rotate_deg(rot) self._snap_threshold = 5.0 if not self._half_fill(): self._path = self._triangle_path else: mpaths = [self._triangle_path_u, self._triangle_path_l, self._triangle_path_d, self._triangle_path_r] fs = self.get_fillstyle() if fs == 'top': self._path = mpaths[(0 + skip) % 4] self._alt_path = mpaths[(2 + skip) % 4] elif fs == 'bottom': self._path = mpaths[(2 + skip) % 4] self._alt_path = mpaths[(0 + skip) % 4] elif fs == 'left': self._path = mpaths[(1 + skip) % 4] self._alt_path = mpaths[(3 + skip) % 4] else: self._path = mpaths[(3 + skip) % 4] self._alt_path = mpaths[(1 + skip) % 4] self._alt_transform = self._transform self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_triangle_up(self): return self._set_triangle(0.0, 0) def _set_triangle_down(self): return self._set_triangle(180.0, 2) def _set_triangle_left(self): return self._set_triangle(90.0, 3) def _set_triangle_right(self): return self._set_triangle(270.0, 1) def _set_square(self): self._transform = Affine2D().translate(-0.5, -0.5) self._snap_threshold = 2.0 if not self._half_fill(): self._path = Path.unit_rectangle() else: # Build a bottom filled square out of two rectangles, one filled. self._path = Path([[0.0, 0.0], [1.0, 0.0], [1.0, 0.5], [0.0, 0.5], [0.0, 0.0]]) self._alt_path = Path([[0.0, 0.5], [1.0, 0.5], [1.0, 1.0], [0.0, 1.0], [0.0, 0.5]]) fs = self.get_fillstyle() rotate = {'bottom': 0, 'right': 90, 'top': 180, 'left': 270}[fs] self._transform.rotate_deg(rotate) self._alt_transform = self._transform self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_diamond(self): self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45) self._snap_threshold = 5.0 if not self._half_fill(): self._path = Path.unit_rectangle() else: self._path = Path([[0, 0], [1, 0], [1, 1], [0, 0]]) self._alt_path = Path([[0, 0], [0, 1], [1, 1], [0, 0]]) fs = self.get_fillstyle() rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs] self._transform.rotate_deg(rotate) self._alt_transform = self._transform self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_thin_diamond(self): self._set_diamond() self._transform.scale(0.6, 1.0) def _set_pentagon(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0 polypath = Path.unit_regular_polygon(5) if not self._half_fill(): self._path = polypath else: verts = polypath.vertices y = (1 + np.sqrt(5)) / 4. top = Path(verts[[0, 1, 4, 0]]) bottom = Path(verts[[1, 2, 3, 4, 1]]) left = Path([verts[0], verts[1], verts[2], [0, -y], verts[0]]) right = Path([verts[0], verts[4], verts[3], [0, -y], verts[0]]) self._path, self._alt_path = { 'top': (top, bottom), 'bottom': (bottom, top), 'left': (left, right), 'right': (right, left), }[self.get_fillstyle()] self._alt_transform = self._transform self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_star(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0 polypath = Path.unit_regular_star(5, innerCircle=0.381966) if not self._half_fill(): self._path = polypath else: verts = polypath.vertices top = Path(np.concatenate([verts[0:4], verts[7:10], verts[0:1]])) bottom = Path(np.concatenate([verts[3:8], verts[3:4]])) left = Path(np.concatenate([verts[0:6], verts[0:1]])) right = Path(np.concatenate([verts[0:1], verts[5:10], verts[0:1]])) self._path, self._alt_path = { 'top': (top, bottom), 'bottom': (bottom, top), 'left': (left, right), 'right': (right, left), }[self.get_fillstyle()] self._alt_transform = self._transform self._joinstyle = self._user_joinstyle or JoinStyle.bevel def _set_hexagon1(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = None polypath = Path.unit_regular_polygon(6) if not self._half_fill(): self._path = polypath else: verts = polypath.vertices # not drawing inside lines x = np.abs(np.cos(5 * np.pi / 6.)) top = Path(np.concatenate([[(-x, 0)], verts[[1, 0, 5]], [(x, 0)]])) bottom = Path(np.concatenate([[(-x, 0)], verts[2:5], [(x, 0)]])) left = Path(verts[0:4]) right = Path(verts[[0, 5, 4, 3]]) self._path, self._alt_path = { 'top': (top, bottom), 'bottom': (bottom, top), 'left': (left, right), 'right': (right, left), }[self.get_fillstyle()] self._alt_transform = self._transform self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_hexagon2(self): self._transform = Affine2D().scale(0.5).rotate_deg(30) self._snap_threshold = None polypath = Path.unit_regular_polygon(6) if not self._half_fill(): self._path = polypath else: verts = polypath.vertices # not drawing inside lines x, y = np.sqrt(3) / 4, 3 / 4. top = Path(verts[[1, 0, 5, 4, 1]]) bottom = Path(verts[1:5]) left = Path(np.concatenate([ [(x, y)], verts[:3], [(-x, -y), (x, y)]])) right = Path(np.concatenate([ [(x, y)], verts[5:2:-1], [(-x, -y)]])) self._path, self._alt_path = { 'top': (top, bottom), 'bottom': (bottom, top), 'left': (left, right), 'right': (right, left), }[self.get_fillstyle()] self._alt_transform = self._transform self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_octagon(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0 polypath = Path.unit_regular_polygon(8) if not self._half_fill(): self._transform.rotate_deg(22.5) self._path = polypath else: x = np.sqrt(2.) / 4. self._path = self._alt_path = Path( [[0, -1], [0, 1], [-x, 1], [-1, x], [-1, -x], [-x, -1], [0, -1]]) fs = self.get_fillstyle() self._transform.rotate_deg( {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180.0) self._joinstyle = self._user_joinstyle or JoinStyle.miter _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]]) def _set_vline(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 1.0 self._filled = False self._path = self._line_marker_path def _set_hline(self): self._set_vline() self._transform = self._transform.rotate_deg(90) _tickhoriz_path = Path([[0.0, 0.0], [1.0, 0.0]]) def _set_tickleft(self): self._transform = Affine2D().scale(-1.0, 1.0) self._snap_threshold = 1.0 self._filled = False self._path = self._tickhoriz_path def _set_tickright(self): self._transform = Affine2D().scale(1.0, 1.0) self._snap_threshold = 1.0 self._filled = False self._path = self._tickhoriz_path _tickvert_path = Path([[-0.0, 0.0], [-0.0, 1.0]]) def _set_tickup(self): self._transform = Affine2D().scale(1.0, 1.0) self._snap_threshold = 1.0 self._filled = False self._path = self._tickvert_path def _set_tickdown(self): self._transform = Affine2D().scale(1.0, -1.0) self._snap_threshold = 1.0 self._filled = False self._path = self._tickvert_path _tri_path = Path([[0.0, 0.0], [0.0, -1.0], [0.0, 0.0], [0.8, 0.5], [0.0, 0.0], [-0.8, 0.5]], [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO]) def _set_tri_down(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0 self._filled = False self._path = self._tri_path def _set_tri_up(self): self._set_tri_down() self._transform = self._transform.rotate_deg(180) def _set_tri_left(self): self._set_tri_down() self._transform = self._transform.rotate_deg(270) def _set_tri_right(self): self._set_tri_down() self._transform = self._transform.rotate_deg(90) _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]]) def _set_caretdown(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 3.0 self._filled = False self._path = self._caret_path self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_caretup(self): self._set_caretdown() self._transform = self._transform.rotate_deg(180) def _set_caretleft(self): self._set_caretdown() self._transform = self._transform.rotate_deg(270) def _set_caretright(self): self._set_caretdown() self._transform = self._transform.rotate_deg(90) _caret_path_base = Path([[-1.0, 0.0], [0.0, -1.5], [1.0, 0]]) def _set_caretdownbase(self): self._set_caretdown() self._path = self._caret_path_base def _set_caretupbase(self): self._set_caretdownbase() self._transform = self._transform.rotate_deg(180) def _set_caretleftbase(self): self._set_caretdownbase() self._transform = self._transform.rotate_deg(270) def _set_caretrightbase(self): self._set_caretdownbase() self._transform = self._transform.rotate_deg(90) _plus_path = Path([[-1.0, 0.0], [1.0, 0.0], [0.0, -1.0], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO]) def _set_plus(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 1.0 self._filled = False self._path = self._plus_path _x_path = Path([[-1.0, -1.0], [1.0, 1.0], [-1.0, 1.0], [1.0, -1.0]], [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO]) def _set_x(self): self._transform = Affine2D().scale(0.5) self._snap_threshold = 3.0 self._filled = False self._path = self._x_path _plus_filled_path = Path._create_closed(np.array([ (-1, -3), (+1, -3), (+1, -1), (+3, -1), (+3, +1), (+1, +1), (+1, +3), (-1, +3), (-1, +1), (-3, +1), (-3, -1), (-1, -1)]) / 6) _plus_filled_path_t = Path._create_closed(np.array([ (+3, 0), (+3, +1), (+1, +1), (+1, +3), (-1, +3), (-1, +1), (-3, +1), (-3, 0)]) / 6) def _set_plus_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._plus_filled_path else: # Rotate top half path to support all partitions self._path = self._alt_path = self._plus_filled_path_t fs = self.get_fillstyle() self._transform.rotate_deg( {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180) _x_filled_path = Path._create_closed(np.array([ (-1, -2), (0, -1), (+1, -2), (+2, -1), (+1, 0), (+2, +1), (+1, +2), (0, +1), (-1, +2), (-2, +1), (-1, 0), (-2, -1)]) / 4) _x_filled_path_t = Path._create_closed(np.array([ (+1, 0), (+2, +1), (+1, +2), (0, +1), (-1, +2), (-2, +1), (-1, 0)]) / 4) def _set_x_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._x_filled_path else: # Rotate top half path to support all partitions self._path = self._alt_path = self._x_filled_path_t fs = self.get_fillstyle() self._transform.rotate_deg( {'top': 0, 'left': 90, 'bottom': 180, 'right': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180)