Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2This module contains all the 2D line class which can draw with a 

3variety of line styles, markers and colors. 

4""" 

5 

6# TODO: expose cap and join style attrs 

7from numbers import Integral, Number, Real 

8import logging 

9 

10import numpy as np 

11 

12from . import artist, cbook, colors as mcolors, docstring, rcParams 

13from .artist import Artist, allow_rasterization 

14from .cbook import ( 

15 _to_unmasked_float_array, ls_mapper, ls_mapper_r, STEP_LOOKUP_MAP) 

16from .markers import MarkerStyle 

17from .path import Path 

18from .transforms import Bbox, TransformedPath 

19 

20# Imported here for backward compatibility, even though they don't 

21# really belong. 

22from . import _path 

23from .markers import ( 

24 CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN, 

25 CARETLEFTBASE, CARETRIGHTBASE, CARETUPBASE, CARETDOWNBASE, 

26 TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN) 

27 

28_log = logging.getLogger(__name__) 

29 

30 

31def _get_dash_pattern(style): 

32 """Convert linestyle -> dash pattern 

33 """ 

34 # go from short hand -> full strings 

35 if isinstance(style, str): 

36 style = ls_mapper.get(style, style) 

37 # un-dashed styles 

38 if style in ['solid', 'None']: 

39 offset, dashes = None, None 

40 # dashed styles 

41 elif style in ['dashed', 'dashdot', 'dotted']: 

42 offset = 0 

43 dashes = tuple(rcParams['lines.{}_pattern'.format(style)]) 

44 # 

45 elif isinstance(style, tuple): 

46 offset, dashes = style 

47 else: 

48 raise ValueError('Unrecognized linestyle: %s' % str(style)) 

49 

50 # normalize offset to be positive and shorter than the dash cycle 

51 if dashes is not None and offset is not None: 

52 dsum = sum(dashes) 

53 if dsum: 

54 offset %= dsum 

55 

56 return offset, dashes 

57 

58 

59def _scale_dashes(offset, dashes, lw): 

60 if not rcParams['lines.scale_dashes']: 

61 return offset, dashes 

62 

63 scaled_offset = scaled_dashes = None 

64 if offset is not None: 

65 scaled_offset = offset * lw 

66 if dashes is not None: 

67 scaled_dashes = [x * lw if x is not None else None 

68 for x in dashes] 

69 

70 return scaled_offset, scaled_dashes 

71 

72 

73def segment_hits(cx, cy, x, y, radius): 

74 """ 

75 Return the indices of the segments in the polyline with coordinates (*cx*, 

76 *cy*) that are within a distance *radius* of the point (*x*, *y*). 

77 """ 

78 # Process single points specially 

79 if len(x) <= 1: 

80 res, = np.nonzero((cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2) 

81 return res 

82 

83 # We need to lop the last element off a lot. 

84 xr, yr = x[:-1], y[:-1] 

85 

86 # Only look at line segments whose nearest point to C on the line 

87 # lies within the segment. 

88 dx, dy = x[1:] - xr, y[1:] - yr 

89 Lnorm_sq = dx ** 2 + dy ** 2 # Possibly want to eliminate Lnorm==0 

90 u = ((cx - xr) * dx + (cy - yr) * dy) / Lnorm_sq 

91 candidates = (u >= 0) & (u <= 1) 

92 

93 # Note that there is a little area near one side of each point 

94 # which will be near neither segment, and another which will 

95 # be near both, depending on the angle of the lines. The 

96 # following radius test eliminates these ambiguities. 

97 point_hits = (cx - x) ** 2 + (cy - y) ** 2 <= radius ** 2 

98 candidates = candidates & ~(point_hits[:-1] | point_hits[1:]) 

99 

100 # For those candidates which remain, determine how far they lie away 

101 # from the line. 

102 px, py = xr + u * dx, yr + u * dy 

103 line_hits = (cx - px) ** 2 + (cy - py) ** 2 <= radius ** 2 

104 line_hits = line_hits & candidates 

105 points, = point_hits.ravel().nonzero() 

106 lines, = line_hits.ravel().nonzero() 

107 return np.concatenate((points, lines)) 

108 

109 

110def _mark_every_path(markevery, tpath, affine, ax_transform): 

111 """ 

112 Helper function that sorts out how to deal the input 

113 `markevery` and returns the points where markers should be drawn. 

114 

115 Takes in the `markevery` value and the line path and returns the 

116 sub-sampled path. 

117 """ 

118 # pull out the two bits of data we want from the path 

119 codes, verts = tpath.codes, tpath.vertices 

120 

121 def _slice_or_none(in_v, slc): 

122 ''' 

123 Helper function to cope with `codes` being an 

124 ndarray or `None` 

125 ''' 

126 if in_v is None: 

127 return None 

128 return in_v[slc] 

129 

130 # if just an int, assume starting at 0 and make a tuple 

131 if isinstance(markevery, Integral): 

132 markevery = (0, markevery) 

133 # if just a float, assume starting at 0.0 and make a tuple 

134 elif isinstance(markevery, Real): 

135 markevery = (0.0, markevery) 

136 

137 if isinstance(markevery, tuple): 

138 if len(markevery) != 2: 

139 raise ValueError('`markevery` is a tuple but its len is not 2; ' 

140 'markevery={}'.format(markevery)) 

141 start, step = markevery 

142 # if step is an int, old behavior 

143 if isinstance(step, Integral): 

144 # tuple of 2 int is for backwards compatibility, 

145 if not isinstance(start, Integral): 

146 raise ValueError( 

147 '`markevery` is a tuple with len 2 and second element is ' 

148 'an int, but the first element is not an int; markevery={}' 

149 .format(markevery)) 

150 # just return, we are done here 

151 

152 return Path(verts[slice(start, None, step)], 

153 _slice_or_none(codes, slice(start, None, step))) 

154 

155 elif isinstance(step, Real): 

156 if not isinstance(start, Real): 

157 raise ValueError( 

158 '`markevery` is a tuple with len 2 and second element is ' 

159 'a float, but the first element is not a float or an int; ' 

160 'markevery={}'.format(markevery)) 

161 # calc cumulative distance along path (in display coords): 

162 disp_coords = affine.transform(tpath.vertices) 

163 delta = np.empty((len(disp_coords), 2)) 

164 delta[0, :] = 0 

165 delta[1:, :] = disp_coords[1:, :] - disp_coords[:-1, :] 

166 delta = np.hypot(*delta.T).cumsum() 

167 # calc distance between markers along path based on the axes 

168 # bounding box diagonal being a distance of unity: 

169 (x0, y0), (x1, y1) = ax_transform.transform([[0, 0], [1, 1]]) 

170 scale = np.hypot(x1 - x0, y1 - y0) 

171 marker_delta = np.arange(start * scale, delta[-1], step * scale) 

172 # find closest actual data point that is closest to 

173 # the theoretical distance along the path: 

174 inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis]) 

175 inds = inds.argmin(axis=1) 

176 inds = np.unique(inds) 

177 # return, we are done here 

178 return Path(verts[inds], _slice_or_none(codes, inds)) 

179 else: 

180 raise ValueError( 

181 f"markevery={markevery!r} is a tuple with len 2, but its " 

182 f"second element is not an int or a float") 

183 

184 elif isinstance(markevery, slice): 

185 # mazol tov, it's already a slice, just return 

186 return Path(verts[markevery], _slice_or_none(codes, markevery)) 

187 

188 elif np.iterable(markevery): 

189 # fancy indexing 

190 try: 

191 return Path(verts[markevery], _slice_or_none(codes, markevery)) 

192 except (ValueError, IndexError): 

193 raise ValueError( 

194 f"markevery={markevery!r} is iterable but not a valid numpy " 

195 f"fancy index") 

196 else: 

197 raise ValueError(f"markevery={markevery!r} is not a recognized value") 

198 

199 

200@cbook._define_aliases({ 

201 "antialiased": ["aa"], 

202 "color": ["c"], 

203 "drawstyle": ["ds"], 

204 "linestyle": ["ls"], 

205 "linewidth": ["lw"], 

206 "markeredgecolor": ["mec"], 

207 "markeredgewidth": ["mew"], 

208 "markerfacecolor": ["mfc"], 

209 "markerfacecoloralt": ["mfcalt"], 

210 "markersize": ["ms"], 

211}) 

212class Line2D(Artist): 

213 """ 

