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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1737 statements  

1from collections.abc import Iterable, Sequence 

2from contextlib import ExitStack 

3import functools 

4import inspect 

5import logging 

6from numbers import Real 

7from operator import attrgetter 

8import re 

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 

21from matplotlib.gridspec import SubplotSpec 

22import matplotlib.image as mimage 

23import matplotlib.lines as mlines 

24import matplotlib.patches as mpatches 

25from matplotlib.rcsetup import cycler, validate_axisbelow 

26import matplotlib.spines as mspines 

27import matplotlib.table as mtable 

28import matplotlib.text as mtext 

29import matplotlib.ticker as mticker 

30import matplotlib.transforms as mtransforms 

31 

32_log = logging.getLogger(__name__) 

33 

34 

35class _axis_method_wrapper: 

36 """ 

37 Helper to generate Axes methods wrapping Axis methods. 

38 

39 After :: 

40 

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

42 

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

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

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

46 

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

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

49 dedented docstring; additional replacements can be given in *doc_sub*. 

50 """ 

51 

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

53 self.attr_name = attr_name 

54 self.method_name = method_name 

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

56 # manipulations within the class body work as expected. 

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

58 self._missing_subs = [] 

59 if doc: 

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

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

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

63 self._missing_subs.append(k) 

64 doc = doc.replace(k, v) 

65 self.__doc__ = doc 

66 

67 def __set_name__(self, owner, name): 

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

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

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

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

72 

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

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

75 

76 wrapper.__module__ = owner.__module__ 

77 wrapper.__name__ = name 

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

79 wrapper.__doc__ = self.__doc__ 

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

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

82 # source would be confusing. 

83 wrapper.__signature__ = inspect.signature( 

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

85 

86 if self._missing_subs: 

87 raise ValueError( 

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

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

90 wrapper.__qualname__, self.method_name, 

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

92 

93 setattr(owner, name, wrapper) 

94 

95 

96class _TransformedBoundsLocator: 

97 """ 

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

99 

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

101 Axes location depending on the renderer. 

102 """ 

103 

104 def __init__(self, bounds, transform): 

105 """ 

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

107 specify the position of the inset Axes. 

108 """ 

109 self._bounds = bounds 

110 self._transform = transform 

111 

112 def __call__(self, ax, renderer): 

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

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

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

116 return mtransforms.TransformedBbox( 

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

118 self._transform - ax.figure.transSubfigure) 

119 

120 

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

122 """ 

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

124 *marker*, *color*) tuple. 

125 

126 Example format strings include: 

127 

128 * 'ko': black circles 

129 * '.b': blue dots 

130 * 'r--': red dashed lines 

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

132 

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

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

135 returning 'None' for the respective quantity. 

136 

137 See Also 

138 -------- 

139 matplotlib.Line2D.lineStyles, matplotlib.colors.cnames 

140 All possible styles and color format strings. 

141 """ 

142 

143 linestyle = None 

144 marker = None 

145 color = None 

146 

147 # First check whether fmt is just a colorspec, but specifically exclude the 

148 # grayscale string "1" (not "1.0"), which is interpreted as the tri_down 

149 # marker "1". The grayscale string "0" could be unambiguously understood 

150 # as a color (black) but also excluded for consistency. 

151 if fmt not in ["0", "1"]: 

152 try: 

153 color = mcolors.to_rgba(fmt) 

154 return linestyle, marker, color 

155 except ValueError: 

156 pass 

157 

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

159 if ambiguous_fmt_datakey else 

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

161 

162 i = 0 

163 while i < len(fmt): 

164 c = fmt[i] 

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

166 if linestyle is not None: 

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

168 linestyle = fmt[i:i+2] 

169 i += 2 

170 elif c in mlines.lineStyles: 

171 if linestyle is not None: 

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

173 linestyle = c 

174 i += 1 

175 elif c in mlines.lineMarkers: 

176 if marker is not None: 

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

178 marker = c 

179 i += 1 

180 elif c in mcolors.get_named_colors_mapping(): 

181 if color is not None: 

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

183 color = c 

184 i += 1 

185 elif c == "C": 

186 cn_color = re.match(r"C\d+", fmt[i:]) 

187 if not cn_color: 

188 raise ValueError(errfmt.format(fmt, "'C' must be followed by a number")) 

189 color = mcolors.to_rgba(cn_color[0]) 

190 i += len(cn_color[0]) 

191 else: 

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

193 

194 if linestyle is None and marker is None: 

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

196 if linestyle is None: 

197 linestyle = 'None' 

198 if marker is None: 

199 marker = 'None' 

200 

201 return linestyle, marker, color 

202 

203 

204class _process_plot_var_args: 

205 """ 

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

207 

208 plot(t, s) 

209 plot(t1, s1, t2, s2) 

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

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

212 

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

214 """ 

215 

216 def __init__(self, command='plot'): 

217 self.command = command 

218 self.set_prop_cycle(None) 

219 

220 def set_prop_cycle(self, cycler): 

221 if cycler is None: 

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

223 self._idx = 0 

224 self._cycler_items = [*cycler] 

225 

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

227 axes._process_unit_info(kwargs=kwargs) 

228 

229 for pos_only in "xy": 

230 if pos_only in kwargs: 

231 raise _api.kwarg_error(self.command, pos_only) 

232 

233 if not args: 

234 return 

235 

236 if data is None: # Process dict views 

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

238 else: # Process the 'data' kwarg. 

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

240 if len(args) == 1: 

241 label_namer_idx = 0 

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

243 # Figure out what the second argument is. 

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

245 # second argument is the label_namer. 

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

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

248 # use it as label_namer. 

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

250 # first argument as label_namer. 

251 try: 

252 _process_plot_format(args[1]) 

253 except ValueError: # case 1) 

254 label_namer_idx = 1 

255 else: 

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

257 _api.warn_external( 

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

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

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

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

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

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

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

265 RuntimeWarning) 

266 label_namer_idx = 1 

267 else: # case 2b) 

268 label_namer_idx = 0 

269 elif len(args) == 3: 

270 label_namer_idx = 1 

271 else: 

272 raise ValueError( 

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

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

275 "instead") 

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

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

278 replaced[label_namer_idx], args[label_namer_idx]) 

279 args = replaced 

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

281 

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

283 kwargs.get("label")): 

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

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

286 "labels") 

287 

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

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

290 

291 while args: 

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

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

294 this += args[0], 

295 args = args[1:] 

296 yield from self._plot_args( 

297 axes, this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey) 

298 

299 def get_next_color(self): 

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

301 entry = self._cycler_items[self._idx] 

302 if "color" in entry: 

303 self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler. 

304 return entry["color"] 

305 else: 

306 return "k" 

307 

308 def _getdefaults(self, kw, ignore=frozenset()): 

309 """ 

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

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

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

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

314 """ 

315 defaults = self._cycler_items[self._idx] 

316 if any(kw.get(k, None) is None for k in {*defaults} - ignore): 

317 self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler. 

318 # Return a new dict to avoid exposing _cycler_items entries to mutation. 

319 return {k: v for k, v in defaults.items() if k not in ignore} 

320 else: 

321 return {} 

322 

323 def _setdefaults(self, defaults, kw): 

324 """ 

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

326 or set to None in *kw*. 

327 """ 

328 for k in defaults: 

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

330 kw[k] = defaults[k] 

331 

332 def _makeline(self, axes, x, y, kw, kwargs): 

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

334 self._setdefaults(self._getdefaults(kw), kw) 

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

336 return seg, kw 

337 

338 def _makefill(self, axes, x, y, kw, kwargs): 

339 # Polygon doesn't directly support unitized inputs. 

340 x = axes.convert_xunits(x) 

341 y = axes.convert_yunits(y) 

342 

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

344 kwargs = kwargs.copy() 

345 

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

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

348 # likely to appear in the default cycler construction. 

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

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

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

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

353 # defaults needed after ignoring the given properties. 

354 ignores = ({'marker', 'markersize', 'markeredgecolor', 

355 'markerfacecolor', 'markeredgewidth'} 

356 # Also ignore anything provided by *kwargs*. 

357 | {k for k, v in kwargs.items() if v is not None}) 

358 

359 # Only using the first dictionary to use as basis 

360 # for getting defaults for back-compat reasons. 

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

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

363 default_dict = self._getdefaults(kw, ignores) 

364 self._setdefaults(default_dict, kw) 

365 

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

367 # mean both facecolor and edgecolor for some reason. 

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

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

370 # This design should probably be revisited as it increases 

371 # complexity. 

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

373 

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

375 default_dict.pop('color', None) 

376 

377 # To get other properties set from the cycler 

378 # modify the kwargs dictionary. 

379 self._setdefaults(default_dict, kwargs) 

380 

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

382 facecolor=facecolor, 

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

384 closed=kw['closed']) 

385 seg.set(**kwargs) 

386 return seg, kwargs 

387 

388 def _plot_args(self, axes, tup, kwargs, *, 

389 return_kwargs=False, ambiguous_fmt_datakey=False): 

390 """ 

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

392 

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

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

395 once for (x2, y2). 

396 

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

398 

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

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

401 

402 Parameters 

403 ---------- 

404 tup : tuple 

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

406 

407 - (y,) 

408 - (x, y) 

409 - (y, fmt) 

410 - (x, y, fmt) 

411 

412 kwargs : dict 

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

414 

415 return_kwargs : bool 

416 Whether to also return the effective keyword arguments after label 

417 unpacking as well. 

418 

419 ambiguous_fmt_datakey : bool 

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

421 misspelled data key. 

422 

423 Returns 

424 ------- 

425 result 

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

427 dataset(s). 

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

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

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

431 `.Polygon` otherwise. 

432 """ 

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

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

435 *xy, fmt = tup 

436 linestyle, marker, color = _process_plot_format( 

437 fmt, ambiguous_fmt_datakey=ambiguous_fmt_datakey) 

438 elif len(tup) == 3: 

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

440 else: 

441 xy = tup 

442 linestyle, marker, color = None, None, None 

443 

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

445 # element array of None which causes problems downstream. 

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

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

448 

449 kw = {} 

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

451 (linestyle, marker, color)): 

452 if val is not None: 

453 # check for conflicts between fmt and kwargs 

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

455 and prop_name in kwargs 

456 and val != 'None'): 

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

458 # because 'o' implicitly unsets the linestyle 

459 # (linestyle='None'). 

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

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

462 # override an implicit unset. 

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

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

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

466 # which only returns 'None'. 

467 _api.warn_external( 

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

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

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

471 f"argument will take precedence.") 

472 kw[prop_name] = val 

473 

474 if len(xy) == 2: 

475 x = _check_1d(xy[0]) 

476 y = _check_1d(xy[1]) 

477 else: 

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

479 

480 if axes.xaxis is not None: 

481 axes.xaxis.update_units(x) 

482 if axes.yaxis is not None: 

483 axes.yaxis.update_units(y) 

484 

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

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

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

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

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

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

491 if x.ndim == 1: 

492 x = x[:, np.newaxis] 

493 if y.ndim == 1: 

494 y = y[:, np.newaxis] 

495 

496 if self.command == 'plot': 

497 make_artist = self._makeline 

498 else: 

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

500 make_artist = self._makefill 

501 

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

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

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

505 if ncx == 0 or ncy == 0: 

506 return [] 

507 

508 label = kwargs.get('label') 

509 n_datasets = max(ncx, ncy) 

510 

511 if cbook.is_scalar_or_string(label): 

512 labels = [label] * n_datasets 

513 elif len(label) == n_datasets: 

514 labels = label 

515 elif n_datasets == 1: 

516 msg = (f'Passing label as a length {len(label)} sequence when ' 

517 'plotting a single dataset is deprecated in Matplotlib 3.9 ' 

518 'and will error in 3.11. To keep the current behavior, ' 

519 'cast the sequence to string before passing.') 

520 _api.warn_deprecated('3.9', message=msg) 

521 labels = [label] 

522 else: 

523 raise ValueError( 

524 f"label must be scalar or have the same length as the input " 

525 f"data, but found {len(label)} for {n_datasets} datasets.") 

526 

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

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

529 for j, label in enumerate(labels)) 

530 

531 if return_kwargs: 

532 return list(result) 

533 else: 

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

535 

536 

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

538class _AxesBase(martist.Artist): 

539 name = "rectilinear" 

540 

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

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

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

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

545 # have 'theta' in _axis_names. 

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

547 # for Axes3D. 

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

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

550 _twinned_axes = cbook.Grouper() 

551 

552 _subclass_uses_cla = False 

553 

554 @property 

555 def _axis_map(self): 

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

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

558 for name in self._axis_names} 

559 

560 def __str__(self): 

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

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

563 

564 def __init__(self, fig, 

565 *args, 

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

567 frameon=True, 

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

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

570 label='', 

571 xscale=None, 

572 yscale=None, 

573 box_aspect=None, 

574 forward_navigation_events="auto", 

575 **kwargs 

576 ): 

577 """ 

578 Build an Axes in a figure. 

579 

580 Parameters 

581 ---------- 

582 fig : `~matplotlib.figure.Figure` 

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

584 

585 *args 

586 ``*args`` can be a single ``(left, bottom, width, height)`` 

587 rectangle or a single `.Bbox`. This specifies the rectangle (in 

588 figure coordinates) where the Axes is positioned. 

589 

590 ``*args`` can also consist of three numbers or a single three-digit 

591 number; in the latter case, the digits are considered as 

592 independent numbers. The numbers are interpreted as ``(nrows, 

593 ncols, index)``: ``(nrows, ncols)`` specifies the size of an array 

594 of subplots, and ``index`` is the 1-based index of the subplot 

595 being created. Finally, ``*args`` can also directly be a 

596 `.SubplotSpec` instance. 

597 

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

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

600 the input `~.axes.Axes`. Note that it is not possible to unshare 

601 axes. 

602 

603 frameon : bool, default: True 

604 Whether the Axes frame is visible. 

605 

606 box_aspect : float, optional 

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

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

609 

610 forward_navigation_events : bool or "auto", default: "auto" 

611 Control whether pan/zoom events are passed through to Axes below 

612 this one. "auto" is *True* for axes with an invisible patch and 

613 *False* otherwise. 

614 

615 **kwargs 

616 Other optional keyword arguments: 

617 

618 %(Axes:kwdoc)s 

619 

620 Returns 

621 ------- 

622 `~.axes.Axes` 

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

624 """ 

625 

626 super().__init__() 

627 if "rect" in kwargs: 

628 if args: 

629 raise TypeError( 

630 "'rect' cannot be used together with positional arguments") 

631 rect = kwargs.pop("rect") 

632 _api.check_isinstance((mtransforms.Bbox, Iterable), rect=rect) 

633 args = (rect,) 

634 subplotspec = None 

635 if len(args) == 1 and isinstance(args[0], mtransforms.Bbox): 

636 self._position = args[0] 

637 elif len(args) == 1 and np.iterable(args[0]): 

638 self._position = mtransforms.Bbox.from_bounds(*args[0]) 

639 else: 

640 self._position = self._originalPosition = mtransforms.Bbox.unit() 

641 subplotspec = SubplotSpec._from_subplot_args(fig, args) 

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

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

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

645 self.axes = self 

646 self._aspect = 'auto' 

647 self._adjustable = 'box' 

648 self._anchor = 'C' 

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

650 self._forward_navigation_events = forward_navigation_events 

651 self._sharex = sharex 

652 self._sharey = sharey 

653 self.set_label(label) 

654 self.set_figure(fig) 

655 # The subplotspec needs to be set after the figure (so that 

656 # figure-level subplotpars are taken into account), but the figure 

657 # needs to be set after self._position is initialized. 

658 if subplotspec: 

659 self.set_subplotspec(subplotspec) 

660 else: 

661 self._subplotspec = None 

662 self.set_box_aspect(box_aspect) 

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

664 

665 self._children = [] 

666 

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

668 # (see colorbar.py): 

669 self._colorbars = [] 

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

671 

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

673 self._init_axis() 

674 if facecolor is None: 

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

676 self._facecolor = facecolor 

677 self._frameon = frameon 

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

679 

680 self._rasterization_zorder = None 

681 self.clear() 

682 

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

684 self.fmt_xdata = None 

685 self.fmt_ydata = None 

686 

687 self.set_navigate(True) 

688 self.set_navigate_mode(None) 

689 

690 if xscale: 

691 self.set_xscale(xscale) 

692 if yscale: 

693 self.set_yscale(yscale) 

694 

695 self._internal_update(kwargs) 

696 

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

