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

692 statements  

1""" 

22D lines with support for a variety of line styles, markers, colors, etc. 

3""" 

4 

5import copy 

6 

7from numbers import Integral, Number, Real 

8import logging 

9 

10import numpy as np 

11 

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 

21 

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) 

29 

30_log = logging.getLogger(__name__) 

31 

32 

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}') 

53 

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 

59 

60 return offset, dashes 

61 

62 

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] 

71 

72 return offset_gaps, gaps 

73 

74 

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 

82 

83 

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 

93 

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

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

96 

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) 

103 

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:]) 

110 

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)) 

119 

120 

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. 

125 

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 

131 

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] 

137 

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) 

144 

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 

159 

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

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

162 

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") 

173 

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) 

178 

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") 

199 

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)) 

203 

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") 

214 

215 

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 """ 

236 

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 } 

246 

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 } 

253 

254 _drawStyles_s = { 

255 'steps': '_draw_steps_pre', 

256 } 

257 

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] 

262 

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 

268 

269 zorder = 2 

270 

271 _subslice_optim_min_size = 1000 

272 

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)) 

286 

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*. 

312 

313 Additional keyword arguments are `.Line2D` properties: 

314 

315 %(Line2D:kwdoc)s 

316 

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. 

320 

321 """ 

322 super().__init__() 

323 

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') 

329 

330 if linewidth is None: 

331 linewidth = mpl.rcParams['lines.linewidth'] 

332 

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'] 

339 

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'] 

352 

353 if drawstyle is None: 

354 drawstyle = 'default' 

355 

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) 

364 

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) 

370 

371 self.set_linewidth(linewidth) 

372 self.set_linestyle(linestyle) 

373 self.set_drawstyle(drawstyle) 

374 

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 

383 

384 self._gapcolor = None 

385 self.set_gapcolor(gapcolor) 

386 

387 self._markevery = None 

388 self._markersize = None 

389 self._antialiased = None 

390 

391 self.set_markevery(markevery) 

392 self.set_antialiased(antialiased) 

393 self.set_markersize(markersize) 

394 

395 self._markeredgecolor = None 

396 self._markeredgewidth = None 

397 self._markerfacecolor = None 

398 self._markerfacecoloralt = None 

399 

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) 

404 

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 

413 

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 

425 

426 self.set_data(xdata, ydata) 

427 

428 def contains(self, mouseevent): 

429 """ 

430 Test whether *mouseevent* occurred on the line. 

431 

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. 

436 

437 Parameters 

438 ---------- 

439 mouseevent : `~matplotlib.backend_bases.MouseEvent` 

440 

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. 

449 

450 TODO: sort returned indices by distance 

451 """ 

452 if self._different_canvas(mouseevent): 

453 return False, {} 

454 

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, {} 

460 

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] 

468 

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 

475 

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 

491 

492 ind += self.ind_offset 

493 

494 # Return the point(s) within radius 

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

496 

497 def get_pickradius(self): 

498 """ 

499 Return the pick radius used for containment tests. 

500 

501 See `.contains` for more details. 

502 """ 

503 return self._pickradius 

504 

505 def set_pickradius(self, pickradius): 

506 """ 

507 Set the pick radius used for containment tests. 

508 

509 See `.contains` for more details. 

510 

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 

519 

520 pickradius = property(get_pickradius, set_pickradius) 

521 

522 def get_fillstyle(self): 

523 """ 

524 Return the marker fill style. 

525 

526 See also `~.Line2D.set_fillstyle`. 

527 """ 

528 return self._marker.get_fillstyle() 

529 

530 def set_fillstyle(self, fs): 

531 """ 

532 Set the marker fill style. 

533 

534 Parameters 

535 ---------- 

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

537 Possible values: 

538 

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. 

544 

545 For examples see :ref:`marker_fill_styles`. 

546 """ 

547 self.set_marker(MarkerStyle(self._marker.get_marker(), fs)) 

548 self.stale = True 

549 

550 def set_markevery(self, every): 

551 """ 

552 Set the markevery property to subsample the plot when using markers. 

553 

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

555 

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. 

561 

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. 

584 

585 For examples see 

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

587 

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. 

595 

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. 

599 

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. 

604 

605 """ 

606 self._markevery = every 

607 self.stale = True 

608 

609 def get_markevery(self): 

610 """ 

611 Return the markevery setting for marker subsampling. 

612 

613 See also `~.Line2D.set_markevery`. 