214 A line - the line can have both a solid linestyle connecting all 

215 the vertices, and a marker at each vertex. Additionally, the 

216 drawing of the solid line is influenced by the drawstyle, e.g., one 

217 can create "stepped" lines in various styles. 

218 """ 

219 

220 lineStyles = _lineStyles = { # hidden names deprecated 

221 '-': '_draw_solid', 

222 '--': '_draw_dashed', 

223 '-.': '_draw_dash_dot', 

224 ':': '_draw_dotted', 

225 'None': '_draw_nothing', 

226 ' ': '_draw_nothing', 

227 '': '_draw_nothing', 

228 } 

229 

230 _drawStyles_l = { 

231 'default': '_draw_lines', 

232 'steps-mid': '_draw_steps_mid', 

233 'steps-pre': '_draw_steps_pre', 

234 'steps-post': '_draw_steps_post', 

235 } 

236 

237 _drawStyles_s = { 

238 'steps': '_draw_steps_pre', 

239 } 

240 

241 # drawStyles should now be deprecated. 

242 drawStyles = {**_drawStyles_l, **_drawStyles_s} 

243 # Need a list ordered with long names first: 

244 drawStyleKeys = [*_drawStyles_l, *_drawStyles_s] 

245 

246 # Referenced here to maintain API. These are defined in 

247 # MarkerStyle 

248 markers = MarkerStyle.markers 

249 filled_markers = MarkerStyle.filled_markers 

250 fillStyles = MarkerStyle.fillstyles 

251 

252 zorder = 2 

253 validCap = ('butt', 'round', 'projecting') 

254 validJoin = ('miter', 'round', 'bevel') 

255 

256 def __str__(self): 

257 if self._label != "": 

258 return f"Line2D({self._label})" 

259 elif self._x is None: 

260 return "Line2D()" 

261 elif len(self._x) > 3: 

262 return "Line2D((%g,%g),(%g,%g),...,(%g,%g))" % ( 

263 self._x[0], self._y[0], self._x[0], 

264 self._y[0], self._x[-1], self._y[-1]) 

265 else: 

266 return "Line2D(%s)" % ",".join( 

267 map("({:g},{:g})".format, self._x, self._y)) 

268 

269 def __init__(self, xdata, ydata, 

270 linewidth=None, # all Nones default to rc 

271 linestyle=None, 

272 color=None, 

273 marker=None, 

274 markersize=None, 

275 markeredgewidth=None, 

276 markeredgecolor=None, 

277 markerfacecolor=None, 

278 markerfacecoloralt='none', 

279 fillstyle=None, 

280 antialiased=None, 

281 dash_capstyle=None, 

282 solid_capstyle=None, 

283 dash_joinstyle=None, 

284 solid_joinstyle=None, 

285 pickradius=5, 

286 drawstyle=None, 

287 markevery=None, 

288 **kwargs 

289 ): 

290 """ 

291 Create a `.Line2D` instance with *x* and *y* data in sequences of 

292 *xdata*, *ydata*. 

293 

294 Additional keyword arguments are `.Line2D` properties: 

295 

296 %(_Line2D_docstr)s 

297 

298 See :meth:`set_linestyle` for a description of the line styles, 

299 :meth:`set_marker` for a description of the markers, and 

300 :meth:`set_drawstyle` for a description of the draw styles. 

301 

