Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from collections import OrderedDict 

2import itertools 

3import logging 

4import math 

5from numbers import Real 

6from operator import attrgetter 

7import types 

8 

9import numpy as np 

10 

11import matplotlib as mpl 

12from matplotlib import cbook, rcParams 

13from matplotlib.cbook import _OrderedSet, _check_1d, index_of 

14from matplotlib import docstring 

15import matplotlib.colors as mcolors 

16import matplotlib.lines as mlines 

17import matplotlib.patches as mpatches 

18import matplotlib.artist as martist 

19import matplotlib.transforms as mtransforms 

20import matplotlib.ticker as mticker 

21import matplotlib.axis as maxis 

22import matplotlib.spines as mspines 

23import matplotlib.font_manager as font_manager 

24import matplotlib.text as mtext 

25import matplotlib.image as mimage 

26from matplotlib.rcsetup import cycler, validate_axisbelow 

27 

28_log = logging.getLogger(__name__) 

29 

30 

31def _process_plot_format(fmt): 

32 """ 

33 Convert a MATLAB style color/line style format string to a (*linestyle*, 

34 *marker*, *color*) tuple. 

35 

36 Example format strings include: 

37 

38 * 'ko': black circles 

39 * '.b': blue dots 

40 * 'r--': red dashed lines 

41 * 'C2--': the third color in the color cycle, dashed lines 

42 

43 See Also 

44 -------- 

45 matplotlib.Line2D.lineStyles, matplotlib.colors.cnames 

46 All possible styles and color format strings. 

47 """ 

48 

49 linestyle = None 

50 marker = None 

51 color = None 

52 

53 # Is fmt just a colorspec? 

54 try: 

55 color = mcolors.to_rgba(fmt) 

56 

57 # We need to differentiate grayscale '1.0' from tri_down marker '1' 

58 try: 

59 fmtint = str(int(fmt)) 

60 except ValueError: 

61 return linestyle, marker, color # Yes 

62 else: 

63 if fmt != fmtint: 

64 # user definitely doesn't want tri_down marker 

65 return linestyle, marker, color # Yes 

66 else: 

67 # ignore converted color 

68 color = None 

69 except ValueError: 

70 pass # No, not just a color. 

71 

72 i = 0 

73 while i < len(fmt): 

74 c = fmt[i] 

75 if fmt[i:i+2] in mlines.lineStyles: # First, the two-char styles. 

76 if linestyle is not None: 

77 raise ValueError( 

78 'Illegal format string "%s"; two linestyle symbols' % fmt) 

79 linestyle = fmt[i:i+2] 

80 i += 2 

81 elif c in mlines.lineStyles: 

82 if linestyle is not None: 

83 raise ValueError( 

84 'Illegal format string "%s"; two linestyle symbols' % fmt) 

85 linestyle = c 

86 i += 1 

87 elif c in mlines.lineMarkers: 

88 if marker is not None: 

89 raise ValueError( 

90 'Illegal format string "%s"; two marker symbols' % fmt) 

91 marker = c 

92 i += 1 

93 elif c in mcolors.get_named_colors_mapping(): 

94 if color is not None: 

95 raise ValueError( 

96 'Illegal format string "%s"; two color symbols' % fmt) 

97 color = c 

98 i += 1 

99 elif c == 'C' and i < len(fmt) - 1: 

100 color_cycle_number = int(fmt[i + 1]) 

101 color = mcolors.to_rgba("C{}".format(color_cycle_number)) 

102 i += 2 

103 else: 

104 raise ValueError( 

105 'Unrecognized character %c in format string' % c) 

106 

107 if linestyle is None and marker is None: 

108 linestyle = rcParams['lines.linestyle'] 

109 if linestyle is None: 

110 linestyle = 'None' 

111 if marker is None: 

112 marker = 'None' 

113 

114 return linestyle, marker, color 

115 

116 

117class _process_plot_var_args: 

118 """ 

119 Process variable length arguments to the plot command, so that 

120 plot commands like the following are supported:: 

121 

122 plot(t, s) 

123 plot(t1, s1, t2, s2) 

124 plot(t1, s1, 'ko', t2, s2) 

125 plot(t1, s1, 'ko', t2, s2, 'r--', t3, e3) 

126 

127 an arbitrary number of *x*, *y*, *fmt* are allowed 

128 """ 

129 def __init__(self, axes, command='plot'): 

130 self.axes = axes 

131 self.command = command 

132 self.set_prop_cycle() 

133 

134 def __getstate__(self): 

135 # note: it is not possible to pickle a generator (and thus a cycler). 

136 return {'axes': self.axes, 'command': self.command} 

137 

138 def __setstate__(self, state): 

139 self.__dict__ = state.copy() 

140 self.set_prop_cycle() 

141 

142 def set_prop_cycle(self, *args, **kwargs): 

143 # Can't do `args == (None,)` as that crashes cycler. 

144 if not (args or kwargs) or (len(args) == 1 and args[0] is None): 

145 prop_cycler = rcParams['axes.prop_cycle'] 

146 else: 

147 prop_cycler = cycler(*args, **kwargs) 

148 

149 self.prop_cycler = itertools.cycle(prop_cycler) 

150 # This should make a copy 

151 self._prop_keys = prop_cycler.keys 

152 

153 def __call__(self, *args, **kwargs): 

154 self.axes._process_unit_info(kwargs=kwargs) 

155 

156 for pos_only in "xy": 

157 if pos_only in kwargs: 

158 raise TypeError("{} got an unexpected keyword argument {!r}" 

159 .format(self.command, pos_only)) 

160 

161 if not args: 

162 return 

163 

164 # Process the 'data' kwarg. 

165 data = kwargs.pop("data", None) 

166 if data is not None: 

167 replaced = [mpl._replacer(data, arg) for arg in args] 

168 if len(args) == 1: 

169 label_namer_idx = 0 

170 elif len(args) == 2: # Can be x, y or y, c. 

171 # Figure out what the second argument is. 

172 # 1) If the second argument cannot be a format shorthand, the 

173 # second argument is the label_namer. 

174 # 2) Otherwise (it could have been a format shorthand), 

175 # a) if we did perform a substitution, emit a warning, and 

176 # use it as label_namer. 

177 # b) otherwise, it is indeed a format shorthand; use the 

178 # first argument as label_namer. 

179 try: 

180 _process_plot_format(args[1]) 

181 except ValueError: # case 1) 

182 label_namer_idx = 1 

183 else: 

184 if replaced[1] is not args[1]: # case 2a) 

185 cbook._warn_external( 

186 f"Second argument {args[1]!r} is ambiguous: could " 

187 f"be a format string but is in 'data'; using as " 

188 f"data. If it was intended as data, set the " 

189 f"format string to an empty string to suppress " 

190 f"this warning. If it was intended as a format " 

191 f"string, explicitly pass the x-values as well. " 

192 f"Alternatively, rename the entry in 'data'.", 

193 RuntimeWarning) 

194 label_namer_idx = 1 

195 else: # case 2b) 

196 label_namer_idx = 0 

197 elif len(args) == 3: 

198 label_namer_idx = 1 

199 else: 

200 raise ValueError( 

201 "Using arbitrary long args with data is not supported due " 

202 "to ambiguity of arguments; use multiple plotting calls " 

203 "instead") 

204 if kwargs.get("label") is None: 

205 kwargs["label"] = mpl._label_from_arg( 

206 replaced[label_namer_idx], args[label_namer_idx]) 

207 args = replaced 

208 

209 # Repeatedly grab (x, y) or (x, y, format) from the front of args and 

210 # massage them into arguments to plot() or fill(). 

211 while args: 

212 this, args = args[:2], args[2:] 

213 if args and isinstance(args[0], str): 

214 this += args[0], 

215 args = args[1:] 

216 yield from self._plot_args(this, kwargs) 

217 

218 def get_next_color(self): 

219 """Return the next color in the cycle.""" 

220 if 'color' not in self._prop_keys: 

221 return 'k' 

222 return next(self.prop_cycler)['color'] 

223 

224 def _getdefaults(self, ignore, kw): 

225 """ 

226 If some keys in the property cycle (excluding those in the set 

227 *ignore*) are absent or set to None in the dict *kw*, return a copy 

228 of the next entry in the property cycle, excluding keys in *ignore*. 

229 Otherwise, don't advance the property cycle, and return an empty dict. 

230 """ 

231 prop_keys = self._prop_keys - ignore 

232 if any(kw.get(k, None) is None for k in prop_keys): 

233 # Need to copy this dictionary or else the next time around 

234 # in the cycle, the dictionary could be missing entries. 

235 default_dict = next(self.prop_cycler).copy() 

236 for p in ignore: 

237 default_dict.pop(p, None) 

238 else: 

239 default_dict = {} 

240 return default_dict 

241 

242 def _setdefaults(self, defaults, kw): 

243 """ 

244 Add to the dict *kw* the entries in the dict *default* that are absent 

245 or set to None in *kw*. 

246 """ 

247 for k in defaults: 

248 if kw.get(k, None) is None: 

249 kw[k] = defaults[k] 

250 

251 def _makeline(self, x, y, kw, kwargs): 

252 kw = {**kw, **kwargs} # Don't modify the original kw. 

253 default_dict = self._getdefaults(set(), kw) 

254 self._setdefaults(default_dict, kw) 

255 seg = mlines.Line2D(x, y, **kw) 

256 return seg 

257 

258 def _makefill(self, x, y, kw, kwargs): 

259 # Polygon doesn't directly support unitized inputs. 

260 x = self.axes.convert_xunits(x) 

261 y = self.axes.convert_yunits(y) 

262 

263 kw = kw.copy() # Don't modify the original kw. 

264 kwargs = kwargs.copy() 

265 

266 # Ignore 'marker'-related properties as they aren't Polygon 

267 # properties, but they are Line2D properties, and so they are 

268 # likely to appear in the default cycler construction. 

269 # This is done here to the defaults dictionary as opposed to the 

270 # other two dictionaries because we do want to capture when a 

271 # *user* explicitly specifies a marker which should be an error. 

272 # We also want to prevent advancing the cycler if there are no 

273 # defaults needed after ignoring the given properties. 

274 ignores = {'marker', 'markersize', 'markeredgecolor', 

275 'markerfacecolor', 'markeredgewidth'} 

276 # Also ignore anything provided by *kwargs*. 

277 for k, v in kwargs.items(): 

278 if v is not None: 

279 ignores.add(k) 

280 

281 # Only using the first dictionary to use as basis 

282 # for getting defaults for back-compat reasons. 

283 # Doing it with both seems to mess things up in 

284 # various places (probably due to logic bugs elsewhere). 

285 default_dict = self._getdefaults(ignores, kw) 

286 self._setdefaults(default_dict, kw) 

287 

288 # Looks like we don't want "color" to be interpreted to 

289 # mean both facecolor and edgecolor for some reason. 

290 # So the "kw" dictionary is thrown out, and only its 

291 # 'color' value is kept and translated as a 'facecolor'. 

292 # This design should probably be revisited as it increases 

293 # complexity. 

294 facecolor = kw.get('color', None) 

295 

296 # Throw out 'color' as it is now handled as a facecolor 

297 default_dict.pop('color', None) 

298 

299 # To get other properties set from the cycler 

300 # modify the kwargs dictionary. 

301 self._setdefaults(default_dict, kwargs) 

302 

303 seg = mpatches.Polygon(np.column_stack((x, y)), 

304 facecolor=facecolor, 

305 fill=kwargs.get('fill', True), 

306 closed=kw['closed']) 

307 seg.set(**kwargs) 

308 return seg 

309 

310 def _plot_args(self, tup, kwargs): 

311 if len(tup) > 1 and isinstance(tup[-1], str): 

312 linestyle, marker, color = _process_plot_format(tup[-1]) 

313 tup = tup[:-1] 

314 elif len(tup) == 3: 

315 raise ValueError('third arg must be a format string') 

316 else: 

317 linestyle, marker, color = None, None, None 

318 

319 # Don't allow any None value; these would be up-converted to one 

320 # element array of None which causes problems downstream. 

321 if any(v is None for v in tup): 

322 raise ValueError("x, y, and format string must not be None") 

323 

324 kw = {} 

325 for k, v in zip(('linestyle', 'marker', 'color'), 

326 (linestyle, marker, color)): 

327 if v is not None: 

328 kw[k] = v 

329 

330 if len(tup) == 2: 

331 x = _check_1d(tup[0]) 

332 y = _check_1d(tup[-1]) 

333 else: 

334 x, y = index_of(tup[-1]) 

335 

336 if self.axes.xaxis is not None: 

337 self.axes.xaxis.update_units(x) 

338 if self.axes.yaxis is not None: 

339 self.axes.yaxis.update_units(y) 

340 

341 if x.shape[0] != y.shape[0]: 

342 raise ValueError(f"x and y must have same first dimension, but " 

343 f"have shapes {x.shape} and {y.shape}") 

344 if x.ndim > 2 or y.ndim > 2: 

345 raise ValueError(f"x and y can be no greater than 2-D, but have " 

346 f"shapes {x.shape} and {y.shape}") 

347 if x.ndim == 1: 

348 x = x[:, np.newaxis] 

349 if y.ndim == 1: 

350 y = y[:, np.newaxis] 

351 

352 if self.command == 'plot': 

353 func = self._makeline 

354 else: 

355 kw['closed'] = kwargs.get('closed', True) 

356 func = self._makefill 

357 

358 ncx, ncy = x.shape[1], y.shape[1] 

359 if ncx > 1 and ncy > 1 and ncx != ncy: 

360 cbook.warn_deprecated( 

361 "2.2", message="cycling among columns of inputs with " 

362 "non-matching shapes is deprecated.") 

363 return [func(x[:, j % ncx], y[:, j % ncy], kw, kwargs) 

364 for j in range(max(ncx, ncy))] 

365 

366 

367class _AxesBase(martist.Artist): 

368 name = "rectilinear" 

369 

370 _shared_x_axes = cbook.Grouper() 

371 _shared_y_axes = cbook.Grouper() 

372 _twinned_axes = cbook.Grouper() 

373 

374 def __str__(self): 

375 return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format( 

376 type(self).__name__, self._position.bounds) 

377 

378 def __init__(self, fig, rect, 

379 facecolor=None, # defaults to rc axes.facecolor 

380 frameon=True, 

381 sharex=None, # use Axes instance's xaxis info 

382 sharey=None, # use Axes instance's yaxis info 

383 label='', 

384 xscale=None, 

385 yscale=None, 

386 **kwargs 

387 ): 

388 """ 

389 Build an axes in a figure. 

390 

391 Parameters 

392 ---------- 

393 fig : `~matplotlib.figure.Figure` 

394 The axes is build in the `.Figure` *fig*. 

395 

396 rect : [left, bottom, width, height] 

397 The axes is build in the rectangle *rect*. *rect* is in 

398 `.Figure` coordinates. 

399 

400 sharex, sharey : `~.axes.Axes`, optional 

401 The x or y `~.matplotlib.axis` is shared with the x or 

402 y axis in the input `~.axes.Axes`. 

403 

404 frameon : bool, optional 

405 True means that the axes frame is visible. 

406 

407 **kwargs 

408 Other optional keyword arguments: 

409 

410 %(Axes)s 

411 

412 Returns 

413 ------- 

414 axes : `~.axes.Axes` 

415 The new `~.axes.Axes` object. 

416 """ 

417 

418 martist.Artist.__init__(self) 

419 if isinstance(rect, mtransforms.Bbox): 

420 self._position = rect 

421 else: 

422 self._position = mtransforms.Bbox.from_bounds(*rect) 

423 if self._position.width < 0 or self._position.height < 0: 

424 raise ValueError('Width and height specified must be non-negative') 

425 self._originalPosition = self._position.frozen() 

426 self.axes = self 

427 self._aspect = 'auto' 

428 self._adjustable = 'box' 

429 self._anchor = 'C' 

430 self._stale_viewlim_x = False 

431 self._stale_viewlim_y = False 

432 self._sharex = sharex 

433 self._sharey = sharey 

434 if sharex is not None: 

435 self._shared_x_axes.join(self, sharex) 

436 if sharey is not None: 

437 self._shared_y_axes.join(self, sharey) 

438 self.set_label(label) 

439 self.set_figure(fig) 

440 

441 self.set_axes_locator(kwargs.get("axes_locator", None)) 

442 

443 self.spines = self._gen_axes_spines() 

444 

445 # this call may differ for non-sep axes, e.g., polar 

446 self._init_axis() 

447 if facecolor is None: 

448 facecolor = rcParams['axes.facecolor'] 

449 self._facecolor = facecolor 

450 self._frameon = frameon 

451 self.set_axisbelow(rcParams['axes.axisbelow']) 

452 

453 self._rasterization_zorder = None 

454 self.cla() 

455 

456 # funcs used to format x and y - fall back on major formatters 

457 self.fmt_xdata = None 

458 self.fmt_ydata = None 

459 

460 self.set_navigate(True) 

461 self.set_navigate_mode(None) 

462 

463 if xscale: 

464 self.set_xscale(xscale) 

465 if yscale: 

466 self.set_yscale(yscale) 

467 

468 self.update(kwargs) 

469 

470 if self.xaxis is not None: 

471 self._xcid = self.xaxis.callbacks.connect( 

472 'units finalize', lambda: self._on_units_changed(scalex=True)) 

473 

474 if self.yaxis is not None: 

475 self._ycid = self.yaxis.callbacks.connect( 

476 'units finalize', lambda: self._on_units_changed(scaley=True)) 

477 

478 self.tick_params( 

479 top=rcParams['xtick.top'] and rcParams['xtick.minor.top'], 

480 bottom=rcParams['xtick.bottom'] and rcParams['xtick.minor.bottom'], 

481 labeltop=(rcParams['xtick.labeltop'] and 

482 rcParams['xtick.minor.top']), 

483 labelbottom=(rcParams['xtick.labelbottom'] and 

484 rcParams['xtick.minor.bottom']), 

485 left=rcParams['ytick.left'] and rcParams['ytick.minor.left'], 

486 right=rcParams['ytick.right'] and rcParams['ytick.minor.right'], 

487 labelleft=(rcParams['ytick.labelleft'] and 

488 rcParams['ytick.minor.left']), 

489 labelright=(rcParams['ytick.labelright'] and 

490 rcParams['ytick.minor.right']), 

491 which='minor') 

492 

493 self.tick_params( 

494 top=rcParams['xtick.top'] and rcParams['xtick.major.top'], 

495 bottom=rcParams['xtick.bottom'] and rcParams['xtick.major.bottom'], 

496 labeltop=(rcParams['xtick.labeltop'] and 

497 rcParams['xtick.major.top']), 

498 labelbottom=(rcParams['xtick.labelbottom'] and 

499 rcParams['xtick.major.bottom']), 

500 left=rcParams['ytick.left'] and rcParams['ytick.major.left'], 

501 right=rcParams['ytick.right'] and rcParams['ytick.major.right'], 

502 labelleft=(rcParams['ytick.labelleft'] and 

503 rcParams['ytick.major.left']), 

504 labelright=(rcParams['ytick.labelright'] and 

505 rcParams['ytick.major.right']), 

506 which='major') 

507 

508 self._layoutbox = None 

509 self._poslayoutbox = None 

510 

511 def __getstate__(self): 

512 # The renderer should be re-created by the figure, and then cached at 

513 # that point. 

514 state = super().__getstate__() 

515 for key in ['_layoutbox', '_poslayoutbox']: 

516 state[key] = None 

517 # Prune the sharing & twinning info to only contain the current group. 

518 for grouper_name in [ 

519 '_shared_x_axes', '_shared_y_axes', '_twinned_axes']: 

520 grouper = getattr(self, grouper_name) 

521 state[grouper_name] = (grouper.get_siblings(self) 

522 if self in grouper else None) 

523 return state 

524 

525 def __setstate__(self, state): 

526 # Merge the grouping info back into the global groupers. 

527 for grouper_name in [ 

528 '_shared_x_axes', '_shared_y_axes', '_twinned_axes']: 

529 siblings = state.pop(grouper_name) 

530 if siblings: 

531 getattr(self, grouper_name).join(*siblings) 

532 self.__dict__ = state 

533 self._stale = True 

534 

535 def get_window_extent(self, *args, **kwargs): 

536 """ 

537 Return the axes bounding box in display space; *args* and *kwargs* 

538 are empty. 

539 

540 This bounding box does not include the spines, ticks, ticklables, 

541 or other labels. For a bounding box including these elements use 

542 `~matplotlib.axes.Axes.get_tightbbox`. 

543 

544 See Also 

545 -------- 

546 matplotlib.axes.Axes.get_tightbbox 

547 matplotlib.axis.Axis.get_tightbbox 

548 matplotlib.spines.get_window_extent 

549 

550 """ 

