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

1691 statements  

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

1from collections.abc import MutableSequence 

2from contextlib import ExitStack 

3import functools 

4import inspect 

5import itertools 

6import logging 

7from numbers import Real 

8from operator import attrgetter 

9import types 

10 

11import numpy as np 

12 

13import matplotlib as mpl 

14from matplotlib import _api, cbook, _docstring, offsetbox 

15import matplotlib.artist as martist 

16import matplotlib.axis as maxis 

17from matplotlib.cbook import _OrderedSet, _check_1d, index_of 

18import matplotlib.collections as mcoll 

19import matplotlib.colors as mcolors 

20import matplotlib.font_manager as font_manager 

21import matplotlib.image as mimage 

22import matplotlib.lines as mlines 

23import matplotlib.patches as mpatches 

24from matplotlib.rcsetup import cycler, validate_axisbelow 

25import matplotlib.spines as mspines 

26import matplotlib.table as mtable 

27import matplotlib.text as mtext 

28import matplotlib.ticker as mticker 

29import matplotlib.transforms as mtransforms 

30 

31_log = logging.getLogger(__name__) 

32 

33 

34class _axis_method_wrapper: 

35 """ 

36 Helper to generate Axes methods wrapping Axis methods. 

37 

38 After :: 

39 

40 get_foo = _axis_method_wrapper("xaxis", "get_bar") 

41 

42 (in the body of a class) ``get_foo`` is a method that forwards it arguments 

43 to the ``get_bar`` method of the ``xaxis`` attribute, and gets its 

44 signature and docstring from ``Axis.get_bar``. 

45 

46 The docstring of ``get_foo`` is built by replacing "this Axis" by "the 

47 {attr_name}" (i.e., "the xaxis", "the yaxis") in the wrapped method's 

48 dedented docstring; additional replacements can by given in *doc_sub*. 

49 """ 

50 

51 def __init__(self, attr_name, method_name, *, doc_sub=None): 

52 self.attr_name = attr_name 

53 self.method_name = method_name 

54 # Immediately put the docstring in ``self.__doc__`` so that docstring 

55 # manipulations within the class body work as expected. 

56 doc = inspect.getdoc(getattr(maxis.Axis, method_name)) 

57 self._missing_subs = [] 

58 if doc: 

59 doc_sub = {"this Axis": f"the {self.attr_name}", **(doc_sub or {})} 

60 for k, v in doc_sub.items(): 

61 if k not in doc: # Delay raising error until we know qualname. 

62 self._missing_subs.append(k) 

63 doc = doc.replace(k, v) 

64 self.__doc__ = doc 

65 

66 def __set_name__(self, owner, name): 

67 # This is called at the end of the class body as 

68 # ``self.__set_name__(cls, name_under_which_self_is_assigned)``; we 

69 # rely on that to give the wrapper the correct __name__/__qualname__. 

70 get_method = attrgetter(f"{self.attr_name}.{self.method_name}") 

71 

72 def wrapper(self, *args, **kwargs): 

73 return get_method(self)(*args, **kwargs) 

74 

75 wrapper.__module__ = owner.__module__ 

76 wrapper.__name__ = name 

77 wrapper.__qualname__ = f"{owner.__qualname__}.{name}" 

78 wrapper.__doc__ = self.__doc__ 

79 # Manually copy the signature instead of using functools.wraps because 

80 # displaying the Axis method source when asking for the Axes method 

81 # source would be confusing. 

82 wrapper.__signature__ = inspect.signature( 

83 getattr(maxis.Axis, self.method_name)) 

84 

85 if self._missing_subs: 

86 raise ValueError( 

87 "The definition of {} expected that the docstring of Axis.{} " 

88 "contains {!r} as substrings".format( 

89 wrapper.__qualname__, self.method_name, 

90 ", ".join(map(repr, self._missing_subs)))) 

91 

92 setattr(owner, name, wrapper) 

93 

94 

95class _TransformedBoundsLocator: 

96 """ 

97 Axes locator for `.Axes.inset_axes` and similarly positioned Axes. 

98 

99 The locator is a callable object used in `.Axes.set_aspect` to compute the 

100 Axes location depending on the renderer. 

101 """ 

102 

103 def __init__(self, bounds, transform): 

104 """ 

105 *bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together 

106 specify the position of the inset Axes. 

107 """ 

108 self._bounds = bounds 

109 self._transform = transform 

110 

111 def __call__(self, ax, renderer): 

112 # Subtracting transSubfigure will typically rely on inverted(), 

113 # freezing the transform; thus, this needs to be delayed until draw 

114 # time as transSubfigure may otherwise change after this is evaluated. 

115 return mtransforms.TransformedBbox( 

116 mtransforms.Bbox.from_bounds(*self._bounds), 

117 self._transform - ax.figure.transSubfigure) 

118 

119 

120def _process_plot_format(fmt, *, ambiguous_fmt_datakey=False): 

121 """ 

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

123 *marker*, *color*) tuple. 

124 

125 Example format strings include: 

126 

127 * 'ko': black circles 

128 * '.b': blue dots 

129 * 'r--': red dashed lines 

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

131 

132 The format is absolute in the sense that if a linestyle or marker is not 

133 defined in *fmt*, there is no line or marker. This is expressed by 

134 returning 'None' for the respective quantity. 

135 

136 See Also 

137 -------- 

138 matplotlib.Line2D.lineStyles, matplotlib.colors.cnames 

139 All possible styles and color format strings. 

140 """ 

141 

142 linestyle = None 

143 marker = None 

144 color = None 

145 

146 # Is fmt just a colorspec? 

147 try: 

148 color = mcolors.to_rgba(fmt) 

149 

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

151 try: 

152 fmtint = str(int(fmt)) 

153 except ValueError: 

154 return linestyle, marker, color # Yes 

155 else: 

156 if fmt != fmtint: 

157 # user definitely doesn't want tri_down marker 

158 return linestyle, marker, color # Yes 

159 else: 

160 # ignore converted color 

161 color = None 

162 except ValueError: 

163 pass # No, not just a color. 

164 

165 errfmt = ("{!r} is neither a data key nor a valid format string ({})" 

166 if ambiguous_fmt_datakey else 

167 "{!r} is not a valid format string ({})") 

168 

169 i = 0 

170 while i < len(fmt): 

171 c = fmt[i] 

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

173 if linestyle is not None: 

174 raise ValueError(errfmt.format(fmt, "two linestyle symbols")) 

175 linestyle = fmt[i:i+2] 

176 i += 2 

177 elif c in mlines.lineStyles: 

178 if linestyle is not None: 

179 raise ValueError(errfmt.format(fmt, "two linestyle symbols")) 

180 linestyle = c 

181 i += 1 

182 elif c in mlines.lineMarkers: 

183 if marker is not None: 

184 raise ValueError(errfmt.format(fmt, "two marker symbols")) 

185 marker = c 

186 i += 1 

187 elif c in mcolors.get_named_colors_mapping(): 

188 if color is not None: 

189 raise ValueError(errfmt.format(fmt, "two color symbols")) 

190 color = c 

191 i += 1 

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

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

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

195 i += 2 

196 else: 

197 raise ValueError( 

198 errfmt.format(fmt, f"unrecognized character {c!r}")) 

199 

200 if linestyle is None and marker is None: 

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

202 if linestyle is None: 

203 linestyle = 'None' 

204 if marker is None: 

205 marker = 'None' 

206 

207 return linestyle, marker, color 

208 

209 

210class _process_plot_var_args: 

211 """ 

212 Process variable length arguments to `~.Axes.plot`, to support :: 

213 

214 plot(t, s) 

215 plot(t1, s1, t2, s2) 

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

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

218 

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

220 """ 

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

222 self.axes = axes 

223 self.command = command 

224 self.set_prop_cycle(None) 

225 

226 def __getstate__(self): 

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

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

229 

230 def __setstate__(self, state): 

231 self.__dict__ = state.copy() 

232 self.set_prop_cycle(None) 

233 

234 def set_prop_cycle(self, cycler): 

235 if cycler is None: 

236 cycler = mpl.rcParams['axes.prop_cycle'] 

237 self.prop_cycler = itertools.cycle(cycler) 

238 self._prop_keys = cycler.keys # This should make a copy 

239 

240 def __call__(self, *args, data=None, **kwargs): 

241 self.axes._process_unit_info(kwargs=kwargs) 

242 

243 for pos_only in "xy": 

244 if pos_only in kwargs: 

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

246 .format(self.command, pos_only)) 

247 

248 if not args: 

249 return 

250 

251 if data is None: # Process dict views 

252 args = [cbook.sanitize_sequence(a) for a in args] 

253 else: # Process the 'data' kwarg. 

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

255 if len(args) == 1: 

256 label_namer_idx = 0 

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

258 # Figure out what the second argument is. 

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

260 # second argument is the label_namer. 

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

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

263 # use it as label_namer. 

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

265 # first argument as label_namer. 

266 try: 

267 _process_plot_format(args[1]) 

268 except ValueError: # case 1) 

269 label_namer_idx = 1 

270 else: 

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

272 _api.warn_external( 

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

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

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

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

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

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

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

280 RuntimeWarning) 

281 label_namer_idx = 1 

282 else: # case 2b) 

283 label_namer_idx = 0 

284 elif len(args) == 3: 

285 label_namer_idx = 1 

286 else: 

287 raise ValueError( 

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

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

290 "instead") 

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

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

293 replaced[label_namer_idx], args[label_namer_idx]) 

294 args = replaced 

295 ambiguous_fmt_datakey = data is not None and len(args) == 2 

296 

297 if len(args) >= 4 and not cbook.is_scalar_or_string( 

298 kwargs.get("label")): 

299 raise ValueError("plot() with multiple groups of data (i.e., " 

300 "pairs of x and y) does not support multiple " 

301 "labels") 

302 

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

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

305 

306 while args: 

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

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

309 this += args[0], 

310 args = args[1:] 

311 yield from self._plot_args( 

312 this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey) 

313 

314 def get_next_color(self): 

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

316 if 'color' not in self._prop_keys: 

317 return 'k' 

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

319 

320 def _getdefaults(self, ignore, kw): 

321 """ 

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

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

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

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

326 """ 

327 prop_keys = self._prop_keys - ignore 

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

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

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

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

332 for p in ignore: 

333 default_dict.pop(p, None) 

334 else: 

335 default_dict = {} 

336 return default_dict 

337 

338 def _setdefaults(self, defaults, kw): 

339 """ 

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

341 or set to None in *kw*. 

342 """ 

343 for k in defaults: 

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

345 kw[k] = defaults[k] 

346 

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

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

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

350 self._setdefaults(default_dict, kw) 

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

352 return seg, kw 

353 

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

355 # Polygon doesn't directly support unitized inputs. 

356 x = self.axes.convert_xunits(x) 

357 y = self.axes.convert_yunits(y) 

358 

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

360 kwargs = kwargs.copy() 

361 

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

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

364 # likely to appear in the default cycler construction. 

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

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

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

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

369 # defaults needed after ignoring the given properties. 

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

371 'markerfacecolor', 'markeredgewidth'} 

372 # Also ignore anything provided by *kwargs*. 

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

374 if v is not None: 

375 ignores.add(k) 

376 

377 # Only using the first dictionary to use as basis 

378 # for getting defaults for back-compat reasons. 

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

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

381 default_dict = self._getdefaults(ignores, kw) 

382 self._setdefaults(default_dict, kw) 

383 

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

385 # mean both facecolor and edgecolor for some reason. 

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

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

388 # This design should probably be revisited as it increases 

389 # complexity. 

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

391 

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

393 default_dict.pop('color', None) 

394 

395 # To get other properties set from the cycler 

396 # modify the kwargs dictionary. 

397 self._setdefaults(default_dict, kwargs) 

398 

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

400 facecolor=facecolor, 

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

402 closed=kw['closed']) 

403 seg.set(**kwargs) 

404 return seg, kwargs 

405 

406 def _plot_args(self, tup, kwargs, *, 

407 return_kwargs=False, ambiguous_fmt_datakey=False): 

408 """ 

409 Process the arguments of ``plot([x], y, [fmt], **kwargs)`` calls. 

410 

411 This processes a single set of ([x], y, [fmt]) parameters; i.e. for 

412 ``plot(x, y, x2, y2)`` it will be called twice. Once for (x, y) and 

413 once for (x2, y2). 

414 

415 x and y may be 2D and thus can still represent multiple datasets. 

416 

417 For multiple datasets, if the keyword argument *label* is a list, this 

418 will unpack the list and assign the individual labels to the datasets. 

419 

420 Parameters 

421 ---------- 

422 tup : tuple 

423 A tuple of the positional parameters. This can be one of 

424 

425 - (y,) 

426 - (x, y) 

427 - (y, fmt) 

428 - (x, y, fmt) 

429 

430 kwargs : dict 

431 The keyword arguments passed to ``plot()``. 

432 

433 return_kwargs : bool 

434 Whether to also return the effective keyword arguments after label 

435 unpacking as well. 

436 

437 ambiguous_fmt_datakey : bool 

438 Whether the format string in *tup* could also have been a 

439 misspelled data key. 

440 

441 Returns 

442 ------- 

443 result 

444 If *return_kwargs* is false, a list of Artists representing the 

445 dataset(s). 

446 If *return_kwargs* is true, a list of (Artist, effective_kwargs) 

447 representing the dataset(s). See *return_kwargs*. 

448 The Artist is either `.Line2D` (if called from ``plot()``) or 

449 `.Polygon` otherwise. 

450 """ 

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

452 # xy is tup with fmt stripped (could still be (y,) only) 

453 *xy, fmt = tup 

454 linestyle, marker, color = _process_plot_format( 

455 fmt, ambiguous_fmt_datakey=ambiguous_fmt_datakey) 

456 elif len(tup) == 3: 

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

458 else: 

459 xy = tup 

460 linestyle, marker, color = None, None, None 

461 

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

463 # element array of None which causes problems downstream. 

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

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

466 

467 kw = {} 

468 for prop_name, val in zip(('linestyle', 'marker', 'color'), 

469 (linestyle, marker, color)): 

470 if val is not None: 

471 # check for conflicts between fmt and kwargs 

472 if (fmt.lower() != 'none' 

473 and prop_name in kwargs 

474 and val != 'None'): 

475 # Technically ``plot(x, y, 'o', ls='--')`` is a conflict 

476 # because 'o' implicitly unsets the linestyle 

477 # (linestyle='None'). 

478 # We'll gracefully not warn in this case because an 

479 # explicit set via kwargs can be seen as intention to 

480 # override an implicit unset. 

481 # Note: We don't val.lower() != 'none' because val is not 

482 # necessarily a string (can be a tuple for colors). This 

483 # is safe, because *val* comes from _process_plot_format() 

484 # which only returns 'None'. 

485 _api.warn_external( 

486 f"{prop_name} is redundantly defined by the " 

487 f"'{prop_name}' keyword argument and the fmt string " 

488 f'"{fmt}" (-> {prop_name}={val!r}). The keyword ' 

489 f"argument will take precedence.") 

490 kw[prop_name] = val 

491 

492 if len(xy) == 2: 

493 x = _check_1d(xy[0]) 

494 y = _check_1d(xy[1]) 

495 else: 

496 x, y = index_of(xy[-1]) 

497 

498 if self.axes.xaxis is not None: 

499 self.axes.xaxis.update_units(x) 

500 if self.axes.yaxis is not None: 

501 self.axes.yaxis.update_units(y) 

502 

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

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

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

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

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

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

509 if x.ndim == 1: 

510 x = x[:, np.newaxis] 

511 if y.ndim == 1: 

512 y = y[:, np.newaxis] 

513 

514 if self.command == 'plot': 

515 make_artist = self._makeline 

516 else: 

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

518 make_artist = self._makefill 

519 

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

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

522 raise ValueError(f"x has {ncx} columns but y has {ncy} columns") 

523 if ncx == 0 or ncy == 0: 

524 return [] 

525 

526 label = kwargs.get('label') 

527 n_datasets = max(ncx, ncy) 

528 if n_datasets > 1 and not cbook.is_scalar_or_string(label): 

529 if len(label) != n_datasets: 

530 raise ValueError(f"label must be scalar or have the same " 

531 f"length as the input data, but found " 

532 f"{len(label)} for {n_datasets} datasets.") 

533 labels = label 

534 else: 

535 labels = [label] * n_datasets 

536 

537 result = (make_artist(x[:, j % ncx], y[:, j % ncy], kw, 

538 {**kwargs, 'label': label}) 

539 for j, label in enumerate(labels)) 

540 

541 if return_kwargs: 

542 return list(result) 

543 else: 

544 return [l[0] for l in result] 

545 

546 

547@_api.define_aliases({"facecolor": ["fc"]}) 

548class _AxesBase(martist.Artist): 

549 name = "rectilinear" 

550 

551 # axis names are the prefixes for the attributes that contain the 

552 # respective axis; e.g. 'x' <-> self.xaxis, containing an XAxis. 

553 # Note that PolarAxes uses these attributes as well, so that we have 

554 # 'x' <-> self.xaxis, containing a ThetaAxis. In particular we do not 

555 # have 'theta' in _axis_names. 

556 # In practice, this is ('x', 'y') for all 2D Axes and ('x', 'y', 'z') 

557 # for Axes3D. 

558 _axis_names = ("x", "y") 

559 _shared_axes = {name: cbook.Grouper() for name in _axis_names} 

560 _twinned_axes = cbook.Grouper() 

561 

562 _subclass_uses_cla = False 

563 

564 @property 

565 def _axis_map(self): 

566 """A mapping of axis names, e.g. 'x', to `Axis` instances.""" 

567 return {name: getattr(self, f"{name}axis") 

568 for name in self._axis_names} 

569 

570 def __str__(self): 

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

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

573 

574 def __init__(self, fig, rect, 

575 *, 

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

577 frameon=True, 

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

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

580 label='', 

581 xscale=None, 

582 yscale=None, 

583 box_aspect=None, 

584 **kwargs 

585 ): 

586 """ 

587 Build an Axes in a figure. 

588 

589 Parameters 

590 ---------- 

591 fig : `~matplotlib.figure.Figure` 

592 The Axes is built in the `.Figure` *fig*. 

593 

594 rect : tuple (left, bottom, width, height). 

595 The Axes is built in the rectangle *rect*. *rect* is in 

596 `.Figure` coordinates. 

597 

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

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

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

601 

602 frameon : bool, default: True 

603 Whether the Axes frame is visible. 

604 

605 box_aspect : float, optional 

606 Set a fixed aspect for the Axes box, i.e. the ratio of height to 

607 width. See `~.axes.Axes.set_box_aspect` for details. 

608 

609 **kwargs 

610 Other optional keyword arguments: 

611 

612 %(Axes:kwdoc)s 

613 

614 Returns 

615 ------- 

616 `~.axes.Axes` 

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

618 """ 