698 axis.callbacks._connect_picklable( 

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

700 

701 rcParams = mpl.rcParams 

702 self.tick_params( 

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

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

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

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

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

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

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

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

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

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

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

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

715 which='minor') 

716 

717 self.tick_params( 

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

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

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

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

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

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

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

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

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

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

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

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

730 which='major') 

731 

732 def __init_subclass__(cls, **kwargs): 

733 parent_uses_cla = super(cls, cls)._subclass_uses_cla 

734 if 'cla' in cls.__dict__: 

735 _api.warn_deprecated( 

736 '3.6', 

737 pending=True, 

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

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

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

741 'Please report ' 

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

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

744 super().__init_subclass__(**kwargs) 

745 

746 def __getstate__(self): 

747 state = super().__getstate__() 

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

749 state["_shared_axes"] = { 

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

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

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

753 if self in self._twinned_axes else None) 

754 return state 

755 

756 def __setstate__(self, state): 

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

758 shared_axes = state.pop("_shared_axes") 

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

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

761 twinned_siblings = state.pop("_twinned_axes") 

762 if twinned_siblings: 

763 self._twinned_axes.join(*twinned_siblings) 

764 self.__dict__ = state 

765 self._stale = True 

766 

767 def __repr__(self): 

768 fields = [] 

769 if self.get_label(): 

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

771 if hasattr(self, "get_title"): 

772 titles = {} 

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

774 title = self.get_title(loc=k) 

775 if title: 

776 titles[k] = title 

777 if titles: 

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

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

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

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

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

783 

784 def get_subplotspec(self): 

785 """Return the `.SubplotSpec` associated with the subplot, or None.""" 

786 return self._subplotspec 

787 

788 def set_subplotspec(self, subplotspec): 

789 """Set the `.SubplotSpec`. associated with the subplot.""" 

790 self._subplotspec = subplotspec 

791 self._set_position(subplotspec.get_position(self.figure)) 

792 

793 def get_gridspec(self): 

794 """Return the `.GridSpec` associated with the subplot, or None.""" 

795 return self._subplotspec.get_gridspec() if self._subplotspec else None 

796 

797 def get_window_extent(self, renderer=None): 

798 """ 

799 Return the Axes bounding box in display space. 

800 

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

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

803 `~matplotlib.axes.Axes.get_tightbbox`. 

804 

805 See Also 

806 -------- 

807 matplotlib.axes.Axes.get_tightbbox 

808 matplotlib.axis.Axis.get_tightbbox 

809 matplotlib.spines.Spine.get_window_extent 

810 """ 

811 return self.bbox 

812 

813 def _init_axis(self): 

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

815 self.xaxis = maxis.XAxis(self, clear=False) 

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

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

818 self.yaxis = maxis.YAxis(self, clear=False) 

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

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

821 

822 def set_figure(self, fig): 

823 # docstring inherited 

824 super().set_figure(fig) 

825 

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

827 fig.transSubfigure) 

828 # these will be updated later as data is added 

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

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

831 self.transScale = mtransforms.TransformWrapper( 

832 mtransforms.IdentityTransform()) 

833 

834 self._set_lim_and_transforms() 

835 

836 def _unstale_viewLim(self): 

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

838 # instead of on every axis. 

839 need_scale = { 

840 name: any(ax._stale_viewlims[name] 

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

842 for name in self._axis_names} 

843 if any(need_scale.values()): 

844 for name in need_scale: 

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

846 ax._stale_viewlims[name] = False 

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

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

849 

850 @property 

851 def viewLim(self): 

852 self._unstale_viewLim() 

853 return self._viewLim 

854 

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

856 """ 

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

858 

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

860 calls to control individual axises incur negligible performance cost. 

861 

862 Parameters 

863 ---------- 

864 axis : str, default: "all" 

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

866 tight : bool or None, default: None 

867 """ 

868 axis_names = _api.check_getitem( 

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

870 axis=axis) 

871 for name in axis_names: 

872 self._stale_viewlims[name] = True 

873 if tight is not None: 

874 self._tight = tight 

875 

876 def _set_lim_and_transforms(self): 

877 """ 

878 Set the *_xaxis_transform*, *_yaxis_transform*, *transScale*, 

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

880 

881 .. note:: 

882 

883 This method is primarily used by rectilinear projections of the 

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

885 new kinds of projection Axes that need different transformations 

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

887 example.) 

888 """ 

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

890 

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

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

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

894 self.transScale = mtransforms.TransformWrapper( 

895 mtransforms.IdentityTransform()) 

896 

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

898 # range of the axes 

899 self.transLimits = mtransforms.BboxTransformFrom( 

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

901 

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

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

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

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

906 

907 self._xaxis_transform = mtransforms.blended_transform_factory( 

908 self.transData, self.transAxes) 

909 self._yaxis_transform = mtransforms.blended_transform_factory( 

910 self.transAxes, self.transData) 

911 

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

913 """ 

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

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

916 y-direction is in axis coordinates. 

917 

918 .. note:: 

919 

920 This transformation is primarily used by the 

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

922 overridden by new kinds of projections that may need to 

923 place axis elements in different locations. 

924 

925 Parameters 

926 ---------- 

927 which : {'grid', 'tick1', 'tick2'} 

928 """ 

929 if which == 'grid': 

930 return self._xaxis_transform 

931 elif which == 'tick1': 

932 # for cartesian projection, this is bottom spine 

933 return self.spines.bottom.get_spine_transform() 

934 elif which == 'tick2': 

935 # for cartesian projection, this is top spine 

936 return self.spines.top.get_spine_transform() 

937 else: 

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

939 

940 def get_xaxis_text1_transform(self, pad_points): 

941 """ 

942 Returns 

943 ------- 

944 transform : Transform 

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

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

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

948 axis coordinates 

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

950 The text vertical alignment. 

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

952 The text horizontal alignment. 

953 

954 Notes 

955 ----- 

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

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

958 may need to place axis elements in different locations. 

959 """ 

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

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

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

963 self.figure.dpi_scale_trans), 

964 "top", labels_align) 

965 

966 def get_xaxis_text2_transform(self, pad_points): 

967 """ 

968 Returns 

969 ------- 

970 transform : Transform 

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

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

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

974 is in axis coordinates 

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

976 The text vertical alignment. 

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

978 The text horizontal alignment. 

979 

980 Notes 

981 ----- 

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

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

984 may need to place axis elements in different locations. 

985 """ 

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

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

988 mtransforms.ScaledTranslation(0, pad_points / 72, 

989 self.figure.dpi_scale_trans), 

990 "bottom", labels_align) 

991 

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

993 """ 

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

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

996 y-direction is in data coordinates. 

997 

998 .. note:: 

999 

1000 This transformation is primarily used by the 

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

1002 overridden by new kinds of projections that may need to 

1003 place axis elements in different locations. 

1004 

1005 Parameters 

1006 ---------- 

1007 which : {'grid', 'tick1', 'tick2'} 

1008 """ 

1009 if which == 'grid': 

1010 return self._yaxis_transform 

1011 elif which == 'tick1': 

1012 # for cartesian projection, this is bottom spine 

1013 return self.spines.left.get_spine_transform() 

1014 elif which == 'tick2': 

1015 # for cartesian projection, this is top spine 

1016 return self.spines.right.get_spine_transform() 

1017 else: 

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

1019 

1020 def get_yaxis_text1_transform(self, pad_points): 

1021 """ 

1022 Returns 

1023 ------- 

1024 transform : Transform 

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

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

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

1028 data coordinates 

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

1030 The text vertical alignment. 

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

1032 The text horizontal alignment. 

1033 

1034 Notes 

1035 ----- 

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

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

1038 may need to place axis elements in different locations. 

1039 """ 

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

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

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

1043 self.figure.dpi_scale_trans), 

1044 labels_align, "right") 

1045 

1046 def get_yaxis_text2_transform(self, pad_points): 

1047 """ 

1048 Returns 

1049 ------- 

1050 transform : Transform 

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

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

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

1054 is in data coordinates 

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

1056 The text vertical alignment. 

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

1058 The text horizontal alignment. 

1059 

1060 Notes 

1061 ----- 

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

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

1064 may need to place axis elements in different locations. 

1065 """ 

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

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

1068 mtransforms.ScaledTranslation(pad_points / 72, 0, 

1069 self.figure.dpi_scale_trans), 

1070 labels_align, "left") 

1071 

1072 def _update_transScale(self): 