551 return self.bbox 

552 

553 def _init_axis(self): 

554 "move this out of __init__ because non-separable axes don't use it" 

555 self.xaxis = maxis.XAxis(self) 

556 self.spines['bottom'].register_axis(self.xaxis) 

557 self.spines['top'].register_axis(self.xaxis) 

558 self.yaxis = maxis.YAxis(self) 

559 self.spines['left'].register_axis(self.yaxis) 

560 self.spines['right'].register_axis(self.yaxis) 

561 self._update_transScale() 

562 

563 def set_figure(self, fig): 

564 """ 

565 Set the `.Figure` for this `.Axes`. 

566 

567 Parameters 

568 ---------- 

569 fig : `.Figure` 

570 """ 

571 martist.Artist.set_figure(self, fig) 

572 

573 self.bbox = mtransforms.TransformedBbox(self._position, 

574 fig.transFigure) 

575 # these will be updated later as data is added 

576 self.dataLim = mtransforms.Bbox.null() 

577 self._viewLim = mtransforms.Bbox.unit() 

578 self.transScale = mtransforms.TransformWrapper( 

579 mtransforms.IdentityTransform()) 

580 

581 self._set_lim_and_transforms() 

582 

583 def _unstale_viewLim(self): 

584 # We should arrange to store this information once per share-group 

585 # instead of on every axis. 

586 scalex = any(ax._stale_viewlim_x 

587 for ax in self._shared_x_axes.get_siblings(self)) 

588 scaley = any(ax._stale_viewlim_y 

589 for ax in self._shared_y_axes.get_siblings(self)) 

590 if scalex or scaley: 

591 for ax in self._shared_x_axes.get_siblings(self): 

592 ax._stale_viewlim_x = False 

593 for ax in self._shared_y_axes.get_siblings(self): 

594 ax._stale_viewlim_y = False 

595 self.autoscale_view(scalex=scalex, scaley=scaley) 

596 

597 @property 

598 def viewLim(self): 

599 self._unstale_viewLim() 

600 return self._viewLim 

601 

602 # API could be better, right now this is just to match the old calls to 

603 # autoscale_view() after each plotting method. 

604 def _request_autoscale_view(self, tight=None, scalex=True, scaley=True): 

605 if tight is not None: 

606 self._tight = tight 

607 if scalex: 

608 self._stale_viewlim_x = True # Else keep old state. 

609 if scaley: 

610 self._stale_viewlim_y = True 

611 

612 def _set_lim_and_transforms(self): 

613 """ 

614 Set the *_xaxis_transform*, *_yaxis_transform*, *transScale*, 

615 *transData*, *transLimits* and *transAxes* transformations. 

616 

617 .. note:: 

618 

619 This method is primarily used by rectilinear projections of the 

620 `~matplotlib.axes.Axes` class, and is meant to be overridden by 

621 new kinds of projection axes that need different transformations 

622 and limits. (See `~matplotlib.projections.polar.PolarAxes` for an 

623 example.) 

624 """ 

625 self.transAxes = mtransforms.BboxTransformTo(self.bbox) 

626 

627 # Transforms the x and y axis separately by a scale factor. 

628 # It is assumed that this part will have non-linear components 

629 # (e.g., for a log scale). 

630 self.transScale = mtransforms.TransformWrapper( 

631 mtransforms.IdentityTransform()) 

632 

633 # An affine transformation on the data, generally to limit the 

634 # range of the axes 

635 self.transLimits = mtransforms.BboxTransformFrom( 

636 mtransforms.TransformedBbox(self._viewLim, self.transScale)) 

637 

638 # The parentheses are important for efficiency here -- they 

639 # group the last two (which are usually affines) separately 

640 # from the first (which, with log-scaling can be non-affine). 

641 self.transData = self.transScale + (self.transLimits + self.transAxes) 

642 

643 self._xaxis_transform = mtransforms.blended_transform_factory( 

644 self.transData, self.transAxes) 

645 self._yaxis_transform = mtransforms.blended_transform_factory( 

646 self.transAxes, self.transData) 

647 

648 def get_xaxis_transform(self, which='grid'): 

649 """ 

650 Get the transformation used for drawing x-axis labels, ticks 

651 and gridlines. The x-direction is in data coordinates and the 

652 y-direction is in axis coordinates. 

653 

654 .. note:: 

655 

656 This transformation is primarily used by the 

657 `~matplotlib.axis.Axis` class, and is meant to be 

658 overridden by new kinds of projections that may need to 

659 place axis elements in different locations. 

660 """ 

661 if which == 'grid': 

662 return self._xaxis_transform 

663 elif which == 'tick1': 

664 # for cartesian projection, this is bottom spine 

665 return self.spines['bottom'].get_spine_transform() 

666 elif which == 'tick2': 

667 # for cartesian projection, this is top spine 

668 return self.spines['top'].get_spine_transform() 

669 else: 

670 raise ValueError('unknown value for which') 

671 

672 def get_xaxis_text1_transform(self, pad_points): 

673 """ 

674 Returns 

675 ------- 

676 transform : Transform 

677 The transform used for drawing x-axis labels, which will add 

678 *pad_points* of padding (in points) between the axes and the label. 

679 The x-direction is in data coordinates and the y-direction is in 

680 axis corrdinates 

681 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} 

682 The text vertical alignment. 

683 halign : {'center', 'left', 'right'} 

684 The text horizontal alignment. 

685 

686 Notes 

687 ----- 

688 This transformation is primarily used by the `~matplotlib.axis.Axis` 

689 class, and is meant to be overridden by new kinds of projections that 

690 may need to place axis elements in different locations. 

691 """ 

692 labels_align = rcParams["xtick.alignment"] 

693 return (self.get_xaxis_transform(which='tick1') + 

694 mtransforms.ScaledTranslation(0, -1 * pad_points / 72, 

695 self.figure.dpi_scale_trans), 

696 "top", labels_align) 

697 

698 def get_xaxis_text2_transform(self, pad_points): 

699 """ 

700 Returns 

701 ------- 

702 transform : Transform 

703 The transform used for drawing secondary x-axis labels, which will 

704 add *pad_points* of padding (in points) between the axes and the 

705 label. The x-direction is in data coordinates and the y-direction 

706 is in axis corrdinates 

707 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} 

708 The text vertical alignment. 

709 halign : {'center', 'left', 'right'} 

710 The text horizontal alignment. 

711 

712 Notes 

713 ----- 

714 This transformation is primarily used by the `~matplotlib.axis.Axis` 

715 class, and is meant to be overridden by new kinds of projections that 

716 may need to place axis elements in different locations. 

717 """ 

718 labels_align = rcParams["xtick.alignment"] 

719 return (self.get_xaxis_transform(which='tick2') + 

720 mtransforms.ScaledTranslation(0, pad_points / 72, 

721 self.figure.dpi_scale_trans), 

722 "bottom", labels_align) 

723 

724 def get_yaxis_transform(self, which='grid'): 

725 """ 

726 Get the transformation used for drawing y-axis labels, ticks 

727 and gridlines. The x-direction is in axis coordinates and the 

728 y-direction is in data coordinates. 

729 

730 .. note:: 

731 

732 This transformation is primarily used by the 

733 `~matplotlib.axis.Axis` class, and is meant to be 

734 overridden by new kinds of projections that may need to 

735 place axis elements in different locations. 

736 """ 

737 if which == 'grid': 

738 return self._yaxis_transform 

739 elif which == 'tick1': 

740 # for cartesian projection, this is bottom spine 

741 return self.spines['left'].get_spine_transform() 

742 elif which == 'tick2': 

743 # for cartesian projection, this is top spine 

744 return self.spines['right'].get_spine_transform() 

745 else: 

746 raise ValueError('unknown value for which') 

747 

748 def get_yaxis_text1_transform(self, pad_points): 

749 """ 

750 Returns 

751 ------- 

752 transform : Transform 

753 The transform used for drawing y-axis labels, which will add 

754 *pad_points* of padding (in points) between the axes and the label. 

755 The x-direction is in axis coordinates and the y-direction is in 

756 data corrdinates 

757 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} 

758 The text vertical alignment. 

759 halign : {'center', 'left', 'right'} 

760 The text horizontal alignment. 

761 

762 Notes 

763 ----- 

764 This transformation is primarily used by the `~matplotlib.axis.Axis` 

765 class, and is meant to be overridden by new kinds of projections that 

766 may need to place axis elements in different locations. 

767 """ 

768 labels_align = rcParams["ytick.alignment"] 

769 return (self.get_yaxis_transform(which='tick1') + 

770 mtransforms.ScaledTranslation(-1 * pad_points / 72, 0, 

771 self.figure.dpi_scale_trans), 

772 labels_align, "right") 

773 

774 def get_yaxis_text2_transform(self, pad_points): 

775 """ 

776 Returns 

777 ------- 

778 transform : Transform 

779 The transform used for drawing secondart y-axis labels, which will 

780 add *pad_points* of padding (in points) between the axes and the 

781 label. The x-direction is in axis coordinates and the y-direction 

782 is in data corrdinates 

783 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} 

784 The text vertical alignment. 

785 halign : {'center', 'left', 'right'} 

786 The text horizontal alignment. 

787 

788 Notes 

789 ----- 

790 This transformation is primarily used by the `~matplotlib.axis.Axis` 

791 class, and is meant to be overridden by new kinds of projections that 

792 may need to place axis elements in different locations. 

793 """ 

794 labels_align = rcParams["ytick.alignment"] 

795 return (self.get_yaxis_transform(which='tick2') + 

796 mtransforms.ScaledTranslation(pad_points / 72, 0, 

797 self.figure.dpi_scale_trans), 

798 labels_align, "left") 

799 

800 def _update_transScale(self): 

801 self.transScale.set( 

802 mtransforms.blended_transform_factory( 

803 self.xaxis.get_transform(), self.yaxis.get_transform())) 

804 for line in getattr(self, "lines", []): # Not set during init. 

805 try: 

806 line._transformed_path.invalidate() 

807 except AttributeError: 

808 pass 

809 

810 def get_position(self, original=False): 

811 """ 

812 Get a copy of the axes rectangle as a `.Bbox`. 

813 

814 Parameters 

815 ---------- 

816 original : bool 

817 If ``True``, return the original position. Otherwise return the 

818 active position. For an explanation of the positions see 

819 `.set_position`. 

820 

821 Returns 

822 ------- 

823 pos : `.Bbox` 

824 

825 """ 

826 if original: 

827 return self._originalPosition.frozen() 

828 else: 

829 locator = self.get_axes_locator() 

830 if not locator: 

831 self.apply_aspect() 

832 return self._position.frozen() 

833 

834 def set_position(self, pos, which='both'): 

835 """ 

836 Set the axes position. 

837 

838 Axes have two position attributes. The 'original' position is the 

839 position allocated for the Axes. The 'active' position is the 

840 position the Axes is actually drawn at. These positions are usually 

841 the same unless a fixed aspect is set to the Axes. See `.set_aspect` 

842 for details. 

843 

844 Parameters 

845 ---------- 

846 pos : [left, bottom, width, height] or `~matplotlib.transforms.Bbox` 

847 The new position of the in `.Figure` coordinates. 

848 

849 which : {'both', 'active', 'original'}, optional 

850 Determines which position variables to change. 

851 

852 """ 

853 self._set_position(pos, which=which) 

854 # because this is being called externally to the library we 

855 # zero the constrained layout parts. 

856 self._layoutbox = None 

857 self._poslayoutbox = None 

858 

859 def _set_position(self, pos, which='both'): 

860 """ 

861 private version of set_position. Call this internally 

862 to get the same functionality of `get_position`, but not 

863 to take the axis out of the constrained_layout 

864 hierarchy. 

865 """ 

866 if not isinstance(pos, mtransforms.BboxBase): 

867 pos = mtransforms.Bbox.from_bounds(*pos) 

868 for ax in self._twinned_axes.get_siblings(self): 

869 if which in ('both', 'active'): 

870 ax._position.set(pos) 

871 if which in ('both', 'original'): 

872 ax._originalPosition.set(pos) 

873 self.stale = True 

874 

875 def reset_position(self): 

876 """ 

877 Reset the active position to the original position. 

878 

879 This resets the a possible position change due to aspect constraints. 

880 For an explanation of the positions see `.set_position`. 

881 """ 

882 for ax in self._twinned_axes.get_siblings(self): 

883 pos = ax.get_position(original=True) 

884 ax.set_position(pos, which='active') 

885 

886 def set_axes_locator(self, locator): 

887 """ 

888 Set the axes locator. 

889 

890 Parameters 

891 ---------- 

892 locator : Callable[[Axes, Renderer], Bbox] 

893 """ 

894 self._axes_locator = locator 

895 self.stale = True 

896 

897 def get_axes_locator(self): 

898 """ 

899 Return the axes_locator. 

900 """ 

901 return self._axes_locator 

902 

903 def _set_artist_props(self, a): 

904 """set the boilerplate props for artists added to axes""" 

905 a.set_figure(self.figure) 

906 if not a.is_transform_set(): 

907 a.set_transform(self.transData) 

908 

909 a.axes = self 

910 if a.mouseover: 

911 self._mouseover_set.add(a) 

912 

913 def _gen_axes_patch(self): 

914 """ 

915 Returns 

916 ------- 

917 Patch 

918 The patch used to draw the background of the axes. It is also used 

919 as the clipping path for any data elements on the axes. 

920 

921 In the standard axes, this is a rectangle, but in other projections 

922 it may not be. 

923 

924 Notes 

925 ----- 

926 Intended to be overridden by new projection types. 

927 """ 

928 return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0) 

929 

930 def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'): 

931 """ 

932 Returns 

933 ------- 

934 dict 

935 Mapping of spine names to `Line2D` or `Patch` instances that are 

936 used to draw axes spines. 

937 

938 In the standard axes, spines are single line segments, but in other 

939 projections they may not be. 

940 

941 Notes 

942 ----- 

943 Intended to be overridden by new projection types. 

944 """ 

945 return OrderedDict((side, mspines.Spine.linear_spine(self, side)) 

946 for side in ['left', 'right', 'bottom', 'top']) 

947 

948 def cla(self): 

949 """Clear the current axes.""" 

950 # Note: this is called by Axes.__init__() 

951 

952 # stash the current visibility state 

953 if hasattr(self, 'patch'): 

954 patch_visible = self.patch.get_visible() 

955 else: 

956 patch_visible = True 

957 

958 xaxis_visible = self.xaxis.get_visible() 

959 yaxis_visible = self.yaxis.get_visible() 

960 

961 self.xaxis.cla() 

962 self.yaxis.cla() 

963 

964 for name, spine in self.spines.items(): 

965 spine.cla() 

966 

967 self.ignore_existing_data_limits = True 

968 self.callbacks = cbook.CallbackRegistry() 

969 

970 if self._sharex is not None: 

971 # major and minor are axis.Ticker class instances with 

972 # locator and formatter attributes 

973 self.xaxis.major = self._sharex.xaxis.major 

974 self.xaxis.minor = self._sharex.xaxis.minor 

975 x0, x1 = self._sharex.get_xlim() 

976 self.set_xlim(x0, x1, emit=False, 

977 auto=self._sharex.get_autoscalex_on()) 

978 self.xaxis._scale = self._sharex.xaxis._scale 

979 else: 

980 self.xaxis._set_scale('linear') 

981 try: 

982 self.set_xlim(0, 1) 

983 except TypeError: 

984 pass 

985 

986 if self._sharey is not None: 

987 self.yaxis.major = self._sharey.yaxis.major 

988 self.yaxis.minor = self._sharey.yaxis.minor 

989 y0, y1 = self._sharey.get_ylim() 

990 self.set_ylim(y0, y1, emit=False, 

991 auto=self._sharey.get_autoscaley_on()) 

992 self.yaxis._scale = self._sharey.yaxis._scale 

993 else: 

994 self.yaxis._set_scale('linear') 

995 try: 

996 self.set_ylim(0, 1) 

997 except TypeError: 

998 pass 

999 # update the minor locator for x and y axis based on rcParams 

1000 if rcParams['xtick.minor.visible']: 

1001 self.xaxis.set_minor_locator(mticker.AutoMinorLocator()) 

1002 

1003 if rcParams['ytick.minor.visible']: 

1004 self.yaxis.set_minor_locator(mticker.AutoMinorLocator()) 

1005 

1006 if self._sharex is None: 

1007 self._autoscaleXon = True 

1008 if self._sharey is None: 

1009 self._autoscaleYon = True 

1010 self._xmargin = rcParams['axes.xmargin'] 

1011 self._ymargin = rcParams['axes.ymargin'] 

1012 self._tight = None 

1013 self._use_sticky_edges = True 

1014 self._update_transScale() # needed? 

1015 

1016 self._get_lines = _process_plot_var_args(self) 

1017 self._get_patches_for_fill = _process_plot_var_args(self, 'fill') 

1018 

1019 self._gridOn = rcParams['axes.grid'] 

1020 self.lines = [] 

1021 self.patches = [] 

1022 self.texts = [] 

1023 self.tables = [] 

1024 self.artists = [] 

1025 self.images = [] 

1026 self._mouseover_set = _OrderedSet() 

1027 self.child_axes = [] 

1028 self._current_image = None # strictly for pyplot via _sci, _gci 

1029 self.legend_ = None 

1030 self.collections = [] # collection.Collection instances 

1031 self.containers = [] 

1032 

1033 self.grid(False) # Disable grid on init to use rcParameter 

1034 self.grid(self._gridOn, which=rcParams['axes.grid.which'], 

1035 axis=rcParams['axes.grid.axis']) 

1036 props = font_manager.FontProperties( 

1037 size=rcParams['axes.titlesize'], 

1038 weight=rcParams['axes.titleweight']) 

1039 

1040 self.title = mtext.Text( 

1041 x=0.5, y=1.0, text='', 

1042 fontproperties=props, 

1043 verticalalignment='baseline', 

1044 horizontalalignment='center', 

1045 ) 

1046 self._left_title = mtext.Text( 

1047 x=0.0, y=1.0, text='', 

1048 fontproperties=props.copy(), 

1049 verticalalignment='baseline', 

1050 horizontalalignment='left', ) 

1051 self._right_title = mtext.Text( 

1052 x=1.0, y=1.0, text='', 

1053 fontproperties=props.copy(), 

1054 verticalalignment='baseline', 

1055 horizontalalignment='right', 

1056 ) 

1057 title_offset_points = rcParams['axes.titlepad'] 

1058 # refactor this out so it can be called in ax.set_title if 

1059 # pad argument used... 

1060 self._set_title_offset_trans(title_offset_points) 

1061 # determine if the title position has been set manually: 

1062 self._autotitlepos = None 

1063 

1064 for _title in (self.title, self._left_title, self._right_title): 

1065 self._set_artist_props(_title) 

1066 

1067 # The patch draws the background of the axes. We want this to be below 

1068 # the other artists. We use the frame to draw the edges so we are 

1069 # setting the edgecolor to None. 

1070 self.patch = self._gen_axes_patch() 

1071 self.patch.set_figure(self.figure) 

1072 self.patch.set_facecolor(self._facecolor) 

1073 self.patch.set_edgecolor('None') 

1074 self.patch.set_linewidth(0) 

1075 self.patch.set_transform(self.transAxes) 

1076 

1077 self.set_axis_on() 

1078 

1079 self.xaxis.set_clip_path(self.patch) 

1080 self.yaxis.set_clip_path(self.patch) 

1081 

1082 self._shared_x_axes.clean() 

1083 self._shared_y_axes.clean() 

1084 if self._sharex: 

1085 self.xaxis.set_visible(xaxis_visible) 

1086 self.patch.set_visible(patch_visible) 

1087 

1088 if self._sharey: 

1089 self.yaxis.set_visible(yaxis_visible) 

1090 self.patch.set_visible(patch_visible) 

1091 

1092 self.stale = True 

1093 

1094 def clear(self): 

1095 """Clear the axes.""" 

1096 self.cla() 

1097 

1098 def get_facecolor(self): 

1099 """Get the facecolor of the Axes.""" 

1100 return self.patch.get_facecolor() 

1101 get_fc = get_facecolor 

1102 

1103 def set_facecolor(self, color): 

1104 """ 

1105 Set the facecolor of the Axes. 

1106 

1107 Parameters 

1108 ---------- 

1109 color : color 

1110 """ 

1111 self._facecolor = color 

1112 self.stale = True 

1113 return self.patch.set_facecolor(color) 

1114 set_fc = set_facecolor 

1115 

1116 def _set_title_offset_trans(self, title_offset_points): 

1117 """ 

1118 Set the offset for the title either from rcParams['axes.titlepad'] 

1119 or from set_title kwarg ``pad``. 

1120 """ 