619 

620 super().__init__() 

621 if isinstance(rect, mtransforms.Bbox): 

622 self._position = rect 

623 else: 

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

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

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

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

628 self.axes = self 

629 self._aspect = 'auto' 

630 self._adjustable = 'box' 

631 self._anchor = 'C' 

632 self._stale_viewlims = {name: False for name in self._axis_names} 

633 self._sharex = sharex 

634 self._sharey = sharey 

635 self.set_label(label) 

636 self.set_figure(fig) 

637 self.set_box_aspect(box_aspect) 

638 self._axes_locator = None # Optionally set via update(kwargs). 

639 # placeholder for any colorbars added that use this Axes. 

640 # (see colorbar.py): 

641 self._colorbars = [] 

642 self.spines = mspines.Spines.from_dict(self._gen_axes_spines()) 

643 

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

645 self._init_axis() 

646 if facecolor is None: 

647 facecolor = mpl.rcParams['axes.facecolor'] 

648 self._facecolor = facecolor 

649 self._frameon = frameon 

650 self.set_axisbelow(mpl.rcParams['axes.axisbelow']) 

651 

652 self._rasterization_zorder = None 

653 self.clear() 

654 

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

656 self.fmt_xdata = None 

657 self.fmt_ydata = None 

658 

659 self.set_navigate(True) 

660 self.set_navigate_mode(None) 

661 

662 if xscale: 

663 self.set_xscale(xscale) 

664 if yscale: 

665 self.set_yscale(yscale) 

666 

667 self._internal_update(kwargs) 

668 

669 for name, axis in self._axis_map.items(): 

670 axis.callbacks._connect_picklable( 

671 'units', self._unit_change_handler(name)) 

672 

673 rcParams = mpl.rcParams 

674 self.tick_params( 

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

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

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

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

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

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

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

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

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

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

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

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

687 which='minor') 

688 

689 self.tick_params( 

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

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

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

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

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

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

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

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

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

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

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

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

702 which='major') 

703 

704 def __init_subclass__(cls, **kwargs): 

705 parent_uses_cla = super(cls, cls)._subclass_uses_cla 

706 if 'cla' in cls.__dict__: 

707 _api.warn_deprecated( 

708 '3.6', 

709 pending=True, 

710 message=f'Overriding `Axes.cla` in {cls.__qualname__} is ' 

711 'pending deprecation in %(since)s and will be fully ' 

712 'deprecated in favor of `Axes.clear` in the future. ' 

713 'Please report ' 

714 f'this to the {cls.__module__!r} author.') 

715 cls._subclass_uses_cla = 'cla' in cls.__dict__ or parent_uses_cla 

716 super().__init_subclass__(**kwargs) 

717 

718 def __getstate__(self): 

719 state = super().__getstate__() 

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

721 state["_shared_axes"] = { 

722 name: self._shared_axes[name].get_siblings(self) 

723 for name in self._axis_names if self in self._shared_axes[name]} 

724 state["_twinned_axes"] = (self._twinned_axes.get_siblings(self) 

725 if self in self._twinned_axes else None) 

726 return state 

727 

728 def __setstate__(self, state): 

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

730 shared_axes = state.pop("_shared_axes") 

731 for name, shared_siblings in shared_axes.items(): 

732 self._shared_axes[name].join(*shared_siblings) 

733 twinned_siblings = state.pop("_twinned_axes") 

734 if twinned_siblings: 

735 self._twinned_axes.join(*twinned_siblings) 

736 self.__dict__ = state 

737 self._stale = True 

738 

739 def __repr__(self): 

740 fields = [] 

741 if self.get_label(): 

742 fields += [f"label={self.get_label()!r}"] 

743 if hasattr(self, "get_title"): 

744 titles = {} 

745 for k in ["left", "center", "right"]: 

746 title = self.get_title(loc=k) 

747 if title: 

748 titles[k] = title 

749 if titles: 

750 fields += [f"title={titles}"] 

751 for name, axis in self._axis_map.items(): 

752 if axis.get_label() and axis.get_label().get_text(): 

753 fields += [f"{name}label={axis.get_label().get_text()!r}"] 

754 return f"<{self.__class__.__name__}: " + ", ".join(fields) + ">" 

755 

756 @_api.delete_parameter("3.6", "args") 

757 @_api.delete_parameter("3.6", "kwargs") 

758 def get_window_extent(self, renderer=None, *args, **kwargs): 

759 """ 

760 Return the Axes bounding box in display space; *args* and *kwargs* 

761 are empty. 

762 

763 This bounding box does not include the spines, ticks, ticklabels, 

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

765 `~matplotlib.axes.Axes.get_tightbbox`. 

766 

767 See Also 

768 -------- 

769 matplotlib.axes.Axes.get_tightbbox 

770 matplotlib.axis.Axis.get_tightbbox 

771 matplotlib.spines.Spine.get_window_extent 

772 """ 

773 return self.bbox 

774 

775 def _init_axis(self): 

776 # This is moved out of __init__ because non-separable axes don't use it 

777 self.xaxis = maxis.XAxis(self) 

778 self.spines.bottom.register_axis(self.xaxis) 

779 self.spines.top.register_axis(self.xaxis) 

780 self.yaxis = maxis.YAxis(self) 

781 self.spines.left.register_axis(self.yaxis) 

782 self.spines.right.register_axis(self.yaxis) 

783 self._update_transScale() 

784 

785 def set_figure(self, fig): 

786 # docstring inherited 

787 super().set_figure(fig) 

788 

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

790 fig.transSubfigure) 

791 # these will be updated later as data is added 

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

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

794 self.transScale = mtransforms.TransformWrapper( 

795 mtransforms.IdentityTransform()) 

796 

797 self._set_lim_and_transforms() 

798 

799 def _unstale_viewLim(self): 

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

801 # instead of on every axis. 

802 need_scale = { 

803 name: any(ax._stale_viewlims[name] 

804 for ax in self._shared_axes[name].get_siblings(self)) 

805 for name in self._axis_names} 

806 if any(need_scale.values()): 

807 for name in need_scale: 

808 for ax in self._shared_axes[name].get_siblings(self): 

809 ax._stale_viewlims[name] = False 

810 self.autoscale_view(**{f"scale{name}": scale 

811 for name, scale in need_scale.items()}) 

812 

813 @property 

814 def viewLim(self): 

815 self._unstale_viewLim() 

816 return self._viewLim 

817 

818 def _request_autoscale_view(self, axis="all", tight=None): 

819 """ 

820 Mark a single axis, or all of them, as stale wrt. autoscaling. 

821 

822 No computation is performed until the next autoscaling; thus, separate 

823 calls to control individual axises incur negligible performance cost. 

824 

825 Parameters 

826 ---------- 

827 axis : str, default: "all" 

828 Either an element of ``self._axis_names``, or "all". 

829 tight : bool or None, default: None 

830 """ 

831 axis_names = _api.check_getitem( 

832 {**{k: [k] for k in self._axis_names}, "all": self._axis_names}, 

833 axis=axis) 

834 for name in axis_names: 

835 self._stale_viewlims[name] = True 

836 if tight is not None: 

837 self._tight = tight 

838 

839 def _set_lim_and_transforms(self): 

840 """ 

841 Set the *_xaxis_transform*, *_yaxis_transform*, *transScale*, 

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

843 

844 .. note:: 

845 

846 This method is primarily used by rectilinear projections of the 

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

848 new kinds of projection Axes that need different transformations 

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

850 example.) 

851 """ 

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

853 

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

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

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

857 self.transScale = mtransforms.TransformWrapper( 

858 mtransforms.IdentityTransform()) 

859 

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

861 # range of the axes 

862 self.transLimits = mtransforms.BboxTransformFrom( 

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

864 

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

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

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

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

869 

870 self._xaxis_transform = mtransforms.blended_transform_factory( 

871 self.transData, self.transAxes) 

872 self._yaxis_transform = mtransforms.blended_transform_factory( 

873 self.transAxes, self.transData) 

874 

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

876 """ 

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

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

879 y-direction is in axis coordinates. 

880 

881 .. note:: 

882 

883 This transformation is primarily used by the 

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

885 overridden by new kinds of projections that may need to 

886 place axis elements in different locations. 

887 """ 

888 if which == 'grid': 

889 return self._xaxis_transform 

890 elif which == 'tick1': 

891 # for cartesian projection, this is bottom spine 

892 return self.spines.bottom.get_spine_transform() 

893 elif which == 'tick2': 

894 # for cartesian projection, this is top spine 

895 return self.spines.top.get_spine_transform() 

896 else: 

897 raise ValueError(f'unknown value for which: {which!r}') 

898 

899 def get_xaxis_text1_transform(self, pad_points): 

900 """ 

901 Returns 

902 ------- 

903 transform : Transform 

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

905 *pad_points* of padding (in points) between the axis and the label. 

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

907 axis coordinates 

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

909 The text vertical alignment. 

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

911 The text horizontal alignment. 

912 

913 Notes 

914 ----- 

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

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

917 may need to place axis elements in different locations. 

918 """ 

919 labels_align = mpl.rcParams["xtick.alignment"] 

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

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

922 self.figure.dpi_scale_trans), 

923 "top", labels_align) 

924 

925 def get_xaxis_text2_transform(self, pad_points): 

926 """ 

927 Returns 

928 ------- 

929 transform : Transform 

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

931 add *pad_points* of padding (in points) between the axis and the 

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

933 is in axis coordinates 

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

935 The text vertical alignment. 

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

937 The text horizontal alignment. 

938 

939 Notes 

940 ----- 

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

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

943 may need to place axis elements in different locations. 

944 """ 

945 labels_align = mpl.rcParams["xtick.alignment"] 

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

947 mtransforms.ScaledTranslation(0, pad_points / 72, 

948 self.figure.dpi_scale_trans), 

949 "bottom", labels_align) 

950 

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

952 """ 

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

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

955 y-direction is in data coordinates. 

956 

957 .. note:: 

958 

959 This transformation is primarily used by the 

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

961 overridden by new kinds of projections that may need to 

962 place axis elements in different locations. 

963 """ 

964 if which == 'grid': 

965 return self._yaxis_transform 

966 elif which == 'tick1': 

967 # for cartesian projection, this is bottom spine 

968 return self.spines.left.get_spine_transform() 

969 elif which == 'tick2': 

970 # for cartesian projection, this is top spine 

971 return self.spines.right.get_spine_transform() 

972 else: 

973 raise ValueError(f'unknown value for which: {which!r}') 

974 

975 def get_yaxis_text1_transform(self, pad_points): 

976 """ 

977 Returns 

978 ------- 

979 transform : Transform 

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

981 *pad_points* of padding (in points) between the axis and the label. 

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

983 data coordinates 

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

985 The text vertical alignment. 

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

987 The text horizontal alignment. 

988 

989 Notes 

990 ----- 

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

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

993 may need to place axis elements in different locations. 

994 """ 

995 labels_align = mpl.rcParams["ytick.alignment"] 

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

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

998 self.figure.dpi_scale_trans), 

999 labels_align, "right") 

1000 

1001 def get_yaxis_text2_transform(self, pad_points): 

1002 """ 

1003 Returns 

1004 ------- 

1005 transform : Transform 

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

1007 add *pad_points* of padding (in points) between the axis and the 

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

1009 is in data coordinates 

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

1011 The text vertical alignment. 

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

1013 The text horizontal alignment. 

1014 

1015 Notes 

1016 ----- 

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

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

1019 may need to place axis elements in different locations. 

1020 """ 

1021 labels_align = mpl.rcParams["ytick.alignment"] 

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

1023 mtransforms.ScaledTranslation(pad_points / 72, 0, 

1024 self.figure.dpi_scale_trans), 

1025 labels_align, "left") 

1026 

1027 def _update_transScale(self): 