1073 self.transScale.set( 

1074 mtransforms.blended_transform_factory( 

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

1076 

1077 def get_position(self, original=False): 

1078 """ 

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

1080 

1081 Parameters 

1082 ---------- 

1083 original : bool 

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

1085 active position. For an explanation of the positions see 

1086 `.set_position`. 

1087 

1088 Returns 

1089 ------- 

1090 `.Bbox` 

1091 

1092 """ 

1093 if original: 

1094 return self._originalPosition.frozen() 

1095 else: 

1096 locator = self.get_axes_locator() 

1097 if not locator: 

1098 self.apply_aspect() 

1099 return self._position.frozen() 

1100 

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

1102 """ 

1103 Set the Axes position. 

1104 

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

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

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

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

1109 `.Axes.set_aspect` for details. 

1110 

1111 Parameters 

1112 ---------- 

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

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

1115 

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

1117 Determines which position variables to change. 

1118 

1119 See Also 

1120 -------- 

1121 matplotlib.transforms.Bbox.from_bounds 

1122 matplotlib.transforms.Bbox.from_extents 

1123 """ 

1124 self._set_position(pos, which=which) 

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

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

1127 self.set_in_layout(False) 

1128 

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

1130 """ 

1131 Private version of set_position. 

1132 

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

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

1135 """ 

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

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

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

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

1140 ax._position.set(pos) 

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

1142 ax._originalPosition.set(pos) 

1143 self.stale = True 

1144 

1145 def reset_position(self): 

1146 """ 

1147 Reset the active position to the original position. 

1148 

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

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

1151 constraints. 

1152 """ 

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

1154 pos = ax.get_position(original=True) 

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

1156 

1157 def set_axes_locator(self, locator): 

1158 """ 

1159 Set the Axes locator. 

1160 

1161 Parameters 

1162 ---------- 

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

1164 """ 

1165 self._axes_locator = locator 

1166 self.stale = True 

1167 

1168 def get_axes_locator(self): 

1169 """ 

1170 Return the axes_locator. 

1171 """ 

1172 return self._axes_locator 

1173 

1174 def _set_artist_props(self, a): 

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

1176 a.set_figure(self.figure) 

1177 if not a.is_transform_set(): 

1178 a.set_transform(self.transData) 

1179 

1180 a.axes = self 

1181 if a.get_mouseover(): 

1182 self._mouseover_set.add(a) 

1183 

1184 def _gen_axes_patch(self): 

1185 """ 

1186 Returns 

1187 ------- 

1188 Patch 

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

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

1191 

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

1193 it may not be. 

1194 

1195 Notes 

1196 ----- 

1197 Intended to be overridden by new projection types. 

1198 """ 

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

1200 

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

1202 """ 

1203 Returns 

1204 ------- 

1205 dict 

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

1207 used to draw Axes spines. 

1208 

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

1210 projections they may not be. 

1211 

1212 Notes 

1213 ----- 

1214 Intended to be overridden by new projection types. 

1215 """ 

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

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

1218 

1219 def sharex(self, other): 

1220 """ 

1221 Share the x-axis with *other*. 

1222 

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

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

1225 another Axes. Note that it is not possible to unshare axes. 

1226 """ 

1227 _api.check_isinstance(_AxesBase, other=other) 

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

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

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

1231 self._sharex = other 

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

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

1234 x0, x1 = other.get_xlim() 

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

1236 self.xaxis._scale = other.xaxis._scale 

1237 

1238 def sharey(self, other): 

1239 """ 

1240 Share the y-axis with *other*. 

1241 

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

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

1244 another Axes. Note that it is not possible to unshare axes. 

1245 """ 

1246 _api.check_isinstance(_AxesBase, other=other) 

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

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

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

1250 self._sharey = other 

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

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

1253 y0, y1 = other.get_ylim() 

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

1255 self.yaxis._scale = other.yaxis._scale 

1256 

1257 def __clear(self): 

1258 """Clear the Axes.""" 

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

1260 # an adapter delegating to the correct implementation. 

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

1262 # deprecation on cla() subclassing expires. 

1263 

1264 # stash the current visibility state 

1265 if hasattr(self, 'patch'): 

1266 patch_visible = self.patch.get_visible() 

1267 else: 

1268 patch_visible = True 

1269 

1270 xaxis_visible = self.xaxis.get_visible() 

1271 yaxis_visible = self.yaxis.get_visible() 

1272 

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

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

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

1276 spine._clear() # Use _clear to not clear Axis again 

1277 

1278 self.ignore_existing_data_limits = True 

1279 self.callbacks = cbook.CallbackRegistry( 

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

1281 

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

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

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

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

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

1287 

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

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

1290 self._tight = None 

1291 self._use_sticky_edges = True 

1292 

1293 self._get_lines = _process_plot_var_args() 

1294 self._get_patches_for_fill = _process_plot_var_args('fill') 

1295 

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

1297 old_children, self._children = self._children, [] 

1298 for chld in old_children: 

1299 chld.axes = chld.figure = None 

1300 self._mouseover_set = _OrderedSet() 

1301 self.child_axes = [] 

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

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

1304 self.legend_ = None 

1305 self.containers = [] 

1306 

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

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

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

1310 props = font_manager.FontProperties( 

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

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

1313 

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

1315 if y is None: 

1316 y = 1.0 

1317 self._autotitlepos = True 

1318 else: 

1319 self._autotitlepos = False 

1320 

1321 self.title = mtext.Text( 

1322 x=0.5, y=y, text='', 

1323 fontproperties=props, 

1324 verticalalignment='baseline', 

1325 horizontalalignment='center', 

1326 ) 

1327 self._left_title = mtext.Text( 

1328 x=0.0, y=y, text='', 

1329 fontproperties=props.copy(), 

1330 verticalalignment='baseline', 

1331 horizontalalignment='left', ) 

1332 self._right_title = mtext.Text( 

1333 x=1.0, y=y, text='', 

1334 fontproperties=props.copy(), 

1335 verticalalignment='baseline', 

1336 horizontalalignment='right', 

1337 ) 

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

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

1340 # pad argument used... 

1341 self._set_title_offset_trans(title_offset_points) 

1342 

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

1344 self._set_artist_props(_title) 

1345 

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

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

1348 # setting the edgecolor to None. 

1349 self.patch = self._gen_axes_patch() 

1350 self.patch.set_figure(self.figure) 

1351 self.patch.set_facecolor(self._facecolor) 

1352 self.patch.set_edgecolor('none') 

1353 self.patch.set_linewidth(0) 

1354 self.patch.set_transform(self.transAxes) 

1355 

1356 self.set_axis_on() 

1357 

1358 self.xaxis.set_clip_path(self.patch) 

1359 self.yaxis.set_clip_path(self.patch) 

1360 

1361 if self._sharex is not None: 

1362 self.xaxis.set_visible(xaxis_visible) 

1363 self.patch.set_visible(patch_visible) 

1364 if self._sharey is not None: 

1365 self.yaxis.set_visible(yaxis_visible) 

1366 self.patch.set_visible(patch_visible) 

1367 

1368 # This comes last, as the call to _set_lim may trigger an autoscale (in 

1369 # case of shared axes), requiring children to be already set up. 

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

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

1372 if share is not None: 

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

1374 else: 

1375 # Although the scale was set to linear as part of clear, 

1376 # polar requires that _set_scale is called again 

1377 if self.name == "polar": 

1378 axis._set_scale("linear") 

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

1380 self._update_transScale() 

1381 

1382 self.stale = True 

1383 

1384 def clear(self): 

1385 """Clear the Axes.""" 

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

1387 # subclass implementation. 

1388 if self._subclass_uses_cla: 

1389 self.cla() 

1390 else: 

1391 self.__clear() 

1392 

1393 def cla(self): 

1394 """Clear the Axes.""" 

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

1396 # subclass implementation. 

1397 if self._subclass_uses_cla: 

1398 self.__clear() 

1399 else: 

1400 self.clear() 

1401 

1402 class ArtistList(Sequence): 

1403 """ 

1404 A sublist of Axes children based on their type. 

1405 

1406 The type-specific children sublists were made immutable in Matplotlib 

1407 3.7. In the future these artist lists may be replaced by tuples. Use 

1408 as if this is a tuple already. 

1409 """ 

1410 def __init__(self, axes, prop_name, 

1411 valid_types=None, invalid_types=None): 

1412 """ 

1413 Parameters 

1414 ---------- 

1415 axes : `~matplotlib.axes.Axes` 

1416 The Axes from which this sublist will pull the children 

1417 Artists. 

1418 prop_name : str 

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

1420 used to generate deprecation warnings. 

1421 valid_types : list of type, optional 

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

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

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

1425 any type of Artist is valid (unless limited by 

1426 *invalid_types*.) 

1427 invalid_types : tuple, optional 

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

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

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

1431 types will be excluded. 

1432 """ 

1433 self._axes = axes 

1434 self._prop_name = prop_name 

1435 self._type_check = lambda artist: ( 

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

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

1438 ) 

1439 

1440 def __repr__(self): 

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

1442 

1443 def __len__(self): 

1444 return sum(self._type_check(artist) 

1445 for artist in self._axes._children) 

1446 

1447 def __iter__(self): 

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

1449 if self._type_check(artist): 

1450 yield artist 

1451 

1452 def __getitem__(self, key): 

1453 return [artist 

1454 for artist in self._axes._children 

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

1456 

1457 def __add__(self, other): 

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

1459 return [*self, *other] 

1460 if isinstance(other, (tuple, _AxesBase.ArtistList)): 

1461 return (*self, *other) 

1462 return NotImplemented 

1463 

1464 def __radd__(self, other): 

1465 if isinstance(other, list): 

1466 return other + list(self) 

1467 if isinstance(other, tuple): 

1468 return other + tuple(self) 

1469 return NotImplemented 

1470 

1471 @property 

1472 def artists(self): 

1473 return self.ArtistList(self, 'artists', invalid_types=( 

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

1475 mtable.Table, mtext.Text)) 

1476 

1477 @property 

1478 def collections(self): 

1479 return self.ArtistList(self, 'collections', 

1480 valid_types=mcoll.Collection) 

1481 

1482 @property 

1483 def images(self): 

1484 return self.ArtistList(self, 'images', valid_types=mimage.AxesImage) 

1485 

1486 @property 

1487 def lines(self): 

1488 return self.ArtistList(self, 'lines', valid_types=mlines.Line2D) 

1489 

1490 @property 

1491 def patches(self): 

1492 return self.ArtistList(self, 'patches', valid_types=mpatches.Patch) 

1493 

1494 @property 

1495 def tables(self): 

1496 return self.ArtistList(self, 'tables', valid_types=mtable.Table) 

1497 

1498 @property 

1499 def texts(self): 

1500 return self.ArtistList(self, 'texts', valid_types=mtext.Text) 

1501 

1502 def get_facecolor(self): 

1503 """Get the facecolor of the Axes.""" 

1504 return self.patch.get_facecolor() 

1505 

1506 def set_facecolor(self, color): 

1507 """ 

1508 Set the facecolor of the Axes. 

1509 

1510 Parameters 

1511 ---------- 

1512 color : :mpltype:`color` 

1513 """ 

1514 self._facecolor = color 

1515 self.stale = True 

1516 return self.patch.set_facecolor(color) 

1517 

1518 def _set_title_offset_trans(self, title_offset_points): 

1519 """ 

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

1521 or from set_title kwarg ``pad``. 

1522 """ 

1523 self.titleOffsetTrans = mtransforms.ScaledTranslation( 

1524 0.0, title_offset_points / 72, 

1525 self.figure.dpi_scale_trans) 

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

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

1528 _title.set_clip_box(None) 

1529 

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

1531 """ 

1532 Set the property cycle of the Axes. 

1533 

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

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

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

1537 

1538 Call signatures:: 

1539 

1540 set_prop_cycle(cycler) 

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

1542 set_prop_cycle(label, values) 

1543 

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

1545 

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

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

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

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

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

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

1552 

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

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

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

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

1557 

1558 Parameters 

1559 ---------- 

1560 cycler : `~cycler.Cycler` 

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

1562 current style. 

1563 

1564 .. ACCEPTS: `~cycler.Cycler` 

1565 

1566 label : str 

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

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

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

1570 

1571 values : iterable 

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

1573 are validated and will raise a ValueError if invalid. 

1574 

1575 See Also 

1576 -------- 

1577 matplotlib.rcsetup.cycler 

1578 Convenience function for creating validated cyclers for properties. 

1579 cycler.cycler 

1580 The original function for creating unvalidated cyclers. 

1581 

1582 Examples 

1583 -------- 

1584 Setting the property cycle for a single property: 

1585 

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

1587 

1588 Setting the property cycle for simultaneously cycling over multiple 

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

1590 

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

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

1593 

1594 """ 

1595 if args and kwargs: 

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

1597 "arguments to this method.") 

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

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

1600 prop_cycle = None 

1601 else: 

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

1603 self._get_lines.set_prop_cycle(prop_cycle) 

1604 self._get_patches_for_fill.set_prop_cycle(prop_cycle) 

1605 

1606 def get_aspect(self): 

1607 """ 

1608 Return the aspect ratio of the Axes scaling. 

1609 

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

1611 """ 

1612 return self._aspect 

1613 

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

1615 """ 

1616 Set the aspect ratio of the Axes scaling, i.e. y/x-scale. 

1617 

1618 Parameters 

1619 ---------- 

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

1621 Possible values: 

1622 

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

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

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

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

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

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

1629 

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

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

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

1633 details. 

1634 

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

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

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

1638 to specify the anchor are abbreviations of cardinal directions: 

1639 

1640 ===== ===================== 

1641 value description 

1642 ===== ===================== 

1643 'C' centered 

1644 'SW' lower left corner 

1645 'S' middle of bottom edge 

1646 'SE' lower right corner 

1647 etc. 

1648 ===== ===================== 

1649 

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

1651 

1652 share : bool, default: False 

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

1654 

1655 See Also 

1656 -------- 

1657 matplotlib.axes.Axes.set_adjustable 

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

1659 matplotlib.axes.Axes.set_anchor 

1660 Set the position in case of extra space. 

1661 """ 

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

1663 aspect = 1 

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

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

1666 if aspect <= 0 or not np.isfinite(aspect): 

1667 raise ValueError("aspect must be finite and positive ") 

1668 

1669 if share: 

1670 axes = {sibling for name in self._axis_names 

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

1672 else: 

1673 axes = [self] 

1674 

1675 for ax in axes: 

1676 ax._aspect = aspect 

1677 

1678 if adjustable is None: 

1679 adjustable = self._adjustable 

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

1681 

1682 if anchor is not None: 

1683 self.set_anchor(anchor, share=share) 

1684 self.stale = True 

1685 

1686 def get_adjustable(self): 

1687 """ 

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

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

1690 

1691 See Also 

1692 -------- 

1693 matplotlib.axes.Axes.set_adjustable 

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

1695 matplotlib.axes.Axes.set_aspect 

1696 For a description of aspect handling. 

1697 """ 

1698 return self._adjustable 

1699 

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

1701 """ 

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

1703 

1704 Parameters 

1705 ---------- 

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

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

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

1709 

1710 share : bool, default: False 

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

1712 

1713 See Also 

1714 -------- 

1715 matplotlib.axes.Axes.set_aspect 

1716 For a description of aspect handling. 

1717 

1718 Notes 

1719 ----- 

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

1721 impose restrictions on how aspect ratios can be imposed. 

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

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

1724 may be used. These limitations are partly a requirement 

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

1726 particular implementation we are currently using, in 

1727 which the adjustments for aspect ratios are done sequentially 

1728 and independently on each Axes as it is drawn. 

1729 """ 

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

1731 if share: 

1732 axs = {sibling for name in self._axis_names 

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

1734 else: 

1735 axs = [self] 

1736 if (adjustable == "datalim" 

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

1738 != _AxesBase.get_data_ratio 

1739 for ax in axs)): 

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

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

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

1743 "Axes which override 'get_data_ratio'") 

1744 for ax in axs: 

1745 ax._adjustable = adjustable 

1746 self.stale = True 

1747 

1748 def get_box_aspect(self): 

1749 """ 

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

1751 

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

1753 figure space) unless explicitly specified. 

1754 

1755 See Also 

1756 -------- 

1757 matplotlib.axes.Axes.set_box_aspect 

1758 for a description of box aspect. 

1759 matplotlib.axes.Axes.set_aspect 

1760 for a description of aspect handling. 

1761 """ 

1762 return self._box_aspect 

1763 

1764 def set_box_aspect(self, aspect=None): 

1765 """ 

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

1767 

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

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

1770 

1771 Parameters 

1772 ---------- 

1773 aspect : float or None 

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

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

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

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

1778 

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

1780 of the Axes are chosen independently. 

1781 

1782 See Also 

1783 -------- 

1784 matplotlib.axes.Axes.set_aspect 

1785 for a description of aspect handling. 

1786 """ 

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

1788 *self._twinned_axes.get_siblings(self)} 

1789 

1790 if aspect is not None: 

1791 aspect = float(aspect) 

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

1793 # adjustable must be "datalim" 

1794 for ax in axs: 

1795 ax.set_adjustable("datalim") 

1796 

1797 for ax in axs: 

1798 ax._box_aspect = aspect 

1799 ax.stale = True 

1800 

1801 def get_anchor(self): 

1802 """ 

1803 Get the anchor location. 

1804 

1805 See Also 

1806 -------- 

1807 matplotlib.axes.Axes.set_anchor 

1808 for a description of the anchor. 

1809 matplotlib.axes.Axes.set_aspect 

1810 for a description of aspect handling. 

1811 """ 

1812 return self._anchor 

1813 

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

1815 """ 

1816 Define the anchor location. 

1817 

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

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

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

1821 available space. 

1822 

1823 Parameters 

1824 ---------- 

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

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

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

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

1829 for (*x*, *y*) coordinates, as shown in the following diagram:: 

1830 

1831 ┌─────────────────┬─────────────────┬─────────────────┐ 

1832 │ 'NW' (0.0, 1.0) │ 'N' (0.5, 1.0) │ 'NE' (1.0, 1.0) │ 

1833 ├─────────────────┼─────────────────┼─────────────────┤ 

1834 │ 'W' (0.0, 0.5) │ 'C' (0.5, 0.5) │ 'E' (1.0, 0.5) │ 

1835 ├─────────────────┼─────────────────┼─────────────────┤ 

1836 │ 'SW' (0.0, 0.0) │ 'S' (0.5, 0.0) │ 'SE' (1.0, 0.0) │ 

1837 └─────────────────┴─────────────────┴─────────────────┘ 

1838 

1839 share : bool, default: False 

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

1841 

1842 See Also 

1843 -------- 

1844 matplotlib.axes.Axes.set_aspect 

1845 for a description of aspect handling. 

1846 """ 

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

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

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

1850 if share: 

1851 axes = {sibling for name in self._axis_names 

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

1853 else: 

1854 axes = [self] 

1855 for ax in axes: 

1856 ax._anchor = anchor 

1857 

1858 self.stale = True 

1859 

1860 def get_data_ratio(self): 

1861 """ 

1862 Return the aspect ratio of the scaled data. 

1863 

1864 Notes 

1865 ----- 

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

1867 """ 

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

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

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

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

1872 return ysize / xsize 

1873 

1874 def apply_aspect(self, position=None): 

1875 """ 

1876 Adjust the Axes for a specified data aspect ratio. 

1877 

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

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

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

1881 

1882 Parameters 

1883 ---------- 

1884 position : None or .Bbox 

1885 

1886 .. note:: 

1887 This parameter exists for historic reasons and is considered 

1888 internal. End users should not use it. 

1889 

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

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

1892 for further details. 

1893 

1894 Notes 

1895 ----- 

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

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

1898 view limits before the Figure is drawn. 

1899 

1900 An alternative with a broader scope is `.Figure.draw_without_rendering`, 

1901 which updates all stale components of a figure, not only the positioning / 

1902 view limits of a single Axes. 

1903 

1904 See Also 

1905 -------- 

1906 matplotlib.axes.Axes.set_aspect 

1907 For a description of aspect ratio handling. 

1908 matplotlib.axes.Axes.set_adjustable 

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

1910 matplotlib.axes.Axes.set_anchor 

1911 Set the position in case of extra space. 

1912 matplotlib.figure.Figure.draw_without_rendering 

1913 Update all stale components of a figure. 

1914 

1915 Examples 

1916 -------- 

1917 A typical usage example would be the following. `~.Axes.imshow` sets the 

1918 aspect to 1, but adapting the Axes position and extent to reflect this is 

1919 deferred until rendering for performance reasons. If you want to know the 

1920 Axes size before, you need to call `.apply_aspect` to get the correct 

1921 values. 

1922 

1923 >>> fig, ax = plt.subplots() 

1924 >>> ax.imshow(np.zeros((3, 3))) 

1925 >>> ax.bbox.width, ax.bbox.height 

1926 (496.0, 369.59999999999997) 

1927 >>> ax.apply_aspect() 

1928 >>> ax.bbox.width, ax.bbox.height 

1929 (369.59999999999997, 369.59999999999997) 

1930 """ 

1931 if position is None: 

1932 position = self.get_position(original=True) 

1933 

1934 aspect = self.get_aspect() 

1935 

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

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

1938 return 

1939 

1940 trans = self.get_figure().transSubfigure 

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

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

1943 fig_aspect = bb.height / bb.width 

1944 

1945 if self._adjustable == 'box': 

1946 if self in self._twinned_axes: 

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

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

1949 box_aspect = aspect * self.get_data_ratio() 

1950 pb = position.frozen() 

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

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

1953 return 

1954 

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

1956 if self._box_aspect is not None: 

1957 pb = position.frozen() 

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

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

1960 if aspect == "auto": 

1961 return 

1962 

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

1964 # of 'box' 

1965 if self._box_aspect is None: 

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

1967 else: 

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

1969 

1970 x_trf = self.xaxis.get_transform() 

1971 y_trf = self.yaxis.get_transform() 

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

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

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

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

1976 

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

1978 data_ratio = box_aspect / aspect 

1979 

1980 y_expander = data_ratio * xsize / ysize - 1 

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

1982 if abs(y_expander) < 0.005: 

1983 return 

1984 

1985 dL = self.dataLim 

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

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

1988 xr = 1.05 * (x1 - x0) 

1989 yr = 1.05 * (y1 - y0) 

1990 

1991 xmarg = xsize - xr 

1992 ymarg = ysize - yr 

1993 Ysize = data_ratio * xsize 

1994 Xsize = ysize / data_ratio 

1995 Xmarg = Xsize - xr 

1996 Ymarg = Ysize - yr 

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

1998 xm = 0 

1999 ym = 0 

2000 

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

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

2003 

2004 if shared_x and shared_y: 

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

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

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

2008 "adjustable='box').") 

2009 

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

2011 if shared_y: 

2012 adjust_y = False 

2013 else: 

2014 if xmarg > xm and ymarg > ym: 

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

2016 (Xmarg < 0 and y_expander > 0)) 

2017 else: 

2018 adjy = y_expander > 0 

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

2020 

2021 if adjust_y: 

2022 yc = 0.5 * (ymin + ymax) 

2023 y0 = yc - Ysize / 2.0 

2024 y1 = yc + Ysize / 2.0 

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

2026 else: 

2027 xc = 0.5 * (xmin + xmax) 

2028 x0 = xc - Xsize / 2.0 

2029 x1 = xc + Xsize / 2.0 

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

2031 

2032 def axis(self, arg=None, /, *, emit=True, **kwargs): 

2033 """ 

2034 Convenience method to get or set some axis properties. 

2035 

2036 Call signatures:: 

2037 

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

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

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

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

2042 

2043 Parameters 

2044 ---------- 

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

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

2047 

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

2049 

2050 option : bool or str 

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

2052 possible values are: 

2053 

2054 ================ =========================================================== 

2055 Value Description 

2056 ================ =========================================================== 

2057 'off' or `False` Hide all axis decorations, i.e. axis labels, spines, 

2058 tick marks, tick labels, and grid lines. 

2059 This is the same as `~.Axes.set_axis_off()`. 

2060 'on' or `True` Do not hide all axis decorations, i.e. axis labels, spines, 

2061 tick marks, tick labels, and grid lines. 

2062 This is the same as `~.Axes.set_axis_on()`. 

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

2064 changing the axis limits. This is the same as 

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

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

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

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

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

2070 Additionally, further autoscaling will be disabled. 

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

2072 disable further autoscaling. 

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

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

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

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

2077 ================ =========================================================== 

2078 

2079 emit : bool, default: True 

2080 Whether observers are notified of the axis limit change. 

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

2082 `~.Axes.set_ylim`. 

2083 

2084 Returns 

2085 ------- 

2086 xmin, xmax, ymin, ymax : float 

2087 The axis limits. 

2088 

2089 See Also 

2090 -------- 

2091 matplotlib.axes.Axes.set_xlim 

2092 matplotlib.axes.Axes.set_ylim 

2093 

2094 Notes 

2095 ----- 

2096 For 3D Axes, this method additionally takes *zmin*, *zmax* as 

2097 parameters and likewise returns them. 

2098 """ 

2099 if isinstance(arg, (str, bool)): 

2100 if arg is True: 

2101 arg = 'on' 

2102 if arg is False: 

2103 arg = 'off' 

2104 arg = arg.lower() 

2105 if arg == 'on': 

2106 self.set_axis_on() 

2107 elif arg == 'off': 

2108 self.set_axis_off() 

2109 elif arg in [ 

2110 'equal', 'tight', 'scaled', 'auto', 'image', 'square']: 

2111 self.set_autoscale_on(True) 

2112 self.set_aspect('auto') 

2113 self.autoscale_view(tight=False) 

2114 if arg == 'equal': 

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

2116 elif arg == 'scaled': 

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

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

2119 elif arg == 'tight': 

2120 self.autoscale_view(tight=True) 

2121 self.set_autoscale_on(False) 

2122 elif arg == 'image': 

2123 self.autoscale_view(tight=True) 

2124 self.set_autoscale_on(False) 

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

2126 elif arg == 'square': 

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

2128 self.set_autoscale_on(False) 

2129 xlim = self.get_xlim() 

2130 ylim = self.get_ylim() 

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

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

2133 emit=emit, auto=False) 

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

2135 emit=emit, auto=False) 

2136 else: 

2137 raise ValueError(f"Unrecognized string {arg!r} to axis; " 

2138 "try 'on' or 'off'") 

2139 else: 

2140 if arg is not None: 

2141 if len(arg) != 2*len(self._axis_names): 

2142 raise TypeError( 

2143 "The first argument to axis() must be an iterable of the form " 

2144 "[{}]".format(", ".join( 

2145 f"{name}min, {name}max" for name in self._axis_names))) 

2146 limits = { 

2147 name: arg[2*i:2*(i+1)] 

2148 for i, name in enumerate(self._axis_names) 

2149 } 

2150 else: 

2151 limits = {} 

2152 for name in self._axis_names: 

2153 ax_min = kwargs.pop(f'{name}min', None) 

2154 ax_max = kwargs.pop(f'{name}max', None) 

2155 limits[name] = (ax_min, ax_max) 

2156 for name, (ax_min, ax_max) in limits.items(): 

2157 ax_auto = (None # Keep autoscale state as is. 

2158 if ax_min is None and ax_max is None 

2159 else False) # Turn off autoscale. 

2160 set_ax_lim = getattr(self, f'set_{name}lim') 

2161 set_ax_lim(ax_min, ax_max, emit=emit, auto=ax_auto) 

2162 if kwargs: 

2163 raise _api.kwarg_error("axis", kwargs) 

2164 lims = () 

2165 for name in self._axis_names: 

2166 get_ax_lim = getattr(self, f'get_{name}lim') 

2167 lims += get_ax_lim() 

2168 return lims 

2169 

2170 def get_legend(self): 

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

2172 return self.legend_ 

2173 

2174 def get_images(self): 

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

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

2177 

2178 def get_lines(self): 

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

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

2181 

2182 def get_xaxis(self): 

2183 """ 

2184 [*Discouraged*] Return the XAxis instance. 

2185 

2186 .. admonition:: Discouraged 

2187 

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

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

2190 """ 

