Coverage for /usr/lib/python3/dist-packages/matplotlib/lines.py: 17%

675 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

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['lines.{}_pattern'.format(style)]) 

46 # 

47 elif isinstance(style, tuple): 

48 offset, dashes = style 

49 if offset is None: 

50 raise ValueError(f'Unrecognized linestyle: {style!r}') 

51 else: 

52 raise ValueError(f'Unrecognized linestyle: {style!r}') 

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 _scale_dashes(offset, dashes, lw): 

64 if not mpl.rcParams['lines.scale_dashes']: 

65 return offset, dashes 

66 scaled_offset = offset * lw 

67 scaled_dashes = ([x * lw if x is not None else None for x in dashes] 

68 if dashes is not None else None) 

69 return scaled_offset, scaled_dashes 

70 

71 

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

73 """ 

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

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

76 """ 

77 # Process single points specially 

78 if len(x) <= 1: 

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

80 return res 

81 

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

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

84 

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

86 # lies within the segment. 

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

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

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

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

91 

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

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

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

95 # following radius test eliminates these ambiguities. 

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

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

98 

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

100 # from the line. 

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

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

103 line_hits = line_hits & candidates 

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

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

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

107 

108 

109def _mark_every_path(markevery, tpath, affine, ax): 

110 """ 

111 Helper function that sorts out how to deal the input 

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

113 

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

115 sub-sampled path. 

116 """ 

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

118 codes, verts = tpath.codes, tpath.vertices 

119 

120 def _slice_or_none(in_v, slc): 

121 """Helper function to cope with `codes` being an ndarray or `None`.""" 

122 if in_v is None: 

123 return None 

124 return in_v[slc] 

125 

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

127 if isinstance(markevery, Integral): 

128 markevery = (0, markevery) 

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

130 elif isinstance(markevery, Real): 

131 markevery = (0.0, markevery) 

132 

133 if isinstance(markevery, tuple): 

134 if len(markevery) != 2: 

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

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

137 start, step = markevery 

138 # if step is an int, old behavior 

139 if isinstance(step, Integral): 

140 # tuple of 2 int is for backwards compatibility, 

141 if not isinstance(start, Integral): 

142 raise ValueError( 

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

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

145 .format(markevery)) 

146 # just return, we are done here 

147 

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

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

150 

151 elif isinstance(step, Real): 

152 if not isinstance(start, Real): 

153 raise ValueError( 

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

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

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

157 if ax is None: 

158 raise ValueError( 

159 "markevery is specified relative to the axes size, but " 

160 "the line does not have a Axes as parent") 

161 

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

163 fin = np.isfinite(verts).all(axis=1) 

164 fverts = verts[fin] 

165 disp_coords = affine.transform(fverts) 

166 

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

168 delta[0, :] = 0 

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

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

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

172 # bounding box diagonal being a distance of unity: 

173 (x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]]) 

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

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

176 # find closest actual data point that is closest to 

177 # the theoretical distance along the path: 

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

179 inds = inds.argmin(axis=1) 

180 inds = np.unique(inds) 

181 # return, we are done here 

182 return Path(fverts[inds], _slice_or_none(codes, inds)) 

183 else: 