1028 self.transScale.set( 

1029 mtransforms.blended_transform_factory( 

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

1031 

1032 def get_position(self, original=False): 

1033 """ 

1034 Return the position of the Axes within the figure as a `.Bbox`. 

1035 

1036 Parameters 

1037 ---------- 

1038 original : bool 

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

1040 active position. For an explanation of the positions see 

1041 `.set_position`. 

1042 

1043 Returns 

1044 ------- 

1045 `.Bbox` 

1046 

1047 """ 

1048 if original: 

1049 return self._originalPosition.frozen() 

1050 else: 

1051 locator = self.get_axes_locator() 

1052 if not locator: 

1053 self.apply_aspect() 

1054 return self._position.frozen() 

1055 

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

1057 """ 

1058 Set the Axes position. 

1059 

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

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

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

1063 the same unless a fixed aspect is set to the Axes. See 

1064 `.Axes.set_aspect` for details. 

1065 

1066 Parameters 

1067 ---------- 

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

1069 The new position of the Axes in `.Figure` coordinates. 

1070 

1071 which : {'both', 'active', 'original'}, default: 'both' 

1072 Determines which position variables to change. 

1073 

1074 See Also 

1075 -------- 

1076 matplotlib.transforms.Bbox.from_bounds 

1077 matplotlib.transforms.Bbox.from_extents 

1078 """ 

1079 self._set_position(pos, which=which) 

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

1081 # don't let it be in the layout. 

1082 self.set_in_layout(False) 

1083 

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

1085 """ 

1086 Private version of set_position. 

1087 

1088 Call this internally to get the same functionality of `set_position`, 

1089 but not to take the axis out of the constrained_layout hierarchy. 

1090 """ 

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

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

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

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

1095 ax._position.set(pos) 

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

1097 ax._originalPosition.set(pos) 

1098 self.stale = True 

1099 

1100 def reset_position(self): 

1101 """ 

1102 Reset the active position to the original position. 

1103 

1104 This undoes changes to the active position (as defined in 

1105 `.set_position`) which may have been performed to satisfy fixed-aspect 

1106 constraints. 

1107 """ 

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

1109 pos = ax.get_position(original=True) 

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

1111 

1112 def set_axes_locator(self, locator): 

1113 """ 

1114 Set the Axes locator. 

1115 

1116 Parameters 

1117 ---------- 

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

1119 """ 

1120 self._axes_locator = locator 

1121 self.stale = True 

1122 

1123 def get_axes_locator(self): 

1124 """ 

1125 Return the axes_locator. 

1126 """ 

1127 return self._axes_locator 

1128 

1129 def _set_artist_props(self, a): 

1130 """Set the boilerplate props for artists added to Axes.""" 

1131 a.set_figure(self.figure) 

1132 if not a.is_transform_set(): 

1133 a.set_transform(self.transData) 

1134 

1135 a.axes = self 

1136 if a.get_mouseover(): 

1137 self._mouseover_set.add(a) 

1138 

1139 def _gen_axes_patch(self): 

1140 """ 

1141 Returns 

1142 ------- 

1143 Patch 

1144 The patch used to draw the background of the Axes. It is also used 

1145 as the clipping path for any data elements on the Axes. 

1146 

1147 In the standard Axes, this is a rectangle, but in other projections 

1148 it may not be. 

1149 

1150 Notes 

1151 ----- 

1152 Intended to be overridden by new projection types. 

1153 """ 

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

1155 

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

1157 """ 

1158 Returns 

1159 ------- 

1160 dict 

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

1162 used to draw Axes spines. 

1163 

1164 In the standard Axes, spines are single line segments, but in other 

1165 projections they may not be. 

1166 

1167 Notes 

1168 ----- 

1169 Intended to be overridden by new projection types. 

1170 """ 

1171 return {side: mspines.Spine.linear_spine(self, side) 

1172 for side in ['left', 'right', 'bottom', 'top']} 

1173 

1174 def sharex(self, other): 

1175 """ 

1176 Share the x-axis with *other*. 

1177 

1178 This is equivalent to passing ``sharex=other`` when constructing the 

1179 Axes, and cannot be used if the x-axis is already being shared with 

1180 another Axes. 

1181 """ 

1182 _api.check_isinstance(_AxesBase, other=other) 

1183 if self._sharex is not None and other is not self._sharex: 

1184 raise ValueError("x-axis is already shared") 

1185 self._shared_axes["x"].join(self, other) 

1186 self._sharex = other 

1187 self.xaxis.major = other.xaxis.major # Ticker instances holding 

1188 self.xaxis.minor = other.xaxis.minor # locator and formatter. 

1189 x0, x1 = other.get_xlim() 

1190 self.set_xlim(x0, x1, emit=False, auto=other.get_autoscalex_on()) 

1191 self.xaxis._scale = other.xaxis._scale 

1192 

1193 def sharey(self, other): 

1194 """ 

1195 Share the y-axis with *other*. 

1196 

1197 This is equivalent to passing ``sharey=other`` when constructing the 

1198 Axes, and cannot be used if the y-axis is already being shared with 

1199 another Axes. 

1200 """ 

1201 _api.check_isinstance(_AxesBase, other=other) 

1202 if self._sharey is not None and other is not self._sharey: 

1203 raise ValueError("y-axis is already shared") 

1204 self._shared_axes["y"].join(self, other) 

1205 self._sharey = other 

1206 self.yaxis.major = other.yaxis.major # Ticker instances holding 

1207 self.yaxis.minor = other.yaxis.minor # locator and formatter. 

1208 y0, y1 = other.get_ylim() 

1209 self.set_ylim(y0, y1, emit=False, auto=other.get_autoscaley_on()) 

1210 self.yaxis._scale = other.yaxis._scale 

1211 

1212 def __clear(self): 

1213 """Clear the Axes.""" 

1214 # The actual implementation of clear() as long as clear() has to be 

1215 # an adapter delegating to the correct implementation. 

1216 # The implementation can move back into clear() when the 

1217 # deprecation on cla() subclassing expires. 

1218 

1219 # stash the current visibility state 

1220 if hasattr(self, 'patch'): 

1221 patch_visible = self.patch.get_visible() 

1222 else: 

1223 patch_visible = True 

1224 

1225 xaxis_visible = self.xaxis.get_visible() 

1226 yaxis_visible = self.yaxis.get_visible() 

1227 

1228 for axis in self._axis_map.values(): 

1229 axis.clear() # Also resets the scale to linear. 

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

1231 spine.clear() 

1232 

1233 self.ignore_existing_data_limits = True 

1234 self.callbacks = cbook.CallbackRegistry( 

1235 signals=["xlim_changed", "ylim_changed", "zlim_changed"]) 

1236 

1237 for name, axis in self._axis_map.items(): 

1238 share = getattr(self, f"_share{name}") 

1239 if share is not None: 

1240 getattr(self, f"share{name}")(share) 

1241 else: 

1242 axis._set_scale("linear") 

1243 axis._set_lim(0, 1, auto=True) 

1244 

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

1246 if mpl.rcParams['xtick.minor.visible']: 

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

1248 if mpl.rcParams['ytick.minor.visible']: 

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

1250 

1251 self._xmargin = mpl.rcParams['axes.xmargin'] 

1252 self._ymargin = mpl.rcParams['axes.ymargin'] 

1253 self._tight = None 

1254 self._use_sticky_edges = True 

1255 self._update_transScale() # needed? 

1256 

1257 self._get_lines = _process_plot_var_args(self) 

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

1259 

1260 self._gridOn = mpl.rcParams['axes.grid'] 

1261 self._children = [] 

1262 self._mouseover_set = _OrderedSet() 

1263 self.child_axes = [] 

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

1265 self._projection_init = None # strictly for pyplot.subplot 

1266 self.legend_ = None 

1267 self.containers = [] 

1268 

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

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

1271 axis=mpl.rcParams['axes.grid.axis']) 

1272 props = font_manager.FontProperties( 

1273 size=mpl.rcParams['axes.titlesize'], 

1274 weight=mpl.rcParams['axes.titleweight']) 

1275 

1276 y = mpl.rcParams['axes.titley'] 

1277 if y is None: 

1278 y = 1.0 

1279 self._autotitlepos = True 

1280 else: 

1281 self._autotitlepos = False 

1282 

1283 self.title = mtext.Text( 

1284 x=0.5, y=y, text='', 

1285 fontproperties=props, 

1286 verticalalignment='baseline', 

1287 horizontalalignment='center', 

1288 ) 

1289 self._left_title = mtext.Text( 

1290 x=0.0, y=y, text='', 

1291 fontproperties=props.copy(), 

1292 verticalalignment='baseline', 

1293 horizontalalignment='left', ) 

1294 self._right_title = mtext.Text( 

1295 x=1.0, y=y, text='', 

1296 fontproperties=props.copy(), 

1297 verticalalignment='baseline', 

1298 horizontalalignment='right', 

1299 ) 

1300 title_offset_points = mpl.rcParams['axes.titlepad'] 

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

1302 # pad argument used... 

1303 self._set_title_offset_trans(title_offset_points) 

1304 

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

1306 self._set_artist_props(_title) 

1307 

1308 # The patch draws the background of the Axes. We want this to be below 

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

1310 # setting the edgecolor to None. 

1311 self.patch = self._gen_axes_patch() 

1312 self.patch.set_figure(self.figure) 

1313 self.patch.set_facecolor(self._facecolor) 

1314 self.patch.set_edgecolor('none') 

1315 self.patch.set_linewidth(0) 

1316 self.patch.set_transform(self.transAxes) 

1317 

1318 self.set_axis_on() 

1319 

1320 self.xaxis.set_clip_path(self.patch) 

1321 self.yaxis.set_clip_path(self.patch) 

1322 

1323 self._shared_axes["x"].clean() 

1324 self._shared_axes["y"].clean() 

1325 if self._sharex is not None: 

1326 self.xaxis.set_visible(xaxis_visible) 

1327 self.patch.set_visible(patch_visible) 

1328 if self._sharey is not None: 

1329 self.yaxis.set_visible(yaxis_visible) 

1330 self.patch.set_visible(patch_visible) 

1331 

1332 self.stale = True 

1333 

1334 def clear(self): 

1335 """Clear the Axes.""" 

1336 # Act as an alias, or as the superclass implementation depending on the 

1337 # subclass implementation. 

1338 if self._subclass_uses_cla: 

1339 self.cla() 

1340 else: 

1341 self.__clear() 

1342 

1343 def cla(self): 

1344 """Clear the Axes.""" 

1345 # Act as an alias, or as the superclass implementation depending on the 

1346 # subclass implementation. 

1347 if self._subclass_uses_cla: 

1348 self.__clear() 

1349 else: 

1350 self.clear() 

1351 

1352 class ArtistList(MutableSequence): 

1353 """ 

1354 A sublist of Axes children based on their type. 

1355 

1356 The type-specific children sublists will become immutable in 

1357 Matplotlib 3.7. Then, these artist lists will likely be replaced by 

1358 tuples. Use as if this is a tuple already. 

1359 

1360 This class exists only for the transition period to warn on the 

1361 deprecated modification of artist lists. 

1362 """ 

1363 def __init__(self, axes, prop_name, add_name, 

1364 valid_types=None, invalid_types=None): 

1365 """ 

1366 Parameters 

1367 ---------- 

1368 axes : .axes.Axes 

1369 The Axes from which this sublist will pull the children 

1370 Artists. 

1371 prop_name : str 

1372 The property name used to access this sublist from the Axes; 

1373 used to generate deprecation warnings. 

1374 add_name : str 

1375 The method name used to add Artists of this sublist's type to 

1376 the Axes; used to generate deprecation warnings. 

1377 valid_types : list of type, optional 

1378 A list of types that determine which children will be returned 

1379 by this sublist. If specified, then the Artists in the sublist 

1380 must be instances of any of these types. If unspecified, then 

1381 any type of Artist is valid (unless limited by 

1382 *invalid_types*.) 

1383 invalid_types : tuple, optional 

1384 A list of types that determine which children will *not* be 

1385 returned by this sublist. If specified, then Artists in the 

1386 sublist will never be an instance of these types. Otherwise, no 

1387 types will be excluded. 

1388 """ 

1389 self._axes = axes 

1390 self._prop_name = prop_name 

1391 self._add_name = add_name 

1392 self._type_check = lambda artist: ( 

1393 (not valid_types or isinstance(artist, valid_types)) and 

1394 (not invalid_types or not isinstance(artist, invalid_types)) 

1395 ) 

1396 

1397 def __repr__(self): 

1398 return f'<Axes.ArtistList of {len(self)} {self._prop_name}>' 

1399 

1400 def __len__(self): 

1401 return sum(self._type_check(artist) 

1402 for artist in self._axes._children) 

1403 

1404 def __iter__(self): 

1405 for artist in list(self._axes._children): 

1406 if self._type_check(artist): 

1407 yield artist 

1408 

1409 def __getitem__(self, key): 

1410 return [artist 

1411 for artist in self._axes._children 

1412 if self._type_check(artist)][key] 

1413 

1414 def __add__(self, other): 

1415 if isinstance(other, (list, _AxesBase.ArtistList)): 

1416 return [*self, *other] 

1417 return NotImplemented 

1418 

1419 def __radd__(self, other): 

1420 if isinstance(other, list): 

1421 return other + list(self) 

1422 return NotImplemented 

1423 

1424 def insert(self, index, item): 

1425 _api.warn_deprecated( 

1426 '3.5', 

1427 name=f'modification of the Axes.{self._prop_name}', 

1428 obj_type='property', 

1429 alternative=f'Axes.{self._add_name}') 

1430 try: 

1431 index = self._axes._children.index(self[index]) 

1432 except IndexError: 

1433 index = None 

1434 getattr(self._axes, self._add_name)(item) 

1435 if index is not None: 

1436 # Move new item to the specified index, if there's something to 

1437 # put it before. 

1438 self._axes._children[index:index] = self._axes._children[-1:] 

1439 del self._axes._children[-1] 

1440 

1441 def __setitem__(self, key, item): 

1442 _api.warn_deprecated( 

1443 '3.5', 

1444 name=f'modification of the Axes.{self._prop_name}', 

1445 obj_type='property', 

1446 alternative=f'Artist.remove() and Axes.f{self._add_name}') 

1447 del self[key] 

1448 if isinstance(key, slice): 

1449 key = key.start 

1450 if not np.iterable(item): 

1451 self.insert(key, item) 

1452 return 

1453 

1454 try: 

1455 index = self._axes._children.index(self[key]) 

1456 except IndexError: 

1457 index = None 

1458 for i, artist in enumerate(item): 

1459 getattr(self._axes, self._add_name)(artist) 

1460 if index is not None: 

1461 # Move new items to the specified index, if there's something 

1462 # to put it before. 

1463 i = -(i + 1) 

1464 self._axes._children[index:index] = self._axes._children[i:] 

1465 del self._axes._children[i:] 

1466 

1467 def __delitem__(self, key): 

1468 _api.warn_deprecated( 

1469 '3.5', 

1470 name=f'modification of the Axes.{self._prop_name}', 

1471 obj_type='property', 

1472 alternative='Artist.remove()') 

1473 if isinstance(key, slice): 

1474 for artist in self[key]: 

1475 artist.remove() 

1476 else: 

1477 self[key].remove() 

1478 

1479 @property 

1480 def artists(self): 

1481 return self.ArtistList(self, 'artists', 'add_artist', invalid_types=( 

1482 mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch, 

1483 mtable.Table, mtext.Text)) 

1484 

1485 @property 

1486 def collections(self): 

1487 return self.ArtistList(self, 'collections', 'add_collection', 

1488 valid_types=mcoll.Collection) 

1489 

1490 @property 

1491 def images(self): 

1492 return self.ArtistList(self, 'images', 'add_image', 

1493 valid_types=mimage.AxesImage) 

1494 

1495 @property 

1496 def lines(self): 

1497 return self.ArtistList(self, 'lines', 'add_line', 

1498 valid_types=mlines.Line2D) 

1499 

1500 @property 

1501 def patches(self): 

1502 return self.ArtistList(self, 'patches', 'add_patch', 

1503 valid_types=mpatches.Patch) 

1504 

1505 @property 

1506 def tables(self): 

1507 return self.ArtistList(self, 'tables', 'add_table', 

1508 valid_types=mtable.Table) 

1509 

1510 @property 

1511 def texts(self): 

1512 return self.ArtistList(self, 'texts', 'add_artist', 

1513 valid_types=mtext.Text) 

1514 

1515 def get_facecolor(self): 

1516 """Get the facecolor of the Axes.""" 

1517 return self.patch.get_facecolor() 

1518 

1519 def set_facecolor(self, color): 

1520 """ 

1521 Set the facecolor of the Axes. 

1522 

1523 Parameters 

1524 ---------- 

1525 color : color 

1526 """ 

1527 self._facecolor = color 

1528 self.stale = True 

1529 return self.patch.set_facecolor(color) 

1530 

1531 def _set_title_offset_trans(self, title_offset_points): 

1532 """ 

1533 Set the offset for the title either from :rc:`axes.titlepad` 

1534 or from set_title kwarg ``pad``. 

1535 """ 

1536 self.titleOffsetTrans = mtransforms.ScaledTranslation( 

1537 0.0, title_offset_points / 72, 

1538 self.figure.dpi_scale_trans) 

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

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

1541 _title.set_clip_box(None) 

1542 

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

1544 """ 

1545 Set the property cycle of the Axes. 

1546 

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

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

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

1550 

1551 Call signatures:: 

1552 

1553 set_prop_cycle(cycler) 

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

1555 set_prop_cycle(label, values) 

1556 

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

1558 

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

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

1561 Axes. If multiple properties are given, their value lists must have 

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

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

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

1565 

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

1567 as the property cycle of the Axes. This form exists for compatibility 

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

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

1570 

1571 Parameters 

1572 ---------- 

1573 cycler : Cycler 

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

1575 current style. 

1576 

1577 label : str 

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

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

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

1581 

1582 values : iterable 

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

1584 are validated and will raise a ValueError if invalid. 

1585 

1586 See Also 

1587 -------- 

1588 matplotlib.rcsetup.cycler 

1589 Convenience function for creating validated cyclers for properties. 

1590 cycler.cycler 

1591 The original function for creating unvalidated cyclers. 

1592 

1593 Examples 

1594 -------- 

1595 Setting the property cycle for a single property: 

1596 

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

1598 

1599 Setting the property cycle for simultaneously cycling over multiple 

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

1601 

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

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

1604 

1605 """ 

1606 if args and kwargs: 

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

1608 "arguments to this method.") 

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

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

1611 prop_cycle = None 

1612 else: 

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

1614 self._get_lines.set_prop_cycle(prop_cycle) 

1615 self._get_patches_for_fill.set_prop_cycle(prop_cycle) 

1616 

1617 def get_aspect(self): 

1618 """ 

1619 Return the aspect ratio of the axes scaling. 

1620 

1621 This is either "auto" or a float giving the ratio of y/x-scale. 

1622 """ 

1623 return self._aspect 

1624 

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

1626 """ 

1627 Set the aspect ratio of the axes scaling, i.e. y/x-scale. 

1628 

1629 Parameters 

1630 ---------- 

1631 aspect : {'auto', 'equal'} or float 

1632 Possible values: 

1633 

1634 - 'auto': fill the position rectangle with data. 

1635 - 'equal': same as ``aspect=1``, i.e. same scaling for x and y. 

1636 - *float*: The displayed size of 1 unit in y-data coordinates will 

1637 be *aspect* times the displayed size of 1 unit in x-data 

1638 coordinates; e.g. for ``aspect=2`` a square in data coordinates 

1639 will be rendered with a height of twice its width. 

1640 

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

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

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

1644 details. 

1645 

1646 anchor : None or str or (float, float), optional 

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

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

1649 to specify the anchor are abbreviations of cardinal directions: 

1650 

1651 ===== ===================== 

1652 value description 

1653 ===== ===================== 

1654 'C' centered 

1655 'SW' lower left corner 

1656 'S' middle of bottom edge 

1657 'SE' lower right corner 

1658 etc. 

1659 ===== ===================== 

1660 

1661 See `~.Axes.set_anchor` for further details. 

1662 

1663 share : bool, default: False 

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

1665 

1666 See Also 

1667 -------- 

1668 matplotlib.axes.Axes.set_adjustable 

1669 Set how the Axes adjusts to achieve the required aspect ratio. 

1670 matplotlib.axes.Axes.set_anchor 

1671 Set the position in case of extra space. 

1672 """ 

1673 if cbook._str_equal(aspect, 'equal'): 

1674 aspect = 1 

1675 if not cbook._str_equal(aspect, 'auto'): 

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

1677 

1678 if share: 

1679 axes = {sibling for name in self._axis_names 

1680 for sibling in self._shared_axes[name].get_siblings(self)} 

1681 else: 

1682 axes = [self] 

1683 

1684 for ax in axes: 

1685 ax._aspect = aspect 

1686 

1687 if adjustable is None: 

1688 adjustable = self._adjustable 

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

1690 

1691 if anchor is not None: 

1692 self.set_anchor(anchor, share=share) 

1693 self.stale = True 

1694 

1695 def get_adjustable(self): 

1696 """ 

1697 Return whether the Axes will adjust its physical dimension ('box') or 

1698 its data limits ('datalim') to achieve the desired aspect ratio. 

1699 

1700 See Also 

1701 -------- 

1702 matplotlib.axes.Axes.set_adjustable 

1703 Set how the Axes adjusts to achieve the required aspect ratio. 

1704 matplotlib.axes.Axes.set_aspect 

1705 For a description of aspect handling. 

1706 """ 

1707 return self._adjustable 

1708 

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

1710 """ 

1711 Set how the Axes adjusts to achieve the required aspect ratio. 

1712 

1713 Parameters 

1714 ---------- 

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

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

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

1718 

1719 share : bool, default: False 

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

1721 

1722 See Also 

1723 -------- 

1724 matplotlib.axes.Axes.set_aspect 

1725 For a description of aspect handling. 

1726 

1727 Notes 

1728 ----- 

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

1730 impose restrictions on how aspect ratios can be imposed. 

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

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

1733 may be used. These limitations are partly a requirement 

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

1735 particular implementation we are currently using, in 

1736 which the adjustments for aspect ratios are done sequentially 

1737 and independently on each Axes as it is drawn. 

1738 """ 

1739 _api.check_in_list(["box", "datalim"], adjustable=adjustable) 

1740 if share: 

1741 axs = {sibling for name in self._axis_names 

1742 for sibling in self._shared_axes[name].get_siblings(self)} 

1743 else: 

1744 axs = [self] 

1745 if (adjustable == "datalim" 

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

1747 != _AxesBase.get_data_ratio 

1748 for ax in axs)): 

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

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

1751 raise ValueError("Cannot set Axes adjustable to 'datalim' for " 

1752 "Axes which override 'get_data_ratio'") 

1753 for ax in axs: 

1754 ax._adjustable = adjustable 

1755 self.stale = True 

1756 

1757 def get_box_aspect(self): 

1758 """ 

1759 Return the Axes box aspect, i.e. the ratio of height to width. 

1760 

1761 The box aspect is ``None`` (i.e. chosen depending on the available 

1762 figure space) unless explicitly specified. 

1763 

1764 See Also 

1765 -------- 

1766 matplotlib.axes.Axes.set_box_aspect 

1767 for a description of box aspect. 

1768 matplotlib.axes.Axes.set_aspect 

1769 for a description of aspect handling. 

1770 """ 

1771 return self._box_aspect 

1772 

1773 def set_box_aspect(self, aspect=None): 

1774 """ 

1775 Set the Axes box aspect, i.e. the ratio of height to width. 

1776 

1777 This defines the aspect of the Axes in figure space and is not to be 

1778 confused with the data aspect (see `~.Axes.set_aspect`). 

1779 

1780 Parameters 

1781 ---------- 

1782 aspect : float or None 

1783 Changes the physical dimensions of the Axes, such that the ratio 

1784 of the Axes height to the Axes width in physical units is equal to 

1785 *aspect*. Defining a box aspect will change the *adjustable* 

1786 property to 'datalim' (see `~.Axes.set_adjustable`). 

1787 

1788 *None* will disable a fixed box aspect so that height and width 

1789 of the Axes are chosen independently. 

1790 

1791 See Also 

1792 -------- 

1793 matplotlib.axes.Axes.set_aspect 

1794 for a description of aspect handling. 

1795 """ 

1796 axs = {*self._twinned_axes.get_siblings(self), 

1797 *self._twinned_axes.get_siblings(self)} 

1798 

1799 if aspect is not None: 

1800 aspect = float(aspect) 

1801 # when box_aspect is set to other than ´None`, 

1802 # adjustable must be "datalim" 

1803 for ax in axs: 

1804 ax.set_adjustable("datalim") 

1805 

1806 for ax in axs: 

1807 ax._box_aspect = aspect 

1808 ax.stale = True 

1809 

1810 def get_anchor(self): 

1811 """ 

1812 Get the anchor location. 

1813 

1814 See Also 

1815 -------- 

1816 matplotlib.axes.Axes.set_anchor 

1817 for a description of the anchor. 

1818 matplotlib.axes.Axes.set_aspect 

1819 for a description of aspect handling. 

1820 """ 

1821 return self._anchor 

1822 

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