1121 self.titleOffsetTrans = mtransforms.ScaledTranslation( 

1122 0.0, title_offset_points / 72, 

1123 self.figure.dpi_scale_trans) 

1124 for _title in (self.title, self._left_title, self._right_title): 

1125 _title.set_transform(self.transAxes + self.titleOffsetTrans) 

1126 _title.set_clip_box(None) 

1127 

1128 def set_prop_cycle(self, *args, **kwargs): 

1129 """ 

1130 Set the property cycle of the Axes. 

1131 

1132 The property cycle controls the style properties such as color, 

1133 marker and linestyle of future plot commands. The style properties 

1134 of data already added to the Axes are not modified. 

1135 

1136 Call signatures:: 

1137 

1138 set_prop_cycle(cycler) 

1139 set_prop_cycle(label=values[, label2=values2[, ...]]) 

1140 set_prop_cycle(label, values) 

1141 

1142 Form 1 sets given `~cycler.Cycler` object. 

1143 

1144 Form 2 creates a `~cycler.Cycler` which cycles over one or more 

1145 properties simultaneously and set it as the property cycle of the 

1146 axes. If multiple properties are given, their value lists must have 

1147 the same length. This is just a shortcut for explicitly creating a 

1148 cycler and passing it to the function, i.e. it's short for 

1149 ``set_prop_cycle(cycler(label=values label2=values2, ...))``. 

1150 

1151 Form 3 creates a `~cycler.Cycler` for a single property and set it 

1152 as the property cycle of the axes. This form exists for compatibility 

1153 with the original `cycler.cycler` interface. Its use is discouraged 

1154 in favor of the kwarg form, i.e. ``set_prop_cycle(label=values)``. 

1155 

1156 Parameters 

1157 ---------- 

1158 cycler : Cycler 

1159 Set the given Cycler. *None* resets to the cycle defined by the 

1160 current style. 

1161 

1162 label : str 

1163 The property key. Must be a valid `.Artist` property. 

1164 For example, 'color' or 'linestyle'. Aliases are allowed, 

1165 such as 'c' for 'color' and 'lw' for 'linewidth'. 

1166 

1167 values : iterable 

1168 Finite-length iterable of the property values. These values 

1169 are validated and will raise a ValueError if invalid. 

1170 

1171 Examples 

1172 -------- 

1173 Setting the property cycle for a single property: 

1174 

1175 >>> ax.set_prop_cycle(color=['red', 'green', 'blue']) 

1176 

1177 Setting the property cycle for simultaneously cycling over multiple 

1178 properties (e.g. red circle, green plus, blue cross): 

1179 

1180 >>> ax.set_prop_cycle(color=['red', 'green', 'blue'], 

1181 ... marker=['o', '+', 'x']) 

1182 

1183 See Also 

1184 -------- 

1185 matplotlib.rcsetup.cycler 

1186 Convenience function for creating validated cyclers for properties. 

1187 cycler.cycler 

1188 The original function for creating unvalidated cyclers. 

1189 

1190 """ 

1191 if args and kwargs: 

1192 raise TypeError("Cannot supply both positional and keyword " 

1193 "arguments to this method.") 

1194 # Can't do `args == (None,)` as that crashes cycler. 

1195 if len(args) == 1 and args[0] is None: 

1196 prop_cycle = None 

1197 else: 

1198 prop_cycle = cycler(*args, **kwargs) 

1199 self._get_lines.set_prop_cycle(prop_cycle) 

1200 self._get_patches_for_fill.set_prop_cycle(prop_cycle) 

1201 

1202 def get_aspect(self): 

1203 return self._aspect 

1204 

1205 def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): 

1206 """ 

1207 Set the aspect of the axis scaling, i.e. the ratio of y-unit to x-unit. 

1208 

1209 Parameters 

1210 ---------- 

1211 aspect : {'auto', 'equal'} or num 

1212 Possible values: 

1213 

1214 ======== ================================================ 

1215 value description 

1216 ======== ================================================ 

1217 'auto' automatic; fill the position rectangle with data 

1218 'equal' same scaling from data to plot units for x and y 

1219 num a circle will be stretched such that the height 

1220 is num times the width. aspect=1 is the same as 

1221 aspect='equal'. 

1222 ======== ================================================ 

1223 

1224 adjustable : None or {'box', 'datalim'}, optional 

1225 If not ``None``, this defines which parameter will be adjusted to 

1226 meet the required aspect. See `.set_adjustable` for further 

1227 details. 

1228 

1229 anchor : None or str or 2-tuple of float, optional 

1230 If not ``None``, this defines where the Axes will be drawn if there 

1231 is extra space due to aspect constraints. The most common way to 

1232 to specify the anchor are abbreviations of cardinal directions: 

1233 

1234 ===== ===================== 

1235 value description 

1236 ===== ===================== 

1237 'C' centered 

1238 'SW' lower left corner 

1239 'S' middle of bottom edge 

1240 'SE' lower right corner 

1241 etc. 

1242 ===== ===================== 

1243 

1244 See `.set_anchor` for further details. 

1245 

1246 share : bool, optional 

1247 If ``True``, apply the settings to all shared Axes. 

1248 Default is ``False``. 

1249 

1250 See Also 

1251 -------- 

1252 matplotlib.axes.Axes.set_adjustable 

1253 defining the parameter to adjust in order to meet the required 

1254 aspect. 

1255 matplotlib.axes.Axes.set_anchor 

1256 defining the position in case of extra space. 

1257 """ 

1258 if not (cbook._str_equal(aspect, 'equal') 

1259 or cbook._str_equal(aspect, 'auto')): 

1260 aspect = float(aspect) # raise ValueError if necessary 

1261 

1262 if (not cbook._str_equal(aspect, 'auto')) and self.name == '3d': 

1263 raise NotImplementedError( 

1264 'It is not currently possible to manually set the aspect ' 

1265 'on 3D axes') 

1266 

1267 if share: 

1268 axes = {*self._shared_x_axes.get_siblings(self), 

1269 *self._shared_y_axes.get_siblings(self)} 

1270 else: 

1271 axes = [self] 

1272 

1273 for ax in axes: 

1274 ax._aspect = aspect 

1275 

1276 if adjustable is None: 

1277 adjustable = self._adjustable 

1278 self.set_adjustable(adjustable, share=share) # Handle sharing. 

1279 

1280 if anchor is not None: 

1281 self.set_anchor(anchor, share=share) 

1282 self.stale = True 

1283 

1284 def get_adjustable(self): 

1285 return self._adjustable 

1286 

1287 def set_adjustable(self, adjustable, share=False): 

1288 """ 

1289 Define which parameter the Axes will change to achieve a given aspect. 

1290 

1291 Parameters 

1292 ---------- 

1293 adjustable : {'box', 'datalim'} 

1294 If 'box', change the physical dimensions of the Axes. 

1295 If 'datalim', change the ``x`` or ``y`` data limits. 

1296 

1297 share : bool, optional 

1298 If ``True``, apply the settings to all shared Axes. 

1299 Default is ``False``. 

1300 

1301 See Also 

1302 -------- 

1303 matplotlib.axes.Axes.set_aspect 

1304 for a description of aspect handling. 

1305 

1306 Notes 

1307 ----- 

1308 Shared Axes (of which twinned Axes are a special case) 

1309 impose restrictions on how aspect ratios can be imposed. 

1310 For twinned Axes, use 'datalim'. For Axes that share both 

1311 x and y, use 'box'. Otherwise, either 'datalim' or 'box' 

1312 may be used. These limitations are partly a requirement 

1313 to avoid over-specification, and partly a result of the 

1314 particular implementation we are currently using, in 

1315 which the adjustments for aspect ratios are done sequentially 

1316 and independently on each Axes as it is drawn. 

1317 """ 

1318 cbook._check_in_list(["box", "datalim"], adjustable=adjustable) 

1319 if share: 

1320 axs = {*self._shared_x_axes.get_siblings(self), 

1321 *self._shared_y_axes.get_siblings(self)} 

1322 else: 

1323 axs = [self] 

1324 if (adjustable == "datalim" 

1325 and any(getattr(ax.get_data_ratio, "__func__", None) 

1326 != _AxesBase.get_data_ratio 

1327 for ax in axs)): 

1328 # Limits adjustment by apply_aspect assumes that the axes' aspect 

1329 # ratio can be computed from the data limits and scales. 

1330 raise ValueError("Cannot set axes adjustable to 'datalim' for " 

1331 "Axes which override 'get_data_ratio'") 

1332 for ax in axs: 

1333 ax._adjustable = adjustable 

1334 self.stale = True 

1335 

1336 def get_anchor(self): 

1337 """ 

1338 Get the anchor location. 

1339 

1340 See Also 

1341 -------- 

1342 matplotlib.axes.Axes.set_anchor 

1343 for a description of the anchor. 

1344 matplotlib.axes.Axes.set_aspect 

1345 for a description of aspect handling. 

1346 """ 

1347 return self._anchor 

1348 

1349 def set_anchor(self, anchor, share=False): 

1350 """ 

1351 Define the anchor location. 

1352 

1353 The actual drawing area (active position) of the Axes may be smaller 

1354 than the Bbox (original position) when a fixed aspect is required. The 

1355 anchor defines where the drawing area will be located within the 

1356 available space. 

1357 

1358 Parameters 

1359 ---------- 

1360 anchor : 2-tuple of floats or {'C', 'SW', 'S', 'SE', ...} 

1361 The anchor position may be either: 

1362 

1363 - a sequence (*cx*, *cy*). *cx* and *cy* may range from 0 

1364 to 1, where 0 is left or bottom and 1 is right or top. 

1365 

1366 - a string using cardinal directions as abbreviation: 

1367 

1368 - 'C' for centered 

1369 - 'S' (south) for bottom-center 

1370 - 'SW' (south west) for bottom-left 

1371 - etc. 

1372 

1373 Here is an overview of the possible positions: 

1374 

1375 +------+------+------+ 

1376 | 'NW' | 'N' | 'NE' | 

1377 +------+------+------+ 

1378 | 'W' | 'C' | 'E' | 

1379 +------+------+------+ 

1380 | 'SW' | 'S' | 'SE' | 

1381 +------+------+------+ 

1382 

1383 share : bool, optional 

1384 If ``True``, apply the settings to all shared Axes. 

1385 Default is ``False``. 

1386 

1387 See Also 

1388 -------- 

1389 matplotlib.axes.Axes.set_aspect 

1390 for a description of aspect handling. 

1391 """ 

1392 if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2): 

1393 raise ValueError('argument must be among %s' % 

1394 ', '.join(mtransforms.Bbox.coefs)) 

1395 if share: 

1396 axes = {*self._shared_x_axes.get_siblings(self), 

1397 *self._shared_y_axes.get_siblings(self)} 

1398 else: 

1399 axes = [self] 

1400 for ax in axes: 

1401 ax._anchor = anchor 

1402 

1403 self.stale = True 

1404 

1405 def get_data_ratio(self): 

1406 """ 

1407 Return the aspect ratio of the scaled data. 

1408 

1409 Notes 

1410 ----- 

1411 This method is intended to be overridden by new projection types. 

1412 """ 

1413 txmin, txmax = self.xaxis.get_transform().transform(self.get_xbound()) 

1414 tymin, tymax = self.yaxis.get_transform().transform(self.get_ybound()) 

1415 xsize = max(abs(txmax - txmin), 1e-30) 

1416 ysize = max(abs(tymax - tymin), 1e-30) 

1417 return ysize / xsize 

1418 

1419 @cbook.deprecated("3.2") 

1420 def get_data_ratio_log(self): 

1421 """ 

1422 Return the aspect ratio of the raw data in log scale. 

1423 

1424 Notes 

1425 ----- 

1426 Will be used when both axis are in log scale. 

1427 """ 

1428 xmin, xmax = self.get_xbound() 

1429 ymin, ymax = self.get_ybound() 

1430 

1431 xsize = max(abs(math.log10(xmax) - math.log10(xmin)), 1e-30) 

1432 ysize = max(abs(math.log10(ymax) - math.log10(ymin)), 1e-30) 

1433 

1434 return ysize / xsize 

1435 

1436 def apply_aspect(self, position=None): 

1437 """ 

1438 Adjust the Axes for a specified data aspect ratio. 

1439 

1440 Depending on `.get_adjustable` this will modify either the Axes box 

1441 (position) or the view limits. In the former case, `.get_anchor` 

1442 will affect the position. 

1443 

1444 Notes 

1445 ----- 

1446 This is called automatically when each Axes is drawn. You may need 

1447 to call it yourself if you need to update the Axes position and/or 

1448 view limits before the Figure is drawn. 

1449 

1450 See Also 

1451 -------- 

1452 matplotlib.axes.Axes.set_aspect 

1453 for a description of aspect ratio handling. 

1454 matplotlib.axes.Axes.set_adjustable 

1455 defining the parameter to adjust in order to meet the required 

1456 aspect. 

1457 matplotlib.axes.Axes.set_anchor 

1458 defining the position in case of extra space. 

1459 """ 

1460 if position is None: 

1461 position = self.get_position(original=True) 

1462 

1463 aspect = self.get_aspect() 

1464 

1465 if aspect == 'auto': 

1466 self._set_position(position, which='active') 

1467 return 

1468 

1469 if aspect == 'equal': 

1470 aspect = 1 

1471 

1472 fig_width, fig_height = self.get_figure().get_size_inches() 

1473 fig_aspect = fig_height / fig_width 

1474 

1475 if self._adjustable == 'box': 

1476 if self in self._twinned_axes: 

1477 raise RuntimeError("Adjustable 'box' is not allowed in a " 

1478 "twinned Axes; use 'datalim' instead") 

1479 box_aspect = aspect * self.get_data_ratio() 

1480 pb = position.frozen() 

1481 pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) 

1482 self._set_position(pb1.anchored(self.get_anchor(), pb), 'active') 

1483 return 

1484 

1485 # self._adjustable == 'datalim' 

1486 

1487 # reset active to original in case it had been changed by prior use 

1488 # of 'box' 

1489 self._set_position(position, which='active') 

1490 

1491 x_trf = self.xaxis.get_transform() 

1492 y_trf = self.yaxis.get_transform() 

1493 xmin, xmax = x_trf.transform(self.get_xbound()) 

1494 ymin, ymax = y_trf.transform(self.get_ybound()) 

1495 xsize = max(abs(xmax - xmin), 1e-30) 

1496 ysize = max(abs(ymax - ymin), 1e-30) 

1497 

1498 l, b, w, h = position.bounds 

1499 box_aspect = fig_aspect * (h / w) 

1500 data_ratio = box_aspect / aspect 

1501 

1502 y_expander = data_ratio * xsize / ysize - 1 

1503 # If y_expander > 0, the dy/dx viewLim ratio needs to increase 

1504 if abs(y_expander) < 0.005: 

1505 return 

1506 

1507 dL = self.dataLim 

1508 x0, x1 = x_trf.transform(dL.intervalx) 

1509 y0, y1 = y_trf.transform(dL.intervaly) 

1510 xr = 1.05 * (x1 - x0) 

1511 yr = 1.05 * (y1 - y0) 

1512 

1513 xmarg = xsize - xr 

1514 ymarg = ysize - yr 

1515 Ysize = data_ratio * xsize 

1516 Xsize = ysize / data_ratio 

1517 Xmarg = Xsize - xr 

1518 Ymarg = Ysize - yr 

1519 # Setting these targets to, e.g., 0.05*xr does not seem to help. 

1520 xm = 0 

1521 ym = 0 

1522 

1523 shared_x = self in self._shared_x_axes 

1524 shared_y = self in self._shared_y_axes 

1525 # Not sure whether we need this check: 

1526 if shared_x and shared_y: 

1527 raise RuntimeError("adjustable='datalim' is not allowed when both " 

1528 "axes are shared") 

1529 

1530 # If y is shared, then we are only allowed to change x, etc. 

1531 if shared_y: 

1532 adjust_y = False 

1533 else: 

1534 if xmarg > xm and ymarg > ym: 

1535 adjy = ((Ymarg > 0 and y_expander < 0) or 

1536 (Xmarg < 0 and y_expander > 0)) 

1537 else: 

1538 adjy = y_expander > 0 

1539 adjust_y = shared_x or adjy # (Ymarg > xmarg) 

1540 

1541 if adjust_y: 

1542 yc = 0.5 * (ymin + ymax) 

1543 y0 = yc - Ysize / 2.0 

1544 y1 = yc + Ysize / 2.0 

1545 self.set_ybound(y_trf.inverted().transform([y0, y1])) 

1546 else: 

1547 xc = 0.5 * (xmin + xmax) 

1548 x0 = xc - Xsize / 2.0 

1549 x1 = xc + Xsize / 2.0 

1550 self.set_xbound(x_trf.inverted().transform([x0, x1])) 

1551 

1552 def axis(self, *args, emit=True, **kwargs): 

1553 """ 

1554 Convenience method to get or set some axis properties. 

1555 

1556 Call signatures:: 

1557 

1558 xmin, xmax, ymin, ymax = axis() 

1559 xmin, xmax, ymin, ymax = axis([xmin, xmax, ymin, ymax]) 

1560 xmin, xmax, ymin, ymax = axis(option) 

1561 xmin, xmax, ymin, ymax = axis(**kwargs) 

1562 

1563 Parameters 

1564 ---------- 

1565 xmin, xmax, ymin, ymax : float, optional 

1566 The axis limits to be set. This can also be achieved using :: 

1567 

1568 ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax)) 

1569 

1570 option : bool or str 

1571 If a bool, turns axis lines and labels on or off. If a string, 

1572 possible values are: 

1573 

1574 ======== ========================================================== 

1575 Value Description 

1576 ======== ========================================================== 

1577 'on' Turn on axis lines and labels. Same as ``True``. 

1578 'off' Turn off axis lines and labels. Same as ``False``. 

1579 'equal' Set equal scaling (i.e., make circles circular) by 

1580 changing axis limits. This is the same as 

1581 ``ax.set_aspect('equal', adjustable='datalim')``. 

1582 Explicit data limits may not be respected in this case. 

1583 'scaled' Set equal scaling (i.e., make circles circular) by 

1584 changing dimensions of the plot box. This is the same as 

1585 ``ax.set_aspect('equal', adjustable='box', anchor='C')``. 

1586 Additionally, further autoscaling will be disabled. 

1587 'tight' Set limits just large enough to show all data, then 

1588 disable further autoscaling. 

1589 'auto' Automatic scaling (fill plot box with data). 

1590 'normal' Same as 'auto'; deprecated. 

1591 'image' 'scaled' with axis limits equal to data limits. 

1592 'square' Square plot; similar to 'scaled', but initially forcing 

1593 ``xmax-xmin == ymax-ymin``. 

1594 ======== ========================================================== 

1595 

1596 emit : bool, optional, default *True* 

1597 Whether observers are notified of the axis limit change. 

1598 This option is passed on to `~.Axes.set_xlim` and 

1599 `~.Axes.set_ylim`. 

1600 

1601 Returns 

1602 ------- 

1603 xmin, xmax, ymin, ymax : float 

1604 The axis limits. 

1605 

1606 See also 

1607 -------- 

1608 matplotlib.axes.Axes.set_xlim 

1609 matplotlib.axes.Axes.set_ylim 

1610 """ 

1611 if len(args) == 1 and isinstance(args[0], (str, bool)): 

1612 s = args[0] 

1613 if s is True: 

1614 s = 'on' 

1615 if s is False: 

1616 s = 'off' 

1617 s = s.lower() 

1618 if s == 'on': 

1619 self.set_axis_on() 

1620 elif s == 'off': 

1621 self.set_axis_off() 

1622 elif s in ('equal', 'tight', 'scaled', 'normal', 

1623 'auto', 'image', 'square'): 

1624 if s == 'normal': 

1625 cbook.warn_deprecated( 

1626 "3.1", message="Passing 'normal' to axis() is " 

1627 "deprecated since %(since)s; use 'auto' instead.") 

1628 self.set_autoscale_on(True) 

1629 self.set_aspect('auto') 

1630 self.autoscale_view(tight=False) 

1631 # self.apply_aspect() 

1632 if s == 'equal': 

1633 self.set_aspect('equal', adjustable='datalim') 

1634 elif s == 'scaled': 

1635 self.set_aspect('equal', adjustable='box', anchor='C') 

1636 self.set_autoscale_on(False) # Req. by Mark Bakker 

1637 elif s == 'tight': 

1638 self.autoscale_view(tight=True) 

1639 self.set_autoscale_on(False) 

1640 elif s == 'image': 

1641 self.autoscale_view(tight=True) 

1642 self.set_autoscale_on(False) 

1643 self.set_aspect('equal', adjustable='box', anchor='C') 

1644 elif s == 'square': 

1645 self.set_aspect('equal', adjustable='box', anchor='C') 