302 """ 

303 Artist.__init__(self) 

304 

305 #convert sequences to numpy arrays 

306 if not np.iterable(xdata): 

307 raise RuntimeError('xdata must be a sequence') 

308 if not np.iterable(ydata): 

309 raise RuntimeError('ydata must be a sequence') 

310 

311 if linewidth is None: 

312 linewidth = rcParams['lines.linewidth'] 

313 

314 if linestyle is None: 

315 linestyle = rcParams['lines.linestyle'] 

316 if marker is None: 

317 marker = rcParams['lines.marker'] 

318 if markerfacecolor is None: 

319 markerfacecolor = rcParams['lines.markerfacecolor'] 

320 if markeredgecolor is None: 

321 markeredgecolor = rcParams['lines.markeredgecolor'] 

322 if color is None: 

323 color = rcParams['lines.color'] 

324 

325 if markersize is None: 

326 markersize = rcParams['lines.markersize'] 

327 if antialiased is None: 

328 antialiased = rcParams['lines.antialiased'] 

329 if dash_capstyle is None: 

330 dash_capstyle = rcParams['lines.dash_capstyle'] 

331 if dash_joinstyle is None: 

332 dash_joinstyle = rcParams['lines.dash_joinstyle'] 

333 if solid_capstyle is None: 

334 solid_capstyle = rcParams['lines.solid_capstyle'] 

335 if solid_joinstyle is None: 

336 solid_joinstyle = rcParams['lines.solid_joinstyle'] 

337 

338 if isinstance(linestyle, str): 

339 ds, ls = self._split_drawstyle_linestyle(linestyle) 

340 if ds is not None and drawstyle is not None and ds != drawstyle: 

341 raise ValueError("Inconsistent drawstyle ({!r}) and linestyle " 

342 "({!r})".format(drawstyle, linestyle)) 

343 linestyle = ls 

344 

345 if ds is not None: 

346 drawstyle = ds 

347 

348 if drawstyle is None: 

349 drawstyle = 'default' 

350 

351 self._dashcapstyle = None 

352 self._dashjoinstyle = None 

353 self._solidjoinstyle = None 

354 self._solidcapstyle = None 

355 self.set_dash_capstyle(dash_capstyle) 

356 self.set_dash_joinstyle(dash_joinstyle) 

357 self.set_solid_capstyle(solid_capstyle) 

358 self.set_solid_joinstyle(solid_joinstyle) 

359 

360 self._linestyles = None 

361 self._drawstyle = None 

362 self._linewidth = linewidth 

363 

364 # scaled dash + offset 

365 self._dashSeq = None 

366 self._dashOffset = 0 

367 # unscaled dash + offset 

368 # this is needed scaling the dash pattern by linewidth 

369 self._us_dashSeq = None 

370 self._us_dashOffset = 0 

371 

372 self.set_linewidth(linewidth) 

373 self.set_linestyle(linestyle) 

374 self.set_drawstyle(drawstyle) 

375 

376 self._color = None 

377 self.set_color(color) 

378 self._marker = MarkerStyle(marker, fillstyle) 

379 

380 self._markevery = None 

381 self._markersize = None 

382 self._antialiased = None 

383 

384 self.set_markevery(markevery) 

385 self.set_antialiased(antialiased) 

386 self.set_markersize(markersize) 

387 

388 self._markeredgecolor = None 

389 self._markeredgewidth = None 

390 self._markerfacecolor = None 

391 self._markerfacecoloralt = None 

392 

393 self.set_markerfacecolor(markerfacecolor) 

394 self.set_markerfacecoloralt(markerfacecoloralt) 

395 self.set_markeredgecolor(markeredgecolor) 

396 self.set_markeredgewidth(markeredgewidth) 

397 

398 # update kwargs before updating data to give the caller a 

399 # chance to init axes (and hence unit support) 

400 self.update(kwargs) 

401 self.pickradius = pickradius 

402 self.ind_offset = 0 

403 if isinstance(self._picker, Number): 

404 self.pickradius = self._picker 

405 

406 self._xorig = np.asarray([]) 

407 self._yorig = np.asarray([]) 

408 self._invalidx = True 

409 self._invalidy = True 

410 self._x = None 

411 self._y = None 

412 self._xy = None 

413 self._path = None 

414 self._transformed_path = None 

415 self._subslice = False 

416 self._x_filled = None # used in subslicing; only x is needed 

417 

418 self.set_data(xdata, ydata) 

419 

420 @cbook.deprecated("3.1") 

421 @property 

422 def verticalOffset(self): 

423 return None 

424 

425 def contains(self, mouseevent): 

426 """ 

427 Test whether the mouse event occurred on the line. The pick 

428 radius determines the precision of the location test (usually 

429 within five points of the value). Use 

430 :meth:`~matplotlib.lines.Line2D.get_pickradius` or 

431 :meth:`~matplotlib.lines.Line2D.set_pickradius` to view or 

432 modify it. 

433 

434 Parameters 

435 ---------- 

436 mouseevent : `matplotlib.backend_bases.MouseEvent` 

437 

438 Returns 

439 ------- 

440 contains : bool 

441 Whether any values are within the radius. 

442 details : dict 

443 A dictionary ``{'ind': pointlist}``, where *pointlist* is a 

444 list of points of the line that are within the pickradius around 

445 the event position. 

446 

447 TODO: sort returned indices by distance 

448 """ 

449 inside, info = self._default_contains(mouseevent) 

450 if inside is not None: 

451 return inside, info 

452 

453 if not isinstance(self.pickradius, Number): 

454 raise ValueError("pick radius should be a distance") 

455 

456 # Make sure we have data to plot 

457 if self._invalidy or self._invalidx: 

458 self.recache() 

459 if len(self._xy) == 0: 

460 return False, {} 

461 

462 # Convert points to pixels 

463 transformed_path = self._get_transformed_path() 

464 path, affine = transformed_path.get_transformed_path_and_affine() 

465 path = affine.transform_path(path) 

466 xy = path.vertices 

467 xt = xy[:, 0] 

468 yt = xy[:, 1] 

469 

470 # Convert pick radius from points to pixels 

471 if self.figure is None: 

472 _log.warning('no figure set when check if mouse is on line') 

473 pixels = self.pickradius 

474 else: 

475 pixels = self.figure.dpi / 72. * self.pickradius 

476 

477 # The math involved in checking for containment (here and inside of 

478 # segment_hits) assumes that it is OK to overflow, so temporarily set 

479 # the error flags accordingly. 

480 with np.errstate(all='ignore'): 

481 # Check for collision 

482 if self._linestyle in ['None', None]: 

483 # If no line, return the nearby point(s) 

484 ind, = np.nonzero( 

485 (xt - mouseevent.x) ** 2 + (yt - mouseevent.y) ** 2 

486 <= pixels ** 2) 

487 else: 

488 # If line, return the nearby segment(s) 

489 ind = segment_hits(mouseevent.x, mouseevent.y, xt, yt, pixels) 

490 if self._drawstyle.startswith("steps"): 

491 ind //= 2 

492 

493 ind += self.ind_offset 

494 

495 # Return the point(s) within radius 

496 return len(ind) > 0, dict(ind=ind) 

497 

498 def get_pickradius(self): 

499 """ 

500 Return the pick radius used for containment tests. 

501 

502 See `.contains` for more details. 

503 """ 

504 return self.pickradius 

505 

506 def set_pickradius(self, d): 

507 """Set the pick radius used for containment tests. 

508 

509 See `.contains` for more details. 

510 

511 Parameters 

512 ---------- 

513 d : float 

514 Pick radius, in points. 

515 """ 

516 self.pickradius = d 

517 

518 def get_fillstyle(self): 

519 """ 

520 Return the marker fill style. 

521 

522 See also `~.Line2D.set_fillstyle`. 

523 """ 

524 return self._marker.get_fillstyle() 

525 

526 def set_fillstyle(self, fs): 

527 """ 

528 Set the marker fill style. 

529 

530 Parameters 

531 ---------- 

532 fs : {'full', 'left', 'right', 'bottom', 'top', 'none'} 

533 Possible values: 

534 

535 - 'full': Fill the whole marker with the *markerfacecolor*. 

536 - 'left', 'right', 'bottom', 'top': Fill the marker half at 

537 the given side with the *markerfacecolor*. The other 

538 half of the marker is filled with *markerfacecoloralt*. 

539 - 'none': No filling. 

540 

541 For examples see 

542 :doc:`/gallery/lines_bars_and_markers/marker_fillstyle_reference`. 

543 """ 

544 self._marker.set_fillstyle(fs) 

545 self.stale = True 

546 

547 def set_markevery(self, every): 

548 """Set the markevery property to subsample the plot when using markers. 