1824 """ 

1825 Define the anchor location. 

1826 

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

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

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

1830 available space. 

1831 

1832 Parameters 

1833 ---------- 

1834 anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', ...} 

1835 Either an (*x*, *y*) pair of relative coordinates (0 is left or 

1836 bottom, 1 is right or top), 'C' (center), or a cardinal direction 

1837 ('SW', southwest, is bottom left, etc.). str inputs are shorthands 

1838 for (*x*, *y*) coordinates, as shown in the following table:: 

1839 

1840 .. code-block:: none 

1841 

1842 +-----------------+-----------------+-----------------+ 

1843 | 'NW' (0.0, 1.0) | 'N' (0.5, 1.0) | 'NE' (1.0, 1.0) | 

1844 +-----------------+-----------------+-----------------+ 

1845 | 'W' (0.0, 0.5) | 'C' (0.5, 0.5) | 'E' (1.0, 0.5) | 

1846 +-----------------+-----------------+-----------------+ 

1847 | 'SW' (0.0, 0.0) | 'S' (0.5, 0.0) | 'SE' (1.0, 0.0) | 

1848 +-----------------+-----------------+-----------------+ 

1849 

1850 share : bool, default: False 

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

1852 

1853 See Also 

1854 -------- 

1855 matplotlib.axes.Axes.set_aspect 

1856 for a description of aspect handling. 

1857 """ 

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

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

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

1861 if share: 

1862 axes = {sibling for name in self._axis_names 

1863 for sibling in self._shared_axes[name].get_siblings(self)} 

1864 else: 

1865 axes = [self] 

1866 for ax in axes: 

1867 ax._anchor = anchor 

1868 

1869 self.stale = True 

1870 

1871 def get_data_ratio(self): 

1872 """ 

1873 Return the aspect ratio of the scaled data. 

1874 

1875 Notes 

1876 ----- 

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

1878 """ 

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

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

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

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

1883 return ysize / xsize 

1884 

1885 def apply_aspect(self, position=None): 

1886 """ 

1887 Adjust the Axes for a specified data aspect ratio. 

1888 

1889 Depending on `.get_adjustable` this will modify either the 

1890 Axes box (position) or the view limits. In the former case, 

1891 `~matplotlib.axes.Axes.get_anchor` will affect the position. 

1892 

1893 Parameters 

1894 ---------- 

1895 position : None or .Bbox 

1896 If not ``None``, this defines the position of the 

1897 Axes within the figure as a Bbox. See `~.Axes.get_position` 

1898 for further details. 

1899 

1900 Notes 

1901 ----- 

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

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

1904 view limits before the Figure is drawn. 

1905 

1906 See Also 

1907 -------- 

1908 matplotlib.axes.Axes.set_aspect 

1909 For a description of aspect ratio handling. 

1910 matplotlib.axes.Axes.set_adjustable 

1911 Set how the Axes adjusts to achieve the required aspect ratio. 

1912 matplotlib.axes.Axes.set_anchor 

1913 Set the position in case of extra space. 

1914 """ 

1915 if position is None: 

1916 position = self.get_position(original=True) 

1917 

1918 aspect = self.get_aspect() 

1919 

1920 if aspect == 'auto' and self._box_aspect is None: 

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

1922 return 

1923 

1924 trans = self.get_figure().transSubfigure 

1925 bb = mtransforms.Bbox.unit().transformed(trans) 

1926 # this is the physical aspect of the panel (or figure): 

1927 fig_aspect = bb.height / bb.width 

1928 

1929 if self._adjustable == 'box': 

1930 if self in self._twinned_axes: 

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

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

1933 box_aspect = aspect * self.get_data_ratio() 

1934 pb = position.frozen() 

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

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

1937 return 

1938 

1939 # The following is only seen if self._adjustable == 'datalim' 

1940 if self._box_aspect is not None: 

1941 pb = position.frozen() 

1942 pb1 = pb.shrunk_to_aspect(self._box_aspect, pb, fig_aspect) 

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

1944 if aspect == "auto": 

1945 return 

1946 

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

1948 # of 'box' 

1949 if self._box_aspect is None: 

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

1951 else: 

1952 position = pb1.anchored(self.get_anchor(), pb) 

1953 

1954 x_trf = self.xaxis.get_transform() 

1955 y_trf = self.yaxis.get_transform() 

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

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

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

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

1960 

1961 box_aspect = fig_aspect * (position.height / position.width) 

1962 data_ratio = box_aspect / aspect 

1963 

1964 y_expander = data_ratio * xsize / ysize - 1 

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

1966 if abs(y_expander) < 0.005: 

1967 return 

1968 

1969 dL = self.dataLim 

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

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

1972 xr = 1.05 * (x1 - x0) 

1973 yr = 1.05 * (y1 - y0) 

1974 

1975 xmarg = xsize - xr 

1976 ymarg = ysize - yr 

1977 Ysize = data_ratio * xsize 

1978 Xsize = ysize / data_ratio 

1979 Xmarg = Xsize - xr 

1980 Ymarg = Ysize - yr 

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

1982 xm = 0 

1983 ym = 0 

1984 

1985 shared_x = self in self._shared_axes["x"] 

1986 shared_y = self in self._shared_axes["y"] 

1987 

1988 if shared_x and shared_y: 

1989 raise RuntimeError("set_aspect(..., adjustable='datalim') or " 

1990 "axis('equal') are not allowed when both axes " 

1991 "are shared. Try set_aspect(..., " 

1992 "adjustable='box').") 

1993 

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

1995 if shared_y: 

1996 adjust_y = False 

1997 else: 

1998 if xmarg > xm and ymarg > ym: 

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

2000 (Xmarg < 0 and y_expander > 0)) 

2001 else: 

2002 adjy = y_expander > 0 

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

2004 

2005 if adjust_y: 

2006 yc = 0.5 * (ymin + ymax) 

2007 y0 = yc - Ysize / 2.0 

2008 y1 = yc + Ysize / 2.0 

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

2010 else: 

2011 xc = 0.5 * (xmin + xmax) 

2012 x0 = xc - Xsize / 2.0 

2013 x1 = xc + Xsize / 2.0 

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

2015 

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

2017 """ 

2018 Convenience method to get or set some axis properties. 

2019 

2020 Call signatures:: 

2021 

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

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

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

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

2026 

2027 Parameters 

2028 ---------- 

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

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

2031 

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

2033 

2034 option : bool or str 

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

2036 possible values are: 

2037 

2038 ======== ========================================================== 

2039 Value Description 

2040 ======== ========================================================== 

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

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

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

2044 changing axis limits. This is the same as 

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

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

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

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

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

2050 Additionally, further autoscaling will be disabled. 

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

2052 disable further autoscaling. 

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

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

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

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

2057 ======== ========================================================== 

2058 

2059 emit : bool, default: True 

2060 Whether observers are notified of the axis limit change. 

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

2062 `~.Axes.set_ylim`. 

2063 

2064 Returns 

2065 ------- 

2066 xmin, xmax, ymin, ymax : float 

2067 The axis limits. 

2068 

2069 See Also 

2070 -------- 

2071 matplotlib.axes.Axes.set_xlim 

2072 matplotlib.axes.Axes.set_ylim 

2073 """ 

2074 if len(args) > 1: 

2075 raise TypeError("axis() takes 0 or 1 positional arguments but " 

2076 f"{len(args)} were given") 

2077 elif len(args) == 1 and isinstance(args[0], (str, bool)): 

2078 s = args[0] 

2079 if s is True: 

2080 s = 'on' 

2081 if s is False: 

2082 s = 'off' 

2083 s = s.lower() 

2084 if s == 'on': 

2085 self.set_axis_on() 

2086 elif s == 'off': 

2087 self.set_axis_off() 

2088 elif s in ('equal', 'tight', 'scaled', 'auto', 'image', 'square'): 

2089 self.set_autoscale_on(True) 

2090 self.set_aspect('auto') 

2091 self.autoscale_view(tight=False) 

2092 if s == 'equal': 

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

2094 elif s == 'scaled': 

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

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

2097 elif s == 'tight': 

2098 self.autoscale_view(tight=True) 

2099 self.set_autoscale_on(False) 

2100 elif s == 'image': 

2101 self.autoscale_view(tight=True) 

2102 self.set_autoscale_on(False) 

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

2104 elif s == 'square': 

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

2106 self.set_autoscale_on(False) 

2107 xlim = self.get_xlim() 

2108 ylim = self.get_ylim() 

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

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

2111 emit=emit, auto=False) 

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

2113 emit=emit, auto=False) 

2114 else: 

2115 raise ValueError(f"Unrecognized string {s!r} to axis; " 

2116 "try 'on' or 'off'") 

2117 else: 

2118 if len(args) == 1: 

2119 limits = args[0] 

2120 try: 

2121 xmin, xmax, ymin, ymax = limits 

2122 except (TypeError, ValueError) as err: 

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

2124 'iterable of the form ' 

2125 '[xmin, xmax, ymin, ymax]') from err 

2126 else: 

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

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

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

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

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

2132 if xmin is None and xmax is None 

2133 else False) # Turn off autoscale. 

2134 yauto = (None 

2135 if ymin is None and ymax is None 

2136 else False) 

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

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

2139 if kwargs: 

2140 raise TypeError(f"axis() got an unexpected keyword argument " 

2141 f"'{next(iter(kwargs))}'") 

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

2143 

2144 def get_legend(self): 

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

2146 return self.legend_ 

2147 

2148 def get_images(self): 

2149 r"""Return a list of `.AxesImage`\s contained by the Axes.""" 

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

2151 

2152 def get_lines(self): 

2153 """Return a list of lines contained by the Axes.""" 

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

2155 

2156 def get_xaxis(self): 

2157 """ 

2158 [*Discouraged*] Return the XAxis instance. 

2159 

2160 .. admonition:: Discouraged 

2161 

2162 The use of this function is discouraged. You should instead 

2163 directly access the attribute ``ax.xaxis``. 

2164 """ 

2165 return self.xaxis 

2166 

2167 def get_yaxis(self): 

2168 """ 

2169 [*Discouraged*] Return the YAxis instance. 

2170 

2171 .. admonition:: Discouraged 

2172 

2173 The use of this function is discouraged. You should instead 

2174 directly access the attribute ``ax.yaxis``. 

2175 """ 

2176 return self.yaxis 

2177 

2178 get_xgridlines = _axis_method_wrapper("xaxis", "get_gridlines") 

2179 get_xticklines = _axis_method_wrapper("xaxis", "get_ticklines") 

2180 get_ygridlines = _axis_method_wrapper("yaxis", "get_gridlines") 

2181 get_yticklines = _axis_method_wrapper("yaxis", "get_ticklines") 

2182 

2183 # Adding and tracking artists 

2184 

2185 def _sci(self, im): 

2186 """ 

2187 Set the current image. 

2188 

2189 This image will be the target of colormap functions like 

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

2191 current image is an attribute of the current Axes. 

2192 """ 

2193 _api.check_isinstance( 

2194 (mpl.contour.ContourSet, mcoll.Collection, mimage.AxesImage), 

2195 im=im) 

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

2197 if im.collections[0] not in self._children: 

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

2199 elif im not in self._children: 

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

2201 "ContourSet in this Axes") 

2202 self._current_image = im 

2203 

2204 def _gci(self): 

2205 """Helper for `~matplotlib.pyplot.gci`; do not use elsewhere.""" 

2206 return self._current_image 

2207 

2208 def has_data(self): 

2209 """ 

2210 Return whether any artists have been added to the Axes. 

2211 

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

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

2214 anything. 

2215 """ 

2216 return any(isinstance(a, (mcoll.Collection, mimage.AxesImage, 

2217 mlines.Line2D, mpatches.Patch)) 

2218 for a in self._children) 

2219 

2220 def _deprecate_noninstance(self, _name, _types, **kwargs): 

2221 """ 

2222 For each *key, value* pair in *kwargs*, check that *value* is an 

2223 instance of one of *_types*; if not, raise an appropriate deprecation. 

2224 """ 

2225 for key, value in kwargs.items(): 

2226 if not isinstance(value, _types): 

2227 _api.warn_deprecated( 

2228 '3.5', name=_name, 

2229 message=f'Passing argument *{key}* of unexpected type ' 

2230 f'{type(value).__qualname__} to %(name)s which only ' 

2231 f'accepts {_types} is deprecated since %(since)s and will ' 

2232 'become an error %(removal)s.') 

2233 

2234 def add_artist(self, a): 

2235 """ 

2236 Add an `.Artist` to the Axes; return the artist. 

2237 

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

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

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

2241 autoscaling. 

2242 

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

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

2245 ``ax.transData``. 

2246 """ 

2247 a.axes = self 

2248 self._children.append(a) 

2249 a._remove_method = self._children.remove 

2250 self._set_artist_props(a) 

2251 a.set_clip_path(self.patch) 

2252 self.stale = True 

2253 return a 

2254 

2255 def add_child_axes(self, ax): 

2256 """ 

2257 Add an `.AxesBase` to the Axes' children; return the child Axes. 

2258 

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

2260 """ 

2261 

2262 # normally Axes have themselves as the Axes, but these need to have 

2263 # their parent... 

2264 # Need to bypass the getter... 

2265 ax._axes = self 

2266 ax.stale_callback = martist._stale_axes_callback 

2267 

2268 self.child_axes.append(ax) 

2269 ax._remove_method = self.child_axes.remove 

2270 self.stale = True 

2271 return ax 

2272 

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

2274 """ 

2275 Add a `.Collection` to the Axes; return the collection. 

2276 """ 

2277 self._deprecate_noninstance('add_collection', mcoll.Collection, 

2278 collection=collection) 

2279 label = collection.get_label() 

2280 if not label: 

2281 collection.set_label(f'_child{len(self._children)}') 

2282 self._children.append(collection) 

2283 collection._remove_method = self._children.remove 

2284 self._set_artist_props(collection) 

2285 

2286 if collection.get_clip_path() is None: 

2287 collection.set_clip_path(self.patch) 

2288 

2289 if autolim: 

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

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

2292 self._unstale_viewLim() 

2293 datalim = collection.get_datalim(self.transData) 

2294 points = datalim.get_points() 

2295 if not np.isinf(datalim.minpos).all(): 

2296 # By definition, if minpos (minimum positive value) is set 

2297 # (i.e., non-inf), then min(points) <= minpos <= max(points), 

2298 # and minpos would be superfluous. However, we add minpos to 

2299 # the call so that self.dataLim will update its own minpos. 

2300 # This ensures that log scales see the correct minimum. 

2301 points = np.concatenate([points, [datalim.minpos]]) 

2302 self.update_datalim(points) 

2303 

2304 self.stale = True 

2305 return collection 

2306 

2307 def add_image(self, image): 

2308 """ 

2309 Add an `.AxesImage` to the Axes; return the image. 

2310 """ 

2311 self._deprecate_noninstance('add_image', mimage.AxesImage, image=image) 

2312 self._set_artist_props(image) 

2313 if not image.get_label(): 

2314 image.set_label(f'_child{len(self._children)}') 

2315 self._children.append(image) 

2316 image._remove_method = self._children.remove 

2317 self.stale = True 

2318 return image 

2319 

2320 def _update_image_limits(self, image): 

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

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

2323 

2324 def add_line(self, line): 

2325 """ 

2326 Add a `.Line2D` to the Axes; return the line. 

2327 """ 

2328 self._deprecate_noninstance('add_line', mlines.Line2D, line=line) 

2329 self._set_artist_props(line) 

2330 if line.get_clip_path() is None: 

2331 line.set_clip_path(self.patch) 

2332 

2333 self._update_line_limits(line) 

2334 if not line.get_label(): 

2335 line.set_label(f'_child{len(self._children)}') 

2336 self._children.append(line) 

2337 line._remove_method = self._children.remove 

2338 self.stale = True 

2339 return line 

2340 

2341 def _add_text(self, txt): 

2342 """ 

2343 Add a `.Text` to the Axes; return the text. 

2344 """ 

2345 self._deprecate_noninstance('_add_text', mtext.Text, txt=txt) 

2346 self._set_artist_props(txt) 

2347 self._children.append(txt) 

2348 txt._remove_method = self._children.remove 

2349 self.stale = True 

2350 return txt 

2351 

2352 def _update_line_limits(self, line): 

2353 """ 

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

2355 """ 

2356 path = line.get_path() 

2357 if path.vertices.size == 0: 

2358 return 

2359 

2360 line_trf = line.get_transform() 

2361 

2362 if line_trf == self.transData: 

2363 data_path = path 

2364 elif any(line_trf.contains_branch_seperately(self.transData)): 

2365 # Compute the transform from line coordinates to data coordinates. 

2366 trf_to_data = line_trf - self.transData 

2367 # If transData is affine we can use the cached non-affine component 

2368 # of line's path (since the non-affine part of line_trf is 

2369 # entirely encapsulated in trf_to_data). 

2370 if self.transData.is_affine: 

2371 line_trans_path = line._get_transformed_path() 

2372 na_path, _ = line_trans_path.get_transformed_path_and_affine() 

2373 data_path = trf_to_data.transform_path_affine(na_path) 

2374 else: 

2375 data_path = trf_to_data.transform_path(path) 

2376 else: 

2377 # For backwards compatibility we update the dataLim with the 

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

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

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

2381 # positioning. 

2382 data_path = path 

2383 

2384 if not data_path.vertices.size: 

2385 return 

2386 

2387 updatex, updatey = line_trf.contains_branch_seperately(self.transData) 

2388 if self.name != "rectilinear": 

2389 # This block is mostly intended to handle axvline in polar plots, 

2390 # for which updatey would otherwise be True. 

2391 if updatex and line_trf == self.get_yaxis_transform(): 

2392 updatex = False 

2393 if updatey and line_trf == self.get_xaxis_transform(): 

2394 updatey = False 

2395 self.dataLim.update_from_path(data_path, 

2396 self.ignore_existing_data_limits, 

2397 updatex=updatex, updatey=updatey) 

2398 self.ignore_existing_data_limits = False 

2399 

2400 def add_patch(self, p): 

2401 """ 

2402 Add a `.Patch` to the Axes; return the patch. 

2403 """ 

2404 self._deprecate_noninstance('add_patch', mpatches.Patch, p=p) 

2405 self._set_artist_props(p) 

2406 if p.get_clip_path() is None: 

2407 p.set_clip_path(self.patch) 

2408 self._update_patch_limits(p) 

2409 self._children.append(p) 

2410 p._remove_method = self._children.remove 

2411 return p 

2412 

2413 def _update_patch_limits(self, patch): 

2414 """Update the data limits for the given patch.""" 

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

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

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

2418 # the auto-scaling 

2419 

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

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

2422 # or height. 

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

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

2425 return 

2426 p = patch.get_path() 

2427 # Get all vertices on the path 

2428 # Loop through each segment to get extrema for Bezier curve sections 

2429 vertices = [] 