1646 self.set_autoscale_on(False) 

1647 xlim = self.get_xlim() 

1648 ylim = self.get_ylim() 

1649 edge_size = max(np.diff(xlim), np.diff(ylim))[0] 

1650 self.set_xlim([xlim[0], xlim[0] + edge_size], 

1651 emit=emit, auto=False) 

1652 self.set_ylim([ylim[0], ylim[0] + edge_size], 

1653 emit=emit, auto=False) 

1654 else: 

1655 raise ValueError('Unrecognized string %s to axis; ' 

1656 'try on or off' % s) 

1657 else: 

1658 if len(args) >= 1: 

1659 if len(args) != 1: 

1660 cbook.warn_deprecated( 

1661 "3.2", message="Passing more than one positional " 

1662 "argument to axis() is deprecated and will raise a " 

1663 "TypeError %(removal)s.") 

1664 limits = args[0] 

1665 try: 

1666 xmin, xmax, ymin, ymax = limits 

1667 except (TypeError, ValueError): 

1668 raise TypeError('the first argument to axis() must be an ' 

1669 'interable of the form ' 

1670 '[xmin, xmax, ymin, ymax]') 

1671 else: 

1672 xmin = kwargs.pop('xmin', None) 

1673 xmax = kwargs.pop('xmax', None) 

1674 ymin = kwargs.pop('ymin', None) 

1675 ymax = kwargs.pop('ymax', None) 

1676 xauto = (None # Keep autoscale state as is. 

1677 if xmin is None and xmax is None 

1678 else False) # Turn off autoscale. 

1679 yauto = (None 

1680 if ymin is None and ymax is None 

1681 else False) 

1682 self.set_xlim(xmin, xmax, emit=emit, auto=xauto) 

1683 self.set_ylim(ymin, ymax, emit=emit, auto=yauto) 

1684 if kwargs: 

1685 cbook.warn_deprecated( 

1686 "3.1", message="Passing unsupported keyword arguments to " 

1687 "axis() will raise a TypeError %(removal)s.") 

1688 return (*self.get_xlim(), *self.get_ylim()) 

1689 

1690 def get_legend(self): 

1691 """Return the `Legend` instance, or None if no legend is defined.""" 

1692 return self.legend_ 

1693 

1694 def get_images(self): 

1695 """return a list of Axes images contained by the Axes""" 

1696 return cbook.silent_list('AxesImage', self.images) 

1697 

1698 def get_lines(self): 

1699 """Return a list of lines contained by the Axes""" 

1700 return cbook.silent_list('Line2D', self.lines) 

1701 

1702 def get_xaxis(self): 

1703 """Return the XAxis instance.""" 

1704 return self.xaxis 

1705 

1706 def get_xgridlines(self): 

1707 """Get the x grid lines as a list of `Line2D` instances.""" 

1708 return self.xaxis.get_gridlines() 

1709 

1710 def get_xticklines(self): 

1711 """Get the x tick lines as a list of `Line2D` instances.""" 

1712 return self.xaxis.get_ticklines() 

1713 

1714 def get_yaxis(self): 

1715 """Return the YAxis instance.""" 

1716 return self.yaxis 

1717 

1718 def get_ygridlines(self): 

1719 """Get the y grid lines as a list of `Line2D` instances.""" 

1720 return self.yaxis.get_gridlines() 

1721 

1722 def get_yticklines(self): 

1723 """Get the y tick lines as a list of `Line2D` instances.""" 

1724 return self.yaxis.get_ticklines() 

1725 

1726 # Adding and tracking artists 

1727 

1728 def _sci(self, im): 

1729 """Set the current image. 

1730 

1731 This image will be the target of colormap functions like 

1732 `~.pyplot.viridis`, and other functions such as `~.pyplot.clim`. The 

1733 current image is an attribute of the current axes. 

1734 """ 

1735 if isinstance(im, mpl.contour.ContourSet): 

1736 if im.collections[0] not in self.collections: 

1737 raise ValueError("ContourSet must be in current Axes") 

1738 elif im not in self.images and im not in self.collections: 

1739 raise ValueError("Argument must be an image, collection, or " 

1740 "ContourSet in this Axes") 

1741 self._current_image = im 

1742 

1743 def _gci(self): 

1744 """ 

1745 Helper for :func:`~matplotlib.pyplot.gci`; 

1746 do not use elsewhere. 

1747 """ 

1748 return self._current_image 

1749 

1750 def has_data(self): 

1751 """ 

1752 Return *True* if any artists have been added to axes. 

1753 

1754 This should not be used to determine whether the *dataLim* 

1755 need to be updated, and may not actually be useful for 

1756 anything. 

1757 """ 

1758 return ( 

1759 len(self.collections) + 

1760 len(self.images) + 

1761 len(self.lines) + 

1762 len(self.patches)) > 0 

1763 

1764 def add_artist(self, a): 

1765 """ 

1766 Add an `~.Artist` to the axes, and return the artist. 

1767 

1768 Use `add_artist` only for artists for which there is no dedicated 

1769 "add" method; and if necessary, use a method such as `update_datalim` 

1770 to manually update the dataLim if the artist is to be included in 

1771 autoscaling. 

1772 

1773 If no ``transform`` has been specified when creating the artist (e.g. 

1774 ``artist.get_transform() == None``) then the transform is set to 

1775 ``ax.transData``. 

1776 """ 

1777 a.axes = self 

1778 self.artists.append(a) 

1779 a._remove_method = self.artists.remove 

1780 self._set_artist_props(a) 

1781 a.set_clip_path(self.patch) 

1782 self.stale = True 

1783 return a 

1784 

1785 def add_child_axes(self, ax): 

1786 """ 

1787 Add an `~.AxesBase` to the axes' children; return the child axes. 

1788 

1789 This is the lowlevel version. See `.axes.Axes.inset_axes`. 

1790 """ 

1791 

1792 # normally axes have themselves as the axes, but these need to have 

1793 # their parent... 

1794 # Need to bypass the getter... 

1795 ax._axes = self 

1796 ax.stale_callback = martist._stale_axes_callback 

1797 

1798 self.child_axes.append(ax) 

1799 ax._remove_method = self.child_axes.remove 

1800 self.stale = True 

1801 return ax 

1802 

1803 def add_collection(self, collection, autolim=True): 

1804 """ 

1805 Add a `~.Collection` to the axes' collections; return the collection. 

1806 """ 

1807 label = collection.get_label() 

1808 if not label: 

1809 collection.set_label('_collection%d' % len(self.collections)) 

1810 self.collections.append(collection) 

1811 collection._remove_method = self.collections.remove 

1812 self._set_artist_props(collection) 

1813 

1814 if collection.get_clip_path() is None: 

1815 collection.set_clip_path(self.patch) 

1816 

1817 if autolim: 

1818 # Make sure viewLim is not stale (mostly to match 

1819 # pre-lazy-autoscale behavior, which is not really better). 

1820 self._unstale_viewLim() 

1821 self.update_datalim(collection.get_datalim(self.transData)) 

1822 

1823 self.stale = True 

1824 return collection 

1825 

1826 def add_image(self, image): 

1827 """ 

1828 Add an `~.AxesImage` to the axes' images; return the image. 

1829 """ 

1830 self._set_artist_props(image) 

1831 if not image.get_label(): 

1832 image.set_label('_image%d' % len(self.images)) 

1833 self.images.append(image) 

1834 image._remove_method = self.images.remove 

1835 self.stale = True 

1836 return image 

1837 

1838 def _update_image_limits(self, image): 

1839 xmin, xmax, ymin, ymax = image.get_extent() 

1840 self.axes.update_datalim(((xmin, ymin), (xmax, ymax))) 

1841 

1842 def add_line(self, line): 

1843 """ 

1844 Add a `.Line2D` to the axes' lines; return the line. 

1845 """ 

1846 self._set_artist_props(line) 

1847 if line.get_clip_path() is None: 

1848 line.set_clip_path(self.patch) 

1849 

1850 self._update_line_limits(line) 

1851 if not line.get_label(): 

1852 line.set_label('_line%d' % len(self.lines)) 

1853 self.lines.append(line) 

1854 line._remove_method = self.lines.remove 

1855 self.stale = True 

1856 return line 

1857 

1858 def _add_text(self, txt): 

1859 """ 

1860 Add a `~.Text` to the axes' texts; return the text. 

1861 """ 

1862 self._set_artist_props(txt) 

1863 self.texts.append(txt) 

1864 txt._remove_method = self.texts.remove 

1865 self.stale = True 

1866 return txt 

1867 

1868 def _update_line_limits(self, line): 

1869 """ 

1870 Figures out the data limit of the given line, updating self.dataLim. 

1871 """ 

1872 path = line.get_path() 

1873 if path.vertices.size == 0: 

1874 return 

1875 

1876 line_trans = line.get_transform() 

1877 

1878 if line_trans == self.transData: 

1879 data_path = path 

1880 

1881 elif any(line_trans.contains_branch_seperately(self.transData)): 

1882 # identify the transform to go from line's coordinates 

1883 # to data coordinates 

1884 trans_to_data = line_trans - self.transData 

1885 

1886 # if transData is affine we can use the cached non-affine component 

1887 # of line's path. (since the non-affine part of line_trans is 

1888 # entirely encapsulated in trans_to_data). 

1889 if self.transData.is_affine: 

1890 line_trans_path = line._get_transformed_path() 

1891 na_path, _ = line_trans_path.get_transformed_path_and_affine() 

1892 data_path = trans_to_data.transform_path_affine(na_path) 

1893 else: 

1894 data_path = trans_to_data.transform_path(path) 

1895 else: 

1896 # for backwards compatibility we update the dataLim with the 

1897 # coordinate range of the given path, even though the coordinate 

1898 # systems are completely different. This may occur in situations 

1899 # such as when ax.transAxes is passed through for absolute 

1900 # positioning. 

1901 data_path = path 

1902 

1903 if data_path.vertices.size > 0: 

1904 updatex, updatey = line_trans.contains_branch_seperately( 

1905 self.transData) 

1906 self.dataLim.update_from_path(data_path, 

1907 self.ignore_existing_data_limits, 

1908 updatex=updatex, 

1909 updatey=updatey) 

1910 self.ignore_existing_data_limits = False 

1911 

1912 def add_patch(self, p): 

1913 """ 

1914 Add a `~.Patch` to the axes' patches; return the patch. 

1915 """ 

1916 self._set_artist_props(p) 

1917 if p.get_clip_path() is None: 

1918 p.set_clip_path(self.patch) 

1919 self._update_patch_limits(p) 

1920 self.patches.append(p) 

1921 p._remove_method = self.patches.remove 

1922 return p 

1923 

1924 def _update_patch_limits(self, patch): 

1925 """update the data limits for patch *p*""" 

1926 # hist can add zero height Rectangles, which is useful to keep 

1927 # the bins, counts and patches lined up, but it throws off log 

1928 # scaling. We'll ignore rects with zero height or width in 

1929 # the auto-scaling 

1930 

1931 # cannot check for '==0' since unitized data may not compare to zero 

1932 # issue #2150 - we update the limits if patch has non zero width 

1933 # or height. 

1934 if (isinstance(patch, mpatches.Rectangle) and 

1935 ((not patch.get_width()) and (not patch.get_height()))): 

1936 return 

1937 vertices = patch.get_path().vertices 

1938 if vertices.size > 0: 

1939 xys = patch.get_patch_transform().transform(vertices) 

1940 if patch.get_data_transform() != self.transData: 

1941 patch_to_data = (patch.get_data_transform() - 

1942 self.transData) 

1943 xys = patch_to_data.transform(xys) 

1944 

1945 updatex, updatey = patch.get_transform().\ 

1946 contains_branch_seperately(self.transData) 

1947 self.update_datalim(xys, updatex=updatex, 

1948 updatey=updatey) 

1949 

1950 def add_table(self, tab): 

1951 """ 

1952 Add a `~.Table` to the axes' tables; return the table. 

1953 """ 

1954 self._set_artist_props(tab) 

1955 self.tables.append(tab) 

1956 tab.set_clip_path(self.patch) 

1957 tab._remove_method = self.tables.remove 

1958 return tab 

1959 

1960 def add_container(self, container): 

1961 """ 

1962 Add a `~.Container` to the axes' containers; return the container. 

1963 """ 

1964 label = container.get_label() 

1965 if not label: 

1966 container.set_label('_container%d' % len(self.containers)) 

1967 self.containers.append(container) 

1968 container._remove_method = self.containers.remove 

1969 return container 

1970 

1971 def _on_units_changed(self, scalex=False, scaley=False): 

1972 """ 

1973 Callback for processing changes to axis units. 

1974 

1975 Currently requests updates of data limits and view limits. 

1976 """ 

1977 self.relim() 

1978 self._request_autoscale_view(scalex=scalex, scaley=scaley) 

1979 

1980 def relim(self, visible_only=False): 

1981 """ 

1982 Recompute the data limits based on current artists. 

1983 

1984 At present, `~.Collection` instances are not supported. 

1985 

1986 Parameters 

1987 ---------- 

1988 visible_only : bool 

1989 Whether to exclude invisible artists. Defaults to False. 

1990 """ 

1991 # Collections are deliberately not supported (yet); see 

1992 # the TODO note in artists.py. 

1993 self.dataLim.ignore(True) 

1994 self.dataLim.set_points(mtransforms.Bbox.null().get_points()) 

1995 self.ignore_existing_data_limits = True 

1996 

1997 for line in self.lines: 

1998 if not visible_only or line.get_visible(): 

1999 self._update_line_limits(line) 

2000 

2001 for p in self.patches: 

2002 if not visible_only or p.get_visible(): 

2003 self._update_patch_limits(p) 

2004 

2005 for image in self.images: 

2006 if not visible_only or image.get_visible(): 

2007 self._update_image_limits(image) 

2008 

2009 def update_datalim(self, xys, updatex=True, updatey=True): 

2010 """ 

2011 Extend the `~.Axes.dataLim` Bbox to include the given points. 

2012 

2013 If no data is set currently, the Bbox will ignore its limits and set 

2014 the bound to be the bounds of the xydata (*xys*). Otherwise, it will 

2015 compute the bounds of the union of its current data and the data in 

2016 *xys*. 

2017 

2018 Parameters 

2019 ---------- 

2020 xys : 2D array-like 

2021 The points to include in the data limits Bbox. This can be either 

2022 a list of (x, y) tuples or a Nx2 array. 

2023 

2024 updatex, updatey : bool, optional, default *True* 

2025 Whether to update the x/y limits. 

2026 """ 

2027 xys = np.asarray(xys) 

2028 if not len(xys): 

2029 return 

2030 self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits, 

2031 updatex=updatex, updatey=updatey) 

2032 self.ignore_existing_data_limits = False 

2033 

2034 def update_datalim_bounds(self, bounds): 

2035 """ 

2036 Extend the `~.Axes.datalim` Bbox to include the given 

2037 `~matplotlib.transforms.Bbox`. 

2038 

2039 Parameters 

2040 ---------- 

2041 bounds : `~matplotlib.transforms.Bbox` 

2042 """ 

2043 self.dataLim.set(mtransforms.Bbox.union([self.dataLim, bounds])) 

2044 

2045 def _process_unit_info(self, xdata=None, ydata=None, kwargs=None): 

2046 """Look for unit *kwargs* and update the axis instances as necessary""" 

2047 

2048 def _process_single_axis(data, axis, unit_name, kwargs): 

2049 # Return if there's no axis set 

2050 if axis is None: 

2051 return kwargs 

2052 

2053 if data is not None: 

2054 # We only need to update if there is nothing set yet. 

2055 if not axis.have_units(): 

2056 axis.update_units(data) 

2057 

2058 # Check for units in the kwargs, and if present update axis 

2059 if kwargs is not None: 

2060 units = kwargs.pop(unit_name, axis.units) 

2061 if self.name == 'polar': 

2062 polar_units = {'xunits': 'thetaunits', 'yunits': 'runits'} 

2063 units = kwargs.pop(polar_units[unit_name], units) 

2064 

2065 if units != axis.units: 

2066 axis.set_units(units) 

2067 # If the units being set imply a different converter, 

2068 # we need to update. 

2069 if data is not None: 

2070 axis.update_units(data) 

2071 return kwargs 

2072 

2073 kwargs = _process_single_axis(xdata, self.xaxis, 'xunits', kwargs) 

2074 kwargs = _process_single_axis(ydata, self.yaxis, 'yunits', kwargs) 

2075 return kwargs 

2076 

2077 def in_axes(self, mouseevent): 

2078 """ 

2079 Return *True* if the given *mouseevent* (in display coords) 

2080 is in the Axes 

2081 """ 

2082 return self.patch.contains(mouseevent)[0] 

2083 

2084 def get_autoscale_on(self): 

2085 """ 

2086 Get whether autoscaling is applied for both axes on plot commands 

2087 """ 

2088 return self._autoscaleXon and self._autoscaleYon 

2089 

2090 def get_autoscalex_on(self): 

2091 """ 

2092 Get whether autoscaling for the x-axis is applied on plot commands 

2093 """ 

2094 return self._autoscaleXon 

2095 

2096 def get_autoscaley_on(self): 

2097 """ 

2098 Get whether autoscaling for the y-axis is applied on plot commands 

2099 """ 

2100 return self._autoscaleYon 

2101 

2102 def set_autoscale_on(self, b): 

2103 """ 

2104 Set whether autoscaling is applied on plot commands 

2105 

2106 Parameters 

2107 ---------- 

2108 b : bool 

2109 """ 

2110 self._autoscaleXon = b 

2111 self._autoscaleYon = b 

2112 

2113 def set_autoscalex_on(self, b): 

2114 """ 

2115 Set whether autoscaling for the x-axis is applied on plot commands 

2116 

2117 Parameters 

2118 ---------- 

2119 b : bool 

2120 """ 

2121 self._autoscaleXon = b 

2122 

2123 def set_autoscaley_on(self, b): 

2124 """ 

2125 Set whether autoscaling for the y-axis is applied on plot commands 

2126 

2127 Parameters 

2128 ---------- 

2129 b : bool 

2130 """ 

2131 self._autoscaleYon = b 

2132 

2133 @property 

2134 def use_sticky_edges(self): 

2135 """ 

2136 When autoscaling, whether to obey all `Artist.sticky_edges`. 

2137 

2138 Default is ``True``. 

2139 

2140 Setting this to ``False`` ensures that the specified margins 

2141 will be applied, even if the plot includes an image, for 

2142 example, which would otherwise force a view limit to coincide 

2143 with its data limit. 

2144 

2145 The changing this property does not change the plot until 

2146 `autoscale` or `autoscale_view` is called. 

2147 """ 

2148 return self._use_sticky_edges 

2149 

2150 @use_sticky_edges.setter 

2151 def use_sticky_edges(self, b): 

2152 self._use_sticky_edges = bool(b) 

2153 # No effect until next autoscaling, which will mark the axes as stale. 

2154 

2155 def set_xmargin(self, m): 

2156 """ 

2157 Set padding of X data limits prior to autoscaling. 

2158 

2159 *m* times the data interval will be added to each 

2160 end of that interval before it is used in autoscaling. 

2161 For example, if your data is in the range [0, 2], a factor of 

2162 ``m = 0.1`` will result in a range [-0.2, 2.2]. 

2163 

2164 Negative values -0.5 < m < 0 will result in clipping of the data range. 

2165 I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in 

2166 a range [0.2, 1.8]. 

2167 

2168 Parameters 

2169 ---------- 

2170 m : float greater than -0.5 

2171 """ 

2172 if m <= -0.5: 

2173 raise ValueError("margin must be greater than -0.5") 

2174 self._xmargin = m 

2175 self.stale = True 

2176 

2177 def set_ymargin(self, m): 

2178 """ 

2179 Set padding of Y data limits prior to autoscaling. 

2180 

2181 *m* times the data interval will be added to each 

2182 end of that interval before it is used in autoscaling. 

2183 For example, if your data is in the range [0, 2], a factor of 

2184 ``m = 0.1`` will result in a range [-0.2, 2.2]. 

2185 

2186 Negative values -0.5 < m < 0 will result in clipping of the data range. 

2187 I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in 

2188 a range [0.2, 1.8]. 

2189 

2190 Parameters 

2191 ---------- 

2192 m : float greater than -0.5 

2193 """ 

2194 if m <= -0.5: 

2195 raise ValueError("margin must be greater than -0.5") 

2196 self._ymargin = m 

2197 self.stale = True 

2198 

2199 def margins(self, *margins, x=None, y=None, tight=True): 