549 

550 e.g., if `every=5`, every 5-th marker will be plotted. 

551 

552 Parameters 

553 ---------- 

554 every : None or int or (int, int) or slice or List[int] or float or \ 

555(float, float) 

556 Which markers to plot. 

557 

558 - every=None, every point will be plotted. 

559 - every=N, every N-th marker will be plotted starting with 

560 marker 0. 

561 - every=(start, N), every N-th marker, starting at point 

562 start, will be plotted. 

563 - every=slice(start, end, N), every N-th marker, starting at 

564 point start, up to but not including point end, will be plotted. 

565 - every=[i, j, m, n], only markers at points i, j, m, and n 

566 will be plotted. 

567 - every=0.1, (i.e. a float) then markers will be spaced at 

568 approximately equal distances along the line; the distance 

569 along the line between markers is determined by multiplying the 

570 display-coordinate distance of the axes bounding-box diagonal 

571 by the value of every. 

572 - every=(0.5, 0.1) (i.e. a length-2 tuple of float), the same 

573 functionality as every=0.1 is exhibited but the first marker will 

574 be 0.5 multiplied by the display-coordinate-diagonal-distance 

575 along the line. 

576 

577 For examples see 

578 :doc:`/gallery/lines_bars_and_markers/markevery_demo`. 

579 

580 Notes 

581 ----- 

582 Setting the markevery property will only show markers at actual data 

583 points. When using float arguments to set the markevery property 

584 on irregularly spaced data, the markers will likely not appear evenly 

585 spaced because the actual data points do not coincide with the 

586 theoretical spacing between markers. 

587 

588 When using a start offset to specify the first marker, the offset will 

589 be from the first data point which may be different from the first 

590 the visible data point if the plot is zoomed in. 

591 

592 If zooming in on a plot when using float arguments then the actual 

593 data points that have markers will change because the distance between 

594 markers is always determined from the display-coordinates 

595 axes-bounding-box-diagonal regardless of the actual axes data limits. 

596 

597 """ 

598 if self._markevery != every: 

599 self.stale = True 

600 self._markevery = every 

601 

602 def get_markevery(self): 

603 """ 

604 Return the markevery setting for marker subsampling. 

605 

606 See also `~.Line2D.set_markevery`. 

607 """ 

608 return self._markevery 

609 

610 def set_picker(self, p): 

611 """Sets the event picker details for the line. 

612 

613 Parameters 

614 ---------- 

615 p : float or callable[[Artist, Event], Tuple[bool, dict]] 

616 If a float, it is used as the pick radius in points. 

617 """ 

618 if callable(p): 

619 self._contains = p 

620 else: 

621 self.pickradius = p 

622 self._picker = p 

623 

624 def get_window_extent(self, renderer): 

625 bbox = Bbox([[0, 0], [0, 0]]) 

626 trans_data_to_xy = self.get_transform().transform 

627 bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()), 

628 ignore=True) 

629 # correct for marker size, if any 

630 if self._marker: 

631 ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5 

632 bbox = bbox.padded(ms) 

633 return bbox 

634 

635 @Artist.axes.setter 

636 def axes(self, ax): 

637 # call the set method from the base-class property 

638 Artist.axes.fset(self, ax) 

639 if ax is not None: 

640 # connect unit-related callbacks 

641 if ax.xaxis is not None: 

642 self._xcid = ax.xaxis.callbacks.connect('units', 

643 self.recache_always) 

644 if ax.yaxis is not None: 

645 self._ycid = ax.yaxis.callbacks.connect('units', 

646 self.recache_always) 

647 

648 def set_data(self, *args): 

649 """ 

650 Set the x and y data. 

651 

652 Parameters 

653 ---------- 

654 *args : (2, N) array or two 1D arrays 

655 """ 

656 if len(args) == 1: 

657 (x, y), = args 

658 else: 

659 x, y = args 

660 

661 self.set_xdata(x) 

662 self.set_ydata(y) 

663 

664 def recache_always(self): 

665 self.recache(always=True) 

666 

667 def recache(self, always=False): 

668 if always or self._invalidx: 

669 xconv = self.convert_xunits(self._xorig) 

670 x = _to_unmasked_float_array(xconv).ravel() 

671 else: 

672 x = self._x 

673 if always or self._invalidy: 

674 yconv = self.convert_yunits(self._yorig) 

675 y = _to_unmasked_float_array(yconv).ravel() 

676 else: 

677 y = self._y 

678 

679 self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float) 

680 self._x, self._y = self._xy.T # views 

681 

682 self._subslice = False 

683 if (self.axes and len(x) > 1000 and self._is_sorted(x) and 

684 self.axes.name == 'rectilinear' and 

685 self.axes.get_xscale() == 'linear' and 

686 self._markevery is None and 

687 self.get_clip_on()): 

688 self._subslice = True 

689 nanmask = np.isnan(x) 

690 if nanmask.any(): 

691 self._x_filled = self._x.copy() 

692 indices = np.arange(len(x)) 

693 self._x_filled[nanmask] = np.interp(indices[nanmask], 

694 indices[~nanmask], self._x[~nanmask]) 

695 else: 

696 self._x_filled = self._x 

697 

698 if self._path is not None: 

699 interpolation_steps = self._path._interpolation_steps 

700 else: 

701 interpolation_steps = 1 

702 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T) 

703 self._path = Path(np.asarray(xy).T, 

704 _interpolation_steps=interpolation_steps) 

705 self._transformed_path = None 

706 self._invalidx = False 

707 self._invalidy = False 

708 

709 def _transform_path(self, subslice=None): 

710 """ 

711 Puts a TransformedPath instance at self._transformed_path; 

712 all invalidation of the transform is then handled by the 

713 TransformedPath instance. 

714 """ 

715 # Masked arrays are now handled by the Path class itself 

716 if subslice is not None: 

717 xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T) 

718 _path = Path(np.asarray(xy).T, 

719 _interpolation_steps=self._path._interpolation_steps) 

720 else: 

721 _path = self._path 

722 self._transformed_path = TransformedPath(_path, self.get_transform()) 

723 

724 def _get_transformed_path(self): 

725 """ 

726 Return the :class:`~matplotlib.transforms.TransformedPath` instance 

727 of this line. 

728 """ 

729 if self._transformed_path is None: 

730 self._transform_path() 

731 return self._transformed_path 

732 

733 def set_transform(self, t): 

734 """ 

735 Set the Transformation instance used by this artist. 

736 

737 Parameters 

738 ---------- 

739 t : `matplotlib.transforms.Transform` 