614 """ 

615 return self._markevery 

616 

617 def set_picker(self, p): 

618 """ 

619 Set the event picker details for the line. 

620 

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 

629 

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 

635 

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 

646 

647 def set_data(self, *args): 

648 """ 

649 Set the x and y data. 

650 

651 Parameters 

652 ---------- 

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

654 

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 

664 

665 self.set_xdata(x) 

666 self.set_ydata(y) 

667 

668 def recache_always(self): 

669 self.recache(always=True) 

670 

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 

682 

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

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

685 

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 

704 

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 

715 

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()) 

730 

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 

736 

737 def set_transform(self, t): 

738 # docstring inherited 

739 self._invalidx = True 

740 self._invalidy = True 

741 super().set_transform(t) 

742 

743 @allow_rasterization 

744 def draw(self, renderer): 

745 # docstring inherited 

746 

747 if not self.get_visible(): 

748 return 

749 

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 

762 

763 if self.get_path_effects(): 

764 from matplotlib.patheffects import PathEffectRenderer 

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

766 

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()) 

775 

776 gc.set_antialiased(self._antialiased) 

777 gc.set_linewidth(self._linewidth) 

778 

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()) 

790 

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) 

795 

796 offset_gaps, gaps = _get_inverse_dash_pattern( 

797 *self._dash_pattern) 

798 

799 gc.set_dashes(offset_gaps, gaps) 

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

801 

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

803 gc.set_foreground(lc_rgba, isRGBA=True) 

804 

805 gc.set_dashes(*self._dash_pattern) 

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

807 gc.restore() 

808 

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) 

815 

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) 

832 

833 marker = self._marker 

834 

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()) 

847 

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 

856 

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) 

866 

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) 

875 

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) 

883 

884 gc.restore() 

885 

886 renderer.close_group('line2d') 

887 self.stale = False 

888 

889 def get_antialiased(self): 

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

891 return self._antialiased 

892 

893 def get_color(self): 

894 """ 

895 Return the line color. 

896 

897 See also `~.Line2D.set_color`. 

898 """ 

899 return self._color 

900 

901 def get_drawstyle(self): 

902 """ 

903 Return the drawstyle. 

904 

905 See also `~.Line2D.set_drawstyle`. 

906 """ 

907 return self._drawstyle 

908 

909 def get_gapcolor(self): 

910 """ 

911 Return the line gapcolor. 

912 

913 See also `~.Line2D.set_gapcolor`. 

914 """ 

915 return self._gapcolor 

916 

917 def get_linestyle(self): 

918 """ 

919 Return the linestyle. 

920 

921 See also `~.Line2D.set_linestyle`. 

922 """ 

923 return self._linestyle 

924 

925 def get_linewidth(self): 

926 """ 

927 Return the linewidth in points. 

928 

929 See also `~.Line2D.set_linewidth`. 

930 """ 

931 return self._linewidth 

932 

933 def get_marker(self): 

934 """ 

935 Return the line marker. 

936 

937 See also `~.Line2D.set_marker`. 

938 """ 

939 return self._marker.get_marker() 

940 

941 def get_markeredgecolor(self): 

942 """ 

943 Return the marker edge color. 

944 

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 

958 

959 def get_markeredgewidth(self): 

960 """ 

961 Return the marker edge width in points. 

962 

963 See also `~.Line2D.set_markeredgewidth`. 

964 """ 

965 return self._markeredgewidth 

966 

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 

975 

976 def get_markerfacecolor(self): 

977 """ 

978 Return the marker face color. 

979 

980 See also `~.Line2D.set_markerfacecolor`. 

981 """ 

982 return self._get_markerfacecolor(alt=False) 

983 

984 def get_markerfacecoloralt(self): 

985 """ 

986 Return the alternate marker face color. 

987 

988 See also `~.Line2D.set_markerfacecoloralt`. 

989 """ 

990 return self._get_markerfacecolor(alt=True) 

991 

992 def get_markersize(self): 

993 """ 

994 Return the marker size in points. 

995 

996 See also `~.Line2D.set_markersize`. 

997 """ 

998 return self._markersize 

999 

1000 def get_data(self, orig=True): 

1001 """ 

1002 Return the line data as an ``(xdata, ydata)`` pair. 

1003 

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

1005 """ 

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

1007 

1008 def get_xdata(self, orig=True): 

1009 """ 

1010 Return the xdata. 

1011 

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 

1020 

1021 def get_ydata(self, orig=True): 

1022 """ 