2200 """ 

2201 Set or retrieve autoscaling margins. 

2202 

2203 The padding added to each limit of the axes is the *margin* 

2204 times the data interval. All input parameters must be floats 

2205 within the range [0, 1]. Passing both positional and keyword 

2206 arguments is invalid and will raise a TypeError. If no 

2207 arguments (positional or otherwise) are provided, the current 

2208 margins will remain in place and simply be returned. 

2209 

2210 Specifying any margin changes only the autoscaling; for example, 

2211 if *xmargin* is not None, then *xmargin* times the X data 

2212 interval will be added to each end of that interval before 

2213 it is used in autoscaling. 

2214 

2215 Parameters 

2216 ---------- 

2217 *margins : float, optional 

2218 If a single positional argument is provided, it specifies 

2219 both margins of the x-axis and y-axis limits. If two 

2220 positional arguments are provided, they will be interpreted 

2221 as *xmargin*, *ymargin*. If setting the margin on a single 

2222 axis is desired, use the keyword arguments described below. 

2223 

2224 x, y : float, optional 

2225 Specific margin values for the x-axis and y-axis, 

2226 respectively. These cannot be used with positional 

2227 arguments, but can be used individually to alter on e.g., 

2228 only the y-axis. 

2229 

2230 tight : bool or None, default is True 

2231 The *tight* parameter is passed to :meth:`autoscale_view`, 

2232 which is executed after a margin is changed; the default 

2233 here is *True*, on the assumption that when margins are 

2234 specified, no additional padding to match tick marks is 

2235 usually desired. Set *tight* to *None* will preserve 

2236 the previous setting. 

2237 

2238 Returns 

2239 ------- 

2240 xmargin, ymargin : float 

2241 

2242 Notes 

2243 ----- 

2244 If a previously used Axes method such as :meth:`pcolor` has set 

2245 :attr:`use_sticky_edges` to `True`, only the limits not set by 

2246 the "sticky artists" will be modified. To force all of the 

2247 margins to be set, set :attr:`use_sticky_edges` to `False` 

2248 before calling :meth:`margins`. 

2249 """ 

2250 

2251 if margins and x is not None and y is not None: 

2252 raise TypeError('Cannot pass both positional and keyword ' 

2253 'arguments for x and/or y.') 

2254 elif len(margins) == 1: 

2255 x = y = margins[0] 

2256 elif len(margins) == 2: 

2257 x, y = margins 

2258 elif margins: 

2259 raise TypeError('Must pass a single positional argument for all ' 

2260 'margins, or one for each margin (x, y).') 

2261 

2262 if x is None and y is None: 

2263 if tight is not True: 

2264 cbook._warn_external(f'ignoring tight={tight!r} in get mode') 

2265 return self._xmargin, self._ymargin 

2266 

2267 if x is not None: 

2268 self.set_xmargin(x) 

2269 if y is not None: 

2270 self.set_ymargin(y) 

2271 

2272 self._request_autoscale_view( 

2273 tight=tight, scalex=(x is not None), scaley=(y is not None) 

2274 ) 

2275 

2276 def set_rasterization_zorder(self, z): 

2277 """ 

2278 Parameters 

2279 ---------- 

2280 z : float or None 

2281 zorder below which artists are rasterized. ``None`` means that 

2282 artists do not get rasterized based on zorder. 

2283 """ 

2284 self._rasterization_zorder = z 

2285 self.stale = True 

2286 

2287 def get_rasterization_zorder(self): 

2288 """Return the zorder value below which artists will be rasterized.""" 

2289 return self._rasterization_zorder 

2290 

2291 def autoscale(self, enable=True, axis='both', tight=None): 

2292 """ 

2293 Autoscale the axis view to the data (toggle). 

2294 

2295 Convenience method for simple axis view autoscaling. 

2296 It turns autoscaling on or off, and then, 

2297 if autoscaling for either axis is on, it performs 

2298 the autoscaling on the specified axis or axes. 

2299 

2300 Parameters 

2301 ---------- 

2302 enable : bool or None, optional 

2303 True (default) turns autoscaling on, False turns it off. 

2304 None leaves the autoscaling state unchanged. 

2305 

2306 axis : {'both', 'x', 'y'}, optional 

2307 Which axis to operate on; default is 'both'. 

2308 

2309 tight : bool or None, optional 

2310 If True, first set the margins to zero. Then, this argument is 

2311 forwarded to `autoscale_view` (regardless of its value); see the 

2312 description of its behavior there. 

2313 """ 

2314 if enable is None: 

2315 scalex = True 

2316 scaley = True 

2317 else: 

2318 scalex = False 

2319 scaley = False 

2320 if axis in ['x', 'both']: 

2321 self._autoscaleXon = bool(enable) 

2322 scalex = self._autoscaleXon 

2323 if axis in ['y', 'both']: 

2324 self._autoscaleYon = bool(enable) 

2325 scaley = self._autoscaleYon 

2326 if tight and scalex: 

2327 self._xmargin = 0 

2328 if tight and scaley: 

2329 self._ymargin = 0 

2330 self._request_autoscale_view(tight=tight, scalex=scalex, scaley=scaley) 

2331 

2332 def autoscale_view(self, tight=None, scalex=True, scaley=True): 

2333 """ 

2334 Autoscale the view limits using the data limits. 

2335 

2336 Parameters 

2337 ---------- 

2338 tight : bool or None 

2339 If *True*, only expand the axis limits using the margins. Note 

2340 that unlike for `autoscale`, ``tight=True`` does *not* set the 

2341 margins to zero. 

2342 

2343 If *False* and :rc:`axes.autolimit_mode` is 'round_numbers', then 

2344 after expansion by the margins, further expand the axis limits 

2345 using the axis major locator. 

2346 

2347 If None (the default), reuse the value set in the previous call to 

2348 `autoscale_view` (the initial value is False, but the default style 

2349 sets :rc:`axes.autolimit_mode` to 'data', in which case this 

2350 behaves like True). 

2351 

2352 scalex : bool 

2353 Whether to autoscale the x axis (default is True). 

2354 

2355 scaley : bool 

2356 Whether to autoscale the x axis (default is True). 

2357 

2358 Notes 

2359 ----- 

2360 The autoscaling preserves any preexisting axis direction reversal. 

2361 

2362 The data limits are not updated automatically when artist data are 

2363 changed after the artist has been added to an Axes instance. In that 

2364 case, use :meth:`matplotlib.axes.Axes.relim` prior to calling 

2365 autoscale_view. 

2366 

2367 If the views of the axes are fixed, e.g. via `set_xlim`, they will 

2368 not be changed by autoscale_view(). 

2369 See :meth:`matplotlib.axes.Axes.autoscale` for an alternative. 

2370 """ 

2371 if tight is not None: 

2372 self._tight = bool(tight) 

2373 

2374 x_stickies = y_stickies = np.array([]) 

2375 if self.use_sticky_edges: 

2376 # Only iterate over axes and artists if needed. The check for 

2377 # ``hasattr(ax, "lines")`` is necessary because this can be called 

2378 # very early in the axes init process (e.g., for twin axes) when 

2379 # these attributes don't even exist yet, in which case 

2380 # `get_children` would raise an AttributeError. 

2381 if self._xmargin and scalex and self._autoscaleXon: 

2382 x_stickies = np.sort(np.concatenate([ 

2383 artist.sticky_edges.x 

2384 for ax in self._shared_x_axes.get_siblings(self) 

2385 if hasattr(ax, "lines") 

2386 for artist in ax.get_children()])) 

2387 if self._ymargin and scaley and self._autoscaleYon: 

2388 y_stickies = np.sort(np.concatenate([ 

2389 artist.sticky_edges.y 

2390 for ax in self._shared_y_axes.get_siblings(self) 

2391 if hasattr(ax, "lines") 

2392 for artist in ax.get_children()])) 

2393 if self.get_xscale().lower() == 'log': 

2394 x_stickies = x_stickies[x_stickies > 0] 

2395 if self.get_yscale().lower() == 'log': 

2396 y_stickies = y_stickies[y_stickies > 0] 

2397 

2398 def handle_single_axis(scale, autoscaleon, shared_axes, interval, 

2399 minpos, axis, margin, stickies, set_bound): 

2400 

2401 if not (scale and autoscaleon): 

2402 return # nothing to do... 

2403 

2404 shared = shared_axes.get_siblings(self) 

2405 dl = [ax.dataLim for ax in shared] 

2406 # ignore non-finite data limits if good limits exist 

2407 finite_dl = [d for d in dl if np.isfinite(d).all()] 

2408 if len(finite_dl): 

2409 # if finite limits exist for atleast one axis (and the 

2410 # other is infinite), restore the finite limits 

2411 x_finite = [d for d in dl 

2412 if (np.isfinite(d.intervalx).all() and 

2413 (d not in finite_dl))] 

2414 y_finite = [d for d in dl 

2415 if (np.isfinite(d.intervaly).all() and 

2416 (d not in finite_dl))] 

2417 

2418 dl = finite_dl 

2419 dl.extend(x_finite) 

2420 dl.extend(y_finite) 

2421 

2422 bb = mtransforms.BboxBase.union(dl) 

2423 x0, x1 = getattr(bb, interval) 

2424 # If x0 and x1 are non finite, use the locator to figure out 

2425 # default limits. 

2426 locator = axis.get_major_locator() 

2427 x0, x1 = locator.nonsingular(x0, x1) 

2428 

2429 # Prevent margin addition from crossing a sticky value. Small 

2430 # tolerances (whose values come from isclose()) must be used due to 

2431 # floating point issues with streamplot. 

2432 def tol(x): return 1e-5 * abs(x) + 1e-8 

2433 # Index of largest element < x0 + tol, if any. 

2434 i0 = stickies.searchsorted(x0 + tol(x0)) - 1 

2435 x0bound = stickies[i0] if i0 != -1 else None 

2436 # Index of smallest element > x1 - tol, if any. 

2437 i1 = stickies.searchsorted(x1 - tol(x1)) 

2438 x1bound = stickies[i1] if i1 != len(stickies) else None 

2439 

2440 # Add the margin in figure space and then transform back, to handle 

2441 # non-linear scales. 

2442 minpos = getattr(bb, minpos) 

2443 transform = axis.get_transform() 

2444 inverse_trans = transform.inverted() 

2445 x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos) 

2446 x0t, x1t = transform.transform([x0, x1]) 

2447 delta = (x1t - x0t) * margin 

2448 if not np.isfinite(delta): 

2449 delta = 0 # If a bound isn't finite, set margin to zero. 

2450 x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta]) 

2451 

2452 # Apply sticky bounds. 

2453 if x0bound is not None: 

2454 x0 = max(x0, x0bound) 

2455 if x1bound is not None: 

2456 x1 = min(x1, x1bound) 

2457 

2458 if not self._tight: 

2459 x0, x1 = locator.view_limits(x0, x1) 

2460 set_bound(x0, x1) 

2461 # End of definition of internal function 'handle_single_axis'. 

2462 

2463 handle_single_axis( 

2464 scalex, self._autoscaleXon, self._shared_x_axes, 'intervalx', 

2465 'minposx', self.xaxis, self._xmargin, x_stickies, self.set_xbound) 

2466 handle_single_axis( 

2467 scaley, self._autoscaleYon, self._shared_y_axes, 'intervaly', 

2468 'minposy', self.yaxis, self._ymargin, y_stickies, self.set_ybound) 

2469 

2470 def _get_axis_list(self): 

2471 return (self.xaxis, self.yaxis) 

2472 

2473 def _get_axis_map(self): 

2474 """ 

2475 Return a mapping of `Axis` "names" to `Axis` instances. 

2476 

2477 The `Axis` name is derived from the attribute under which the instance 

2478 is stored, so e.g. for polar axes, the theta-axis is still named "x" 

2479 and the r-axis is still named "y" (for back-compatibility). 

2480 

2481 In practice, this means that the entries are typically "x" and "y", and 

2482 additionally "z" for 3D axes. 

2483 """ 

2484 d = {} 

2485 axis_list = self._get_axis_list() 

2486 for k, v in vars(self).items(): 

2487 if k.endswith("axis") and v in axis_list: 

2488 d[k[:-len("axis")]] = v 

2489 return d 

2490 

2491 def _update_title_position(self, renderer): 

2492 """ 

2493 Update the title position based on the bounding box enclosing 

2494 all the ticklabels and x-axis spine and xlabel... 

2495 """ 

2496 

2497 if self._autotitlepos is not None and not self._autotitlepos: 

2498 _log.debug('title position was updated manually, not adjusting') 

2499 return 

2500 

2501 titles = (self.title, self._left_title, self._right_title) 

2502 

2503 if self._autotitlepos is None: 

2504 for title in titles: 

2505 x, y = title.get_position() 

2506 if not np.isclose(y, 1.0): 

2507 self._autotitlepos = False 

2508 _log.debug('not adjusting title pos because a title was' 

2509 ' already placed manually: %f', y) 

2510 return 

2511 self._autotitlepos = True 

2512 

2513 for title in titles: 

2514 x, _ = title.get_position() 

2515 # need to start again in case of window resizing 

2516 title.set_position((x, 1.0)) 

2517 # need to check all our twins too... 

2518 axs = self._twinned_axes.get_siblings(self) 

2519 # and all the children 

2520 for ax in self.child_axes: 

2521 if ax is not None: 

2522 locator = ax.get_axes_locator() 

2523 if locator: 

2524 pos = locator(self, renderer) 

2525 ax.apply_aspect(pos) 

2526 else: 

2527 ax.apply_aspect() 

2528 axs = axs + [ax] 

2529 top = 0 

2530 for ax in axs: 

2531 if (ax.xaxis.get_ticks_position() in ['top', 'unknown'] 

2532 or ax.xaxis.get_label_position() == 'top'): 

2533 bb = ax.xaxis.get_tightbbox(renderer) 

2534 else: 

2535 bb = ax.get_window_extent(renderer) 

2536 if bb is not None: 

2537 top = max(top, bb.ymax) 

2538 if title.get_window_extent(renderer).ymin < top: 

2539 _, y = self.transAxes.inverted().transform((0, top)) 

2540 title.set_position((x, y)) 

2541 # empirically, this doesn't always get the min to top, 

2542 # so we need to adjust again. 

2543 if title.get_window_extent(renderer).ymin < top: 

2544 _, y = self.transAxes.inverted().transform( 

2545 (0., 2 * top - title.get_window_extent(renderer).ymin)) 

2546 title.set_position((x, y)) 

2547 

2548 ymax = max(title.get_position()[1] for title in titles) 

2549 for title in titles: 

2550 # now line up all the titles at the highest baseline. 

2551 x, _ = title.get_position() 

2552 title.set_position((x, ymax)) 

2553 

2554 # Drawing 

2555 @martist.allow_rasterization 

2556 def draw(self, renderer=None, inframe=False): 

2557 """Draw everything (plot lines, axes, labels)""" 

2558 if renderer is None: 

2559 renderer = self.figure._cachedRenderer 

2560 if renderer is None: 

2561 raise RuntimeError('No renderer defined') 

2562 if not self.get_visible(): 

2563 return 

2564 self._unstale_viewLim() 

2565 

2566 renderer.open_group('axes', gid=self.get_gid()) 

2567 

2568 # prevent triggering call backs during the draw process 

2569 self._stale = True 

2570 

2571 # loop over self and child axes... 

2572 locator = self.get_axes_locator() 

2573 if locator: 

2574 pos = locator(self, renderer) 

2575 self.apply_aspect(pos) 

2576 else: 

2577 self.apply_aspect() 

2578 

2579 artists = self.get_children() 

2580 artists.remove(self.patch) 

2581 

2582 # the frame draws the edges around the axes patch -- we 

2583 # decouple these so the patch can be in the background and the 

2584 # frame in the foreground. Do this before drawing the axis 

2585 # objects so that the spine has the opportunity to update them. 

2586 if not (self.axison and self._frameon): 

2587 for spine in self.spines.values(): 

2588 artists.remove(spine) 

2589 

2590 self._update_title_position(renderer) 

2591 

2592 if not self.axison or inframe: 

2593 for _axis in self._get_axis_list(): 

2594 artists.remove(_axis) 

2595 

2596 if inframe: 

2597 artists.remove(self.title) 

2598 artists.remove(self._left_title) 

2599 artists.remove(self._right_title) 

2600 

2601 if not self.figure.canvas.is_saving(): 

2602 artists = [a for a in artists 

2603 if not a.get_animated() or a in self.images] 

2604 artists = sorted(artists, key=attrgetter('zorder')) 

2605 

2606 # rasterize artists with negative zorder 

2607 # if the minimum zorder is negative, start rasterization 

2608 rasterization_zorder = self._rasterization_zorder 

2609 

2610 if (rasterization_zorder is not None and 

2611 artists and artists[0].zorder < rasterization_zorder): 

2612 renderer.start_rasterizing() 

2613 artists_rasterized = [a for a in artists 

2614 if a.zorder < rasterization_zorder] 

2615 artists = [a for a in artists 

2616 if a.zorder >= rasterization_zorder] 

2617 else: 

2618 artists_rasterized = [] 

2619 

2620 # the patch draws the background rectangle -- the frame below 

2621 # will draw the edges 

2622 if self.axison and self._frameon: 

2623 self.patch.draw(renderer) 

2624 

2625 if artists_rasterized: 

2626 for a in artists_rasterized: 

2627 a.draw(renderer) 

2628 renderer.stop_rasterizing() 

2629 

2630 mimage._draw_list_compositing_images(renderer, self, artists) 

2631 

2632 renderer.close_group('axes') 

2633 self.stale = False 

2634 

2635 def draw_artist(self, a): 

2636 """ 

2637 This method can only be used after an initial draw which 

2638 caches the renderer. It is used to efficiently update Axes 

2639 data (axis ticks, labels, etc are not updated) 

2640 """ 

2641 if self.figure._cachedRenderer is None: 

2642 raise AttributeError("draw_artist can only be used after an " 

2643 "initial draw which caches the renderer") 

2644 a.draw(self.figure._cachedRenderer) 

2645 

2646 def redraw_in_frame(self): 

2647 """ 

2648 This method can only be used after an initial draw which 

2649 caches the renderer. It is used to efficiently update Axes 

2650 data (axis ticks, labels, etc are not updated) 

2651 """ 

2652 if self.figure._cachedRenderer is None: 

2653 raise AttributeError("redraw_in_frame can only be used after an " 

2654 "initial draw which caches the renderer") 

2655 self.draw(self.figure._cachedRenderer, inframe=True) 

2656 

2657 def get_renderer_cache(self): 

2658 return self.figure._cachedRenderer 

2659 

2660 # Axes rectangle characteristics 

2661 

2662 def get_frame_on(self): 

2663 """Get whether the axes rectangle patch is drawn.""" 

2664 return self._frameon 

2665 

2666 def set_frame_on(self, b): 

2667 """ 

2668 Set whether the axes rectangle patch is drawn. 

2669 

2670 Parameters 

2671 ---------- 

2672 b : bool 

2673 """ 

2674 self._frameon = b 

2675 self.stale = True 

2676 

2677 def get_axisbelow(self): 

2678 """ 

2679 Get whether axis ticks and gridlines are above or below most artists. 

2680 

2681 Returns 

2682 ------- 

2683 axisbelow : bool or 'line' 

2684 

2685 See Also 

2686 -------- 

2687 set_axisbelow 

2688 """ 

2689 return self._axisbelow 

2690 

2691 def set_axisbelow(self, b): 

2692 """ 

2693 Set whether axis ticks and gridlines are above or below most artists. 

2694 

2695 This controls the zorder of the ticks and gridlines. For more 

2696 information on the zorder see :doc:`/gallery/misc/zorder_demo`. 

2697 

2698 Parameters 

2699 ---------- 

2700 b : bool or 'line' 

2701 Possible values: 

2702 

2703 - *True* (zorder = 0.5): Ticks and gridlines are below all Artists. 

2704 - 'line' (zorder = 1.5): Ticks and gridlines are above patches 

2705 (e.g. rectangles, with default zorder = 1) but still below lines 

2706 and markers (with their default zorder = 2). 

2707 - *False* (zorder = 2.5): Ticks and gridlines are above patches 

2708 and lines / markers. 

2709 

2710 See Also 

2711 -------- 

2712 get_axisbelow 

2713 """ 

2714 self._axisbelow = axisbelow = validate_axisbelow(b) 

2715 if axisbelow is True: 

2716 zorder = 0.5 

2717 elif axisbelow is False: 

2718 zorder = 2.5 

2719 elif axisbelow == "line": 

2720 zorder = 1.5 

2721 else: 

2722 raise ValueError("Unexpected axisbelow value") 

2723 for axis in self._get_axis_list(): 

2724 axis.set_zorder(zorder) 

2725 self.stale = True 

2726 

2727 @docstring.dedent_interpd 

2728 def grid(self, b=None, which='major', axis='both', **kwargs): 

