Coverage for /usr/lib/python3/dist-packages/matplotlib/lines.py: 17%
675 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
1"""
22D lines with support for a variety of line styles, markers, colors, etc.
3"""
5import copy
7from numbers import Integral, Number, Real
8import logging
10import numpy as np
12import matplotlib as mpl
13from . import _api, cbook, colors as mcolors, _docstring
14from .artist import Artist, allow_rasterization
15from .cbook import (
16 _to_unmasked_float_array, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP)
17from .markers import MarkerStyle
18from .path import Path
19from .transforms import Bbox, BboxTransformTo, TransformedPath
20from ._enums import JoinStyle, CapStyle
22# Imported here for backward compatibility, even though they don't
23# really belong.
24from . import _path
25from .markers import ( # noqa
26 CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN,
27 CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE,
28 TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN)
30_log = logging.getLogger(__name__)
33def _get_dash_pattern(style):
34 """Convert linestyle to dash pattern."""
35 # go from short hand -> full strings
36 if isinstance(style, str):
37 style = ls_mapper.get(style, style)
38 # un-dashed styles
39 if style in ['solid', 'None']:
40 offset = 0
41 dashes = None
42 # dashed styles
43 elif style in ['dashed', 'dashdot', 'dotted']:
44 offset = 0
45 dashes = tuple(mpl.rcParams['lines.{}_pattern'.format(style)])
46 #
47 elif isinstance(style, tuple):
48 offset, dashes = style
49 if offset is None:
50 raise ValueError(f'Unrecognized linestyle: {style!r}')
51 else:
52 raise ValueError(f'Unrecognized linestyle: {style!r}')
54 # normalize offset to be positive and shorter than the dash cycle
55 if dashes is not None:
56 dsum = sum(dashes)
57 if dsum:
58 offset %= dsum
60 return offset, dashes
63def _scale_dashes(offset, dashes, lw):
64 if not mpl.rcParams['lines.scale_dashes']:
65 return offset, dashes
66 scaled_offset = offset * lw
67 scaled_dashes = ([x * lw if x is not None else None for x in dashes]
68 if dashes is not None else None)
69 return scaled_offset, scaled_dashes
72def segment_hits(cx, cy, x, y, radius):
73 """
74 Return the indices of the segments in the polyline with coordinates (*cx*,
75 *cy*) that are within a distance *radius* of the point (*x*, *y*).
76 """
77 # Process single points specially
78 if len(x) <= 1:
79 res, = np.nonzero((cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2)
80 return res
82 # We need to lop the last element off a lot.
83 xr, yr = x[:-1], y[:-1]
85 # Only look at line segments whose nearest point to C on the line
86 # lies within the segment.
87 dx, dy = x[1:] - xr, y[1:] - yr
88 Lnorm_sq = dx ** 2 + dy ** 2 # Possibly want to eliminate Lnorm==0
89 u = ((cx - xr) * dx + (cy - yr) * dy) / Lnorm_sq
90 candidates = (u >= 0) & (u <= 1)
92 # Note that there is a little area near one side of each point
93 # which will be near neither segment, and another which will
94 # be near both, depending on the angle of the lines. The
95 # following radius test eliminates these ambiguities.
96 point_hits = (cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2
97 candidates = candidates & ~(point_hits[:-1] | point_hits[1:])
99 # For those candidates which remain, determine how far they lie away
100 # from the line.
101 px, py = xr + u * dx, yr + u * dy
102 line_hits = (cx - px) ** 2 + (cy - py) ** 2 <= radius ** 2
103 line_hits = line_hits & candidates
104 points, = point_hits.ravel().nonzero()
105 lines, = line_hits.ravel().nonzero()
106 return np.concatenate((points, lines))
109def _mark_every_path(markevery, tpath, affine, ax):
110 """
111 Helper function that sorts out how to deal the input
112 `markevery` and returns the points where markers should be drawn.
114 Takes in the `markevery` value and the line path and returns the
115 sub-sampled path.
116 """
117 # pull out the two bits of data we want from the path
118 codes, verts = tpath.codes, tpath.vertices
120 def _slice_or_none(in_v, slc):
121 """Helper function to cope with `codes` being an ndarray or `None`."""
122 if in_v is None:
123 return None
124 return in_v[slc]
126 # if just an int, assume starting at 0 and make a tuple
127 if isinstance(markevery, Integral):
128 markevery = (0, markevery)
129 # if just a float, assume starting at 0.0 and make a tuple
130 elif isinstance(markevery, Real):
131 markevery = (0.0, markevery)
133 if isinstance(markevery, tuple):
134 if len(markevery) != 2:
135 raise ValueError('`markevery` is a tuple but its len is not 2; '
136 'markevery={}'.format(markevery))
137 start, step = markevery
138 # if step is an int, old behavior
139 if isinstance(step, Integral):
140 # tuple of 2 int is for backwards compatibility,
141 if not isinstance(start, Integral):
142 raise ValueError(
143 '`markevery` is a tuple with len 2 and second element is '
144 'an int, but the first element is not an int; markevery={}'
145 .format(markevery))
146 # just return, we are done here
148 return Path(verts[slice(start, None, step)],
149 _slice_or_none(codes, slice(start, None, step)))
151 elif isinstance(step, Real):
152 if not isinstance(start, Real):
153 raise ValueError(
154 '`markevery` is a tuple with len 2 and second element is '
155 'a float, but the first element is not a float or an int; '
156 'markevery={}'.format(markevery))
157 if ax is None:
158 raise ValueError(
159 "markevery is specified relative to the axes size, but "
160 "the line does not have a Axes as parent")
162 # calc cumulative distance along path (in display coords):
163 fin = np.isfinite(verts).all(axis=1)
164 fverts = verts[fin]
165 disp_coords = affine.transform(fverts)
167 delta = np.empty((len(disp_coords), 2))
168 delta[0, :] = 0
169 delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :]
170 delta = np.hypot(*delta.T).cumsum()
171 # calc distance between markers along path based on the axes
172 # bounding box diagonal being a distance of unity:
173 (x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]])
174 scale = np.hypot(x1 - x0, y1 - y0)
175 marker_delta = np.arange(start * scale, delta[-1], step * scale)
176 # find closest actual data point that is closest to
177 # the theoretical distance along the path:
178 inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis])
179 inds = inds.argmin(axis=1)
180 inds = np.unique(inds)
181 # return, we are done here
182 return Path(fverts[inds], _slice_or_none(codes, inds))
183 else:
184 raise ValueError(
185 f"markevery={markevery!r} is a tuple with len 2, but its "
186 f"second element is not an int or a float")
188 elif isinstance(markevery, slice):
189 # mazol tov, it's already a slice, just return
190 return Path(verts[markevery], _slice_or_none(codes, markevery))
192 elif np.iterable(markevery):
193 # fancy indexing
194 try:
195 return Path(verts[markevery], _slice_or_none(codes, markevery))
196 except (ValueError, IndexError) as err:
197 raise ValueError(
198 f"markevery={markevery!r} is iterable but not a valid numpy "
199 f"fancy index") from err
200 else:
201 raise ValueError(f"markevery={markevery!r} is not a recognized value")
204@_docstring.interpd
205@_api.define_aliases({
206 "antialiased": ["aa"],
207 "color": ["c"],
208 "drawstyle": ["ds"],
209 "linestyle": ["ls"],
210 "linewidth": ["lw"],
211 "markeredgecolor": ["mec"],
212 "markeredgewidth": ["mew"],
213 "markerfacecolor": ["mfc"],
214 "markerfacecoloralt": ["mfcalt"],
215 "markersize": ["ms"],
216})
217class Line2D(Artist):
218 """
219 A line - the line can have both a solid linestyle connecting all
220 the vertices, and a marker at each vertex. Additionally, the
221 drawing of the solid line is influenced by the drawstyle, e.g., one
222 can create "stepped" lines in various styles.
223 """
225 lineStyles = _lineStyles = { # hidden names deprecated
226 '-': '_draw_solid',
227 '--': '_draw_dashed',
228 '-.': '_draw_dash_dot',
229 ':': '_draw_dotted',
230 'None': '_draw_nothing',
231 ' ': '_draw_nothing',
232 '': '_draw_nothing',
233 }
235 _drawStyles_l = {
236 'default': '_draw_lines',
237 'steps-mid': '_draw_steps_mid',
238 'steps-pre': '_draw_steps_pre',
239 'steps-post': '_draw_steps_post',
240 }
242 _drawStyles_s = {
243 'steps': '_draw_steps_pre',
244 }
246 # drawStyles should now be deprecated.
247 drawStyles = {**_drawStyles_l, **_drawStyles_s}
248 # Need a list ordered with long names first:
249 drawStyleKeys = [*_drawStyles_l, *_drawStyles_s]
251 # Referenced here to maintain API. These are defined in
252 # MarkerStyle
253 markers = MarkerStyle.markers
254 filled_markers = MarkerStyle.filled_markers
255 fillStyles = MarkerStyle.fillstyles
257 zorder = 2
259 def __str__(self):
260 if self._label != "":
261 return f"Line2D({self._label})"
262 elif self._x is None:
263 return "Line2D()"
264 elif len(self._x) > 3:
265 return "Line2D((%g,%g),(%g,%g),...,(%g,%g))" % (
266 self._x[0], self._y[0], self._x[0],
267 self._y[0], self._x[-1], self._y[-1])
268 else:
269 return "Line2D(%s)" % ",".join(
270 map("({:g},{:g})".format, self._x, self._y))
272 @_api.make_keyword_only("3.6", name="linewidth")
273 def __init__(self, xdata, ydata,
274 linewidth=None, # all Nones default to rc
275 linestyle=None,
276 color=None,
277 gapcolor=None,
278 marker=None,
279 markersize=None,
280 markeredgewidth=None,
281 markeredgecolor=None,
282 markerfacecolor=None,
283 markerfacecoloralt='none',
284 fillstyle=None,
285 antialiased=None,
286 dash_capstyle=None,
287 solid_capstyle=None,
288 dash_joinstyle=None,
289 solid_joinstyle=None,
290 pickradius=5,
291 drawstyle=None,
292 markevery=None,
293 **kwargs
294 ):
295 """
296 Create a `.Line2D` instance with *x* and *y* data in sequences of
297 *xdata*, *ydata*.
299 Additional keyword arguments are `.Line2D` properties:
301 %(Line2D:kwdoc)s
303 See :meth:`set_linestyle` for a description of the line styles,
304 :meth:`set_marker` for a description of the markers, and
305 :meth:`set_drawstyle` for a description of the draw styles.
307 """
308 super().__init__()
310 # Convert sequences to NumPy arrays.
311 if not np.iterable(xdata):
312 raise RuntimeError('xdata must be a sequence')
313 if not np.iterable(ydata):
314 raise RuntimeError('ydata must be a sequence')
316 if linewidth is None:
317 linewidth = mpl.rcParams['lines.linewidth']
319 if linestyle is None:
320 linestyle = mpl.rcParams['lines.linestyle']
321 if marker is None:
322 marker = mpl.rcParams['lines.marker']
323 if color is None:
324 color = mpl.rcParams['lines.color']
326 if markersize is None:
327 markersize = mpl.rcParams['lines.markersize']
328 if antialiased is None:
329 antialiased = mpl.rcParams['lines.antialiased']
330 if dash_capstyle is None:
331 dash_capstyle = mpl.rcParams['lines.dash_capstyle']
332 if dash_joinstyle is None:
333 dash_joinstyle = mpl.rcParams['lines.dash_joinstyle']
334 if solid_capstyle is None:
335 solid_capstyle = mpl.rcParams['lines.solid_capstyle']
336 if solid_joinstyle is None:
337 solid_joinstyle = mpl.rcParams['lines.solid_joinstyle']
339 if drawstyle is None:
340 drawstyle = 'default'
342 self._dashcapstyle = None
343 self._dashjoinstyle = None
344 self._solidjoinstyle = None
345 self._solidcapstyle = None
346 self.set_dash_capstyle(dash_capstyle)
347 self.set_dash_joinstyle(dash_joinstyle)
348 self.set_solid_capstyle(solid_capstyle)
349 self.set_solid_joinstyle(solid_joinstyle)
351 self._linestyles = None
352 self._drawstyle = None
353 self._linewidth = linewidth
354 self._unscaled_dash_pattern = (0, None) # offset, dash
355 self._dash_pattern = (0, None) # offset, dash (scaled by linewidth)
357 self.set_linewidth(linewidth)
358 self.set_linestyle(linestyle)
359 self.set_drawstyle(drawstyle)
361 self._color = None
362 self.set_color(color)
363 if marker is None:
364 marker = 'none' # Default.
365 if not isinstance(marker, MarkerStyle):
366 self._marker = MarkerStyle(marker, fillstyle)
367 else:
368 self._marker = marker
370 self._gapcolor = None
371 self.set_gapcolor(gapcolor)
373 self._markevery = None
374 self._markersize = None
375 self._antialiased = None
377 self.set_markevery(markevery)
378 self.set_antialiased(antialiased)
379 self.set_markersize(markersize)
381 self._markeredgecolor = None
382 self._markeredgewidth = None
383 self._markerfacecolor = None
384 self._markerfacecoloralt = None
386 self.set_markerfacecolor(markerfacecolor) # Normalizes None to rc.
387 self.set_markerfacecoloralt(markerfacecoloralt)
388 self.set_markeredgecolor(markeredgecolor) # Normalizes None to rc.
389 self.set_markeredgewidth(markeredgewidth)
391 # update kwargs before updating data to give the caller a
392 # chance to init axes (and hence unit support)
393 self._internal_update(kwargs)
394 self._pickradius = pickradius
395 self.ind_offset = 0
396 if (isinstance(self._picker, Number) and
397 not isinstance(self._picker, bool)):
398 self._pickradius = self._picker
400 self._xorig = np.asarray([])
401 self._yorig = np.asarray([])
402 self._invalidx = True
403 self._invalidy = True
404 self._x = None
405 self._y = None
406 self._xy = None
407 self._path = None
408 self._transformed_path = None
409 self._subslice = False
410 self._x_filled = None # used in subslicing; only x is needed
412 self.set_data(xdata, ydata)
414 def contains(self, mouseevent):
415 """
416 Test whether *mouseevent* occurred on the line.
418 An event is deemed to have occurred "on" the line if it is less
419 than ``self.pickradius`` (default: 5 points) away from it. Use
420 `~.Line2D.get_pickradius` or `~.Line2D.set_pickradius` to get or set
421 the pick radius.
423 Parameters
424 ----------
425 mouseevent : `matplotlib.backend_bases.MouseEvent`
427 Returns
428 -------
429 contains : bool
430 Whether any values are within the radius.
431 details : dict
432 A dictionary ``{'ind': pointlist}``, where *pointlist* is a
433 list of points of the line that are within the pickradius around
434 the event position.
436 TODO: sort returned indices by distance
437 """
438 inside, info = self._default_contains(mouseevent)
439 if inside is not None:
440 return inside, info
442 # Make sure we have data to plot
443 if self._invalidy or self._invalidx:
444 self.recache()
445 if len(self._xy) == 0:
446 return False, {}
448 # Convert points to pixels
449 transformed_path = self._get_transformed_path()
450 path, affine = transformed_path.get_transformed_path_and_affine()
451 path = affine.transform_path(path)
452 xy = path.vertices
453 xt = xy[:, 0]
454 yt = xy[:, 1]
456 # Convert pick radius from points to pixels
457 if self.figure is None:
458 _log.warning('no figure set when check if mouse is on line')
459 pixels = self._pickradius
460 else:
461 pixels = self.figure.dpi / 72. * self._pickradius
463 # The math involved in checking for containment (here and inside of
464 # segment_hits) assumes that it is OK to overflow, so temporarily set
465 # the error flags accordingly.
466 with np.errstate(all='ignore'):
467 # Check for collision
468 if self._linestyle in ['None', None]:
469 # If no line, return the nearby point(s)
470 ind, = np.nonzero(
471 (xt - mouseevent.x) ** 2 + (yt - mouseevent.y) ** 2
472 <= pixels ** 2)
473 else:
474 # If line, return the nearby segment(s)
475 ind = segment_hits(mouseevent.x, mouseevent.y, xt, yt, pixels)
476 if self._drawstyle.startswith("steps"):
477 ind //= 2
479 ind += self.ind_offset
481 # Return the point(s) within radius
482 return len(ind) > 0, dict(ind=ind)
484 def get_pickradius(self):
485 """
486 Return the pick radius used for containment tests.
488 See `.contains` for more details.
489 """
490 return self._pickradius
492 @_api.rename_parameter("3.6", "d", "pickradius")
493 def set_pickradius(self, pickradius):
494 """
495 Set the pick radius used for containment tests.
497 See `.contains` for more details.
499 Parameters
500 ----------
501 pickradius : float
502 Pick radius, in points.
503 """
504 if not isinstance(pickradius, Number) or pickradius < 0:
505 raise ValueError("pick radius should be a distance")
506 self._pickradius = pickradius
508 pickradius = property(get_pickradius, set_pickradius)
510 def get_fillstyle(self):
511 """
512 Return the marker fill style.
514 See also `~.Line2D.set_fillstyle`.
515 """
516 return self._marker.get_fillstyle()
518 def set_fillstyle(self, fs):
519 """
520 Set the marker fill style.
522 Parameters
523 ----------
524 fs : {'full', 'left', 'right', 'bottom', 'top', 'none'}
525 Possible values:
527 - 'full': Fill the whole marker with the *markerfacecolor*.
528 - 'left', 'right', 'bottom', 'top': Fill the marker half at
529 the given side with the *markerfacecolor*. The other
530 half of the marker is filled with *markerfacecoloralt*.
531 - 'none': No filling.
533 For examples see :ref:`marker_fill_styles`.
534 """
535 self.set_marker(MarkerStyle(self._marker.get_marker(), fs))
536 self.stale = True
538 def set_markevery(self, every):
539 """
540 Set the markevery property to subsample the plot when using markers.
542 e.g., if ``every=5``, every 5-th marker will be plotted.
544 Parameters
545 ----------
546 every : None or int or (int, int) or slice or list[int] or float or \
547(float, float) or list[bool]
548 Which markers to plot.
550 - ``every=None``: every point will be plotted.
551 - ``every=N``: every N-th marker will be plotted starting with
552 marker 0.
553 - ``every=(start, N)``: every N-th marker, starting at index
554 *start*, will be plotted.
555 - ``every=slice(start, end, N)``: every N-th marker, starting at
556 index *start*, up to but not including index *end*, will be
557 plotted.
558 - ``every=[i, j, m, ...]``: only markers at the given indices
559 will be plotted.
560 - ``every=[True, False, True, ...]``: only positions that are True
561 will be plotted. The list must have the same length as the data
562 points.
563 - ``every=0.1``, (i.e. a float): markers will be spaced at
564 approximately equal visual distances along the line; the distance
565 along the line between markers is determined by multiplying the
566 display-coordinate distance of the axes bounding-box diagonal
567 by the value of *every*.
568 - ``every=(0.5, 0.1)`` (i.e. a length-2 tuple of float): similar
569 to ``every=0.1`` but the first marker will be offset along the
570 line by 0.5 multiplied by the
571 display-coordinate-diagonal-distance along the line.
573 For examples see
574 :doc:`/gallery/lines_bars_and_markers/markevery_demo`.
576 Notes
577 -----
578 Setting *markevery* will still only draw markers at actual data points.
579 While the float argument form aims for uniform visual spacing, it has
580 to coerce from the ideal spacing to the nearest available data point.
581 Depending on the number and distribution of data points, the result
582 may still not look evenly spaced.
584 When using a start offset to specify the first marker, the offset will
585 be from the first data point which may be different from the first
586 the visible data point if the plot is zoomed in.
588 If zooming in on a plot when using float arguments then the actual
589 data points that have markers will change because the distance between
590 markers is always determined from the display-coordinates
591 axes-bounding-box-diagonal regardless of the actual axes data limits.
593 """
594 self._markevery = every
595 self.stale = True
597 def get_markevery(self):
598 """
599 Return the markevery setting for marker subsampling.
601 See also `~.Line2D.set_markevery`.
602 """
603 return self._markevery
605 def set_picker(self, p):
606 """
607 Set the event picker details for the line.
609 Parameters
610 ----------
611 p : float or callable[[Artist, Event], tuple[bool, dict]]
612 If a float, it is used as the pick radius in points.
613 """
614 if callable(p):
615 self._contains = p
616 else:
617 self.set_pickradius(p)
618 self._picker = p
620 def get_bbox(self):
621 """Get the bounding box of this line."""
622 bbox = Bbox([[0, 0], [0, 0]])
623 bbox.update_from_data_xy(self.get_xydata())
624 return bbox
626 def get_window_extent(self, renderer=None):
627 bbox = Bbox([[0, 0], [0, 0]])
628 trans_data_to_xy = self.get_transform().transform
629 bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()),
630 ignore=True)
631 # correct for marker size, if any
632 if self._marker:
633 ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5
634 bbox = bbox.padded(ms)
635 return bbox
637 def set_data(self, *args):
638 """
639 Set the x and y data.
641 Parameters
642 ----------
643 *args : (2, N) array or two 1D arrays
644 """
645 if len(args) == 1:
646 (x, y), = args
647 else:
648 x, y = args
650 self.set_xdata(x)
651 self.set_ydata(y)
653 def recache_always(self):
654 self.recache(always=True)
656 def recache(self, always=False):
657 if always or self._invalidx:
658 xconv = self.convert_xunits(self._xorig)
659 x = _to_unmasked_float_array(xconv).ravel()
660 else:
661 x = self._x
662 if always or self._invalidy:
663 yconv = self.convert_yunits(self._yorig)
664 y = _to_unmasked_float_array(yconv).ravel()
665 else:
666 y = self._y
668 self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
669 self._x, self._y = self._xy.T # views
671 self._subslice = False
672 if (self.axes and len(x) > 1000 and self._is_sorted(x) and
673 self.axes.name == 'rectilinear' and
674 self.axes.get_xscale() == 'linear' and
675 self._markevery is None and
676 self.get_clip_on() and
677 self.get_transform() == self.axes.transData):
678 self._subslice = True
679 nanmask = np.isnan(x)
680 if nanmask.any():
681 self._x_filled = self._x.copy()
682 indices = np.arange(len(x))
683 self._x_filled[nanmask] = np.interp(
684 indices[nanmask], indices[~nanmask], self._x[~nanmask])
685 else:
686 self._x_filled = self._x
688 if self._path is not None:
689 interpolation_steps = self._path._interpolation_steps
690 else:
691 interpolation_steps = 1
692 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T)
693 self._path = Path(np.asarray(xy).T,
694 _interpolation_steps=interpolation_steps)
695 self._transformed_path = None
696 self._invalidx = False
697 self._invalidy = False
699 def _transform_path(self, subslice=None):
700 """
701 Put a TransformedPath instance at self._transformed_path;
702 all invalidation of the transform is then handled by the
703 TransformedPath instance.
704 """
705 # Masked arrays are now handled by the Path class itself
706 if subslice is not None:
707 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T)
708 _path = Path(np.asarray(xy).T,
709 _interpolation_steps=self._path._interpolation_steps)
710 else:
711 _path = self._path
712 self._transformed_path = TransformedPath(_path, self.get_transform())
714 def _get_transformed_path(self):
715 """Return this line's `~matplotlib.transforms.TransformedPath`."""
716 if self._transformed_path is None:
717 self._transform_path()
718 return self._transformed_path
720 def set_transform(self, t):
721 # docstring inherited
722 self._invalidx = True
723 self._invalidy = True
724 super().set_transform(t)
726 def _is_sorted(self, x):
727 """Return whether x is sorted in ascending order."""
728 # We don't handle the monotonically decreasing case.
729 return _path.is_sorted(x)
731 @allow_rasterization
732 def draw(self, renderer):
733 # docstring inherited
735 if not self.get_visible():
736 return
738 if self._invalidy or self._invalidx:
739 self.recache()
740 self.ind_offset = 0 # Needed for contains() method.
741 if self._subslice and self.axes:
742 x0, x1 = self.axes.get_xbound()
743 i0 = self._x_filled.searchsorted(x0, 'left')
744 i1 = self._x_filled.searchsorted(x1, 'right')
745 subslice = slice(max(i0 - 1, 0), i1 + 1)
746 self.ind_offset = subslice.start
747 self._transform_path(subslice)
748 else:
749 subslice = None
751 if self.get_path_effects():
752 from matplotlib.patheffects import PathEffectRenderer
753 renderer = PathEffectRenderer(self.get_path_effects(), renderer)
755 renderer.open_group('line2d', self.get_gid())
756 if self._lineStyles[self._linestyle] != '_draw_nothing':
757 tpath, affine = (self._get_transformed_path()
758 .get_transformed_path_and_affine())
759 if len(tpath.vertices):
760 gc = renderer.new_gc()
761 self._set_gc_clip(gc)
762 gc.set_url(self.get_url())
764 gc.set_antialiased(self._antialiased)
765 gc.set_linewidth(self._linewidth)
767 if self.is_dashed():
768 cap = self._dashcapstyle
769 join = self._dashjoinstyle
770 else:
771 cap = self._solidcapstyle
772 join = self._solidjoinstyle
773 gc.set_joinstyle(join)
774 gc.set_capstyle(cap)
775 gc.set_snap(self.get_snap())
776 if self.get_sketch_params() is not None:
777 gc.set_sketch_params(*self.get_sketch_params())
779 # We first draw a path within the gaps if needed.
780 if self.is_dashed() and self._gapcolor is not None:
781 lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha)
782 gc.set_foreground(lc_rgba, isRGBA=True)
784 # Define the inverse pattern by moving the last gap to the
785 # start of the sequence.
786 dashes = self._dash_pattern[1]
787 gaps = dashes[-1:] + dashes[:-1]
788 # Set the offset so that this new first segment is skipped
789 # (see backend_bases.GraphicsContextBase.set_dashes for
790 # offset definition).
791 offset_gaps = self._dash_pattern[0] + dashes[-1]
793 gc.set_dashes(offset_gaps, gaps)
794 renderer.draw_path(gc, tpath, affine.frozen())
796 lc_rgba = mcolors.to_rgba(self._color, self._alpha)
797 gc.set_foreground(lc_rgba, isRGBA=True)
799 gc.set_dashes(*self._dash_pattern)
800 renderer.draw_path(gc, tpath, affine.frozen())
801 gc.restore()
803 if self._marker and self._markersize > 0:
804 gc = renderer.new_gc()
805 self._set_gc_clip(gc)
806 gc.set_url(self.get_url())
807 gc.set_linewidth(self._markeredgewidth)
808 gc.set_antialiased(self._antialiased)
810 ec_rgba = mcolors.to_rgba(
811 self.get_markeredgecolor(), self._alpha)
812 fc_rgba = mcolors.to_rgba(
813 self._get_markerfacecolor(), self._alpha)
814 fcalt_rgba = mcolors.to_rgba(
815 self._get_markerfacecolor(alt=True), self._alpha)
816 # If the edgecolor is "auto", it is set according to the *line*
817 # color but inherits the alpha value of the *face* color, if any.
818 if (cbook._str_equal(self._markeredgecolor, "auto")
819 and not cbook._str_lower_equal(
820 self.get_markerfacecolor(), "none")):
821 ec_rgba = ec_rgba[:3] + (fc_rgba[3],)
822 gc.set_foreground(ec_rgba, isRGBA=True)
823 if self.get_sketch_params() is not None:
824 scale, length, randomness = self.get_sketch_params()
825 gc.set_sketch_params(scale/2, length/2, 2*randomness)
827 marker = self._marker
829 # Markers *must* be drawn ignoring the drawstyle (but don't pay the
830 # recaching if drawstyle is already "default").
831 if self.get_drawstyle() != "default":
832 with cbook._setattr_cm(
833 self, _drawstyle="default", _transformed_path=None):
834 self.recache()
835 self._transform_path(subslice)
836 tpath, affine = (self._get_transformed_path()
837 .get_transformed_points_and_affine())
838 else:
839 tpath, affine = (self._get_transformed_path()
840 .get_transformed_points_and_affine())
842 if len(tpath.vertices):
843 # subsample the markers if markevery is not None
844 markevery = self.get_markevery()
845 if markevery is not None:
846 subsampled = _mark_every_path(
847 markevery, tpath, affine, self.axes)
848 else:
849 subsampled = tpath
851 snap = marker.get_snap_threshold()
852 if isinstance(snap, Real):
853 snap = renderer.points_to_pixels(self._markersize) >= snap
854 gc.set_snap(snap)
855 gc.set_joinstyle(marker.get_joinstyle())
856 gc.set_capstyle(marker.get_capstyle())
857 marker_path = marker.get_path()
858 marker_trans = marker.get_transform()
859 w = renderer.points_to_pixels(self._markersize)
861 if cbook._str_equal(marker.get_marker(), ","):
862 gc.set_linewidth(0)
863 else:
864 # Don't scale for pixels, and don't stroke them
865 marker_trans = marker_trans.scale(w)
866 renderer.draw_markers(gc, marker_path, marker_trans,
867 subsampled, affine.frozen(),
868 fc_rgba)
870 alt_marker_path = marker.get_alt_path()
871 if alt_marker_path:
872 alt_marker_trans = marker.get_alt_transform()
873 alt_marker_trans = alt_marker_trans.scale(w)
874 renderer.draw_markers(
875 gc, alt_marker_path, alt_marker_trans, subsampled,
876 affine.frozen(), fcalt_rgba)
878 gc.restore()
880 renderer.close_group('line2d')
881 self.stale = False
883 def get_antialiased(self):
884 """Return whether antialiased rendering is used."""
885 return self._antialiased
887 def get_color(self):
888 """
889 Return the line color.
891 See also `~.Line2D.set_color`.
892 """
893 return self._color
895 def get_drawstyle(self):
896 """
897 Return the drawstyle.
899 See also `~.Line2D.set_drawstyle`.
900 """
901 return self._drawstyle
903 def get_gapcolor(self):
904 """
905 Return the line gapcolor.
907 See also `~.Line2D.set_gapcolor`.
908 """
909 return self._gapcolor
911 def get_linestyle(self):
912 """
913 Return the linestyle.
915 See also `~.Line2D.set_linestyle`.
916 """
917 return self._linestyle
919 def get_linewidth(self):
920 """
921 Return the linewidth in points.
923 See also `~.Line2D.set_linewidth`.
924 """
925 return self._linewidth
927 def get_marker(self):
928 """
929 Return the line marker.
931 See also `~.Line2D.set_marker`.
932 """
933 return self._marker.get_marker()
935 def get_markeredgecolor(self):
936 """
937 Return the marker edge color.
939 See also `~.Line2D.set_markeredgecolor`.
940 """
941 mec = self._markeredgecolor
942 if cbook._str_equal(mec, 'auto'):
943 if mpl.rcParams['_internal.classic_mode']:
944 if self._marker.get_marker() in ('.', ','):
945 return self._color
946 if (self._marker.is_filled()
947 and self._marker.get_fillstyle() != 'none'):
948 return 'k' # Bad hard-wired default...
949 return self._color
950 else:
951 return mec
953 def get_markeredgewidth(self):
954 """
955 Return the marker edge width in points.
957 See also `~.Line2D.set_markeredgewidth`.
958 """
959 return self._markeredgewidth
961 def _get_markerfacecolor(self, alt=False):
962 if self._marker.get_fillstyle() == 'none':
963 return 'none'
964 fc = self._markerfacecoloralt if alt else self._markerfacecolor
965 if cbook._str_lower_equal(fc, 'auto'):
966 return self._color
967 else:
968 return fc
970 def get_markerfacecolor(self):
971 """
972 Return the marker face color.
974 See also `~.Line2D.set_markerfacecolor`.
975 """
976 return self._get_markerfacecolor(alt=False)
978 def get_markerfacecoloralt(self):
979 """
980 Return the alternate marker face color.
982 See also `~.Line2D.set_markerfacecoloralt`.
983 """
984 return self._get_markerfacecolor(alt=True)
986 def get_markersize(self):
987 """
988 Return the marker size in points.
990 See also `~.Line2D.set_markersize`.
991 """
992 return self._markersize
994 def get_data(self, orig=True):
995 """
996 Return the line data as an ``(xdata, ydata)`` pair.
998 If *orig* is *True*, return the original data.
999 """
1000 return self.get_xdata(orig=orig), self.get_ydata(orig=orig)
1002 def get_xdata(self, orig=True):
1003 """
1004 Return the xdata.
1006 If *orig* is *True*, return the original data, else the
1007 processed data.
1008 """
1009 if orig:
1010 return self._xorig
1011 if self._invalidx:
1012 self.recache()
1013 return self._x
1015 def get_ydata(self, orig=True):
1016 """
1017 Return the ydata.
1019 If *orig* is *True*, return the original data, else the
1020 processed data.
1021 """
1022 if orig:
1023 return self._yorig
1024 if self._invalidy:
1025 self.recache()
1026 return self._y
1028 def get_path(self):
1029 """Return the `~matplotlib.path.Path` associated with this line."""
1030 if self._invalidy or self._invalidx:
1031 self.recache()
1032 return self._path
1034 def get_xydata(self):
1035 """
1036 Return the *xy* data as a Nx2 numpy array.
1037 """
1038 if self._invalidy or self._invalidx:
1039 self.recache()
1040 return self._xy
1042 def set_antialiased(self, b):
1043 """
1044 Set whether to use antialiased rendering.
1046 Parameters
1047 ----------
1048 b : bool
1049 """
1050 if self._antialiased != b:
1051 self.stale = True
1052 self._antialiased = b
1054 def set_color(self, color):
1055 """
1056 Set the color of the line.
1058 Parameters
1059 ----------
1060 color : color
1061 """
1062 mcolors._check_color_like(color=color)
1063 self._color = color
1064 self.stale = True
1066 def set_drawstyle(self, drawstyle):
1067 """
1068 Set the drawstyle of the plot.
1070 The drawstyle determines how the points are connected.
1072 Parameters
1073 ----------
1074 drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \
1075'steps-post'}, default: 'default'
1076 For 'default', the points are connected with straight lines.
1078 The steps variants connect the points with step-like lines,
1079 i.e. horizontal lines with vertical steps. They differ in the
1080 location of the step:
1082 - 'steps-pre': The step is at the beginning of the line segment,
1083 i.e. the line will be at the y-value of point to the right.
1084 - 'steps-mid': The step is halfway between the points.
1085 - 'steps-post: The step is at the end of the line segment,
1086 i.e. the line will be at the y-value of the point to the left.
1087 - 'steps' is equal to 'steps-pre' and is maintained for
1088 backward-compatibility.
1090 For examples see :doc:`/gallery/lines_bars_and_markers/step_demo`.
1091 """
1092 if drawstyle is None:
1093 drawstyle = 'default'
1094 _api.check_in_list(self.drawStyles, drawstyle=drawstyle)
1095 if self._drawstyle != drawstyle:
1096 self.stale = True
1097 # invalidate to trigger a recache of the path
1098 self._invalidx = True
1099 self._drawstyle = drawstyle
1101 def set_gapcolor(self, gapcolor):
1102 """
1103 Set a color to fill the gaps in the dashed line style.
1105 .. note::
1107 Striped lines are created by drawing two interleaved dashed lines.
1108 There can be overlaps between those two, which may result in
1109 artifacts when using transparency.
1111 This functionality is experimental and may change.
1113 Parameters
1114 ----------
1115 gapcolor : color or None
1116 The color with which to fill the gaps. If None, the gaps are
1117 unfilled.
1118 """
1119 if gapcolor is not None:
1120 mcolors._check_color_like(color=gapcolor)
1121 self._gapcolor = gapcolor
1122 self.stale = True
1124 def set_linewidth(self, w):
1125 """
1126 Set the line width in points.
1128 Parameters
1129 ----------
1130 w : float
1131 Line width, in points.
1132 """
1133 w = float(w)
1134 if self._linewidth != w:
1135 self.stale = True
1136 self._linewidth = w
1137 self._dash_pattern = _scale_dashes(*self._unscaled_dash_pattern, w)
1139 def set_linestyle(self, ls):
1140 """
1141 Set the linestyle of the line.
1143 Parameters
1144 ----------
1145 ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
1146 Possible values:
1148 - A string:
1150 ========================================== =================
1151 linestyle description
1152 ========================================== =================
1153 ``'-'`` or ``'solid'`` solid line
1154 ``'--'`` or ``'dashed'`` dashed line
1155 ``'-.'`` or ``'dashdot'`` dash-dotted line
1156 ``':'`` or ``'dotted'`` dotted line
1157 ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
1158 ========================================== =================
1160 - Alternatively a dash tuple of the following form can be
1161 provided::
1163 (offset, onoffseq)
1165 where ``onoffseq`` is an even length tuple of on and off ink
1166 in points. See also :meth:`set_dashes`.
1168 For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`.
1169 """
1170 if isinstance(ls, str):
1171 if ls in [' ', '', 'none']:
1172 ls = 'None'
1173 _api.check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls)
1174 if ls not in self._lineStyles:
1175 ls = ls_mapper_r[ls]
1176 self._linestyle = ls
1177 else:
1178 self._linestyle = '--'
1179 self._unscaled_dash_pattern = _get_dash_pattern(ls)
1180 self._dash_pattern = _scale_dashes(
1181 *self._unscaled_dash_pattern, self._linewidth)
1182 self.stale = True
1184 @_docstring.interpd
1185 def set_marker(self, marker):
1186 """
1187 Set the line marker.
1189 Parameters
1190 ----------
1191 marker : marker style string, `~.path.Path` or `~.markers.MarkerStyle`
1192 See `~matplotlib.markers` for full description of possible
1193 arguments.
1194 """
1195 self._marker = MarkerStyle(marker, self._marker.get_fillstyle())
1196 self.stale = True
1198 def _set_markercolor(self, name, has_rcdefault, val):
1199 if val is None:
1200 val = mpl.rcParams[f"lines.{name}"] if has_rcdefault else "auto"
1201 attr = f"_{name}"
1202 current = getattr(self, attr)
1203 if current is None:
1204 self.stale = True
1205 else:
1206 neq = current != val
1207 # Much faster than `np.any(current != val)` if no arrays are used.
1208 if neq.any() if isinstance(neq, np.ndarray) else neq:
1209 self.stale = True
1210 setattr(self, attr, val)
1212 def set_markeredgecolor(self, ec):
1213 """
1214 Set the marker edge color.
1216 Parameters
1217 ----------
1218 ec : color
1219 """
1220 self._set_markercolor("markeredgecolor", True, ec)
1222 def set_markerfacecolor(self, fc):
1223 """
1224 Set the marker face color.
1226 Parameters
1227 ----------
1228 fc : color
1229 """
1230 self._set_markercolor("markerfacecolor", True, fc)
1232 def set_markerfacecoloralt(self, fc):
1233 """
1234 Set the alternate marker face color.
1236 Parameters
1237 ----------
1238 fc : color
1239 """
1240 self._set_markercolor("markerfacecoloralt", False, fc)
1242 def set_markeredgewidth(self, ew):
1243 """
1244 Set the marker edge width in points.
1246 Parameters
1247 ----------
1248 ew : float
1249 Marker edge width, in points.
1250 """
1251 if ew is None:
1252 ew = mpl.rcParams['lines.markeredgewidth']
1253 if self._markeredgewidth != ew:
1254 self.stale = True
1255 self._markeredgewidth = ew
1257 def set_markersize(self, sz):
1258 """
1259 Set the marker size in points.
1261 Parameters
1262 ----------
1263 sz : float
1264 Marker size, in points.
1265 """
1266 sz = float(sz)
1267 if self._markersize != sz:
1268 self.stale = True
1269 self._markersize = sz
1271 def set_xdata(self, x):
1272 """
1273 Set the data array for x.
1275 Parameters
1276 ----------
1277 x : 1D array
1278 """
1279 self._xorig = copy.copy(x)
1280 self._invalidx = True
1281 self.stale = True
1283 def set_ydata(self, y):
1284 """
1285 Set the data array for y.
1287 Parameters
1288 ----------
1289 y : 1D array
1290 """
1291 self._yorig = copy.copy(y)
1292 self._invalidy = True
1293 self.stale = True
1295 def set_dashes(self, seq):
1296 """
1297 Set the dash sequence.
1299 The dash sequence is a sequence of floats of even length describing
1300 the length of dashes and spaces in points.
1302 For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point
1303 dashes separated by 2 point spaces.
1305 See also `~.Line2D.set_gapcolor`, which allows those spaces to be
1306 filled with a color.
1308 Parameters
1309 ----------
1310 seq : sequence of floats (on/off ink in points) or (None, None)
1311 If *seq* is empty or ``(None, None)``, the linestyle will be set
1312 to solid.
1313 """
1314 if seq == (None, None) or len(seq) == 0:
1315 self.set_linestyle('-')
1316 else:
1317 self.set_linestyle((0, seq))
1319 def update_from(self, other):
1320 """Copy properties from *other* to self."""
1321 super().update_from(other)
1322 self._linestyle = other._linestyle
1323 self._linewidth = other._linewidth
1324 self._color = other._color
1325 self._gapcolor = other._gapcolor
1326 self._markersize = other._markersize
1327 self._markerfacecolor = other._markerfacecolor
1328 self._markerfacecoloralt = other._markerfacecoloralt
1329 self._markeredgecolor = other._markeredgecolor
1330 self._markeredgewidth = other._markeredgewidth
1331 self._unscaled_dash_pattern = other._unscaled_dash_pattern
1332 self._dash_pattern = other._dash_pattern
1333 self._dashcapstyle = other._dashcapstyle
1334 self._dashjoinstyle = other._dashjoinstyle
1335 self._solidcapstyle = other._solidcapstyle
1336 self._solidjoinstyle = other._solidjoinstyle
1338 self._linestyle = other._linestyle
1339 self._marker = MarkerStyle(marker=other._marker)
1340 self._drawstyle = other._drawstyle
1342 @_docstring.interpd
1343 def set_dash_joinstyle(self, s):
1344 """
1345 How to join segments of the line if it `~Line2D.is_dashed`.
1347 The default joinstyle is :rc:`lines.dash_joinstyle`.
1349 Parameters
1350 ----------
1351 s : `.JoinStyle` or %(JoinStyle)s
1352 """
1353 js = JoinStyle(s)
1354 if self._dashjoinstyle != js:
1355 self.stale = True
1356 self._dashjoinstyle = js
1358 @_docstring.interpd
1359 def set_solid_joinstyle(self, s):
1360 """
1361 How to join segments if the line is solid (not `~Line2D.is_dashed`).
1363 The default joinstyle is :rc:`lines.solid_joinstyle`.
1365 Parameters
1366 ----------
1367 s : `.JoinStyle` or %(JoinStyle)s
1368 """
1369 js = JoinStyle(s)
1370 if self._solidjoinstyle != js:
1371 self.stale = True
1372 self._solidjoinstyle = js
1374 def get_dash_joinstyle(self):
1375 """
1376 Return the `.JoinStyle` for dashed lines.
1378 See also `~.Line2D.set_dash_joinstyle`.
1379 """
1380 return self._dashjoinstyle.name
1382 def get_solid_joinstyle(self):
1383 """
1384 Return the `.JoinStyle` for solid lines.
1386 See also `~.Line2D.set_solid_joinstyle`.
1387 """
1388 return self._solidjoinstyle.name
1390 @_docstring.interpd
1391 def set_dash_capstyle(self, s):
1392 """
1393 How to draw the end caps if the line is `~Line2D.is_dashed`.
1395 The default capstyle is :rc:`lines.dash_capstyle`.
1397 Parameters
1398 ----------
1399 s : `.CapStyle` or %(CapStyle)s
1400 """
1401 cs = CapStyle(s)
1402 if self._dashcapstyle != cs:
1403 self.stale = True
1404 self._dashcapstyle = cs
1406 @_docstring.interpd
1407 def set_solid_capstyle(self, s):
1408 """
1409 How to draw the end caps if the line is solid (not `~Line2D.is_dashed`)
1411 The default capstyle is :rc:`lines.solid_capstyle`.
1413 Parameters
1414 ----------
1415 s : `.CapStyle` or %(CapStyle)s
1416 """
1417 cs = CapStyle(s)
1418 if self._solidcapstyle != cs:
1419 self.stale = True
1420 self._solidcapstyle = cs
1422 def get_dash_capstyle(self):
1423 """
1424 Return the `.CapStyle` for dashed lines.
1426 See also `~.Line2D.set_dash_capstyle`.
1427 """
1428 return self._dashcapstyle.name
1430 def get_solid_capstyle(self):
1431 """
1432 Return the `.CapStyle` for solid lines.
1434 See also `~.Line2D.set_solid_capstyle`.
1435 """
1436 return self._solidcapstyle.name
1438 def is_dashed(self):
1439 """
1440 Return whether line has a dashed linestyle.
1442 A custom linestyle is assumed to be dashed, we do not inspect the
1443 ``onoffseq`` directly.
1445 See also `~.Line2D.set_linestyle`.
1446 """
1447 return self._linestyle in ('--', '-.', ':')
1450class _AxLine(Line2D):
1451 """
1452 A helper class that implements `~.Axes.axline`, by recomputing the artist
1453 transform at draw time.
1454 """
1456 def __init__(self, xy1, xy2, slope, **kwargs):
1457 super().__init__([0, 1], [0, 1], **kwargs)
1459 if (xy2 is None and slope is None or
1460 xy2 is not None and slope is not None):
1461 raise TypeError(
1462 "Exactly one of 'xy2' and 'slope' must be given")
1464 self._slope = slope
1465 self._xy1 = xy1
1466 self._xy2 = xy2
1468 def get_transform(self):
1469 ax = self.axes
1470 points_transform = self._transform - ax.transData + ax.transScale
1472 if self._xy2 is not None:
1473 # two points were given
1474 (x1, y1), (x2, y2) = \
1475 points_transform.transform([self._xy1, self._xy2])
1476 dx = x2 - x1
1477 dy = y2 - y1
1478 if np.allclose(x1, x2):
1479 if np.allclose(y1, y2):
1480 raise ValueError(
1481 f"Cannot draw a line through two identical points "
1482 f"(x={(x1, x2)}, y={(y1, y2)})")
1483 slope = np.inf
1484 else:
1485 slope = dy / dx
1486 else:
1487 # one point and a slope were given
1488 x1, y1 = points_transform.transform(self._xy1)
1489 slope = self._slope
1490 (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
1491 # General case: find intersections with view limits in either
1492 # direction, and draw between the middle two points.
1493 if np.isclose(slope, 0):
1494 start = vxlo, y1
1495 stop = vxhi, y1
1496 elif np.isinf(slope):
1497 start = x1, vylo
1498 stop = x1, vyhi
1499 else:
1500 _, start, stop, _ = sorted([
1501 (vxlo, y1 + (vxlo - x1) * slope),
1502 (vxhi, y1 + (vxhi - x1) * slope),
1503 (x1 + (vylo - y1) / slope, vylo),
1504 (x1 + (vyhi - y1) / slope, vyhi),
1505 ])
1506 return (BboxTransformTo(Bbox([start, stop]))
1507 + ax.transLimits + ax.transAxes)
1509 def draw(self, renderer):
1510 self._transformed_path = None # Force regen.
1511 super().draw(renderer)
1514class VertexSelector:
1515 """
1516 Manage the callbacks to maintain a list of selected vertices for `.Line2D`.
1517 Derived classes should override the `process_selected` method to do
1518 something with the picks.
1520 Here is an example which highlights the selected verts with red circles::
1522 import numpy as np
1523 import matplotlib.pyplot as plt
1524 import matplotlib.lines as lines
1526 class HighlightSelected(lines.VertexSelector):
1527 def __init__(self, line, fmt='ro', **kwargs):
1528 super().__init__(line)
1529 self.markers, = self.axes.plot([], [], fmt, **kwargs)
1531 def process_selected(self, ind, xs, ys):
1532 self.markers.set_data(xs, ys)
1533 self.canvas.draw()
1535 fig, ax = plt.subplots()
1536 x, y = np.random.rand(2, 30)
1537 line, = ax.plot(x, y, 'bs-', picker=5)
1539 selector = HighlightSelected(line)
1540 plt.show()
1541 """
1543 def __init__(self, line):
1544 """
1545 Parameters
1546 ----------
1547 line : `.Line2D`
1548 The line must already have been added to an `~.axes.Axes` and must
1549 have its picker property set.
1550 """
1551 if line.axes is None:
1552 raise RuntimeError('You must first add the line to the Axes')
1553 if line.get_picker() is None:
1554 raise RuntimeError('You must first set the picker property '
1555 'of the line')
1556 self.axes = line.axes
1557 self.line = line
1558 self.cid = self.canvas.callbacks._connect_picklable(
1559 'pick_event', self.onpick)
1560 self.ind = set()
1562 canvas = property(lambda self: self.axes.figure.canvas)
1564 def process_selected(self, ind, xs, ys):
1565 """
1566 Default "do nothing" implementation of the `process_selected` method.
1568 Parameters
1569 ----------
1570 ind : list of int
1571 The indices of the selected vertices.
1572 xs, ys : array-like
1573 The coordinates of the selected vertices.
1574 """
1575 pass
1577 def onpick(self, event):
1578 """When the line is picked, update the set of selected indices."""
1579 if event.artist is not self.line:
1580 return
1581 self.ind ^= set(event.ind)
1582 ind = sorted(self.ind)
1583 xdata, ydata = self.line.get_data()
1584 self.process_selected(ind, xdata[ind], ydata[ind])
1587lineStyles = Line2D._lineStyles
1588lineMarkers = MarkerStyle.markers
1589drawStyles = Line2D.drawStyles
1590fillStyles = MarkerStyle.fillstyles