2191 return self.xaxis 

2192 

2193 def get_yaxis(self): 

2194 """ 

2195 [*Discouraged*] Return the YAxis instance. 

2196 

2197 .. admonition:: Discouraged 

2198 

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

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

2201 """ 

2202 return self.yaxis 

2203 

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

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

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

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

2208 

2209 # Adding and tracking artists 

2210 

2211 def _sci(self, im): 

2212 """ 

2213 Set the current image. 

2214 

2215 This image will be the target of colormap functions like 

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

2217 current image is an attribute of the current Axes. 

2218 """ 

2219 _api.check_isinstance((mcoll.Collection, mimage.AxesImage), im=im) 

2220 if im not in self._children: 

2221 raise ValueError("Argument must be an image or collection in this Axes") 

2222 self._current_image = im 

2223 

2224 def _gci(self): 

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

2226 return self._current_image 

2227 

2228 def has_data(self): 

2229 """ 

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

2231 

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

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

2234 anything. 

2235 """ 

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

2237 mlines.Line2D, mpatches.Patch)) 

2238 for a in self._children) 

2239 

2240 def add_artist(self, a): 

2241 """ 

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

2243 

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

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

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

2247 autoscaling. 

2248 

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

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

2251 ``ax.transData``. 

2252 """ 

2253 a.axes = self 

2254 self._children.append(a) 

2255 a._remove_method = self._children.remove 

2256 self._set_artist_props(a) 

2257 if a.get_clip_path() is None: 

2258 a.set_clip_path(self.patch) 

2259 self.stale = True 

2260 return a 

2261 

2262 def add_child_axes(self, ax): 

2263 """ 

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

2265 

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

2267 """ 

2268 

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

2270 # their parent... 

2271 # Need to bypass the getter... 

2272 ax._axes = self 

2273 ax.stale_callback = martist._stale_axes_callback 

2274 

2275 self.child_axes.append(ax) 

2276 ax._remove_method = functools.partial( 

2277 self.figure._remove_axes, owners=[self.child_axes]) 

2278 self.stale = True 

2279 return ax 

2280 

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

2282 """ 

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

2284 """ 

2285 _api.check_isinstance(mcoll.Collection, collection=collection) 

2286 if not collection.get_label(): 

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

2288 self._children.append(collection) 

2289 collection._remove_method = self._children.remove 

2290 self._set_artist_props(collection) 

2291 

2292 if collection.get_clip_path() is None: 

2293 collection.set_clip_path(self.patch) 

2294 

2295 if autolim: 

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

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

2298 self._unstale_viewLim() 

2299 datalim = collection.get_datalim(self.transData) 

2300 points = datalim.get_points() 

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

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

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

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

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

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

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

2308 self.update_datalim(points) 

2309 

2310 self.stale = True 

2311 return collection 

2312 

2313 def add_image(self, image): 

2314 """ 

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

2316 """ 

2317 _api.check_isinstance(mimage.AxesImage, image=image) 

2318 self._set_artist_props(image) 

2319 if not image.get_label(): 

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

2321 self._children.append(image) 

2322 image._remove_method = self._children.remove 

2323 self.stale = True 

2324 return image 

2325 

2326 def _update_image_limits(self, image): 

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

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

2329 

2330 def add_line(self, line): 

2331 """ 

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

2333 """ 

2334 _api.check_isinstance(mlines.Line2D, line=line) 

2335 self._set_artist_props(line) 

2336 if line.get_clip_path() is None: 

2337 line.set_clip_path(self.patch) 

2338 

2339 self._update_line_limits(line) 

2340 if not line.get_label(): 

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

2342 self._children.append(line) 

2343 line._remove_method = self._children.remove 

2344 self.stale = True 

2345 return line 

2346 

2347 def _add_text(self, txt): 

2348 """ 

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

2350 """ 

2351 _api.check_isinstance(mtext.Text, txt=txt) 

2352 self._set_artist_props(txt) 

2353 self._children.append(txt) 

2354 txt._remove_method = self._children.remove 

2355 self.stale = True 

2356 return txt 

2357 

2358 def _update_line_limits(self, line): 

2359 """ 

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

2361 """ 

2362 path = line.get_path() 

2363 if path.vertices.size == 0: 

2364 return 

2365 

2366 line_trf = line.get_transform() 

2367 

2368 if line_trf == self.transData: 

2369 data_path = path 

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

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

2372 trf_to_data = line_trf - self.transData 

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

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

2375 # entirely encapsulated in trf_to_data). 

2376 if self.transData.is_affine: 

2377 line_trans_path = line._get_transformed_path() 

2378 na_path, _ = line_trans_path.get_transformed_path_and_affine() 

2379 data_path = trf_to_data.transform_path_affine(na_path) 

2380 else: 

2381 data_path = trf_to_data.transform_path(path) 

2382 else: 

2383 # For backwards compatibility we update the dataLim with the 

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

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

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

2387 # positioning. 

2388 data_path = path 

2389 

2390 if not data_path.vertices.size: 

2391 return 

2392 

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

2394 if self.name != "rectilinear": 

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

2396 # for which updatey would otherwise be True. 

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

2398 updatex = False 

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

2400 updatey = False 

2401 self.dataLim.update_from_path(data_path, 

2402 self.ignore_existing_data_limits, 

2403 updatex=updatex, updatey=updatey) 

2404 self.ignore_existing_data_limits = False 

2405 

2406 def add_patch(self, p): 

2407 """ 

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

2409 """ 

2410 _api.check_isinstance(mpatches.Patch, p=p) 

2411 self._set_artist_props(p) 

2412 if p.get_clip_path() is None: 

2413 p.set_clip_path(self.patch) 

2414 self._update_patch_limits(p) 

2415 self._children.append(p) 

2416 p._remove_method = self._children.remove 

2417 return p 

2418 

2419 def _update_patch_limits(self, patch): 

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

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

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

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

2424 # the auto-scaling 

2425 

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

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

2428 # or height. 

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

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

2431 return 

2432 p = patch.get_path() 

2433 # Get all vertices on the path 

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

2435 vertices = [] 

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

2437 # Get distance along the curve of any extrema 

2438 _, dzeros = curve.axis_aligned_extrema() 

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

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

2441 

2442 if len(vertices): 

2443 vertices = np.vstack(vertices) 

2444 

2445 patch_trf = patch.get_transform() 

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

2447 if not (updatex or updatey): 

2448 return 

2449 if self.name != "rectilinear": 

2450 # As in _update_line_limits, but for axvspan. 

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

2452 updatex = False 

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

2454 updatey = False 

2455 trf_to_data = patch_trf - self.transData 

2456 xys = trf_to_data.transform(vertices) 

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

2458 

2459 def add_table(self, tab): 

2460 """ 

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

2462 """ 

2463 _api.check_isinstance(mtable.Table, tab=tab) 

2464 self._set_artist_props(tab) 

2465 self._children.append(tab) 

2466 if tab.get_clip_path() is None: 

2467 tab.set_clip_path(self.patch) 

2468 tab._remove_method = self._children.remove 

2469 return tab 

2470 

2471 def add_container(self, container): 

2472 """ 

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

2474 """ 

2475 label = container.get_label() 

2476 if not label: 

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

2478 self.containers.append(container) 

2479 container._remove_method = self.containers.remove 

2480 return container 

2481 

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

2483 """ 

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

2485 """ 

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

2487 return functools.partial( 

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

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

2490 for line in self.lines: 

2491 line.recache_always() 

2492 self.relim() 

2493 self._request_autoscale_view(axis_name) 

2494 

2495 def relim(self, visible_only=False): 

2496 """ 

2497 Recompute the data limits based on current artists. 

2498 

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

2500 

2501 Parameters 

2502 ---------- 

2503 visible_only : bool, default: False 

2504 Whether to exclude invisible artists. 

2505 """ 

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

2507 # the TODO note in artists.py. 

2508 self.dataLim.ignore(True) 

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

2510 self.ignore_existing_data_limits = True 

2511 

2512 for artist in self._children: 

2513 if not visible_only or artist.get_visible(): 

2514 if isinstance(artist, mlines.Line2D): 

2515 self._update_line_limits(artist) 

2516 elif isinstance(artist, mpatches.Patch): 

2517 self._update_patch_limits(artist) 

2518 elif isinstance(artist, mimage.AxesImage): 

2519 self._update_image_limits(artist) 

2520 

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

2522 """ 

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

2524 

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

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

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

2528 *xys*. 

2529 

2530 Parameters 

2531 ---------- 

2532 xys : 2D array-like 

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

2534 a list of (x, y) tuples or a (N, 2) array. 

2535 

2536 updatex, updatey : bool, default: True 

2537 Whether to update the x/y limits. 

2538 """ 

2539 xys = np.asarray(xys) 

2540 if not np.any(np.isfinite(xys)): 

2541 return 

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

2543 updatex=updatex, updatey=updatey) 

2544 self.ignore_existing_data_limits = False 

2545 

2546 def _process_unit_info(self, datasets=None, kwargs=None, *, convert=True): 

2547 """ 

2548 Set axis units based on *datasets* and *kwargs*, and optionally apply 

2549 unit conversions to *datasets*. 

2550 

2551 Parameters 

2552 ---------- 

2553 datasets : list 

2554 List of (axis_name, dataset) pairs (where the axis name is defined 

2555 as in `._axis_map`). Individual datasets can also be None 

2556 (which gets passed through). 

2557 kwargs : dict 

2558 Other parameters from which unit info (i.e., the *xunits*, 

2559 *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for 

2560 polar) entries) is popped, if present. Note that this dict is 

2561 mutated in-place! 

2562 convert : bool, default: True 

2563 Whether to return the original datasets or the converted ones. 

2564 

2565 Returns 

2566 ------- 

2567 list 

2568 Either the original datasets if *convert* is False, or the 

2569 converted ones if *convert* is True (the default). 

2570 """ 

2571 # The API makes datasets a list of pairs rather than an axis_name to 

2572 # dataset mapping because it is sometimes necessary to process multiple 

2573 # datasets for a single axis, and concatenating them may be tricky 

2574 # (e.g. if some are scalars, etc.). 

2575 datasets = datasets or [] 

2576 kwargs = kwargs or {} 

2577 axis_map = self._axis_map 

2578 for axis_name, data in datasets: 

2579 try: 

2580 axis = axis_map[axis_name] 

2581 except KeyError: 

2582 raise ValueError(f"Invalid axis name: {axis_name!r}") from None 

2583 # Update from data if axis is already set but no unit is set yet. 

2584 if axis is not None and data is not None and not axis.have_units(): 

2585 axis.update_units(data) 

2586 for axis_name, axis in axis_map.items(): 

2587 # Return if no axis is set. 

2588 if axis is None: 

2589 continue 

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

2591 units = kwargs.pop(f"{axis_name}units", axis.units) 

2592 if self.name == "polar": 

2593 # Special case: polar supports "thetaunits"/"runits". 

2594 polar_units = {"x": "thetaunits", "y": "runits"} 

2595 units = kwargs.pop(polar_units[axis_name], units) 

2596 if units != axis.units and units is not None: 

2597 axis.set_units(units) 

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

2599 # we need to update again. 

2600 for dataset_axis_name, data in datasets: 

2601 if dataset_axis_name == axis_name and data is not None: 

2602 axis.update_units(data) 

2603 return [axis_map[axis_name].convert_units(data) 

2604 if convert and data is not None else data 

2605 for axis_name, data in datasets] 

2606 

2607 def in_axes(self, mouseevent): 

2608 """ 

2609 Return whether the given event (in display coords) is in the Axes. 

2610 """ 

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

2612 

2613 get_autoscalex_on = _axis_method_wrapper("xaxis", "_get_autoscale_on") 

2614 get_autoscaley_on = _axis_method_wrapper("yaxis", "_get_autoscale_on") 

2615 set_autoscalex_on = _axis_method_wrapper("xaxis", "_set_autoscale_on") 

2616 set_autoscaley_on = _axis_method_wrapper("yaxis", "_set_autoscale_on") 

2617 

2618 def get_autoscale_on(self): 

2619 """Return True if each axis is autoscaled, False otherwise.""" 

2620 return all(axis._get_autoscale_on() 

2621 for axis in self._axis_map.values()) 

2622 

2623 def set_autoscale_on(self, b): 

2624 """ 

2625 Set whether autoscaling is applied to each axis on the next draw or 

2626 call to `.Axes.autoscale_view`. 

2627 

2628 Parameters 

2629 ---------- 

2630 b : bool 

2631 """ 

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

2633 axis._set_autoscale_on(b) 

2634 

2635 @property 

2636 def use_sticky_edges(self): 

2637 """ 

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

2639 

2640 Default is ``True``. 

2641 

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

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

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

2645 with its data limit. 

2646 

2647 The changing this property does not change the plot until 

2648 `autoscale` or `autoscale_view` is called. 

2649 """ 

2650 return self._use_sticky_edges 

2651 

2652 @use_sticky_edges.setter 

2653 def use_sticky_edges(self, b): 

2654 self._use_sticky_edges = bool(b) 

2655 # No effect until next autoscaling, which will mark the Axes as stale. 

2656 

2657 def get_xmargin(self): 

2658 """ 

2659 Retrieve autoscaling margin of the x-axis. 

2660 

2661 .. versionadded:: 3.9 

2662 

2663 Returns 

2664 ------- 

2665 xmargin : float 

2666 

2667 See Also 

2668 -------- 

2669 matplotlib.axes.Axes.set_xmargin 

2670 """ 

2671 return self._xmargin 

2672 

2673 def get_ymargin(self): 

2674 """ 

2675 Retrieve autoscaling margin of the y-axis. 

2676 

2677 .. versionadded:: 3.9 

2678 

2679 Returns 

2680 ------- 

2681 ymargin : float 

2682 

2683 See Also 

2684 -------- 

2685 matplotlib.axes.Axes.set_ymargin 

2686 """ 

2687 return self._ymargin 

2688 

2689 def set_xmargin(self, m): 

2690 """ 

2691 Set padding of X data limits prior to autoscaling. 

2692 

2693 *m* times the data interval will be added to each end of that interval 

2694 before it is used in autoscaling. If *m* is negative, this will clip 

2695 the data range instead of expanding it. 

2696 

2697 For example, if your data is in the range [0, 2], a margin of 0.1 will 

2698 result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range 

2699 of [0.2, 1.8]. 

2700 

2701 Parameters 

2702 ---------- 

2703 m : float greater than -0.5 

2704 """ 

2705 if m <= -0.5: 

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

2707 self._xmargin = m 

2708 self._request_autoscale_view("x") 

2709 self.stale = True 

2710 

2711 def set_ymargin(self, m): 

2712 """ 

2713 Set padding of Y data limits prior to autoscaling. 

2714 

2715 *m* times the data interval will be added to each end of that interval 

2716 before it is used in autoscaling. If *m* is negative, this will clip 

2717 the data range instead of expanding it. 

2718 

2719 For example, if your data is in the range [0, 2], a margin of 0.1 will 

2720 result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range 

2721 of [0.2, 1.8]. 

2722 

2723 Parameters 

2724 ---------- 

2725 m : float greater than -0.5 

2726 """ 

2727 if m <= -0.5: 

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

2729 self._ymargin = m 

2730 self._request_autoscale_view("y") 

2731 self.stale = True 

2732 

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

2734 """ 

2735 Set or retrieve autoscaling margins. 

2736 

2737 The padding added to each limit of the Axes is the *margin* 

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

2739 greater than -0.5. Passing both positional and keyword 

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

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

2742 margins will remain unchanged and simply be returned. 

2743 

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

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

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

2747 it is used in autoscaling. 

2748 

2749 Parameters 

2750 ---------- 

2751 *margins : float, optional 

2752 If a single positional argument is provided, it specifies 

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

2754 positional arguments are provided, they will be interpreted 

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

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

2757 

2758 x, y : float, optional 

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

2760 respectively. These cannot be used with positional 

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

2762 only the y-axis. 

2763 

2764 tight : bool or None, default: True 

2765 The *tight* parameter is passed to `~.axes.Axes.autoscale_view`, 

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

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

2768 specified, no additional padding to match tick marks is 

2769 usually desired. Setting *tight* to *None* preserves 

2770 the previous setting. 

2771 

2772 Returns 

2773 ------- 

2774 xmargin, ymargin : float 

2775 

2776 Notes 

2777 ----- 

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

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

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

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

2782 before calling :meth:`margins`. 

2783 """ 

2784 

2785 if margins and (x is not None or y is not None): 

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

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

2788 elif len(margins) == 1: 

2789 x = y = margins[0] 

2790 elif len(margins) == 2: 

2791 x, y = margins 

2792 elif margins: 

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

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

2795 

2796 if x is None and y is None: 

2797 if tight is not True: 

2798 _api.warn_external(f'ignoring tight={tight!r} in get mode') 

2799 return self._xmargin, self._ymargin 

2800 

2801 if tight is not None: 

2802 self._tight = tight 

2803 if x is not None: 

2804 self.set_xmargin(x) 

2805 if y is not None: 

2806 self.set_ymargin(y) 

2807 

2808 def set_rasterization_zorder(self, z): 

2809 """ 

2810 Set the zorder threshold for rasterization for vector graphics output. 

2811 

2812 All artists with a zorder below the given value will be rasterized if 

2813 they support rasterization. 

2814 

2815 This setting is ignored for pixel-based output. 

2816 

2817 See also :doc:`/gallery/misc/rasterization_demo`. 

2818 

2819 Parameters 

2820 ---------- 

2821 z : float or None 

2822 The zorder below which artists are rasterized. 

2823 If ``None`` rasterization based on zorder is deactivated. 

2824 """ 

2825 self._rasterization_zorder = z 