184 raise ValueError( 

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

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

187 

188 elif isinstance(markevery, slice): 

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

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

191 

192 elif np.iterable(markevery): 

193 # fancy indexing 

194 try: 

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

196 except (ValueError, IndexError) as err: 

197 raise ValueError( 

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

199 f"fancy index") from err 

200 else: 

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

202 

203 

204@_docstring.interpd 

205@_api.define_aliases({ 

206 "antialiased": ["aa"], 

207 "color": ["c"], 

208 "drawstyle": ["ds"], 

209 "linestyle": ["ls"], 

210 "linewidth": ["lw"], 

211 "markeredgecolor": ["mec"], 

212 "markeredgewidth": ["mew"], 

213 "markerfacecolor": ["mfc"], 

214 "markerfacecoloralt": ["mfcalt"], 

215 "markersize": ["ms"], 

216}) 

217class Line2D(Artist): 

218 """ 

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

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

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

222 can create "stepped" lines in various styles. 

223 """ 

224 

225 lineStyles = _lineStyles = { # hidden names deprecated 

226 '-': '_draw_solid', 

227 '--': '_draw_dashed', 

228 '-.': '_draw_dash_dot', 

229 ':': '_draw_dotted', 

230 'None': '_draw_nothing', 

231 ' ': '_draw_nothing', 

232 '': '_draw_nothing', 

233 } 

234 

235 _drawStyles_l = { 

236 'default': '_draw_lines', 

237 'steps-mid': '_draw_steps_mid', 

238 'steps-pre': '_draw_steps_pre', 

239 'steps-post': '_draw_steps_post', 

240 } 

241 

242 _drawStyles_s = { 

243 'steps': '_draw_steps_pre', 

244 } 

245 

246 # drawStyles should now be deprecated. 

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

248 # Need a list ordered with long names first: 

249 drawStyleKeys = [*_drawStyles_l, *_drawStyles_s] 

250 

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

252 # MarkerStyle 

253 markers = MarkerStyle.markers 

254 filled_markers = MarkerStyle.filled_markers 

255 fillStyles = MarkerStyle.fillstyles 

256 

257 zorder = 2 

258 

259 def __str__(self): 

260 if self._label != "": 

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

262 elif self._x is None: 

263 return "Line2D()" 

264 elif len(self._x) > 3: 

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

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

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

268 else: 

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

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

271 

272 @_api.make_keyword_only("3.6", name="linewidth") 

273 def __init__(self, xdata, ydata, 

274 linewidth=None, # all Nones default to rc 

275 linestyle=None, 

276 color=None, 

277 gapcolor=None, 

278 marker=None, 

279 markersize=None, 

280 markeredgewidth=None, 

281 markeredgecolor=None, 

282 markerfacecolor=None, 

283 markerfacecoloralt='none', 

284 fillstyle=None, 

285 antialiased=None, 

286 dash_capstyle=None, 

287 solid_capstyle=None, 

288 dash_joinstyle=None, 

289 solid_joinstyle=None, 

290 pickradius=5, 

291 drawstyle=None, 

292 markevery=None, 

293 **kwargs 

294 ): 

295 """ 

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

297 *xdata*, *ydata*. 

298 

299 Additional keyword arguments are `.Line2D` properties: 

300 

301 %(Line2D:kwdoc)s 

302 

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

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

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

306 

307 """ 

308 super().__init__() 

309 

310 # Convert sequences to NumPy arrays. 

311 if not np.iterable(xdata): 

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

313 if not np.iterable(ydata): 

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

315 

316 if linewidth is None: 

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

318 

319 if linestyle is None: 

320 linestyle = mpl.rcParams['lines.linestyle'] 

321 if marker is None: 

322 marker = mpl.rcParams['lines.marker'] 

323 if color is None: 

324 color = mpl.rcParams['lines.color'] 

325 

326 if markersize is None: 

327 markersize = mpl.rcParams['lines.markersize'] 

328 if antialiased is None: 

329 antialiased = mpl.rcParams['lines.antialiased'] 

330 if dash_capstyle is None: 

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

332 if dash_joinstyle is None: 

333 dash_joinstyle = mpl.rcParams['lines.dash_joinstyle'] 

334 if solid_capstyle is None: 

335 solid_capstyle = mpl.rcParams['lines.solid_capstyle'] 

336 if solid_joinstyle is None: 

337 solid_joinstyle = mpl.rcParams['lines.solid_joinstyle'] 

338 

339 if drawstyle is None: 

340 drawstyle = 'default' 

341 

342 self._dashcapstyle = None 

343 self._dashjoinstyle = None 

344 self._solidjoinstyle = None 

345 self._solidcapstyle = None 

346 self.set_dash_capstyle(dash_capstyle) 

347 self.set_dash_joinstyle(dash_joinstyle) 

348 self.set_solid_capstyle(solid_capstyle) 

349 self.set_solid_joinstyle(solid_joinstyle) 

350 

351 self._linestyles = None 

352 self._drawstyle = None 

353 self._linewidth = linewidth 

354 self._unscaled_dash_pattern = (0, None) # offset, dash 

355 self._dash_pattern = (0, None) # offset, dash (scaled by linewidth) 

356 

357 self.set_linewidth(linewidth) 

358 self.set_linestyle(linestyle) 

359 self.set_drawstyle(drawstyle) 

360 

361 self._color = None 

362 self.set_color(color) 

363 if marker is None: 

364 marker = 'none' # Default. 

365 if not isinstance(marker, MarkerStyle): 

366 self._marker = MarkerStyle(marker, fillstyle) 

367 else: 

368 self._marker = marker 

369 

370 self._gapcolor = None 

371 self.set_gapcolor(gapcolor) 

372 

373 self._markevery = None 

374 self._markersize = None 

375 self._antialiased = None 

376 

377 self.set_markevery(markevery) 

378 self.set_antialiased(antialiased) 

379 self.set_markersize(markersize) 

380 

381 self._markeredgecolor = None 

382 self._markeredgewidth = None 

383 self._markerfacecolor = None 

384 self._markerfacecoloralt = None 

385 

386 self.set_markerfacecolor(markerfacecolor) # Normalizes None to rc. 

387 self.set_markerfacecoloralt(markerfacecoloralt) 

388 self.set_markeredgecolor(markeredgecolor) # Normalizes None to rc. 

389 self.set_markeredgewidth(markeredgewidth) 

390 

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

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

393 self._internal_update(kwargs) 

394 self._pickradius = pickradius 

395 self.ind_offset = 0 

396 if (isinstance(self._picker, Number) and 

397 not isinstance(self._picker, bool)): 

398 self._pickradius = self._picker 

399 

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

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

402 self._invalidx = True 

403 self._invalidy = True 

404 self._x = None 

405 self._y = None 

406 self._xy = None 

407 self._path = None 

408 self._transformed_path = None 

409 self._subslice = False 

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

411 

412 self.set_data(xdata, ydata) 

413 

414 def contains(self, mouseevent): 

415 """ 

416 Test whether *mouseevent* occurred on the line. 

417 

418 An event is deemed to have occurred "on" the line if it is less 

419 than ``self.pickradius`` (default: 5 points) away from it. Use 

420 `~.Line2D.get_pickradius` or `~.Line2D.set_pickradius` to get or set 

421 the pick radius. 

422 

423 Parameters 

424 ---------- 

425 mouseevent : `matplotlib.backend_bases.MouseEvent` 

426 

427 Returns 

428 ------- 

429 contains : bool 

430 Whether any values are within the radius. 

431 details : dict 

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

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

434 the event position. 

435 

436 TODO: sort returned indices by distance 

437 """ 

438 inside, info = self._default_contains(mouseevent) 

439 if inside is not None: 

440 return inside, info 

441 

442 # Make sure we have data to plot 

443 if self._invalidy or self._invalidx: 

444 self.recache() 

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

446 return False, {} 

447 

448 # Convert points to pixels 

449 transformed_path = self._get_transformed_path() 

450 path, affine = transformed_path.get_transformed_path_and_affine() 

451 path = affine.transform_path(path) 

452 xy = path.vertices 

453 xt = xy[:, 0] 

454 yt = xy[:, 1] 

455 

456 # Convert pick radius from points to pixels 

457 if self.figure is None: 

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

459 pixels = self._pickradius 

460 else: 

461 pixels = self.figure.dpi / 72. * self._pickradius 

462 

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

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

465 # the error flags accordingly. 

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

467 # Check for collision 

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

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

470 ind, = np.nonzero( 

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

472 <= pixels ** 2) 

473 else: 

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

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

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

477 ind //= 2 

478 

479 ind += self.ind_offset 

480 

481 # Return the point(s) within radius 

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

483 

484 def get_pickradius(self): 

485 """ 

486 Return the pick radius used for containment tests. 

487 

488 See `.contains` for more details. 

489 """ 

490 return self._pickradius 

491 

492 @_api.rename_parameter("3.6", "d", "pickradius") 

493 def set_pickradius(self, pickradius): 

494 """ 

495 Set the pick radius used for containment tests. 

496 

497 See `.contains` for more details. 

498 

499 Parameters 

500 ---------- 

501 pickradius : float 

502 Pick radius, in points. 

503 """ 

504 if not isinstance(pickradius, Number) or pickradius < 0: 

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

506 self._pickradius = pickradius 

507 

508 pickradius = property(get_pickradius, set_pickradius) 

509 

510 def get_fillstyle(self): 

511 """ 

512 Return the marker fill style. 

513 

514 See also `~.Line2D.set_fillstyle`. 

515 """ 

516 return self._marker.get_fillstyle() 

517 

518 def set_fillstyle(self, fs): 

519 """ 

520 Set the marker fill style. 

521 

522 Parameters 

523 ---------- 

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

525 Possible values: 

526 

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

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

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

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

531 - 'none': No filling. 

532 

533 For examples see :ref:`marker_fill_styles`. 

534 """ 

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

536 self.stale = True 

537 

538 def set_markevery(self, every): 

539 """ 

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

541 

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

543 

544 Parameters 

545 ---------- 

546 every : None or int or (int, int) or slice or list[int] or float or \ 

547(float, float) or list[bool] 

548 Which markers to plot. 

549 

550 - ``every=None``: every point will be plotted. 

551 - ``every=N``: every N-th marker will be plotted starting with 

552 marker 0. 

553 - ``every=(start, N)``: every N-th marker, starting at index 

554 *start*, will be plotted. 

555 - ``every=slice(start, end, N)``: every N-th marker, starting at 

556 index *start*, up to but not including index *end*, will be 

557 plotted. 

558 - ``every=[i, j, m, ...]``: only markers at the given indices 

559 will be plotted. 

560 - ``every=[True, False, True, ...]``: only positions that are True 

561 will be plotted. The list must have the same length as the data 

562 points. 

563 - ``every=0.1``, (i.e. a float): markers will be spaced at 

564 approximately equal visual distances along the line; the distance 

565 along the line between markers is determined by multiplying the 

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

567 by the value of *every*. 

568 - ``every=(0.5, 0.1)`` (i.e. a length-2 tuple of float): similar 

569 to ``every=0.1`` but the first marker will be offset along the 

570 line by 0.5 multiplied by the 

571 display-coordinate-diagonal-distance along the line. 

572 

573 For examples see 

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

575 

576 Notes 

577 ----- 

578 Setting *markevery* will still only draw markers at actual data points. 

579 While the float argument form aims for uniform visual spacing, it has 

580 to coerce from the ideal spacing to the nearest available data point. 

581 Depending on the number and distribution of data points, the result 

582 may still not look evenly spaced. 

583 

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

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

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

587 

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

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

590 markers is always determined from the display-coordinates 

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

592 

593 """ 

594 self._markevery = every 

595 self.stale = True 

596 

597 def get_markevery(self): 

598 """ 

599 Return the markevery setting for marker subsampling. 

600 

601 See also `~.Line2D.set_markevery`. 

602 """ 

603 return self._markevery 

604 

605 def set_picker(self, p): 

606 """ 

607 Set the event picker details for the line. 

608 

609 Parameters 

610 ---------- 

611 p : float or callable[[Artist, Event], tuple[bool, dict]] 

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

613 """ 

614 if callable(p): 

615 self._contains = p 

616 else: 

617 self.set_pickradius(p) 

618 self._picker = p 

619 

620 def get_bbox(self): 

621 """Get the bounding box of this line.""" 

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

623 bbox.update_from_data_xy(self.get_xydata()) 

624 return bbox 

625 

626 def get_window_extent(self, renderer=None): 

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

628 trans_data_to_xy = self.get_transform().transform 

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

630 ignore=True) 