740 """ 

741 Artist.set_transform(self, t) 

742 self._invalidx = True 

743 self._invalidy = True 

744 self.stale = True 

745 

746 def _is_sorted(self, x): 

747 """Return whether x is sorted in ascending order.""" 

748 # We don't handle the monotonically decreasing case. 

749 return _path.is_sorted(x) 

750 

751 @allow_rasterization 

752 def draw(self, renderer): 

753 # docstring inherited from Artist.draw. 

754 

755 if not self.get_visible(): 

756 return 

757 

758 if self._invalidy or self._invalidx: 

759 self.recache() 

760 self.ind_offset = 0 # Needed for contains() method. 

761 if self._subslice and self.axes: 

762 x0, x1 = self.axes.get_xbound() 

763 i0 = self._x_filled.searchsorted(x0, 'left') 

764 i1 = self._x_filled.searchsorted(x1, 'right') 

765 subslice = slice(max(i0 - 1, 0), i1 + 1) 

766 self.ind_offset = subslice.start 

767 self._transform_path(subslice) 

768 else: 

769 subslice = None 

770 

771 if self.get_path_effects(): 

772 from matplotlib.patheffects import PathEffectRenderer 

773 renderer = PathEffectRenderer(self.get_path_effects(), renderer) 

774 

775 renderer.open_group('line2d', self.get_gid()) 

776 if self._lineStyles[self._linestyle] != '_draw_nothing': 

777 tpath, affine = (self._get_transformed_path() 

778 .get_transformed_path_and_affine()) 

779 if len(tpath.vertices): 

780 gc = renderer.new_gc() 

781 self._set_gc_clip(gc) 

782 

783 lc_rgba = mcolors.to_rgba(self._color, self._alpha) 

784 gc.set_foreground(lc_rgba, isRGBA=True) 

785 

786 gc.set_antialiased(self._antialiased) 

787 gc.set_linewidth(self._linewidth) 

788 

789 if self.is_dashed(): 

790 cap = self._dashcapstyle 

791 join = self._dashjoinstyle 

792 else: 

793 cap = self._solidcapstyle 

794 join = self._solidjoinstyle 

795 gc.set_joinstyle(join) 

796 gc.set_capstyle(cap) 

797 gc.set_snap(self.get_snap()) 

798 if self.get_sketch_params() is not None: 

799 gc.set_sketch_params(*self.get_sketch_params()) 

800 

801 gc.set_dashes(self._dashOffset, self._dashSeq) 

802 renderer.draw_path(gc, tpath, affine.frozen()) 

803 gc.restore() 

804 

805 if self._marker and self._markersize > 0: 

806 gc = renderer.new_gc() 

807 self._set_gc_clip(gc) 

808 gc.set_linewidth(self._markeredgewidth) 

809 gc.set_antialiased(self._antialiased) 

810 

811 ec_rgba = mcolors.to_rgba( 

812 self.get_markeredgecolor(), self._alpha) 

813 fc_rgba = mcolors.to_rgba( 

814 self._get_markerfacecolor(), self._alpha) 

815 fcalt_rgba = mcolors.to_rgba( 

816 self._get_markerfacecolor(alt=True), self._alpha) 

817 # If the edgecolor is "auto", it is set according to the *line* 

818 # color but inherits the alpha value of the *face* color, if any. 

819 if (cbook._str_equal(self._markeredgecolor, "auto") 

820 and not cbook._str_lower_equal( 

821 self.get_markerfacecolor(), "none")): 

822 ec_rgba = ec_rgba[:3] + (fc_rgba[3],) 

823 gc.set_foreground(ec_rgba, isRGBA=True) 

824 if self.get_sketch_params() is not None: 

825 scale, length, randomness = self.get_sketch_params() 

826 gc.set_sketch_params(scale/2, length/2, 2*randomness) 

827 

828 marker = self._marker 

829 

830 # Markers *must* be drawn ignoring the drawstyle (but don't pay the 

831 # recaching if drawstyle is already "default"). 

832 if self.get_drawstyle() != "default": 

833 with cbook._setattr_cm( 

834 self, _drawstyle="default", _transformed_path=None): 

835 self.recache() 

836 self._transform_path(subslice) 

837 tpath, affine = (self._get_transformed_path() 

838 .get_transformed_points_and_affine()) 

839 else: 

840 tpath, affine = (self._get_transformed_path() 

841 .get_transformed_points_and_affine()) 

842 

843 if len(tpath.vertices): 

844 # subsample the markers if markevery is not None 

845 markevery = self.get_markevery() 

846 if markevery is not None: 

847 subsampled = _mark_every_path(markevery, tpath, 

848 affine, self.axes.transAxes) 

849 else: 

850 subsampled = tpath 

851 

852 snap = marker.get_snap_threshold() 

853 if isinstance(snap, Real): 

854 snap = renderer.points_to_pixels(self._markersize) >= snap 

855 gc.set_snap(snap) 

856 gc.set_joinstyle(marker.get_joinstyle()) 

857 gc.set_capstyle(marker.get_capstyle()) 

858 marker_path = marker.get_path() 

859 marker_trans = marker.get_transform() 

860 w = renderer.points_to_pixels(self._markersize) 

861 

862 if cbook._str_equal(marker.get_marker(), ","): 

863 gc.set_linewidth(0) 

864 else: 

865 # Don't scale for pixels, and don't stroke them 

866 marker_trans = marker_trans.scale(w) 

867 renderer.draw_markers(gc, marker_path, marker_trans, 

868 subsampled, affine.frozen(), 

869 fc_rgba) 

870 

871 alt_marker_path = marker.get_alt_path() 

872 if alt_marker_path: 

873 alt_marker_trans = marker.get_alt_transform() 

874 alt_marker_trans = alt_marker_trans.scale(w) 

875 renderer.draw_markers( 

876 gc, alt_marker_path, alt_marker_trans, subsampled, 

877 affine.frozen(), fcalt_rgba) 

878 

879 gc.restore() 

880 

881 renderer.close_group('line2d') 

882 self.stale = False 

883 

884 def get_antialiased(self): 

885 """Return whether antialiased rendering is used.""" 

886 return self._antialiased 

887 

888 def get_color(self): 

889 """ 

890 Return the line color. 

891 

892 See also `~.Line2D.set_color`. 

893 """ 

894 return self._color 

895 

896 def get_drawstyle(self): 

897 """ 

898 Return the drawstyle. 

899 

900 See also `~.Line2D.set_drawstyle`. 

901 """ 

902 return self._drawstyle 

903 

904 def get_linestyle(self): 

905 """ 

906 Return the linestyle. 

907 

908 See also `~.Line2D.set_linestyle`. 

909 """ 

910 return self._linestyle 

911 

912 def get_linewidth(self): 

913 """ 

914 Return the linewidth in points. 

915 

916 See also `~.Line2D.set_linewidth`. 

917 """ 

918 return self._linewidth 

919 

920 def get_marker(self): 

921 """ 

922 Return the line marker. 

923 