2826 self.stale = True 

2827 

2828 def get_rasterization_zorder(self): 

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

2830 return self._rasterization_zorder 

2831 

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

2833 """ 

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

2835 

2836 Convenience method for simple axis view autoscaling. 

2837 It turns autoscaling on or off, and then, 

2838 if autoscaling for either axis is on, it performs 

2839 the autoscaling on the specified axis or Axes. 

2840 

2841 Parameters 

2842 ---------- 

2843 enable : bool or None, default: True 

2844 True turns autoscaling on, False turns it off. 

2845 None leaves the autoscaling state unchanged. 

2846 axis : {'both', 'x', 'y'}, default: 'both' 

2847 The axis on which to operate. (For 3D Axes, *axis* can also be set 

2848 to 'z', and 'both' refers to all three Axes.) 

2849 tight : bool or None, default: None 

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

2851 forwarded to `~.axes.Axes.autoscale_view` (regardless of 

2852 its value); see the description of its behavior there. 

2853 """ 

2854 if enable is None: 

2855 scalex = True 

2856 scaley = True 

2857 else: 

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

2859 self.set_autoscalex_on(bool(enable)) 

2860 scalex = self.get_autoscalex_on() 

2861 else: 

2862 scalex = False 

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

2864 self.set_autoscaley_on(bool(enable)) 

2865 scaley = self.get_autoscaley_on() 

2866 else: 

2867 scaley = False 

2868 if tight and scalex: 

2869 self._xmargin = 0 

2870 if tight and scaley: 

2871 self._ymargin = 0 

2872 if scalex: 

2873 self._request_autoscale_view("x", tight=tight) 

2874 if scaley: 

2875 self._request_autoscale_view("y", tight=tight) 

2876 

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

2878 """ 

2879 Autoscale the view limits using the data limits. 

2880 

2881 Parameters 

2882 ---------- 

2883 tight : bool or None 

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

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

2886 margins to zero. 

2887 

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

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

2890 using the axis major locator. 

2891 

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

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

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

2895 behaves like True). 

2896 

2897 scalex : bool, default: True 

2898 Whether to autoscale the x-axis. 

2899 

2900 scaley : bool, default: True 

2901 Whether to autoscale the y-axis. 

2902 

2903 Notes 

2904 ----- 

2905 The autoscaling preserves any preexisting axis direction reversal. 

2906 

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

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

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

2910 autoscale_view. 

2911 

2912 If the views of the Axes are fixed, e.g. via `set_xlim`, they will 

2913 not be changed by autoscale_view(). 

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

2915 """ 

2916 if tight is not None: 

2917 self._tight = bool(tight) 

2918 

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

2920 if self.use_sticky_edges: 

2921 if self._xmargin and scalex and self.get_autoscalex_on(): 

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

2923 artist.sticky_edges.x 

2924 for ax in self._shared_axes["x"].get_siblings(self) 

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

2926 if self._ymargin and scaley and self.get_autoscaley_on(): 

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

2928 artist.sticky_edges.y 

2929 for ax in self._shared_axes["y"].get_siblings(self) 

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

2931 if self.get_xscale() == 'log': 

2932 x_stickies = x_stickies[x_stickies > 0] 

2933 if self.get_yscale() == 'log': 

2934 y_stickies = y_stickies[y_stickies > 0] 

2935 

2936 def handle_single_axis( 

2937 scale, shared_axes, name, axis, margin, stickies, set_bound): 

2938 

2939 if not (scale and axis._get_autoscale_on()): 

2940 return # nothing to do... 

2941 

2942 shared = shared_axes.get_siblings(self) 

2943 # Base autoscaling on finite data limits when there is at least one 

2944 # finite data limit among all the shared_axes and intervals. 

2945 values = [val for ax in shared 

2946 for val in getattr(ax.dataLim, f"interval{name}") 

2947 if np.isfinite(val)] 

2948 if values: 

2949 x0, x1 = (min(values), max(values)) 

2950 elif getattr(self._viewLim, f"mutated{name}")(): 

2951 # No data, but explicit viewLims already set: 

2952 # in mutatedx or mutatedy. 

2953 return 

2954 else: 

2955 x0, x1 = (-np.inf, np.inf) 

2956 # If x0 and x1 are nonfinite, get default limits from the locator. 

2957 locator = axis.get_major_locator() 

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

2959 # Find the minimum minpos for use in the margin calculation. 

2960 minimum_minpos = min( 

2961 getattr(ax.dataLim, f"minpos{name}") for ax in shared) 

2962 

2963 # Prevent margin addition from crossing a sticky value. A small 

2964 # tolerance must be added due to floating point issues with 

2965 # streamplot; it is defined relative to x1-x0 but has 

2966 # no absolute term (e.g. "+1e-8") to avoid issues when working with 

2967 # datasets where all values are tiny (less than 1e-8). 

2968 tol = 1e-5 * abs(x1 - x0) 

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

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

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

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

2973 i1 = stickies.searchsorted(x1 - tol) 

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

2975 

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

2977 # non-linear scales. 

2978 transform = axis.get_transform() 

2979 inverse_trans = transform.inverted() 

2980 x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minimum_minpos) 

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

2982 delta = (x1t - x0t) * margin 

2983 if not np.isfinite(delta): 

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

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

2986 

2987 # Apply sticky bounds. 

2988 if x0bound is not None: 

2989 x0 = max(x0, x0bound) 

2990 if x1bound is not None: 

2991 x1 = min(x1, x1bound) 

2992 

2993 if not self._tight: 

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

2995 set_bound(x0, x1) 

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

2997 

2998 handle_single_axis( 

2999 scalex, self._shared_axes["x"], 'x', self.xaxis, self._xmargin, 

3000 x_stickies, self.set_xbound) 

3001 handle_single_axis( 

3002 scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin, 

3003 y_stickies, self.set_ybound) 

3004 

3005 def _update_title_position(self, renderer): 

3006 """ 

3007 Update the title position based on the bounding box enclosing 

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

3009 """ 

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

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

3012 return 

3013 

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

3015 

3016 # Need to check all our twins too, aligned axes, and all the children 

3017 # as well. 

3018 axs = set() 

3019 axs.update(self.child_axes) 

3020 axs.update(self._twinned_axes.get_siblings(self)) 

3021 axs.update(self.figure._align_label_groups['title'].get_siblings(self)) 

3022 

3023 for ax in self.child_axes: # Child positions must be updated first. 

3024 locator = ax.get_axes_locator() 

3025 ax.apply_aspect(locator(self, renderer) if locator else None) 

3026 

3027 for title in titles: 

3028 x, _ = title.get_position() 

3029 # need to start again in case of window resizing 

3030 title.set_position((x, 1.0)) 

3031 top = -np.inf 

3032 for ax in axs: 

3033 bb = None 

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

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

3036 bb = ax.xaxis.get_tightbbox(renderer) 

3037 if bb is None: 

3038 if 'outline' in ax.spines: 

3039 # Special case for colorbars: 

3040 bb = ax.spines['outline'].get_window_extent() 

3041 else: 

3042 bb = ax.get_window_extent(renderer) 

3043 top = max(top, bb.ymax) 

3044 if title.get_text(): 

3045 ax.yaxis.get_tightbbox(renderer) # update offsetText 

3046 if ax.yaxis.offsetText.get_text(): 

3047 bb = ax.yaxis.offsetText.get_tightbbox(renderer) 

3048 if bb.intersection(title.get_tightbbox(renderer), bb): 

3049 top = bb.ymax 

3050 if top < 0: 

3051 # the top of Axes is not even on the figure, so don't try and 

3052 # automatically place it. 

3053 _log.debug('top of Axes not in the figure, so title not moved') 

3054 return 

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

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

3057 title.set_position((x, y)) 

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

3059 # so we need to adjust again. 

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

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

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

3063 title.set_position((x, y)) 

3064 

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

3066 for title in titles: 

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

3068 x, _ = title.get_position() 

3069 title.set_position((x, ymax)) 

3070 

3071 # Drawing 

3072 @martist.allow_rasterization 

3073 def draw(self, renderer): 

3074 # docstring inherited 

3075 if renderer is None: 

3076 raise RuntimeError('No renderer defined') 

3077 if not self.get_visible(): 

3078 return 

3079 self._unstale_viewLim() 

3080 

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

3082 

3083 # prevent triggering call backs during the draw process 

3084 self._stale = True 

3085 

3086 # loop over self and child Axes... 

3087 locator = self.get_axes_locator() 

3088 self.apply_aspect(locator(self, renderer) if locator else None) 

3089 

3090 artists = self.get_children() 

3091 artists.remove(self.patch) 

3092 

3093 # the frame draws the edges around the Axes patch -- we 

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

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

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

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

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

3099 artists.remove(spine) 

3100 

3101 self._update_title_position(renderer) 

3102 

3103 if not self.axison: 

3104 for _axis in self._axis_map.values(): 

3105 artists.remove(_axis) 

3106 

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

3108 artists = [ 

3109 a for a in artists 

3110 if not a.get_animated() or isinstance(a, mimage.AxesImage)] 

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

3112 

3113 # rasterize artists with negative zorder 

3114 # if the minimum zorder is negative, start rasterization 

3115 rasterization_zorder = self._rasterization_zorder 

3116 

3117 if (rasterization_zorder is not None and 

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

3119 split_index = np.searchsorted( 

3120 [art.zorder for art in artists], 

3121 rasterization_zorder, side='right' 

3122 ) 

3123 artists_rasterized = artists[:split_index] 

3124 artists = artists[split_index:] 

3125 else: 

3126 artists_rasterized = [] 

3127 

3128 if self.axison and self._frameon: 

3129 if artists_rasterized: 

3130 artists_rasterized = [self.patch] + artists_rasterized 

3131 else: 

3132 artists = [self.patch] + artists 

3133 

3134 if artists_rasterized: 

3135 _draw_rasterized(self.figure, artists_rasterized, renderer) 

3136 

3137 mimage._draw_list_compositing_images( 

3138 renderer, self, artists, self.figure.suppressComposite) 

3139 

3140 renderer.close_group('axes') 

3141 self.stale = False 

3142 

3143 def draw_artist(self, a): 

3144 """ 

3145 Efficiently redraw a single artist. 

3146 """ 

3147 a.draw(self.figure.canvas.get_renderer()) 

3148 

3149 def redraw_in_frame(self): 

3150 """ 

3151 Efficiently redraw Axes data, but not axis ticks, labels, etc. 

3152 """ 

3153 with ExitStack() as stack: 

3154 for artist in [*self._axis_map.values(), 

3155 self.title, self._left_title, self._right_title]: 

3156 stack.enter_context(artist._cm_set(visible=False)) 

3157 self.draw(self.figure.canvas.get_renderer()) 

3158 

3159 # Axes rectangle characteristics 

3160 

3161 def get_frame_on(self): 

3162 """Get whether the Axes rectangle patch is drawn.""" 

3163 return self._frameon 

3164 

3165 def set_frame_on(self, b): 

3166 """ 

3167 Set whether the Axes rectangle patch is drawn. 

3168 

3169 Parameters 

3170 ---------- 

3171 b : bool 

3172 """ 

3173 self._frameon = b 

3174 self.stale = True 

3175 

3176 def get_axisbelow(self): 

3177 """ 

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

3179 

3180 Returns 

3181 ------- 

3182 bool or 'line' 

3183 

3184 See Also 

3185 -------- 

3186 set_axisbelow 

3187 """ 

3188 return self._axisbelow 

3189 

3190 def set_axisbelow(self, b): 

3191 """ 

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

3193 

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

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

3196 

3197 Parameters 

3198 ---------- 

3199 b : bool or 'line' 

3200 Possible values: 

3201 

3202 - *True* (zorder = 0.5): Ticks and gridlines are below patches and 

3203 lines, though still above images. 

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

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

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

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

3208 and lines / markers. 

3209 

3210 Notes 

3211 ----- 

3212 For more control, call the `~.Artist.set_zorder` method of each axis. 

3213 

3214 See Also 

3215 -------- 

3216 get_axisbelow 

3217 """ 

3218 # Check that b is True, False or 'line' 

3219 self._axisbelow = axisbelow = validate_axisbelow(b) 

3220 zorder = { 

3221 True: 0.5, 

3222 'line': 1.5, 

3223 False: 2.5, 

3224 }[axisbelow] 

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

3226 axis.set_zorder(zorder) 

3227 self.stale = True 

3228 

3229 @_docstring.dedent_interpd 

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

3231 """ 

3232 Configure the grid lines. 

3233 

3234 Parameters 

3235 ---------- 

3236 visible : bool or None, optional 

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

3238 is assumed you want the grid on and *visible* will be set to True. 

3239 

3240 If *visible* is *None* and there are no *kwargs*, this toggles the 

3241 visibility of the lines. 

3242 

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

3244 The grid lines to apply the changes on. 

3245 

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

3247 The axis to apply the changes on. 

3248 

3249 **kwargs : `~matplotlib.lines.Line2D` properties 

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

3251 

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

3253 

3254 Valid keyword arguments are: 

3255 

3256 %(Line2D:kwdoc)s 

3257 

3258 Notes 

3259 ----- 

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

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

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

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

3264 `~.Artist.set_zorder` method of each axis. 

3265 """ 

3266 _api.check_in_list(['x', 'y', 'both'], axis=axis) 

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

3268 self.xaxis.grid(visible, which=which, **kwargs) 

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

3270 self.yaxis.grid(visible, which=which, **kwargs) 

3271 

3272 def ticklabel_format(self, *, axis='both', style=None, scilimits=None, 

3273 useOffset=None, useLocale=None, useMathText=None): 

3274 r""" 

3275 Configure the `.ScalarFormatter` used by default for linear Axes. 

3276 

3277 If a parameter is not set, the corresponding property of the formatter 

3278 is left unchanged. 

3279 

3280 Parameters 

3281 ---------- 

3282 axis : {'x', 'y', 'both'}, default: 'both' 

3283 The axis to configure. Only major ticks are affected. 

3284 

3285 style : {'sci', 'scientific', 'plain'} 

3286 Whether to use scientific notation. 

3287 The formatter default is to use scientific notation. 

3288 'sci' is equivalent to 'scientific'. 

3289 

3290 scilimits : pair of ints (m, n) 

3291 Scientific notation is used only for numbers outside the range 

3292 10\ :sup:`m` to 10\ :sup:`n` (and only if the formatter is 

3293 configured to use scientific notation at all). Use (0, 0) to 

3294 include all numbers. Use (m, m) where m != 0 to fix the order of 

3295 magnitude to 10\ :sup:`m`. 

3296 The formatter default is :rc:`axes.formatter.limits`. 

3297 

3298 useOffset : bool or float 

3299 If True, the offset is calculated as needed. 

3300 If False, no offset is used. 

3301 If a numeric value, it sets the offset. 

3302 The formatter default is :rc:`axes.formatter.useoffset`. 

3303 

3304 useLocale : bool 

3305 Whether to format the number using the current locale or using the 

3306 C (English) locale. This affects e.g. the decimal separator. The 

3307 formatter default is :rc:`axes.formatter.use_locale`. 

3308 

3309 useMathText : bool 

3310 Render the offset and scientific notation in mathtext. 

3311 The formatter default is :rc:`axes.formatter.use_mathtext`. 

3312 

3313 Raises 

3314 ------ 

3315 AttributeError 

3316 If the current formatter is not a `.ScalarFormatter`. 

3317 """ 

3318 if isinstance(style, str): 

3319 style = style.lower() 

3320 axis = axis.lower() 

3321 if scilimits is not None: 

3322 try: 

3323 m, n = scilimits 

3324 m + n + 1 # check that both are numbers 

3325 except (ValueError, TypeError) as err: 

3326 raise ValueError("scilimits must be a sequence of 2 integers" 

3327 ) from err 

3328 STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None, None: None} 

3329 # The '' option is included for backwards-compatibility. 

3330 is_sci_style = _api.check_getitem(STYLES, style=style) 

3331 axis_map = {**{k: [v] for k, v in self._axis_map.items()}, 

3332 'both': list(self._axis_map.values())} 

3333 axises = _api.check_getitem(axis_map, axis=axis) 

3334 try: 

3335 for axis in axises: 

3336 if is_sci_style is not None: 

3337 axis.major.formatter.set_scientific(is_sci_style) 

3338 if scilimits is not None: 

3339 axis.major.formatter.set_powerlimits(scilimits) 

3340 if useOffset is not None: 

3341 axis.major.formatter.set_useOffset(useOffset) 

3342 if useLocale is not None: 

3343 axis.major.formatter.set_useLocale(useLocale) 

3344 if useMathText is not None: 

3345 axis.major.formatter.set_useMathText(useMathText) 

3346 except AttributeError as err: 

3347 raise AttributeError( 

3348 "This method only works with the ScalarFormatter") from err 

3349 

3350 def locator_params(self, axis='both', tight=None, **kwargs): 

3351 """ 

3352 Control behavior of major tick locators. 

3353 

3354 Because the locator is involved in autoscaling, `~.Axes.autoscale_view` 

3355 is called automatically after the parameters are changed. 

3356 

3357 Parameters 

3358 ---------- 

3359 axis : {'both', 'x', 'y'}, default: 'both' 

3360 The axis on which to operate. (For 3D Axes, *axis* can also be 

3361 set to 'z', and 'both' refers to all three axes.) 

3362 tight : bool or None, optional 

3363 Parameter passed to `~.Axes.autoscale_view`. 

3364 Default is None, for no change. 

3365 

3366 Other Parameters 

3367 ---------------- 

3368 **kwargs 

3369 Remaining keyword arguments are passed to directly to the 

3370 ``set_params()`` method of the locator. Supported keywords depend 

3371 on the type of the locator. See for example 

3372 `~.ticker.MaxNLocator.set_params` for the `.ticker.MaxNLocator` 

3373 used by default for linear. 

3374 

3375 Examples 

3376 -------- 

3377 When plotting small subplots, one might want to reduce the maximum 

3378 number of ticks and use tight bounds, for example:: 

3379 

3380 ax.locator_params(tight=True, nbins=4) 

3381 

3382 """ 