2430 for curve, code in p.iter_bezier(simplify=False): 

2431 # Get distance along the curve of any extrema 

2432 _, dzeros = curve.axis_aligned_extrema() 

2433 # Calculate vertices of start, end and any extrema in between 

2434 vertices.append(curve([0, *dzeros, 1])) 

2435 

2436 if len(vertices): 

2437 vertices = np.row_stack(vertices) 

2438 

2439 patch_trf = patch.get_transform() 

2440 updatex, updatey = patch_trf.contains_branch_seperately(self.transData) 

2441 if not (updatex or updatey): 

2442 return 

2443 if self.name != "rectilinear": 

2444 # As in _update_line_limits, but for axvspan. 

2445 if updatex and patch_trf == self.get_yaxis_transform(): 

2446 updatex = False 

2447 if updatey and patch_trf == self.get_xaxis_transform(): 

2448 updatey = False 

2449 trf_to_data = patch_trf - self.transData 

2450 xys = trf_to_data.transform(vertices) 

2451 self.update_datalim(xys, updatex=updatex, updatey=updatey) 

2452 

2453 def add_table(self, tab): 

2454 """ 

2455 Add a `.Table` to the Axes; return the table. 

2456 """ 

2457 self._deprecate_noninstance('add_table', mtable.Table, tab=tab) 

2458 self._set_artist_props(tab) 

2459 self._children.append(tab) 

2460 tab.set_clip_path(self.patch) 

2461 tab._remove_method = self._children.remove 

2462 return tab 

2463 

2464 def add_container(self, container): 

2465 """ 

2466 Add a `.Container` to the Axes' containers; return the container. 

2467 """ 

2468 label = container.get_label() 

2469 if not label: 

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

2471 self.containers.append(container) 

2472 container._remove_method = self.containers.remove 

2473 return container 

2474 

2475 def _unit_change_handler(self, axis_name, event=None): 

2476 """ 

2477 Process axis units changes: requests updates to data and view limits. 

2478 """ 

2479 if event is None: # Allow connecting `self._unit_change_handler(name)` 

2480 return functools.partial( 

2481 self._unit_change_handler, axis_name, event=object()) 

2482 _api.check_in_list(self._axis_map, axis_name=axis_name) 

2483 for line in self.lines: 

2484 line.recache_always() 

2485 self.relim() 

2486 self._request_autoscale_view(axis_name) 

2487 

2488 def relim(self, visible_only=False): 

2489 """ 

2490 Recompute the data limits based on current artists. 

2491 

2492 At present, `.Collection` instances are not supported. 

2493 

2494 Parameters 

2495 ---------- 

2496 visible_only : bool, default: False 

2497 Whether to exclude invisible artists. 

2498 """ 

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

2500 # the TODO note in artists.py. 

2501 self.dataLim.ignore(True) 

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

2503 self.ignore_existing_data_limits = True 

2504 

2505 for artist in self._children: 

2506 if not visible_only or artist.get_visible(): 

2507 if isinstance(artist, mlines.Line2D): 

2508 self._update_line_limits(artist) 

2509 elif isinstance(artist, mpatches.Patch): 

2510 self._update_patch_limits(artist) 

2511 elif isinstance(artist, mimage.AxesImage): 

2512 self._update_image_limits(artist) 

2513 

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

2515 """ 

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

2517 

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

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

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

2521 *xys*. 

2522 

2523 Parameters 

2524 ---------- 

2525 xys : 2D array-like 

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

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

2528 

2529 updatex, updatey : bool, default: True 

2530 Whether to update the x/y limits. 

2531 """ 

2532 xys = np.asarray(xys) 

2533 if not np.any(np.isfinite(xys)): 

2534 return 

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

2536 updatex=updatex, updatey=updatey) 

2537 self.ignore_existing_data_limits = False 

2538 

2539 def _process_unit_info(self, datasets=None, kwargs=None, *, convert=True): 

2540 """ 

2541 Set axis units based on *datasets* and *kwargs*, and optionally apply 

2542 unit conversions to *datasets*. 

2543 

2544 Parameters 

2545 ---------- 

2546 datasets : list 

2547 List of (axis_name, dataset) pairs (where the axis name is defined 

2548 as in `._axis_map`). Individual datasets can also be None 

2549 (which gets passed through). 

2550 kwargs : dict 

2551 Other parameters from which unit info (i.e., the *xunits*, 

2552 *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for 

2553 polar) entries) is popped, if present. Note that this dict is 

2554 mutated in-place! 

2555 convert : bool, default: True 

2556 Whether to return the original datasets or the converted ones. 

2557 

2558 Returns 

2559 ------- 

2560 list 

2561 Either the original datasets if *convert* is False, or the 

2562 converted ones if *convert* is True (the default). 

2563 """ 

2564 # The API makes datasets a list of pairs rather than an axis_name to 

2565 # dataset mapping because it is sometimes necessary to process multiple 

2566 # datasets for a single axis, and concatenating them may be tricky 

2567 # (e.g. if some are scalars, etc.). 

2568 datasets = datasets or [] 

2569 kwargs = kwargs or {} 

2570 axis_map = self._axis_map 

2571 for axis_name, data in datasets: 

2572 try: 

2573 axis = axis_map[axis_name] 

2574 except KeyError: 

2575 raise ValueError(f"Invalid axis name: {axis_name!r}") from None 

2576 # Update from data if axis is already set but no unit is set yet. 

2577 if axis is not None and data is not None and not axis.have_units(): 

2578 axis.update_units(data) 

2579 for axis_name, axis in axis_map.items(): 

2580 # Return if no axis is set. 

2581 if axis is None: 

2582 continue 

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

2584 units = kwargs.pop(f"{axis_name}units", axis.units) 

2585 if self.name == "polar": 

2586 # Special case: polar supports "thetaunits"/"runits". 

2587 polar_units = {"x": "thetaunits", "y": "runits"} 

2588 units = kwargs.pop(polar_units[axis_name], units) 

2589 if units != axis.units and units is not None: 

2590 axis.set_units(units) 

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

2592 # we need to update again. 

2593 for dataset_axis_name, data in datasets: 

2594 if dataset_axis_name == axis_name and data is not None: 

2595 axis.update_units(data) 

2596 return [axis_map[axis_name].convert_units(data) 

2597 if convert and data is not None else data 

2598 for axis_name, data in datasets] 

2599 

2600 def in_axes(self, mouseevent): 

2601 """ 

2602 Return whether the given event (in display coords) is in the Axes. 

2603 """ 

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

2605 

2606 get_autoscalex_on = _axis_method_wrapper("xaxis", "_get_autoscale_on") 

2607 get_autoscaley_on = _axis_method_wrapper("yaxis", "_get_autoscale_on") 

2608 set_autoscalex_on = _axis_method_wrapper("xaxis", "_set_autoscale_on") 

2609 set_autoscaley_on = _axis_method_wrapper("yaxis", "_set_autoscale_on") 

2610 

2611 def get_autoscale_on(self): 

2612 """Return True if each axis is autoscaled, False otherwise.""" 

2613 return all(axis._get_autoscale_on() 

2614 for axis in self._axis_map.values()) 

2615 

2616 def set_autoscale_on(self, b): 

2617 """ 

2618 Set whether autoscaling is applied to each axis on the next draw or 

2619 call to `.Axes.autoscale_view`. 

2620 

2621 Parameters 

2622 ---------- 

2623 b : bool 

2624 """ 

2625 for axis in self._axis_map.values(): 

2626 axis._set_autoscale_on(b) 

2627 

2628 @property 

2629 def use_sticky_edges(self): 

2630 """ 

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

2632 

2633 Default is ``True``. 

2634 

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

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

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

2638 with its data limit. 

2639 

2640 The changing this property does not change the plot until 

2641 `autoscale` or `autoscale_view` is called. 

2642 """ 

2643 return self._use_sticky_edges 

2644 

2645 @use_sticky_edges.setter 

2646 def use_sticky_edges(self, b): 

2647 self._use_sticky_edges = bool(b) 

2648 # No effect until next autoscaling, which will mark the Axes as stale. 

2649 

2650 def set_xmargin(self, m): 

2651 """ 

2652 Set padding of X data limits prior to autoscaling. 

2653 

2654 *m* times the data interval will be added to each end of that interval 

2655 before it is used in autoscaling. If *m* is negative, this will clip 

2656 the data range instead of expanding it. 

2657 

2658 For example, if your data is in the range [0, 2], a margin of 0.1 will 

2659 result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range 

2660 of [0.2, 1.8]. 

2661 

2662 Parameters 

2663 ---------- 

2664 m : float greater than -0.5 

2665 """ 

2666 if m <= -0.5: 

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

2668 self._xmargin = m 

2669 self._request_autoscale_view("x") 

2670 self.stale = True 

2671 

2672 def set_ymargin(self, m): 

2673 """ 

2674 Set padding of Y data limits prior to autoscaling. 

2675 

2676 *m* times the data interval will be added to each end of that interval 

2677 before it is used in autoscaling. If *m* is negative, this will clip 

2678 the data range instead of expanding it. 

2679 

2680 For example, if your data is in the range [0, 2], a margin of 0.1 will 

2681 result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range 

2682 of [0.2, 1.8]. 

2683 

2684 Parameters 

2685 ---------- 

2686 m : float greater than -0.5 

2687 """ 

2688 if m <= -0.5: 

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

2690 self._ymargin = m 

2691 self._request_autoscale_view("y") 

2692 self.stale = True 

2693 

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

2695 """ 

2696 Set or retrieve autoscaling margins. 

2697 

2698 The padding added to each limit of the Axes is the *margin* 

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

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

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

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

2703 margins will remain in place and simply be returned. 

2704 

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

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

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

2708 it is used in autoscaling. 

2709 

2710 Parameters 

2711 ---------- 

2712 *margins : float, optional 

2713 If a single positional argument is provided, it specifies 

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

2715 positional arguments are provided, they will be interpreted 

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

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

2718 

2719 x, y : float, optional 

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

2721 respectively. These cannot be used with positional 

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

2723 only the y-axis. 

2724 

2725 tight : bool or None, default: True 

2726 The *tight* parameter is passed to `~.axes.Axes.autoscale_view`, 

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

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

2729 specified, no additional padding to match tick marks is 

2730 usually desired. Setting *tight* to *None* preserves 

2731 the previous setting. 

2732 

2733 Returns 

2734 ------- 

2735 xmargin, ymargin : float 

2736 

2737 Notes 

2738 ----- 

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

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

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

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

2743 before calling :meth:`margins`. 

2744 """ 

2745 

2746 if margins and (x is not None or y is not None): 

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

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

2749 elif len(margins) == 1: 

2750 x = y = margins[0] 

2751 elif len(margins) == 2: 

2752 x, y = margins 

2753 elif margins: 

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

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

2756 

2757 if x is None and y is None: 

2758 if tight is not True: 

2759 _api.warn_external(f'ignoring tight={tight!r} in get mode') 

2760 return self._xmargin, self._ymargin 

2761 

2762 if tight is not None: 

2763 self._tight = tight 

2764 if x is not None: 

2765 self.set_xmargin(x) 

2766 if y is not None: 

2767 self.set_ymargin(y) 

2768 

2769 def set_rasterization_zorder(self, z): 

2770 """ 

2771 Set the zorder threshold for rasterization for vector graphics output. 

2772 

2773 All artists with a zorder below the given value will be rasterized if 

2774 they support rasterization. 

2775 

2776 This setting is ignored for pixel-based output. 

2777 

2778 See also :doc:`/gallery/misc/rasterization_demo`. 

2779 

2780 Parameters 

2781 ---------- 

2782 z : float or None 

2783 The zorder below which artists are rasterized. 

2784 If ``None`` rasterization based on zorder is deactivated. 

2785 """ 

2786 self._rasterization_zorder = z 

2787 self.stale = True 

2788 

2789 def get_rasterization_zorder(self): 

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

2791 return self._rasterization_zorder 

2792 

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

2794 """ 

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

2796 

2797 Convenience method for simple axis view autoscaling. 

2798 It turns autoscaling on or off, and then, 

2799 if autoscaling for either axis is on, it performs 

2800 the autoscaling on the specified axis or Axes. 

2801 

2802 Parameters 

2803 ---------- 

2804 enable : bool or None, default: True 

2805 True turns autoscaling on, False turns it off. 

2806 None leaves the autoscaling state unchanged. 

2807 axis : {'both', 'x', 'y'}, default: 'both' 

2808 The axis on which to operate. (For 3D Axes, *axis* can also be set 

2809 to 'z', and 'both' refers to all three axes.) 

2810 tight : bool or None, default: None 

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

2812 forwarded to `~.axes.Axes.autoscale_view` (regardless of 

2813 its value); see the description of its behavior there. 

2814 """ 

2815 if enable is None: 

2816 scalex = True 

2817 scaley = True 

2818 else: 

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

2820 self.set_autoscalex_on(bool(enable)) 

2821 scalex = self.get_autoscalex_on() 

2822 else: 

2823 scalex = False 

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

2825 self.set_autoscaley_on(bool(enable)) 

2826 scaley = self.get_autoscaley_on() 

2827 else: 

2828 scaley = False 

2829 if tight and scalex: 

2830 self._xmargin = 0 

2831 if tight and scaley: 

2832 self._ymargin = 0 

2833 if scalex: 

2834 self._request_autoscale_view("x", tight=tight) 

2835 if scaley: 

2836 self._request_autoscale_view("y", tight=tight) 

2837 

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

2839 """ 

2840 Autoscale the view limits using the data limits. 

2841 

2842 Parameters 

2843 ---------- 

2844 tight : bool or None 

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

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

2847 margins to zero. 

2848 

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

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

2851 using the axis major locator. 

2852 

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

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

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

2856 behaves like True). 

2857 

2858 scalex : bool, default: True 

2859 Whether to autoscale the x axis. 

2860 

2861 scaley : bool, default: True 

2862 Whether to autoscale the y axis. 

2863 

2864 Notes 

2865 ----- 

2866 The autoscaling preserves any preexisting axis direction reversal. 

2867 

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

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

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

2871 autoscale_view. 

2872 

2873 If the views of the Axes are fixed, e.g. via `set_xlim`, they will 

2874 not be changed by autoscale_view(). 

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

2876 """ 

2877 if tight is not None: 

2878 self._tight = bool(tight) 

2879 

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

2881 if self.use_sticky_edges: 

2882 # Only iterate over Axes and artists if needed. The check for 

2883 # ``hasattr(ax, "_children")`` is necessary because this can be 

2884 # called very early in the Axes init process (e.g., for twin Axes) 

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

2886 # `get_children` would raise an AttributeError. 

2887 if self._xmargin and scalex and self.get_autoscalex_on(): 

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

2889 artist.sticky_edges.x 

2890 for ax in self._shared_axes["x"].get_siblings(self) 

2891 if hasattr(ax, "_children") 

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

2893 if self._ymargin and scaley and self.get_autoscaley_on(): 

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

2895 artist.sticky_edges.y 

2896 for ax in self._shared_axes["y"].get_siblings(self) 

2897 if hasattr(ax, "_children") 

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

2899 if self.get_xscale() == 'log': 

2900 x_stickies = x_stickies[x_stickies > 0] 

2901 if self.get_yscale() == 'log': 

2902 y_stickies = y_stickies[y_stickies > 0] 

2903 

2904 def handle_single_axis( 

2905 scale, shared_axes, name, axis, margin, stickies, set_bound): 

2906 

2907 if not (scale and axis._get_autoscale_on()): 

2908 return # nothing to do... 

2909 

2910 shared = shared_axes.get_siblings(self) 

2911 # Base autoscaling on finite data limits when there is at least one 

2912 # finite data limit among all the shared_axes and intervals. 

2913 values = [val for ax in shared 

2914 for val in getattr(ax.dataLim, f"interval{name}") 

2915 if np.isfinite(val)] 

2916 if values: 

2917 x0, x1 = (min(values), max(values)) 

2918 elif getattr(self._viewLim, f"mutated{name}")(): 

2919 # No data, but explicit viewLims already set: 

2920 # in mutatedx or mutatedy. 

2921 return 

2922 else: 

2923 x0, x1 = (-np.inf, np.inf) 

2924 # If x0 and x1 are nonfinite, get default limits from the locator. 

2925 locator = axis.get_major_locator() 

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

2927 # Find the minimum minpos for use in the margin calculation. 

2928 minimum_minpos = min( 

2929 getattr(ax.dataLim, f"minpos{name}") for ax in shared) 

2930 

2931 # Prevent margin addition from crossing a sticky value. A small 

2932 # tolerance must be added due to floating point issues with 

2933 # streamplot; it is defined relative to x0, x1, x1-x0 but has 

2934 # no absolute term (e.g. "+1e-8") to avoid issues when working with 

2935 # datasets where all values are tiny (less than 1e-8). 

2936 tol = 1e-5 * max(abs(x0), abs(x1), abs(x1 - x0)) 

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

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

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

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

2941 i1 = stickies.searchsorted(x1 - tol) 

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

2943 

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

2945 # non-linear scales. 

2946 transform = axis.get_transform() 

2947 inverse_trans = transform.inverted() 

2948 x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minimum_minpos) 

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

2950 delta = (x1t - x0t) * margin 

2951 if not np.isfinite(delta): 

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

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

2954 

2955 # Apply sticky bounds. 

2956 if x0bound is not None: 

2957 x0 = max(x0, x0bound) 

2958 if x1bound is not None: 

2959 x1 = min(x1, x1bound) 

2960 

2961 if not self._tight: 

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

2963 set_bound(x0, x1) 

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

2965 

2966 handle_single_axis( 

2967 scalex, self._shared_axes["x"], 'x', self.xaxis, self._xmargin, 

2968 x_stickies, self.set_xbound) 

2969 handle_single_axis( 

2970 scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin, 

2971 y_stickies, self.set_ybound) 

2972 

2973 def _update_title_position(self, renderer): 

2974 """ 

2975 Update the title position based on the bounding box enclosing 

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

2977 """ 

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

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

2980 return 

2981 

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

2983 

2984 # Need to check all our twins too, and all the children as well. 

2985 axs = self._twinned_axes.get_siblings(self) + self.child_axes 

2986 for ax in self.child_axes: # Child positions must be updated first. 

2987 locator = ax.get_axes_locator() 

2988 ax.apply_aspect(locator(self, renderer) if locator else None) 

2989 

2990 for title in titles: 

2991 x, _ = title.get_position() 

2992 # need to start again in case of window resizing 

2993 title.set_position((x, 1.0)) 

2994 top = -np.inf 

2995 for ax in axs: 

2996 bb = None 

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

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

2999 bb = ax.xaxis.get_tightbbox(renderer) 

