Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/lines.py: 18%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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[f'lines.{style}_pattern'])
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 _get_inverse_dash_pattern(offset, dashes):
64 """Return the inverse of the given dash pattern, for filling the gaps."""
65 # Define the inverse pattern by moving the last gap to the start of the
66 # sequence.
67 gaps = dashes[-1:] + dashes[:-1]
68 # Set the offset so that this new first segment is skipped
69 # (see backend_bases.GraphicsContextBase.set_dashes for offset definition).
70 offset_gaps = offset + dashes[-1]
72 return offset_gaps, gaps
75def _scale_dashes(offset, dashes, lw):
76 if not mpl.rcParams['lines.scale_dashes']:
77 return offset, dashes
78 scaled_offset = offset * lw
79 scaled_dashes = ([x * lw if x is not None else None for x in dashes]
80 if dashes is not None else None)
81 return scaled_offset, scaled_dashes
84def segment_hits(cx, cy, x, y, radius):
85 """
86 Return the indices of the segments in the polyline with coordinates (*cx*,
87 *cy*) that are within a distance *radius* of the point (*x*, *y*).
88 """
89 # Process single points specially
90 if len(x) <= 1:
91 res, = np.nonzero((cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2)
92 return res
94 # We need to lop the last element off a lot.
95 xr, yr = x[:-1], y[:-1]
97 # Only look at line segments whose nearest point to C on the line
98 # lies within the segment.
99 dx, dy = x[1:] - xr, y[1:] - yr
100 Lnorm_sq = dx ** 2 + dy ** 2 # Possibly want to eliminate Lnorm==0
101 u = ((cx - xr) * dx + (cy - yr) * dy) / Lnorm_sq
102 candidates = (u >= 0) & (u <= 1)
104 # Note that there is a little area near one side of each point
105 # which will be near neither segment, and another which will
106 # be near both, depending on the angle of the lines. The
107 # following radius test eliminates these ambiguities.
108 point_hits = (cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2
109 candidates = candidates & ~(point_hits[:-1] | point_hits[1:])
111 # For those candidates which remain, determine how far they lie away
112 # from the line.
113 px, py = xr + u * dx, yr + u * dy
114 line_hits = (cx - px) ** 2 + (cy - py) ** 2 <= radius ** 2
115 line_hits = line_hits & candidates
116 points, = point_hits.ravel().nonzero()
117 lines, = line_hits.ravel().nonzero()
118 return np.concatenate((points, lines))
121def _mark_every_path(markevery, tpath, affine, ax):
122 """
123 Helper function that sorts out how to deal the input
124 `markevery` and returns the points where markers should be drawn.
126 Takes in the `markevery` value and the line path and returns the
127 sub-sampled path.
128 """
129 # pull out the two bits of data we want from the path
130 codes, verts = tpath.codes, tpath.vertices
132 def _slice_or_none(in_v, slc):
133 """Helper function to cope with `codes` being an ndarray or `None`."""
134 if in_v is None:
135 return None
136 return in_v[slc]
138 # if just an int, assume starting at 0 and make a tuple
139 if isinstance(markevery, Integral):
140 markevery = (0, markevery)
141 # if just a float, assume starting at 0.0 and make a tuple
142 elif isinstance(markevery, Real):
143 markevery = (0.0, markevery)
145 if isinstance(markevery, tuple):
146 if len(markevery) != 2:
147 raise ValueError('`markevery` is a tuple but its len is not 2; '
148 f'markevery={markevery}')
149 start, step = markevery
150 # if step is an int, old behavior
151 if isinstance(step, Integral):
152 # tuple of 2 int is for backwards compatibility,
153 if not isinstance(start, Integral):
154 raise ValueError(
155 '`markevery` is a tuple with len 2 and second element is '
156 'an int, but the first element is not an int; '
157 f'markevery={markevery}')
158 # just return, we are done here
160 return Path(verts[slice(start, None, step)],
161 _slice_or_none(codes, slice(start, None, step)))
163 elif isinstance(step, Real):
164 if not isinstance(start, Real):
165 raise ValueError(
166 '`markevery` is a tuple with len 2 and second element is '
167 'a float, but the first element is not a float or an int; '
168 f'markevery={markevery}')
169 if ax is None:
170 raise ValueError(
171 "markevery is specified relative to the Axes size, but "
172 "the line does not have a Axes as parent")
174 # calc cumulative distance along path (in display coords):
175 fin = np.isfinite(verts).all(axis=1)
176 fverts = verts[fin]
177 disp_coords = affine.transform(fverts)
179 delta = np.empty((len(disp_coords), 2))
180 delta[0, :] = 0
181 delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :]
182 delta = np.hypot(*delta.T).cumsum()
183 # calc distance between markers along path based on the Axes
184 # bounding box diagonal being a distance of unity:
185 (x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]])
186 scale = np.hypot(x1 - x0, y1 - y0)
187 marker_delta = np.arange(start * scale, delta[-1], step * scale)
188 # find closest actual data point that is closest to
189 # the theoretical distance along the path:
190 inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis])
191 inds = inds.argmin(axis=1)
192 inds = np.unique(inds)
193 # return, we are done here
194 return Path(fverts[inds], _slice_or_none(codes, inds))
195 else:
196 raise ValueError(
197 f"markevery={markevery!r} is a tuple with len 2, but its "
198 f"second element is not an int or a float")
200 elif isinstance(markevery, slice):
201 # mazol tov, it's already a slice, just return
202 return Path(verts[markevery], _slice_or_none(codes, markevery))
204 elif np.iterable(markevery):
205 # fancy indexing
206 try:
207 return Path(verts[markevery], _slice_or_none(codes, markevery))
208 except (ValueError, IndexError) as err:
209 raise ValueError(
210 f"markevery={markevery!r} is iterable but not a valid numpy "
211 f"fancy index") from err
212 else:
213 raise ValueError(f"markevery={markevery!r} is not a recognized value")
216@_docstring.interpd
217@_api.define_aliases({
218 "antialiased": ["aa"],
219 "color": ["c"],
220 "drawstyle": ["ds"],
221 "linestyle": ["ls"],
222 "linewidth": ["lw"],
223 "markeredgecolor": ["mec"],
224 "markeredgewidth": ["mew"],
225 "markerfacecolor": ["mfc"],
226 "markerfacecoloralt": ["mfcalt"],
227 "markersize": ["ms"],
228})
229class Line2D(Artist):
230 """
231 A line - the line can have both a solid linestyle connecting all
232 the vertices, and a marker at each vertex. Additionally, the
233 drawing of the solid line is influenced by the drawstyle, e.g., one
234 can create "stepped" lines in various styles.
235 """
237 lineStyles = _lineStyles = { # hidden names deprecated
238 '-': '_draw_solid',
239 '--': '_draw_dashed',
240 '-.': '_draw_dash_dot',
241 ':': '_draw_dotted',
242 'None': '_draw_nothing',
243 ' ': '_draw_nothing',
244 '': '_draw_nothing',
245 }
247 _drawStyles_l = {
248 'default': '_draw_lines',
249 'steps-mid': '_draw_steps_mid',
250 'steps-pre': '_draw_steps_pre',
251 'steps-post': '_draw_steps_post',
252 }
254 _drawStyles_s = {
255 'steps': '_draw_steps_pre',
256 }
258 # drawStyles should now be deprecated.
259 drawStyles = {**_drawStyles_l, **_drawStyles_s}
260 # Need a list ordered with long names first:
261 drawStyleKeys = [*_drawStyles_l, *_drawStyles_s]
263 # Referenced here to maintain API. These are defined in
264 # MarkerStyle
265 markers = MarkerStyle.markers
266 filled_markers = MarkerStyle.filled_markers
267 fillStyles = MarkerStyle.fillstyles
269 zorder = 2
271 _subslice_optim_min_size = 1000
273 def __str__(self):
274 if self._label != "":
275 return f"Line2D({self._label})"
276 elif self._x is None:
277 return "Line2D()"
278 elif len(self._x) > 3:
279 return "Line2D(({:g},{:g}),({:g},{:g}),...,({:g},{:g}))".format(
280 self._x[0], self._y[0],
281 self._x[1], self._y[1],
282 self._x[-1], self._y[-1])
283 else:
284 return "Line2D(%s)" % ",".join(
285 map("({:g},{:g})".format, self._x, self._y))
287 def __init__(self, xdata, ydata, *,
288 linewidth=None, # all Nones default to rc
289 linestyle=None,
290 color=None,
291 gapcolor=None,
292 marker=None,
293 markersize=None,
294 markeredgewidth=None,
295 markeredgecolor=None,
296 markerfacecolor=None,
297 markerfacecoloralt='none',
298 fillstyle=None,
299 antialiased=None,
300 dash_capstyle=None,
301 solid_capstyle=None,
302 dash_joinstyle=None,
303 solid_joinstyle=None,
304 pickradius=5,
305 drawstyle=None,
306 markevery=None,
307 **kwargs
308 ):
309 """
310 Create a `.Line2D` instance with *x* and *y* data in sequences of
311 *xdata*, *ydata*.
313 Additional keyword arguments are `.Line2D` properties:
315 %(Line2D:kwdoc)s
317 See :meth:`set_linestyle` for a description of the line styles,
318 :meth:`set_marker` for a description of the markers, and
319 :meth:`set_drawstyle` for a description of the draw styles.
321 """
322 super().__init__()
324 # Convert sequences to NumPy arrays.
325 if not np.iterable(xdata):
326 raise RuntimeError('xdata must be a sequence')
327 if not np.iterable(ydata):
328 raise RuntimeError('ydata must be a sequence')
330 if linewidth is None:
331 linewidth = mpl.rcParams['lines.linewidth']
333 if linestyle is None:
334 linestyle = mpl.rcParams['lines.linestyle']
335 if marker is None:
336 marker = mpl.rcParams['lines.marker']
337 if color is None:
338 color = mpl.rcParams['lines.color']
340 if markersize is None:
341 markersize = mpl.rcParams['lines.markersize']
342 if antialiased is None:
343 antialiased = mpl.rcParams['lines.antialiased']
344 if dash_capstyle is None:
345 dash_capstyle = mpl.rcParams['lines.dash_capstyle']
346 if dash_joinstyle is None:
347 dash_joinstyle = mpl.rcParams['lines.dash_joinstyle']
348 if solid_capstyle is None:
349 solid_capstyle = mpl.rcParams['lines.solid_capstyle']
350 if solid_joinstyle is None:
351 solid_joinstyle = mpl.rcParams['lines.solid_joinstyle']
353 if drawstyle is None:
354 drawstyle = 'default'
356 self._dashcapstyle = None
357 self._dashjoinstyle = None
358 self._solidjoinstyle = None
359 self._solidcapstyle = None
360 self.set_dash_capstyle(dash_capstyle)
361 self.set_dash_joinstyle(dash_joinstyle)
362 self.set_solid_capstyle(solid_capstyle)
363 self.set_solid_joinstyle(solid_joinstyle)
365 self._linestyles = None
366 self._drawstyle = None
367 self._linewidth = linewidth
368 self._unscaled_dash_pattern = (0, None) # offset, dash
369 self._dash_pattern = (0, None) # offset, dash (scaled by linewidth)
371 self.set_linewidth(linewidth)
372 self.set_linestyle(linestyle)
373 self.set_drawstyle(drawstyle)
375 self._color = None
376 self.set_color(color)
377 if marker is None:
378 marker = 'none' # Default.
379 if not isinstance(marker, MarkerStyle):
380 self._marker = MarkerStyle(marker, fillstyle)
381 else:
382 self._marker = marker
384 self._gapcolor = None
385 self.set_gapcolor(gapcolor)
387 self._markevery = None
388 self._markersize = None
389 self._antialiased = None
391 self.set_markevery(markevery)
392 self.set_antialiased(antialiased)
393 self.set_markersize(markersize)
395 self._markeredgecolor = None
396 self._markeredgewidth = None
397 self._markerfacecolor = None
398 self._markerfacecoloralt = None
400 self.set_markerfacecolor(markerfacecolor) # Normalizes None to rc.
401 self.set_markerfacecoloralt(markerfacecoloralt)
402 self.set_markeredgecolor(markeredgecolor) # Normalizes None to rc.
403 self.set_markeredgewidth(markeredgewidth)
405 # update kwargs before updating data to give the caller a
406 # chance to init axes (and hence unit support)
407 self._internal_update(kwargs)
408 self.pickradius = pickradius
409 self.ind_offset = 0
410 if (isinstance(self._picker, Number) and
411 not isinstance(self._picker, bool)):
412 self._pickradius = self._picker
414 self._xorig = np.asarray([])
415 self._yorig = np.asarray([])
416 self._invalidx = True
417 self._invalidy = True
418 self._x = None
419 self._y = None
420 self._xy = None
421 self._path = None
422 self._transformed_path = None
423 self._subslice = False
424 self._x_filled = None # used in subslicing; only x is needed
426 self.set_data(xdata, ydata)
428 def contains(self, mouseevent):
429 """
430 Test whether *mouseevent* occurred on the line.
432 An event is deemed to have occurred "on" the line if it is less
433 than ``self.pickradius`` (default: 5 points) away from it. Use
434 `~.Line2D.get_pickradius` or `~.Line2D.set_pickradius` to get or set
435 the pick radius.
437 Parameters
438 ----------
439 mouseevent : `~matplotlib.backend_bases.MouseEvent`
441 Returns
442 -------
443 contains : bool
444 Whether any values are within the radius.
445 details : dict
446 A dictionary ``{'ind': pointlist}``, where *pointlist* is a
447 list of points of the line that are within the pickradius around
448 the event position.
450 TODO: sort returned indices by distance
451 """
452 if self._different_canvas(mouseevent):
453 return False, {}
455 # Make sure we have data to plot
456 if self._invalidy or self._invalidx:
457 self.recache()
458 if len(self._xy) == 0:
459 return False, {}
461 # Convert points to pixels
462 transformed_path = self._get_transformed_path()
463 path, affine = transformed_path.get_transformed_path_and_affine()
464 path = affine.transform_path(path)
465 xy = path.vertices
466 xt = xy[:, 0]
467 yt = xy[:, 1]
469 # Convert pick radius from points to pixels
470 if self.figure is None:
471 _log.warning('no figure set when check if mouse is on line')
472 pixels = self._pickradius
473 else:
474 pixels = self.figure.dpi / 72. * self._pickradius
476 # The math involved in checking for containment (here and inside of
477 # segment_hits) assumes that it is OK to overflow, so temporarily set
478 # the error flags accordingly.
479 with np.errstate(all='ignore'):
480 # Check for collision
481 if self._linestyle in ['None', None]:
482 # If no line, return the nearby point(s)
483 ind, = np.nonzero(
484 (xt - mouseevent.x) ** 2 + (yt - mouseevent.y) ** 2
485 <= pixels ** 2)
486 else:
487 # If line, return the nearby segment(s)
488 ind = segment_hits(mouseevent.x, mouseevent.y, xt, yt, pixels)
489 if self._drawstyle.startswith("steps"):
490 ind //= 2
492 ind += self.ind_offset
494 # Return the point(s) within radius
495 return len(ind) > 0, dict(ind=ind)
497 def get_pickradius(self):
498 """
499 Return the pick radius used for containment tests.
501 See `.contains` for more details.
502 """
503 return self._pickradius
505 def set_pickradius(self, pickradius):
506 """
507 Set the pick radius used for containment tests.
509 See `.contains` for more details.
511 Parameters
512 ----------
513 pickradius : float
514 Pick radius, in points.
515 """
516 if not isinstance(pickradius, Real) or pickradius < 0:
517 raise ValueError("pick radius should be a distance")
518 self._pickradius = pickradius
520 pickradius = property(get_pickradius, set_pickradius)
522 def get_fillstyle(self):
523 """
524 Return the marker fill style.
526 See also `~.Line2D.set_fillstyle`.
527 """
528 return self._marker.get_fillstyle()
530 def set_fillstyle(self, fs):
531 """
532 Set the marker fill style.
534 Parameters
535 ----------
536 fs : {'full', 'left', 'right', 'bottom', 'top', 'none'}
537 Possible values:
539 - 'full': Fill the whole marker with the *markerfacecolor*.
540 - 'left', 'right', 'bottom', 'top': Fill the marker half at
541 the given side with the *markerfacecolor*. The other
542 half of the marker is filled with *markerfacecoloralt*.
543 - 'none': No filling.
545 For examples see :ref:`marker_fill_styles`.
546 """
547 self.set_marker(MarkerStyle(self._marker.get_marker(), fs))
548 self.stale = True
550 def set_markevery(self, every):
551 """
552 Set the markevery property to subsample the plot when using markers.
554 e.g., if ``every=5``, every 5-th marker will be plotted.
556 Parameters
557 ----------
558 every : None or int or (int, int) or slice or list[int] or float or \
559(float, float) or list[bool]
560 Which markers to plot.
562 - ``every=None``: every point will be plotted.
563 - ``every=N``: every N-th marker will be plotted starting with
564 marker 0.
565 - ``every=(start, N)``: every N-th marker, starting at index
566 *start*, will be plotted.
567 - ``every=slice(start, end, N)``: every N-th marker, starting at
568 index *start*, up to but not including index *end*, will be
569 plotted.
570 - ``every=[i, j, m, ...]``: only markers at the given indices
571 will be plotted.
572 - ``every=[True, False, True, ...]``: only positions that are True
573 will be plotted. The list must have the same length as the data
574 points.
575 - ``every=0.1``, (i.e. a float): markers will be spaced at
576 approximately equal visual distances along the line; the distance
577 along the line between markers is determined by multiplying the
578 display-coordinate distance of the Axes bounding-box diagonal
579 by the value of *every*.
580 - ``every=(0.5, 0.1)`` (i.e. a length-2 tuple of float): similar
581 to ``every=0.1`` but the first marker will be offset along the
582 line by 0.5 multiplied by the
583 display-coordinate-diagonal-distance along the line.
585 For examples see
586 :doc:`/gallery/lines_bars_and_markers/markevery_demo`.
588 Notes
589 -----
590 Setting *markevery* will still only draw markers at actual data points.
591 While the float argument form aims for uniform visual spacing, it has
592 to coerce from the ideal spacing to the nearest available data point.
593 Depending on the number and distribution of data points, the result
594 may still not look evenly spaced.
596 When using a start offset to specify the first marker, the offset will
597 be from the first data point which may be different from the first
598 the visible data point if the plot is zoomed in.
600 If zooming in on a plot when using float arguments then the actual
601 data points that have markers will change because the distance between
602 markers is always determined from the display-coordinates
603 axes-bounding-box-diagonal regardless of the actual axes data limits.
605 """
606 self._markevery = every
607 self.stale = True
609 def get_markevery(self):
610 """
611 Return the markevery setting for marker subsampling.
613 See also `~.Line2D.set_markevery`.
614 """
615 return self._markevery
617 def set_picker(self, p):
618 """
619 Set the event picker details for the line.
621 Parameters
622 ----------
623 p : float or callable[[Artist, Event], tuple[bool, dict]]
624 If a float, it is used as the pick radius in points.
625 """
626 if not callable(p):
627 self.set_pickradius(p)
628 self._picker = p
630 def get_bbox(self):
631 """Get the bounding box of this line."""
632 bbox = Bbox([[0, 0], [0, 0]])
633 bbox.update_from_data_xy(self.get_xydata())
634 return bbox
636 def get_window_extent(self, renderer=None):
637 bbox = Bbox([[0, 0], [0, 0]])
638 trans_data_to_xy = self.get_transform().transform
639 bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()),
640 ignore=True)
641 # correct for marker size, if any
642 if self._marker:
643 ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5
644 bbox = bbox.padded(ms)
645 return bbox
647 def set_data(self, *args):
648 """
649 Set the x and y data.
651 Parameters
652 ----------
653 *args : (2, N) array or two 1D arrays
655 See Also
656 --------
657 set_xdata
658 set_ydata
659 """
660 if len(args) == 1:
661 (x, y), = args
662 else:
663 x, y = args
665 self.set_xdata(x)
666 self.set_ydata(y)
668 def recache_always(self):
669 self.recache(always=True)
671 def recache(self, always=False):
672 if always or self._invalidx:
673 xconv = self.convert_xunits(self._xorig)
674 x = _to_unmasked_float_array(xconv).ravel()
675 else:
676 x = self._x
677 if always or self._invalidy:
678 yconv = self.convert_yunits(self._yorig)
679 y = _to_unmasked_float_array(yconv).ravel()
680 else:
681 y = self._y
683 self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
684 self._x, self._y = self._xy.T # views
686 self._subslice = False
687 if (self.axes
688 and len(x) > self._subslice_optim_min_size
689 and _path.is_sorted_and_has_non_nan(x)
690 and self.axes.name == 'rectilinear'
691 and self.axes.get_xscale() == 'linear'
692 and self._markevery is None
693 and self.get_clip_on()
694 and self.get_transform() == self.axes.transData):
695 self._subslice = True
696 nanmask = np.isnan(x)
697 if nanmask.any():
698 self._x_filled = self._x.copy()
699 indices = np.arange(len(x))
700 self._x_filled[nanmask] = np.interp(
701 indices[nanmask], indices[~nanmask], self._x[~nanmask])
702 else:
703 self._x_filled = self._x
705 if self._path is not None:
706 interpolation_steps = self._path._interpolation_steps
707 else:
708 interpolation_steps = 1
709 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T)
710 self._path = Path(np.asarray(xy).T,
711 _interpolation_steps=interpolation_steps)
712 self._transformed_path = None
713 self._invalidx = False
714 self._invalidy = False
716 def _transform_path(self, subslice=None):
717 """
718 Put a TransformedPath instance at self._transformed_path;
719 all invalidation of the transform is then handled by the
720 TransformedPath instance.
721 """
722 # Masked arrays are now handled by the Path class itself
723 if subslice is not None:
724 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T)
725 _path = Path(np.asarray(xy).T,
726 _interpolation_steps=self._path._interpolation_steps)
727 else:
728 _path = self._path
729 self._transformed_path = TransformedPath(_path, self.get_transform())
731 def _get_transformed_path(self):
732 """Return this line's `~matplotlib.transforms.TransformedPath`."""
733 if self._transformed_path is None:
734 self._transform_path()
735 return self._transformed_path
737 def set_transform(self, t):
738 # docstring inherited
739 self._invalidx = True
740 self._invalidy = True
741 super().set_transform(t)
743 @allow_rasterization
744 def draw(self, renderer):
745 # docstring inherited
747 if not self.get_visible():
748 return
750 if self._invalidy or self._invalidx:
751 self.recache()
752 self.ind_offset = 0 # Needed for contains() method.
753 if self._subslice and self.axes:
754 x0, x1 = self.axes.get_xbound()
755 i0 = self._x_filled.searchsorted(x0, 'left')
756 i1 = self._x_filled.searchsorted(x1, 'right')
757 subslice = slice(max(i0 - 1, 0), i1 + 1)
758 self.ind_offset = subslice.start
759 self._transform_path(subslice)
760 else:
761 subslice = None
763 if self.get_path_effects():
764 from matplotlib.patheffects import PathEffectRenderer
765 renderer = PathEffectRenderer(self.get_path_effects(), renderer)
767 renderer.open_group('line2d', self.get_gid())
768 if self._lineStyles[self._linestyle] != '_draw_nothing':
769 tpath, affine = (self._get_transformed_path()
770 .get_transformed_path_and_affine())
771 if len(tpath.vertices):
772 gc = renderer.new_gc()
773 self._set_gc_clip(gc)
774 gc.set_url(self.get_url())
776 gc.set_antialiased(self._antialiased)
777 gc.set_linewidth(self._linewidth)
779 if self.is_dashed():
780 cap = self._dashcapstyle
781 join = self._dashjoinstyle
782 else:
783 cap = self._solidcapstyle
784 join = self._solidjoinstyle
785 gc.set_joinstyle(join)
786 gc.set_capstyle(cap)
787 gc.set_snap(self.get_snap())
788 if self.get_sketch_params() is not None:
789 gc.set_sketch_params(*self.get_sketch_params())
791 # We first draw a path within the gaps if needed.
792 if self.is_dashed() and self._gapcolor is not None:
793 lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha)
794 gc.set_foreground(lc_rgba, isRGBA=True)
796 offset_gaps, gaps = _get_inverse_dash_pattern(
797 *self._dash_pattern)
799 gc.set_dashes(offset_gaps, gaps)
800 renderer.draw_path(gc, tpath, affine.frozen())
802 lc_rgba = mcolors.to_rgba(self._color, self._alpha)
803 gc.set_foreground(lc_rgba, isRGBA=True)
805 gc.set_dashes(*self._dash_pattern)
806 renderer.draw_path(gc, tpath, affine.frozen())
807 gc.restore()
809 if self._marker and self._markersize > 0:
810 gc = renderer.new_gc()
811 self._set_gc_clip(gc)
812 gc.set_url(self.get_url())
813 gc.set_linewidth(self._markeredgewidth)
814 gc.set_antialiased(self._antialiased)
816 ec_rgba = mcolors.to_rgba(
817 self.get_markeredgecolor(), self._alpha)
818 fc_rgba = mcolors.to_rgba(
819 self._get_markerfacecolor(), self._alpha)
820 fcalt_rgba = mcolors.to_rgba(
821 self._get_markerfacecolor(alt=True), self._alpha)
822 # If the edgecolor is "auto", it is set according to the *line*
823 # color but inherits the alpha value of the *face* color, if any.
824 if (cbook._str_equal(self._markeredgecolor, "auto")
825 and not cbook._str_lower_equal(
826 self.get_markerfacecolor(), "none")):
827 ec_rgba = ec_rgba[:3] + (fc_rgba[3],)
828 gc.set_foreground(ec_rgba, isRGBA=True)
829 if self.get_sketch_params() is not None:
830 scale, length, randomness = self.get_sketch_params()
831 gc.set_sketch_params(scale/2, length/2, 2*randomness)
833 marker = self._marker
835 # Markers *must* be drawn ignoring the drawstyle (but don't pay the
836 # recaching if drawstyle is already "default").
837 if self.get_drawstyle() != "default":
838 with cbook._setattr_cm(
839 self, _drawstyle="default", _transformed_path=None):
840 self.recache()
841 self._transform_path(subslice)
842 tpath, affine = (self._get_transformed_path()
843 .get_transformed_points_and_affine())
844 else:
845 tpath, affine = (self._get_transformed_path()
846 .get_transformed_points_and_affine())
848 if len(tpath.vertices):
849 # subsample the markers if markevery is not None
850 markevery = self.get_markevery()
851 if markevery is not None:
852 subsampled = _mark_every_path(
853 markevery, tpath, affine, self.axes)
854 else:
855 subsampled = tpath
857 snap = marker.get_snap_threshold()
858 if isinstance(snap, Real):
859 snap = renderer.points_to_pixels(self._markersize) >= snap
860 gc.set_snap(snap)
861 gc.set_joinstyle(marker.get_joinstyle())
862 gc.set_capstyle(marker.get_capstyle())
863 marker_path = marker.get_path()
864 marker_trans = marker.get_transform()
865 w = renderer.points_to_pixels(self._markersize)
867 if cbook._str_equal(marker.get_marker(), ","):
868 gc.set_linewidth(0)
869 else:
870 # Don't scale for pixels, and don't stroke them
871 marker_trans = marker_trans.scale(w)
872 renderer.draw_markers(gc, marker_path, marker_trans,
873 subsampled, affine.frozen(),
874 fc_rgba)
876 alt_marker_path = marker.get_alt_path()
877 if alt_marker_path:
878 alt_marker_trans = marker.get_alt_transform()
879 alt_marker_trans = alt_marker_trans.scale(w)
880 renderer.draw_markers(
881 gc, alt_marker_path, alt_marker_trans, subsampled,
882 affine.frozen(), fcalt_rgba)
884 gc.restore()
886 renderer.close_group('line2d')
887 self.stale = False
889 def get_antialiased(self):
890 """Return whether antialiased rendering is used."""
891 return self._antialiased
893 def get_color(self):
894 """
895 Return the line color.
897 See also `~.Line2D.set_color`.
898 """
899 return self._color
901 def get_drawstyle(self):
902 """
903 Return the drawstyle.
905 See also `~.Line2D.set_drawstyle`.
906 """
907 return self._drawstyle
909 def get_gapcolor(self):
910 """
911 Return the line gapcolor.
913 See also `~.Line2D.set_gapcolor`.
914 """
915 return self._gapcolor
917 def get_linestyle(self):
918 """
919 Return the linestyle.
921 See also `~.Line2D.set_linestyle`.
922 """
923 return self._linestyle
925 def get_linewidth(self):
926 """
927 Return the linewidth in points.
929 See also `~.Line2D.set_linewidth`.
930 """
931 return self._linewidth
933 def get_marker(self):
934 """
935 Return the line marker.
937 See also `~.Line2D.set_marker`.
938 """
939 return self._marker.get_marker()
941 def get_markeredgecolor(self):
942 """
943 Return the marker edge color.
945 See also `~.Line2D.set_markeredgecolor`.
946 """
947 mec = self._markeredgecolor
948 if cbook._str_equal(mec, 'auto'):
949 if mpl.rcParams['_internal.classic_mode']:
950 if self._marker.get_marker() in ('.', ','):
951 return self._color
952 if (self._marker.is_filled()
953 and self._marker.get_fillstyle() != 'none'):
954 return 'k' # Bad hard-wired default...
955 return self._color
956 else:
957 return mec
959 def get_markeredgewidth(self):
960 """
961 Return the marker edge width in points.
963 See also `~.Line2D.set_markeredgewidth`.
964 """
965 return self._markeredgewidth
967 def _get_markerfacecolor(self, alt=False):
968 if self._marker.get_fillstyle() == 'none':
969 return 'none'
970 fc = self._markerfacecoloralt if alt else self._markerfacecolor
971 if cbook._str_lower_equal(fc, 'auto'):
972 return self._color
973 else:
974 return fc
976 def get_markerfacecolor(self):
977 """
978 Return the marker face color.
980 See also `~.Line2D.set_markerfacecolor`.
981 """
982 return self._get_markerfacecolor(alt=False)
984 def get_markerfacecoloralt(self):
985 """
986 Return the alternate marker face color.
988 See also `~.Line2D.set_markerfacecoloralt`.
989 """
990 return self._get_markerfacecolor(alt=True)
992 def get_markersize(self):
993 """
994 Return the marker size in points.
996 See also `~.Line2D.set_markersize`.
997 """
998 return self._markersize
1000 def get_data(self, orig=True):
1001 """
1002 Return the line data as an ``(xdata, ydata)`` pair.
1004 If *orig* is *True*, return the original data.
1005 """
1006 return self.get_xdata(orig=orig), self.get_ydata(orig=orig)
1008 def get_xdata(self, orig=True):
1009 """
1010 Return the xdata.
1012 If *orig* is *True*, return the original data, else the
1013 processed data.
1014 """
1015 if orig:
1016 return self._xorig
1017 if self._invalidx:
1018 self.recache()
1019 return self._x
1021 def get_ydata(self, orig=True):
1022 """
1023 Return the ydata.
1025 If *orig* is *True*, return the original data, else the
1026 processed data.
1027 """
1028 if orig:
1029 return self._yorig
1030 if self._invalidy:
1031 self.recache()
1032 return self._y
1034 def get_path(self):
1035 """Return the `~matplotlib.path.Path` associated with this line."""
1036 if self._invalidy or self._invalidx:
1037 self.recache()
1038 return self._path
1040 def get_xydata(self):
1041 """Return the *xy* data as a (N, 2) array."""
1042 if self._invalidy or self._invalidx:
1043 self.recache()
1044 return self._xy
1046 def set_antialiased(self, b):
1047 """
1048 Set whether to use antialiased rendering.
1050 Parameters
1051 ----------
1052 b : bool
1053 """
1054 if self._antialiased != b:
1055 self.stale = True
1056 self._antialiased = b
1058 def set_color(self, color):
1059 """
1060 Set the color of the line.
1062 Parameters
1063 ----------
1064 color : :mpltype:`color`
1065 """
1066 mcolors._check_color_like(color=color)
1067 self._color = color
1068 self.stale = True
1070 def set_drawstyle(self, drawstyle):
1071 """
1072 Set the drawstyle of the plot.
1074 The drawstyle determines how the points are connected.
1076 Parameters
1077 ----------
1078 drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \
1079'steps-post'}, default: 'default'
1080 For 'default', the points are connected with straight lines.
1082 The steps variants connect the points with step-like lines,
1083 i.e. horizontal lines with vertical steps. They differ in the
1084 location of the step:
1086 - 'steps-pre': The step is at the beginning of the line segment,
1087 i.e. the line will be at the y-value of point to the right.
1088 - 'steps-mid': The step is halfway between the points.
1089 - 'steps-post: The step is at the end of the line segment,
1090 i.e. the line will be at the y-value of the point to the left.
1091 - 'steps' is equal to 'steps-pre' and is maintained for
1092 backward-compatibility.
1094 For examples see :doc:`/gallery/lines_bars_and_markers/step_demo`.
1095 """
1096 if drawstyle is None:
1097 drawstyle = 'default'
1098 _api.check_in_list(self.drawStyles, drawstyle=drawstyle)
1099 if self._drawstyle != drawstyle:
1100 self.stale = True
1101 # invalidate to trigger a recache of the path
1102 self._invalidx = True
1103 self._drawstyle = drawstyle
1105 def set_gapcolor(self, gapcolor):
1106 """
1107 Set a color to fill the gaps in the dashed line style.
1109 .. note::
1111 Striped lines are created by drawing two interleaved dashed lines.
1112 There can be overlaps between those two, which may result in
1113 artifacts when using transparency.
1115 This functionality is experimental and may change.
1117 Parameters
1118 ----------
1119 gapcolor : :mpltype:`color` or None
1120 The color with which to fill the gaps. If None, the gaps are
1121 unfilled.
1122 """
1123 if gapcolor is not None:
1124 mcolors._check_color_like(color=gapcolor)
1125 self._gapcolor = gapcolor
1126 self.stale = True
1128 def set_linewidth(self, w):
1129 """
1130 Set the line width in points.
1132 Parameters
1133 ----------
1134 w : float
1135 Line width, in points.
1136 """
1137 w = float(w)
1138 if self._linewidth != w:
1139 self.stale = True
1140 self._linewidth = w
1141 self._dash_pattern = _scale_dashes(*self._unscaled_dash_pattern, w)
1143 def set_linestyle(self, ls):
1144 """
1145 Set the linestyle of the line.
1147 Parameters
1148 ----------
1149 ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
1150 Possible values:
1152 - A string:
1154 ========================================== =================
1155 linestyle description
1156 ========================================== =================
1157 ``'-'`` or ``'solid'`` solid line
1158 ``'--'`` or ``'dashed'`` dashed line
1159 ``'-.'`` or ``'dashdot'`` dash-dotted line
1160 ``':'`` or ``'dotted'`` dotted line
1161 ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
1162 ========================================== =================
1164 - Alternatively a dash tuple of the following form can be
1165 provided::
1167 (offset, onoffseq)
1169 where ``onoffseq`` is an even length tuple of on and off ink
1170 in points. See also :meth:`set_dashes`.
1172 For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`.
1173 """
1174 if isinstance(ls, str):
1175 if ls in [' ', '', 'none']:
1176 ls = 'None'
1177 _api.check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls)
1178 if ls not in self._lineStyles:
1179 ls = ls_mapper_r[ls]
1180 self._linestyle = ls
1181 else:
1182 self._linestyle = '--'
1183 self._unscaled_dash_pattern = _get_dash_pattern(ls)
1184 self._dash_pattern = _scale_dashes(
1185 *self._unscaled_dash_pattern, self._linewidth)
1186 self.stale = True
1188 @_docstring.interpd
1189 def set_marker(self, marker):
1190 """
1191 Set the line marker.
1193 Parameters
1194 ----------
1195 marker : marker style string, `~.path.Path` or `~.markers.MarkerStyle`
1196 See `~matplotlib.markers` for full description of possible
1197 arguments.
1198 """
1199 self._marker = MarkerStyle(marker, self._marker.get_fillstyle())
1200 self.stale = True
1202 def _set_markercolor(self, name, has_rcdefault, val):
1203 if val is None:
1204 val = mpl.rcParams[f"lines.{name}"] if has_rcdefault else "auto"
1205 attr = f"_{name}"
1206 current = getattr(self, attr)
1207 if current is None:
1208 self.stale = True
1209 else:
1210 neq = current != val
1211 # Much faster than `np.any(current != val)` if no arrays are used.
1212 if neq.any() if isinstance(neq, np.ndarray) else neq:
1213 self.stale = True
1214 setattr(self, attr, val)
1216 def set_markeredgecolor(self, ec):
1217 """
1218 Set the marker edge color.
1220 Parameters
1221 ----------
1222 ec : :mpltype:`color`
1223 """
1224 self._set_markercolor("markeredgecolor", True, ec)
1226 def set_markerfacecolor(self, fc):
1227 """
1228 Set the marker face color.
1230 Parameters
1231 ----------
1232 fc : :mpltype:`color`
1233 """
1234 self._set_markercolor("markerfacecolor", True, fc)
1236 def set_markerfacecoloralt(self, fc):
1237 """
1238 Set the alternate marker face color.
1240 Parameters
1241 ----------
1242 fc : :mpltype:`color`
1243 """
1244 self._set_markercolor("markerfacecoloralt", False, fc)
1246 def set_markeredgewidth(self, ew):
1247 """
1248 Set the marker edge width in points.
1250 Parameters
1251 ----------
1252 ew : float
1253 Marker edge width, in points.
1254 """
1255 if ew is None:
1256 ew = mpl.rcParams['lines.markeredgewidth']
1257 if self._markeredgewidth != ew:
1258 self.stale = True
1259 self._markeredgewidth = ew
1261 def set_markersize(self, sz):
1262 """
1263 Set the marker size in points.
1265 Parameters
1266 ----------
1267 sz : float
1268 Marker size, in points.
1269 """
1270 sz = float(sz)
1271 if self._markersize != sz:
1272 self.stale = True
1273 self._markersize = sz
1275 def set_xdata(self, x):
1276 """
1277 Set the data array for x.
1279 Parameters
1280 ----------
1281 x : 1D array
1283 See Also
1284 --------
1285 set_data
1286 set_ydata
1287 """
1288 if not np.iterable(x):
1289 raise RuntimeError('x must be a sequence')
1290 self._xorig = copy.copy(x)
1291 self._invalidx = True
1292 self.stale = True
1294 def set_ydata(self, y):
1295 """
1296 Set the data array for y.
1298 Parameters
1299 ----------
1300 y : 1D array
1302 See Also
1303 --------
1304 set_data
1305 set_xdata
1306 """
1307 if not np.iterable(y):
1308 raise RuntimeError('y must be a sequence')
1309 self._yorig = copy.copy(y)
1310 self._invalidy = True
1311 self.stale = True
1313 def set_dashes(self, seq):
1314 """
1315 Set the dash sequence.
1317 The dash sequence is a sequence of floats of even length describing
1318 the length of dashes and spaces in points.
1320 For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point
1321 dashes separated by 2 point spaces.
1323 See also `~.Line2D.set_gapcolor`, which allows those spaces to be
1324 filled with a color.
1326 Parameters
1327 ----------
1328 seq : sequence of floats (on/off ink in points) or (None, None)
1329 If *seq* is empty or ``(None, None)``, the linestyle will be set
1330 to solid.
1331 """
1332 if seq == (None, None) or len(seq) == 0:
1333 self.set_linestyle('-')
1334 else:
1335 self.set_linestyle((0, seq))
1337 def update_from(self, other):
1338 """Copy properties from *other* to self."""
1339 super().update_from(other)
1340 self._linestyle = other._linestyle
1341 self._linewidth = other._linewidth
1342 self._color = other._color
1343 self._gapcolor = other._gapcolor
1344 self._markersize = other._markersize
1345 self._markerfacecolor = other._markerfacecolor
1346 self._markerfacecoloralt = other._markerfacecoloralt
1347 self._markeredgecolor = other._markeredgecolor
1348 self._markeredgewidth = other._markeredgewidth
1349 self._unscaled_dash_pattern = other._unscaled_dash_pattern
1350 self._dash_pattern = other._dash_pattern
1351 self._dashcapstyle = other._dashcapstyle
1352 self._dashjoinstyle = other._dashjoinstyle
1353 self._solidcapstyle = other._solidcapstyle
1354 self._solidjoinstyle = other._solidjoinstyle
1356 self._linestyle = other._linestyle
1357 self._marker = MarkerStyle(marker=other._marker)
1358 self._drawstyle = other._drawstyle
1360 @_docstring.interpd
1361 def set_dash_joinstyle(self, s):
1362 """
1363 How to join segments of the line if it `~Line2D.is_dashed`.
1365 The default joinstyle is :rc:`lines.dash_joinstyle`.
1367 Parameters
1368 ----------
1369 s : `.JoinStyle` or %(JoinStyle)s
1370 """
1371 js = JoinStyle(s)
1372 if self._dashjoinstyle != js:
1373 self.stale = True
1374 self._dashjoinstyle = js
1376 @_docstring.interpd
1377 def set_solid_joinstyle(self, s):
1378 """
1379 How to join segments if the line is solid (not `~Line2D.is_dashed`).
1381 The default joinstyle is :rc:`lines.solid_joinstyle`.
1383 Parameters
1384 ----------
1385 s : `.JoinStyle` or %(JoinStyle)s
1386 """
1387 js = JoinStyle(s)
1388 if self._solidjoinstyle != js:
1389 self.stale = True
1390 self._solidjoinstyle = js
1392 def get_dash_joinstyle(self):
1393 """
1394 Return the `.JoinStyle` for dashed lines.
1396 See also `~.Line2D.set_dash_joinstyle`.
1397 """
1398 return self._dashjoinstyle.name
1400 def get_solid_joinstyle(self):
1401 """
1402 Return the `.JoinStyle` for solid lines.
1404 See also `~.Line2D.set_solid_joinstyle`.
1405 """
1406 return self._solidjoinstyle.name
1408 @_docstring.interpd
1409 def set_dash_capstyle(self, s):
1410 """
1411 How to draw the end caps if the line is `~Line2D.is_dashed`.
1413 The default capstyle is :rc:`lines.dash_capstyle`.
1415 Parameters
1416 ----------
1417 s : `.CapStyle` or %(CapStyle)s
1418 """
1419 cs = CapStyle(s)
1420 if self._dashcapstyle != cs:
1421 self.stale = True
1422 self._dashcapstyle = cs
1424 @_docstring.interpd
1425 def set_solid_capstyle(self, s):
1426 """
1427 How to draw the end caps if the line is solid (not `~Line2D.is_dashed`)
1429 The default capstyle is :rc:`lines.solid_capstyle`.
1431 Parameters
1432 ----------
1433 s : `.CapStyle` or %(CapStyle)s
1434 """
1435 cs = CapStyle(s)
1436 if self._solidcapstyle != cs:
1437 self.stale = True
1438 self._solidcapstyle = cs
1440 def get_dash_capstyle(self):
1441 """
1442 Return the `.CapStyle` for dashed lines.
1444 See also `~.Line2D.set_dash_capstyle`.
1445 """
1446 return self._dashcapstyle.name
1448 def get_solid_capstyle(self):
1449 """
1450 Return the `.CapStyle` for solid lines.
1452 See also `~.Line2D.set_solid_capstyle`.
1453 """
1454 return self._solidcapstyle.name
1456 def is_dashed(self):
1457 """
1458 Return whether line has a dashed linestyle.
1460 A custom linestyle is assumed to be dashed, we do not inspect the
1461 ``onoffseq`` directly.
1463 See also `~.Line2D.set_linestyle`.
1464 """
1465 return self._linestyle in ('--', '-.', ':')
1468class AxLine(Line2D):
1469 """
1470 A helper class that implements `~.Axes.axline`, by recomputing the artist
1471 transform at draw time.
1472 """
1474 def __init__(self, xy1, xy2, slope, **kwargs):
1475 """
1476 Parameters
1477 ----------
1478 xy1 : (float, float)
1479 The first set of (x, y) coordinates for the line to pass through.
1480 xy2 : (float, float) or None
1481 The second set of (x, y) coordinates for the line to pass through.
1482 Both *xy2* and *slope* must be passed, but one of them must be None.
1483 slope : float or None
1484 The slope of the line. Both *xy2* and *slope* must be passed, but one of
1485 them must be None.
1486 """
1487 super().__init__([0, 1], [0, 1], **kwargs)
1489 if (xy2 is None and slope is None or
1490 xy2 is not None and slope is not None):
1491 raise TypeError(
1492 "Exactly one of 'xy2' and 'slope' must be given")
1494 self._slope = slope
1495 self._xy1 = xy1
1496 self._xy2 = xy2
1498 def get_transform(self):
1499 ax = self.axes
1500 points_transform = self._transform - ax.transData + ax.transScale
1502 if self._xy2 is not None:
1503 # two points were given
1504 (x1, y1), (x2, y2) = \
1505 points_transform.transform([self._xy1, self._xy2])
1506 dx = x2 - x1
1507 dy = y2 - y1
1508 if dx == 0:
1509 if dy == 0:
1510 raise ValueError(
1511 f"Cannot draw a line through two identical points "
1512 f"(x={(x1, x2)}, y={(y1, y2)})")
1513 slope = np.inf
1514 else:
1515 slope = dy / dx
1516 else:
1517 # one point and a slope were given
1518 x1, y1 = points_transform.transform(self._xy1)
1519 slope = self._slope
1520 (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim)
1521 # General case: find intersections with view limits in either
1522 # direction, and draw between the middle two points.
1523 if slope == 0:
1524 start = vxlo, y1
1525 stop = vxhi, y1
1526 elif np.isinf(slope):
1527 start = x1, vylo
1528 stop = x1, vyhi
1529 else:
1530 _, start, stop, _ = sorted([
1531 (vxlo, y1 + (vxlo - x1) * slope),
1532 (vxhi, y1 + (vxhi - x1) * slope),
1533 (x1 + (vylo - y1) / slope, vylo),
1534 (x1 + (vyhi - y1) / slope, vyhi),
1535 ])
1536 return (BboxTransformTo(Bbox([start, stop]))
1537 + ax.transLimits + ax.transAxes)
1539 def draw(self, renderer):
1540 self._transformed_path = None # Force regen.
1541 super().draw(renderer)
1543 def get_xy1(self):
1544 """
1545 Return the *xy1* value of the line.
1546 """
1547 return self._xy1
1549 def get_xy2(self):
1550 """
1551 Return the *xy2* value of the line.
1552 """
1553 return self._xy2
1555 def get_slope(self):
1556 """
1557 Return the *slope* value of the line.
1558 """
1559 return self._slope
1561 def set_xy1(self, x, y):
1562 """
1563 Set the *xy1* value of the line.
1565 Parameters
1566 ----------
1567 x, y : float
1568 Points for the line to pass through.
1569 """
1570 self._xy1 = x, y
1572 def set_xy2(self, x, y):
1573 """
1574 Set the *xy2* value of the line.
1576 Parameters
1577 ----------
1578 x, y : float
1579 Points for the line to pass through.
1580 """
1581 if self._slope is None:
1582 self._xy2 = x, y
1583 else:
1584 raise ValueError("Cannot set an 'xy2' value while 'slope' is set;"
1585 " they differ but their functionalities overlap")
1587 def set_slope(self, slope):
1588 """
1589 Set the *slope* value of the line.
1591 Parameters
1592 ----------
1593 slope : float
1594 The slope of the line.
1595 """
1596 if self._xy2 is None:
1597 self._slope = slope
1598 else:
1599 raise ValueError("Cannot set a 'slope' value while 'xy2' is set;"
1600 " they differ but their functionalities overlap")
1603class VertexSelector:
1604 """
1605 Manage the callbacks to maintain a list of selected vertices for `.Line2D`.
1606 Derived classes should override the `process_selected` method to do
1607 something with the picks.
1609 Here is an example which highlights the selected verts with red circles::
1611 import numpy as np
1612 import matplotlib.pyplot as plt
1613 import matplotlib.lines as lines
1615 class HighlightSelected(lines.VertexSelector):
1616 def __init__(self, line, fmt='ro', **kwargs):
1617 super().__init__(line)
1618 self.markers, = self.axes.plot([], [], fmt, **kwargs)
1620 def process_selected(self, ind, xs, ys):
1621 self.markers.set_data(xs, ys)
1622 self.canvas.draw()
1624 fig, ax = plt.subplots()
1625 x, y = np.random.rand(2, 30)
1626 line, = ax.plot(x, y, 'bs-', picker=5)
1628 selector = HighlightSelected(line)
1629 plt.show()
1630 """
1632 def __init__(self, line):
1633 """
1634 Parameters
1635 ----------
1636 line : `~matplotlib.lines.Line2D`
1637 The line must already have been added to an `~.axes.Axes` and must
1638 have its picker property set.
1639 """
1640 if line.axes is None:
1641 raise RuntimeError('You must first add the line to the Axes')
1642 if line.get_picker() is None:
1643 raise RuntimeError('You must first set the picker property '
1644 'of the line')
1645 self.axes = line.axes
1646 self.line = line
1647 self.cid = self.canvas.callbacks._connect_picklable(
1648 'pick_event', self.onpick)
1649 self.ind = set()
1651 canvas = property(lambda self: self.axes.figure.canvas)
1653 def process_selected(self, ind, xs, ys):
1654 """
1655 Default "do nothing" implementation of the `process_selected` method.
1657 Parameters
1658 ----------
1659 ind : list of int
1660 The indices of the selected vertices.
1661 xs, ys : array-like
1662 The coordinates of the selected vertices.
1663 """
1664 pass
1666 def onpick(self, event):
1667 """When the line is picked, update the set of selected indices."""
1668 if event.artist is not self.line:
1669 return
1670 self.ind ^= set(event.ind)
1671 ind = sorted(self.ind)
1672 xdata, ydata = self.line.get_data()
1673 self.process_selected(ind, xdata[ind], ydata[ind])
1676lineStyles = Line2D._lineStyles
1677lineMarkers = MarkerStyle.markers
1678drawStyles = Line2D.drawStyles
1679fillStyles = MarkerStyle.fillstyles