1023 Return the ydata. 

1024 

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 

1033 

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 

1039 

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 

1045 

1046 def set_antialiased(self, b): 

1047 """ 

1048 Set whether to use antialiased rendering. 

1049 

1050 Parameters 

1051 ---------- 

1052 b : bool 

1053 """ 

1054 if self._antialiased != b: 

1055 self.stale = True 

1056 self._antialiased = b 

1057 

1058 def set_color(self, color): 

1059 """ 

1060 Set the color of the line. 

1061 

1062 Parameters 

1063 ---------- 

1064 color : :mpltype:`color` 

1065 """ 

1066 mcolors._check_color_like(color=color) 

1067 self._color = color 

1068 self.stale = True 

1069 

1070 def set_drawstyle(self, drawstyle): 

1071 """ 

1072 Set the drawstyle of the plot. 

1073 

1074 The drawstyle determines how the points are connected. 

1075 

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. 

1081 

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: 

1085 

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. 

1093 

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 

1104 

1105 def set_gapcolor(self, gapcolor): 

1106 """ 

1107 Set a color to fill the gaps in the dashed line style. 

1108 

1109 .. note:: 

1110 

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. 

1114 

1115 This functionality is experimental and may change. 

1116 

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 

1127 

1128 def set_linewidth(self, w): 

1129 """ 

1130 Set the line width in points. 

1131 

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) 

1142 

1143 def set_linestyle(self, ls): 

1144 """ 

1145 Set the linestyle of the line. 

1146 

1147 Parameters 

1148 ---------- 

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

1150 Possible values: 

1151 

1152 - A string: 

1153 

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 ========================================== ================= 

1163 

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

1165 provided:: 

1166 

1167 (offset, onoffseq) 

1168 

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

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

1171 

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 

1187 

1188 @_docstring.interpd 

1189 def set_marker(self, marker): 

1190 """ 

1191 Set the line marker. 

1192 

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 

1201 

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) 

1215 

1216 def set_markeredgecolor(self, ec): 

1217 """ 

1218 Set the marker edge color. 

1219 

1220 Parameters 

1221 ---------- 

1222 ec : :mpltype:`color` 

1223 """ 

1224 self._set_markercolor("markeredgecolor", True, ec) 

1225 

1226 def set_markerfacecolor(self, fc): 

1227 """ 

1228 Set the marker face color. 

1229 

1230 Parameters 

1231 ---------- 

1232 fc : :mpltype:`color` 

1233 """ 

1234 self._set_markercolor("markerfacecolor", True, fc) 

1235 

1236 def set_markerfacecoloralt(self, fc): 

1237 """ 

1238 Set the alternate marker face color. 

1239 

1240 Parameters 

1241 ---------- 

1242 fc : :mpltype:`color` 

1243 """ 

1244 self._set_markercolor("markerfacecoloralt", False, fc) 

1245 

1246 def set_markeredgewidth(self, ew): 

1247 """ 

1248 Set the marker edge width in points. 

1249 

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 

1260 

1261 def set_markersize(self, sz): 

1262 """ 

1263 Set the marker size in points. 

1264 

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 

1274 

1275 def set_xdata(self, x): 

1276 """ 

1277 Set the data array for x. 

1278 

1279 Parameters 

1280 ---------- 

1281 x : 1D array 

1282 

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 

1293 

1294 def set_ydata(self, y): 

1295 """ 

1296 Set the data array for y. 

1297 

1298 Parameters 

1299 ---------- 

1300 y : 1D array 

1301 

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 

1312 

1313 def set_dashes(self, seq): 

1314 """ 

1315 Set the dash sequence. 

1316 

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

1318 the length of dashes and spaces in points. 

1319 

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

1321 dashes separated by 2 point spaces. 

1322 

1323 See also `~.Line2D.set_gapcolor`, which allows those spaces to be 

1324 filled with a color. 

1325 

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)) 

1336 

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 

1355 

1356 self._linestyle = other._linestyle 

1357 self._marker = MarkerStyle(marker=other._marker) 

1358 self._drawstyle = other._drawstyle 

1359 

1360 @_docstring.interpd 

1361 def set_dash_joinstyle(self, s): 

1362 """ 

1363 How to join segments of the line if it `~Line2D.is_dashed`. 

1364 

1365 The default joinstyle is :rc:`lines.dash_joinstyle`. 

1366 

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 

1375 

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`). 

1380 

1381 The default joinstyle is :rc:`lines.solid_joinstyle`. 

1382 

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 

1391 

1392 def get_dash_joinstyle(self): 

1393 """ 

