""" This module contains functions to handle markers. Used by both the marker functionality of `~matplotlib.axes.Axes.plot` and `~matplotlib.axes.Axes.scatter`.
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 ``""`` 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, style, angle)`` The marker can also be a tuple ``(numsides, style, angle)``, which will create a custom, regular symbol.
``numsides``: the number of sides
``style``: the style of the regular symbol:
- 0: a regular polygon - 1: a star-like symbol - 2: an asterisk - 3: a circle (``numsides`` and ``angle`` is ignored); deprecated.
``angle``: the angle of rotation of the symbol ============================== ====== =========================================
For backward compatibility, the form ``(verts, 0)`` is also accepted, but it is deprecated and equivalent to just ``verts`` for giving a raw set of vertices that define the shape.
``None`` is the default which means 'nothing', however this table is referred to from other docs for the valid inputs from marker inputs and in those cases ``None`` still means 'default'.
Note that special symbols can be defined via the :doc:`STIX math font </tutorials/text/mathtext>`, e.g. ``"$\u266B$"``. For an overview over the STIX font symbols refer to the `STIX font table <http://www.stixfonts.org/allGlyphs.html>`_. 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)
Examples showing the use of markers:
* :doc:`/gallery/lines_bars_and_markers/marker_reference` * :doc:`/gallery/lines_bars_and_markers/marker_fillstyle_reference` * :doc:`/gallery/shapes_and_collections/marker_path`
.. |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 """
# special-purpose marker identifiers: CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN, CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE) = range(12)
'.': '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. 'o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X')
# TODO: Is this ever used as a non-constant?
""" MarkerStyle
Attributes ---------- markers : list of known marks
fillstyles : list of known fillstyles
filled_markers : list of known filled markers.
Parameters ---------- marker : string or array_like, optional, default: None See the descriptions of possible markers in the module docstring.
fillstyle : string, optional, default: 'full' 'full', 'left", 'right', 'bottom', 'top', 'none' """
""" Sets fillstyle
Parameters ---------- fillstyle : string amongst known fillstyles """ raise ValueError("Unrecognized fillstyle %s" % ' '.join(self.fillstyles))
marker.shape[1] == 2): self._marker_function = self._set_vertices self._marker_function = self._set_mathtext_path self._marker_function = self._set_path_marker marker[1] in (0, 1, 2, 3)): self._marker_function = self._set_tuple_marker marker in self.markers): self, '_set_' + self.markers[marker]) else: try: Path(marker) self._marker_function = self._set_vertices except ValueError: raise ValueError('Unrecognized marker style {!r}' .format(marker))
return self._alt_transform.frozen()
verts = path.vertices rescale = max(np.max(np.abs(verts[:, 0])), np.max(np.abs(verts[:, 1]))) self._transform = Affine2D().scale(0.5 / rescale) self._path = path
self._set_custom_marker(self._marker)
verts = self._marker marker = Path(verts) self._set_custom_marker(marker)
marker = self._marker if isinstance(marker[0], Number): 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 = 'miter' elif symstyle == 1: self._path = Path.unit_regular_star(numsides) self._joinstyle = 'bevel' elif symstyle == 2: self._path = Path.unit_regular_asterisk(numsides) self._filled = False self._joinstyle = 'bevel' elif symstyle == 3: cbook.warn_deprecated( "3.0", "Setting a circle marker using `(..., 3)` is " "deprecated since Matplotlib 3.0, and support for it will " "be removed in 3.2. Directly pass 'o' instead.") self._path = Path.unit_circle() self._transform = Affine2D().scale(0.5).rotate_deg(rotation) else: cbook.warn_deprecated( "3.0", "Passing vertices as `(verts, 0)` is deprecated since " "Matplotlib 3.0, and support for it will be removed in 3.2. " "Directly pass `verts` instead.") verts = np.asarray(marker[0]) path = Path(verts) self._set_custom_marker(path)
""" Draws mathtext markers '$...$' using TextPath object.
Submitted by tcb """ from matplotlib.text import TextPath from matplotlib.font_manager import FontProperties
# again, the properties could be initialised just once outside # this function # Font size is irrelevant here, it will be rescaled based on # the drawn size later props = FontProperties(size=1.0) text = TextPath(xy=(0, 0), s=self.get_marker(), fontproperties=props, usetex=rcParams['text.usetex']) if len(text.vertices) == 0: return
xmin, ymin = text.vertices.min(axis=0) xmax, ymax = text.vertices.max(axis=0) width = xmax - xmin height = ymax - ymin max_dim = max(width, height) self._transform = Affine2D() \ .translate(-xmin + 0.5 * -width, -ymin + 0.5 * -height) \ .scale(1.0 / max_dim) self._path = text self._snap = False
else: # build a right-half circle if fs == 'bottom': rotate = 270. elif fs == 'top': rotate = 90. elif fs == 'left': rotate = 180. else: rotate = 0.
self._path = self._alt_path = Path.unit_circle_righthalf() self._transform.rotate_deg(rotate) self._alt_transform = self._transform.frozen().rotate_deg(180.)
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
[[0.0, 1.0], [-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) # Going down halfway looks to small. Golden ratio is too far. [[0.0, 1.0], [-3 / 5., -1 / 5.], [3 / 5., -1 / 5.], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) [[-3 / 5., -1 / 5.], [3 / 5., -1 / 5.], [1.0, -1.0], [-1.0, -1.0], [-3 / 5., -1 / 5.]], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) [[0.0, 1.0], [0.0, -1.0], [-1.0, -1.0], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) [[0.0, 1.0], [0.0, -1.0], [1.0, -1.0], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
self._transform = Affine2D().scale(0.5, 0.5).rotate_deg(rot) self._snap_threshold = 5.0 fs = self.get_fillstyle()
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]
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 = 'miter'
return self._set_triangle(0.0, 0)
return self._set_triangle(180.0, 2)
return self._set_triangle(90.0, 3)
return self._set_triangle(270.0, 1)
else: # build a bottom filled square out of two rectangles, one # filled. Use the rotation to support left, right, bottom # or top if fs == 'bottom': rotate = 0. elif fs == 'top': rotate = 180. elif fs == 'left': rotate = 270. else: rotate = 90.
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]]) self._transform.rotate_deg(rotate) self._alt_transform = self._transform
self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45) self._snap_threshold = 5.0 fs = self.get_fillstyle() if not self._half_fill(): self._path = Path.unit_rectangle() else: self._path = Path([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) self._alt_path = Path([[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.0, 0.0]])
if fs == 'bottom': rotate = 270. elif fs == 'top': rotate = 90. elif fs == 'left': rotate = 180. else: rotate = 0.
self._transform.rotate_deg(rotate) self._alt_transform = self._transform
self._joinstyle = 'miter'
self._set_diamond() self._transform.scale(0.6, 1.0)
self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0
polypath = Path.unit_regular_polygon(5) fs = self.get_fillstyle()
if not self._half_fill(): self._path = polypath else: verts = polypath.vertices
y = (1 + np.sqrt(5)) / 4. top = Path([verts[0], verts[1], verts[4], verts[0]]) bottom = Path([verts[1], verts[2], verts[3], verts[4], verts[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]])
if fs == 'top': mpath, mpath_alt = top, bottom elif fs == 'bottom': mpath, mpath_alt = bottom, top elif fs == 'left': mpath, mpath_alt = left, right else: mpath, mpath_alt = right, left self._path = mpath self._alt_path = mpath_alt self._alt_transform = self._transform
self._joinstyle = 'miter'
self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0
fs = self.get_fillstyle() polypath = Path.unit_regular_star(5, innerCircle=0.381966)
if not self._half_fill(): self._path = polypath else: verts = polypath.vertices
top = Path(np.vstack((verts[0:4, :], verts[7:10, :], verts[0]))) bottom = Path(np.vstack((verts[3:8, :], verts[3]))) left = Path(np.vstack((verts[0:6, :], verts[0]))) right = Path(np.vstack((verts[0], verts[5:10, :], verts[0])))
if fs == 'top': mpath, mpath_alt = top, bottom elif fs == 'bottom': mpath, mpath_alt = bottom, top elif fs == 'left': mpath, mpath_alt = left, right else: mpath, mpath_alt = right, left self._path = mpath self._alt_path = mpath_alt self._alt_transform = self._transform
self._joinstyle = 'bevel'
self._transform = Affine2D().scale(0.5) self._snap_threshold = None
fs = self.get_fillstyle() 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.vstack(([-x, 0], verts[(1, 0, 5), :], [x, 0]))) bottom = Path(np.vstack(([-x, 0], verts[2:5, :], [x, 0]))) left = Path(verts[(0, 1, 2, 3), :]) right = Path(verts[(0, 5, 4, 3), :])
if fs == 'top': mpath, mpath_alt = top, bottom elif fs == 'bottom': mpath, mpath_alt = bottom, top elif fs == 'left': mpath, mpath_alt = left, right else: mpath, mpath_alt = right, left
self._path = mpath self._alt_path = mpath_alt self._alt_transform = self._transform
self._joinstyle = 'miter'
self._transform = Affine2D().scale(0.5).rotate_deg(30) self._snap_threshold = None
fs = self.get_fillstyle() 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, 2, 3, 4), :]) left = Path(np.vstack(([x, y], verts[(0, 1, 2), :], [-x, -y], [x, y]))) right = Path(np.vstack(([x, y], verts[(5, 4, 3), :], [-x, -y])))
if fs == 'top': mpath, mpath_alt = top, bottom elif fs == 'bottom': mpath, mpath_alt = bottom, top elif fs == 'left': mpath, mpath_alt = left, right else: mpath, mpath_alt = right, left
self._path = mpath self._alt_path = mpath_alt self._alt_transform = self._transform
self._joinstyle = 'miter'
self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0
fs = self.get_fillstyle() 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. half = Path([[0, -1], [0, 1], [-x, 1], [-1, x], [-1, -x], [-x, -1], [0, -1]])
if fs == 'bottom': rotate = 90. elif fs == 'top': rotate = 270. elif fs == 'right': rotate = 180. else: rotate = 0.
self._transform.rotate_deg(rotate) self._path = self._alt_path = half self._alt_transform = self._transform.frozen().rotate_deg(180.0)
self._joinstyle = 'miter'
self._transform = Affine2D().scale(0.5) self._snap_threshold = 1.0 self._filled = False self._path = self._line_marker_path
self._set_vline() self._transform = self._transform.rotate_deg(90)
[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])
self._transform = Affine2D().scale(0.5) self._snap_threshold = 5.0 self._filled = False self._path = self._tri_path
self._set_tri_down() self._transform = self._transform.rotate_deg(180)
self._set_tri_down() self._transform = self._transform.rotate_deg(270)
self._set_tri_down() self._transform = self._transform.rotate_deg(90)
self._transform = Affine2D().scale(0.5) self._snap_threshold = 3.0 self._filled = False self._path = self._caret_path self._joinstyle = 'miter'
self._set_caretdown() self._transform = self._transform.rotate_deg(180)
self._set_caretdown() self._transform = self._transform.rotate_deg(270)
self._set_caretdown() self._transform = self._transform.rotate_deg(90)
self._set_caretdown() self._path = self._caret_path_base
self._set_caretdownbase() self._transform = self._transform.rotate_deg(180)
self._set_caretdownbase() self._transform = self._transform.rotate_deg(270)
self._set_caretdownbase() self._transform = self._transform.rotate_deg(90)
[0.0, -1.0], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO])
self._transform = Affine2D().scale(0.5) self._snap_threshold = 1.0 self._filled = False self._path = self._plus_path
[-1.0, 1.0], [1.0, -1.0]], [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO])
(1, 1/3), (1, 2/3), (2/3, 2/3), (2/3, 1), (1/3, 1), (1/3, 2/3), (0, 2/3), (0, 1/3), (1/3, 1/3), (1/3, 0)], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
(2/3, 1), (1/3, 1), (1/3, 2/3), (0, 2/3), (0, 1/2), (1, 1/2)], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
self._transform = Affine2D().translate(-0.5, -0.5) self._snap_threshold = 5.0 self._joinstyle = 'miter' fs = self.get_fillstyle() if not self._half_fill(): self._path = self._plus_filled_path else: # Rotate top half path to support all partitions if fs == 'top': rotate, rotate_alt = 0, 180 elif fs == 'bottom': rotate, rotate_alt = 180, 0 elif fs == 'left': rotate, rotate_alt = 90, 270 else: rotate, rotate_alt = 270, 90
self._path = self._plus_filled_path_t self._alt_path = self._plus_filled_path_t self._alt_transform = Affine2D().translate(-0.5, -0.5) self._transform.rotate_deg(rotate) self._alt_transform.rotate_deg(rotate_alt)
(0.75, 0.5), (1, 0.75), (0.75, 1), (0.5, 0.75), (0.25, 1), (0, 0.75), (0.25, 0.5), (0, 0.25), (0.25, 0)], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
(0.5, 0.75), (0.25, 1), (0, 0.75), (0.25, 0.5), (0.75, 0.5)], [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY])
self._transform = Affine2D().translate(-0.5, -0.5) self._snap_threshold = 5.0 self._joinstyle = 'miter' fs = self.get_fillstyle() if not self._half_fill(): self._path = self._x_filled_path else: # Rotate top half path to support all partitions if fs == 'top': rotate, rotate_alt = 0, 180 elif fs == 'bottom': rotate, rotate_alt = 180, 0 elif fs == 'left': rotate, rotate_alt = 90, 270 else: rotate, rotate_alt = 270, 90
self._path = self._x_filled_path_t self._alt_path = self._x_filled_path_t self._alt_transform = Affine2D().translate(-0.5, -0.5) self._transform.rotate_deg(rotate) self._alt_transform.rotate_deg(rotate_alt) |