3000 if bb is None: 

3001 if 'outline' in ax.spines: 

3002 # Special case for colorbars: 

3003 bb = ax.spines['outline'].get_window_extent() 

3004 else: 

3005 bb = ax.get_window_extent(renderer) 

3006 top = max(top, bb.ymax) 

3007 if title.get_text(): 

3008 ax.yaxis.get_tightbbox(renderer) # update offsetText 

3009 if ax.yaxis.offsetText.get_text(): 

3010 bb = ax.yaxis.offsetText.get_tightbbox(renderer) 

3011 if bb.intersection(title.get_tightbbox(renderer), bb): 

3012 top = bb.ymax 

3013 if top < 0: 

3014 # the top of Axes is not even on the figure, so don't try and 

3015 # automatically place it. 

3016 _log.debug('top of Axes not in the figure, so title not moved') 

3017 return 

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

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

3020 title.set_position((x, y)) 

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

3022 # so we need to adjust again. 

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

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

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

3026 title.set_position((x, y)) 

3027 

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

3029 for title in titles: 

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

3031 x, _ = title.get_position() 

3032 title.set_position((x, ymax)) 

3033 

3034 # Drawing 

3035 @martist.allow_rasterization 

3036 def draw(self, renderer): 

3037 # docstring inherited 

3038 if renderer is None: 

3039 raise RuntimeError('No renderer defined') 

3040 if not self.get_visible(): 

3041 return 

3042 self._unstale_viewLim() 

3043 

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

3045 

3046 # prevent triggering call backs during the draw process 

3047 self._stale = True 

3048 

3049 # loop over self and child Axes... 

3050 locator = self.get_axes_locator() 

3051 self.apply_aspect(locator(self, renderer) if locator else None) 

3052 

3053 artists = self.get_children() 

3054 artists.remove(self.patch) 

3055 

3056 # the frame draws the edges around the Axes patch -- we 

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

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

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

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

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

3062 artists.remove(spine) 

3063 

3064 self._update_title_position(renderer) 

3065 

3066 if not self.axison: 

3067 for _axis in self._axis_map.values(): 

3068 artists.remove(_axis) 

3069 

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

3071 artists = [ 

3072 a for a in artists 

3073 if not a.get_animated() or isinstance(a, mimage.AxesImage)] 

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

3075 

3076 # rasterize artists with negative zorder 

3077 # if the minimum zorder is negative, start rasterization 

3078 rasterization_zorder = self._rasterization_zorder 

3079 

3080 if (rasterization_zorder is not None and 

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

3082 renderer.start_rasterizing() 

3083 artists_rasterized = [a for a in artists 

3084 if a.zorder < rasterization_zorder] 

3085 artists = [a for a in artists 

3086 if a.zorder >= rasterization_zorder] 

3087 else: 

3088 artists_rasterized = [] 

3089 

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

3091 # will draw the edges 

3092 if self.axison and self._frameon: 

3093 self.patch.draw(renderer) 

3094 

3095 if artists_rasterized: 

3096 for a in artists_rasterized: 

3097 a.draw(renderer) 

3098 renderer.stop_rasterizing() 

3099 

3100 mimage._draw_list_compositing_images( 

3101 renderer, self, artists, self.figure.suppressComposite) 

3102 

3103 renderer.close_group('axes') 

3104 self.stale = False 

3105 

3106 def draw_artist(self, a): 

3107 """ 

3108 Efficiently redraw a single artist. 

3109 """ 

3110 a.draw(self.figure.canvas.get_renderer()) 

3111 

3112 def redraw_in_frame(self): 

3113 """ 

3114 Efficiently redraw Axes data, but not axis ticks, labels, etc. 

3115 """ 

3116 with ExitStack() as stack: 

3117 for artist in [*self._axis_map.values(), 

3118 self.title, self._left_title, self._right_title]: 

3119 stack.enter_context(artist._cm_set(visible=False)) 

3120 self.draw(self.figure.canvas.get_renderer()) 

3121 

3122 @_api.deprecated("3.6", alternative="Axes.figure.canvas.get_renderer()") 

3123 def get_renderer_cache(self): 

3124 return self.figure.canvas.get_renderer() 

3125 

3126 # Axes rectangle characteristics 

3127 

3128 def get_frame_on(self): 

3129 """Get whether the Axes rectangle patch is drawn.""" 

3130 return self._frameon 

3131 

3132 def set_frame_on(self, b): 

3133 """ 

3134 Set whether the Axes rectangle patch is drawn. 

3135 

3136 Parameters 

3137 ---------- 

3138 b : bool 

3139 """ 

3140 self._frameon = b 

3141 self.stale = True 

3142 

3143 def get_axisbelow(self): 

3144 """ 

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

3146 

3147 Returns 

3148 ------- 

3149 bool or 'line' 

3150 

3151 See Also 

3152 -------- 

3153 set_axisbelow 

3154 """ 

3155 return self._axisbelow 

3156 

3157 def set_axisbelow(self, b): 

3158 """ 

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

3160 

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

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

3163 

3164 Parameters 

3165 ---------- 

3166 b : bool or 'line' 

3167 Possible values: 

3168 

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

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

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

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

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

3174 and lines / markers. 

3175 

3176 See Also 

3177 -------- 

3178 get_axisbelow 

3179 """ 

3180 # Check that b is True, False or 'line' 

3181 self._axisbelow = axisbelow = validate_axisbelow(b) 

3182 zorder = { 

3183 True: 0.5, 

3184 'line': 1.5, 

3185 False: 2.5, 

3186 }[axisbelow] 

3187 for axis in self._axis_map.values(): 

3188 axis.set_zorder(zorder) 

3189 self.stale = True 

3190 

3191 @_docstring.dedent_interpd 

3192 @_api.rename_parameter("3.5", "b", "visible") 

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

3194 """ 

3195 Configure the grid lines. 

3196 

3197 Parameters 

3198 ---------- 

3199 visible : bool or None, optional 

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

3201 is assumed you want the grid on and *visible* will be set to True. 

3202 

3203 If *visible* is *None* and there are no *kwargs*, this toggles the 

3204 visibility of the lines. 

3205 

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

3207 The grid lines to apply the changes on. 

3208 

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

3210 The axis to apply the changes on. 

3211 

3212 **kwargs : `.Line2D` properties 

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

3214 

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

3216 

3217 Valid keyword arguments are: 

3218 

3219 %(Line2D:kwdoc)s 

3220 

3221 Notes 

3222 ----- 

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

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

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

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

3227 `~.Artist.set_zorder` method of each axis. 

3228 """ 

3229 _api.check_in_list(['x', 'y', 'both'], axis=axis) 

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

3231 self.xaxis.grid(visible, which=which, **kwargs) 

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

3233 self.yaxis.grid(visible, which=which, **kwargs) 

3234 

3235 def ticklabel_format(self, *, axis='both', style='', scilimits=None, 

3236 useOffset=None, useLocale=None, useMathText=None): 

3237 r""" 

3238 Configure the `.ScalarFormatter` used by default for linear Axes. 

3239 

3240 If a parameter is not set, the corresponding property of the formatter 

3241 is left unchanged. 

3242 

3243 Parameters 

3244 ---------- 

3245 axis : {'x', 'y', 'both'}, default: 'both' 

3246 The axis to configure. Only major ticks are affected. 

3247 

3248 style : {'sci', 'scientific', 'plain'} 

3249 Whether to use scientific notation. 

3250 The formatter default is to use scientific notation. 

3251 

3252 scilimits : pair of ints (m, n) 

3253 Scientific notation is used only for numbers outside the range 

3254 10\ :sup:`m` to 10\ :sup:`n` (and only if the formatter is 

3255 configured to use scientific notation at all). Use (0, 0) to 

3256 include all numbers. Use (m, m) where m != 0 to fix the order of 

3257 magnitude to 10\ :sup:`m`. 

3258 The formatter default is :rc:`axes.formatter.limits`. 

3259 

3260 useOffset : bool or float 

3261 If True, the offset is calculated as needed. 

3262 If False, no offset is used. 

3263 If a numeric value, it sets the offset. 

3264 The formatter default is :rc:`axes.formatter.useoffset`. 

3265 

3266 useLocale : bool 

3267 Whether to format the number using the current locale or using the 

3268 C (English) locale. This affects e.g. the decimal separator. The 

3269 formatter default is :rc:`axes.formatter.use_locale`. 

3270 

3271 useMathText : bool 

3272 Render the offset and scientific notation in mathtext. 

3273 The formatter default is :rc:`axes.formatter.use_mathtext`. 

3274 

3275 Raises 

3276 ------ 

3277 AttributeError 

3278 If the current formatter is not a `.ScalarFormatter`. 

3279 """ 

3280 style = style.lower() 

3281 axis = axis.lower() 

3282 if scilimits is not None: 

3283 try: 

3284 m, n = scilimits 

3285 m + n + 1 # check that both are numbers 

3286 except (ValueError, TypeError) as err: 

3287 raise ValueError("scilimits must be a sequence of 2 integers" 

3288 ) from err 

3289 STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None} 

3290 is_sci_style = _api.check_getitem(STYLES, style=style) 

3291 axis_map = {**{k: [v] for k, v in self._axis_map.items()}, 

3292 'both': list(self._axis_map.values())} 

3293 axises = _api.check_getitem(axis_map, axis=axis) 

3294 try: 

3295 for axis in axises: 

3296 if is_sci_style is not None: 

3297 axis.major.formatter.set_scientific(is_sci_style) 

3298 if scilimits is not None: 

3299 axis.major.formatter.set_powerlimits(scilimits) 

3300 if useOffset is not None: 

3301 axis.major.formatter.set_useOffset(useOffset) 

3302 if useLocale is not None: 

3303 axis.major.formatter.set_useLocale(useLocale) 

3304 if useMathText is not None: 

3305 axis.major.formatter.set_useMathText(useMathText) 

3306 except AttributeError as err: 

3307 raise AttributeError( 

3308 "This method only works with the ScalarFormatter") from err 

3309 

3310 def locator_params(self, axis='both', tight=None, **kwargs): 

3311 """ 

3312 Control behavior of major tick locators. 

3313 

3314 Because the locator is involved in autoscaling, `~.Axes.autoscale_view` 

3315 is called automatically after the parameters are changed. 

3316 

3317 Parameters 

3318 ---------- 

3319 axis : {'both', 'x', 'y'}, default: 'both' 

3320 The axis on which to operate. (For 3D Axes, *axis* can also be 

3321 set to 'z', and 'both' refers to all three axes.) 

3322 tight : bool or None, optional 

3323 Parameter passed to `~.Axes.autoscale_view`. 

3324 Default is None, for no change. 

3325 

3326 Other Parameters 

3327 ---------------- 

3328 **kwargs 

3329 Remaining keyword arguments are passed to directly to the 

3330 ``set_params()`` method of the locator. Supported keywords depend 

3331 on the type of the locator. See for example 

3332 `~.ticker.MaxNLocator.set_params` for the `.ticker.MaxNLocator` 

3333 used by default for linear. 

3334 

3335 Examples 

3336 -------- 

3337 When plotting small subplots, one might want to reduce the maximum 

3338 number of ticks and use tight bounds, for example:: 

3339 

3340 ax.locator_params(tight=True, nbins=4) 

3341 

3342 """ 

3343 _api.check_in_list([*self._axis_names, "both"], axis=axis) 

3344 for name in self._axis_names: 

3345 if axis in [name, "both"]: 

3346 loc = self._axis_map[name].get_major_locator() 

3347 loc.set_params(**kwargs) 

3348 self._request_autoscale_view(name, tight=tight) 

3349 self.stale = True 

3350 

3351 def tick_params(self, axis='both', **kwargs): 

3352 """ 

3353 Change the appearance of ticks, tick labels, and gridlines. 

3354 

3355 Tick properties that are not explicitly set using the keyword 

3356 arguments remain unchanged unless *reset* is True. 

3357 

3358 Parameters 

3359 ---------- 

3360 axis : {'x', 'y', 'both'}, default: 'both' 

3361 The axis to which the parameters are applied. 

3362 which : {'major', 'minor', 'both'}, default: 'major' 

3363 The group of ticks to which the parameters are applied. 

3364 reset : bool, default: False 

3365 Whether to reset the ticks to defaults before updating them. 

3366 

3367 Other Parameters 

3368 ---------------- 

3369 direction : {'in', 'out', 'inout'} 

3370 Puts ticks inside the Axes, outside the Axes, or both. 

3371 length : float 

3372 Tick length in points. 

3373 width : float 

3374 Tick width in points. 

3375 color : color 

3376 Tick color. 

3377 pad : float 

3378 Distance in points between tick and label. 

3379 labelsize : float or str 

3380 Tick label font size in points or as a string (e.g., 'large'). 

3381 labelcolor : color 

3382 Tick label color. 

3383 colors : color 

3384 Tick color and label color. 

3385 zorder : float 

3386 Tick and label zorder. 

3387 bottom, top, left, right : bool 

3388 Whether to draw the respective ticks. 

3389 labelbottom, labeltop, labelleft, labelright : bool 

3390 Whether to draw the respective tick labels. 

3391 labelrotation : float 

3392 Tick label rotation 

3393 grid_color : color 

3394 Gridline color. 

3395 grid_alpha : float 

3396 Transparency of gridlines: 0 (transparent) to 1 (opaque). 

3397 grid_linewidth : float 

3398 Width of gridlines in points. 

3399 grid_linestyle : str 

3400 Any valid `.Line2D` line style spec. 

3401 

3402 Examples 

3403 -------- 

3404 :: 

3405 

3406 ax.tick_params(direction='out', length=6, width=2, colors='r', 

3407 grid_color='r', grid_alpha=0.5) 

3408 

3409 This will make all major ticks be red, pointing out of the box, 

3410 and with dimensions 6 points by 2 points. Tick labels will 

3411 also be red. Gridlines will be red and translucent. 

3412 

3413 """ 

3414 _api.check_in_list(['x', 'y', 'both'], axis=axis) 

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

3416 xkw = dict(kwargs) 

3417 xkw.pop('left', None) 

3418 xkw.pop('right', None) 

3419 xkw.pop('labelleft', None) 

3420 xkw.pop('labelright', None) 

3421 self.xaxis.set_tick_params(**xkw) 

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

3423 ykw = dict(kwargs) 

3424 ykw.pop('top', None) 

3425 ykw.pop('bottom', None) 

3426 ykw.pop('labeltop', None) 

3427 ykw.pop('labelbottom', None) 

3428 self.yaxis.set_tick_params(**ykw) 

3429 

3430 def set_axis_off(self): 

3431 """ 

3432 Turn the x- and y-axis off. 

3433 

3434 This affects the axis lines, ticks, ticklabels, grid and axis labels. 

3435 """ 

3436 self.axison = False 

3437 self.stale = True 

3438 

3439 def set_axis_on(self): 

3440 """ 

3441 Turn the x- and y-axis on. 

3442 

3443 This affects the axis lines, ticks, ticklabels, grid and axis labels. 

3444 """ 

3445 self.axison = True 

3446 self.stale = True 

3447 

3448 # data limits, ticks, tick labels, and formatting 

3449 

3450 def get_xlabel(self): 

3451 """ 

3452 Get the xlabel text string. 

3453 """ 

3454 label = self.xaxis.get_label() 

3455 return label.get_text() 

3456 

3457 def set_xlabel(self, xlabel, fontdict=None, labelpad=None, *, 

3458 loc=None, **kwargs): 

3459 """ 

3460 Set the label for the x-axis. 

3461 

3462 Parameters 

3463 ---------- 

3464 xlabel : str 

3465 The label text. 

3466 

3467 labelpad : float, default: :rc:`axes.labelpad` 

3468 Spacing in points from the Axes bounding box including ticks 

3469 and tick labels. If None, the previous value is left as is. 

3470 

3471 loc : {'left', 'center', 'right'}, default: :rc:`xaxis.labellocation` 

3472 The label position. This is a high-level alternative for passing 

3473 parameters *x* and *horizontalalignment*. 

3474 

3475 Other Parameters 

3476 ---------------- 

3477 **kwargs : `.Text` properties 

3478 `.Text` properties control the appearance of the label. 

3479 

3480 See Also 

3481 -------- 

3482 text : Documents the properties supported by `.Text`. 

3483 """ 

3484 if labelpad is not None: 

3485 self.xaxis.labelpad = labelpad 

3486 protected_kw = ['x', 'horizontalalignment', 'ha'] 

3487 if {*kwargs} & {*protected_kw}: 

3488 if loc is not None: 

3489 raise TypeError(f"Specifying 'loc' is disallowed when any of " 

3490 f"its corresponding low level keyword " 

3491 f"arguments ({protected_kw}) are also " 

3492 f"supplied") 

3493 

3494 else: 

3495 loc = (loc if loc is not None 

3496 else mpl.rcParams['xaxis.labellocation']) 

3497 _api.check_in_list(('left', 'center', 'right'), loc=loc) 

3498 

3499 x = { 

3500 'left': 0, 

3501 'center': 0.5, 

3502 'right': 1, 

3503 }[loc] 

3504 kwargs.update(x=x, horizontalalignment=loc) 

3505 

3506 return self.xaxis.set_label_text(xlabel, fontdict, **kwargs) 

3507 

3508 def invert_xaxis(self): 

3509 """ 

3510 Invert the x-axis. 

3511 

3512 See Also 

3513 -------- 

3514 xaxis_inverted 

3515 get_xlim, set_xlim 

3516 get_xbound, set_xbound 

3517 """ 

3518 self.xaxis.set_inverted(not self.xaxis.get_inverted()) 

3519 

3520 xaxis_inverted = _axis_method_wrapper("xaxis", "get_inverted") 

3521 

3522 def get_xbound(self): 

3523 """ 

3524 Return the lower and upper x-axis bounds, in increasing order. 

3525 

3526 See Also 

3527 -------- 

3528 set_xbound 

3529 get_xlim, set_xlim 

3530 invert_xaxis, xaxis_inverted 

3531 """ 

3532 left, right = self.get_xlim() 

3533 if left < right: 

3534 return left, right 

3535 else: 

3536 return right, left 

3537 

3538 def set_xbound(self, lower=None, upper=None): 

3539 """ 

3540 Set the lower and upper numerical bounds of the x-axis. 

3541 

3542 This method will honor axis inversion regardless of parameter order. 

3543 It will not change the autoscaling setting (`.get_autoscalex_on()`). 

3544 

3545 Parameters 

3546 ---------- 

3547 lower, upper : float or None 

3548 The lower and upper bounds. If *None*, the respective axis bound 

3549 is not modified. 

3550 

3551 See Also 

3552 -------- 

3553 get_xbound 

3554 get_xlim, set_xlim 

3555 invert_xaxis, xaxis_inverted 

3556 """ 