2729 """ 

2730 Configure the grid lines. 

2731 

2732 Parameters 

2733 ---------- 

2734 b : bool or None, optional 

2735 Whether to show the grid lines. If any *kwargs* are supplied, 

2736 it is assumed you want the grid on and *b* will be set to True. 

2737 

2738 If *b* is *None* and there are no *kwargs*, this toggles the 

2739 visibility of the lines. 

2740 

2741 which : {'major', 'minor', 'both'}, optional 

2742 The grid lines to apply the changes on. 

2743 

2744 axis : {'both', 'x', 'y'}, optional 

2745 The axis to apply the changes on. 

2746 

2747 **kwargs : `.Line2D` properties 

2748 Define the line properties of the grid, e.g.:: 

2749 

2750 grid(color='r', linestyle='-', linewidth=2) 

2751 

2752 Valid keyword arguments are: 

2753 

2754 %(_Line2D_docstr)s 

2755 

2756 Notes 

2757 ----- 

2758 The axis is drawn as a unit, so the effective zorder for drawing the 

2759 grid is determined by the zorder of each axis, not by the zorder of the 

2760 `.Line2D` objects comprising the grid. Therefore, to set grid zorder, 

2761 use `.set_axisbelow` or, for more control, call the 

2762 `~matplotlib.axis.Axis.set_zorder` method of each axis. 

2763 """ 

2764 if len(kwargs): 

2765 b = True 

2766 cbook._check_in_list(['x', 'y', 'both'], axis=axis) 

2767 if axis in ['x', 'both']: 

2768 self.xaxis.grid(b, which=which, **kwargs) 

2769 if axis in ['y', 'both']: 

2770 self.yaxis.grid(b, which=which, **kwargs) 

2771 

2772 def ticklabel_format(self, *, axis='both', style='', scilimits=None, 

2773 useOffset=None, useLocale=None, useMathText=None): 

2774 r""" 

2775 Change the `~matplotlib.ticker.ScalarFormatter` used by 

2776 default for linear axes. 

2777 

2778 Optional keyword arguments: 

2779 

2780 ============== ========================================= 

2781 Keyword Description 

2782 ============== ========================================= 

2783 *axis* {'x', 'y', 'both'} 

2784 *style* {'sci' (or 'scientific'), 'plain'} 

2785 plain turns off scientific notation 

2786 *scilimits* (m, n), pair of integers; if *style* 

2787 is 'sci', scientific notation will 

2788 be used for numbers outside the range 

2789 10\ :sup:`m` to 10\ :sup:`n`. 

2790 Use (0, 0) to include all numbers. 

2791 Use (m, m) where m != 0 to fix the order 

2792 of magnitude to 10\ :sup:`m`. 

2793 *useOffset* bool or float 

2794 If True, the offset will be calculated as 

2795 needed; if False, no offset will be used; 

2796 if a numeric offset is specified, it will 

2797 be used. 

2798 *useLocale* If True, format the number according to 

2799 the current locale. This affects things 

2800 such as the character used for the 

2801 decimal separator. If False, use 

2802 C-style (English) formatting. The 

2803 default setting is controlled by the 

2804 axes.formatter.use_locale rcparam. 

2805 *useMathText* If True, render the offset and scientific 

2806 notation in mathtext 

2807 ============== ========================================= 

2808 

2809 Only the major ticks are affected. 

2810 If the method is called when the `~matplotlib.ticker.ScalarFormatter` 

2811 is not the `~matplotlib.ticker.Formatter` being used, an 

2812 `AttributeError` will be raised. 

2813 """ 

2814 style = style.lower() 

2815 axis = axis.lower() 

2816 if scilimits is not None: 

2817 try: 

2818 m, n = scilimits 

2819 m + n + 1 # check that both are numbers 

2820 except (ValueError, TypeError): 

2821 raise ValueError("scilimits must be a sequence of 2 integers") 

2822 STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None} 

2823 is_sci_style = cbook._check_getitem(STYLES, style=style) 

2824 axis_map = {**{k: [v] for k, v in self._get_axis_map().items()}, 

2825 'both': self._get_axis_list()} 

2826 axises = cbook._check_getitem(axis_map, axis=axis) 

2827 try: 

2828 for axis in axises: 

2829 if is_sci_style is not None: 

2830 axis.major.formatter.set_scientific(is_sci_style) 

2831 if scilimits is not None: 

2832 axis.major.formatter.set_powerlimits(scilimits) 

2833 if useOffset is not None: 

2834 axis.major.formatter.set_useOffset(useOffset) 

2835 if useLocale is not None: 

2836 axis.major.formatter.set_useLocale(useLocale) 

2837 if useMathText is not None: 

2838 axis.major.formatter.set_useMathText(useMathText) 

2839 except AttributeError: 

2840 raise AttributeError( 

2841 "This method only works with the ScalarFormatter") 

2842 

2843 def locator_params(self, axis='both', tight=None, **kwargs): 

2844 """ 

2845 Control behavior of major tick locators. 

2846 

2847 Because the locator is involved in autoscaling, `~.Axes.autoscale_view` 

2848 is called automatically after the parameters are changed. 

2849 

2850 Parameters 

2851 ---------- 

2852 axis : {'both', 'x', 'y'}, optional 

2853 The axis on which to operate. 

2854 

2855 tight : bool or None, optional 

2856 Parameter passed to `~.Axes.autoscale_view`. 

2857 Default is None, for no change. 

2858 

2859 Other Parameters 

2860 ---------------- 

2861 **kwargs 

2862 Remaining keyword arguments are passed to directly to the 

2863 ``set_params()`` method of the locator. Supported keywords depend 

2864 on the type of the locator. See for example 

2865 `~.ticker.MaxNLocator.set_params` for the `.ticker.MaxNLocator` 

2866 used by default for linear axes. 

2867 

2868 Examples 

2869 -------- 

2870 When plotting small subplots, one might want to reduce the maximum 

2871 number of ticks and use tight bounds, for example:: 

2872 

2873 ax.locator_params(tight=True, nbins=4) 

2874 

2875 """ 

2876 _x = axis in ['x', 'both'] 

2877 _y = axis in ['y', 'both'] 

2878 if _x: 

2879 self.xaxis.get_major_locator().set_params(**kwargs) 

2880 if _y: 

2881 self.yaxis.get_major_locator().set_params(**kwargs) 

2882 self._request_autoscale_view(tight=tight, scalex=_x, scaley=_y) 

2883 

2884 def tick_params(self, axis='both', **kwargs): 

2885 """Change the appearance of ticks, tick labels, and gridlines. 

2886 

2887 Parameters 

2888 ---------- 

2889 axis : {'x', 'y', 'both'}, optional 

2890 Which axis to apply the parameters to. 

2891 

2892 Other Parameters 

2893 ---------------- 

2894 axis : {'x', 'y', 'both'} 

2895 Axis on which to operate; default is 'both'. 

2896 reset : bool, default: False 

2897 If *True*, set all parameters to defaults before processing other 

2898 keyword arguments. 

2899 which : {'major', 'minor', 'both'} 

2900 Default is 'major'; apply arguments to *which* ticks. 

2901 direction : {'in', 'out', 'inout'} 

2902 Puts ticks inside the axes, outside the axes, or both. 

2903 length : float 

2904 Tick length in points. 

2905 width : float 

2906 Tick width in points. 

2907 color : color 

2908 Tick color. 

2909 pad : float 

2910 Distance in points between tick and label. 

2911 labelsize : float or str 

2912 Tick label font size in points or as a string (e.g., 'large'). 

2913 labelcolor : color 

2914 Tick label color. 

2915 colors : color 

2916 Tick color and label color. 

2917 zorder : float 

2918 Tick and label zorder. 

2919 bottom, top, left, right : bool 

2920 Whether to draw the respective ticks. 

2921 labelbottom, labeltop, labelleft, labelright : bool 

2922 Whether to draw the respective tick labels. 

2923 labelrotation : float 

2924 Tick label rotation 

2925 grid_color : color 

2926 Gridline color. 

2927 grid_alpha : float 

2928 Transparency of gridlines: 0 (transparent) to 1 (opaque). 

2929 grid_linewidth : float 

2930 Width of gridlines in points. 

2931 grid_linestyle : str 

2932 Any valid `.Line2D` line style spec. 

2933 

2934 Examples 

2935 -------- 

2936 Usage :: 

2937 

2938 ax.tick_params(direction='out', length=6, width=2, colors='r', 

2939 grid_color='r', grid_alpha=0.5) 

2940 

2941 This will make all major ticks be red, pointing out of the box, 

2942 and with dimensions 6 points by 2 points. Tick labels will 

2943 also be red. Gridlines will be red and translucent. 

2944 

2945 """ 

2946 cbook._check_in_list(['x', 'y', 'both'], axis=axis) 

2947 if axis in ['x', 'both']: 

2948 xkw = dict(kwargs) 

2949 xkw.pop('left', None) 

2950 xkw.pop('right', None) 

2951 xkw.pop('labelleft', None) 

2952 xkw.pop('labelright', None) 

2953 self.xaxis.set_tick_params(**xkw) 

2954 if axis in ['y', 'both']: 

2955 ykw = dict(kwargs) 

2956 ykw.pop('top', None) 

2957 ykw.pop('bottom', None) 

2958 ykw.pop('labeltop', None) 

2959 ykw.pop('labelbottom', None) 

2960 self.yaxis.set_tick_params(**ykw) 

2961 

2962 def set_axis_off(self): 

2963 """ 

2964 Turn the x- and y-axis off. 

2965 

2966 This affects the axis lines, ticks, ticklabels, grid and axis labels. 

2967 """ 

2968 self.axison = False 

2969 self.stale = True 

2970 

2971 def set_axis_on(self): 

2972 """ 

2973 Turn the x- and y-axis on. 

2974 

2975 This affects the axis lines, ticks, ticklabels, grid and axis labels. 

2976 """ 

2977 self.axison = True 

2978 self.stale = True 

2979 

2980 # data limits, ticks, tick labels, and formatting 

2981 

2982 def invert_xaxis(self): 

2983 """ 

2984 Invert the x-axis. 

2985 

2986 See Also 

2987 -------- 

2988 xaxis_inverted 

2989 get_xlim, set_xlim 

2990 get_xbound, set_xbound 

2991 """ 

2992 self.xaxis.set_inverted(not self.xaxis.get_inverted()) 

2993 

2994 def xaxis_inverted(self): 

2995 """ 

2996 Return whether the x-axis is inverted. 

2997 

2998 The axis is inverted if the left value is larger than the right value. 

2999 

3000 See Also 

3001 -------- 

3002 invert_xaxis 

3003 get_xlim, set_xlim 

3004 get_xbound, set_xbound 

3005 """ 

3006 return self.xaxis.get_inverted() 

3007 

3008 def get_xbound(self): 

3009 """ 

3010 Return the lower and upper x-axis bounds, in increasing order. 

3011 

3012 See Also 

3013 -------- 

3014 set_xbound 

3015 get_xlim, set_xlim 

3016 invert_xaxis, xaxis_inverted 

3017 """ 

3018 left, right = self.get_xlim() 

3019 if left < right: 

3020 return left, right 

3021 else: 

3022 return right, left 

3023 

3024 def set_xbound(self, lower=None, upper=None): 

3025 """ 

3026 Set the lower and upper numerical bounds of the x-axis. 

3027 

3028 This method will honor axes inversion regardless of parameter order. 

3029 It will not change the autoscaling setting (``Axes._autoscaleXon``). 

3030 

3031 Parameters 

3032 ---------- 

3033 lower, upper : float or None 

3034 The lower and upper bounds. If *None*, the respective axis bound 

3035 is not modified. 

3036 

3037 See Also 

3038 -------- 

3039 get_xbound 

3040 get_xlim, set_xlim 

3041 invert_xaxis, xaxis_inverted 

3042 """ 

3043 if upper is None and np.iterable(lower): 

3044 lower, upper = lower 

3045 

3046 old_lower, old_upper = self.get_xbound() 

3047 

3048 if lower is None: 

3049 lower = old_lower 

3050 if upper is None: 

3051 upper = old_upper 

3052 

3053 if self.xaxis_inverted(): 

3054 if lower < upper: 

3055 self.set_xlim(upper, lower, auto=None) 

3056 else: 

3057 self.set_xlim(lower, upper, auto=None) 

3058 else: 

3059 if lower < upper: 

3060 self.set_xlim(lower, upper, auto=None) 

3061 else: 

3062 self.set_xlim(upper, lower, auto=None) 

3063 

3064 def get_xlim(self): 

3065 """ 

3066 Return the x-axis view limits. 

3067 

3068 Returns 

3069 ------- 

3070 left, right : (float, float) 

3071 The current x-axis limits in data coordinates. 

3072 

3073 See Also 

3074 -------- 

3075 set_xlim 

3076 set_xbound, get_xbound 

3077 invert_xaxis, xaxis_inverted 

3078 

3079 Notes 

3080 ----- 

3081 The x-axis may be inverted, in which case the *left* value will 

3082 be greater than the *right* value. 

3083 

3084 """ 

3085 return tuple(self.viewLim.intervalx) 

3086 

3087 def _validate_converted_limits(self, limit, convert): 

3088 """ 

3089 Raise ValueError if converted limits are non-finite. 

3090 

3091 Note that this function also accepts None as a limit argument. 

3092 

3093 Returns 

3094 ------- 

3095 The limit value after call to convert(), or None if limit is None. 

3096 """ 

3097 if limit is not None: 

3098 converted_limit = convert(limit) 

3099 if (isinstance(converted_limit, Real) 

3100 and not np.isfinite(converted_limit)): 

3101 raise ValueError("Axis limits cannot be NaN or Inf") 

3102 return converted_limit 

3103 

3104 def set_xlim(self, left=None, right=None, emit=True, auto=False, 

3105 *, xmin=None, xmax=None): 

3106 """ 

3107 Set the x-axis view limits. 

3108 

3109 Parameters 

3110 ---------- 

3111 left : float, optional 

3112 The left xlim in data coordinates. Passing *None* leaves the 

3113 limit unchanged. 

3114 

3115 The left and right xlims may also be passed as the tuple 

3116 (*left*, *right*) as the first positional argument (or as 

3117 the *left* keyword argument). 

3118 

3119 .. ACCEPTS: (bottom: float, top: float) 

3120 

3121 right : float, optional 

3122 The right xlim in data coordinates. Passing *None* leaves the 

3123 limit unchanged. 

3124 

3125 emit : bool, optional 

3126 Whether to notify observers of limit change (default: True). 

3127 

3128 auto : bool or None, optional 

3129 Whether to turn on autoscaling of the x-axis. True turns on, 

3130 False turns off (default action), None leaves unchanged. 

3131 

3132 xmin, xmax : scalar, optional 

3133 They are equivalent to left and right respectively, 

3134 and it is an error to pass both *xmin* and *left* or 

3135 *xmax* and *right*. 

3136 

3137 Returns 

3138 ------- 

3139 left, right : (float, float) 

3140 The new x-axis limits in data coordinates. 

3141 

3142 See Also 

3143 -------- 

3144 get_xlim 

3145 set_xbound, get_xbound 

3146 invert_xaxis, xaxis_inverted 

3147 

3148 Notes 

3149 ----- 

3150 The *left* value may be greater than the *right* value, in which 

3151 case the x-axis values will decrease from left to right. 

3152 

3153 Examples 

3154 -------- 

3155 >>> set_xlim(left, right) 

3156 >>> set_xlim((left, right)) 

3157 >>> left, right = set_xlim(left, right) 

3158 

3159 One limit may be left unchanged. 

3160 

3161 >>> set_xlim(right=right_lim) 

3162 

3163 Limits may be passed in reverse order to flip the direction of 

3164 the x-axis. For example, suppose *x* represents the number of 

3165 years before present. The x-axis limits might be set like the 

3166 following so 5000 years ago is on the left of the plot and the 

3167 present is on the right. 

3168 

3169 >>> set_xlim(5000, 0) 

3170 

3171 """ 

3172 if right is None and np.iterable(left): 

3173 left, right = left 

3174 if xmin is not None: 

3175 if left is not None: 

3176 raise TypeError('Cannot pass both `xmin` and `left`') 

3177 left = xmin 

3178 if xmax is not None: 

3179 if right is not None: 

3180 raise TypeError('Cannot pass both `xmax` and `right`') 

3181 right = xmax 

3182 

3183 self._process_unit_info(xdata=(left, right)) 

3184 left = self._validate_converted_limits(left, self.convert_xunits) 

3185 right = self._validate_converted_limits(right, self.convert_xunits) 

3186 

3187 if left is None or right is None: 

3188 # Axes init calls set_xlim(0, 1) before get_xlim() can be called, 

3189 # so only grab the limits if we really need them. 

3190 old_left, old_right = self.get_xlim() 

3191 if left is None: 

3192 left = old_left 

3193 if right is None: 

3194 right = old_right 

3195 

3196 if self.get_xscale() == 'log' and (left <= 0 or right <= 0): 

3197 # Axes init calls set_xlim(0, 1) before get_xlim() can be called, 

3198 # so only grab the limits if we really need them. 

3199 old_left, old_right = self.get_xlim() 

3200 if left <= 0: 

3201 cbook._warn_external( 

3202 'Attempted to set non-positive left xlim on a ' 

3203 'log-scaled axis.\n' 

3204 'Invalid limit will be ignored.') 

3205 left = old_left 

3206 if right <= 0: 

3207 cbook._warn_external( 

3208 'Attempted to set non-positive right xlim on a ' 

3209 'log-scaled axis.\n' 

3210 'Invalid limit will be ignored.') 

3211 right = old_right 

3212 if left == right: 

3213 cbook._warn_external( 

3214 f"Attempting to set identical left == right == {left} results " 

3215 f"in singular transformations; automatically expanding.") 

3216 reverse = left > right 

3217 left, right = self.xaxis.get_major_locator().nonsingular(left, right) 

3218 left, right = self.xaxis.limit_range_for_scale(left, right) 

3219 # cast to bool to avoid bad interaction between python 3.8 and np.bool_ 

3220 left, right = sorted([left, right], reverse=bool(reverse)) 

3221 

3222 self._viewLim.intervalx = (left, right) 

3223 # Mark viewlims as no longer stale without triggering an autoscale. 

3224 for ax in self._shared_x_axes.get_siblings(self): 

3225 ax._stale_viewlim_x = False 

3226 if auto is not None: 

3227 self._autoscaleXon = bool(auto) 

3228 

3229 if emit: 

3230 self.callbacks.process('xlim_changed', self) 

3231 # Call all of the other x-axes that are shared with this one 

3232 for other in self._shared_x_axes.get_siblings(self): 

3233 if other is not self: 

3234 other.set_xlim(self.viewLim.intervalx, 

3235 emit=False, auto=auto) 

3236 if other.figure != self.figure: 

3237 other.figure.canvas.draw_idle() 

3238 self.stale = True 

3239 return left, right 

3240 

3241 def get_xscale(self): 

3242 """ 

3243 Return the x-axis scale as string. 

3244 

3245 See Also 

3246 -------- 

3247 set_xscale 

3248 """ 

3249 return self.xaxis.get_scale() 

3250 

3251 def set_xscale(self, value, **kwargs): 

3252 """ 

3253 Set the x-axis scale. 

3254 

3255 Parameters 

3256 ---------- 

3257 value : {"linear", "log", "symlog", "logit", ...} 

3258 The axis scale type to apply. 

3259 

3260 **kwargs 

3261 Different keyword arguments are accepted, depending on the scale. 

3262 See the respective class keyword arguments: 

3263 

3264 - `matplotlib.scale.LinearScale` 

3265 - `matplotlib.scale.LogScale` 

3266 - `matplotlib.scale.SymmetricalLogScale` 

3267 - `matplotlib.scale.LogitScale` 

3268 

3269 Notes 

3270 ----- 

3271 By default, Matplotlib supports the above mentioned scales. 

3272 Additionally, custom scales may be registered using 

3273 `matplotlib.scale.register_scale`. These scales can then also 

3274 be used here. 

3275 """ 

3276 old_default_lims = (self.xaxis.get_major_locator() 

3277 .nonsingular(-np.inf, np.inf)) 

3278 g = self.get_shared_x_axes() 

3279 for ax in g.get_siblings(self): 

3280 ax.xaxis._set_scale(value, **kwargs) 

3281 ax._update_transScale() 

3282 ax.stale = True 

3283 new_default_lims = (self.xaxis.get_major_locator() 

3284 .nonsingular(-np.inf, np.inf)) 

3285 if old_default_lims != new_default_lims: 

3286 # Force autoscaling now, to take advantage of the scale locator's 

3287 # nonsingular() before it possibly gets swapped out by the user. 

3288 self.autoscale_view(scaley=False) 

3289 

3290 @cbook._make_keyword_only("3.2", "minor") 