631 # correct for marker size, if any 

632 if self._marker: 

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

634 bbox = bbox.padded(ms) 

635 return bbox 

636 

637 def set_data(self, *args): 

638 """ 

639 Set the x and y data. 

640 

641 Parameters 

642 ---------- 

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

644 """ 

645 if len(args) == 1: 

646 (x, y), = args 

647 else: 

648 x, y = args 

649 

650 self.set_xdata(x) 

651 self.set_ydata(y) 

652 

653 def recache_always(self): 

654 self.recache(always=True) 

655 

656 def recache(self, always=False): 

657 if always or self._invalidx: 

658 xconv = self.convert_xunits(self._xorig) 

659 x = _to_unmasked_float_array(xconv).ravel() 

660 else: 

661 x = self._x 

662 if always or self._invalidy: 

663 yconv = self.convert_yunits(self._yorig) 

664 y = _to_unmasked_float_array(yconv).ravel() 

665 else: 

666 y = self._y 

667 

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

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

670 

671 self._subslice = False 

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

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

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

675 self._markevery is None and 

676 self.get_clip_on() and 

677 self.get_transform() == self.axes.transData): 

678 self._subslice = True 

679 nanmask = np.isnan(x) 

680 if nanmask.any(): 

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

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

683 self._x_filled[nanmask] = np.interp( 

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

685 else: 

686 self._x_filled = self._x 

687 

688 if self._path is not None: 

689 interpolation_steps = self._path._interpolation_steps 

690 else: 

691 interpolation_steps = 1 

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

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

694 _interpolation_steps=interpolation_steps) 