1394 Return the `.JoinStyle` for dashed lines. 

1395 

1396 See also `~.Line2D.set_dash_joinstyle`. 

1397 """ 

1398 return self._dashjoinstyle.name 

1399 

1400 def get_solid_joinstyle(self): 

1401 """ 

1402 Return the `.JoinStyle` for solid lines. 

1403 

1404 See also `~.Line2D.set_solid_joinstyle`. 

1405 """ 

1406 return self._solidjoinstyle.name 

1407 

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`. 

1412 

1413 The default capstyle is :rc:`lines.dash_capstyle`. 

1414 

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 

1423 

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`) 

1428 

1429 The default capstyle is :rc:`lines.solid_capstyle`. 

1430 

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 

1439 

1440 def get_dash_capstyle(self): 

1441 """ 

1442 Return the `.CapStyle` for dashed lines. 

1443 

1444 See also `~.Line2D.set_dash_capstyle`. 

1445 """ 

1446 return self._dashcapstyle.name 

1447 

1448 def get_solid_capstyle(self): 

1449 """ 

1450 Return the `.CapStyle` for solid lines. 

1451 

1452 See also `~.Line2D.set_solid_capstyle`. 

1453 """ 

1454 return self._solidcapstyle.name 

1455 

1456 def is_dashed(self): 

1457 """ 

1458 Return whether line has a dashed linestyle. 

1459 

1460 A custom linestyle is assumed to be dashed, we do not inspect the 

1461 ``onoffseq`` directly. 

1462 

1463 See also `~.Line2D.set_linestyle`. 

1464 """ 

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

1466 

1467 

1468class AxLine(Line2D): 

1469 """ 

1470 A helper class that implements `~.Axes.axline`, by recomputing the artist 

1471 transform at draw time. 

1472 """ 

1473 

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) 

1488 

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") 

1493 

1494 self._slope = slope 

1495 self._xy1 = xy1 

1496 self._xy2 = xy2 

1497 

1498 def get_transform(self): 

1499 ax = self.axes 

1500 points_transform = self._transform - ax.transData + ax.transScale 

1501 

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) 

1538 

1539 def draw(self, renderer): 

1540 self._transformed_path = None # Force regen. 

1541 super().draw(renderer) 

1542 

1543 def get_xy1(self): 

1544 """ 

1545 Return the *xy1* value of the line. 

1546 """ 

1547 return self._xy1 

1548 

1549 def get_xy2(self): 

1550 """ 

1551 Return the *xy2* value of the line. 

1552 """ 

1553 return self._xy2 

1554 

1555 def get_slope(self): 

1556 """ 

1557 Return the *slope* value of the line. 

1558 """ 

1559 return self._slope 

1560 

1561 def set_xy1(self, x, y): 

1562 """ 

1563 Set the *xy1* value of the line. 

1564 

1565 Parameters 

1566 ---------- 

1567 x, y : float 

1568 Points for the line to pass through. 

1569 """ 

1570 self._xy1 = x, y 

1571 

1572 def set_xy2(self, x, y): 

1573 """ 

1574 Set the *xy2* value of the line. 

1575 

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") 

1586 

1587 def set_slope(self, slope): 

1588 """ 

1589 Set the *slope* value of the line. 

1590 

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") 

1601 

1602 

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. 

1608 

1609 Here is an example which highlights the selected verts with red circles:: 

1610 

1611 import numpy as np 

1612 import matplotlib.pyplot as plt 

1613 import matplotlib.lines as lines 

1614 

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) 

1619 

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

1621 self.markers.set_data(xs, ys) 

1622 self.canvas.draw() 

1623 

1624 fig, ax = plt.subplots() 

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

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

1627 

1628 selector = HighlightSelected(line) 

1629 plt.show() 

1630 """ 

1631 

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() 

1650 

1651 canvas = property(lambda self: self.axes.figure.canvas) 

1652 

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

1654 """ 

1655 Default "do nothing" implementation of the `process_selected` method. 

1656 

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 

1665 

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]) 

1674 

1675 

1676lineStyles = Line2D._lineStyles 

1677lineMarkers = MarkerStyle.markers 

1678drawStyles = Line2D.drawStyles 

1679fillStyles = MarkerStyle.fillstyles