3383 _api.check_in_list([*self._axis_names, "both"], axis=axis) 

3384 for name in self._axis_names: 

3385 if axis in [name, "both"]: 

3386 loc = self._axis_map[name].get_major_locator() 

3387 loc.set_params(**kwargs) 

3388 self._request_autoscale_view(name, tight=tight) 

3389 self.stale = True 

3390 

3391 def tick_params(self, axis='both', **kwargs): 

3392 """ 

3393 Change the appearance of ticks, tick labels, and gridlines. 

3394 

3395 Tick properties that are not explicitly set using the keyword 

3396 arguments remain unchanged unless *reset* is True. For the current 

3397 style settings, see `.Axis.get_tick_params`. 

3398 

3399 Parameters 

3400 ---------- 

3401 axis : {'x', 'y', 'both'}, default: 'both' 

3402 The axis to which the parameters are applied. 

3403 which : {'major', 'minor', 'both'}, default: 'major' 

3404 The group of ticks to which the parameters are applied. 

3405 reset : bool, default: False 

3406 Whether to reset the ticks to defaults before updating them. 

3407 

3408 Other Parameters 

3409 ---------------- 

3410 direction : {'in', 'out', 'inout'} 

3411 Puts ticks inside the Axes, outside the Axes, or both. 

3412 length : float 

3413 Tick length in points. 

3414 width : float 

3415 Tick width in points. 

3416 color : :mpltype:`color` 

3417 Tick color. 

3418 pad : float 

3419 Distance in points between tick and label. 

3420 labelsize : float or str 

3421 Tick label font size in points or as a string (e.g., 'large'). 

3422 labelcolor : :mpltype:`color` 

3423 Tick label color. 

3424 labelfontfamily : str 

3425 Tick label font. 

3426 colors : :mpltype:`color` 

3427 Tick color and label color. 

3428 zorder : float 

3429 Tick and label zorder. 

3430 bottom, top, left, right : bool 

3431 Whether to draw the respective ticks. 

3432 labelbottom, labeltop, labelleft, labelright : bool 

3433 Whether to draw the respective tick labels. 

3434 labelrotation : float 

3435 Tick label rotation 

3436 grid_color : :mpltype:`color` 

3437 Gridline color. 

3438 grid_alpha : float 

3439 Transparency of gridlines: 0 (transparent) to 1 (opaque). 

3440 grid_linewidth : float 

3441 Width of gridlines in points. 

3442 grid_linestyle : str 

3443 Any valid `.Line2D` line style spec. 

3444 

3445 Examples 

3446 -------- 

3447 :: 

3448 

3449 ax.tick_params(direction='out', length=6, width=2, colors='r', 

3450 grid_color='r', grid_alpha=0.5) 

3451 

3452 This will make all major ticks be red, pointing out of the box, 

3453 and with dimensions 6 points by 2 points. Tick labels will 

3454 also be red. Gridlines will be red and translucent. 

3455 

3456 """ 

3457 _api.check_in_list(['x', 'y', 'both'], axis=axis) 

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

3459 xkw = dict(kwargs) 

3460 xkw.pop('left', None) 

3461 xkw.pop('right', None) 

3462 xkw.pop('labelleft', None) 

3463 xkw.pop('labelright', None) 

3464 self.xaxis.set_tick_params(**xkw) 

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

3466 ykw = dict(kwargs) 

3467 ykw.pop('top', None) 

3468 ykw.pop('bottom', None) 

3469 ykw.pop('labeltop', None) 

3470 ykw.pop('labelbottom', None) 

3471 self.yaxis.set_tick_params(**ykw) 

3472 

3473 def set_axis_off(self): 

3474 """ 

3475 Hide all visual components of the x- and y-axis. 

3476 

3477 This sets a flag to suppress drawing of all axis decorations, i.e. 

3478 axis labels, axis spines, and the axis tick component (tick markers, 

3479 tick labels, and grid lines). Individual visibility settings of these 

3480 components are ignored as long as `set_axis_off()` is in effect. 

3481 """ 

3482 self.axison = False 

3483 self.stale = True 

3484 

3485 def set_axis_on(self): 

3486 """ 

3487 Do not hide all visual components of the x- and y-axis. 

3488 

3489 This reverts the effect of a prior `.set_axis_off()` call. Whether the 

3490 individual axis decorations are drawn is controlled by their respective 

3491 visibility settings. 

3492 

3493 This is on by default. 

3494 """ 

3495 self.axison = True 

3496 self.stale = True 

3497 

3498 # data limits, ticks, tick labels, and formatting 

3499 

3500 def get_xlabel(self): 

3501 """ 

3502 Get the xlabel text string. 

3503 """ 

3504 label = self.xaxis.get_label() 

3505 return label.get_text() 

3506 

3507 def set_xlabel(self, xlabel, fontdict=None, labelpad=None, *, 

3508 loc=None, **kwargs): 

3509 """ 

3510 Set the label for the x-axis. 

3511 

3512 Parameters 

3513 ---------- 

3514 xlabel : str 

3515 The label text. 

3516 

3517 labelpad : float, default: :rc:`axes.labelpad` 

3518 Spacing in points from the Axes bounding box including ticks 

3519 and tick labels. If None, the previous value is left as is. 

3520 

3521 loc : {'left', 'center', 'right'}, default: :rc:`xaxis.labellocation` 

3522 The label position. This is a high-level alternative for passing 

3523 parameters *x* and *horizontalalignment*. 

3524 

3525 Other Parameters 

3526 ---------------- 

3527 **kwargs : `~matplotlib.text.Text` properties 

3528 `.Text` properties control the appearance of the label. 

3529 

3530 See Also 

3531 -------- 

3532 text : Documents the properties supported by `.Text`. 

3533 """ 

3534 if labelpad is not None: 

3535 self.xaxis.labelpad = labelpad 

3536 protected_kw = ['x', 'horizontalalignment', 'ha'] 

3537 if {*kwargs} & {*protected_kw}: 

3538 if loc is not None: 

3539 raise TypeError(f"Specifying 'loc' is disallowed when any of " 

3540 f"its corresponding low level keyword " 

3541 f"arguments ({protected_kw}) are also " 

3542 f"supplied") 

3543 

3544 else: 

3545 loc = (loc if loc is not None 

3546 else mpl.rcParams['xaxis.labellocation']) 

3547 _api.check_in_list(('left', 'center', 'right'), loc=loc) 

3548 

3549 x = { 

3550 'left': 0, 

3551 'center': 0.5, 

3552 'right': 1, 

3553 }[loc] 

3554 kwargs.update(x=x, horizontalalignment=loc) 

3555 

3556 return self.xaxis.set_label_text(xlabel, fontdict, **kwargs) 

3557 

3558 def invert_xaxis(self): 

3559 """ 

3560 Invert the x-axis. 

3561 

3562 See Also 

3563 -------- 

3564 xaxis_inverted 

3565 get_xlim, set_xlim 

3566 get_xbound, set_xbound 

3567 """ 

3568 self.xaxis.set_inverted(not self.xaxis.get_inverted()) 

3569 

3570 xaxis_inverted = _axis_method_wrapper("xaxis", "get_inverted") 

3571 

3572 def get_xbound(self): 

3573 """ 

3574 Return the lower and upper x-axis bounds, in increasing order. 

3575 

3576 See Also 

3577 -------- 

3578 set_xbound 

3579 get_xlim, set_xlim 

3580 invert_xaxis, xaxis_inverted 

3581 """ 

3582 left, right = self.get_xlim() 

3583 if left < right: 

3584 return left, right 

3585 else: 

3586 return right, left 

3587 

3588 def set_xbound(self, lower=None, upper=None): 

3589 """ 

3590 Set the lower and upper numerical bounds of the x-axis. 

3591 

3592 This method will honor axis inversion regardless of parameter order. 

3593 It will not change the autoscaling setting (`.get_autoscalex_on()`). 

3594 

3595 Parameters 

3596 ---------- 

3597 lower, upper : float or None 

3598 The lower and upper bounds. If *None*, the respective axis bound 

3599 is not modified. 

3600 

3601 .. ACCEPTS: (lower: float, upper: float) 

3602 

3603 See Also 

3604 -------- 

3605 get_xbound 

3606 get_xlim, set_xlim 

3607 invert_xaxis, xaxis_inverted 

3608 """ 

3609 if upper is None and np.iterable(lower): 

3610 lower, upper = lower 

3611 

3612 old_lower, old_upper = self.get_xbound() 

3613 if lower is None: 

3614 lower = old_lower 

3615 if upper is None: 

3616 upper = old_upper 

3617 

3618 self.set_xlim(sorted((lower, upper), 

3619 reverse=bool(self.xaxis_inverted())), 

3620 auto=None) 

3621 

3622 def get_xlim(self): 

3623 """ 

3624 Return the x-axis view limits. 

3625 

3626 Returns 

3627 ------- 

3628 left, right : (float, float) 

3629 The current x-axis limits in data coordinates. 

3630 

3631 See Also 

3632 -------- 

3633 .Axes.set_xlim 

3634 .Axes.set_xbound, .Axes.get_xbound 

3635 .Axes.invert_xaxis, .Axes.xaxis_inverted 

3636 

3637 Notes 

3638 ----- 

3639 The x-axis may be inverted, in which case the *left* value will 

3640 be greater than the *right* value. 

3641 """ 

3642 return tuple(self.viewLim.intervalx) 

3643 

3644 def _validate_converted_limits(self, limit, convert): 

3645 """ 

3646 Raise ValueError if converted limits are non-finite. 

3647 

3648 Note that this function also accepts None as a limit argument. 

3649 

3650 Returns 

3651 ------- 

3652 The limit value after call to convert(), or None if limit is None. 

3653 """ 

3654 if limit is not None: 

3655 converted_limit = convert(limit) 

3656 if isinstance(converted_limit, np.ndarray): 

3657 converted_limit = converted_limit.squeeze() 

3658 if (isinstance(converted_limit, Real) 

3659 and not np.isfinite(converted_limit)): 

3660 raise ValueError("Axis limits cannot be NaN or Inf") 

3661 return converted_limit 

3662 

3663 def set_xlim(self, left=None, right=None, *, emit=True, auto=False, 

3664 xmin=None, xmax=None): 

3665 """ 

3666 Set the x-axis view limits. 

3667 

3668 Parameters 

3669 ---------- 

3670 left : float, optional 

3671 The left xlim in data coordinates. Passing *None* leaves the 

3672 limit unchanged. 

3673 

3674 The left and right xlims may also be passed as the tuple 

3675 (*left*, *right*) as the first positional argument (or as 

3676 the *left* keyword argument). 

3677 

3678 .. ACCEPTS: (left: float, right: float) 

3679 

3680 right : float, optional 

3681 The right xlim in data coordinates. Passing *None* leaves the 

3682 limit unchanged. 

3683 

3684 emit : bool, default: True 

3685 Whether to notify observers of limit change. 

3686 

3687 auto : bool or None, default: False 

3688 Whether to turn on autoscaling of the x-axis. True turns on, 

3689 False turns off, None leaves unchanged. 

3690 

3691 xmin, xmax : float, optional 

3692 They are equivalent to left and right respectively, and it is an 

3693 error to pass both *xmin* and *left* or *xmax* and *right*. 

3694 

3695 Returns 

3696 ------- 

3697 left, right : (float, float) 

3698 The new x-axis limits in data coordinates. 

3699 

3700 See Also 

3701 -------- 

3702 get_xlim 

3703 set_xbound, get_xbound 

3704 invert_xaxis, xaxis_inverted 

3705 

3706 Notes 

3707 ----- 

3708 The *left* value may be greater than the *right* value, in which 

3709 case the x-axis values will decrease from left to right. 

3710 

3711 Examples 

3712 -------- 

3713 >>> set_xlim(left, right) 

3714 >>> set_xlim((left, right)) 

3715 >>> left, right = set_xlim(left, right) 

3716 

3717 One limit may be left unchanged. 

3718 

3719 >>> set_xlim(right=right_lim) 

3720 

3721 Limits may be passed in reverse order to flip the direction of 

3722 the x-axis. For example, suppose *x* represents the number of 

3723 years before present. The x-axis limits might be set like the 

3724 following so 5000 years ago is on the left of the plot and the 

3725 present is on the right. 

3726 

3727 >>> set_xlim(5000, 0) 

3728 """ 

3729 if right is None and np.iterable(left): 

3730 left, right = left 

3731 if xmin is not None: 

3732 if left is not None: 

3733 raise TypeError("Cannot pass both 'left' and 'xmin'") 

3734 left = xmin 

3735 if xmax is not None: 

3736 if right is not None: 

3737 raise TypeError("Cannot pass both 'right' and 'xmax'") 

3738 right = xmax 

3739 return self.xaxis._set_lim(left, right, emit=emit, auto=auto) 

3740 

3741 get_xscale = _axis_method_wrapper("xaxis", "get_scale") 

3742 set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale") 

3743 get_xticks = _axis_method_wrapper("xaxis", "get_ticklocs") 

3744 set_xticks = _axis_method_wrapper("xaxis", "set_ticks", 

3745 doc_sub={'set_ticks': 'set_xticks'}) 

3746 get_xmajorticklabels = _axis_method_wrapper("xaxis", "get_majorticklabels") 

3747 get_xminorticklabels = _axis_method_wrapper("xaxis", "get_minorticklabels") 

3748 get_xticklabels = _axis_method_wrapper("xaxis", "get_ticklabels") 

3749 set_xticklabels = _axis_method_wrapper( 

3750 "xaxis", "set_ticklabels", 

3751 doc_sub={"Axis.set_ticks": "Axes.set_xticks"}) 

3752 

3753 def get_ylabel(self): 

3754 """ 

3755 Get the ylabel text string. 

3756 """ 

3757 label = self.yaxis.get_label() 

3758 return label.get_text() 

3759 

3760 def set_ylabel(self, ylabel, fontdict=None, labelpad=None, *, 

3761 loc=None, **kwargs): 

3762 """ 

3763 Set the label for the y-axis. 

3764 

3765 Parameters 

3766 ---------- 

3767 ylabel : str 

3768 The label text. 

3769 

3770 labelpad : float, default: :rc:`axes.labelpad` 

3771 Spacing in points from the Axes bounding box including ticks 

3772 and tick labels. If None, the previous value is left as is. 

3773 

3774 loc : {'bottom', 'center', 'top'}, default: :rc:`yaxis.labellocation` 

3775 The label position. This is a high-level alternative for passing 

3776 parameters *y* and *horizontalalignment*. 

3777 

3778 Other Parameters 

3779 ---------------- 

3780 **kwargs : `~matplotlib.text.Text` properties 

3781 `.Text` properties control the appearance of the label. 

3782 

3783 See Also 

3784 -------- 

3785 text : Documents the properties supported by `.Text`. 

3786 """ 

3787 if labelpad is not None: 

3788 self.yaxis.labelpad = labelpad 

3789 protected_kw = ['y', 'horizontalalignment', 'ha'] 

3790 if {*kwargs} & {*protected_kw}: 

3791 if loc is not None: 

3792 raise TypeError(f"Specifying 'loc' is disallowed when any of " 

3793 f"its corresponding low level keyword " 

3794 f"arguments ({protected_kw}) are also " 

3795 f"supplied") 

3796 

3797 else: 

3798 loc = (loc if loc is not None 

3799 else mpl.rcParams['yaxis.labellocation']) 

3800 _api.check_in_list(('bottom', 'center', 'top'), loc=loc) 

3801 

3802 y, ha = { 

3803 'bottom': (0, 'left'), 

3804 'center': (0.5, 'center'), 

3805 'top': (1, 'right') 

3806 }[loc] 

3807 kwargs.update(y=y, horizontalalignment=ha) 

3808 

3809 return self.yaxis.set_label_text(ylabel, fontdict, **kwargs) 

3810 

3811 def invert_yaxis(self): 

3812 """ 

3813 Invert the y-axis. 

3814 

3815 See Also 

3816 -------- 

3817 yaxis_inverted 

3818 get_ylim, set_ylim 

3819 get_ybound, set_ybound 

3820 """ 

3821 self.yaxis.set_inverted(not self.yaxis.get_inverted()) 

3822 

3823 yaxis_inverted = _axis_method_wrapper("yaxis", "get_inverted") 

3824 

3825 def get_ybound(self): 

3826 """ 

3827 Return the lower and upper y-axis bounds, in increasing order. 

3828 

3829 See Also 

3830 -------- 

3831 set_ybound 

3832 get_ylim, set_ylim 

3833 invert_yaxis, yaxis_inverted 

3834 """ 

3835 bottom, top = self.get_ylim() 

3836 if bottom < top: 

3837 return bottom, top 

3838 else: 

3839 return top, bottom 

3840 

3841 def set_ybound(self, lower=None, upper=None): 

3842 """ 

3843 Set the lower and upper numerical bounds of the y-axis. 

3844 

3845 This method will honor axis inversion regardless of parameter order. 

3846 It will not change the autoscaling setting (`.get_autoscaley_on()`). 

3847 

3848 Parameters 

3849 ---------- 

3850 lower, upper : float or None 

3851 The lower and upper bounds. If *None*, the respective axis bound 

3852 is not modified. 

3853 

3854 .. ACCEPTS: (lower: float, upper: float) 

3855 

3856 See Also 

3857 -------- 

3858 get_ybound 

3859 get_ylim, set_ylim 

3860 invert_yaxis, yaxis_inverted 

3861 """ 