924 See also `~.Line2D.set_marker`. 

925 """ 

926 return self._marker.get_marker() 

927 

928 def get_markeredgecolor(self): 

929 """ 

930 Return the marker edge color. 

931 

932 See also `~.Line2D.set_markeredgecolor`. 

933 """ 

934 mec = self._markeredgecolor 

935 if cbook._str_equal(mec, 'auto'): 

936 if rcParams['_internal.classic_mode']: 

937 if self._marker.get_marker() in ('.', ','): 

938 return self._color 

939 if self._marker.is_filled() and self.get_fillstyle() != 'none': 

940 return 'k' # Bad hard-wired default... 

941 return self._color 

942 else: 

943 return mec 

944 

945 def get_markeredgewidth(self): 

946 """ 

947 Return the marker edge width in points. 

948 

949 See also `~.Line2D.set_markeredgewidth`. 

950 """ 

951 return self._markeredgewidth 

952 

953 def _get_markerfacecolor(self, alt=False): 

954 fc = self._markerfacecoloralt if alt else self._markerfacecolor 

955 if cbook._str_lower_equal(fc, 'auto'): 

956 if self.get_fillstyle() == 'none': 

957 return 'none' 

958 else: 

959 return self._color 

960 else: 

961 return fc 

962 

963 def get_markerfacecolor(self): 

964 """ 

965 Return the marker face color. 

966 

967 See also `~.Line2D.set_markerfacecolor`. 

968 """ 

969 return self._get_markerfacecolor(alt=False) 

970 

971 def get_markerfacecoloralt(self): 

972 """ 

973 Return the alternate marker face color. 

974 

975 See also `~.Line2D.set_markerfacecoloralt`. 

976 """ 

977 return self._get_markerfacecolor(alt=True) 

978 

979 def get_markersize(self): 

980 """ 

981 Return the marker size in points. 

982 

983 See also `~.Line2D.set_markersize`. 

984 """ 

985 return self._markersize 

986 

987 def get_data(self, orig=True): 

988 """ 

989 Return the xdata, ydata. 

990 

991 If *orig* is *True*, return the original data. 

992 """ 

993 return self.get_xdata(orig=orig), self.get_ydata(orig=orig) 

994 

995 def get_xdata(self, orig=True): 

996 """ 

997 Return the xdata. 

998 

999 If *orig* is *True*, return the original data, else the 

1000 processed data. 

1001 """ 

1002 if orig: 

1003 return self._xorig 

1004 if self._invalidx: 

1005 self.recache() 

1006 return self._x 

1007 

1008 def get_ydata(self, orig=True): 

1009 """ 

1010 Return the ydata. 

1011 

1012 If *orig* is *True*, return the original data, else the 

1013 processed data. 

1014 """ 

1015 if orig: 

1016 return self._yorig 

1017 if self._invalidy: 

1018 self.recache() 

1019 return self._y 

1020 

1021 def get_path(self): 

1022 """ 

1023 Return the :class:`~matplotlib.path.Path` object associated 

1024 with this line. 

1025 """ 

1026 if self._invalidy or self._invalidx: 

1027 self.recache() 

1028 return self._path 

1029 

1030 def get_xydata(self): 

1031 """ 

1032 Return the *xy* data as a Nx2 numpy array. 

1033 """ 

1034 if self._invalidy or self._invalidx: 

1035 self.recache() 

1036 return self._xy 

1037 

1038 def set_antialiased(self, b): 

1039 """ 

1040 Set whether to use antialiased rendering. 

1041 

1042 Parameters 

1043 ---------- 

1044 b : bool 

1045 """ 

1046 if self._antialiased != b: 

1047 self.stale = True 

1048 self._antialiased = b 

1049 

1050 def set_color(self, color): 

1051 """ 

1052 Set the color of the line. 

1053 

1054 Parameters 

1055 ---------- 

1056 color : color 

1057 """ 

1058 self._color = color 

1059 self.stale = True 

1060 

1061 def set_drawstyle(self, drawstyle): 

1062 """ 

1063 Set the drawstyle of the plot. 

1064 

1065 The drawstyle determines how the points are connected. 

1066 

1067 Parameters 

1068 ---------- 

1069 drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \ 

1070'steps-post'}, default: 'default' 

1071 For 'default', the points are connected with straight lines. 

1072 

1073 The steps variants connect the points with step-like lines, 

1074 i.e. horizontal lines with vertical steps. They differ in the 

1075 location of the step: 

1076 

1077 - 'steps-pre': The step is at the beginning of the line segment, 

1078 i.e. the line will be at the y-value of point to the right. 

1079 - 'steps-mid': The step is halfway between the points. 

1080 - 'steps-post: The step is at the end of the line segment, 

1081 i.e. the line will be at the y-value of the point to the left. 

1082 - 'steps' is equal to 'steps-pre' and is maintained for 

1083 backward-compatibility. 

1084 

1085 For examples see :doc:`/gallery/lines_bars_and_markers/step_demo`. 

1086 """ 

1087 if drawstyle is None: 

1088 drawstyle = 'default' 

1089 cbook._check_in_list(self.drawStyles, drawstyle=drawstyle) 

1090 if self._drawstyle != drawstyle: 

1091 self.stale = True 

1092 # invalidate to trigger a recache of the path 

1093 self._invalidx = True 

1094 self._drawstyle = drawstyle 

1095 

1096 def set_linewidth(self, w): 

1097 """ 

1098 Set the line width in points. 

1099 

1100 Parameters 

1101 ---------- 

1102 w : float 

1103 Line width, in points. 

1104 """ 

1105 w = float(w) 

1106 

1107 if self._linewidth != w: 

1108 self.stale = True 

1109 self._linewidth = w 

1110 # rescale the dashes + offset 

1111 self._dashOffset, self._dashSeq = _scale_dashes( 

1112 self._us_dashOffset, self._us_dashSeq, self._linewidth) 

1113 

1114 def _split_drawstyle_linestyle(self, ls): 

1115 """ 

1116 Split drawstyle from linestyle string. 

1117 

1118 If *ls* is only a drawstyle default to returning a linestyle 

1119 of '-'. 

1120 

1121 Parameters 

1122 ---------- 

1123 ls : str 

1124 The linestyle to be processed 

1125 

1126 Returns 

1127 ------- 

1128 ret_ds : str or None 

1129 If the linestyle string does not contain a drawstyle prefix 

1130 return None, otherwise return it. 

1131 

1132 ls : str 

1133 The linestyle with the drawstyle (if any) stripped. 

