Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/lines.py : 16%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This module contains all the 2D line class which can draw with a
3variety of line styles, markers and colors.
4"""
6# TODO: expose cap and join style attrs
7from numbers import Integral, Number, Real
8import logging
10import numpy as np
12from . import artist, cbook, colors as mcolors, docstring, rcParams
13from .artist import Artist, allow_rasterization
14from .cbook import (
15 _to_unmasked_float_array, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP)
16from .markers import MarkerStyle
17from .path import Path
18from .transforms import Bbox, TransformedPath
20# Imported here for backward compatibility, even though they don't
21# really belong.
22from . import _path
23from .markers import (
24 CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN,
25 CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE,
26 TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN)
28_log = logging.getLogger(__name__)
31def _get_dash_pattern(style):
32 """Convert linestyle -> dash pattern
33 """
34 # go from short hand -> full strings
35 if isinstance(style, str):
36 style = ls_mapper.get(style, style)
37 # un-dashed styles
38 if style in ['solid', 'None']:
39 offset, dashes = None, None
40 # dashed styles
41 elif style in ['dashed', 'dashdot', 'dotted']:
42 offset = 0
43 dashes = tuple(rcParams['lines.{}_pattern'.format(style)])
44 #
45 elif isinstance(style, tuple):
46 offset, dashes = style
47 else:
48 raise ValueError('Unrecognized linestyle: %s' % str(style))
50 # normalize offset to be positive and shorter than the dash cycle
51 if dashes is not None and offset is not None:
52 dsum = sum(dashes)
53 if dsum:
54 offset %= dsum
56 return offset, dashes
59def _scale_dashes(offset, dashes, lw):
60 if not rcParams['lines.scale_dashes']:
61 return offset, dashes
63 scaled_offset = scaled_dashes = None
64 if offset is not None:
65 scaled_offset = offset * lw
66 if dashes is not None:
67 scaled_dashes = [x * lw if x is not None else None
68 for x in dashes]
70 return scaled_offset, scaled_dashes
73def segment_hits(cx, cy, x, y, radius):
74 """
75 Return the indices of the segments in the polyline with coordinates (*cx*,
76 *cy*) that are within a distance *radius* of the point (*x*, *y*).
77 """
78 # Process single points specially
79 if len(x) <= 1:
80 res, = np.nonzero((cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2)
81 return res
83 # We need to lop the last element off a lot.
84 xr, yr = x[:-1], y[:-1]
86 # Only look at line segments whose nearest point to C on the line
87 # lies within the segment.
88 dx, dy = x[1:] - xr, y[1:] - yr
89 Lnorm_sq = dx ** 2 + dy ** 2 # Possibly want to eliminate Lnorm==0
90 u = ((cx - xr) * dx + (cy - yr) * dy) / Lnorm_sq
91 candidates = (u >= 0) & (u <= 1)
93 # Note that there is a little area near one side of each point
94 # which will be near neither segment, and another which will
95 # be near both, depending on the angle of the lines. The
96 # following radius test eliminates these ambiguities.
97 point_hits = (cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2
98 candidates = candidates & ~(point_hits[:-1] | point_hits[1:])
100 # For those candidates which remain, determine how far they lie away
101 # from the line.
102 px, py = xr + u * dx, yr + u * dy
103 line_hits = (cx - px) ** 2 + (cy - py) ** 2 <= radius ** 2
104 line_hits = line_hits & candidates
105 points, = point_hits.ravel().nonzero()
106 lines, = line_hits.ravel().nonzero()
107 return np.concatenate((points, lines))
110def _mark_every_path(markevery, tpath, affine, ax_transform):
111 """
112 Helper function that sorts out how to deal the input
113 `markevery` and returns the points where markers should be drawn.
115 Takes in the `markevery` value and the line path and returns the
116 sub-sampled path.
117 """
118 # pull out the two bits of data we want from the path
119 codes, verts = tpath.codes, tpath.vertices
121 def _slice_or_none(in_v, slc):
122 '''
123 Helper function to cope with `codes` being an
124 ndarray or `None`
125 '''
126 if in_v is None:
127 return None
128 return in_v[slc]
130 # if just an int, assume starting at 0 and make a tuple
131 if isinstance(markevery, Integral):
132 markevery = (0, markevery)
133 # if just a float, assume starting at 0.0 and make a tuple
134 elif isinstance(markevery, Real):
135 markevery = (0.0, markevery)
137 if isinstance(markevery, tuple):
138 if len(markevery) != 2:
139 raise ValueError('`markevery` is a tuple but its len is not 2; '
140 'markevery={}'.format(markevery))
141 start, step = markevery
142 # if step is an int, old behavior
143 if isinstance(step, Integral):
144 # tuple of 2 int is for backwards compatibility,
145 if not isinstance(start, Integral):
146 raise ValueError(
147 '`markevery` is a tuple with len 2 and second element is '
148 'an int, but the first element is not an int; markevery={}'
149 .format(markevery))
150 # just return, we are done here
152 return Path(verts[slice(start, None, step)],
153 _slice_or_none(codes, slice(start, None, step)))
155 elif isinstance(step, Real):
156 if not isinstance(start, Real):
157 raise ValueError(
158 '`markevery` is a tuple with len 2 and second element is '
159 'a float, but the first element is not a float or an int; '
160 'markevery={}'.format(markevery))
161 # calc cumulative distance along path (in display coords):
162 disp_coords = affine.transform(tpath.vertices)
163 delta = np.empty((len(disp_coords), 2))
164 delta[0, :] = 0
165 delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :]
166 delta = np.hypot(*delta.T).cumsum()
167 # calc distance between markers along path based on the axes
168 # bounding box diagonal being a distance of unity:
169 (x0, y0), (x1, y1) = ax_transform.transform([[0, 0], [1, 1]])
170 scale = np.hypot(x1 - x0, y1 - y0)
171 marker_delta = np.arange(start * scale, delta[-1], step * scale)
172 # find closest actual data point that is closest to
173 # the theoretical distance along the path:
174 inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis])
175 inds = inds.argmin(axis=1)
176 inds = np.unique(inds)
177 # return, we are done here
178 return Path(verts[inds], _slice_or_none(codes, inds))
179 else:
180 raise ValueError(
181 f"markevery={markevery!r} is a tuple with len 2, but its "
182 f"second element is not an int or a float")
184 elif isinstance(markevery, slice):
185 # mazol tov, it's already a slice, just return
186 return Path(verts[markevery], _slice_or_none(codes, markevery))
188 elif np.iterable(markevery):
189 # fancy indexing
190 try:
191 return Path(verts[markevery], _slice_or_none(codes, markevery))
192 except (ValueError, IndexError):
193 raise ValueError(
194 f"markevery={markevery!r} is iterable but not a valid numpy "
195 f"fancy index")
196 else:
197 raise ValueError(f"markevery={markevery!r} is not a recognized value")
200@cbook._define_aliases({
201 "antialiased": ["aa"],
202 "color": ["c"],
203 "drawstyle": ["ds"],
204 "linestyle": ["ls"],
205 "linewidth": ["lw"],
206 "markeredgecolor": ["mec"],
207 "markeredgewidth": ["mew"],
208 "markerfacecolor": ["mfc"],
209 "markerfacecoloralt": ["mfcalt"],
210 "markersize": ["ms"],
211})
212class Line2D(Artist):
213 """
214 A line - the line can have both a solid linestyle connecting all
215 the vertices, and a marker at each vertex. Additionally, the
216 drawing of the solid line is influenced by the drawstyle, e.g., one
217 can create "stepped" lines in various styles.
218 """
220 lineStyles = _lineStyles = { # hidden names deprecated
221 '-': '_draw_solid',
222 '--': '_draw_dashed',
223 '-.': '_draw_dash_dot',
224 ':': '_draw_dotted',
225 'None': '_draw_nothing',
226 ' ': '_draw_nothing',
227 '': '_draw_nothing',
228 }
230 _drawStyles_l = {
231 'default': '_draw_lines',
232 'steps-mid': '_draw_steps_mid',
233 'steps-pre': '_draw_steps_pre',
234 'steps-post': '_draw_steps_post',
235 }
237 _drawStyles_s = {
238 'steps': '_draw_steps_pre',
239 }
241 # drawStyles should now be deprecated.
242 drawStyles = {**_drawStyles_l, **_drawStyles_s}
243 # Need a list ordered with long names first:
244 drawStyleKeys = [*_drawStyles_l, *_drawStyles_s]
246 # Referenced here to maintain API. These are defined in
247 # MarkerStyle
248 markers = MarkerStyle.markers
249 filled_markers = MarkerStyle.filled_markers
250 fillStyles = MarkerStyle.fillstyles
252 zorder = 2
253 validCap = ('butt', 'round', 'projecting')
254 validJoin = ('miter', 'round', 'bevel')
256 def __str__(self):
257 if self._label != "":
258 return f"Line2D({self._label})"
259 elif self._x is None:
260 return "Line2D()"
261 elif len(self._x) > 3:
262 return "Line2D((%g,%g),(%g,%g),...,(%g,%g))" % (
263 self._x[0], self._y[0], self._x[0],
264 self._y[0], self._x[-1], self._y[-1])
265 else:
266 return "Line2D(%s)" % ",".join(
267 map("({:g},{:g})".format, self._x, self._y))
269 def __init__(self, xdata, ydata,
270 linewidth=None, # all Nones default to rc
271 linestyle=None,
272 color=None,
273 marker=None,
274 markersize=None,
275 markeredgewidth=None,
276 markeredgecolor=None,
277 markerfacecolor=None,
278 markerfacecoloralt='none',
279 fillstyle=None,
280 antialiased=None,
281 dash_capstyle=None,
282 solid_capstyle=None,
283 dash_joinstyle=None,
284 solid_joinstyle=None,
285 pickradius=5,
286 drawstyle=None,
287 markevery=None,
288 **kwargs
289 ):
290 """
291 Create a `.Line2D` instance with *x* and *y* data in sequences of
292 *xdata*, *ydata*.
294 Additional keyword arguments are `.Line2D` properties:
296 %(_Line2D_docstr)s
298 See :meth:`set_linestyle` for a description of the line styles,
299 :meth:`set_marker` for a description of the markers, and
300 :meth:`set_drawstyle` for a description of the draw styles.
302 """
303 Artist.__init__(self)
305 #convert sequences to numpy arrays
306 if not np.iterable(xdata):
307 raise RuntimeError('xdata must be a sequence')
308 if not np.iterable(ydata):
309 raise RuntimeError('ydata must be a sequence')
311 if linewidth is None:
312 linewidth = rcParams['lines.linewidth']
314 if linestyle is None:
315 linestyle = rcParams['lines.linestyle']
316 if marker is None:
317 marker = rcParams['lines.marker']
318 if markerfacecolor is None:
319 markerfacecolor = rcParams['lines.markerfacecolor']
320 if markeredgecolor is None:
321 markeredgecolor = rcParams['lines.markeredgecolor']
322 if color is None:
323 color = rcParams['lines.color']
325 if markersize is None:
326 markersize = rcParams['lines.markersize']
327 if antialiased is None:
328 antialiased = rcParams['lines.antialiased']
329 if dash_capstyle is None:
330 dash_capstyle = rcParams['lines.dash_capstyle']
331 if dash_joinstyle is None:
332 dash_joinstyle = rcParams['lines.dash_joinstyle']
333 if solid_capstyle is None:
334 solid_capstyle = rcParams['lines.solid_capstyle']
335 if solid_joinstyle is None:
336 solid_joinstyle = rcParams['lines.solid_joinstyle']
338 if isinstance(linestyle, str):
339 ds, ls = self._split_drawstyle_linestyle(linestyle)
340 if ds is not None and drawstyle is not None and ds != drawstyle:
341 raise ValueError("Inconsistent drawstyle ({!r}) and linestyle "
342 "({!r})".format(drawstyle, linestyle))
343 linestyle = ls
345 if ds is not None:
346 drawstyle = ds
348 if drawstyle is None:
349 drawstyle = 'default'
351 self._dashcapstyle = None
352 self._dashjoinstyle = None
353 self._solidjoinstyle = None
354 self._solidcapstyle = None
355 self.set_dash_capstyle(dash_capstyle)
356 self.set_dash_joinstyle(dash_joinstyle)
357 self.set_solid_capstyle(solid_capstyle)
358 self.set_solid_joinstyle(solid_joinstyle)
360 self._linestyles = None
361 self._drawstyle = None
362 self._linewidth = linewidth
364 # scaled dash + offset
365 self._dashSeq = None
366 self._dashOffset = 0
367 # unscaled dash + offset
368 # this is needed scaling the dash pattern by linewidth
369 self._us_dashSeq = None
370 self._us_dashOffset = 0
372 self.set_linewidth(linewidth)
373 self.set_linestyle(linestyle)
374 self.set_drawstyle(drawstyle)
376 self._color = None
377 self.set_color(color)
378 self._marker = MarkerStyle(marker, fillstyle)
380 self._markevery = None
381 self._markersize = None
382 self._antialiased = None
384 self.set_markevery(markevery)
385 self.set_antialiased(antialiased)
386 self.set_markersize(markersize)
388 self._markeredgecolor = None
389 self._markeredgewidth = None
390 self._markerfacecolor = None
391 self._markerfacecoloralt = None
393 self.set_markerfacecolor(markerfacecolor)
394 self.set_markerfacecoloralt(markerfacecoloralt)
395 self.set_markeredgecolor(markeredgecolor)
396 self.set_markeredgewidth(markeredgewidth)
398 # update kwargs before updating data to give the caller a
399 # chance to init axes (and hence unit support)
400 self.update(kwargs)
401 self.pickradius = pickradius
402 self.ind_offset = 0
403 if isinstance(self._picker, Number):
404 self.pickradius = self._picker
406 self._xorig = np.asarray([])
407 self._yorig = np.asarray([])
408 self._invalidx = True
409 self._invalidy = True
410 self._x = None
411 self._y = None
412 self._xy = None
413 self._path = None
414 self._transformed_path = None
415 self._subslice = False
416 self._x_filled = None # used in subslicing; only x is needed
418 self.set_data(xdata, ydata)
420 @cbook.deprecated("3.1")
421 @property
422 def verticalOffset(self):
423 return None
425 def contains(self, mouseevent):
426 """
427 Test whether the mouse event occurred on the line. The pick
428 radius determines the precision of the location test (usually
429 within five points of the value). Use
430 :meth:`~matplotlib.lines.Line2D.get_pickradius` or
431 :meth:`~matplotlib.lines.Line2D.set_pickradius` to view or
432 modify it.
434 Parameters
435 ----------
436 mouseevent : `matplotlib.backend_bases.MouseEvent`
438 Returns
439 -------
440 contains : bool
441 Whether any values are within the radius.
442 details : dict
443 A dictionary ``{'ind': pointlist}``, where *pointlist* is a
444 list of points of the line that are within the pickradius around
445 the event position.
447 TODO: sort returned indices by distance
448 """
449 inside, info = self._default_contains(mouseevent)
450 if inside is not None:
451 return inside, info
453 if not isinstance(self.pickradius, Number):
454 raise ValueError("pick radius should be a distance")
456 # Make sure we have data to plot
457 if self._invalidy or self._invalidx:
458 self.recache()
459 if len(self._xy) == 0:
460 return False, {}
462 # Convert points to pixels
463 transformed_path = self._get_transformed_path()
464 path, affine = transformed_path.get_transformed_path_and_affine()
465 path = affine.transform_path(path)
466 xy = path.vertices
467 xt = xy[:, 0]
468 yt = xy[:, 1]
470 # Convert pick radius from points to pixels
471 if self.figure is None:
472 _log.warning('no figure set when check if mouse is on line')
473 pixels = self.pickradius
474 else:
475 pixels = self.figure.dpi / 72. * self.pickradius
477 # The math involved in checking for containment (here and inside of
478 # segment_hits) assumes that it is OK to overflow, so temporarily set
479 # the error flags accordingly.
480 with np.errstate(all='ignore'):
481 # Check for collision
482 if self._linestyle in ['None', None]:
483 # If no line, return the nearby point(s)
484 ind, = np.nonzero(
485 (xt - mouseevent.x) ** 2 + (yt - mouseevent.y) ** 2
486 <= pixels ** 2)
487 else:
488 # If line, return the nearby segment(s)
489 ind = segment_hits(mouseevent.x, mouseevent.y, xt, yt, pixels)
490 if self._drawstyle.startswith("steps"):
491 ind //= 2
493 ind += self.ind_offset
495 # Return the point(s) within radius
496 return len(ind) > 0, dict(ind=ind)
498 def get_pickradius(self):
499 """
500 Return the pick radius used for containment tests.
502 See `.contains` for more details.
503 """
504 return self.pickradius
506 def set_pickradius(self, d):
507 """Set the pick radius used for containment tests.
509 See `.contains` for more details.
511 Parameters
512 ----------
513 d : float
514 Pick radius, in points.
515 """
516 self.pickradius = d
518 def get_fillstyle(self):
519 """
520 Return the marker fill style.
522 See also `~.Line2D.set_fillstyle`.
523 """
524 return self._marker.get_fillstyle()
526 def set_fillstyle(self, fs):
527 """
528 Set the marker fill style.
530 Parameters
531 ----------
532 fs : {'full', 'left', 'right', 'bottom', 'top', 'none'}
533 Possible values:
535 - 'full': Fill the whole marker with the *markerfacecolor*.
536 - 'left', 'right', 'bottom', 'top': Fill the marker half at
537 the given side with the *markerfacecolor*. The other
538 half of the marker is filled with *markerfacecoloralt*.
539 - 'none': No filling.
541 For examples see
542 :doc:`/gallery/lines_bars_and_markers/marker_fillstyle_reference`.
543 """
544 self._marker.set_fillstyle(fs)
545 self.stale = True
547 def set_markevery(self, every):
548 """Set the markevery property to subsample the plot when using markers.
550 e.g., if `every=5`, every 5-th marker will be plotted.
552 Parameters
553 ----------
554 every : None or int or (int, int) or slice or List[int] or float or \
555(float, float)
556 Which markers to plot.
558 - every=None, every point will be plotted.
559 - every=N, every N-th marker will be plotted starting with
560 marker 0.
561 - every=(start, N), every N-th marker, starting at point
562 start, will be plotted.
563 - every=slice(start, end, N), every N-th marker, starting at
564 point start, up to but not including point end, will be plotted.
565 - every=[i, j, m, n], only markers at points i, j, m, and n
566 will be plotted.
567 - every=0.1, (i.e. a float) then markers will be spaced at
568 approximately equal distances along the line; the distance
569 along the line between markers is determined by multiplying the
570 display-coordinate distance of the axes bounding-box diagonal
571 by the value of every.
572 - every=(0.5, 0.1) (i.e. a length-2 tuple of float), the same
573 functionality as every=0.1 is exhibited but the first marker will
574 be 0.5 multiplied by the display-coordinate-diagonal-distance
575 along the line.
577 For examples see
578 :doc:`/gallery/lines_bars_and_markers/markevery_demo`.
580 Notes
581 -----
582 Setting the markevery property will only show markers at actual data
583 points. When using float arguments to set the markevery property
584 on irregularly spaced data, the markers will likely not appear evenly
585 spaced because the actual data points do not coincide with the
586 theoretical spacing between markers.
588 When using a start offset to specify the first marker, the offset will
589 be from the first data point which may be different from the first
590 the visible data point if the plot is zoomed in.
592 If zooming in on a plot when using float arguments then the actual
593 data points that have markers will change because the distance between
594 markers is always determined from the display-coordinates
595 axes-bounding-box-diagonal regardless of the actual axes data limits.
597 """
598 if self._markevery != every:
599 self.stale = True
600 self._markevery = every
602 def get_markevery(self):
603 """
604 Return the markevery setting for marker subsampling.
606 See also `~.Line2D.set_markevery`.
607 """
608 return self._markevery
610 def set_picker(self, p):
611 """Sets the event picker details for the line.
613 Parameters
614 ----------
615 p : float or callable[[Artist, Event], Tuple[bool, dict]]
616 If a float, it is used as the pick radius in points.
617 """
618 if callable(p):
619 self._contains = p
620 else:
621 self.pickradius = p
622 self._picker = p
624 def get_window_extent(self, renderer):
625 bbox = Bbox([[0, 0], [0, 0]])
626 trans_data_to_xy = self.get_transform().transform
627 bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()),
628 ignore=True)
629 # correct for marker size, if any
630 if self._marker:
631 ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5
632 bbox = bbox.padded(ms)
633 return bbox
635 @Artist.axes.setter
636 def axes(self, ax):
637 # call the set method from the base-class property
638 Artist.axes.fset(self, ax)
639 if ax is not None:
640 # connect unit-related callbacks
641 if ax.xaxis is not None:
642 self._xcid = ax.xaxis.callbacks.connect('units',
643 self.recache_always)
644 if ax.yaxis is not None:
645 self._ycid = ax.yaxis.callbacks.connect('units',
646 self.recache_always)
648 def set_data(self, *args):
649 """
650 Set the x and y data.
652 Parameters
653 ----------
654 *args : (2, N) array or two 1D arrays
655 """
656 if len(args) == 1:
657 (x, y), = args
658 else:
659 x, y = args
661 self.set_xdata(x)
662 self.set_ydata(y)
664 def recache_always(self):
665 self.recache(always=True)
667 def recache(self, always=False):
668 if always or self._invalidx:
669 xconv = self.convert_xunits(self._xorig)
670 x = _to_unmasked_float_array(xconv).ravel()
671 else:
672 x = self._x
673 if always or self._invalidy:
674 yconv = self.convert_yunits(self._yorig)
675 y = _to_unmasked_float_array(yconv).ravel()
676 else:
677 y = self._y
679 self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
680 self._x, self._y = self._xy.T # views
682 self._subslice = False
683 if (self.axes and len(x) > 1000 and self._is_sorted(x) and
684 self.axes.name == 'rectilinear' and
685 self.axes.get_xscale() == 'linear' and
686 self._markevery is None and
687 self.get_clip_on()):
688 self._subslice = True
689 nanmask = np.isnan(x)
690 if nanmask.any():
691 self._x_filled = self._x.copy()
692 indices = np.arange(len(x))
693 self._x_filled[nanmask] = np.interp(indices[nanmask],
694 indices[~nanmask], self._x[~nanmask])
695 else:
696 self._x_filled = self._x
698 if self._path is not None:
699 interpolation_steps = self._path._interpolation_steps
700 else:
701 interpolation_steps = 1
702 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T)
703 self._path = Path(np.asarray(xy).T,
704 _interpolation_steps=interpolation_steps)
705 self._transformed_path = None
706 self._invalidx = False
707 self._invalidy = False
709 def _transform_path(self, subslice=None):
710 """
711 Puts a TransformedPath instance at self._transformed_path;
712 all invalidation of the transform is then handled by the
713 TransformedPath instance.
714 """
715 # Masked arrays are now handled by the Path class itself
716 if subslice is not None:
717 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T)
718 _path = Path(np.asarray(xy).T,
719 _interpolation_steps=self._path._interpolation_steps)
720 else:
721 _path = self._path
722 self._transformed_path = TransformedPath(_path, self.get_transform())
724 def _get_transformed_path(self):
725 """
726 Return the :class:`~matplotlib.transforms.TransformedPath` instance
727 of this line.
728 """
729 if self._transformed_path is None:
730 self._transform_path()
731 return self._transformed_path
733 def set_transform(self, t):
734 """
735 Set the Transformation instance used by this artist.
737 Parameters
738 ----------
739 t : `matplotlib.transforms.Transform`
740 """
741 Artist.set_transform(self, t)
742 self._invalidx = True
743 self._invalidy = True
744 self.stale = True
746 def _is_sorted(self, x):
747 """Return whether x is sorted in ascending order."""
748 # We don't handle the monotonically decreasing case.
749 return _path.is_sorted(x)
751 @allow_rasterization
752 def draw(self, renderer):
753 # docstring inherited from Artist.draw.
755 if not self.get_visible():
756 return
758 if self._invalidy or self._invalidx:
759 self.recache()
760 self.ind_offset = 0 # Needed for contains() method.
761 if self._subslice and self.axes:
762 x0, x1 = self.axes.get_xbound()
763 i0 = self._x_filled.searchsorted(x0, 'left')
764 i1 = self._x_filled.searchsorted(x1, 'right')
765 subslice = slice(max(i0 - 1, 0), i1 + 1)
766 self.ind_offset = subslice.start
767 self._transform_path(subslice)
768 else:
769 subslice = None
771 if self.get_path_effects():
772 from matplotlib.patheffects import PathEffectRenderer
773 renderer = PathEffectRenderer(self.get_path_effects(), renderer)
775 renderer.open_group('line2d', self.get_gid())
776 if self._lineStyles[self._linestyle] != '_draw_nothing':
777 tpath, affine = (self._get_transformed_path()
778 .get_transformed_path_and_affine())
779 if len(tpath.vertices):
780 gc = renderer.new_gc()
781 self._set_gc_clip(gc)
783 lc_rgba = mcolors.to_rgba(self._color, self._alpha)
784 gc.set_foreground(lc_rgba, isRGBA=True)
786 gc.set_antialiased(self._antialiased)
787 gc.set_linewidth(self._linewidth)
789 if self.is_dashed():
790 cap = self._dashcapstyle
791 join = self._dashjoinstyle
792 else:
793 cap = self._solidcapstyle
794 join = self._solidjoinstyle
795 gc.set_joinstyle(join)
796 gc.set_capstyle(cap)
797 gc.set_snap(self.get_snap())
798 if self.get_sketch_params() is not None:
799 gc.set_sketch_params(*self.get_sketch_params())
801 gc.set_dashes(self._dashOffset, self._dashSeq)
802 renderer.draw_path(gc, tpath, affine.frozen())
803 gc.restore()
805 if self._marker and self._markersize > 0:
806 gc = renderer.new_gc()
807 self._set_gc_clip(gc)
808 gc.set_linewidth(self._markeredgewidth)
809 gc.set_antialiased(self._antialiased)
811 ec_rgba = mcolors.to_rgba(
812 self.get_markeredgecolor(), self._alpha)
813 fc_rgba = mcolors.to_rgba(
814 self._get_markerfacecolor(), self._alpha)
815 fcalt_rgba = mcolors.to_rgba(
816 self._get_markerfacecolor(alt=True), self._alpha)
817 # If the edgecolor is "auto", it is set according to the *line*
818 # color but inherits the alpha value of the *face* color, if any.
819 if (cbook._str_equal(self._markeredgecolor, "auto")
820 and not cbook._str_lower_equal(
821 self.get_markerfacecolor(), "none")):
822 ec_rgba = ec_rgba[:3] + (fc_rgba[3],)
823 gc.set_foreground(ec_rgba, isRGBA=True)
824 if self.get_sketch_params() is not None:
825 scale, length, randomness = self.get_sketch_params()
826 gc.set_sketch_params(scale/2, length/2, 2*randomness)
828 marker = self._marker
830 # Markers *must* be drawn ignoring the drawstyle (but don't pay the
831 # recaching if drawstyle is already "default").
832 if self.get_drawstyle() != "default":
833 with cbook._setattr_cm(
834 self, _drawstyle="default", _transformed_path=None):
835 self.recache()
836 self._transform_path(subslice)
837 tpath, affine = (self._get_transformed_path()
838 .get_transformed_points_and_affine())
839 else:
840 tpath, affine = (self._get_transformed_path()
841 .get_transformed_points_and_affine())
843 if len(tpath.vertices):
844 # subsample the markers if markevery is not None
845 markevery = self.get_markevery()
846 if markevery is not None:
847 subsampled = _mark_every_path(markevery, tpath,
848 affine, self.axes.transAxes)
849 else:
850 subsampled = tpath
852 snap = marker.get_snap_threshold()
853 if isinstance(snap, Real):
854 snap = renderer.points_to_pixels(self._markersize) >= snap
855 gc.set_snap(snap)
856 gc.set_joinstyle(marker.get_joinstyle())
857 gc.set_capstyle(marker.get_capstyle())
858 marker_path = marker.get_path()
859 marker_trans = marker.get_transform()
860 w = renderer.points_to_pixels(self._markersize)
862 if cbook._str_equal(marker.get_marker(), ","):
863 gc.set_linewidth(0)
864 else:
865 # Don't scale for pixels, and don't stroke them
866 marker_trans = marker_trans.scale(w)
867 renderer.draw_markers(gc, marker_path, marker_trans,
868 subsampled, affine.frozen(),
869 fc_rgba)
871 alt_marker_path = marker.get_alt_path()
872 if alt_marker_path:
873 alt_marker_trans = marker.get_alt_transform()
874 alt_marker_trans = alt_marker_trans.scale(w)
875 renderer.draw_markers(
876 gc, alt_marker_path, alt_marker_trans, subsampled,
877 affine.frozen(), fcalt_rgba)
879 gc.restore()
881 renderer.close_group('line2d')
882 self.stale = False
884 def get_antialiased(self):
885 """Return whether antialiased rendering is used."""
886 return self._antialiased
888 def get_color(self):
889 """
890 Return the line color.
892 See also `~.Line2D.set_color`.
893 """
894 return self._color
896 def get_drawstyle(self):
897 """
898 Return the drawstyle.
900 See also `~.Line2D.set_drawstyle`.
901 """
902 return self._drawstyle
904 def get_linestyle(self):
905 """
906 Return the linestyle.
908 See also `~.Line2D.set_linestyle`.
909 """
910 return self._linestyle
912 def get_linewidth(self):
913 """
914 Return the linewidth in points.
916 See also `~.Line2D.set_linewidth`.
917 """
918 return self._linewidth
920 def get_marker(self):
921 """
922 Return the line marker.
924 See also `~.Line2D.set_marker`.
925 """
926 return self._marker.get_marker()
928 def get_markeredgecolor(self):
929 """
930 Return the marker edge color.
932 See also `~.Line2D.set_markeredgecolor`.
933 """
934 mec = self._markeredgecolor
935 if cbook._str_equal(mec, 'auto'):
936 if rcParams['_internal.classic_mode']:
937 if self._marker.get_marker() in ('.', ','):
938 return self._color
939 if self._marker.is_filled() and self.get_fillstyle() != 'none':
940 return 'k' # Bad hard-wired default...
941 return self._color
942 else:
943 return mec
945 def get_markeredgewidth(self):
946 """
947 Return the marker edge width in points.
949 See also `~.Line2D.set_markeredgewidth`.
950 """
951 return self._markeredgewidth
953 def _get_markerfacecolor(self, alt=False):
954 fc = self._markerfacecoloralt if alt else self._markerfacecolor
955 if cbook._str_lower_equal(fc, 'auto'):
956 if self.get_fillstyle() == 'none':
957 return 'none'
958 else:
959 return self._color
960 else:
961 return fc
963 def get_markerfacecolor(self):
964 """
965 Return the marker face color.
967 See also `~.Line2D.set_markerfacecolor`.
968 """
969 return self._get_markerfacecolor(alt=False)
971 def get_markerfacecoloralt(self):
972 """
973 Return the alternate marker face color.
975 See also `~.Line2D.set_markerfacecoloralt`.
976 """
977 return self._get_markerfacecolor(alt=True)
979 def get_markersize(self):
980 """
981 Return the marker size in points.
983 See also `~.Line2D.set_markersize`.
984 """
985 return self._markersize
987 def get_data(self, orig=True):
988 """
989 Return the xdata, ydata.
991 If *orig* is *True*, return the original data.
992 """
993 return self.get_xdata(orig=orig), self.get_ydata(orig=orig)
995 def get_xdata(self, orig=True):
996 """
997 Return the xdata.
999 If *orig* is *True*, return the original data, else the
1000 processed data.
1001 """
1002 if orig:
1003 return self._xorig
1004 if self._invalidx:
1005 self.recache()
1006 return self._x
1008 def get_ydata(self, orig=True):
1009 """
1010 Return the ydata.
1012 If *orig* is *True*, return the original data, else the
1013 processed data.
1014 """
1015 if orig:
1016 return self._yorig
1017 if self._invalidy:
1018 self.recache()
1019 return self._y
1021 def get_path(self):
1022 """
1023 Return the :class:`~matplotlib.path.Path` object associated
1024 with this line.
1025 """
1026 if self._invalidy or self._invalidx:
1027 self.recache()
1028 return self._path
1030 def get_xydata(self):
1031 """
1032 Return the *xy* data as a Nx2 numpy array.
1033 """
1034 if self._invalidy or self._invalidx:
1035 self.recache()
1036 return self._xy
1038 def set_antialiased(self, b):
1039 """
1040 Set whether to use antialiased rendering.
1042 Parameters
1043 ----------
1044 b : bool
1045 """
1046 if self._antialiased != b:
1047 self.stale = True
1048 self._antialiased = b
1050 def set_color(self, color):
1051 """
1052 Set the color of the line.
1054 Parameters
1055 ----------
1056 color : color
1057 """
1058 self._color = color
1059 self.stale = True
1061 def set_drawstyle(self, drawstyle):
1062 """
1063 Set the drawstyle of the plot.
1065 The drawstyle determines how the points are connected.
1067 Parameters
1068 ----------
1069 drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \
1070'steps-post'}, default: 'default'
1071 For 'default', the points are connected with straight lines.
1073 The steps variants connect the points with step-like lines,
1074 i.e. horizontal lines with vertical steps. They differ in the
1075 location of the step:
1077 - 'steps-pre': The step is at the beginning of the line segment,
1078 i.e. the line will be at the y-value of point to the right.
1079 - 'steps-mid': The step is halfway between the points.
1080 - 'steps-post: The step is at the end of the line segment,
1081 i.e. the line will be at the y-value of the point to the left.
1082 - 'steps' is equal to 'steps-pre' and is maintained for
1083 backward-compatibility.
1085 For examples see :doc:`/gallery/lines_bars_and_markers/step_demo`.
1086 """
1087 if drawstyle is None:
1088 drawstyle = 'default'
1089 cbook._check_in_list(self.drawStyles, drawstyle=drawstyle)
1090 if self._drawstyle != drawstyle:
1091 self.stale = True
1092 # invalidate to trigger a recache of the path
1093 self._invalidx = True
1094 self._drawstyle = drawstyle
1096 def set_linewidth(self, w):
1097 """
1098 Set the line width in points.
1100 Parameters
1101 ----------
1102 w : float
1103 Line width, in points.
1104 """
1105 w = float(w)
1107 if self._linewidth != w:
1108 self.stale = True
1109 self._linewidth = w
1110 # rescale the dashes + offset
1111 self._dashOffset, self._dashSeq = _scale_dashes(
1112 self._us_dashOffset, self._us_dashSeq, self._linewidth)
1114 def _split_drawstyle_linestyle(self, ls):
1115 """
1116 Split drawstyle from linestyle string.
1118 If *ls* is only a drawstyle default to returning a linestyle
1119 of '-'.
1121 Parameters
1122 ----------
1123 ls : str
1124 The linestyle to be processed
1126 Returns
1127 -------
1128 ret_ds : str or None
1129 If the linestyle string does not contain a drawstyle prefix
1130 return None, otherwise return it.
1132 ls : str
1133 The linestyle with the drawstyle (if any) stripped.
1134 """
1135 for ds in self.drawStyleKeys: # long names are first in the list
1136 if ls.startswith(ds):
1137 cbook.warn_deprecated(
1138 "3.1", message="Passing the drawstyle with the linestyle "
1139 "as a single string is deprecated since Matplotlib "
1140 "%(since)s and support will be removed %(removal)s; "
1141 "please pass the drawstyle separately using the drawstyle "
1142 "keyword argument to Line2D or set_drawstyle() method (or "
1143 "ds/set_ds()).")
1144 return ds, ls[len(ds):] or '-'
1145 return None, ls
1147 def set_linestyle(self, ls):
1148 """
1149 Set the linestyle of the line.
1151 Parameters
1152 ----------
1153 ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
1154 Possible values:
1156 - A string:
1158 =============================== =================
1159 Linestyle Description
1160 =============================== =================
1161 ``'-'`` or ``'solid'`` solid line
1162 ``'--'`` or ``'dashed'`` dashed line
1163 ``'-.'`` or ``'dashdot'`` dash-dotted line
1164 ``':'`` or ``'dotted'`` dotted line
1165 ``'None'`` or ``' '`` or ``''`` draw nothing
1166 =============================== =================
1168 - Alternatively a dash tuple of the following form can be
1169 provided::
1171 (offset, onoffseq)
1173 where ``onoffseq`` is an even length tuple of on and off ink
1174 in points. See also :meth:`set_dashes`.
1176 For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`.
1177 """
1178 if isinstance(ls, str):
1179 ds, ls = self._split_drawstyle_linestyle(ls)
1180 if ds is not None:
1181 self.set_drawstyle(ds)
1183 if ls in [' ', '', 'none']:
1184 ls = 'None'
1186 cbook._check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls)
1187 if ls not in self._lineStyles:
1188 ls = ls_mapper_r[ls]
1189 self._linestyle = ls
1190 else:
1191 self._linestyle = '--'
1193 # get the unscaled dashes
1194 self._us_dashOffset, self._us_dashSeq = _get_dash_pattern(ls)
1195 # compute the linewidth scaled dashes
1196 self._dashOffset, self._dashSeq = _scale_dashes(
1197 self._us_dashOffset, self._us_dashSeq, self._linewidth)
1199 @docstring.dedent_interpd
1200 def set_marker(self, marker):
1201 """
1202 Set the line marker.
1204 Parameters
1205 ----------
1206 marker : marker style
1207 See `~matplotlib.markers` for full description of possible
1208 arguments.
1209 """
1210 self._marker.set_marker(marker)
1211 self.stale = True
1213 def set_markeredgecolor(self, ec):
1214 """
1215 Set the marker edge color.
1217 Parameters
1218 ----------
1219 ec : color
1220 """
1221 if ec is None:
1222 ec = 'auto'
1223 if (self._markeredgecolor is None
1224 or np.any(self._markeredgecolor != ec)):
1225 self.stale = True
1226 self._markeredgecolor = ec
1228 def set_markeredgewidth(self, ew):
1229 """
1230 Set the marker edge width in points.
1232 Parameters
1233 ----------
1234 ew : float
1235 Marker edge width, in points.
1236 """
1237 if ew is None:
1238 ew = rcParams['lines.markeredgewidth']
1239 if self._markeredgewidth != ew:
1240 self.stale = True
1241 self._markeredgewidth = ew
1243 def set_markerfacecolor(self, fc):
1244 """
1245 Set the marker face color.
1247 Parameters
1248 ----------
1249 fc : color
1250 """
1251 if fc is None:
1252 fc = 'auto'
1253 if np.any(self._markerfacecolor != fc):
1254 self.stale = True
1255 self._markerfacecolor = fc
1257 def set_markerfacecoloralt(self, fc):
1258 """
1259 Set the alternate marker face color.
1261 Parameters
1262 ----------
1263 fc : color
1264 """
1265 if fc is None:
1266 fc = 'auto'
1267 if np.any(self._markerfacecoloralt != fc):
1268 self.stale = True
1269 self._markerfacecoloralt = fc
1271 def set_markersize(self, sz):
1272 """
1273 Set the marker size in points.
1275 Parameters
1276 ----------
1277 sz : float
1278 Marker size, in points.
1279 """
1280 sz = float(sz)
1281 if self._markersize != sz:
1282 self.stale = True
1283 self._markersize = sz
1285 def set_xdata(self, x):
1286 """
1287 Set the data array for x.
1289 Parameters
1290 ----------
1291 x : 1D array
1292 """
1293 self._xorig = x
1294 self._invalidx = True
1295 self.stale = True
1297 def set_ydata(self, y):
1298 """
1299 Set the data array for y.
1301 Parameters
1302 ----------
1303 y : 1D array
1304 """
1305 self._yorig = y
1306 self._invalidy = True
1307 self.stale = True
1309 def set_dashes(self, seq):
1310 """
1311 Set the dash sequence.
1313 The dash sequence is a sequence of floats of even length describing
1314 the length of dashes and spaces in points.
1316 For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point
1317 dashes separated by 2 point spaces.
1319 Parameters
1320 ----------
1321 seq : sequence of floats (on/off ink in points) or (None, None)
1322 If *seq* is empty or ``(None, None)``, the linestyle will be set
1323 to solid.
1324 """
1325 if seq == (None, None) or len(seq) == 0:
1326 self.set_linestyle('-')
1327 else:
1328 self.set_linestyle((0, seq))
1330 def update_from(self, other):
1331 """Copy properties from *other* to self."""
1332 Artist.update_from(self, other)
1333 self._linestyle = other._linestyle
1334 self._linewidth = other._linewidth
1335 self._color = other._color
1336 self._markersize = other._markersize
1337 self._markerfacecolor = other._markerfacecolor
1338 self._markerfacecoloralt = other._markerfacecoloralt
1339 self._markeredgecolor = other._markeredgecolor
1340 self._markeredgewidth = other._markeredgewidth
1341 self._dashSeq = other._dashSeq
1342 self._us_dashSeq = other._us_dashSeq
1343 self._dashOffset = other._dashOffset
1344 self._us_dashOffset = other._us_dashOffset
1345 self._dashcapstyle = other._dashcapstyle
1346 self._dashjoinstyle = other._dashjoinstyle
1347 self._solidcapstyle = other._solidcapstyle
1348 self._solidjoinstyle = other._solidjoinstyle
1350 self._linestyle = other._linestyle
1351 self._marker = MarkerStyle(other._marker.get_marker(),
1352 other._marker.get_fillstyle())
1353 self._drawstyle = other._drawstyle
1355 def set_dash_joinstyle(self, s):
1356 """
1357 Set the join style for dashed lines.
1359 Parameters
1360 ----------
1361 s : {'miter', 'round', 'bevel'}
1362 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`.
1363 """
1364 s = s.lower()
1365 cbook._check_in_list(self.validJoin, s=s)
1366 if self._dashjoinstyle != s:
1367 self.stale = True
1368 self._dashjoinstyle = s
1370 def set_solid_joinstyle(self, s):
1371 """
1372 Set the join style for solid lines.
1374 Parameters
1375 ----------
1376 s : {'miter', 'round', 'bevel'}
1377 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`.
1378 """
1379 s = s.lower()
1380 cbook._check_in_list(self.validJoin, s=s)
1381 if self._solidjoinstyle != s:
1382 self.stale = True
1383 self._solidjoinstyle = s
1385 def get_dash_joinstyle(self):
1386 """
1387 Return the join style for dashed lines.
1389 See also `~.Line2D.set_dash_joinstyle`.
1390 """
1391 return self._dashjoinstyle
1393 def get_solid_joinstyle(self):
1394 """
1395 Return the join style for solid lines.
1397 See also `~.Line2D.set_solid_joinstyle`.
1398 """
1399 return self._solidjoinstyle
1401 def set_dash_capstyle(self, s):
1402 """
1403 Set the cap style for dashed lines.
1405 Parameters
1406 ----------
1407 s : {'butt', 'round', 'projecting'}
1408 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`.
1409 """
1410 s = s.lower()
1411 cbook._check_in_list(self.validCap, s=s)
1412 if self._dashcapstyle != s:
1413 self.stale = True
1414 self._dashcapstyle = s
1416 def set_solid_capstyle(self, s):
1417 """
1418 Set the cap style for solid lines.
1420 Parameters
1421 ----------
1422 s : {'butt', 'round', 'projecting'}
1423 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`.
1424 """
1425 s = s.lower()
1426 cbook._check_in_list(self.validCap, s=s)
1427 if self._solidcapstyle != s:
1428 self.stale = True
1429 self._solidcapstyle = s
1431 def get_dash_capstyle(self):
1432 """
1433 Return the cap style for dashed lines.
1435 See also `~.Line2D.set_dash_capstyle`.
1436 """
1437 return self._dashcapstyle
1439 def get_solid_capstyle(self):
1440 """
1441 Return the cap style for solid lines.
1443 See also `~.Line2D.set_solid_capstyle`.
1444 """
1445 return self._solidcapstyle
1447 def is_dashed(self):
1448 """
1449 Return whether line has a dashed linestyle.
1451 See also `~.Line2D.set_linestyle`.
1452 """
1453 return self._linestyle in ('--', '-.', ':')
1456class VertexSelector:
1457 """
1458 Manage the callbacks to maintain a list of selected vertices for
1459 `.Line2D`. Derived classes should override
1460 :meth:`~matplotlib.lines.VertexSelector.process_selected` to do
1461 something with the picks.
1463 Here is an example which highlights the selected verts with red
1464 circles::
1466 import numpy as np
1467 import matplotlib.pyplot as plt
1468 import matplotlib.lines as lines
1470 class HighlightSelected(lines.VertexSelector):
1471 def __init__(self, line, fmt='ro', **kwargs):
1472 lines.VertexSelector.__init__(self, line)
1473 self.markers, = self.axes.plot([], [], fmt, **kwargs)
1475 def process_selected(self, ind, xs, ys):
1476 self.markers.set_data(xs, ys)
1477 self.canvas.draw()
1479 fig, ax = plt.subplots()
1480 x, y = np.random.rand(2, 30)
1481 line, = ax.plot(x, y, 'bs-', picker=5)
1483 selector = HighlightSelected(line)
1484 plt.show()
1486 """
1487 def __init__(self, line):
1488 """
1489 Initialize the class with a `.Line2D` instance. The line should
1490 already be added to some :class:`matplotlib.axes.Axes` instance and
1491 should have the picker property set.
1492 """
1493 if line.axes is None:
1494 raise RuntimeError('You must first add the line to the Axes')
1496 if line.get_picker() is None:
1497 raise RuntimeError('You must first set the picker property '
1498 'of the line')
1500 self.axes = line.axes
1501 self.line = line
1502 self.canvas = self.axes.figure.canvas
1503 self.cid = self.canvas.mpl_connect('pick_event', self.onpick)
1505 self.ind = set()
1507 def process_selected(self, ind, xs, ys):
1508 """
1509 Default "do nothing" implementation of the
1510 :meth:`process_selected` method.
1512 *ind* are the indices of the selected vertices. *xs* and *ys*
1513 are the coordinates of the selected vertices.
1514 """
1515 pass
1517 def onpick(self, event):
1518 """When the line is picked, update the set of selected indices."""
1519 if event.artist is not self.line:
1520 return
1521 self.ind ^= set(event.ind)
1522 ind = sorted(self.ind)
1523 xdata, ydata = self.line.get_data()
1524 self.process_selected(ind, xdata[ind], ydata[ind])
1527lineStyles = Line2D._lineStyles
1528lineMarkers = MarkerStyle.markers
1529drawStyles = Line2D.drawStyles
1530fillStyles = MarkerStyle.fillstyles
1532docstring.interpd.update(_Line2D_docstr=artist.kwdoc(Line2D))
1534# You can not set the docstring of an instancemethod,
1535# but you can on the underlying function. Go figure.
1536docstring.dedent_interpd(Line2D.__init__)