3862 if upper is None and np.iterable(lower): 

3863 lower, upper = lower 

3864 

3865 old_lower, old_upper = self.get_ybound() 

3866 if lower is None: 

3867 lower = old_lower 

3868 if upper is None: 

3869 upper = old_upper 

3870 

3871 self.set_ylim(sorted((lower, upper), 

3872 reverse=bool(self.yaxis_inverted())), 

3873 auto=None) 

3874 

3875 def get_ylim(self): 

3876 """ 

3877 Return the y-axis view limits. 

3878 

3879 Returns 

3880 ------- 

3881 bottom, top : (float, float) 

3882 The current y-axis limits in data coordinates. 

3883 

3884 See Also 

3885 -------- 

3886 .Axes.set_ylim 

3887 .Axes.set_ybound, .Axes.get_ybound 

3888 .Axes.invert_yaxis, .Axes.yaxis_inverted 

3889 

3890 Notes 

3891 ----- 

3892 The y-axis may be inverted, in which case the *bottom* value 

3893 will be greater than the *top* value. 

3894 """ 

3895 return tuple(self.viewLim.intervaly) 

3896 

3897 def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False, 

3898 ymin=None, ymax=None): 

3899 """ 

3900 Set the y-axis view limits. 

3901 

3902 Parameters 

3903 ---------- 

3904 bottom : float, optional 

3905 The bottom ylim in data coordinates. Passing *None* leaves the 

3906 limit unchanged. 

3907 

3908 The bottom and top ylims may also be passed as the tuple 

3909 (*bottom*, *top*) as the first positional argument (or as 

3910 the *bottom* keyword argument). 

3911 

3912 .. ACCEPTS: (bottom: float, top: float) 

3913 

3914 top : float, optional 

3915 The top ylim in data coordinates. Passing *None* leaves the 

3916 limit unchanged. 

3917 

3918 emit : bool, default: True 

3919 Whether to notify observers of limit change. 

3920 

3921 auto : bool or None, default: False 

3922 Whether to turn on autoscaling of the y-axis. *True* turns on, 

3923 *False* turns off, *None* leaves unchanged. 

3924 

3925 ymin, ymax : float, optional 

3926 They are equivalent to bottom and top respectively, and it is an 

3927 error to pass both *ymin* and *bottom* or *ymax* and *top*. 

3928 

3929 Returns 

3930 ------- 

3931 bottom, top : (float, float) 

3932 The new y-axis limits in data coordinates. 

3933 

3934 See Also 

3935 -------- 

3936 get_ylim 

3937 set_ybound, get_ybound 

3938 invert_yaxis, yaxis_inverted 

3939 

3940 Notes 

3941 ----- 

3942 The *bottom* value may be greater than the *top* value, in which 

3943 case the y-axis values will decrease from *bottom* to *top*. 

3944 

3945 Examples 

3946 -------- 

3947 >>> set_ylim(bottom, top) 

3948 >>> set_ylim((bottom, top)) 

3949 >>> bottom, top = set_ylim(bottom, top) 

3950 

3951 One limit may be left unchanged. 

3952 

3953 >>> set_ylim(top=top_lim) 

3954 

3955 Limits may be passed in reverse order to flip the direction of 

3956 the y-axis. For example, suppose ``y`` represents depth of the 

3957 ocean in m. The y-axis limits might be set like the following 

3958 so 5000 m depth is at the bottom of the plot and the surface, 

3959 0 m, is at the top. 

3960 

3961 >>> set_ylim(5000, 0) 

3962 """ 

3963 if top is None and np.iterable(bottom): 

3964 bottom, top = bottom 

3965 if ymin is not None: 

3966 if bottom is not None: 

3967 raise TypeError("Cannot pass both 'bottom' and 'ymin'") 

3968 bottom = ymin 

3969 if ymax is not None: 

3970 if top is not None: 

3971 raise TypeError("Cannot pass both 'top' and 'ymax'") 

3972 top = ymax 

3973 return self.yaxis._set_lim(bottom, top, emit=emit, auto=auto) 

3974 

3975 get_yscale = _axis_method_wrapper("yaxis", "get_scale") 

3976 set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale") 

3977 get_yticks = _axis_method_wrapper("yaxis", "get_ticklocs") 

3978 set_yticks = _axis_method_wrapper("yaxis", "set_ticks", 

3979 doc_sub={'set_ticks': 'set_yticks'}) 

3980 get_ymajorticklabels = _axis_method_wrapper("yaxis", "get_majorticklabels") 

3981 get_yminorticklabels = _axis_method_wrapper("yaxis", "get_minorticklabels") 

3982 get_yticklabels = _axis_method_wrapper("yaxis", "get_ticklabels") 

3983 set_yticklabels = _axis_method_wrapper( 

3984 "yaxis", "set_ticklabels", 

3985 doc_sub={"Axis.set_ticks": "Axes.set_yticks"}) 

3986 

3987 xaxis_date = _axis_method_wrapper("xaxis", "axis_date") 

3988 yaxis_date = _axis_method_wrapper("yaxis", "axis_date") 

3989 

3990 def format_xdata(self, x): 

3991 """ 

3992 Return *x* formatted as an x-value. 

3993 

3994 This function will use the `.fmt_xdata` attribute if it is not None, 

3995 else will fall back on the xaxis major formatter. 

3996 """ 

3997 return (self.fmt_xdata if self.fmt_xdata is not None 

3998 else self.xaxis.get_major_formatter().format_data_short)(x) 

3999 

4000 def format_ydata(self, y): 

4001 """ 

4002 Return *y* formatted as a y-value. 

4003 

4004 This function will use the `.fmt_ydata` attribute if it is not None, 

4005 else will fall back on the yaxis major formatter. 

4006 """ 

4007 return (self.fmt_ydata if self.fmt_ydata is not None 

4008 else self.yaxis.get_major_formatter().format_data_short)(y) 

4009 

4010 def format_coord(self, x, y): 

4011 """Return a format string formatting the *x*, *y* coordinates.""" 

4012 twins = self._twinned_axes.get_siblings(self) 

4013 if len(twins) == 1: 

4014 return "(x, y) = ({}, {})".format( 

4015 "???" if x is None else self.format_xdata(x), 

4016 "???" if y is None else self.format_ydata(y)) 

4017 screen_xy = self.transData.transform((x, y)) 

4018 xy_strs = [] 

4019 # Retrieve twins in the order of self.figure.axes to sort tied zorders (which is 

4020 # the common case) by the order in which they are added to the figure. 

4021 for ax in sorted(twins, key=attrgetter("zorder")): 

4022 data_x, data_y = ax.transData.inverted().transform(screen_xy) 

4023 xy_strs.append( 

4024 "({}, {})".format(ax.format_xdata(data_x), ax.format_ydata(data_y))) 

4025 return "(x, y) = {}".format(" | ".join(xy_strs)) 

4026 

4027 def minorticks_on(self): 

4028 """ 

4029 Display minor ticks on the Axes. 

4030 

4031 Displaying minor ticks may reduce performance; you may turn them off 

4032 using `minorticks_off()` if drawing speed is a problem. 

4033 """ 

4034 self.xaxis.minorticks_on() 

4035 self.yaxis.minorticks_on() 

4036 

4037 def minorticks_off(self): 

4038 """Remove minor ticks from the Axes.""" 

4039 self.xaxis.minorticks_off() 

4040 self.yaxis.minorticks_off() 

4041 

4042 # Interactive manipulation 

4043 

4044 def can_zoom(self): 

4045 """ 

4046 Return whether this Axes supports the zoom box button functionality. 

4047 """ 

4048 return True 

4049 

4050 def can_pan(self): 

4051 """ 

4052 Return whether this Axes supports any pan/zoom button functionality. 

4053 """ 

4054 return True 

4055 

4056 def get_navigate(self): 

4057 """ 

4058 Get whether the Axes responds to navigation commands. 

4059 """ 

4060 return self._navigate 

4061 

4062 def set_navigate(self, b): 

4063 """ 

4064 Set whether the Axes responds to navigation toolbar commands. 

4065 

4066 Parameters 

4067 ---------- 

4068 b : bool 

4069 

4070 See Also 

4071 -------- 

4072 matplotlib.axes.Axes.set_forward_navigation_events 

4073 

4074 """ 

4075 self._navigate = b 

4076 

4077 def get_navigate_mode(self): 

4078 """ 

4079 Get the navigation toolbar button status: 'PAN', 'ZOOM', or None. 

4080 """ 

4081 return self._navigate_mode 

4082 

4083 def set_navigate_mode(self, b): 

4084 """ 

4085 Set the navigation toolbar button status. 

4086 

4087 .. warning:: 

4088 This is not a user-API function. 

4089 

4090 """ 

4091 self._navigate_mode = b 

4092 

4093 def _get_view(self): 

4094 """ 

4095 Save information required to reproduce the current view. 

4096 

4097 This method is called before a view is changed, such as during a pan or zoom 

4098 initiated by the user. It returns an opaque object that describes the current 

4099 view, in a format compatible with :meth:`_set_view`. 

4100 

4101 The default implementation saves the view limits and autoscaling state. 

4102 Subclasses may override this as needed, as long as :meth:`_set_view` is also 

4103 adjusted accordingly. 

4104 """ 

4105 return { 

4106 "xlim": self.get_xlim(), "autoscalex_on": self.get_autoscalex_on(), 

4107 "ylim": self.get_ylim(), "autoscaley_on": self.get_autoscaley_on(), 

4108 } 

4109 

4110 def _set_view(self, view): 

4111 """ 

4112 Apply a previously saved view. 

4113 

4114 This method is called when restoring a view (with the return value of 

4115 :meth:`_get_view` as argument), such as with the navigation buttons. 

4116 

4117 Subclasses that override :meth:`_get_view` also need to override this method 

4118 accordingly. 

4119 """ 

4120 self.set(**view) 

4121 

4122 def _prepare_view_from_bbox(self, bbox, direction='in', 

4123 mode=None, twinx=False, twiny=False): 

4124 """ 

4125 Helper function to prepare the new bounds from a bbox. 

4126 

4127 This helper function returns the new x and y bounds from the zoom 

4128 bbox. This a convenience method to abstract the bbox logic 

4129 out of the base setter. 

4130 """ 

4131 if len(bbox) == 3: 

4132 xp, yp, scl = bbox # Zooming code 

4133 if scl == 0: # Should not happen 

4134 scl = 1. 

4135 if scl > 1: 

4136 direction = 'in' 

4137 else: 

4138 direction = 'out' 

4139 scl = 1/scl 

4140 # get the limits of the axes 

4141 (xmin, ymin), (xmax, ymax) = self.transData.transform( 

4142 np.transpose([self.get_xlim(), self.get_ylim()])) 

4143 # set the range 

4144 xwidth = xmax - xmin 

4145 ywidth = ymax - ymin 

4146 xcen = (xmax + xmin)*.5 

4147 ycen = (ymax + ymin)*.5 

4148 xzc = (xp*(scl - 1) + xcen)/scl 

4149 yzc = (yp*(scl - 1) + ycen)/scl 

4150 bbox = [xzc - xwidth/2./scl, yzc - ywidth/2./scl, 

4151 xzc + xwidth/2./scl, yzc + ywidth/2./scl] 

4152 elif len(bbox) != 4: 

4153 # should be len 3 or 4 but nothing else 

4154 _api.warn_external( 

4155 "Warning in _set_view_from_bbox: bounding box is not a tuple " 

4156 "of length 3 or 4. Ignoring the view change.") 

4157 return 

4158 

4159 # Original limits. 

4160 xmin0, xmax0 = self.get_xbound() 

4161 ymin0, ymax0 = self.get_ybound() 

4162 # The zoom box in screen coords. 

4163 startx, starty, stopx, stopy = bbox 

4164 # Convert to data coords. 

4165 (startx, starty), (stopx, stopy) = self.transData.inverted().transform( 

4166 [(startx, starty), (stopx, stopy)]) 

4167 # Clip to axes limits. 

4168 xmin, xmax = np.clip(sorted([startx, stopx]), xmin0, xmax0) 

4169 ymin, ymax = np.clip(sorted([starty, stopy]), ymin0, ymax0) 

4170 # Don't double-zoom twinned axes or if zooming only the other axis. 

4171 if twinx or mode == "y": 

4172 xmin, xmax = xmin0, xmax0 

4173 if twiny or mode == "x": 

4174 ymin, ymax = ymin0, ymax0 

4175 

4176 if direction == "in": 

4177 new_xbound = xmin, xmax 

4178 new_ybound = ymin, ymax 

4179 

4180 elif direction == "out": 

4181 x_trf = self.xaxis.get_transform() 

4182 sxmin0, sxmax0, sxmin, sxmax = x_trf.transform( 

4183 [xmin0, xmax0, xmin, xmax]) # To screen space. 

4184 factor = (sxmax0 - sxmin0) / (sxmax - sxmin) # Unzoom factor. 

4185 # Move original bounds away by 

4186 # (factor) x (distance between unzoom box and Axes bbox). 

4187 sxmin1 = sxmin0 - factor * (sxmin - sxmin0) 

4188 sxmax1 = sxmax0 + factor * (sxmax0 - sxmax) 

4189 # And back to data space. 

4190 new_xbound = x_trf.inverted().transform([sxmin1, sxmax1]) 

4191 

4192 y_trf = self.yaxis.get_transform() 

4193 symin0, symax0, symin, symax = y_trf.transform( 

4194 [ymin0, ymax0, ymin, ymax]) 

4195 factor = (symax0 - symin0) / (symax - symin) 

4196 symin1 = symin0 - factor * (symin - symin0) 

4197 symax1 = symax0 + factor * (symax0 - symax) 

4198 new_ybound = y_trf.inverted().transform([symin1, symax1]) 

4199 

4200 return new_xbound, new_ybound 

4201 

4202 def _set_view_from_bbox(self, bbox, direction='in', 

4203 mode=None, twinx=False, twiny=False): 

4204 """ 

4205 Update view from a selection bbox. 

4206 

4207 .. note:: 

4208 

4209 Intended to be overridden by new projection types, but if not, the 

4210 default implementation sets the view limits to the bbox directly. 

4211 

4212 Parameters 

4213 ---------- 

4214 bbox : 4-tuple or 3 tuple 

4215 * If bbox is a 4 tuple, it is the selected bounding box limits, 

4216 in *display* coordinates. 

4217 * If bbox is a 3 tuple, it is an (xp, yp, scl) triple, where 

4218 (xp, yp) is the center of zooming and scl the scale factor to 

4219 zoom by. 

4220 

4221 direction : str 

4222 The direction to apply the bounding box. 

4223 * `'in'` - The bounding box describes the view directly, i.e., 

4224 it zooms in. 

4225 * `'out'` - The bounding box describes the size to make the 

4226 existing view, i.e., it zooms out. 

4227 

4228 mode : str or None 

4229 The selection mode, whether to apply the bounding box in only the 

4230 `'x'` direction, `'y'` direction or both (`None`). 

4231 

4232 twinx : bool 

4233 Whether this axis is twinned in the *x*-direction. 

4234 

4235 twiny : bool 

4236 Whether this axis is twinned in the *y*-direction. 

4237 """ 

4238 new_xbound, new_ybound = self._prepare_view_from_bbox( 

4239 bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny) 

4240 if not twinx and mode != "y": 

4241 self.set_xbound(new_xbound) 

4242 self.set_autoscalex_on(False) 

4243 if not twiny and mode != "x": 

4244 self.set_ybound(new_ybound) 

4245 self.set_autoscaley_on(False) 

4246 

4247 def start_pan(self, x, y, button): 

4248 """ 

4249 Called when a pan operation has started. 

4250 

4251 Parameters 

4252 ---------- 

4253 x, y : float 

4254 The mouse coordinates in display coords. 

4255 button : `.MouseButton` 

4256 The pressed mouse button. 

4257 

4258 Notes 

4259 ----- 

4260 This is intended to be overridden by new projection types. 

4261 """ 

4262 self._pan_start = types.SimpleNamespace( 

4263 lim=self.viewLim.frozen(), 

4264 trans=self.transData.frozen(), 

4265 trans_inverse=self.transData.inverted().frozen(), 

4266 bbox=self.bbox.frozen(), 

4267 x=x, 

4268 y=y) 

4269 

4270 def end_pan(self): 

4271 """ 

4272 Called when a pan operation completes (when the mouse button is up.) 

4273 

4274 Notes 

4275 ----- 

4276 This is intended to be overridden by new projection types. 

4277 """ 

4278 del self._pan_start 

4279 

4280 def _get_pan_points(self, button, key, x, y): 

4281 """ 

4282 Helper function to return the new points after a pan. 

4283 

4284 This helper function returns the points on the axis after a pan has 

4285 occurred. This is a convenience method to abstract the pan logic 

4286 out of the base setter. 

4287 """ 

4288 def format_deltas(key, dx, dy): 

4289 if key == 'control': 

4290 if abs(dx) > abs(dy): 

4291 dy = dx 

4292 else: 

4293 dx = dy 

4294 elif key == 'x': 

4295 dy = 0 

4296 elif key == 'y': 

4297 dx = 0 

4298 elif key == 'shift': 

4299 if 2 * abs(dx) < abs(dy): 

4300 dx = 0 

4301 elif 2 * abs(dy) < abs(dx): 

4302 dy = 0 

4303 elif abs(dx) > abs(dy): 

4304 dy = dy / abs(dy) * abs(dx) 

4305 else: 

4306 dx = dx / abs(dx) * abs(dy) 

4307 return dx, dy 

4308 

4309 p = self._pan_start 