1134 """ 

1135 for ds in self.drawStyleKeys: # long names are first in the list 

1136 if ls.startswith(ds): 

1137 cbook.warn_deprecated( 

1138 "3.1", message="Passing the drawstyle with the linestyle " 

1139 "as a single string is deprecated since Matplotlib " 

1140 "%(since)s and support will be removed %(removal)s; " 

1141 "please pass the drawstyle separately using the drawstyle " 

1142 "keyword argument to Line2D or set_drawstyle() method (or " 

1143 "ds/set_ds()).") 

1144 return ds, ls[len(ds):] or '-' 

1145 return None, ls 

1146 

1147 def set_linestyle(self, ls): 

1148 """ 

1149 Set the linestyle of the line. 

1150 

1151 Parameters 

1152 ---------- 

1153 ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} 

1154 Possible values: 

1155 

1156 - A string: 

1157 

1158 =============================== ================= 

1159 Linestyle Description 

1160 =============================== ================= 

1161 ``'-'`` or ``'solid'`` solid line 

1162 ``'--'`` or ``'dashed'`` dashed line 

1163 ``'-.'`` or ``'dashdot'`` dash-dotted line 

1164 ``':'`` or ``'dotted'`` dotted line 

1165 ``'None'`` or ``' '`` or ``''`` draw nothing 

1166 =============================== ================= 

1167 

1168 - Alternatively a dash tuple of the following form can be 

1169 provided:: 

1170 

1171 (offset, onoffseq) 

1172 

1173 where ``onoffseq`` is an even length tuple of on and off ink 

1174 in points. See also :meth:`set_dashes`. 

1175 