3291 def get_xticks(self, minor=False): 

3292 """Return the x ticks as a list of locations""" 

3293 return self.xaxis.get_ticklocs(minor=minor) 

3294 

3295 @cbook._make_keyword_only("3.2", "minor") 

3296 def set_xticks(self, ticks, minor=False): 

3297 """ 

3298 Set the x ticks with list of *ticks* 

3299 

3300 Parameters 

3301 ---------- 

3302 ticks : list 

3303 List of x-axis tick locations. 

3304 

3305 minor : bool, optional 

3306 If ``False`` sets major ticks, if ``True`` sets minor ticks. 

3307 Default is ``False``. 

3308 """ 

3309 ret = self.xaxis.set_ticks(ticks, minor=minor) 

3310 self.stale = True 

3311 return ret 

3312 

3313 def get_xmajorticklabels(self): 

3314 """ 

3315 Get the major x tick labels. 

3316 

3317 Returns 

3318 ------- 

3319 labels : list 

3320 List of `~matplotlib.text.Text` instances 

3321 """ 

3322 return self.xaxis.get_majorticklabels() 

3323 

3324 def get_xminorticklabels(self): 

3325 """ 

3326 Get the minor x tick labels. 

3327 

3328 Returns 

3329 ------- 

3330 labels : list 

3331 List of `~matplotlib.text.Text` instances 

3332 """ 

3333 return self.xaxis.get_minorticklabels() 

3334 

3335 def get_xticklabels(self, minor=False, which=None): 

3336 """ 

3337 Get the x tick labels as a list of `~matplotlib.text.Text` instances. 

3338 

3339 Parameters 

3340 ---------- 

3341 minor : bool, optional 

3342 If True return the minor ticklabels, 

3343 else return the major ticklabels. 

3344 

3345 which : None, ('minor', 'major', 'both') 

3346 Overrides *minor*. 

3347 

3348 Selects which ticklabels to return 

3349 

3350 Returns 

3351 ------- 

3352 ret : list 

3353 List of `~matplotlib.text.Text` instances. 

3354 """ 

3355 return self.xaxis.get_ticklabels(minor=minor, which=which) 

3356 

3357 def set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs): 

3358 """ 

3359 Set the x-tick labels with list of string labels. 

3360 

3361 Parameters 

3362 ---------- 

3363 labels : List[str] 

3364 List of string labels. 

3365 

3366 fontdict : dict, optional 

3367 A dictionary controlling the appearance of the ticklabels. 

3368 The default *fontdict* is:: 

3369 

3370 {'fontsize': rcParams['axes.titlesize'], 

3371 'fontweight': rcParams['axes.titleweight'], 

3372 'verticalalignment': 'baseline', 

3373 'horizontalalignment': loc} 

3374 

3375 minor : bool, optional 

3376 Whether to set the minor ticklabels rather than the major ones. 

3377 

3378 Returns 

3379 ------- 

3380 A list of `~.text.Text` instances. 

3381 

3382 Other Parameters 

3383 ----------------- 

3384 **kwargs : `~.text.Text` properties. 

3385 """ 

3386 if fontdict is not None: 

3387 kwargs.update(fontdict) 

3388 ret = self.xaxis.set_ticklabels(labels, 

3389 minor=minor, **kwargs) 

3390 self.stale = True 

3391 return ret 

3392 

3393 def invert_yaxis(self): 

3394 """ 

3395 Invert the y-axis. 

3396 

3397 See Also 

3398 -------- 

3399 yaxis_inverted 

3400 get_ylim, set_ylim 

3401 get_ybound, set_ybound 

3402 """ 

3403 self.yaxis.set_inverted(not self.yaxis.get_inverted()) 

3404 

3405 def yaxis_inverted(self): 

3406 """ 

3407 Return whether the y-axis is inverted. 

3408 

3409 The axis is inverted if the bottom value is larger than the top value. 

3410 

3411 See Also 

3412 -------- 

3413 invert_yaxis 

3414 get_ylim, set_ylim 

3415 get_ybound, set_ybound 

3416 """ 

3417 return self.yaxis.get_inverted() 

3418 

3419 def get_ybound(self): 

3420 """ 

3421 Return the lower and upper y-axis bounds, in increasing order. 

3422 

3423 See Also 

3424 -------- 

3425 set_ybound 

3426 get_ylim, set_ylim 

3427 invert_yaxis, yaxis_inverted 

3428 """ 

3429 bottom, top = self.get_ylim() 

3430 if bottom < top: 

3431 return bottom, top 

3432 else: 

3433 return top, bottom 

3434 

3435 def set_ybound(self, lower=None, upper=None): 

3436 """ 

3437 Set the lower and upper numerical bounds of the y-axis. 

3438 

3439 This method will honor axes inversion regardless of parameter order. 

3440 It will not change the autoscaling setting (``Axes._autoscaleYon``). 

3441 

3442 Parameters 

3443 ---------- 

3444 lower, upper : float or None 

3445 The lower and upper bounds. If *None*, the respective axis bound 

3446 is not modified. 

3447 

3448 See Also 

3449 -------- 

3450 get_ybound 

3451 get_ylim, set_ylim 

3452 invert_yaxis, yaxis_inverted 

3453 """ 

3454 if upper is None and np.iterable(lower): 

3455 lower, upper = lower 

3456 

3457 old_lower, old_upper = self.get_ybound() 

3458 

3459 if lower is None: 

3460 lower = old_lower 

3461 if upper is None: 

3462 upper = old_upper 

3463 

3464 if self.yaxis_inverted(): 

3465 if lower < upper: 

3466 self.set_ylim(upper, lower, auto=None) 

3467 else: 

3468 self.set_ylim(lower, upper, auto=None) 

3469 else: 

3470 if lower < upper: 

3471 self.set_ylim(lower, upper, auto=None) 

3472 else: 

3473 self.set_ylim(upper, lower, auto=None) 

3474 

3475 def get_ylim(self): 

3476 """ 

3477 Return the y-axis view limits. 

3478 

3479 Returns 

3480 ------- 

3481 bottom, top : (float, float) 

3482 The current y-axis limits in data coordinates. 

3483 

3484 See Also 

3485 -------- 

3486 set_ylim 

3487 set_ybound, get_ybound 

3488 invert_yaxis, yaxis_inverted 

3489 

3490 Notes 

3491 ----- 

3492 The y-axis may be inverted, in which case the *bottom* value 

3493 will be greater than the *top* value. 

3494 

3495 """ 

3496 return tuple(self.viewLim.intervaly) 

3497 

3498 def set_ylim(self, bottom=None, top=None, emit=True, auto=False, 

3499 *, ymin=None, ymax=None): 

3500 """ 

3501 Set the y-axis view limits. 

3502 

3503 Parameters 

3504 ---------- 

3505 bottom : float, optional 

3506 The bottom ylim in data coordinates. Passing *None* leaves the 

3507 limit unchanged. 

3508 

3509 The bottom and top ylims may also be passed as the tuple 

3510 (*bottom*, *top*) as the first positional argument (or as 

3511 the *bottom* keyword argument). 

3512 

3513 .. ACCEPTS: (bottom: float, top: float) 

3514 

3515 top : float, optional 

3516 The top ylim in data coordinates. Passing *None* leaves the 

3517 limit unchanged. 

3518 

3519 emit : bool, optional 

3520 Whether to notify observers of limit change (default: ``True``). 

3521 

3522 auto : bool or None, optional 

3523 Whether to turn on autoscaling of the y-axis. *True* turns on, 

3524 *False* turns off (default action), *None* leaves unchanged. 

3525 

3526 ymin, ymax : scalar, optional 

3527 They are equivalent to bottom and top respectively, 

3528 and it is an error to pass both *ymin* and *bottom* or 

3529 *ymax* and *top*. 

3530 

3531 Returns 

3532 ------- 

3533 bottom, top : (float, float) 

3534 The new y-axis limits in data coordinates. 

3535 

3536 See Also 

3537 -------- 

3538 get_ylim 

3539 set_ybound, get_ybound 

3540 invert_yaxis, yaxis_inverted 

3541 

3542 Notes 

3543 ----- 

3544 The *bottom* value may be greater than the *top* value, in which 

3545 case the y-axis values will decrease from *bottom* to *top*. 

3546 

3547 Examples 

3548 -------- 

3549 >>> set_ylim(bottom, top) 

3550 >>> set_ylim((bottom, top)) 

3551 >>> bottom, top = set_ylim(bottom, top) 

3552 

3553 One limit may be left unchanged. 

3554 

3555 >>> set_ylim(top=top_lim) 

3556 

3557 Limits may be passed in reverse order to flip the direction of 

3558 the y-axis. For example, suppose ``y`` represents depth of the 

3559 ocean in m. The y-axis limits might be set like the following 

3560 so 5000 m depth is at the bottom of the plot and the surface, 

3561 0 m, is at the top. 

3562 

3563 >>> set_ylim(5000, 0) 

3564 """ 

3565 if top is None and np.iterable(bottom): 

3566 bottom, top = bottom 

3567 if ymin is not None: 

3568 if bottom is not None: 

3569 raise TypeError('Cannot pass both `ymin` and `bottom`') 

3570 bottom = ymin 

3571 if ymax is not None: 

3572 if top is not None: 

3573 raise TypeError('Cannot pass both `ymax` and `top`') 

3574 top = ymax 

3575 

3576 self._process_unit_info(ydata=(bottom, top)) 

3577 bottom = self._validate_converted_limits(bottom, self.convert_yunits) 

3578 top = self._validate_converted_limits(top, self.convert_yunits) 

3579 

3580 if bottom is None or top is None: 

3581 # Axes init calls set_ylim(0, 1) before get_ylim() can be called, 

3582 # so only grab the limits if we really need them. 

3583 old_bottom, old_top = self.get_ylim() 

3584 if bottom is None: 

3585 bottom = old_bottom 

3586 if top is None: 

3587 top = old_top 

3588 

3589 if self.get_yscale() == 'log' and (bottom <= 0 or top <= 0): 

3590 # Axes init calls set_xlim(0, 1) before get_xlim() can be called, 

3591 # so only grab the limits if we really need them. 

3592 old_bottom, old_top = self.get_ylim() 

3593 if bottom <= 0: 

3594 cbook._warn_external( 

3595 'Attempted to set non-positive bottom ylim on a ' 

3596 'log-scaled axis.\n' 

3597 'Invalid limit will be ignored.') 

3598 bottom = old_bottom 

3599 if top <= 0: 

3600 cbook._warn_external( 

3601 'Attempted to set non-positive top ylim on a ' 

3602 'log-scaled axis.\n' 

3603 'Invalid limit will be ignored.') 

3604 top = old_top 

3605 if bottom == top: 

3606 cbook._warn_external( 

3607 f"Attempting to set identical bottom == top == {bottom} " 

3608 f"results in singular transformations; automatically " 

3609 f"expanding.") 

3610 reverse = bottom > top 

3611 bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top) 

3612 bottom, top = self.yaxis.limit_range_for_scale(bottom, top) 

3613 # cast to bool to avoid bad interaction between python 3.8 and np.bool_ 

3614 bottom, top = sorted([bottom, top], reverse=bool(reverse)) 

3615 

3616 self._viewLim.intervaly = (bottom, top) 

3617 # Mark viewlims as no longer stale without triggering an autoscale. 

3618 for ax in self._shared_y_axes.get_siblings(self): 

3619 ax._stale_viewlim_y = False 

3620 if auto is not None: 

3621 self._autoscaleYon = bool(auto) 

3622 

3623 if emit: 

3624 self.callbacks.process('ylim_changed', self) 

3625 # Call all of the other y-axes that are shared with this one 

3626 for other in self._shared_y_axes.get_siblings(self): 

3627 if other is not self: 

3628 other.set_ylim(self.viewLim.intervaly, 

3629 emit=False, auto=auto) 

3630 if other.figure != self.figure: 

3631 other.figure.canvas.draw_idle() 

3632 self.stale = True 

3633 return bottom, top 

3634 

3635 def get_yscale(self): 

3636 """ 

3637 Return the y-axis scale as string. 

3638 

3639 See Also 

3640 -------- 

3641 set_yscale 

3642 """ 

3643 return self.yaxis.get_scale() 

3644 

3645 def set_yscale(self, value, **kwargs): 

3646 """ 

3647 Set the y-axis scale. 

3648 

3649 Parameters 

3650 ---------- 

3651 value : {"linear", "log", "symlog", "logit", ...} 

3652 The axis scale type to apply. 

3653 

3654 **kwargs 

3655 Different keyword arguments are accepted, depending on the scale. 

3656 See the respective class keyword arguments: 

3657 

3658 - `matplotlib.scale.LinearScale` 

3659 - `matplotlib.scale.LogScale` 

3660 - `matplotlib.scale.SymmetricalLogScale` 

3661 - `matplotlib.scale.LogitScale` 

3662 

3663 Notes 

3664 ----- 

3665 By default, Matplotlib supports the above mentioned scales. 

3666 Additionally, custom scales may be registered using 

3667 `matplotlib.scale.register_scale`. These scales can then also 

3668 be used here. 

3669 """ 

3670 old_default_lims = (self.yaxis.get_major_locator() 

3671 .nonsingular(-np.inf, np.inf)) 

3672 g = self.get_shared_y_axes() 

3673 for ax in g.get_siblings(self): 

3674 ax.yaxis._set_scale(value, **kwargs) 

3675 ax._update_transScale() 

3676 ax.stale = True 

3677 new_default_lims = (self.yaxis.get_major_locator() 

3678 .nonsingular(-np.inf, np.inf)) 

3679 if old_default_lims != new_default_lims: 

3680 # Force autoscaling now, to take advantage of the scale locator's 

3681 # nonsingular() before it possibly gets swapped out by the user. 

3682 self.autoscale_view(scalex=False) 

3683 

3684 @cbook._make_keyword_only("3.2", "minor") 

3685 def get_yticks(self, minor=False): 

3686 """Return the y ticks as a list of locations""" 

3687 return self.yaxis.get_ticklocs(minor=minor) 

3688 

3689 @cbook._make_keyword_only("3.2", "minor") 

3690 def set_yticks(self, ticks, minor=False): 

3691 """ 

3692 Set the y ticks with list of *ticks* 

3693 

3694 Parameters 

3695 ---------- 

3696 ticks : list 

3697 List of y-axis tick locations 

3698 

3699 minor : bool, optional 

3700 If ``False`` sets major ticks, if ``True`` sets minor ticks. 

3701 Default is ``False``. 

3702 """ 

3703 ret = self.yaxis.set_ticks(ticks, minor=minor) 

3704 return ret 

3705 

3706 def get_ymajorticklabels(self): 

3707 """ 

3708 Get the major y tick labels. 

3709 

3710 Returns 

3711 ------- 

3712 labels : list 

3713 List of `~matplotlib.text.Text` instances 

3714 """ 

3715 return self.yaxis.get_majorticklabels() 

3716 

3717 def get_yminorticklabels(self): 

3718 """ 

3719 Get the minor y tick labels. 

3720 

3721 Returns 

3722 ------- 

3723 labels : list 

3724 List of `~matplotlib.text.Text` instances 

3725 """ 

3726 return self.yaxis.get_minorticklabels() 

3727 

3728 def get_yticklabels(self, minor=False, which=None): 

3729 """ 

3730 Get the y tick labels as a list of `~matplotlib.text.Text` instances. 

3731 

3732 Parameters 

3733 ---------- 

3734 minor : bool 

3735 If True return the minor ticklabels, 

3736 else return the major ticklabels 

3737 

3738 which : None, ('minor', 'major', 'both') 

3739 Overrides *minor*. 

3740 

3741 Selects which ticklabels to return 

3742 

3743 Returns 

3744 ------- 

3745 ret : list 

3746 List of `~matplotlib.text.Text` instances. 

3747 """ 

3748 return self.yaxis.get_ticklabels(minor=minor, which=which) 

3749 

3750 def set_yticklabels(self, labels, fontdict=None, minor=False, **kwargs): 

3751 """ 

3752 Set the y-tick labels with list of strings labels. 

3753 

3754 Parameters 

3755 ---------- 

3756 labels : List[str] 

3757 list of string labels 

3758 

3759 fontdict : dict, optional 

3760 A dictionary controlling the appearance of the ticklabels. 

3761 The default *fontdict* is:: 

3762 

3763 {'fontsize': rcParams['axes.titlesize'], 

3764 'fontweight': rcParams['axes.titleweight'], 

3765 'verticalalignment': 'baseline', 

3766 'horizontalalignment': loc} 

3767 

3768 minor : bool, optional 

3769 Whether to set the minor ticklabels rather than the major ones. 

3770 

3771 Returns 

3772 ------- 

3773 A list of `~.text.Text` instances. 

3774 

3775 Other Parameters 

3776 ---------------- 

3777 **kwargs : `~.text.Text` properties. 

3778 """ 

3779 if fontdict is not None: 

3780 kwargs.update(fontdict) 

3781 return self.yaxis.set_ticklabels(labels, 

3782 minor=minor, **kwargs) 

3783 

3784 def xaxis_date(self, tz=None): 

3785 """ 

3786 Sets up x-axis ticks and labels that treat the x data as dates. 

3787 

3788 Parameters 

3789 ---------- 

3790 tz : str or `tzinfo` instance, optional 

3791 Timezone. Defaults to :rc:`timezone`. 

3792 """ 

3793 # should be enough to inform the unit conversion interface 

3794 # dates are coming in 

3795 self.xaxis.axis_date(tz) 

3796 

3797 def yaxis_date(self, tz=None): 

3798 """ 

3799 Sets up y-axis ticks and labels that treat the y data as dates. 

3800 

3801 Parameters 

3802 ---------- 

3803 tz : str or `tzinfo` instance, optional 

3804 Timezone. Defaults to :rc:`timezone`. 

3805 """ 

3806 self.yaxis.axis_date(tz) 

3807 

3808 def format_xdata(self, x): 

3809 """ 

3810 Return *x* formatted as an x-value. 

3811 

3812 This function will use the `.fmt_xdata` attribute if it is not None, 

3813 else will fall back on the xaxis major formatter. 

3814 """ 

3815 return (self.fmt_xdata if self.fmt_xdata is not None 

3816 else self.xaxis.get_major_formatter().format_data_short)(x) 

3817 

3818 def format_ydata(self, y): 

3819 """ 

3820 Return *y* formatted as an y-value. 

3821 

3822 This function will use the `.fmt_ydata` attribute if it is not None, 

3823 else will fall back on the yaxis major formatter. 

3824 """ 

3825 return (self.fmt_ydata if self.fmt_ydata is not None 

3826 else self.yaxis.get_major_formatter().format_data_short)(y) 

3827 

3828 def format_coord(self, x, y): 

3829 """Return a format string formatting the *x*, *y* coordinates.""" 

3830 if x is None: 

3831 xs = '???' 

3832 else: 

3833 xs = self.format_xdata(x) 

3834 if y is None: 

3835 ys = '???' 

3836 else: 

3837 ys = self.format_ydata(y) 

3838 return 'x=%s y=%s' % (xs, ys) 

3839 

3840 def minorticks_on(self): 

3841 """ 

3842 Display minor ticks on the axes. 

3843 

3844 Displaying minor ticks may reduce performance; you may turn them off 

3845 using `minorticks_off()` if drawing speed is a problem. 

3846 """ 

3847 for ax in (self.xaxis, self.yaxis): 

3848 scale = ax.get_scale() 

3849 if scale == 'log': 

3850 s = ax._scale 

3851 ax.set_minor_locator(mticker.LogLocator(s.base, s.subs)) 

3852 elif scale == 'symlog': 

3853 s = ax._scale 

3854 ax.set_minor_locator( 

3855 mticker.SymmetricalLogLocator(s._transform, s.subs)) 

3856 else: 

3857 ax.set_minor_locator(mticker.AutoMinorLocator()) 

3858 

3859 def minorticks_off(self): 

3860 """Remove minor ticks from the axes.""" 

3861 self.xaxis.set_minor_locator(mticker.NullLocator()) 

3862 self.yaxis.set_minor_locator(mticker.NullLocator()) 

3863 

3864 # Interactive manipulation 

3865 

3866 def can_zoom(self): 

3867 """ 

3868 Return *True* if this axes supports the zoom box button functionality. 

3869 """ 

3870 return True 

3871 

3872 def can_pan(self): 

3873 """ 

3874 Return *True* if this axes supports any pan/zoom button functionality. 

3875 """ 

3876 return True 

3877 

3878 def get_navigate(self): 

3879 """ 

3880 Get whether the axes responds to navigation commands 

3881 """ 

3882 return self._navigate 

3883 

3884 def set_navigate(self, b): 