695 self._transformed_path = None 

696 self._invalidx = False 

697 self._invalidy = False 

698 

699 def _transform_path(self, subslice=None): 

700 """ 

701 Put a TransformedPath instance at self._transformed_path; 

702 all invalidation of the transform is then handled by the 

703 TransformedPath instance. 

704 """ 

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

706 if subslice is not None: 

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

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

709 _interpolation_steps=self._path._interpolation_steps) 

710 else: 

711 _path = self._path 

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

713 

714 def _get_transformed_path(self): 

715 """Return this line's `~matplotlib.transforms.TransformedPath`.""" 

716 if self._transformed_path is None: 

717 self._transform_path() 

718 return self._transformed_path 

719 

720 def set_transform(self, t): 

721 # docstring inherited 

722 self._invalidx = True 

723 self._invalidy = True 

724 super().set_transform(t) 

725 

726 def _is_sorted(self, x): 

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

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

729 return _path.is_sorted(x) 

730 

731 @allow_rasterization 

732 def draw(self, renderer): 

733 # docstring inherited 

734 

735 if not self.get_visible(): 

736 return 

737 

738 if self._invalidy or self._invalidx: 

739 self.recache() 

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

741 if self._subslice and self.axes: 

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

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

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

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

746 self.ind_offset = subslice.start 

747 self._transform_path(subslice) 

748 else: 

749 subslice = None 

750 

751 if self.get_path_effects(): 

752 from matplotlib.patheffects import PathEffectRenderer 

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

754 

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

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

757 tpath, affine = (self._get_transformed_path() 

758 .get_transformed_path_and_affine()) 

759 if len(tpath.vertices): 

760 gc = renderer.new_gc() 

761 self._set_gc_clip(gc) 

762 gc.set_url(self.get_url()) 

763 

764 gc.set_antialiased(self._antialiased) 

765 gc.set_linewidth(self._linewidth) 

766 

767 if self.is_dashed(): 

768 cap = self._dashcapstyle 

769 join = self._dashjoinstyle 

770 else: 

771 cap = self._solidcapstyle 

772 join = self._solidjoinstyle 

773 gc.set_joinstyle(join) 

774 gc.set_capstyle(cap) 

775 gc.set_snap(self.get_snap()) 

776 if self.get_sketch_params() is not None: 

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

778 

779 # We first draw a path within the gaps if needed. 

780 if self.is_dashed() and self._gapcolor is not None: 

781 lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha) 

782 gc.set_foreground(lc_rgba, isRGBA=True) 

783 

784 # Define the inverse pattern by moving the last gap to the 

785 # start of the sequence. 

786 dashes = self._dash_pattern[1] 

787 gaps = dashes[-1:] + dashes[:-1] 

788 # Set the offset so that this new first segment is skipped 

789 # (see backend_bases.GraphicsContextBase.set_dashes for 

790 # offset definition). 

791 offset_gaps = self._dash_pattern[0] + dashes[-1] 

792 

793 gc.set_dashes(offset_gaps, gaps) 

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

795 

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

797 gc.set_foreground(lc_rgba, isRGBA=True) 

798 

799 gc.set_dashes(*self._dash_pattern) 

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

801 gc.restore() 

802 

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

804 gc = renderer.new_gc() 

805 self._set_gc_clip(gc) 

806 gc.set_url(self.get_url()) 

807 gc.set_linewidth(self._markeredgewidth) 

808 gc.set_antialiased(self._antialiased) 

809 

810 ec_rgba = mcolors.to_rgba( 

811 self.get_markeredgecolor(), self._alpha) 

812 fc_rgba = mcolors.to_rgba( 

813 self._get_markerfacecolor(), self._alpha) 