3557 if upper is None and np.iterable(lower): 

3558 lower, upper = lower 

3559 

3560 old_lower, old_upper = self.get_xbound() 

3561 if lower is None: 

3562 lower = old_lower 

3563 if upper is None: 

3564 upper = old_upper 

3565 

3566 self.set_xlim(sorted((lower, upper), 

3567 reverse=bool(self.xaxis_inverted())), 

3568 auto=None) 

3569 

3570 def get_xlim(self): 

3571 """ 

3572 Return the x-axis view limits. 

3573 

3574 Returns 

3575 ------- 

3576 left, right : (float, float) 

3577 The current x-axis limits in data coordinates. 

3578 

3579 See Also 

3580 -------- 

3581 .Axes.set_xlim 

3582 set_xbound, get_xbound 

3583 invert_xaxis, xaxis_inverted 

3584 

3585 Notes 

3586 ----- 

3587 The x-axis may be inverted, in which case the *left* value will 

3588 be greater than the *right* value. 

3589 """ 

3590 return tuple(self.viewLim.intervalx) 

3591 

3592 def _validate_converted_limits(self, limit, convert): 

3593 """ 

3594 Raise ValueError if converted limits are non-finite. 

3595 

3596 Note that this function also accepts None as a limit argument. 

3597 

3598 Returns 

3599 ------- 

3600 The limit value after call to convert(), or None if limit is None. 

3601 """ 

3602 if limit is not None: 

3603 converted_limit = convert(limit) 

3604 if (isinstance(converted_limit, Real) 

3605 and not np.isfinite(converted_limit)): 

3606 raise ValueError("Axis limits cannot be NaN or Inf") 

3607 return converted_limit 

3608 

3609 @_api.make_keyword_only("3.6", "emit") 

3610 def set_xlim(self, left=None, right=None, emit=True, auto=False, 

3611 *, xmin=None, xmax=None): 

3612 """ 

3613 Set the x-axis view limits. 

3614 

3615 Parameters 

3616 ---------- 

3617 left : float, optional 

3618 The left xlim in data coordinates. Passing *None* leaves the 

3619 limit unchanged. 

3620 

3621 The left and right xlims may also be passed as the tuple 

3622 (*left*, *right*) as the first positional argument (or as 

3623 the *left* keyword argument). 

3624 

3625 .. ACCEPTS: (bottom: float, top: float) 

3626 

3627 right : float, optional 

3628 The right xlim in data coordinates. Passing *None* leaves the 

3629 limit unchanged. 

3630 

3631 emit : bool, default: True 

3632 Whether to notify observers of limit change. 

3633 

3634 auto : bool or None, default: False 

3635 Whether to turn on autoscaling of the x-axis. True turns on, 

3636 False turns off, None leaves unchanged. 

3637 

3638 xmin, xmax : float, optional 

3639 They are equivalent to left and right respectively, and it is an 

3640 error to pass both *xmin* and *left* or *xmax* and *right*. 

3641 

3642 Returns 

3643 ------- 

3644 left, right : (float, float) 

3645 The new x-axis limits in data coordinates. 

3646 

3647 See Also 

3648 -------- 

3649 get_xlim 

3650 set_xbound, get_xbound 

3651 invert_xaxis, xaxis_inverted 

3652 

3653 Notes 

3654 ----- 

3655 The *left* value may be greater than the *right* value, in which 

3656 case the x-axis values will decrease from left to right. 

3657 

3658 Examples 

3659 -------- 

3660 >>> set_xlim(left, right) 

3661 >>> set_xlim((left, right)) 

3662 >>> left, right = set_xlim(left, right) 

3663 

3664 One limit may be left unchanged. 

3665 

3666 >>> set_xlim(right=right_lim) 

3667 

3668 Limits may be passed in reverse order to flip the direction of 

3669 the x-axis. For example, suppose *x* represents the number of 

3670 years before present. The x-axis limits might be set like the 

3671 following so 5000 years ago is on the left of the plot and the 

3672 present is on the right. 

3673 

3674 >>> set_xlim(5000, 0) 

3675 """ 

3676 if right is None and np.iterable(left): 

3677 left, right = left 

3678 if xmin is not None: 

3679 if left is not None: 

3680 raise TypeError("Cannot pass both 'left' and 'xmin'") 

3681 left = xmin 

3682 if xmax is not None: 

3683 if right is not None: 

3684 raise TypeError("Cannot pass both 'right' and 'xmax'") 

3685 right = xmax 

3686 return self.xaxis._set_lim(left, right, emit=emit, auto=auto) 

3687 

3688 get_xscale = _axis_method_wrapper("xaxis", "get_scale") 

3689 set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale") 

3690 get_xticks = _axis_method_wrapper("xaxis", "get_ticklocs") 

3691 set_xticks = _axis_method_wrapper("xaxis", "set_ticks") 

3692 get_xmajorticklabels = _axis_method_wrapper("xaxis", "get_majorticklabels") 

3693 get_xminorticklabels = _axis_method_wrapper("xaxis", "get_minorticklabels") 

3694 get_xticklabels = _axis_method_wrapper("xaxis", "get_ticklabels") 

3695 set_xticklabels = _axis_method_wrapper( 

3696 "xaxis", "_set_ticklabels", 

3697 doc_sub={"Axis.set_ticks": "Axes.set_xticks"}) 

3698 

3699 def get_ylabel(self): 

3700 """ 

3701 Get the ylabel text string. 

3702 """ 

3703 label = self.yaxis.get_label() 

3704 return label.get_text() 

3705 

3706 def set_ylabel(self, ylabel, fontdict=None, labelpad=None, *, 

3707 loc=None, **kwargs): 

3708 """ 

3709 Set the label for the y-axis. 

3710 

3711 Parameters 

3712 ---------- 

3713 ylabel : str 

3714 The label text. 

3715 

3716 labelpad : float, default: :rc:`axes.labelpad` 

3717 Spacing in points from the Axes bounding box including ticks 

3718 and tick labels. If None, the previous value is left as is. 

3719 

3720 loc : {'bottom', 'center', 'top'}, default: :rc:`yaxis.labellocation` 

3721 The label position. This is a high-level alternative for passing 

3722 parameters *y* and *horizontalalignment*. 

3723 

3724 Other Parameters 

3725 ---------------- 

3726 **kwargs : `.Text` properties 

3727 `.Text` properties control the appearance of the label. 

3728 

3729 See Also 

3730 -------- 

3731 text : Documents the properties supported by `.Text`. 

3732 """ 

3733 if labelpad is not None: 

3734 self.yaxis.labelpad = labelpad 

3735 protected_kw = ['y', 'horizontalalignment', 'ha'] 

3736 if {*kwargs} & {*protected_kw}: 

3737 if loc is not None: 

3738 raise TypeError(f"Specifying 'loc' is disallowed when any of " 

3739 f"its corresponding low level keyword " 

3740 f"arguments ({protected_kw}) are also " 

3741 f"supplied") 

3742 

3743 else: 

3744 loc = (loc if loc is not None 

3745 else mpl.rcParams['yaxis.labellocation']) 

3746 _api.check_in_list(('bottom', 'center', 'top'), loc=loc) 

3747 

3748 y, ha = { 

3749 'bottom': (0, 'left'), 

3750 'center': (0.5, 'center'), 

3751 'top': (1, 'right') 

3752 }[loc] 

3753 kwargs.update(y=y, horizontalalignment=ha) 

3754 

3755 return self.yaxis.set_label_text(ylabel, fontdict, **kwargs) 

3756 

3757 def invert_yaxis(self): 

3758 """ 

3759 Invert the y-axis. 

3760 

3761 See Also 

3762 -------- 

3763 yaxis_inverted 

3764 get_ylim, set_ylim 

3765 get_ybound, set_ybound 

3766 """ 

3767 self.yaxis.set_inverted(not self.yaxis.get_inverted()) 

3768 

3769 yaxis_inverted = _axis_method_wrapper("yaxis", "get_inverted") 

3770 

3771 def get_ybound(self): 

3772 """ 

3773 Return the lower and upper y-axis bounds, in increasing order. 

3774 

3775 See Also 

3776 -------- 

3777 set_ybound 

3778 get_ylim, set_ylim 

3779 invert_yaxis, yaxis_inverted 

3780 """ 

3781 bottom, top = self.get_ylim() 

3782 if bottom < top: 

3783 return bottom, top 

3784 else: 

3785 return top, bottom 

3786 

3787 def set_ybound(self, lower=None, upper=None): 

3788 """ 

3789 Set the lower and upper numerical bounds of the y-axis. 

3790 

3791 This method will honor axis inversion regardless of parameter order. 

3792 It will not change the autoscaling setting (`.get_autoscaley_on()`). 

3793 

3794 Parameters 

3795 ---------- 

3796 lower, upper : float or None 

3797 The lower and upper bounds. If *None*, the respective axis bound 

3798 is not modified. 

3799 

3800 See Also 

3801 -------- 

3802 get_ybound 

3803 get_ylim, set_ylim 

3804 invert_yaxis, yaxis_inverted 

3805 """ 

3806 if upper is None and np.iterable(lower): 

3807 lower, upper = lower 

3808 

3809 old_lower, old_upper = self.get_ybound() 

3810 if lower is None: 

3811 lower = old_lower 

3812 if upper is None: 

3813 upper = old_upper 

3814 

3815 self.set_ylim(sorted((lower, upper), 

3816 reverse=bool(self.yaxis_inverted())), 

3817 auto=None) 

3818 

3819 def get_ylim(self): 

3820 """ 

3821 Return the y-axis view limits. 

3822 

3823 Returns 

3824 ------- 

3825 bottom, top : (float, float) 

3826 The current y-axis limits in data coordinates. 

3827 

3828 See Also 

3829 -------- 

3830 .Axes.set_ylim 

3831 set_ybound, get_ybound 

3832 invert_yaxis, yaxis_inverted 

3833 

3834 Notes 

3835 ----- 

3836 The y-axis may be inverted, in which case the *bottom* value 

3837 will be greater than the *top* value. 

3838 """ 

3839 return tuple(self.viewLim.intervaly) 

3840 

3841 @_api.make_keyword_only("3.6", "emit") 

3842 def set_ylim(self, bottom=None, top=None, emit=True, auto=False, 

3843 *, ymin=None, ymax=None): 

3844 """ 

3845 Set the y-axis view limits. 

3846 

3847 Parameters 

3848 ---------- 

3849 bottom : float, optional 

3850 The bottom ylim in data coordinates. Passing *None* leaves the 

3851 limit unchanged. 

3852 

3853 The bottom and top ylims may also be passed as the tuple 

3854 (*bottom*, *top*) as the first positional argument (or as 

3855 the *bottom* keyword argument). 

3856 

3857 .. ACCEPTS: (bottom: float, top: float) 

3858 

3859 top : float, optional 

3860 The top ylim in data coordinates. Passing *None* leaves the 

3861 limit unchanged. 

3862 

3863 emit : bool, default: True 

3864 Whether to notify observers of limit change. 

3865 

3866 auto : bool or None, default: False 

3867 Whether to turn on autoscaling of the y-axis. *True* turns on, 

3868 *False* turns off, *None* leaves unchanged. 

3869 

3870 ymin, ymax : float, optional 

3871 They are equivalent to bottom and top respectively, and it is an 

3872 error to pass both *ymin* and *bottom* or *ymax* and *top*. 

3873 

3874 Returns 

3875 ------- 

3876 bottom, top : (float, float) 

3877 The new y-axis limits in data coordinates. 

3878 

3879 See Also 

3880 -------- 

3881 get_ylim 

3882 set_ybound, get_ybound 

3883 invert_yaxis, yaxis_inverted 

3884 

3885 Notes 

3886 ----- 

3887 The *bottom* value may be greater than the *top* value, in which 

3888 case the y-axis values will decrease from *bottom* to *top*. 

3889 

3890 Examples 

3891 -------- 

3892 >>> set_ylim(bottom, top) 

3893 >>> set_ylim((bottom, top)) 

3894 >>> bottom, top = set_ylim(bottom, top) 

3895 

3896 One limit may be left unchanged. 

3897 

3898 >>> set_ylim(top=top_lim) 

3899 

3900 Limits may be passed in reverse order to flip the direction of 

3901 the y-axis. For example, suppose ``y`` represents depth of the 

3902 ocean in m. The y-axis limits might be set like the following 

3903 so 5000 m depth is at the bottom of the plot and the surface, 

3904 0 m, is at the top. 

3905 

3906 >>> set_ylim(5000, 0) 

3907 """ 

3908 if top is None and np.iterable(bottom): 

3909 bottom, top = bottom 

3910 if ymin is not None: 

3911 if bottom is not None: 

3912 raise TypeError("Cannot pass both 'bottom' and 'ymin'") 

3913 bottom = ymin 

3914 if ymax is not None: 

3915 if top is not None: 

3916 raise TypeError("Cannot pass both 'top' and 'ymax'") 

3917 top = ymax 

3918 return self.yaxis._set_lim(bottom, top, emit=emit, auto=auto) 

3919 

3920 get_yscale = _axis_method_wrapper("yaxis", "get_scale") 

3921 set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale") 

3922 get_yticks = _axis_method_wrapper("yaxis", "get_ticklocs") 

3923 set_yticks = _axis_method_wrapper("yaxis", "set_ticks") 

3924 get_ymajorticklabels = _axis_method_wrapper("yaxis", "get_majorticklabels") 

3925 get_yminorticklabels = _axis_method_wrapper("yaxis", "get_minorticklabels") 

3926 get_yticklabels = _axis_method_wrapper("yaxis", "get_ticklabels") 

3927 set_yticklabels = _axis_method_wrapper( 

3928 "yaxis", "_set_ticklabels", 

3929 doc_sub={"Axis.set_ticks": "Axes.set_yticks"}) 

3930 

3931 xaxis_date = _axis_method_wrapper("xaxis", "axis_date") 

3932 yaxis_date = _axis_method_wrapper("yaxis", "axis_date") 

3933 

3934 def format_xdata(self, x): 

3935 """ 

3936 Return *x* formatted as an x-value. 

3937 

3938 This function will use the `.fmt_xdata` attribute if it is not None, 

3939 else will fall back on the xaxis major formatter. 

3940 """ 

3941 return (self.fmt_xdata if self.fmt_xdata is not None 

3942 else self.xaxis.get_major_formatter().format_data_short)(x) 

3943 

3944 def format_ydata(self, y): 

3945 """ 

3946 Return *y* formatted as an y-value. 

3947 

3948 This function will use the `.fmt_ydata` attribute if it is not None, 

3949 else will fall back on the yaxis major formatter. 

3950 """ 

3951 return (self.fmt_ydata if self.fmt_ydata is not None 

3952 else self.yaxis.get_major_formatter().format_data_short)(y) 

3953 

3954 def format_coord(self, x, y): 

3955 """Return a format string formatting the *x*, *y* coordinates.""" 

3956 return "x={} y={}".format( 

3957 "???" if x is None else self.format_xdata(x), 

3958 "???" if y is None else self.format_ydata(y), 

3959 ) 

3960 

3961 def minorticks_on(self): 

3962 """ 

3963 Display minor ticks on the Axes. 

3964 

3965 Displaying minor ticks may reduce performance; you may turn them off 

3966 using `minorticks_off()` if drawing speed is a problem. 

3967 """ 

3968 for ax in (self.xaxis, self.yaxis): 

3969 scale = ax.get_scale() 

3970 if scale == 'log': 

3971 s = ax._scale 

3972 ax.set_minor_locator(mticker.LogLocator(s.base, s.subs)) 

3973 elif scale == 'symlog': 

3974 s = ax._scale 

3975 ax.set_minor_locator( 

3976 mticker.SymmetricalLogLocator(s._transform, s.subs)) 

3977 else: 

3978 ax.set_minor_locator(mticker.AutoMinorLocator()) 

3979 

3980 def minorticks_off(self): 

3981 """Remove minor ticks from the Axes.""" 

3982 self.xaxis.set_minor_locator(mticker.NullLocator()) 

3983 self.yaxis.set_minor_locator(mticker.NullLocator()) 

3984 

3985 # Interactive manipulation 

3986 

3987 def can_zoom(self): 

3988 """ 

3989 Return whether this Axes supports the zoom box button functionality. 

3990 """ 

3991 return True 

3992 

3993 def can_pan(self): 

3994 """ 

3995 Return whether this Axes supports any pan/zoom button functionality. 

3996 """ 

3997 return True 

3998 

3999 def get_navigate(self): 

4000 """ 

4001 Get whether the Axes responds to navigation commands. 

4002 """ 

4003 return self._navigate 

4004 

4005 def set_navigate(self, b): 

4006 """ 

4007 Set whether the Axes responds to navigation toolbar commands. 

4008 

4009 Parameters 

4010 ---------- 

4011 b : bool 

4012 """ 

4013 self._navigate = b 

4014 

4015 def get_navigate_mode(self): 

4016 """ 

4017 Get the navigation toolbar button status: 'PAN', 'ZOOM', or None. 

4018 """ 

4019 return self._navigate_mode 

4020 

4021 def set_navigate_mode(self, b): 

4022 """ 

4023 Set the navigation toolbar button status. 

4024 

4025 .. warning:: 

4026 This is not a user-API function. 

4027 

4028 """ 

4029 self._navigate_mode = b 

4030 

4031 def _get_view(self): 

4032 """ 

4033 Save information required to reproduce the current view. 

4034 

4035 Called before a view is changed, such as during a pan or zoom 

4036 initiated by the user. You may return any information you deem 

4037 necessary to describe the view. 

4038 

4039 .. note:: 

4040 

4041 Intended to be overridden by new projection types, but if not, the 

4042 default implementation saves the view limits. You *must* implement 

4043 :meth:`_set_view` if you implement this method. 

4044 """ 

4045 xmin, xmax = self.get_xlim() 

4046 ymin, ymax = self.get_ylim() 

4047 return xmin, xmax, ymin, ymax 

4048 

4049 def _set_view(self, view): 

4050 """ 

4051 Apply a previously saved view. 

4052 

4053 Called when restoring a view, such as with the navigation buttons. 

4054 

4055 .. note:: 

4056 

4057 Intended to be overridden by new projection types, but if not, the 

4058 default implementation restores the view limits. You *must* 

4059 implement :meth:`_get_view` if you implement this method. 

4060 """ 

4061 xmin, xmax, ymin, ymax = view 

4062 self.set_xlim((xmin, xmax)) 

4063 self.set_ylim((ymin, ymax)) 

4064 

4065 def _prepare_view_from_bbox(self, bbox, direction='in', 

4066 mode=None, twinx=False, twiny=False): 