4310 dx = x - p.x 

4311 dy = y - p.y 

4312 if dx == dy == 0: 

4313 return 

4314 if button == 1: 

4315 dx, dy = format_deltas(key, dx, dy) 

4316 result = p.bbox.translated(-dx, -dy).transformed(p.trans_inverse) 

4317 elif button == 3: 

4318 try: 

4319 dx = -dx / self.bbox.width 

4320 dy = -dy / self.bbox.height 

4321 dx, dy = format_deltas(key, dx, dy) 

4322 if self.get_aspect() != 'auto': 

4323 dx = dy = 0.5 * (dx + dy) 

4324 alpha = np.power(10.0, (dx, dy)) 

4325 start = np.array([p.x, p.y]) 

4326 oldpoints = p.lim.transformed(p.trans) 

4327 newpoints = start + alpha * (oldpoints - start) 

4328 result = (mtransforms.Bbox(newpoints) 

4329 .transformed(p.trans_inverse)) 

4330 except OverflowError: 

4331 _api.warn_external('Overflow while panning') 

4332 return 

4333 else: 

4334 return 

4335 

4336 valid = np.isfinite(result.transformed(p.trans)) 

4337 points = result.get_points().astype(object) 

4338 # Just ignore invalid limits (typically, underflow in log-scale). 

4339 points[~valid] = None 

4340 return points 

4341 

4342 def drag_pan(self, button, key, x, y): 

4343 """ 

4344 Called when the mouse moves during a pan operation. 

4345 

4346 Parameters 

4347 ---------- 

4348 button : `.MouseButton` 

4349 The pressed mouse button. 

4350 key : str or None 

4351 The pressed key, if any. 

4352 x, y : float 

4353 The mouse coordinates in display coords. 

4354 

4355 Notes 

4356 ----- 

4357 This is intended to be overridden by new projection types. 

4358 """ 

4359 points = self._get_pan_points(button, key, x, y) 

4360 if points is not None: 

4361 self.set_xlim(points[:, 0]) 

4362 self.set_ylim(points[:, 1]) 

4363 

4364 def get_children(self): 

4365 # docstring inherited. 

4366 return [ 

4367 *self._children, 

4368 *self.spines.values(), 

4369 *self._axis_map.values(), 

4370 self.title, self._left_title, self._right_title, 

4371 *self.child_axes, 

4372 *([self.legend_] if self.legend_ is not None else []), 

4373 self.patch, 

4374 ] 

4375 

4376 def contains(self, mouseevent): 

4377 # docstring inherited. 

4378 return self.patch.contains(mouseevent) 

4379 

4380 def contains_point(self, point): 

4381 """ 

4382 Return whether *point* (pair of pixel coordinates) is inside the Axes 

4383 patch. 

4384 """ 

4385 return self.patch.contains_point(point, radius=1.0) 

4386 

4387 def get_default_bbox_extra_artists(self): 

4388 """ 

4389 Return a default list of artists that are used for the bounding box 

4390 calculation. 

4391 

4392 Artists are excluded either by not being visible or 

4393 ``artist.set_in_layout(False)``. 

4394 """ 

4395 

4396 artists = self.get_children() 

4397 

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

4399 # axis tight bboxes are calculated separately inside 

4400 # Axes.get_tightbbox() using for_layout_only=True 

4401 artists.remove(axis) 

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

4403 # don't do bbox on spines if frame not on. 

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

4405 artists.remove(spine) 

4406 

4407 artists.remove(self.title) 

4408 artists.remove(self._left_title) 

4409 artists.remove(self._right_title) 

4410 

4411 # always include types that do not internally implement clipping 

4412 # to Axes. may have clip_on set to True and clip_box equivalent 

4413 # to ax.bbox but then ignore these properties during draws. 

4414 noclip = (_AxesBase, maxis.Axis, 

4415 offsetbox.AnnotationBbox, offsetbox.OffsetBox) 

4416 return [a for a in artists if a.get_visible() and a.get_in_layout() 

4417 and (isinstance(a, noclip) or not a._fully_clipped_to_axes())] 

4418 

4419 @_api.make_keyword_only("3.8", "call_axes_locator") 

4420 def get_tightbbox(self, renderer=None, call_axes_locator=True, 

4421 bbox_extra_artists=None, *, for_layout_only=False): 

4422 """ 

4423 Return the tight bounding box of the Axes, including axis and their 

4424 decorators (xlabel, title, etc). 

4425 

4426 Artists that have ``artist.set_in_layout(False)`` are not included 

4427 in the bbox. 

4428 

4429 Parameters 

4430 ---------- 

4431 renderer : `.RendererBase` subclass 

4432 renderer that will be used to draw the figures (i.e. 

4433 ``fig.canvas.get_renderer()``) 

4434 

4435 bbox_extra_artists : list of `.Artist` or ``None`` 

4436 List of artists to include in the tight bounding box. If 

4437 ``None`` (default), then all artist children of the Axes are 

4438 included in the tight bounding box. 

4439 

4440 call_axes_locator : bool, default: True 

4441 If *call_axes_locator* is ``False``, it does not call the 

4442 ``_axes_locator`` attribute, which is necessary to get the correct 

4443 bounding box. ``call_axes_locator=False`` can be used if the 

4444 caller is only interested in the relative size of the tightbbox 

4445 compared to the Axes bbox. 

4446 

4447 for_layout_only : default: False 

4448 The bounding box will *not* include the x-extent of the title and 

4449 the xlabel, or the y-extent of the ylabel. 

4450 

4451 Returns 

4452 ------- 

4453 `.BboxBase` 

4454 Bounding box in figure pixel coordinates. 

4455 

4456 See Also 

4457 -------- 

4458 matplotlib.axes.Axes.get_window_extent 

4459 matplotlib.axis.Axis.get_tightbbox 

4460 matplotlib.spines.Spine.get_window_extent 

4461 """ 

4462 

4463 bb = [] 

4464 if renderer is None: 

4465 renderer = self.figure._get_renderer() 

4466 

4467 if not self.get_visible(): 

4468 return None 

4469 

4470 locator = self.get_axes_locator() 

4471 self.apply_aspect( 

4472 locator(self, renderer) if locator and call_axes_locator else None) 

4473 

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

4475 if self.axison and axis.get_visible(): 

4476 ba = martist._get_tightbbox_for_layout_only(axis, renderer) 

4477 if ba: 

4478 bb.append(ba) 

4479 self._update_title_position(renderer) 

4480 axbbox = self.get_window_extent(renderer) 

4481 bb.append(axbbox) 

4482 

4483 for title in [self.title, self._left_title, self._right_title]: 

4484 if title.get_visible(): 

4485 bt = title.get_window_extent(renderer) 

4486 if for_layout_only and bt.width > 0: 

4487 # make the title bbox 1 pixel wide so its width 

4488 # is not accounted for in bbox calculations in 

4489 # tight/constrained_layout 

4490 bt.x0 = (bt.x0 + bt.x1) / 2 - 0.5 

4491 bt.x1 = bt.x0 + 1.0 

4492 bb.append(bt) 

4493 

4494 bbox_artists = bbox_extra_artists 

4495 if bbox_artists is None: 

4496 bbox_artists = self.get_default_bbox_extra_artists() 

4497 

4498 for a in bbox_artists: 

4499 bbox = a.get_tightbbox(renderer) 

4500 if (bbox is not None 

4501 and 0 < bbox.width < np.inf 

4502 and 0 < bbox.height < np.inf): 

4503 bb.append(bbox) 

4504 return mtransforms.Bbox.union( 

4505 [b for b in bb if b.width != 0 or b.height != 0]) 

4506 

4507 def _make_twin_axes(self, *args, **kwargs): 

4508 """Make a twinx Axes of self. This is used for twinx and twiny.""" 

4509 if 'sharex' in kwargs and 'sharey' in kwargs: 

4510 # The following line is added in v2.2 to avoid breaking Seaborn, 

4511 # which currently uses this internal API. 

4512 if kwargs["sharex"] is not self and kwargs["sharey"] is not self: 

4513 raise ValueError("Twinned Axes may share only one axis") 

4514 ss = self.get_subplotspec() 

4515 if ss: 

4516 twin = self.figure.add_subplot(ss, *args, **kwargs) 

4517 else: 

4518 twin = self.figure.add_axes( 

4519 self.get_position(True), *args, **kwargs, 

4520 axes_locator=_TransformedBoundsLocator( 

4521 [0, 0, 1, 1], self.transAxes)) 

4522 self.set_adjustable('datalim') 

4523 twin.set_adjustable('datalim') 

4524 twin.set_zorder(self.zorder) 

4525 

4526 self._twinned_axes.join(self, twin) 

4527 return twin 

4528 

4529 def twinx(self): 

4530 """ 

4531 Create a twin Axes sharing the xaxis. 

4532 

4533 Create a new Axes with an invisible x-axis and an independent 

4534 y-axis positioned opposite to the original one (i.e. at right). The 

4535 x-axis autoscale setting will be inherited from the original 

4536 Axes. To ensure that the tick marks of both y-axes align, see 

4537 `~matplotlib.ticker.LinearLocator`. 

4538 

4539 Returns 

4540 ------- 

4541 Axes 

4542 The newly created Axes instance 

4543 

4544 Notes 

4545 ----- 

4546 For those who are 'picking' artists while using twinx, pick 

4547 events are only called for the artists in the top-most Axes. 

4548 """ 

4549 ax2 = self._make_twin_axes(sharex=self) 

4550 ax2.yaxis.tick_right() 

4551 ax2.yaxis.set_label_position('right') 

4552 ax2.yaxis.set_offset_position('right') 

4553 ax2.set_autoscalex_on(self.get_autoscalex_on()) 

4554 self.yaxis.tick_left() 

4555 ax2.xaxis.set_visible(False) 

4556 ax2.patch.set_visible(False) 

4557 ax2.xaxis.units = self.xaxis.units 

4558 return ax2 

4559 

4560 def twiny(self): 

4561 """ 

4562 Create a twin Axes sharing the yaxis. 

4563 

4564 Create a new Axes with an invisible y-axis and an independent 

4565 x-axis positioned opposite to the original one (i.e. at top). The 

4566 y-axis autoscale setting will be inherited from the original Axes. 

4567 To ensure that the tick marks of both x-axes align, see 

4568 `~matplotlib.ticker.LinearLocator`. 

4569 

4570 Returns 

4571 ------- 

4572 Axes 

4573 The newly created Axes instance 

4574 

4575 Notes 

4576 ----- 

4577 For those who are 'picking' artists while using twiny, pick 

4578 events are only called for the artists in the top-most Axes. 

4579 """ 

4580 ax2 = self._make_twin_axes(sharey=self) 

4581 ax2.xaxis.tick_top() 

4582 ax2.xaxis.set_label_position('top') 

4583 ax2.set_autoscaley_on(self.get_autoscaley_on()) 

4584 self.xaxis.tick_bottom() 

4585 ax2.yaxis.set_visible(False) 

4586 ax2.patch.set_visible(False) 

4587 ax2.yaxis.units = self.yaxis.units 

4588 return ax2 

4589 

4590 def get_shared_x_axes(self): 

4591 """Return an immutable view on the shared x-axes Grouper.""" 

4592 return cbook.GrouperView(self._shared_axes["x"]) 

4593 

4594 def get_shared_y_axes(self): 

4595 """Return an immutable view on the shared y-axes Grouper.""" 

4596 return cbook.GrouperView(self._shared_axes["y"]) 

4597 

4598 def label_outer(self, remove_inner_ticks=False): 

4599 """ 

4600 Only show "outer" labels and tick labels. 

4601 

4602 x-labels are only kept for subplots on the last row (or first row, if 

4603 labels are on the top side); y-labels only for subplots on the first 

4604 column (or last column, if labels are on the right side). 

4605 

4606 Parameters 

4607 ---------- 

4608 remove_inner_ticks : bool, default: False 

4609 If True, remove the inner ticks as well (not only tick labels). 

4610 

4611 .. versionadded:: 3.8 

4612 """ 

4613 self._label_outer_xaxis(skip_non_rectangular_axes=False, 

4614 remove_inner_ticks=remove_inner_ticks) 

4615 self._label_outer_yaxis(skip_non_rectangular_axes=False, 

4616 remove_inner_ticks=remove_inner_ticks) 

4617 

4618 def _label_outer_xaxis(self, *, skip_non_rectangular_axes, 

4619 remove_inner_ticks=False): 

4620 # see documentation in label_outer. 

4621 if skip_non_rectangular_axes and not isinstance(self.patch, 

4622 mpl.patches.Rectangle): 

4623 return 

4624 ss = self.get_subplotspec() 

4625 if not ss: 

4626 return 

4627 label_position = self.xaxis.get_label_position() 

4628 if not ss.is_first_row(): # Remove top label/ticklabels/offsettext. 

4629 if label_position == "top": 

4630 self.set_xlabel("") 

4631 top_kw = {'top': False} if remove_inner_ticks else {} 

4632 self.xaxis.set_tick_params( 

4633 which="both", labeltop=False, **top_kw) 

4634 if self.xaxis.offsetText.get_position()[1] == 1: 

4635 self.xaxis.offsetText.set_visible(False) 

4636 if not ss.is_last_row(): # Remove bottom label/ticklabels/offsettext. 

4637 if label_position == "bottom": 

4638 self.set_xlabel("") 

4639 bottom_kw = {'bottom': False} if remove_inner_ticks else {} 

4640 self.xaxis.set_tick_params( 

4641 which="both", labelbottom=False, **bottom_kw) 

4642 if self.xaxis.offsetText.get_position()[1] == 0: 

4643 self.xaxis.offsetText.set_visible(False) 

4644 

4645 def _label_outer_yaxis(self, *, skip_non_rectangular_axes, 

4646 remove_inner_ticks=False): 

4647 # see documentation in label_outer. 

4648 if skip_non_rectangular_axes and not isinstance(self.patch, 

4649 mpl.patches.Rectangle): 

4650 return 

4651 ss = self.get_subplotspec() 

4652 if not ss: 

4653 return 

4654 label_position = self.yaxis.get_label_position() 

4655 if not ss.is_first_col(): # Remove left label/ticklabels/offsettext. 

4656 if label_position == "left": 

4657 self.set_ylabel("") 

4658 left_kw = {'left': False} if remove_inner_ticks else {} 

4659 self.yaxis.set_tick_params( 

4660 which="both", labelleft=False, **left_kw) 

4661 if self.yaxis.offsetText.get_position()[0] == 0: 

4662 self.yaxis.offsetText.set_visible(False) 

4663 if not ss.is_last_col(): # Remove right label/ticklabels/offsettext. 

4664 if label_position == "right": 

4665 self.set_ylabel("") 

4666 right_kw = {'right': False} if remove_inner_ticks else {} 

4667 self.yaxis.set_tick_params( 

4668 which="both", labelright=False, **right_kw) 

4669 if self.yaxis.offsetText.get_position()[0] == 1: 

4670 self.yaxis.offsetText.set_visible(False) 

4671 

4672 def set_forward_navigation_events(self, forward): 

4673 """ 

4674 Set how pan/zoom events are forwarded to Axes below this one. 

4675 

4676 Parameters 

4677 ---------- 

4678 forward : bool or "auto" 

4679 Possible values: 

4680 

4681 - True: Forward events to other axes with lower or equal zorder. 

4682 - False: Events are only executed on this axes. 

4683 - "auto": Default behaviour (*True* for axes with an invisible 

4684 patch and *False* otherwise) 

4685 

4686 See Also 

4687 -------- 

4688 matplotlib.axes.Axes.set_navigate 

4689 

4690 """ 

4691 self._forward_navigation_events = forward 

4692 

4693 def get_forward_navigation_events(self): 

4694 """Get how pan/zoom events are forwarded to Axes below this one.""" 

4695 return self._forward_navigation_events 

4696 

4697 

4698def _draw_rasterized(figure, artists, renderer): 

4699 """ 

4700 A helper function for rasterizing the list of artists. 

4701 

4702 The bookkeeping to track if we are or are not in rasterizing mode 

4703 with the mixed-mode backends is relatively complicated and is now 

4704 handled in the matplotlib.artist.allow_rasterization decorator. 

4705 

4706 This helper defines the absolute minimum methods and attributes on a 

4707 shim class to be compatible with that decorator and then uses it to 

4708 rasterize the list of artists. 

4709 

4710 This is maybe too-clever, but allows us to reuse the same code that is 

4711 used on normal artists to participate in the "are we rasterizing" 

4712 accounting. 

4713 

4714 Please do not use this outside of the "rasterize below a given zorder" 

4715 functionality of Axes. 

4716 

4717 Parameters 

4718 ---------- 

4719 figure : matplotlib.figure.Figure 

4720 The figure all of the artists belong to (not checked). We need this 

4721 because we can at the figure level suppress composition and insert each 

4722 rasterized artist as its own image. 

4723 

4724 artists : List[matplotlib.artist.Artist] 

4725 The list of Artists to be rasterized. These are assumed to all 

4726 be in the same Figure. 

4727 

4728 renderer : matplotlib.backendbases.RendererBase 

4729 The currently active renderer 

4730 

4731 Returns 

4732 ------- 

4733 None 

4734 

4735 """ 

4736 class _MinimalArtist: 

4737 def get_rasterized(self): 

4738 return True 

4739 

4740 def get_agg_filter(self): 

4741 return None 

4742 

4743 def __init__(self, figure, artists): 

4744 self.figure = figure 

4745 self.artists = artists 

4746 

4747 @martist.allow_rasterization 

4748 def draw(self, renderer): 

4749 for a in self.artists: 

4750 a.draw(renderer) 

4751 

4752 return _MinimalArtist(figure, artists).draw(renderer)