814 fcalt_rgba = mcolors.to_rgba( 

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

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

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

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

819 and not cbook._str_lower_equal( 

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

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

822 gc.set_foreground(ec_rgba, isRGBA=True) 

823 if self.get_sketch_params() is not None: 

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

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

826 

827 marker = self._marker 

828 

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

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

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

832 with cbook._setattr_cm( 

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

834 self.recache() 

835 self._transform_path(subslice) 

836 tpath, affine = (self._get_transformed_path() 

837 .get_transformed_points_and_affine()) 

838 else: 

839 tpath, affine = (self._get_transformed_path() 

840 .get_transformed_points_and_affine()) 

841 

842 if len(tpath.vertices): 

843 # subsample the markers if markevery is not None 

844 markevery = self.get_markevery() 

845 if markevery is not None: 

846 subsampled = _mark_every_path( 

847 markevery, tpath, affine, self.axes) 

848 else: 

849 subsampled = tpath 

850 

851 snap = marker.get_snap_threshold() 

852 if isinstance(snap, Real): 

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

854 gc.set_snap(snap) 

855 gc.set_joinstyle(marker.get_joinstyle()) 

856 gc.set_capstyle(marker.get_capstyle()) 

857 marker_path = marker.get_path() 

858 marker_trans = marker.get_transform() 

859 w = renderer.points_to_pixels(self._markersize) 

860 

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

862 gc.set_linewidth(0) 

863 else: 

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

865 marker_trans = marker_trans.scale(w) 

866 renderer.draw_markers(gc, marker_path, marker_trans, 

867 subsampled, affine.frozen(), 

868 fc_rgba) 

869 

870 alt_marker_path = marker.get_alt_path() 

871 if alt_marker_path: 

872 alt_marker_trans = marker.get_alt_transform() 

873 alt_marker_trans = alt_marker_trans.scale(w) 

874 renderer.draw_markers( 

875 gc, alt_marker_path, alt_marker_trans, subsampled, 

876 affine.frozen(), fcalt_rgba) 

877 

878 gc.restore() 

879 

880 renderer.close_group('line2d') 

881 self.stale = False 

882 

883 def get_antialiased(self): 

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

885 return self._antialiased 

886 

887 def get_color(self): 

888 """ 

889 Return the line color. 

890 

891 See also `~.Line2D.set_color`. 

892 """ 

893 return self._color 

894 

895 def get_drawstyle(self): 

896 """ 

897 Return the drawstyle. 

898 

899 See also `~.Line2D.set_drawstyle`. 

900 """ 

901 return self._drawstyle 

902 

903 def get_gapcolor(self): 

904 """ 

905 Return the line gapcolor. 

906 

907 See also `~.Line2D.set_gapcolor`. 

908 """ 

909 return self._gapcolor 

910 

911 def get_linestyle(self): 

912 """ 

913 Return the linestyle. 

914 

915 See also `~.Line2D.set_linestyle`. 

916 """ 

917 return self._linestyle 

918 

919 def get_linewidth(self): 

920 """ 

921 Return the linewidth in points. 

922 

923 See also `~.Line2D.set_linewidth`. 

924 """ 

925 return self._linewidth 

926 

927 def get_marker(self): 

928 """ 

929 Return the line marker. 

930 

931 See also `~.Line2D.set_marker`. 

932 """ 

933 return self._marker.get_marker() 

934 

935 def get_markeredgecolor(self): 

936 """ 

937 Return the marker edge color. 

938 

939 See also `~.Line2D.set_markeredgecolor`. 

940 """ 

941 mec = self._markeredgecolor 

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

943 if mpl.rcParams['_internal.classic_mode']: 

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

945 return self._color 

946 if (self._marker.is_filled() 

947 and self._marker.get_fillstyle() != 'none'): 

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

949 return self._color 

950 else: 

951 return mec 

952 

953 def get_markeredgewidth(self): 

954 """ 

955 Return the marker edge width in points. 

956 

957 See also `~.Line2D.set_markeredgewidth`. 

958 """ 

959 return self._markeredgewidth 

960 

961 def _get_markerfacecolor(self, alt=False): 

962 if self._marker.get_fillstyle() == 'none': 

963 return 'none' 

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

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

966 return self._color 

967 else: 

968 return fc 

969 

970 def get_markerfacecolor(self): 

971 """ 

972 Return the marker face color. 

973 

974 See also `~.Line2D.set_markerfacecolor`. 

975 """ 

976 return self._get_markerfacecolor(alt=False) 

977 

978 def get_markerfacecoloralt(self): 

979 """ 

980 Return the alternate marker face color. 

981 

982 See also `~.Line2D.set_markerfacecoloralt`. 

983 """ 

984 return self._get_markerfacecolor(alt=True) 

985 

986 def get_markersize(self): 

987 """ 

988 Return the marker size in points. 

989 

990 See also `~.Line2D.set_markersize`. 

991 """ 

992 return self._markersize 

993 

994 def get_data(self, orig=True): 

995 """ 

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

997 

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

999 """ 

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

1001 

1002 def get_xdata(self, orig=True): 

1003 """ 

1004 Return the xdata. 

1005 

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

1007 processed data. 

1008 """ 

1009 if orig: 

1010 return self._xorig 

1011 if self._invalidx: 

1012 self.recache() 

1013 return self._x 

1014 

1015 def get_ydata(self, orig=True): 

1016 """ 

1017 Return the ydata. 

1018 

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

1020 processed data. 

1021 """ 

1022 if orig: 

1023 return self._yorig 

1024 if self._invalidy: 

1025 self.recache() 

1026 return self._y 

1027 

1028 def get_path(self): 

1029 """Return the `~matplotlib.path.Path` associated with this line.""" 

1030 if self._invalidy or self._invalidx: 

1031 self.recache() 

1032 return self._path 

1033 

1034 def get_xydata(self): 

1035 """ 

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

1037 """ 

1038 if self._invalidy or self._invalidx: 

1039 self.recache() 

1040 return self._xy 

1041 

1042 def set_antialiased(self, b): 

1043 """ 

1044 Set whether to use antialiased rendering. 

1045 

1046 Parameters 

1047 ---------- 

1048 b : bool 

1049 """ 

1050 if self._antialiased != b: 

1051 self.stale = True 

1052 self._antialiased = b 

1053 

1054 def set_color(self, color): 

1055 """ 

1056 Set the color of the line. 

1057 

1058 Parameters 

1059 ---------- 

1060 color : color 

1061 """ 

1062 mcolors._check_color_like(color=color) 

1063 self._color = color 

1064 self.stale = True 

1065 

1066 def set_drawstyle(self, drawstyle): 

1067 """ 

1068 Set the drawstyle of the plot. 

1069 

1070 The drawstyle determines how the points are connected. 

1071 

1072 Parameters 

1073 ---------- 

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

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

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

1077 

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

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

1080 location of the step: 

1081 

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

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

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

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

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

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

1088 backward-compatibility. 

1089 

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

1091 """ 

1092 if drawstyle is None: 

1093 drawstyle = 'default' 

1094 _api.check_in_list(self.drawStyles, drawstyle=drawstyle) 

1095 if self._drawstyle != drawstyle: 

1096 self.stale = True 

1097 # invalidate to trigger a recache of the path 

1098 self._invalidx = True 

1099 self._drawstyle = drawstyle 

1100 

1101 def set_gapcolor(self, gapcolor): 

1102 """ 

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

1104 

1105 .. note:: 

1106 

1107 Striped lines are created by drawing two interleaved dashed lines. 

1108 There can be overlaps between those two, which may result in 

1109 artifacts when using transparency. 

1110 

1111 This functionality is experimental and may change. 

1112 

1113 Parameters 

1114 ---------- 

1115 gapcolor : color or None 

1116 The color with which to fill the gaps. If None, the gaps are 

1117 unfilled. 

1118 """ 

1119 if gapcolor is not None: 

1120 mcolors._check_color_like(color=gapcolor) 

1121 self._gapcolor = gapcolor 

1122 self.stale = True 

1123 

1124 def set_linewidth(self, w): 

1125 """ 

1126 Set the line width in points. 

1127 

1128 Parameters 

1129 ---------- 

1130 w : float 

1131 Line width, in points. 

1132 """ 

1133 w = float(w) 

1134 if self._linewidth != w: 

1135 self.stale = True 

1136 self._linewidth = w 

1137 self._dash_pattern = _scale_dashes(*self._unscaled_dash_pattern, w) 

1138 

1139 def set_linestyle(self, ls): 

1140 """ 

1141 Set the linestyle of the line. 

1142 

1143 Parameters 

1144 ---------- 

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

1146 Possible values: 

1147 

1148 - A string: 

1149 

1150 ========================================== ================= 

1151 linestyle description 

1152 ========================================== ================= 

1153 ``'-'`` or ``'solid'`` solid line 

1154 ``'--'`` or ``'dashed'`` dashed line 

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

1156 ``':'`` or ``'dotted'`` dotted line 

1157 ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing 

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

1159 

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

1161 provided:: 

1162 

1163 (offset, onoffseq) 

1164 

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

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

1167 

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

1169 """ 

1170 if isinstance(ls, str): 

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

1172 ls = 'None' 

1173 _api.check_in_list([*self._lineStyles, *ls_mapper_r], ls=ls) 

1174 if ls not in self._lineStyles: 

1175 ls = ls_mapper_r[ls] 

1176 self._linestyle = ls 

1177 else: 

1178 self._linestyle = '--' 

1179 self._unscaled_dash_pattern = _get_dash_pattern(ls) 

1180 self._dash_pattern = _scale_dashes( 

1181 *self._unscaled_dash_pattern, self._linewidth) 

1182 self.stale = True 

1183 

1184 @_docstring.interpd 

1185 def set_marker(self, marker): 

1186 """ 

1187 Set the line marker. 

1188 

1189 Parameters 

1190 ---------- 

1191 marker : marker style string, `~.path.Path` or `~.markers.MarkerStyle` 

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

1193 arguments. 

1194 """ 

1195 self._marker = MarkerStyle(marker, self._marker.get_fillstyle()) 

1196 self.stale = True 

1197 

1198 def _set_markercolor(self, name, has_rcdefault, val): 

1199 if val is None: 

1200 val = mpl.rcParams[f"lines.{name}"] if has_rcdefault else "auto" 

1201 attr = f"_{name}" 

1202 current = getattr(self, attr) 

1203 if current is None: 

1204 self.stale = True 

1205 else: 

1206 neq = current != val 

1207 # Much faster than `np.any(current != val)` if no arrays are used. 

1208 if neq.any() if isinstance(neq, np.ndarray) else neq: 

1209 self.stale = True 

1210 setattr(self, attr, val) 

1211 

1212 def set_markeredgecolor(self, ec): 

1213 """ 

1214 Set the marker edge color. 

1215 

1216 Parameters 

1217 ---------- 

1218 ec : color 

1219 """ 

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

1221 

1222 def set_markerfacecolor(self, fc): 

1223 """ 

1224 Set the marker face color. 

1225 

1226 Parameters 

1227 ---------- 

1228 fc : color 

1229 """ 

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

1231 

1232 def set_markerfacecoloralt(self, fc): 

1233 """ 

1234 Set the alternate marker face color. 

1235 

1236 Parameters 

1237 ---------- 

1238 fc : color 

1239 """ 

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

1241 

1242 def set_markeredgewidth(self, ew): 

1243 """ 

1244 Set the marker edge width in points. 

1245 

1246 Parameters 

1247 ---------- 

1248 ew : float 

1249 Marker edge width, in points. 

1250 """ 

1251 if ew is None: 

1252 ew = mpl.rcParams['lines.markeredgewidth'] 

1253 if self._markeredgewidth != ew: 

1254 self.stale = True 

1255 self._markeredgewidth = ew 

1256 

1257 def set_markersize(self, sz): 

1258 """ 

1259 Set the marker size in points. 

1260 

1261 Parameters 

1262 ---------- 

1263 sz : float 

1264 Marker size, in points. 

1265 """ 

1266 sz = float(sz) 

1267 if self._markersize != sz: 

1268 self.stale = True 

1269 self._markersize = sz 

1270 

1271 def set_xdata(self, x): 

1272 """ 

1273 Set the data array for x. 

1274 

1275 Parameters 

1276 ---------- 

1277 x : 1D array 

1278 """ 

1279 self._xorig = copy.copy(x) 

1280 self._invalidx = True 

1281 self.stale = True 

1282 

1283 def set_ydata(self, y): 

1284 """ 

1285 Set the data array for y. 

1286 

1287 Parameters 

1288 ---------- 

1289 y : 1D array 

1290 """ 

1291 self._yorig = copy.copy(y) 

1292 self._invalidy = True 

1293 self.stale = True 

1294 

1295 def set_dashes(self, seq): 

1296 """ 

1297 Set the dash sequence. 

1298 

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

1300 the length of dashes and spaces in points. 

1301 

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

1303 dashes separated by 2 point spaces. 

1304 

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

1306 filled with a color. 

1307 

1308 Parameters 

1309 ---------- 

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

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

1312 to solid. 

1313 """ 

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

1315 self.set_linestyle('-') 

1316 else: 

1317 self.set_linestyle((0, seq)) 

1318 

1319 def update_from(self, other): 

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

1321 super().update_from(other) 

1322 self._linestyle = other._linestyle 

1323 self._linewidth = other._linewidth 

1324 self._color = other._color 

1325 self._gapcolor = other._gapcolor 

1326 self._markersize = other._markersize 

1327 self._markerfacecolor = other._markerfacecolor 

1328 self._markerfacecoloralt = other._markerfacecoloralt 

1329 self._markeredgecolor = other._markeredgecolor 

1330 self._markeredgewidth = other._markeredgewidth 

1331 self._unscaled_dash_pattern = other._unscaled_dash_pattern 

1332 self._dash_pattern = other._dash_pattern 

1333 self._dashcapstyle = other._dashcapstyle 

1334 self._dashjoinstyle = other._dashjoinstyle 

1335 self._solidcapstyle = other._solidcapstyle 

1336 self._solidjoinstyle = other._solidjoinstyle 

1337 

1338 self._linestyle = other._linestyle 

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

1340 self._drawstyle = other._drawstyle 

1341 

1342 @_docstring.interpd 

1343 def set_dash_joinstyle(self, s): 

1344 """ 

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

1346 

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

1348 

1349 Parameters 

1350 ---------- 

1351 s : `.JoinStyle` or %(JoinStyle)s 

1352 """ 

1353 js = JoinStyle(s) 

1354 if self._dashjoinstyle != js: 

1355 self.stale = True 

1356 self._dashjoinstyle = js 

1357 

1358 @_docstring.interpd 

1359 def set_solid_joinstyle(self, s): 

1360 """ 

1361 How to join segments if the line is solid (not `~Line2D.is_dashed`). 

1362 

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

1364 

1365 Parameters 

1366 ---------- 

1367 s : `.JoinStyle` or %(JoinStyle)s 

1368 """ 

1369 js = JoinStyle(s) 

1370 if self._solidjoinstyle != js: 

1371 self.stale = True 

1372 self._solidjoinstyle = js 

1373 

1374 def get_dash_joinstyle(self): 

1375 """ 

1376 Return the `.JoinStyle` for dashed lines. 

1377 

1378 See also `~.Line2D.set_dash_joinstyle`. 

1379 """ 

1380 return self._dashjoinstyle.name 

1381 

1382 def get_solid_joinstyle(self): 

1383 """ 

1384 Return the `.JoinStyle` for solid lines. 

1385 

1386 See also `~.Line2D.set_solid_joinstyle`. 

1387 """ 

1388 return self._solidjoinstyle.name 

1389 

1390 @_docstring.interpd 

1391 def set_dash_capstyle(self, s): 

1392 """ 

1393 How to draw the end caps if the line is `~Line2D.is_dashed`. 

1394 

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

1396 

1397 Parameters 

1398 ---------- 

1399 s : `.CapStyle` or %(CapStyle)s 

1400 """ 

1401 cs = CapStyle(s) 

1402 if self._dashcapstyle != cs: 

1403 self.stale = True 

1404 self._dashcapstyle = cs 

1405 

1406 @_docstring.interpd 

1407 def set_solid_capstyle(self, s): 

1408 """ 

1409 How to draw the end caps if the line is solid (not `~Line2D.is_dashed`) 

1410 

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

1412 

1413 Parameters 

1414 ---------- 

1415 s : `.CapStyle` or %(CapStyle)s 

1416 """ 

1417 cs = CapStyle(s) 

1418 if self._solidcapstyle != cs: 

1419 self.stale = True 

1420 self._solidcapstyle = cs 

1421 

1422 def get_dash_capstyle(self): 

1423 """ 

1424 Return the `.CapStyle` for dashed lines. 

1425 

1426 See also `~.Line2D.set_dash_capstyle`. 

1427 """ 

1428 return self._dashcapstyle.name 

1429 

1430 def get_solid_capstyle(self): 

1431 """ 

1432 Return the `.CapStyle` for solid lines. 

1433 

1434 See also `~.Line2D.set_solid_capstyle`. 

1435 """ 

1436 return self._solidcapstyle.name 

1437 

1438 def is_dashed(self): 

1439 """ 

1440 Return whether line has a dashed linestyle. 

1441 

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

1443 ``onoffseq`` directly. 

1444 

1445 See also `~.Line2D.set_linestyle`. 

1446 """ 

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

1448 

1449 

1450class _AxLine(Line2D): 

1451 """ 

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

1453 transform at draw time. 

1454 """ 

1455 

1456 def __init__(self, xy1, xy2, slope, **kwargs): 

1457 super().__init__([0, 1], [0, 1], **kwargs) 

1458 

1459 if (xy2 is None and slope is None or 

1460 xy2 is not None and slope is not None): 

1461 raise TypeError( 

1462 "Exactly one of 'xy2' and 'slope' must be given") 

1463 

1464 self._slope = slope 

1465 self._xy1 = xy1 

1466 self._xy2 = xy2 

1467 

1468 def get_transform(self): 

1469 ax = self.axes 

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

1471 

1472 if self._xy2 is not None: 

1473 # two points were given 

1474 (x1, y1), (x2, y2) = \ 

1475 points_transform.transform([self._xy1, self._xy2]) 

1476 dx = x2 - x1 

1477 dy = y2 - y1 

1478 if np.allclose(x1, x2): 

1479 if np.allclose(y1, y2): 

1480 raise ValueError( 

1481 f"Cannot draw a line through two identical points " 

1482 f"(x={(x1, x2)}, y={(y1, y2)})") 

1483 slope = np.inf 

1484 else: 

1485 slope = dy / dx 

1486 else: 

1487 # one point and a slope were given 

1488 x1, y1 = points_transform.transform(self._xy1) 

1489 slope = self._slope 

1490 (vxlo, vylo), (vxhi, vyhi) = ax.transScale.transform(ax.viewLim) 

1491 # General case: find intersections with view limits in either 

1492 # direction, and draw between the middle two points. 

1493 if np.isclose(slope, 0): 

1494 start = vxlo, y1 

1495 stop = vxhi, y1 

1496 elif np.isinf(slope): 

1497 start = x1, vylo 

1498 stop = x1, vyhi 

1499 else: 

1500 _, start, stop, _ = sorted([ 

1501 (vxlo, y1 + (vxlo - x1) * slope), 

1502 (vxhi, y1 + (vxhi - x1) * slope), 

1503 (x1 + (vylo - y1) / slope, vylo), 

1504 (x1 + (vyhi - y1) / slope, vyhi), 

1505 ]) 

1506 return (BboxTransformTo(Bbox([start, stop])) 

1507 + ax.transLimits + ax.transAxes) 

1508 

1509 def draw(self, renderer): 

1510 self._transformed_path = None # Force regen. 

1511 super().draw(renderer) 

1512 

1513 

1514class VertexSelector: 

1515 """ 

1516 Manage the callbacks to maintain a list of selected vertices for `.Line2D`. 

1517 Derived classes should override the `process_selected` method to do 

1518 something with the picks. 

1519 

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

1521 

1522 import numpy as np 

1523 import matplotlib.pyplot as plt 

1524 import matplotlib.lines as lines 

1525 

1526 class HighlightSelected(lines.VertexSelector): 

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

1528 super().__init__(line) 

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

1530 

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

1532 self.markers.set_data(xs, ys) 

1533 self.canvas.draw() 

1534 

1535 fig, ax = plt.subplots() 

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

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

1538 

1539 selector = HighlightSelected(line) 

1540 plt.show() 

1541 """ 

1542 

1543 def __init__(self, line): 

1544 """ 

1545 Parameters 

1546 ---------- 

1547 line : `.Line2D` 

1548 The line must already have been added to an `~.axes.Axes` and must 

1549 have its picker property set. 

1550 """ 

1551 if line.axes is None: 

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

1553 if line.get_picker() is None: 

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

1555 'of the line') 

1556 self.axes = line.axes 

1557 self.line = line 

1558 self.cid = self.canvas.callbacks._connect_picklable( 

1559 'pick_event', self.onpick) 

1560 self.ind = set() 

1561 

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

1563 

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

1565 """ 

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

1567 

1568 Parameters 

1569 ---------- 

1570 ind : list of int 

1571 The indices of the selected vertices. 

1572 xs, ys : array-like 

1573 The coordinates of the selected vertices. 

1574 """ 

1575 pass 

1576 

1577 def onpick(self, event): 

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

1579 if event.artist is not self.line: 

1580 return 

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

1582 ind = sorted(self.ind) 

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

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

1585 

1586 

1587lineStyles = Line2D._lineStyles 

1588lineMarkers = MarkerStyle.markers 

1589drawStyles = Line2D.drawStyles 

1590fillStyles = MarkerStyle.fillstyles