1176 For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`. 

1177 """ 

1178 if isinstance(ls, str): 

1179 ds, ls = self._split_drawstyle_linestyle(ls) 

1180 if ds is not None: 

1181 self.set_drawstyle(ds) 

1182 

1183 if ls in [' ', '', 'none']: 

1184 ls = 'None' 

1185 

1186 cbook._check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls) 

1187 if ls not in self._lineStyles: 

1188 ls = ls_mapper_r[ls] 

1189 self._linestyle = ls 

1190 else: 

1191 self._linestyle = '--' 

1192 

1193 # get the unscaled dashes 

1194 self._us_dashOffset, self._us_dashSeq = _get_dash_pattern(ls) 

1195 # compute the linewidth scaled dashes 

1196 self._dashOffset, self._dashSeq = _scale_dashes( 

1197 self._us_dashOffset, self._us_dashSeq, self._linewidth) 

1198 

1199 @docstring.dedent_interpd 

1200 def set_marker(self, marker): 

1201 """ 

1202 Set the line marker. 

1203 

1204 Parameters 

1205 ---------- 

1206 marker : marker style 

1207 See `~matplotlib.markers` for full description of possible 

1208 arguments. 

1209 """ 

1210 self._marker.set_marker(marker) 

1211 self.stale = True 

1212 

1213 def set_markeredgecolor(self, ec): 

1214 """ 

1215 Set the marker edge color. 

1216 

1217 Parameters 

1218 ---------- 

1219 ec : color 

1220 """ 

1221 if ec is None: 

1222 ec = 'auto' 

1223 if (self._markeredgecolor is None 

1224 or np.any(self._markeredgecolor != ec)): 

1225 self.stale = True 

1226 self._markeredgecolor = ec 

1227 

1228 def set_markeredgewidth(self, ew): 

1229 """ 

1230 Set the marker edge width in points. 

1231 

1232 Parameters 

1233 ---------- 

1234 ew : float 

1235 Marker edge width, in points. 

1236 """ 

1237 if ew is None: 

1238 ew = rcParams['lines.markeredgewidth'] 

1239 if self._markeredgewidth != ew: 

1240 self.stale = True 

1241 self._markeredgewidth = ew 

1242 

1243 def set_markerfacecolor(self, fc): 

1244 """ 

1245 Set the marker face color. 

1246 

1247 Parameters 

1248 ---------- 

1249 fc : color 

1250 """ 

1251 if fc is None: 

1252 fc = 'auto' 

1253 if np.any(self._markerfacecolor != fc): 

1254 self.stale = True 

1255 self._markerfacecolor = fc 

1256 

1257 def set_markerfacecoloralt(self, fc): 

1258 """ 

1259 Set the alternate marker face color. 

1260 

1261 Parameters 

1262 ---------- 

1263 fc : color 

1264 """ 

1265 if fc is None: 

1266 fc = 'auto' 

1267 if np.any(self._markerfacecoloralt != fc): 

1268 self.stale = True 

1269 self._markerfacecoloralt = fc 

1270 

1271 def set_markersize(self, sz): 

1272 """ 

1273 Set the marker size in points. 

1274 

1275 Parameters 

1276 ---------- 

1277 sz : float 

1278 Marker size, in points. 

1279 """ 

1280 sz = float(sz) 

1281 if self._markersize != sz: 

1282 self.stale = True 

1283 self._markersize = sz 

1284 

1285 def set_xdata(self, x): 

1286 """ 

1287 Set the data array for x. 

1288 

1289 Parameters 

1290 ---------- 

1291 x : 1D array 

1292 """ 

1293 self._xorig = x 

1294 self._invalidx = True 

1295 self.stale = True 

1296 

1297 def set_ydata(self, y): 

1298 """ 

1299 Set the data array for y. 

1300 

1301 Parameters 

1302 ---------- 

1303 y : 1D array 

1304 """ 

1305 self._yorig = y 

1306 self._invalidy = True 

1307 self.stale = True 

1308 

1309 def set_dashes(self, seq): 

1310 """ 

1311 Set the dash sequence. 

1312 

1313 The dash sequence is a sequence of floats of even length describing 

1314 the length of dashes and spaces in points. 

1315 

1316 For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point 

1317 dashes separated by 2 point spaces. 

1318 

1319 Parameters 

1320 ---------- 

1321 seq : sequence of floats (on/off ink in points) or (None, None) 

1322 If *seq* is empty or ``(None, None)``, the linestyle will be set 

1323 to solid. 

1324 """ 

1325 if seq == (None, None) or len(seq) == 0: 

1326 self.set_linestyle('-') 

1327 else: 

1328 self.set_linestyle((0, seq)) 

1329 

1330 def update_from(self, other): 

1331 """Copy properties from *other* to self.""" 

1332 Artist.update_from(self, other) 

1333 self._linestyle = other._linestyle 

1334 self._linewidth = other._linewidth 

1335 self._color = other._color 

1336 self._markersize = other._markersize 

1337 self._markerfacecolor = other._markerfacecolor 

1338 self._markerfacecoloralt = other._markerfacecoloralt 

1339 self._markeredgecolor = other._markeredgecolor 

1340 self._markeredgewidth = other._markeredgewidth 

1341 self._dashSeq = other._dashSeq 

1342 self._us_dashSeq = other._us_dashSeq 

1343 self._dashOffset = other._dashOffset 

1344 self._us_dashOffset = other._us_dashOffset 

1345 self._dashcapstyle = other._dashcapstyle 

1346 self._dashjoinstyle = other._dashjoinstyle 

1347 self._solidcapstyle = other._solidcapstyle 

1348 self._solidjoinstyle = other._solidjoinstyle 

1349 

1350 self._linestyle = other._linestyle 

1351 self._marker = MarkerStyle(other._marker.get_marker(), 

1352 other._marker.get_fillstyle()) 

1353 self._drawstyle = other._drawstyle 

1354 

1355 def set_dash_joinstyle(self, s): 

1356 """ 

1357 Set the join style for dashed lines. 

1358 

1359 Parameters 

1360 ---------- 

1361 s : {'miter', 'round', 'bevel'} 

1362 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. 

1363 """ 

1364 s = s.lower() 

1365 cbook._check_in_list(self.validJoin, s=s) 

1366 if self._dashjoinstyle != s: 

1367 self.stale = True 

1368 self._dashjoinstyle = s 

1369 

1370 def set_solid_joinstyle(self, s): 

1371 """ 

1372 Set the join style for solid lines. 

1373 

1374 Parameters 

1375 ---------- 

1376 s : {'miter', 'round', 'bevel'} 

1377 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. 

1378 """ 

1379 s = s.lower() 

1380 cbook._check_in_list(self.validJoin, s=s) 

1381 if self._solidjoinstyle != s: 

1382 self.stale = True 

1383 self._solidjoinstyle = s 

1384 

1385 def get_dash_joinstyle(self): 

1386 """ 

1387 Return the join style for dashed lines. 

1388 

1389 See also `~.Line2D.set_dash_joinstyle`. 

1390 """ 

1391 return self._dashjoinstyle 

1392 

1393 def get_solid_joinstyle(self): 

1394 """ 

1395 Return the join style for solid lines. 

1396 

1397 See also `~.Line2D.set_solid_joinstyle`. 

1398 """ 

1399 return self._solidjoinstyle 

1400 

1401 def set_dash_capstyle(self, s): 

1402 """ 

1403 Set the cap style for dashed lines. 

1404 

1405 Parameters 

1406 ---------- 

1407 s : {'butt', 'round', 'projecting'} 

1408 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. 

1409 """ 

1410 s = s.lower() 

1411 cbook._check_in_list(self.validCap, s=s) 

1412 if self._dashcapstyle != s: 

1413 self.stale = True 

1414 self._dashcapstyle = s 

1415 

1416 def set_solid_capstyle(self, s): 

1417 """ 

1418 Set the cap style for solid lines. 

1419 

1420 Parameters 

1421 ---------- 

1422 s : {'butt', 'round', 'projecting'} 

1423 For examples see :doc:`/gallery/lines_bars_and_markers/joinstyle`. 

1424 """ 

1425 s = s.lower() 

1426 cbook._check_in_list(self.validCap, s=s) 

1427 if self._solidcapstyle != s: 

1428 self.stale = True 

1429 self._solidcapstyle = s 

1430 

1431 def get_dash_capstyle(self): 

1432 """ 

1433 Return the cap style for dashed lines. 

1434 

1435 See also `~.Line2D.set_dash_capstyle`. 

1436 """ 

1437 return self._dashcapstyle 

1438 

1439 def get_solid_capstyle(self): 

1440 """ 

1441 Return the cap style for solid lines. 

1442 

1443 See also `~.Line2D.set_solid_capstyle`. 

1444 """ 

1445 return self._solidcapstyle 

1446 

1447 def is_dashed(self): 

1448 """ 

1449 Return whether line has a dashed linestyle. 

1450 

1451 See also `~.Line2D.set_linestyle`. 

1452 """ 

1453 return self._linestyle in ('--', '-.', ':') 

1454 

1455 

1456class VertexSelector: 

1457 """ 

1458 Manage the callbacks to maintain a list of selected vertices for 

1459 `.Line2D`. Derived classes should override 

1460 :meth:`~matplotlib.lines.VertexSelector.process_selected` to do 

1461 something with the picks. 

1462 

1463 Here is an example which highlights the selected verts with red 

1464 circles:: 

1465 

1466 import numpy as np 

1467 import matplotlib.pyplot as plt 

1468 import matplotlib.lines as lines 

1469 

1470 class HighlightSelected(lines.VertexSelector): 

1471 def __init__(self, line, fmt='ro', **kwargs): 

1472 lines.VertexSelector.__init__(self, line) 

1473 self.markers, = self.axes.plot([], [], fmt, **kwargs) 

1474 

1475 def process_selected(self, ind, xs, ys): 

1476 self.markers.set_data(xs, ys) 

1477 self.canvas.draw() 

1478 

1479 fig, ax = plt.subplots() 

1480 x, y = np.random.rand(2, 30) 

1481 line, = ax.plot(x, y, 'bs-', picker=5) 

1482 

1483 selector = HighlightSelected(line) 

1484 plt.show() 

1485 

1486 """ 

1487 def __init__(self, line): 

1488 """ 

1489 Initialize the class with a `.Line2D` instance. The line should 

1490 already be added to some :class:`matplotlib.axes.Axes` instance and 

1491 should have the picker property set. 

1492 """ 

1493 if line.axes is None: 

1494 raise RuntimeError('You must first add the line to the Axes') 

1495 

1496 if line.get_picker() is None: 

1497 raise RuntimeError('You must first set the picker property ' 

1498 'of the line') 

1499 

1500 self.axes = line.axes 

1501 self.line = line 

1502 self.canvas = self.axes.figure.canvas 

1503 self.cid = self.canvas.mpl_connect('pick_event', self.onpick) 

1504 

1505 self.ind = set() 

1506 

1507 def process_selected(self, ind, xs, ys): 

1508 """ 

1509 Default "do nothing" implementation of the 

1510 :meth:`process_selected` method. 

1511 

1512 *ind* are the indices of the selected vertices. *xs* and *ys* 

1513 are the coordinates of the selected vertices. 

1514 """ 

1515 pass 

1516 

1517 def onpick(self, event): 

1518 """When the line is picked, update the set of selected indices.""" 

1519 if event.artist is not self.line: 

1520 return 

1521 self.ind ^= set(event.ind) 

1522 ind = sorted(self.ind) 

1523 xdata, ydata = self.line.get_data() 

1524 self.process_selected(ind, xdata[ind], ydata[ind]) 

1525 

1526 

1527lineStyles = Line2D._lineStyles 

1528lineMarkers = MarkerStyle.markers 

1529drawStyles = Line2D.drawStyles 

1530fillStyles = MarkerStyle.fillstyles 

1531 

1532docstring.interpd.update(_Line2D_docstr=artist.kwdoc(Line2D)) 

1533 

1534# You can not set the docstring of an instancemethod, 

1535# but you can on the underlying function. Go figure. 

1536docstring.dedent_interpd(Line2D.__init__)