3885 """ 

3886 Set whether the axes responds to navigation toolbar commands 

3887 

3888 Parameters 

3889 ---------- 

3890 b : bool 

3891 """ 

3892 self._navigate = b 

3893 

3894 def get_navigate_mode(self): 

3895 """ 

3896 Get the navigation toolbar button status: 'PAN', 'ZOOM', or None 

3897 """ 

3898 return self._navigate_mode 

3899 

3900 def set_navigate_mode(self, b): 

3901 """ 

3902 Set the navigation toolbar button status; 

3903 

3904 .. warning:: 

3905 this is not a user-API function. 

3906 

3907 """ 

3908 self._navigate_mode = b 

3909 

3910 def _get_view(self): 

3911 """ 

3912 Save information required to reproduce the current view. 

3913 

3914 Called before a view is changed, such as during a pan or zoom 

3915 initiated by the user. You may return any information you deem 

3916 necessary to describe the view. 

3917 

3918 .. note:: 

3919 

3920 Intended to be overridden by new projection types, but if not, the 

3921 default implementation saves the view limits. You *must* implement 

3922 :meth:`_set_view` if you implement this method. 

3923 """ 

3924 xmin, xmax = self.get_xlim() 

3925 ymin, ymax = self.get_ylim() 

3926 return (xmin, xmax, ymin, ymax) 

3927 

3928 def _set_view(self, view): 

3929 """ 

3930 Apply a previously saved view. 

3931 

3932 Called when restoring a view, such as with the navigation buttons. 

3933 

3934 .. note:: 

3935 

3936 Intended to be overridden by new projection types, but if not, the 

3937 default implementation restores the view limits. You *must* 

3938 implement :meth:`_get_view` if you implement this method. 

3939 """ 

3940 xmin, xmax, ymin, ymax = view 

3941 self.set_xlim((xmin, xmax)) 

3942 self.set_ylim((ymin, ymax)) 

3943 

3944 def _set_view_from_bbox(self, bbox, direction='in', 

3945 mode=None, twinx=False, twiny=False): 

3946 """ 

3947 Update view from a selection bbox. 

3948 

3949 .. note:: 

3950 

3951 Intended to be overridden by new projection types, but if not, the 

3952 default implementation sets the view limits to the bbox directly. 

3953 

3954 Parameters 

3955 ---------- 

3956 bbox : 4-tuple or 3 tuple 

3957 * If bbox is a 4 tuple, it is the selected bounding box limits, 

3958 in *display* coordinates. 

3959 * If bbox is a 3 tuple, it is an (xp, yp, scl) triple, where 

3960 (xp, yp) is the center of zooming and scl the scale factor to 

3961 zoom by. 

3962 

3963 direction : str 

3964 The direction to apply the bounding box. 

3965 * `'in'` - The bounding box describes the view directly, i.e., 

3966 it zooms in. 

3967 * `'out'` - The bounding box describes the size to make the 

3968 existing view, i.e., it zooms out. 

3969 

3970 mode : str or None 

3971 The selection mode, whether to apply the bounding box in only the 

3972 `'x'` direction, `'y'` direction or both (`None`). 

3973 

3974 twinx : bool 

3975 Whether this axis is twinned in the *x*-direction. 

3976 

3977 twiny : bool 

3978 Whether this axis is twinned in the *y*-direction. 

3979 """ 

3980 Xmin, Xmax = self.get_xlim() 

3981 Ymin, Ymax = self.get_ylim() 

3982 

3983 if len(bbox) == 3: 

3984 # Zooming code 

3985 xp, yp, scl = bbox 

3986 

3987 # Should not happen 

3988 if scl == 0: 

3989 scl = 1. 

3990 

3991 # direction = 'in' 

3992 if scl > 1: 

3993 direction = 'in' 

3994 else: 

3995 direction = 'out' 

3996 scl = 1/scl 

3997 

3998 # get the limits of the axes 

3999 tranD2C = self.transData.transform 

4000 xmin, ymin = tranD2C((Xmin, Ymin)) 

4001 xmax, ymax = tranD2C((Xmax, Ymax)) 

4002 

4003 # set the range 

4004 xwidth = xmax - xmin 

4005 ywidth = ymax - ymin 

4006 xcen = (xmax + xmin)*.5 

4007 ycen = (ymax + ymin)*.5 

4008 xzc = (xp*(scl - 1) + xcen)/scl 

4009 yzc = (yp*(scl - 1) + ycen)/scl 

4010 

4011 bbox = [xzc - xwidth/2./scl, yzc - ywidth/2./scl, 

4012 xzc + xwidth/2./scl, yzc + ywidth/2./scl] 

4013 elif len(bbox) != 4: 

4014 # should be len 3 or 4 but nothing else 

4015 cbook._warn_external( 

4016 "Warning in _set_view_from_bbox: bounding box is not a tuple " 

4017 "of length 3 or 4. Ignoring the view change.") 

4018 return 

4019 

4020 # Just grab bounding box 

4021 lastx, lasty, x, y = bbox 

4022 

4023 # zoom to rect 

4024 inverse = self.transData.inverted() 

4025 (lastx, lasty), (x, y) = inverse.transform([(lastx, lasty), (x, y)]) 

4026 

4027 if twinx: 

4028 x0, x1 = Xmin, Xmax 

4029 else: 

4030 if Xmin < Xmax: 

4031 if x < lastx: 

4032 x0, x1 = x, lastx 

4033 else: 

4034 x0, x1 = lastx, x 

4035 if x0 < Xmin: 

4036 x0 = Xmin 

4037 if x1 > Xmax: 

4038 x1 = Xmax 

4039 else: 

4040 if x > lastx: 

4041 x0, x1 = x, lastx 

4042 else: 

4043 x0, x1 = lastx, x 

4044 if x0 > Xmin: 

4045 x0 = Xmin 

4046 if x1 < Xmax: 

4047 x1 = Xmax 

4048 

4049 if twiny: 

4050 y0, y1 = Ymin, Ymax 

4051 else: 

4052 if Ymin < Ymax: 

4053 if y < lasty: 

4054 y0, y1 = y, lasty 

4055 else: 

4056 y0, y1 = lasty, y 

4057 if y0 < Ymin: 

4058 y0 = Ymin 

4059 if y1 > Ymax: 

4060 y1 = Ymax 

4061 else: 

4062 if y > lasty: 

4063 y0, y1 = y, lasty 

4064 else: 

4065 y0, y1 = lasty, y 

4066 if y0 > Ymin: 

4067 y0 = Ymin 

4068 if y1 < Ymax: 

4069 y1 = Ymax 

4070 

4071 if direction == 'in': 

4072 if mode == 'x': 

4073 self.set_xlim((x0, x1)) 

4074 elif mode == 'y': 

4075 self.set_ylim((y0, y1)) 

4076 else: 

4077 self.set_xlim((x0, x1)) 

4078 self.set_ylim((y0, y1)) 

4079 elif direction == 'out': 

4080 if self.get_xscale() == 'log': 

4081 alpha = np.log(Xmax / Xmin) / np.log(x1 / x0) 

4082 rx1 = pow(Xmin / x0, alpha) * Xmin 

4083 rx2 = pow(Xmax / x0, alpha) * Xmin 

4084 else: 

4085 alpha = (Xmax - Xmin) / (x1 - x0) 

4086 rx1 = alpha * (Xmin - x0) + Xmin 

4087 rx2 = alpha * (Xmax - x0) + Xmin 

4088 if self.get_yscale() == 'log': 

4089 alpha = np.log(Ymax / Ymin) / np.log(y1 / y0) 

4090 ry1 = pow(Ymin / y0, alpha) * Ymin 

4091 ry2 = pow(Ymax / y0, alpha) * Ymin 

4092 else: 

4093 alpha = (Ymax - Ymin) / (y1 - y0) 

4094 ry1 = alpha * (Ymin - y0) + Ymin 

4095 ry2 = alpha * (Ymax - y0) + Ymin 

4096 

4097 if mode == 'x': 

4098 self.set_xlim((rx1, rx2)) 

4099 elif mode == 'y': 

4100 self.set_ylim((ry1, ry2)) 

4101 else: 

4102 self.set_xlim((rx1, rx2)) 

4103 self.set_ylim((ry1, ry2)) 

4104 

4105 def start_pan(self, x, y, button): 

4106 """ 

4107 Called when a pan operation has started. 

4108 

4109 *x*, *y* are the mouse coordinates in display coords. 

4110 button is the mouse button number: 

4111 

4112 * 1: LEFT 

4113 * 2: MIDDLE 

4114 * 3: RIGHT 

4115 

4116 .. note:: 

4117 

4118 Intended to be overridden by new projection types. 

4119 

4120 """ 

4121 self._pan_start = types.SimpleNamespace( 

4122 lim=self.viewLim.frozen(), 

4123 trans=self.transData.frozen(), 

4124 trans_inverse=self.transData.inverted().frozen(), 

4125 bbox=self.bbox.frozen(), 

4126 x=x, 

4127 y=y) 

4128 

4129 def end_pan(self): 

4130 """ 

4131 Called when a pan operation completes (when the mouse button 

4132 is up.) 

4133 

4134 .. note:: 

4135 

4136 Intended to be overridden by new projection types. 

4137 

4138 """ 

4139 del self._pan_start 

4140 

4141 def drag_pan(self, button, key, x, y): 

4142 """ 

4143 Called when the mouse moves during a pan operation. 

4144 

4145 *button* is the mouse button number: 

4146 

4147 * 1: LEFT 

4148 * 2: MIDDLE 

4149 * 3: RIGHT 

4150 

4151 *key* is a "shift" key 

4152 

4153 *x*, *y* are the mouse coordinates in display coords. 

4154 

4155 .. note:: 

4156 

4157 Intended to be overridden by new projection types. 

4158 

4159 """ 

4160 def format_deltas(key, dx, dy): 

4161 if key == 'control': 

4162 if abs(dx) > abs(dy): 

4163 dy = dx 

4164 else: 

4165 dx = dy 

4166 elif key == 'x': 

4167 dy = 0 

4168 elif key == 'y': 

4169 dx = 0 

4170 elif key == 'shift': 

4171 if 2 * abs(dx) < abs(dy): 

4172 dx = 0 

4173 elif 2 * abs(dy) < abs(dx): 

4174 dy = 0 

4175 elif abs(dx) > abs(dy): 

4176 dy = dy / abs(dy) * abs(dx) 

4177 else: 

4178 dx = dx / abs(dx) * abs(dy) 

4179 return dx, dy 

4180 

4181 p = self._pan_start 

4182 dx = x - p.x 

4183 dy = y - p.y 

4184 if dx == dy == 0: 

4185 return 

4186 if button == 1: 

4187 dx, dy = format_deltas(key, dx, dy) 

4188 result = p.bbox.translated(-dx, -dy).transformed(p.trans_inverse) 

4189 elif button == 3: 

4190 try: 

4191 dx = -dx / self.bbox.width 

4192 dy = -dy / self.bbox.height 

4193 dx, dy = format_deltas(key, dx, dy) 

4194 if self.get_aspect() != 'auto': 

4195 dx = dy = 0.5 * (dx + dy) 

4196 alpha = np.power(10.0, (dx, dy)) 

4197 start = np.array([p.x, p.y]) 

4198 oldpoints = p.lim.transformed(p.trans) 

4199 newpoints = start + alpha * (oldpoints - start) 

4200 result = (mtransforms.Bbox(newpoints) 

4201 .transformed(p.trans_inverse)) 

4202 except OverflowError: 

4203 cbook._warn_external('Overflow while panning') 

4204 return 

4205 else: 

4206 return 

4207 

4208 valid = np.isfinite(result.transformed(p.trans)) 

4209 points = result.get_points().astype(object) 

4210 # Just ignore invalid limits (typically, underflow in log-scale). 

4211 points[~valid] = None 

4212 self.set_xlim(points[:, 0]) 

4213 self.set_ylim(points[:, 1]) 

4214 

4215 def get_children(self): 

4216 # docstring inherited. 

4217 return [ 

4218 *self.collections, 

4219 *self.patches, 

4220 *self.lines, 

4221 *self.texts, 

4222 *self.artists, 

4223 *self.spines.values(), 

4224 *self._get_axis_list(), 

4225 self.title, self._left_title, self._right_title, 

4226 *self.tables, 

4227 *self.images, 

4228 *self.child_axes, 

4229 *([self.legend_] if self.legend_ is not None else []), 

4230 self.patch, 

4231 ] 

4232 

4233 def contains(self, mouseevent): 

4234 # docstring inherited. 

4235 inside, info = self._default_contains(mouseevent) 

4236 if inside is not None: 

4237 return inside, info 

4238 return self.patch.contains(mouseevent) 

4239 

4240 def contains_point(self, point): 

4241 """ 

4242 Return whether *point* (pair of pixel coordinates) is inside the axes 

4243 patch. 

4244 """ 

4245 return self.patch.contains_point(point, radius=1.0) 

4246 

4247 def get_default_bbox_extra_artists(self): 

4248 """ 

4249 Return a default list of artists that are used for the bounding box 

4250 calculation. 

4251 

4252 Artists are excluded either by not being visible or 

4253 ``artist.set_in_layout(False)``. 

4254 """ 

4255 

4256 artists = self.get_children() 

4257 

4258 if not (self.axison and self._frameon): 

4259 # don't do bbox on spines if frame not on. 

4260 for spine in self.spines.values(): 

4261 artists.remove(spine) 

4262 

4263 if not self.axison: 

4264 for _axis in self._get_axis_list(): 

4265 artists.remove(_axis) 

4266 

4267 return [artist for artist in artists 

4268 if (artist.get_visible() and artist.get_in_layout())] 

4269 

4270 def get_tightbbox(self, renderer, call_axes_locator=True, 

4271 bbox_extra_artists=None): 

4272 """ 

4273 Return the tight bounding box of the axes, including axis and their 

4274 decorators (xlabel, title, etc). 

4275 

4276 Artists that have ``artist.set_in_layout(False)`` are not included 

4277 in the bbox. 

4278 

4279 Parameters 

4280 ---------- 

4281 renderer : `.RendererBase` instance 

4282 renderer that will be used to draw the figures (i.e. 

4283 ``fig.canvas.get_renderer()``) 

4284 

4285 bbox_extra_artists : list of `.Artist` or ``None`` 

4286 List of artists to include in the tight bounding box. If 

4287 ``None`` (default), then all artist children of the axes are 

4288 included in the tight bounding box. 

4289 

4290 call_axes_locator : boolean (default ``True``) 

4291 If *call_axes_locator* is ``False``, it does not call the 

4292 ``_axes_locator`` attribute, which is necessary to get the correct 

4293 bounding box. ``call_axes_locator=False`` can be used if the 

4294 caller is only interested in the relative size of the tightbbox 

4295 compared to the axes bbox. 

4296 

4297 Returns 

4298 ------- 

4299 bbox : `.BboxBase` 

4300 bounding box in figure pixel coordinates. 

4301 

4302 See Also 

4303 -------- 

4304 matplotlib.axes.Axes.get_window_extent 

4305 matplotlib.axis.Axis.get_tightbbox 

4306 matplotlib.spines.Spine.get_window_extent 

4307 

4308 """ 

4309 

4310 bb = [] 

4311 

4312 if not self.get_visible(): 

4313 return None 

4314 

4315 locator = self.get_axes_locator() 

4316 if locator and call_axes_locator: 

4317 pos = locator(self, renderer) 

4318 self.apply_aspect(pos) 

4319 else: 

4320 self.apply_aspect() 

4321 

4322 if self.axison: 

4323 bb_xaxis = self.xaxis.get_tightbbox(renderer) 

4324 if bb_xaxis: 

4325 bb.append(bb_xaxis) 

4326 

4327 bb_yaxis = self.yaxis.get_tightbbox(renderer) 

4328 if bb_yaxis: 

4329 bb.append(bb_yaxis) 

4330 

4331 self._update_title_position(renderer) 

4332 axbbox = self.get_window_extent(renderer) 

4333 bb.append(axbbox) 

4334 

4335 self._update_title_position(renderer) 

4336 if self.title.get_visible(): 

4337 bb.append(self.title.get_window_extent(renderer)) 

4338 if self._left_title.get_visible(): 

4339 bb.append(self._left_title.get_window_extent(renderer)) 

4340 if self._right_title.get_visible(): 

4341 bb.append(self._right_title.get_window_extent(renderer)) 

4342 

4343 bb.append(self.get_window_extent(renderer)) 

4344 

4345 bbox_artists = bbox_extra_artists 

4346 if bbox_artists is None: 

4347 bbox_artists = self.get_default_bbox_extra_artists() 

4348 

4349 for a in bbox_artists: 

4350 # Extra check here to quickly see if clipping is on and 

4351 # contained in the axes. If it is, don't get the tightbbox for 

4352 # this artist because this can be expensive: 

4353 clip_extent = a._get_clipping_extent_bbox() 

4354 if clip_extent is not None: 

4355 clip_extent = mtransforms.Bbox.intersection(clip_extent, 

4356 axbbox) 

4357 if np.all(clip_extent.extents == axbbox.extents): 

4358 # clip extent is inside the axes bbox so don't check 

4359 # this artist 

4360 continue 

4361 bbox = a.get_tightbbox(renderer) 

4362 if (bbox is not None 

4363 and 0 < bbox.width < np.inf 

4364 and 0 < bbox.height < np.inf): 

4365 bb.append(bbox) 

4366 _bbox = mtransforms.Bbox.union( 

4367 [b for b in bb if b.width != 0 or b.height != 0]) 

4368 

4369 return _bbox 

4370 

4371 def _make_twin_axes(self, *args, **kwargs): 

4372 """Make a twinx axes of self. This is used for twinx and twiny.""" 

4373 # Typically, SubplotBase._make_twin_axes is called instead of this. 

4374 if 'sharex' in kwargs and 'sharey' in kwargs: 

4375 raise ValueError("Twinned Axes may share only one axis") 

4376 ax2 = self.figure.add_axes(self.get_position(True), *args, **kwargs) 

4377 self.set_adjustable('datalim') 

4378 ax2.set_adjustable('datalim') 

4379 self._twinned_axes.join(self, ax2) 

4380 return ax2 

4381 

4382 def twinx(self): 

4383 """ 

4384 Create a twin Axes sharing the xaxis. 

4385 

4386 Create a new Axes with an invisible x-axis and an independent 

4387 y-axis positioned opposite to the original one (i.e. at right). The 

4388 x-axis autoscale setting will be inherited from the original 

4389 Axes. To ensure that the tick marks of both y-axes align, see 

4390 `~matplotlib.ticker.LinearLocator`. 

4391 

4392 Returns 

4393 ------- 

4394 ax_twin : Axes 

4395 The newly created Axes instance 

4396 

4397 Notes 

4398 ----- 

4399 For those who are 'picking' artists while using twinx, pick 

4400 events are only called for the artists in the top-most axes. 

4401 """ 

4402 ax2 = self._make_twin_axes(sharex=self) 

4403 ax2.yaxis.tick_right() 

4404 ax2.yaxis.set_label_position('right') 

4405 ax2.yaxis.set_offset_position('right') 

4406 ax2.set_autoscalex_on(self.get_autoscalex_on()) 

4407 self.yaxis.tick_left() 

4408 ax2.xaxis.set_visible(False) 

4409 ax2.patch.set_visible(False) 

4410 return ax2 

4411 

4412 def twiny(self): 

4413 """ 

4414 Create a twin Axes sharing the yaxis. 

4415 

4416 Create a new Axes with an invisible y-axis and an independent 

4417 x-axis positioned opposite to the original one (i.e. at top). The 

4418 y-axis autoscale setting will be inherited from the original Axes. 

4419 To ensure that the tick marks of both x-axes align, see 

4420 `~matplotlib.ticker.LinearLocator`. 

4421 

4422 Returns 

4423 ------- 

4424 ax_twin : Axes 

4425 The newly created Axes instance 

4426 

4427 Notes 

4428 ----- 

4429 For those who are 'picking' artists while using twiny, pick 

4430 events are only called for the artists in the top-most axes. 

4431 """ 

4432 ax2 = self._make_twin_axes(sharey=self) 

4433 ax2.xaxis.tick_top() 

4434 ax2.xaxis.set_label_position('top') 

4435 ax2.set_autoscaley_on(self.get_autoscaley_on()) 

4436 self.xaxis.tick_bottom() 

4437 ax2.yaxis.set_visible(False) 

4438 ax2.patch.set_visible(False) 

4439 return ax2 

4440 

4441 def get_shared_x_axes(self): 

4442 """Return a reference to the shared axes Grouper object for x axes.""" 

4443 return self._shared_x_axes 

4444 

4445 def get_shared_y_axes(self): 

4446 """Return a reference to the shared axes Grouper object for y axes.""" 

4447 return self._shared_y_axes