4067 """ 

4068 Helper function to prepare the new bounds from a bbox. 

4069 

4070 This helper function returns the new x and y bounds from the zoom 

4071 bbox. This a convenience method to abstract the bbox logic 

4072 out of the base setter. 

4073 """ 

4074 if len(bbox) == 3: 

4075 xp, yp, scl = bbox # Zooming code 

4076 if scl == 0: # Should not happen 

4077 scl = 1. 

4078 if scl > 1: 

4079 direction = 'in' 

4080 else: 

4081 direction = 'out' 

4082 scl = 1/scl 

4083 # get the limits of the axes 

4084 (xmin, ymin), (xmax, ymax) = self.transData.transform( 

4085 np.transpose([self.get_xlim(), self.get_ylim()])) 

4086 # set the range 

4087 xwidth = xmax - xmin 

4088 ywidth = ymax - ymin 

4089 xcen = (xmax + xmin)*.5 

4090 ycen = (ymax + ymin)*.5 

4091 xzc = (xp*(scl - 1) + xcen)/scl 

4092 yzc = (yp*(scl - 1) + ycen)/scl 

4093 bbox = [xzc - xwidth/2./scl, yzc - ywidth/2./scl, 

4094 xzc + xwidth/2./scl, yzc + ywidth/2./scl] 

4095 elif len(bbox) != 4: 

4096 # should be len 3 or 4 but nothing else 

4097 _api.warn_external( 

4098 "Warning in _set_view_from_bbox: bounding box is not a tuple " 

4099 "of length 3 or 4. Ignoring the view change.") 

4100 return 

4101 

4102 # Original limits. 

4103 xmin0, xmax0 = self.get_xbound() 

4104 ymin0, ymax0 = self.get_ybound() 

4105 # The zoom box in screen coords. 

4106 startx, starty, stopx, stopy = bbox 

4107 # Convert to data coords. 

4108 (startx, starty), (stopx, stopy) = self.transData.inverted().transform( 

4109 [(startx, starty), (stopx, stopy)]) 

4110 # Clip to axes limits. 

4111 xmin, xmax = np.clip(sorted([startx, stopx]), xmin0, xmax0) 

4112 ymin, ymax = np.clip(sorted([starty, stopy]), ymin0, ymax0) 

4113 # Don't double-zoom twinned axes or if zooming only the other axis. 

4114 if twinx or mode == "y": 

4115 xmin, xmax = xmin0, xmax0 

4116 if twiny or mode == "x": 

4117 ymin, ymax = ymin0, ymax0 

4118 

4119 if direction == "in": 

4120 new_xbound = xmin, xmax 

4121 new_ybound = ymin, ymax 

4122 

4123 elif direction == "out": 

4124 x_trf = self.xaxis.get_transform() 

4125 sxmin0, sxmax0, sxmin, sxmax = x_trf.transform( 

4126 [xmin0, xmax0, xmin, xmax]) # To screen space. 

4127 factor = (sxmax0 - sxmin0) / (sxmax - sxmin) # Unzoom factor. 

4128 # Move original bounds away by 

4129 # (factor) x (distance between unzoom box and Axes bbox). 

4130 sxmin1 = sxmin0 - factor * (sxmin - sxmin0) 

4131 sxmax1 = sxmax0 + factor * (sxmax0 - sxmax) 

4132 # And back to data space. 

4133 new_xbound = x_trf.inverted().transform([sxmin1, sxmax1]) 

4134 

4135 y_trf = self.yaxis.get_transform() 

4136 symin0, symax0, symin, symax = y_trf.transform( 

4137 [ymin0, ymax0, ymin, ymax]) 

4138 factor = (symax0 - symin0) / (symax - symin) 

4139 symin1 = symin0 - factor * (symin - symin0) 

4140 symax1 = symax0 + factor * (symax0 - symax) 

4141 new_ybound = y_trf.inverted().transform([symin1, symax1]) 

4142 

4143 return new_xbound, new_ybound 

4144 

4145 def _set_view_from_bbox(self, bbox, direction='in', 

4146 mode=None, twinx=False, twiny=False): 

4147 """ 

4148 Update view from a selection bbox. 

4149 

4150 .. note:: 

4151 

4152 Intended to be overridden by new projection types, but if not, the 

4153 default implementation sets the view limits to the bbox directly. 

4154 

4155 Parameters 

4156 ---------- 

4157 bbox : 4-tuple or 3 tuple 

4158 * If bbox is a 4 tuple, it is the selected bounding box limits, 

4159 in *display* coordinates. 

4160 * If bbox is a 3 tuple, it is an (xp, yp, scl) triple, where 

4161 (xp, yp) is the center of zooming and scl the scale factor to 

4162 zoom by. 

4163 

4164 direction : str 

4165 The direction to apply the bounding box. 

4166 * `'in'` - The bounding box describes the view directly, i.e., 

4167 it zooms in. 

4168 * `'out'` - The bounding box describes the size to make the 

4169 existing view, i.e., it zooms out. 

4170 

4171 mode : str or None 

4172 The selection mode, whether to apply the bounding box in only the 

4173 `'x'` direction, `'y'` direction or both (`None`). 

4174 

4175 twinx : bool 

4176 Whether this axis is twinned in the *x*-direction. 

4177 

4178 twiny : bool 

4179 Whether this axis is twinned in the *y*-direction. 

4180 """ 

4181 new_xbound, new_ybound = self._prepare_view_from_bbox( 

4182 bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny) 

4183 if not twinx and mode != "y": 

4184 self.set_xbound(new_xbound) 

4185 self.set_autoscalex_on(False) 

4186 if not twiny and mode != "x": 

4187 self.set_ybound(new_ybound) 

4188 self.set_autoscaley_on(False) 

4189 

4190 def start_pan(self, x, y, button): 

4191 """ 

4192 Called when a pan operation has started. 

4193 

4194 Parameters 

4195 ---------- 

4196 x, y : float 

4197 The mouse coordinates in display coords. 

4198 button : `.MouseButton` 

4199 The pressed mouse button. 

4200 

4201 Notes 

4202 ----- 

4203 This is intended to be overridden by new projection types. 

4204 """ 

4205 self._pan_start = types.SimpleNamespace( 

4206 lim=self.viewLim.frozen(), 

4207 trans=self.transData.frozen(), 

4208 trans_inverse=self.transData.inverted().frozen(), 

4209 bbox=self.bbox.frozen(), 

4210 x=x, 

4211 y=y) 

4212 

4213 def end_pan(self): 

4214 """ 

4215 Called when a pan operation completes (when the mouse button is up.) 

4216 

4217 Notes 

4218 ----- 

4219 This is intended to be overridden by new projection types. 

4220 """ 

4221 del self._pan_start 

4222 

4223 def _get_pan_points(self, button, key, x, y): 

4224 """ 

4225 Helper function to return the new points after a pan. 

4226 

4227 This helper function returns the points on the axis after a pan has 

4228 occurred. This is a convenience method to abstract the pan logic 

4229 out of the base setter. 

4230 """ 

4231 def format_deltas(key, dx, dy): 

4232 if key == 'control': 

4233 if abs(dx) > abs(dy): 

4234 dy = dx 

4235 else: 

4236 dx = dy 

4237 elif key == 'x': 

4238 dy = 0 

4239 elif key == 'y': 

4240 dx = 0 

4241 elif key == 'shift': 

4242 if 2 * abs(dx) < abs(dy): 

4243 dx = 0 

4244 elif 2 * abs(dy) < abs(dx): 

4245 dy = 0 

4246 elif abs(dx) > abs(dy): 

4247 dy = dy / abs(dy) * abs(dx) 

4248 else: 

4249 dx = dx / abs(dx) * abs(dy) 

4250 return dx, dy 

4251 

4252 p = self._pan_start 

4253 dx = x - p.x 

4254 dy = y - p.y 

4255 if dx == dy == 0: 

4256 return 

4257 if button == 1: 

4258 dx, dy = format_deltas(key, dx, dy) 

4259 result = p.bbox.translated(-dx, -dy).transformed(p.trans_inverse) 

4260 elif button == 3: 

4261 try: 

4262 dx = -dx / self.bbox.width 

4263 dy = -dy / self.bbox.height 

4264 dx, dy = format_deltas(key, dx, dy) 

4265 if self.get_aspect() != 'auto': 

4266 dx = dy = 0.5 * (dx + dy) 

4267 alpha = np.power(10.0, (dx, dy)) 

4268 start = np.array([p.x, p.y]) 

4269 oldpoints = p.lim.transformed(p.trans) 

4270 newpoints = start + alpha * (oldpoints - start) 

4271 result = (mtransforms.Bbox(newpoints) 

4272 .transformed(p.trans_inverse)) 

4273 except OverflowError: 

4274 _api.warn_external('Overflow while panning') 

4275 return 

4276 else: 

4277 return 

4278 

4279 valid = np.isfinite(result.transformed(p.trans)) 

4280 points = result.get_points().astype(object) 

4281 # Just ignore invalid limits (typically, underflow in log-scale). 

4282 points[~valid] = None 

4283 return points 

4284 

4285 def drag_pan(self, button, key, x, y): 

4286 """ 

4287 Called when the mouse moves during a pan operation. 

4288 

4289 Parameters 

4290 ---------- 

4291 button : `.MouseButton` 

4292 The pressed mouse button. 

4293 key : str or None 

4294 The pressed key, if any. 

4295 x, y : float 

4296 The mouse coordinates in display coords. 

4297 

4298 Notes 

4299 ----- 

4300 This is intended to be overridden by new projection types. 

4301 """ 

4302 points = self._get_pan_points(button, key, x, y) 

4303 if points is not None: 

4304 self.set_xlim(points[:, 0]) 

4305 self.set_ylim(points[:, 1]) 

4306 

4307 def get_children(self): 

4308 # docstring inherited. 

4309 return [ 

4310 *self._children, 

4311 *self.spines.values(), 

4312 *self._axis_map.values(), 

4313 self.title, self._left_title, self._right_title, 

4314 *self.child_axes, 

4315 *([self.legend_] if self.legend_ is not None else []), 

4316 self.patch, 

4317 ] 

4318 

4319 def contains(self, mouseevent): 

4320 # docstring inherited. 

4321 inside, info = self._default_contains(mouseevent) 

4322 if inside is not None: 

4323 return inside, info 

4324 return self.patch.contains(mouseevent) 

4325 

4326 def contains_point(self, point): 

4327 """ 

4328 Return whether *point* (pair of pixel coordinates) is inside the Axes 

4329 patch. 

4330 """ 

4331 return self.patch.contains_point(point, radius=1.0) 

4332 

4333 def get_default_bbox_extra_artists(self): 

4334 """ 

4335 Return a default list of artists that are used for the bounding box 

4336 calculation. 

4337 

4338 Artists are excluded either by not being visible or 

4339 ``artist.set_in_layout(False)``. 

4340 """ 

4341 

4342 artists = self.get_children() 

4343 

4344 for axis in self._axis_map.values(): 

4345 # axis tight bboxes are calculated separately inside 

4346 # Axes.get_tightbbox() using for_layout_only=True 

4347 artists.remove(axis) 

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

4349 # don't do bbox on spines if frame not on. 

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

4351 artists.remove(spine) 

4352 

4353 artists.remove(self.title) 

4354 artists.remove(self._left_title) 

4355 artists.remove(self._right_title) 

4356 

4357 # always include types that do not internally implement clipping 

4358 # to Axes. may have clip_on set to True and clip_box equivalent 

4359 # to ax.bbox but then ignore these properties during draws. 

4360 noclip = (_AxesBase, maxis.Axis, 

4361 offsetbox.AnnotationBbox, offsetbox.OffsetBox) 

4362 return [a for a in artists if a.get_visible() and a.get_in_layout() 

4363 and (isinstance(a, noclip) or not a._fully_clipped_to_axes())] 

4364 

4365 def get_tightbbox(self, renderer=None, call_axes_locator=True, 

4366 bbox_extra_artists=None, *, for_layout_only=False): 

4367 """ 

4368 Return the tight bounding box of the Axes, including axis and their 

4369 decorators (xlabel, title, etc). 

4370 

4371 Artists that have ``artist.set_in_layout(False)`` are not included 

4372 in the bbox. 

4373 

4374 Parameters 

4375 ---------- 

4376 renderer : `.RendererBase` subclass 

4377 renderer that will be used to draw the figures (i.e. 

4378 ``fig.canvas.get_renderer()``) 

4379 

4380 bbox_extra_artists : list of `.Artist` or ``None`` 

4381 List of artists to include in the tight bounding box. If 

4382 ``None`` (default), then all artist children of the Axes are 

4383 included in the tight bounding box. 

4384 

4385 call_axes_locator : bool, default: True 

4386 If *call_axes_locator* is ``False``, it does not call the 

4387 ``_axes_locator`` attribute, which is necessary to get the correct 

4388 bounding box. ``call_axes_locator=False`` can be used if the 

4389 caller is only interested in the relative size of the tightbbox 

4390 compared to the Axes bbox. 

4391 

4392 for_layout_only : default: False 

4393 The bounding box will *not* include the x-extent of the title and 

4394 the xlabel, or the y-extent of the ylabel. 

4395 

4396 Returns 

4397 ------- 

4398 `.BboxBase` 

4399 Bounding box in figure pixel coordinates. 

4400 

4401 See Also 

4402 -------- 

4403 matplotlib.axes.Axes.get_window_extent 

4404 matplotlib.axis.Axis.get_tightbbox 

4405 matplotlib.spines.Spine.get_window_extent 

4406 """ 

4407 

4408 bb = [] 

4409 if renderer is None: 

4410 renderer = self.figure._get_renderer() 

4411 

4412 if not self.get_visible(): 

4413 return None 

4414 

4415 locator = self.get_axes_locator() 

4416 self.apply_aspect( 

4417 locator(self, renderer) if locator and call_axes_locator else None) 

4418 

4419 for axis in self._axis_map.values(): 

4420 if self.axison and axis.get_visible(): 

4421 ba = martist._get_tightbbox_for_layout_only(axis, renderer) 

4422 if ba: 

4423 bb.append(ba) 

4424 self._update_title_position(renderer) 

4425 axbbox = self.get_window_extent(renderer) 

4426 bb.append(axbbox) 

4427 

4428 for title in [self.title, self._left_title, self._right_title]: 

4429 if title.get_visible(): 

4430 bt = title.get_window_extent(renderer) 

4431 if for_layout_only and bt.width > 0: 

4432 # make the title bbox 1 pixel wide so its width 

4433 # is not accounted for in bbox calculations in 

4434 # tight/constrained_layout 

4435 bt.x0 = (bt.x0 + bt.x1) / 2 - 0.5 

4436 bt.x1 = bt.x0 + 1.0 

4437 bb.append(bt) 

4438 

4439 bbox_artists = bbox_extra_artists 

4440 if bbox_artists is None: 

4441 bbox_artists = self.get_default_bbox_extra_artists() 

4442 

4443 for a in bbox_artists: 

4444 bbox = a.get_tightbbox(renderer) 

4445 if (bbox is not None 

4446 and 0 < bbox.width < np.inf 

4447 and 0 < bbox.height < np.inf): 

4448 bb.append(bbox) 

4449 return mtransforms.Bbox.union( 

4450 [b for b in bb if b.width != 0 or b.height != 0]) 

4451 

4452 def _make_twin_axes(self, *args, **kwargs): 

4453 """Make a twinx Axes of self. This is used for twinx and twiny.""" 

4454 # Typically, SubplotBase._make_twin_axes is called instead of this. 

4455 if 'sharex' in kwargs and 'sharey' in kwargs: 

4456 raise ValueError("Twinned Axes may share only one axis") 

4457 ax2 = self.figure.add_axes( 

4458 self.get_position(True), *args, **kwargs, 

4459 axes_locator=_TransformedBoundsLocator( 

4460 [0, 0, 1, 1], self.transAxes)) 

4461 self.set_adjustable('datalim') 

4462 ax2.set_adjustable('datalim') 

4463 self._twinned_axes.join(self, ax2) 

4464 return ax2 

4465 

4466 def twinx(self): 

4467 """ 

4468 Create a twin Axes sharing the xaxis. 

4469 

4470 Create a new Axes with an invisible x-axis and an independent 

4471 y-axis positioned opposite to the original one (i.e. at right). The 

4472 x-axis autoscale setting will be inherited from the original 

4473 Axes. To ensure that the tick marks of both y-axes align, see 

4474 `~matplotlib.ticker.LinearLocator`. 

4475 

4476 Returns 

4477 ------- 

4478 Axes 

4479 The newly created Axes instance 

4480 

4481 Notes 

4482 ----- 

4483 For those who are 'picking' artists while using twinx, pick 

4484 events are only called for the artists in the top-most Axes. 

4485 """ 

4486 ax2 = self._make_twin_axes(sharex=self) 

4487 ax2.yaxis.tick_right() 

4488 ax2.yaxis.set_label_position('right') 

4489 ax2.yaxis.set_offset_position('right') 

4490 ax2.set_autoscalex_on(self.get_autoscalex_on()) 

4491 self.yaxis.tick_left() 

4492 ax2.xaxis.set_visible(False) 

4493 ax2.patch.set_visible(False) 

4494 return ax2 

4495 

4496 def twiny(self): 

4497 """ 

4498 Create a twin Axes sharing the yaxis. 

4499 

4500 Create a new Axes with an invisible y-axis and an independent 

4501 x-axis positioned opposite to the original one (i.e. at top). The 

4502 y-axis autoscale setting will be inherited from the original Axes. 

4503 To ensure that the tick marks of both x-axes align, see 

4504 `~matplotlib.ticker.LinearLocator`. 

4505 

4506 Returns 

4507 ------- 

4508 Axes 

4509 The newly created Axes instance 

4510 

4511 Notes 

4512 ----- 

4513 For those who are 'picking' artists while using twiny, pick 

4514 events are only called for the artists in the top-most Axes. 

4515 """ 

4516 ax2 = self._make_twin_axes(sharey=self) 

4517 ax2.xaxis.tick_top() 

4518 ax2.xaxis.set_label_position('top') 

4519 ax2.set_autoscaley_on(self.get_autoscaley_on()) 

4520 self.xaxis.tick_bottom() 

4521 ax2.yaxis.set_visible(False) 

4522 ax2.patch.set_visible(False) 

4523 return ax2 

4524 

4525 def get_shared_x_axes(self): 

4526 """Return an immutable view on the shared x-axes Grouper.""" 

4527 return cbook.GrouperView(self._shared_axes["x"]) 

4528 

4529 def get_shared_y_axes(self): 

4530 """Return an immutable view on the shared y-axes Grouper.""" 

4531 return cbook.GrouperView(self._shared_axes["y"])