Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/backend_bases.py: 24%

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

1265 statements  

1""" 

2Abstract base classes define the primitives that renderers and 

3graphics contexts must implement to serve as a Matplotlib backend. 

4 

5`RendererBase` 

6 An abstract base class to handle drawing/rendering operations. 

7 

8`FigureCanvasBase` 

9 The abstraction layer that separates the `.Figure` from the backend 

10 specific details like a user interface drawing area. 

11 

12`GraphicsContextBase` 

13 An abstract base class that provides color, line styles, etc. 

14 

15`Event` 

16 The base class for all of the Matplotlib event handling. Derived classes 

17 such as `KeyEvent` and `MouseEvent` store the meta data like keys and 

18 buttons pressed, x and y locations in pixel and `~.axes.Axes` coordinates. 

19 

20`ShowBase` 

21 The base class for the ``Show`` class of each interactive backend; the 

22 'show' callable is then set to ``Show.__call__``. 

23 

24`ToolContainerBase` 

25 The base class for the Toolbar class of each interactive backend. 

26""" 

27 

28from collections import namedtuple 

29from contextlib import ExitStack, contextmanager, nullcontext 

30from enum import Enum, IntEnum 

31import functools 

32import importlib 

33import inspect 

34import io 

35import itertools 

36import logging 

37import os 

38import pathlib 

39import signal 

40import socket 

41import sys 

42import time 

43import weakref 

44from weakref import WeakKeyDictionary 

45 

46import numpy as np 

47 

48import matplotlib as mpl 

49from matplotlib import ( 

50 _api, backend_tools as tools, cbook, colors, _docstring, text, 

51 _tight_bbox, transforms, widgets, is_interactive, rcParams) 

52from matplotlib._pylab_helpers import Gcf 

53from matplotlib.backend_managers import ToolManager 

54from matplotlib.cbook import _setattr_cm 

55from matplotlib.layout_engine import ConstrainedLayoutEngine 

56from matplotlib.path import Path 

57from matplotlib.texmanager import TexManager 

58from matplotlib.transforms import Affine2D 

59from matplotlib._enums import JoinStyle, CapStyle 

60 

61 

62_log = logging.getLogger(__name__) 

63_default_filetypes = { 

64 'eps': 'Encapsulated Postscript', 

65 'jpg': 'Joint Photographic Experts Group', 

66 'jpeg': 'Joint Photographic Experts Group', 

67 'pdf': 'Portable Document Format', 

68 'pgf': 'PGF code for LaTeX', 

69 'png': 'Portable Network Graphics', 

70 'ps': 'Postscript', 

71 'raw': 'Raw RGBA bitmap', 

72 'rgba': 'Raw RGBA bitmap', 

73 'svg': 'Scalable Vector Graphics', 

74 'svgz': 'Scalable Vector Graphics', 

75 'tif': 'Tagged Image File Format', 

76 'tiff': 'Tagged Image File Format', 

77 'webp': 'WebP Image Format', 

78} 

79_default_backends = { 

80 'eps': 'matplotlib.backends.backend_ps', 

81 'jpg': 'matplotlib.backends.backend_agg', 

82 'jpeg': 'matplotlib.backends.backend_agg', 

83 'pdf': 'matplotlib.backends.backend_pdf', 

84 'pgf': 'matplotlib.backends.backend_pgf', 

85 'png': 'matplotlib.backends.backend_agg', 

86 'ps': 'matplotlib.backends.backend_ps', 

87 'raw': 'matplotlib.backends.backend_agg', 

88 'rgba': 'matplotlib.backends.backend_agg', 

89 'svg': 'matplotlib.backends.backend_svg', 

90 'svgz': 'matplotlib.backends.backend_svg', 

91 'tif': 'matplotlib.backends.backend_agg', 

92 'tiff': 'matplotlib.backends.backend_agg', 

93 'webp': 'matplotlib.backends.backend_agg', 

94} 

95 

96 

97def register_backend(format, backend, description=None): 

98 """ 

99 Register a backend for saving to a given file format. 

100 

101 Parameters 

102 ---------- 

103 format : str 

104 File extension 

105 backend : module string or canvas class 

106 Backend for handling file output 

107 description : str, default: "" 

108 Description of the file type. 

109 """ 

110 if description is None: 

111 description = '' 

112 _default_backends[format] = backend 

113 _default_filetypes[format] = description 

114 

115 

116def get_registered_canvas_class(format): 

117 """ 

118 Return the registered default canvas for given file format. 

119 Handles deferred import of required backend. 

120 """ 

121 if format not in _default_backends: 

122 return None 

123 backend_class = _default_backends[format] 

124 if isinstance(backend_class, str): 

125 backend_class = importlib.import_module(backend_class).FigureCanvas 

126 _default_backends[format] = backend_class 

127 return backend_class 

128 

129 

130class RendererBase: 

131 """ 

132 An abstract base class to handle drawing/rendering operations. 

133 

134 The following methods must be implemented in the backend for full 

135 functionality (though just implementing `draw_path` alone would give a 

136 highly capable backend): 

137 

138 * `draw_path` 

139 * `draw_image` 

140 * `draw_gouraud_triangles` 

141 

142 The following methods *should* be implemented in the backend for 

143 optimization reasons: 

144 

145 * `draw_text` 

146 * `draw_markers` 

147 * `draw_path_collection` 

148 * `draw_quad_mesh` 

149 """ 

150 def __init__(self): 

151 super().__init__() 

152 self._texmanager = None 

153 self._text2path = text.TextToPath() 

154 self._raster_depth = 0 

155 self._rasterizing = False 

156 

157 def open_group(self, s, gid=None): 

158 """ 

159 Open a grouping element with label *s* and *gid* (if set) as id. 

160 

161 Only used by the SVG renderer. 

162 """ 

163 

164 def close_group(self, s): 

165 """ 

166 Close a grouping element with label *s*. 

167 

168 Only used by the SVG renderer. 

169 """ 

170 

171 def draw_path(self, gc, path, transform, rgbFace=None): 

172 """Draw a `~.path.Path` instance using the given affine transform.""" 

173 raise NotImplementedError 

174 

175 def draw_markers(self, gc, marker_path, marker_trans, path, 

176 trans, rgbFace=None): 

177 """ 

178 Draw a marker at each of *path*'s vertices (excluding control points). 

179 

180 The base (fallback) implementation makes multiple calls to `draw_path`. 

181 Backends may want to override this method in order to draw the marker 

182 only once and reuse it multiple times. 

183 

184 Parameters 

185 ---------- 

186 gc : `.GraphicsContextBase` 

187 The graphics context. 

188 marker_path : `~matplotlib.path.Path` 

189 The path for the marker. 

190 marker_trans : `~matplotlib.transforms.Transform` 

191 An affine transform applied to the marker. 

192 path : `~matplotlib.path.Path` 

193 The locations to draw the markers. 

194 trans : `~matplotlib.transforms.Transform` 

195 An affine transform applied to the path. 

196 rgbFace : :mpltype:`color`, optional 

197 """ 

198 for vertices, codes in path.iter_segments(trans, simplify=False): 

199 if len(vertices): 

200 x, y = vertices[-2:] 

201 self.draw_path(gc, marker_path, 

202 marker_trans + 

203 transforms.Affine2D().translate(x, y), 

204 rgbFace) 

205 

206 def draw_path_collection(self, gc, master_transform, paths, all_transforms, 

207 offsets, offset_trans, facecolors, edgecolors, 

208 linewidths, linestyles, antialiaseds, urls, 

209 offset_position): 

210 """ 

211 Draw a collection of *paths*. 

212 

213 Each path is first transformed by the corresponding entry 

214 in *all_transforms* (a list of (3, 3) matrices) and then by 

215 *master_transform*. They are then translated by the corresponding 

216 entry in *offsets*, which has been first transformed by *offset_trans*. 

217 

218 *facecolors*, *edgecolors*, *linewidths*, *linestyles*, and 

219 *antialiased* are lists that set the corresponding properties. 

220 

221 *offset_position* is unused now, but the argument is kept for 

222 backwards compatibility. 

223 

224 The base (fallback) implementation makes multiple calls to `draw_path`. 

225 Backends may want to override this in order to render each set of 

226 path data only once, and then reference that path multiple times with 

227 the different offsets, colors, styles etc. The generator methods 

228 `_iter_collection_raw_paths` and `_iter_collection` are provided to 

229 help with (and standardize) the implementation across backends. It 

230 is highly recommended to use those generators, so that changes to the 

231 behavior of `draw_path_collection` can be made globally. 

232 """ 

233 path_ids = self._iter_collection_raw_paths(master_transform, 

234 paths, all_transforms) 

235 

236 for xo, yo, path_id, gc0, rgbFace in self._iter_collection( 

237 gc, list(path_ids), offsets, offset_trans, 

238 facecolors, edgecolors, linewidths, linestyles, 

239 antialiaseds, urls, offset_position): 

240 path, transform = path_id 

241 # Only apply another translation if we have an offset, else we 

242 # reuse the initial transform. 

243 if xo != 0 or yo != 0: 

244 # The transformation can be used by multiple paths. Since 

245 # translate is a inplace operation, we need to copy the 

246 # transformation by .frozen() before applying the translation. 

247 transform = transform.frozen() 

248 transform.translate(xo, yo) 

249 self.draw_path(gc0, path, transform, rgbFace) 

250 

251 def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight, 

252 coordinates, offsets, offsetTrans, facecolors, 

253 antialiased, edgecolors): 

254 """ 

255 Draw a quadmesh. 

256 

257 The base (fallback) implementation converts the quadmesh to paths and 

258 then calls `draw_path_collection`. 

259 """ 

260 

261 from matplotlib.collections import QuadMesh 

262 paths = QuadMesh._convert_mesh_to_paths(coordinates) 

263 

264 if edgecolors is None: 

265 edgecolors = facecolors 

266 linewidths = np.array([gc.get_linewidth()], float) 

267 

268 return self.draw_path_collection( 

269 gc, master_transform, paths, [], offsets, offsetTrans, facecolors, 

270 edgecolors, linewidths, [], [antialiased], [None], 'screen') 

271 

272 def draw_gouraud_triangles(self, gc, triangles_array, colors_array, 

273 transform): 

274 """ 

275 Draw a series of Gouraud triangles. 

276 

277 Parameters 

278 ---------- 

279 gc : `.GraphicsContextBase` 

280 The graphics context. 

281 triangles_array : (N, 3, 2) array-like 

282 Array of *N* (x, y) points for the triangles. 

283 colors_array : (N, 3, 4) array-like 

284 Array of *N* RGBA colors for each point of the triangles. 

285 transform : `~matplotlib.transforms.Transform` 

286 An affine transform to apply to the points. 

287 """ 

288 raise NotImplementedError 

289 

290 def _iter_collection_raw_paths(self, master_transform, paths, 

291 all_transforms): 

292 """ 

293 Helper method (along with `_iter_collection`) to implement 

294 `draw_path_collection` in a memory-efficient manner. 

295 

296 This method yields all of the base path/transform combinations, given a 

297 master transform, a list of paths and list of transforms. 

298 

299 The arguments should be exactly what is passed in to 

300 `draw_path_collection`. 

301 

302 The backend should take each yielded path and transform and create an 

303 object that can be referenced (reused) later. 

304 """ 

305 Npaths = len(paths) 

306 Ntransforms = len(all_transforms) 

307 N = max(Npaths, Ntransforms) 

308 

309 if Npaths == 0: 

310 return 

311 

312 transform = transforms.IdentityTransform() 

313 for i in range(N): 

314 path = paths[i % Npaths] 

315 if Ntransforms: 

316 transform = Affine2D(all_transforms[i % Ntransforms]) 

317 yield path, transform + master_transform 

318 

319 def _iter_collection_uses_per_path(self, paths, all_transforms, 

320 offsets, facecolors, edgecolors): 

321 """ 

322 Compute how many times each raw path object returned by 

323 `_iter_collection_raw_paths` would be used when calling 

324 `_iter_collection`. This is intended for the backend to decide 

325 on the tradeoff between using the paths in-line and storing 

326 them once and reusing. Rounds up in case the number of uses 

327 is not the same for every path. 

328 """ 

329 Npaths = len(paths) 

330 if Npaths == 0 or len(facecolors) == len(edgecolors) == 0: 

331 return 0 

332 Npath_ids = max(Npaths, len(all_transforms)) 

333 N = max(Npath_ids, len(offsets)) 

334 return (N + Npath_ids - 1) // Npath_ids 

335 

336 def _iter_collection(self, gc, path_ids, offsets, offset_trans, facecolors, 

337 edgecolors, linewidths, linestyles, 

338 antialiaseds, urls, offset_position): 

339 """ 

340 Helper method (along with `_iter_collection_raw_paths`) to implement 

341 `draw_path_collection` in a memory-efficient manner. 

342 

343 This method yields all of the path, offset and graphics context 

344 combinations to draw the path collection. The caller should already 

345 have looped over the results of `_iter_collection_raw_paths` to draw 

346 this collection. 

347 

348 The arguments should be the same as that passed into 

349 `draw_path_collection`, with the exception of *path_ids*, which is a 

350 list of arbitrary objects that the backend will use to reference one of 

351 the paths created in the `_iter_collection_raw_paths` stage. 

352 

353 Each yielded result is of the form:: 

354 

355 xo, yo, path_id, gc, rgbFace 

356 

357 where *xo*, *yo* is an offset; *path_id* is one of the elements of 

358 *path_ids*; *gc* is a graphics context and *rgbFace* is a color to 

359 use for filling the path. 

360 """ 

361 Npaths = len(path_ids) 

362 Noffsets = len(offsets) 

363 N = max(Npaths, Noffsets) 

364 Nfacecolors = len(facecolors) 

365 Nedgecolors = len(edgecolors) 

366 Nlinewidths = len(linewidths) 

367 Nlinestyles = len(linestyles) 

368 Nurls = len(urls) 

369 

370 if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0: 

371 return 

372 

373 gc0 = self.new_gc() 

374 gc0.copy_properties(gc) 

375 

376 def cycle_or_default(seq, default=None): 

377 # Cycle over *seq* if it is not empty; else always yield *default*. 

378 return (itertools.cycle(seq) if len(seq) 

379 else itertools.repeat(default)) 

380 

381 pathids = cycle_or_default(path_ids) 

382 toffsets = cycle_or_default(offset_trans.transform(offsets), (0, 0)) 

383 fcs = cycle_or_default(facecolors) 

384 ecs = cycle_or_default(edgecolors) 

385 lws = cycle_or_default(linewidths) 

386 lss = cycle_or_default(linestyles) 

387 aas = cycle_or_default(antialiaseds) 

388 urls = cycle_or_default(urls) 

389 

390 if Nedgecolors == 0: 

391 gc0.set_linewidth(0.0) 

392 

393 for pathid, (xo, yo), fc, ec, lw, ls, aa, url in itertools.islice( 

394 zip(pathids, toffsets, fcs, ecs, lws, lss, aas, urls), N): 

395 if not (np.isfinite(xo) and np.isfinite(yo)): 

396 continue 

397 if Nedgecolors: 

398 if Nlinewidths: 

399 gc0.set_linewidth(lw) 

400 if Nlinestyles: 

401 gc0.set_dashes(*ls) 

402 if len(ec) == 4 and ec[3] == 0.0: 

403 gc0.set_linewidth(0) 

404 else: 

405 gc0.set_foreground(ec) 

406 if fc is not None and len(fc) == 4 and fc[3] == 0: 

407 fc = None 

408 gc0.set_antialiased(aa) 

409 if Nurls: 

410 gc0.set_url(url) 

411 yield xo, yo, pathid, gc0, fc 

412 gc0.restore() 

413 

414 def get_image_magnification(self): 

415 """ 

416 Get the factor by which to magnify images passed to `draw_image`. 

417 Allows a backend to have images at a different resolution to other 

418 artists. 

419 """ 

420 return 1.0 

421 

422 def draw_image(self, gc, x, y, im, transform=None): 

423 """ 

424 Draw an RGBA image. 

425 

426 Parameters 

427 ---------- 

428 gc : `.GraphicsContextBase` 

429 A graphics context with clipping information. 

430 

431 x : scalar 

432 The distance in physical units (i.e., dots or pixels) from the left 

433 hand side of the canvas. 

434 

435 y : scalar 

436 The distance in physical units (i.e., dots or pixels) from the 

437 bottom side of the canvas. 

438 

439 im : (N, M, 4) array of `numpy.uint8` 

440 An array of RGBA pixels. 

441 

442 transform : `~matplotlib.transforms.Affine2DBase` 

443 If and only if the concrete backend is written such that 

444 `option_scale_image` returns ``True``, an affine transformation 

445 (i.e., an `.Affine2DBase`) *may* be passed to `draw_image`. The 

446 translation vector of the transformation is given in physical units 

447 (i.e., dots or pixels). Note that the transformation does not 

448 override *x* and *y*, and has to be applied *before* translating 

449 the result by *x* and *y* (this can be accomplished by adding *x* 

450 and *y* to the translation vector defined by *transform*). 

451 """ 

452 raise NotImplementedError 

453 

454 def option_image_nocomposite(self): 

455 """ 

456 Return whether image composition by Matplotlib should be skipped. 

457 

458 Raster backends should usually return False (letting the C-level 

459 rasterizer take care of image composition); vector backends should 

460 usually return ``not rcParams["image.composite_image"]``. 

461 """ 

462 return False 

463 

464 def option_scale_image(self): 

465 """ 

466 Return whether arbitrary affine transformations in `draw_image` are 

467 supported (True for most vector backends). 

468 """ 

469 return False 

470 

471 def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None): 

472 """ 

473 Draw a TeX instance. 

474 

475 Parameters 

476 ---------- 

477 gc : `.GraphicsContextBase` 

478 The graphics context. 

479 x : float 

480 The x location of the text in display coords. 

481 y : float 

482 The y location of the text baseline in display coords. 

483 s : str 

484 The TeX text string. 

485 prop : `~matplotlib.font_manager.FontProperties` 

486 The font properties. 

487 angle : float 

488 The rotation angle in degrees anti-clockwise. 

489 mtext : `~matplotlib.text.Text` 

490 The original text object to be rendered. 

491 """ 

492 self._draw_text_as_path(gc, x, y, s, prop, angle, ismath="TeX") 

493 

494 def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): 

495 """ 

496 Draw a text instance. 

497 

498 Parameters 

499 ---------- 

500 gc : `.GraphicsContextBase` 

501 The graphics context. 

502 x : float 

503 The x location of the text in display coords. 

504 y : float 

505 The y location of the text baseline in display coords. 

506 s : str 

507 The text string. 

508 prop : `~matplotlib.font_manager.FontProperties` 

509 The font properties. 

510 angle : float 

511 The rotation angle in degrees anti-clockwise. 

512 ismath : bool or "TeX" 

513 If True, use mathtext parser. If "TeX", use tex for rendering. 

514 mtext : `~matplotlib.text.Text` 

515 The original text object to be rendered. 

516 """ 

517 self._draw_text_as_path(gc, x, y, s, prop, angle, ismath) 

518 

519 def _get_text_path_transform(self, x, y, s, prop, angle, ismath): 

520 """ 

521 Return the text path and transform. 

522 

523 Parameters 

524 ---------- 

525 x : float 

526 The x location of the text in display coords. 

527 y : float 

528 The y location of the text baseline in display coords. 

529 s : str 

530 The text to be converted. 

531 prop : `~matplotlib.font_manager.FontProperties` 

532 The font property. 

533 angle : float 

534 Angle in degrees to render the text at. 

535 ismath : bool or "TeX" 

536 If True, use mathtext parser. If "TeX", use tex for rendering. 

537 """ 

538 

539 text2path = self._text2path 

540 fontsize = self.points_to_pixels(prop.get_size_in_points()) 

541 verts, codes = text2path.get_text_path(prop, s, ismath=ismath) 

542 

543 path = Path(verts, codes) 

544 angle = np.deg2rad(angle) 

545 if self.flipy(): 

546 width, height = self.get_canvas_width_height() 

547 transform = (Affine2D() 

548 .scale(fontsize / text2path.FONT_SCALE) 

549 .rotate(angle) 

550 .translate(x, height - y)) 

551 else: 

552 transform = (Affine2D() 

553 .scale(fontsize / text2path.FONT_SCALE) 

554 .rotate(angle) 

555 .translate(x, y)) 

556 

557 return path, transform 

558 

559 def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): 

560 """ 

561 Draw the text by converting them to paths using `.TextToPath`. 

562 

563 Parameters 

564 ---------- 

565 gc : `.GraphicsContextBase` 

566 The graphics context. 

567 x : float 

568 The x location of the text in display coords. 

569 y : float 

570 The y location of the text baseline in display coords. 

571 s : str 

572 The text to be converted. 

573 prop : `~matplotlib.font_manager.FontProperties` 

574 The font property. 

575 angle : float 

576 Angle in degrees to render the text at. 

577 ismath : bool or "TeX" 

578 If True, use mathtext parser. If "TeX", use tex for rendering. 

579 """ 

580 path, transform = self._get_text_path_transform( 

581 x, y, s, prop, angle, ismath) 

582 color = gc.get_rgb() 

583 gc.set_linewidth(0.0) 

584 self.draw_path(gc, path, transform, rgbFace=color) 

585 

586 def get_text_width_height_descent(self, s, prop, ismath): 

587 """ 

588 Get the width, height, and descent (offset from the bottom to the baseline), in 

589 display coords, of the string *s* with `.FontProperties` *prop*. 

590 

591 Whitespace at the start and the end of *s* is included in the reported width. 

592 """ 

593 fontsize = prop.get_size_in_points() 

594 

595 if ismath == 'TeX': 

596 # todo: handle properties 

597 return self.get_texmanager().get_text_width_height_descent( 

598 s, fontsize, renderer=self) 

599 

600 dpi = self.points_to_pixels(72) 

601 if ismath: 

602 dims = self._text2path.mathtext_parser.parse(s, dpi, prop) 

603 return dims[0:3] # return width, height, descent 

604 

605 flags = self._text2path._get_hinting_flag() 

606 font = self._text2path._get_font(prop) 

607 font.set_size(fontsize, dpi) 

608 # the width and height of unrotated string 

609 font.set_text(s, 0.0, flags=flags) 

610 w, h = font.get_width_height() 

611 d = font.get_descent() 

612 w /= 64.0 # convert from subpixels 

613 h /= 64.0 

614 d /= 64.0 

615 return w, h, d 

616 

617 def flipy(self): 

618 """ 

619 Return whether y values increase from top to bottom. 

620 

621 Note that this only affects drawing of texts. 

622 """ 

623 return True 

624 

625 def get_canvas_width_height(self): 

626 """Return the canvas width and height in display coords.""" 

627 return 1, 1 

628 

629 def get_texmanager(self): 

630 """Return the `.TexManager` instance.""" 

631 if self._texmanager is None: 

632 self._texmanager = TexManager() 

633 return self._texmanager 

634 

635 def new_gc(self): 

636 """Return an instance of a `.GraphicsContextBase`.""" 

637 return GraphicsContextBase() 

638 

639 def points_to_pixels(self, points): 

640 """ 

641 Convert points to display units. 

642 

643 You need to override this function (unless your backend 

644 doesn't have a dpi, e.g., postscript or svg). Some imaging 

645 systems assume some value for pixels per inch:: 

646 

647 points to pixels = points * pixels_per_inch/72 * dpi/72 

648 

649 Parameters 

650 ---------- 

651 points : float or array-like 

652 

653 Returns 

654 ------- 

655 Points converted to pixels 

656 """ 

657 return points 

658 

659 def start_rasterizing(self): 

660 """ 

661 Switch to the raster renderer. 

662 

663 Used by `.MixedModeRenderer`. 

664 """ 

665 

666 def stop_rasterizing(self): 

667 """ 

668 Switch back to the vector renderer and draw the contents of the raster 

669 renderer as an image on the vector renderer. 

670 

671 Used by `.MixedModeRenderer`. 

672 """ 

673 

674 def start_filter(self): 

675 """ 

676 Switch to a temporary renderer for image filtering effects. 

677 

678 Currently only supported by the agg renderer. 

679 """ 

680 

681 def stop_filter(self, filter_func): 

682 """ 

683 Switch back to the original renderer. The contents of the temporary 

684 renderer is processed with the *filter_func* and is drawn on the 

685 original renderer as an image. 

686 

687 Currently only supported by the agg renderer. 

688 """ 

689 

690 def _draw_disabled(self): 

691 """ 

692 Context manager to temporary disable drawing. 

693 

694 This is used for getting the drawn size of Artists. This lets us 

695 run the draw process to update any Python state but does not pay the 

696 cost of the draw_XYZ calls on the canvas. 

697 """ 

698 no_ops = { 

699 meth_name: lambda *args, **kwargs: None 

700 for meth_name in dir(RendererBase) 

701 if (meth_name.startswith("draw_") 

702 or meth_name in ["open_group", "close_group"]) 

703 } 

704 

705 return _setattr_cm(self, **no_ops) 

706 

707 

708class GraphicsContextBase: 

709 """An abstract base class that provides color, line styles, etc.""" 

710 

711 def __init__(self): 

712 self._alpha = 1.0 

713 self._forced_alpha = False # if True, _alpha overrides A from RGBA 

714 self._antialiased = 1 # use 0, 1 not True, False for extension code 

715 self._capstyle = CapStyle('butt') 

716 self._cliprect = None 

717 self._clippath = None 

718 self._dashes = 0, None 

719 self._joinstyle = JoinStyle('round') 

720 self._linestyle = 'solid' 

721 self._linewidth = 1 

722 self._rgb = (0.0, 0.0, 0.0, 1.0) 

723 self._hatch = None 

724 self._hatch_color = colors.to_rgba(rcParams['hatch.color']) 

725 self._hatch_linewidth = rcParams['hatch.linewidth'] 

726 self._url = None 

727 self._gid = None 

728 self._snap = None 

729 self._sketch = None 

730 

731 def copy_properties(self, gc): 

732 """Copy properties from *gc* to self.""" 

733 self._alpha = gc._alpha 

734 self._forced_alpha = gc._forced_alpha 

735 self._antialiased = gc._antialiased 

736 self._capstyle = gc._capstyle 

737 self._cliprect = gc._cliprect 

738 self._clippath = gc._clippath 

739 self._dashes = gc._dashes 

740 self._joinstyle = gc._joinstyle 

741 self._linestyle = gc._linestyle 

742 self._linewidth = gc._linewidth 

743 self._rgb = gc._rgb 

744 self._hatch = gc._hatch 

745 self._hatch_color = gc._hatch_color 

746 self._hatch_linewidth = gc._hatch_linewidth 

747 self._url = gc._url 

748 self._gid = gc._gid 

749 self._snap = gc._snap 

750 self._sketch = gc._sketch 

751 

752 def restore(self): 

753 """ 

754 Restore the graphics context from the stack - needed only 

755 for backends that save graphics contexts on a stack. 

756 """ 

757 

758 def get_alpha(self): 

759 """ 

760 Return the alpha value used for blending - not supported on all 

761 backends. 

762 """ 

763 return self._alpha 

764 

765 def get_antialiased(self): 

766 """Return whether the object should try to do antialiased rendering.""" 

767 return self._antialiased 

768 

769 def get_capstyle(self): 

770 """Return the `.CapStyle`.""" 

771 return self._capstyle.name 

772 

773 def get_clip_rectangle(self): 

774 """ 

775 Return the clip rectangle as a `~matplotlib.transforms.Bbox` instance. 

776 """ 

777 return self._cliprect 

778 

779 def get_clip_path(self): 

780 """ 

781 Return the clip path in the form (path, transform), where path 

782 is a `~.path.Path` instance, and transform is 

783 an affine transform to apply to the path before clipping. 

784 """ 

785 if self._clippath is not None: 

786 tpath, tr = self._clippath.get_transformed_path_and_affine() 

787 if np.all(np.isfinite(tpath.vertices)): 

788 return tpath, tr 

789 else: 

790 _log.warning("Ill-defined clip_path detected. Returning None.") 

791 return None, None 

792 return None, None 

793 

794 def get_dashes(self): 

795 """ 

796 Return the dash style as an (offset, dash-list) pair. 

797 

798 See `.set_dashes` for details. 

799 

800 Default value is (None, None). 

801 """ 

802 return self._dashes 

803 

804 def get_forced_alpha(self): 

805 """ 

806 Return whether the value given by get_alpha() should be used to 

807 override any other alpha-channel values. 

808 """ 

809 return self._forced_alpha 

810 

811 def get_joinstyle(self): 

812 """Return the `.JoinStyle`.""" 

813 return self._joinstyle.name 

814 

815 def get_linewidth(self): 

816 """Return the line width in points.""" 

817 return self._linewidth 

818 

819 def get_rgb(self): 

820 """Return a tuple of three or four floats from 0-1.""" 

821 return self._rgb 

822 

823 def get_url(self): 

824 """Return a url if one is set, None otherwise.""" 

825 return self._url 

826 

827 def get_gid(self): 

828 """Return the object identifier if one is set, None otherwise.""" 

829 return self._gid 

830 

831 def get_snap(self): 

832 """ 

833 Return the snap setting, which can be: 

834 

835 * True: snap vertices to the nearest pixel center 

836 * False: leave vertices as-is 

837 * None: (auto) If the path contains only rectilinear line segments, 

838 round to the nearest pixel center 

839 """ 

840 return self._snap 

841 

842 def set_alpha(self, alpha): 

843 """ 

844 Set the alpha value used for blending - not supported on all backends. 

845 

846 If ``alpha=None`` (the default), the alpha components of the 

847 foreground and fill colors will be used to set their respective 

848 transparencies (where applicable); otherwise, ``alpha`` will override 

849 them. 

850 """ 

851 if alpha is not None: 

852 self._alpha = alpha 

853 self._forced_alpha = True 

854 else: 

855 self._alpha = 1.0 

856 self._forced_alpha = False 

857 self.set_foreground(self._rgb, isRGBA=True) 

858 

859 def set_antialiased(self, b): 

860 """Set whether object should be drawn with antialiased rendering.""" 

861 # Use ints to make life easier on extension code trying to read the gc. 

862 self._antialiased = int(bool(b)) 

863 

864 @_docstring.interpd 

865 def set_capstyle(self, cs): 

866 """ 

867 Set how to draw endpoints of lines. 

868 

869 Parameters 

870 ---------- 

871 cs : `.CapStyle` or %(CapStyle)s 

872 """ 

873 self._capstyle = CapStyle(cs) 

874 

875 def set_clip_rectangle(self, rectangle): 

876 """Set the clip rectangle to a `.Bbox` or None.""" 

877 self._cliprect = rectangle 

878 

879 def set_clip_path(self, path): 

880 """Set the clip path to a `.TransformedPath` or None.""" 

881 _api.check_isinstance((transforms.TransformedPath, None), path=path) 

882 self._clippath = path 

883 

884 def set_dashes(self, dash_offset, dash_list): 

885 """ 

886 Set the dash style for the gc. 

887 

888 Parameters 

889 ---------- 

890 dash_offset : float 

891 Distance, in points, into the dash pattern at which to 

892 start the pattern. It is usually set to 0. 

893 dash_list : array-like or None 

894 The on-off sequence as points. None specifies a solid line. All 

895 values must otherwise be non-negative (:math:`\\ge 0`). 

896 

897 Notes 

898 ----- 

899 See p. 666 of the PostScript 

900 `Language Reference 

901 <https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf>`_ 

902 for more info. 

903 """ 

904 if dash_list is not None: 

905 dl = np.asarray(dash_list) 

906 if np.any(dl < 0.0): 

907 raise ValueError( 

908 "All values in the dash list must be non-negative") 

909 if dl.size and not np.any(dl > 0.0): 

910 raise ValueError( 

911 'At least one value in the dash list must be positive') 

912 self._dashes = dash_offset, dash_list 

913 

914 def set_foreground(self, fg, isRGBA=False): 

915 """ 

916 Set the foreground color. 

917 

918 Parameters 

919 ---------- 

920 fg : :mpltype:`color` 

921 isRGBA : bool 

922 If *fg* is known to be an ``(r, g, b, a)`` tuple, *isRGBA* can be 

923 set to True to improve performance. 

924 """ 

925 if self._forced_alpha and isRGBA: 

926 self._rgb = fg[:3] + (self._alpha,) 

927 elif self._forced_alpha: 

928 self._rgb = colors.to_rgba(fg, self._alpha) 

929 elif isRGBA: 

930 self._rgb = fg 

931 else: 

932 self._rgb = colors.to_rgba(fg) 

933 

934 @_docstring.interpd 

935 def set_joinstyle(self, js): 

936 """ 

937 Set how to draw connections between line segments. 

938 

939 Parameters 

940 ---------- 

941 js : `.JoinStyle` or %(JoinStyle)s 

942 """ 

943 self._joinstyle = JoinStyle(js) 

944 

945 def set_linewidth(self, w): 

946 """Set the linewidth in points.""" 

947 self._linewidth = float(w) 

948 

949 def set_url(self, url): 

950 """Set the url for links in compatible backends.""" 

951 self._url = url 

952 

953 def set_gid(self, id): 

954 """Set the id.""" 

955 self._gid = id 

956 

957 def set_snap(self, snap): 

958 """ 

959 Set the snap setting which may be: 

960 

961 * True: snap vertices to the nearest pixel center 

962 * False: leave vertices as-is 

963 * None: (auto) If the path contains only rectilinear line segments, 

964 round to the nearest pixel center 

965 """ 

966 self._snap = snap 

967 

968 def set_hatch(self, hatch): 

969 """Set the hatch style (for fills).""" 

970 self._hatch = hatch 

971 

972 def get_hatch(self): 

973 """Get the current hatch style.""" 

974 return self._hatch 

975 

976 def get_hatch_path(self, density=6.0): 

977 """Return a `.Path` for the current hatch.""" 

978 hatch = self.get_hatch() 

979 if hatch is None: 

980 return None 

981 return Path.hatch(hatch, density) 

982 

983 def get_hatch_color(self): 

984 """Get the hatch color.""" 

985 return self._hatch_color 

986 

987 def set_hatch_color(self, hatch_color): 

988 """Set the hatch color.""" 

989 self._hatch_color = hatch_color 

990 

991 def get_hatch_linewidth(self): 

992 """Get the hatch linewidth.""" 

993 return self._hatch_linewidth 

994 

995 def get_sketch_params(self): 

996 """ 

997 Return the sketch parameters for the artist. 

998 

999 Returns 

1000 ------- 

1001 tuple or `None` 

1002 

1003 A 3-tuple with the following elements: 

1004 

1005 * ``scale``: The amplitude of the wiggle perpendicular to the 

1006 source line. 

1007 * ``length``: The length of the wiggle along the line. 

1008 * ``randomness``: The scale factor by which the length is 

1009 shrunken or expanded. 

1010 

1011 May return `None` if no sketch parameters were set. 

1012 """ 

1013 return self._sketch 

1014 

1015 def set_sketch_params(self, scale=None, length=None, randomness=None): 

1016 """ 

1017 Set the sketch parameters. 

1018 

1019 Parameters 

1020 ---------- 

1021 scale : float, optional 

1022 The amplitude of the wiggle perpendicular to the source line, in 

1023 pixels. If scale is `None`, or not provided, no sketch filter will 

1024 be provided. 

1025 length : float, default: 128 

1026 The length of the wiggle along the line, in pixels. 

1027 randomness : float, default: 16 

1028 The scale factor by which the length is shrunken or expanded. 

1029 """ 

1030 self._sketch = ( 

1031 None if scale is None 

1032 else (scale, length or 128., randomness or 16.)) 

1033 

1034 

1035class TimerBase: 

1036 """ 

1037 A base class for providing timer events, useful for things animations. 

1038 Backends need to implement a few specific methods in order to use their 

1039 own timing mechanisms so that the timer events are integrated into their 

1040 event loops. 

1041 

1042 Subclasses must override the following methods: 

1043 

1044 - ``_timer_start``: Backend-specific code for starting the timer. 

1045 - ``_timer_stop``: Backend-specific code for stopping the timer. 

1046 

1047 Subclasses may additionally override the following methods: 

1048 

1049 - ``_timer_set_single_shot``: Code for setting the timer to single shot 

1050 operating mode, if supported by the timer object. If not, the `Timer` 

1051 class itself will store the flag and the ``_on_timer`` method should be 

1052 overridden to support such behavior. 

1053 

1054 - ``_timer_set_interval``: Code for setting the interval on the timer, if 

1055 there is a method for doing so on the timer object. 

1056 

1057 - ``_on_timer``: The internal function that any timer object should call, 

1058 which will handle the task of running all callbacks that have been set. 

1059 """ 

1060 

1061 def __init__(self, interval=None, callbacks=None): 

1062 """ 

1063 Parameters 

1064 ---------- 

1065 interval : int, default: 1000ms 

1066 The time between timer events in milliseconds. Will be stored as 

1067 ``timer.interval``. 

1068 callbacks : list[tuple[callable, tuple, dict]] 

1069 List of (func, args, kwargs) tuples that will be called upon timer 

1070 events. This list is accessible as ``timer.callbacks`` and can be 

1071 manipulated directly, or the functions `~.TimerBase.add_callback` 

1072 and `~.TimerBase.remove_callback` can be used. 

1073 """ 

1074 self.callbacks = [] if callbacks is None else callbacks.copy() 

1075 # Set .interval and not ._interval to go through the property setter. 

1076 self.interval = 1000 if interval is None else interval 

1077 self.single_shot = False 

1078 

1079 def __del__(self): 

1080 """Need to stop timer and possibly disconnect timer.""" 

1081 self._timer_stop() 

1082 

1083 @_api.delete_parameter("3.9", "interval", alternative="timer.interval") 

1084 def start(self, interval=None): 

1085 """ 

1086 Start the timer object. 

1087 

1088 Parameters 

1089 ---------- 

1090 interval : int, optional 

1091 Timer interval in milliseconds; overrides a previously set interval 

1092 if provided. 

1093 """ 

1094 if interval is not None: 

1095 self.interval = interval 

1096 self._timer_start() 

1097 

1098 def stop(self): 

1099 """Stop the timer.""" 

1100 self._timer_stop() 

1101 

1102 def _timer_start(self): 

1103 pass 

1104 

1105 def _timer_stop(self): 

1106 pass 

1107 

1108 @property 

1109 def interval(self): 

1110 """The time between timer events, in milliseconds.""" 

1111 return self._interval 

1112 

1113 @interval.setter 

1114 def interval(self, interval): 

1115 # Force to int since none of the backends actually support fractional 

1116 # milliseconds, and some error or give warnings. 

1117 # Some backends also fail when interval == 0, so ensure >= 1 msec 

1118 interval = max(int(interval), 1) 

1119 self._interval = interval 

1120 self._timer_set_interval() 

1121 

1122 @property 

1123 def single_shot(self): 

1124 """Whether this timer should stop after a single run.""" 

1125 return self._single 

1126 

1127 @single_shot.setter 

1128 def single_shot(self, ss): 

1129 self._single = ss 

1130 self._timer_set_single_shot() 

1131 

1132 def add_callback(self, func, *args, **kwargs): 

1133 """ 

1134 Register *func* to be called by timer when the event fires. Any 

1135 additional arguments provided will be passed to *func*. 

1136 

1137 This function returns *func*, which makes it possible to use it as a 

1138 decorator. 

1139 """ 

1140 self.callbacks.append((func, args, kwargs)) 

1141 return func 

1142 

1143 def remove_callback(self, func, *args, **kwargs): 

1144 """ 

1145 Remove *func* from list of callbacks. 

1146 

1147 *args* and *kwargs* are optional and used to distinguish between copies 

1148 of the same function registered to be called with different arguments. 

1149 This behavior is deprecated. In the future, ``*args, **kwargs`` won't 

1150 be considered anymore; to keep a specific callback removable by itself, 

1151 pass it to `add_callback` as a `functools.partial` object. 

1152 """ 

1153 if args or kwargs: 

1154 _api.warn_deprecated( 

1155 "3.1", message="In a future version, Timer.remove_callback " 

1156 "will not take *args, **kwargs anymore, but remove all " 

1157 "callbacks where the callable matches; to keep a specific " 

1158 "callback removable by itself, pass it to add_callback as a " 

1159 "functools.partial object.") 

1160 self.callbacks.remove((func, args, kwargs)) 

1161 else: 

1162 funcs = [c[0] for c in self.callbacks] 

1163 if func in funcs: 

1164 self.callbacks.pop(funcs.index(func)) 

1165 

1166 def _timer_set_interval(self): 

1167 """Used to set interval on underlying timer object.""" 

1168 

1169 def _timer_set_single_shot(self): 

1170 """Used to set single shot on underlying timer object.""" 

1171 

1172 def _on_timer(self): 

1173 """ 

1174 Runs all function that have been registered as callbacks. Functions 

1175 can return False (or 0) if they should not be called any more. If there 

1176 are no callbacks, the timer is automatically stopped. 

1177 """ 

1178 for func, args, kwargs in self.callbacks: 

1179 ret = func(*args, **kwargs) 

1180 # docstring above explains why we use `if ret == 0` here, 

1181 # instead of `if not ret`. 

1182 # This will also catch `ret == False` as `False == 0` 

1183 # but does not annoy the linters 

1184 # https://docs.python.org/3/library/stdtypes.html#boolean-values 

1185 if ret == 0: 

1186 self.callbacks.remove((func, args, kwargs)) 

1187 

1188 if len(self.callbacks) == 0: 

1189 self.stop() 

1190 

1191 

1192class Event: 

1193 """ 

1194 A Matplotlib event. 

1195 

1196 The following attributes are defined and shown with their default values. 

1197 Subclasses may define additional attributes. 

1198 

1199 Attributes 

1200 ---------- 

1201 name : str 

1202 The event name. 

1203 canvas : `FigureCanvasBase` 

1204 The backend-specific canvas instance generating the event. 

1205 guiEvent 

1206 The GUI event that triggered the Matplotlib event. 

1207 """ 

1208 

1209 def __init__(self, name, canvas, guiEvent=None): 

1210 self.name = name 

1211 self.canvas = canvas 

1212 self._guiEvent = guiEvent 

1213 self._guiEvent_deleted = False 

1214 

1215 def _process(self): 

1216 """Process this event on ``self.canvas``, then unset ``guiEvent``.""" 

1217 self.canvas.callbacks.process(self.name, self) 

1218 self._guiEvent_deleted = True 

1219 

1220 @property 

1221 def guiEvent(self): 

1222 # After deprecation elapses: remove _guiEvent_deleted; make guiEvent a plain 

1223 # attribute set to None by _process. 

1224 if self._guiEvent_deleted: 

1225 _api.warn_deprecated( 

1226 "3.8", message="Accessing guiEvent outside of the original GUI event " 

1227 "handler is unsafe and deprecated since %(since)s; in the future, the " 

1228 "attribute will be set to None after quitting the event handler. You " 

1229 "may separately record the value of the guiEvent attribute at your own " 

1230 "risk.") 

1231 return self._guiEvent 

1232 

1233 

1234class DrawEvent(Event): 

1235 """ 

1236 An event triggered by a draw operation on the canvas. 

1237 

1238 In most backends, callbacks subscribed to this event will be fired after 

1239 the rendering is complete but before the screen is updated. Any extra 

1240 artists drawn to the canvas's renderer will be reflected without an 

1241 explicit call to ``blit``. 

1242 

1243 .. warning:: 

1244 

1245 Calling ``canvas.draw`` and ``canvas.blit`` in these callbacks may 

1246 not be safe with all backends and may cause infinite recursion. 

1247 

1248 A DrawEvent has a number of special attributes in addition to those defined 

1249 by the parent `Event` class. 

1250 

1251 Attributes 

1252 ---------- 

1253 renderer : `RendererBase` 

1254 The renderer for the draw event. 

1255 """ 

1256 def __init__(self, name, canvas, renderer): 

1257 super().__init__(name, canvas) 

1258 self.renderer = renderer 

1259 

1260 

1261class ResizeEvent(Event): 

1262 """ 

1263 An event triggered by a canvas resize. 

1264 

1265 A ResizeEvent has a number of special attributes in addition to those 

1266 defined by the parent `Event` class. 

1267 

1268 Attributes 

1269 ---------- 

1270 width : int 

1271 Width of the canvas in pixels. 

1272 height : int 

1273 Height of the canvas in pixels. 

1274 """ 

1275 

1276 def __init__(self, name, canvas): 

1277 super().__init__(name, canvas) 

1278 self.width, self.height = canvas.get_width_height() 

1279 

1280 

1281class CloseEvent(Event): 

1282 """An event triggered by a figure being closed.""" 

1283 

1284 

1285class LocationEvent(Event): 

1286 """ 

1287 An event that has a screen location. 

1288 

1289 A LocationEvent has a number of special attributes in addition to those 

1290 defined by the parent `Event` class. 

1291 

1292 Attributes 

1293 ---------- 

1294 x, y : int or None 

1295 Event location in pixels from bottom left of canvas. 

1296 inaxes : `~matplotlib.axes.Axes` or None 

1297 The `~.axes.Axes` instance over which the mouse is, if any. 

1298 xdata, ydata : float or None 

1299 Data coordinates of the mouse within *inaxes*, or *None* if the mouse 

1300 is not over an Axes. 

1301 modifiers : frozenset 

1302 The keyboard modifiers currently being pressed (except for KeyEvent). 

1303 """ 

1304 

1305 # Fully delete all occurrences of lastevent after deprecation elapses. 

1306 _lastevent = None 

1307 lastevent = _api.deprecated("3.8")( 

1308 _api.classproperty(lambda cls: cls._lastevent)) 

1309 _last_axes_ref = None 

1310 

1311 def __init__(self, name, canvas, x, y, guiEvent=None, *, modifiers=None): 

1312 super().__init__(name, canvas, guiEvent=guiEvent) 

1313 # x position - pixels from left of canvas 

1314 self.x = int(x) if x is not None else x 

1315 # y position - pixels from right of canvas 

1316 self.y = int(y) if y is not None else y 

1317 self.inaxes = None # the Axes instance the mouse is over 

1318 self.xdata = None # x coord of mouse in data coords 

1319 self.ydata = None # y coord of mouse in data coords 

1320 self.modifiers = frozenset(modifiers if modifiers is not None else []) 

1321 

1322 if x is None or y is None: 

1323 # cannot check if event was in Axes if no (x, y) info 

1324 return 

1325 

1326 self._set_inaxes(self.canvas.inaxes((x, y)) 

1327 if self.canvas.mouse_grabber is None else 

1328 self.canvas.mouse_grabber, 

1329 (x, y)) 

1330 

1331 # Splitting _set_inaxes out is useful for the axes_leave_event handler: it 

1332 # needs to generate synthetic LocationEvents with manually-set inaxes. In 

1333 # that latter case, xy has already been cast to int so it can directly be 

1334 # read from self.x, self.y; in the normal case, however, it is more 

1335 # accurate to pass the untruncated float x, y values passed to the ctor. 

1336 

1337 def _set_inaxes(self, inaxes, xy=None): 

1338 self.inaxes = inaxes 

1339 if inaxes is not None: 

1340 try: 

1341 self.xdata, self.ydata = inaxes.transData.inverted().transform( 

1342 xy if xy is not None else (self.x, self.y)) 

1343 except ValueError: 

1344 pass 

1345 

1346 

1347class MouseButton(IntEnum): 

1348 LEFT = 1 

1349 MIDDLE = 2 

1350 RIGHT = 3 

1351 BACK = 8 

1352 FORWARD = 9 

1353 

1354 

1355class MouseEvent(LocationEvent): 

1356 """ 

1357 A mouse event ('button_press_event', 'button_release_event', \ 

1358'scroll_event', 'motion_notify_event'). 

1359 

1360 A MouseEvent has a number of special attributes in addition to those 

1361 defined by the parent `Event` and `LocationEvent` classes. 

1362 

1363 Attributes 

1364 ---------- 

1365 button : None or `MouseButton` or {'up', 'down'} 

1366 The button pressed. 'up' and 'down' are used for scroll events. 

1367 

1368 Note that LEFT and RIGHT actually refer to the "primary" and 

1369 "secondary" buttons, i.e. if the user inverts their left and right 

1370 buttons ("left-handed setting") then the LEFT button will be the one 

1371 physically on the right. 

1372 

1373 If this is unset, *name* is "scroll_event", and *step* is nonzero, then 

1374 this will be set to "up" or "down" depending on the sign of *step*. 

1375 

1376 key : None or str 

1377 The key pressed when the mouse event triggered, e.g. 'shift'. 

1378 See `KeyEvent`. 

1379 

1380 .. warning:: 

1381 This key is currently obtained from the last 'key_press_event' or 

1382 'key_release_event' that occurred within the canvas. Thus, if the 

1383 last change of keyboard state occurred while the canvas did not have 

1384 focus, this attribute will be wrong. On the other hand, the 

1385 ``modifiers`` attribute should always be correct, but it can only 

1386 report on modifier keys. 

1387 

1388 step : float 

1389 The number of scroll steps (positive for 'up', negative for 'down'). 

1390 This applies only to 'scroll_event' and defaults to 0 otherwise. 

1391 

1392 dblclick : bool 

1393 Whether the event is a double-click. This applies only to 

1394 'button_press_event' and is False otherwise. In particular, it's 

1395 not used in 'button_release_event'. 

1396 

1397 Examples 

1398 -------- 

1399 :: 

1400 

1401 def on_press(event): 

1402 print('you pressed', event.button, event.xdata, event.ydata) 

1403 

1404 cid = fig.canvas.mpl_connect('button_press_event', on_press) 

1405 """ 

1406 

1407 def __init__(self, name, canvas, x, y, button=None, key=None, 

1408 step=0, dblclick=False, guiEvent=None, *, modifiers=None): 

1409 super().__init__( 

1410 name, canvas, x, y, guiEvent=guiEvent, modifiers=modifiers) 

1411 if button in MouseButton.__members__.values(): 

1412 button = MouseButton(button) 

1413 if name == "scroll_event" and button is None: 

1414 if step > 0: 

1415 button = "up" 

1416 elif step < 0: 

1417 button = "down" 

1418 self.button = button 

1419 self.key = key 

1420 self.step = step 

1421 self.dblclick = dblclick 

1422 

1423 def __str__(self): 

1424 return (f"{self.name}: " 

1425 f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) " 

1426 f"button={self.button} dblclick={self.dblclick} " 

1427 f"inaxes={self.inaxes}") 

1428 

1429 

1430class PickEvent(Event): 

1431 """ 

1432 A pick event. 

1433 

1434 This event is fired when the user picks a location on the canvas 

1435 sufficiently close to an artist that has been made pickable with 

1436 `.Artist.set_picker`. 

1437 

1438 A PickEvent has a number of special attributes in addition to those defined 

1439 by the parent `Event` class. 

1440 

1441 Attributes 

1442 ---------- 

1443 mouseevent : `MouseEvent` 

1444 The mouse event that generated the pick. 

1445 artist : `~matplotlib.artist.Artist` 

1446 The picked artist. Note that artists are not pickable by default 

1447 (see `.Artist.set_picker`). 

1448 other 

1449 Additional attributes may be present depending on the type of the 

1450 picked object; e.g., a `.Line2D` pick may define different extra 

1451 attributes than a `.PatchCollection` pick. 

1452 

1453 Examples 

1454 -------- 

1455 Bind a function ``on_pick()`` to pick events, that prints the coordinates 

1456 of the picked data point:: 

1457 

1458 ax.plot(np.rand(100), 'o', picker=5) # 5 points tolerance 

1459 

1460 def on_pick(event): 

1461 line = event.artist 

1462 xdata, ydata = line.get_data() 

1463 ind = event.ind 

1464 print(f'on pick line: {xdata[ind]:.3f}, {ydata[ind]:.3f}') 

1465 

1466 cid = fig.canvas.mpl_connect('pick_event', on_pick) 

1467 """ 

1468 

1469 def __init__(self, name, canvas, mouseevent, artist, 

1470 guiEvent=None, **kwargs): 

1471 if guiEvent is None: 

1472 guiEvent = mouseevent.guiEvent 

1473 super().__init__(name, canvas, guiEvent) 

1474 self.mouseevent = mouseevent 

1475 self.artist = artist 

1476 self.__dict__.update(kwargs) 

1477 

1478 

1479class KeyEvent(LocationEvent): 

1480 """ 

1481 A key event (key press, key release). 

1482 

1483 A KeyEvent has a number of special attributes in addition to those defined 

1484 by the parent `Event` and `LocationEvent` classes. 

1485 

1486 Attributes 

1487 ---------- 

1488 key : None or str 

1489 The key(s) pressed. Could be *None*, a single case sensitive Unicode 

1490 character ("g", "G", "#", etc.), a special key ("control", "shift", 

1491 "f1", "up", etc.) or a combination of the above (e.g., "ctrl+alt+g", 

1492 "ctrl+alt+G"). 

1493 

1494 Notes 

1495 ----- 

1496 Modifier keys will be prefixed to the pressed key and will be in the order 

1497 "ctrl", "alt", "super". The exception to this rule is when the pressed key 

1498 is itself a modifier key, therefore "ctrl+alt" and "alt+control" can both 

1499 be valid key values. 

1500 

1501 Examples 

1502 -------- 

1503 :: 

1504 

1505 def on_key(event): 

1506 print('you pressed', event.key, event.xdata, event.ydata) 

1507 

1508 cid = fig.canvas.mpl_connect('key_press_event', on_key) 

1509 """ 

1510 

1511 def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): 

1512 super().__init__(name, canvas, x, y, guiEvent=guiEvent) 

1513 self.key = key 

1514 

1515 

1516# Default callback for key events. 

1517def _key_handler(event): 

1518 # Dead reckoning of key. 

1519 if event.name == "key_press_event": 

1520 event.canvas._key = event.key 

1521 elif event.name == "key_release_event": 

1522 event.canvas._key = None 

1523 

1524 

1525# Default callback for mouse events. 

1526def _mouse_handler(event): 

1527 # Dead-reckoning of button and key. 

1528 if event.name == "button_press_event": 

1529 event.canvas._button = event.button 

1530 elif event.name == "button_release_event": 

1531 event.canvas._button = None 

1532 elif event.name == "motion_notify_event" and event.button is None: 

1533 event.button = event.canvas._button 

1534 if event.key is None: 

1535 event.key = event.canvas._key 

1536 # Emit axes_enter/axes_leave. 

1537 if event.name == "motion_notify_event": 

1538 last_ref = LocationEvent._last_axes_ref 

1539 last_axes = last_ref() if last_ref else None 

1540 if last_axes != event.inaxes: 

1541 if last_axes is not None: 

1542 # Create a synthetic LocationEvent for the axes_leave_event. 

1543 # Its inaxes attribute needs to be manually set (because the 

1544 # cursor is actually *out* of that Axes at that point); this is 

1545 # done with the internal _set_inaxes method which ensures that 

1546 # the xdata and ydata attributes are also correct. 

1547 try: 

1548 leave_event = LocationEvent( 

1549 "axes_leave_event", last_axes.figure.canvas, 

1550 event.x, event.y, event.guiEvent, 

1551 modifiers=event.modifiers) 

1552 leave_event._set_inaxes(last_axes) 

1553 last_axes.figure.canvas.callbacks.process( 

1554 "axes_leave_event", leave_event) 

1555 except Exception: 

1556 pass # The last canvas may already have been torn down. 

1557 if event.inaxes is not None: 

1558 event.canvas.callbacks.process("axes_enter_event", event) 

1559 LocationEvent._last_axes_ref = ( 

1560 weakref.ref(event.inaxes) if event.inaxes else None) 

1561 LocationEvent._lastevent = ( 

1562 None if event.name == "figure_leave_event" else event) 

1563 

1564 

1565def _get_renderer(figure, print_method=None): 

1566 """ 

1567 Get the renderer that would be used to save a `.Figure`. 

1568 

1569 If you need a renderer without any active draw methods use 

1570 renderer._draw_disabled to temporary patch them out at your call site. 

1571 """ 

1572 # This is implemented by triggering a draw, then immediately jumping out of 

1573 # Figure.draw() by raising an exception. 

1574 

1575 class Done(Exception): 

1576 pass 

1577 

1578 def _draw(renderer): raise Done(renderer) 

1579 

1580 with cbook._setattr_cm(figure, draw=_draw), ExitStack() as stack: 

1581 if print_method is None: 

1582 fmt = figure.canvas.get_default_filetype() 

1583 # Even for a canvas' default output type, a canvas switch may be 

1584 # needed, e.g. for FigureCanvasBase. 

1585 print_method = stack.enter_context( 

1586 figure.canvas._switch_canvas_and_return_print_method(fmt)) 

1587 try: 

1588 print_method(io.BytesIO()) 

1589 except Done as exc: 

1590 renderer, = exc.args 

1591 return renderer 

1592 else: 

1593 raise RuntimeError(f"{print_method} did not call Figure.draw, so " 

1594 f"no renderer is available") 

1595 

1596 

1597def _no_output_draw(figure): 

1598 # _no_output_draw was promoted to the figure level, but 

1599 # keep this here in case someone was calling it... 

1600 figure.draw_without_rendering() 

1601 

1602 

1603def _is_non_interactive_terminal_ipython(ip): 

1604 """ 

1605 Return whether we are in a terminal IPython, but non interactive. 

1606 

1607 When in _terminal_ IPython, ip.parent will have and `interact` attribute, 

1608 if this attribute is False we do not setup eventloop integration as the 

1609 user will _not_ interact with IPython. In all other case (ZMQKernel, or is 

1610 interactive), we do. 

1611 """ 

1612 return (hasattr(ip, 'parent') 

1613 and (ip.parent is not None) 

1614 and getattr(ip.parent, 'interact', None) is False) 

1615 

1616 

1617@contextmanager 

1618def _allow_interrupt(prepare_notifier, handle_sigint): 

1619 """ 

1620 A context manager that allows terminating a plot by sending a SIGINT. It 

1621 is necessary because the running backend prevents the Python interpreter 

1622 from running and processing signals (i.e., to raise a KeyboardInterrupt). 

1623 To solve this, one needs to somehow wake up the interpreter and make it 

1624 close the plot window. We do this by using the signal.set_wakeup_fd() 

1625 function which organizes a write of the signal number into a socketpair. 

1626 A backend-specific function, *prepare_notifier*, arranges to listen to 

1627 the pair's read socket while the event loop is running. (If it returns a 

1628 notifier object, that object is kept alive while the context manager runs.) 

1629 

1630 If SIGINT was indeed caught, after exiting the on_signal() function the 

1631 interpreter reacts to the signal according to the handler function which 

1632 had been set up by a signal.signal() call; here, we arrange to call the 

1633 backend-specific *handle_sigint* function. Finally, we call the old SIGINT 

1634 handler with the same arguments that were given to our custom handler. 

1635 

1636 We do this only if the old handler for SIGINT was not None, which means 

1637 that a non-python handler was installed, i.e. in Julia, and not SIG_IGN 

1638 which means we should ignore the interrupts. 

1639 

1640 Parameters 

1641 ---------- 

1642 prepare_notifier : Callable[[socket.socket], object] 

1643 handle_sigint : Callable[[], object] 

1644 """ 

1645 

1646 old_sigint_handler = signal.getsignal(signal.SIGINT) 

1647 if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL): 

1648 yield 

1649 return 

1650 

1651 handler_args = None 

1652 wsock, rsock = socket.socketpair() 

1653 wsock.setblocking(False) 

1654 rsock.setblocking(False) 

1655 old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno()) 

1656 notifier = prepare_notifier(rsock) 

1657 

1658 def save_args_and_handle_sigint(*args): 

1659 nonlocal handler_args 

1660 handler_args = args 

1661 handle_sigint() 

1662 

1663 signal.signal(signal.SIGINT, save_args_and_handle_sigint) 

1664 try: 

1665 yield 

1666 finally: 

1667 wsock.close() 

1668 rsock.close() 

1669 signal.set_wakeup_fd(old_wakeup_fd) 

1670 signal.signal(signal.SIGINT, old_sigint_handler) 

1671 if handler_args is not None: 

1672 old_sigint_handler(*handler_args) 

1673 

1674 

1675class FigureCanvasBase: 

1676 """ 

1677 The canvas the figure renders into. 

1678 

1679 Attributes 

1680 ---------- 

1681 figure : `~matplotlib.figure.Figure` 

1682 A high-level figure instance. 

1683 """ 

1684 

1685 # Set to one of {"qt", "gtk3", "gtk4", "wx", "tk", "macosx"} if an 

1686 # interactive framework is required, or None otherwise. 

1687 required_interactive_framework = None 

1688 

1689 # The manager class instantiated by new_manager. 

1690 # (This is defined as a classproperty because the manager class is 

1691 # currently defined *after* the canvas class, but one could also assign 

1692 # ``FigureCanvasBase.manager_class = FigureManagerBase`` 

1693 # after defining both classes.) 

1694 manager_class = _api.classproperty(lambda cls: FigureManagerBase) 

1695 

1696 events = [ 

1697 'resize_event', 

1698 'draw_event', 

1699 'key_press_event', 

1700 'key_release_event', 

1701 'button_press_event', 

1702 'button_release_event', 

1703 'scroll_event', 

1704 'motion_notify_event', 

1705 'pick_event', 

1706 'figure_enter_event', 

1707 'figure_leave_event', 

1708 'axes_enter_event', 

1709 'axes_leave_event', 

1710 'close_event' 

1711 ] 

1712 

1713 fixed_dpi = None 

1714 

1715 filetypes = _default_filetypes 

1716 

1717 @_api.classproperty 

1718 def supports_blit(cls): 

1719 """If this Canvas sub-class supports blitting.""" 

1720 return (hasattr(cls, "copy_from_bbox") 

1721 and hasattr(cls, "restore_region")) 

1722 

1723 def __init__(self, figure=None): 

1724 from matplotlib.figure import Figure 

1725 self._fix_ipython_backend2gui() 

1726 self._is_idle_drawing = True 

1727 self._is_saving = False 

1728 if figure is None: 

1729 figure = Figure() 

1730 figure.set_canvas(self) 

1731 self.figure = figure 

1732 self.manager = None 

1733 self.widgetlock = widgets.LockDraw() 

1734 self._button = None # the button pressed 

1735 self._key = None # the key pressed 

1736 self.mouse_grabber = None # the Axes currently grabbing mouse 

1737 self.toolbar = None # NavigationToolbar2 will set me 

1738 self._is_idle_drawing = False 

1739 # We don't want to scale up the figure DPI more than once. 

1740 figure._original_dpi = figure.dpi 

1741 self._device_pixel_ratio = 1 

1742 super().__init__() # Typically the GUI widget init (if any). 

1743 

1744 callbacks = property(lambda self: self.figure._canvas_callbacks) 

1745 button_pick_id = property(lambda self: self.figure._button_pick_id) 

1746 scroll_pick_id = property(lambda self: self.figure._scroll_pick_id) 

1747 

1748 @classmethod 

1749 @functools.cache 

1750 def _fix_ipython_backend2gui(cls): 

1751 # Fix hard-coded module -> toolkit mapping in IPython (used for 

1752 # `ipython --auto`). This cannot be done at import time due to 

1753 # ordering issues, so we do it when creating a canvas, and should only 

1754 # be done once per class (hence the `cache`). 

1755 

1756 # This function will not be needed when Python 3.12, the latest version 

1757 # supported by IPython < 8.24, reaches end-of-life in late 2028. 

1758 # At that time this function can be made a no-op and deprecated. 

1759 mod_ipython = sys.modules.get("IPython") 

1760 if mod_ipython is None or mod_ipython.version_info[:2] >= (8, 24): 

1761 # Use of backend2gui is not needed for IPython >= 8.24 as the 

1762 # functionality has been moved to Matplotlib. 

1763 return 

1764 

1765 import IPython 

1766 ip = IPython.get_ipython() 

1767 if not ip: 

1768 return 

1769 from IPython.core import pylabtools as pt 

1770 if (not hasattr(pt, "backend2gui") 

1771 or not hasattr(ip, "enable_matplotlib")): 

1772 # In case we ever move the patch to IPython and remove these APIs, 

1773 # don't break on our side. 

1774 return 

1775 backend2gui_rif = { 

1776 "qt": "qt", 

1777 "gtk3": "gtk3", 

1778 "gtk4": "gtk4", 

1779 "wx": "wx", 

1780 "macosx": "osx", 

1781 }.get(cls.required_interactive_framework) 

1782 if backend2gui_rif: 

1783 if _is_non_interactive_terminal_ipython(ip): 

1784 ip.enable_gui(backend2gui_rif) 

1785 

1786 @classmethod 

1787 def new_manager(cls, figure, num): 

1788 """ 

1789 Create a new figure manager for *figure*, using this canvas class. 

1790 

1791 Notes 

1792 ----- 

1793 This method should not be reimplemented in subclasses. If 

1794 custom manager creation logic is needed, please reimplement 

1795 ``FigureManager.create_with_canvas``. 

1796 """ 

1797 return cls.manager_class.create_with_canvas(cls, figure, num) 

1798 

1799 @contextmanager 

1800 def _idle_draw_cntx(self): 

1801 self._is_idle_drawing = True 

1802 try: 

1803 yield 

1804 finally: 

1805 self._is_idle_drawing = False 

1806 

1807 def is_saving(self): 

1808 """ 

1809 Return whether the renderer is in the process of saving 

1810 to a file, rather than rendering for an on-screen buffer. 

1811 """ 

1812 return self._is_saving 

1813 

1814 def blit(self, bbox=None): 

1815 """Blit the canvas in bbox (default entire canvas).""" 

1816 

1817 def inaxes(self, xy): 

1818 """ 

1819 Return the topmost visible `~.axes.Axes` containing the point *xy*. 

1820 

1821 Parameters 

1822 ---------- 

1823 xy : (float, float) 

1824 (x, y) pixel positions from left/bottom of the canvas. 

1825 

1826 Returns 

1827 ------- 

1828 `~matplotlib.axes.Axes` or None 

1829 The topmost visible Axes containing the point, or None if there 

1830 is no Axes at the point. 

1831 """ 

1832 axes_list = [a for a in self.figure.get_axes() 

1833 if a.patch.contains_point(xy) and a.get_visible()] 

1834 if axes_list: 

1835 axes = cbook._topmost_artist(axes_list) 

1836 else: 

1837 axes = None 

1838 

1839 return axes 

1840 

1841 def grab_mouse(self, ax): 

1842 """ 

1843 Set the child `~.axes.Axes` which is grabbing the mouse events. 

1844 

1845 Usually called by the widgets themselves. It is an error to call this 

1846 if the mouse is already grabbed by another Axes. 

1847 """ 

1848 if self.mouse_grabber not in (None, ax): 

1849 raise RuntimeError("Another Axes already grabs mouse input") 

1850 self.mouse_grabber = ax 

1851 

1852 def release_mouse(self, ax): 

1853 """ 

1854 Release the mouse grab held by the `~.axes.Axes` *ax*. 

1855 

1856 Usually called by the widgets. It is ok to call this even if *ax* 

1857 doesn't have the mouse grab currently. 

1858 """ 

1859 if self.mouse_grabber is ax: 

1860 self.mouse_grabber = None 

1861 

1862 def set_cursor(self, cursor): 

1863 """ 

1864 Set the current cursor. 

1865 

1866 This may have no effect if the backend does not display anything. 

1867 

1868 If required by the backend, this method should trigger an update in 

1869 the backend event loop after the cursor is set, as this method may be 

1870 called e.g. before a long-running task during which the GUI is not 

1871 updated. 

1872 

1873 Parameters 

1874 ---------- 

1875 cursor : `.Cursors` 

1876 The cursor to display over the canvas. Note: some backends may 

1877 change the cursor for the entire window. 

1878 """ 

1879 

1880 def draw(self, *args, **kwargs): 

1881 """ 

1882 Render the `.Figure`. 

1883 

1884 This method must walk the artist tree, even if no output is produced, 

1885 because it triggers deferred work that users may want to access 

1886 before saving output to disk. For example computing limits, 

1887 auto-limits, and tick values. 

1888 """ 

1889 

1890 def draw_idle(self, *args, **kwargs): 

1891 """ 

1892 Request a widget redraw once control returns to the GUI event loop. 

1893 

1894 Even if multiple calls to `draw_idle` occur before control returns 

1895 to the GUI event loop, the figure will only be rendered once. 

1896 

1897 Notes 

1898 ----- 

1899 Backends may choose to override the method and implement their own 

1900 strategy to prevent multiple renderings. 

1901 

1902 """ 

1903 if not self._is_idle_drawing: 

1904 with self._idle_draw_cntx(): 

1905 self.draw(*args, **kwargs) 

1906 

1907 @property 

1908 def device_pixel_ratio(self): 

1909 """ 

1910 The ratio of physical to logical pixels used for the canvas on screen. 

1911 

1912 By default, this is 1, meaning physical and logical pixels are the same 

1913 size. Subclasses that support High DPI screens may set this property to 

1914 indicate that said ratio is different. All Matplotlib interaction, 

1915 unless working directly with the canvas, remains in logical pixels. 

1916 

1917 """ 

1918 return self._device_pixel_ratio 

1919 

1920 def _set_device_pixel_ratio(self, ratio): 

1921 """ 

1922 Set the ratio of physical to logical pixels used for the canvas. 

1923 

1924 Subclasses that support High DPI screens can set this property to 

1925 indicate that said ratio is different. The canvas itself will be 

1926 created at the physical size, while the client side will use the 

1927 logical size. Thus the DPI of the Figure will change to be scaled by 

1928 this ratio. Implementations that support High DPI screens should use 

1929 physical pixels for events so that transforms back to Axes space are 

1930 correct. 

1931 

1932 By default, this is 1, meaning physical and logical pixels are the same 

1933 size. 

1934 

1935 Parameters 

1936 ---------- 

1937 ratio : float 

1938 The ratio of logical to physical pixels used for the canvas. 

1939 

1940 Returns 

1941 ------- 

1942 bool 

1943 Whether the ratio has changed. Backends may interpret this as a 

1944 signal to resize the window, repaint the canvas, or change any 

1945 other relevant properties. 

1946 """ 

1947 if self._device_pixel_ratio == ratio: 

1948 return False 

1949 # In cases with mixed resolution displays, we need to be careful if the 

1950 # device pixel ratio changes - in this case we need to resize the 

1951 # canvas accordingly. Some backends provide events that indicate a 

1952 # change in DPI, but those that don't will update this before drawing. 

1953 dpi = ratio * self.figure._original_dpi 

1954 self.figure._set_dpi(dpi, forward=False) 

1955 self._device_pixel_ratio = ratio 

1956 return True 

1957 

1958 def get_width_height(self, *, physical=False): 

1959 """ 

1960 Return the figure width and height in integral points or pixels. 

1961 

1962 When the figure is used on High DPI screens (and the backend supports 

1963 it), the truncation to integers occurs after scaling by the device 

1964 pixel ratio. 

1965 

1966 Parameters 

1967 ---------- 

1968 physical : bool, default: False 

1969 Whether to return true physical pixels or logical pixels. Physical 

1970 pixels may be used by backends that support HiDPI, but still 

1971 configure the canvas using its actual size. 

1972 

1973 Returns 

1974 ------- 

1975 width, height : int 

1976 The size of the figure, in points or pixels, depending on the 

1977 backend. 

1978 """ 

1979 return tuple(int(size / (1 if physical else self.device_pixel_ratio)) 

1980 for size in self.figure.bbox.max) 

1981 

1982 @classmethod 

1983 def get_supported_filetypes(cls): 

1984 """Return dict of savefig file formats supported by this backend.""" 

1985 return cls.filetypes 

1986 

1987 @classmethod 

1988 def get_supported_filetypes_grouped(cls): 

1989 """ 

1990 Return a dict of savefig file formats supported by this backend, 

1991 where the keys are a file type name, such as 'Joint Photographic 

1992 Experts Group', and the values are a list of filename extensions used 

1993 for that filetype, such as ['jpg', 'jpeg']. 

1994 """ 

1995 groupings = {} 

1996 for ext, name in cls.filetypes.items(): 

1997 groupings.setdefault(name, []).append(ext) 

1998 groupings[name].sort() 

1999 return groupings 

2000 

2001 @contextmanager 

2002 def _switch_canvas_and_return_print_method(self, fmt, backend=None): 

2003 """ 

2004 Context manager temporarily setting the canvas for saving the figure:: 

2005 

2006 with canvas._switch_canvas_and_return_print_method(fmt, backend) \\ 

2007 as print_method: 

2008 # ``print_method`` is a suitable ``print_{fmt}`` method, and 

2009 # the figure's canvas is temporarily switched to the method's 

2010 # canvas within the with... block. ``print_method`` is also 

2011 # wrapped to suppress extra kwargs passed by ``print_figure``. 

2012 

2013 Parameters 

2014 ---------- 

2015 fmt : str 

2016 If *backend* is None, then determine a suitable canvas class for 

2017 saving to format *fmt* -- either the current canvas class, if it 

2018 supports *fmt*, or whatever `get_registered_canvas_class` returns; 

2019 switch the figure canvas to that canvas class. 

2020 backend : str or None, default: None 

2021 If not None, switch the figure canvas to the ``FigureCanvas`` class 

2022 of the given backend. 

2023 """ 

2024 canvas = None 

2025 if backend is not None: 

2026 # Return a specific canvas class, if requested. 

2027 from .backends.registry import backend_registry 

2028 canvas_class = backend_registry.load_backend_module(backend).FigureCanvas 

2029 if not hasattr(canvas_class, f"print_{fmt}"): 

2030 raise ValueError( 

2031 f"The {backend!r} backend does not support {fmt} output") 

2032 canvas = canvas_class(self.figure) 

2033 elif hasattr(self, f"print_{fmt}"): 

2034 # Return the current canvas if it supports the requested format. 

2035 canvas = self 

2036 else: 

2037 # Return a default canvas for the requested format, if it exists. 

2038 canvas_class = get_registered_canvas_class(fmt) 

2039 if canvas_class is None: 

2040 raise ValueError( 

2041 "Format {!r} is not supported (supported formats: {})".format( 

2042 fmt, ", ".join(sorted(self.get_supported_filetypes())))) 

2043 canvas = canvas_class(self.figure) 

2044 canvas._is_saving = self._is_saving 

2045 meth = getattr(canvas, f"print_{fmt}") 

2046 mod = (meth.func.__module__ 

2047 if hasattr(meth, "func") # partialmethod, e.g. backend_wx. 

2048 else meth.__module__) 

2049 if mod.startswith(("matplotlib.", "mpl_toolkits.")): 

2050 optional_kws = { # Passed by print_figure for other renderers. 

2051 "dpi", "facecolor", "edgecolor", "orientation", 

2052 "bbox_inches_restore"} 

2053 skip = optional_kws - {*inspect.signature(meth).parameters} 

2054 print_method = functools.wraps(meth)(lambda *args, **kwargs: meth( 

2055 *args, **{k: v for k, v in kwargs.items() if k not in skip})) 

2056 else: # Let third-parties do as they see fit. 

2057 print_method = meth 

2058 try: 

2059 yield print_method 

2060 finally: 

2061 self.figure.canvas = self 

2062 

2063 def print_figure( 

2064 self, filename, dpi=None, facecolor=None, edgecolor=None, 

2065 orientation='portrait', format=None, *, 

2066 bbox_inches=None, pad_inches=None, bbox_extra_artists=None, 

2067 backend=None, **kwargs): 

2068 """ 

2069 Render the figure to hardcopy. Set the figure patch face and edge 

2070 colors. This is useful because some of the GUIs have a gray figure 

2071 face color background and you'll probably want to override this on 

2072 hardcopy. 

2073 

2074 Parameters 

2075 ---------- 

2076 filename : str or path-like or file-like 

2077 The file where the figure is saved. 

2078 

2079 dpi : float, default: :rc:`savefig.dpi` 

2080 The dots per inch to save the figure in. 

2081 

2082 facecolor : :mpltype:`color` or 'auto', default: :rc:`savefig.facecolor` 

2083 The facecolor of the figure. If 'auto', use the current figure 

2084 facecolor. 

2085 

2086 edgecolor : :mpltype:`color` or 'auto', default: :rc:`savefig.edgecolor` 

2087 The edgecolor of the figure. If 'auto', use the current figure 

2088 edgecolor. 

2089 

2090 orientation : {'landscape', 'portrait'}, default: 'portrait' 

2091 Only currently applies to PostScript printing. 

2092 

2093 format : str, optional 

2094 Force a specific file format. If not given, the format is inferred 

2095 from the *filename* extension, and if that fails from 

2096 :rc:`savefig.format`. 

2097 

2098 bbox_inches : 'tight' or `.Bbox`, default: :rc:`savefig.bbox` 

2099 Bounding box in inches: only the given portion of the figure is 

2100 saved. If 'tight', try to figure out the tight bbox of the figure. 

2101 

2102 pad_inches : float or 'layout', default: :rc:`savefig.pad_inches` 

2103 Amount of padding in inches around the figure when bbox_inches is 

2104 'tight'. If 'layout' use the padding from the constrained or 

2105 compressed layout engine; ignored if one of those engines is not in 

2106 use. 

2107 

2108 bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional 

2109 A list of extra artists that will be considered when the 

2110 tight bbox is calculated. 

2111 

2112 backend : str, optional 

2113 Use a non-default backend to render the file, e.g. to render a 

2114 png file with the "cairo" backend rather than the default "agg", 

2115 or a pdf file with the "pgf" backend rather than the default 

2116 "pdf". Note that the default backend is normally sufficient. See 

2117 :ref:`the-builtin-backends` for a list of valid backends for each 

2118 file format. Custom backends can be referenced as "module://...". 

2119 """ 

2120 if format is None: 

2121 # get format from filename, or from backend's default filetype 

2122 if isinstance(filename, os.PathLike): 

2123 filename = os.fspath(filename) 

2124 if isinstance(filename, str): 

2125 format = os.path.splitext(filename)[1][1:] 

2126 if format is None or format == '': 

2127 format = self.get_default_filetype() 

2128 if isinstance(filename, str): 

2129 filename = filename.rstrip('.') + '.' + format 

2130 format = format.lower() 

2131 

2132 if dpi is None: 

2133 dpi = rcParams['savefig.dpi'] 

2134 if dpi == 'figure': 

2135 dpi = getattr(self.figure, '_original_dpi', self.figure.dpi) 

2136 

2137 if kwargs.get("papertype") == 'auto': 

2138 # When deprecation elapses, remove backend_ps._get_papertype & its callers. 

2139 _api.warn_deprecated( 

2140 "3.8", name="papertype='auto'", addendum="Pass an explicit paper type, " 

2141 "'figure', or omit the *papertype* argument entirely.") 

2142 

2143 # Remove the figure manager, if any, to avoid resizing the GUI widget. 

2144 with cbook._setattr_cm(self, manager=None), \ 

2145 self._switch_canvas_and_return_print_method(format, backend) \ 

2146 as print_method, \ 

2147 cbook._setattr_cm(self.figure, dpi=dpi), \ 

2148 cbook._setattr_cm(self.figure.canvas, _device_pixel_ratio=1), \ 

2149 cbook._setattr_cm(self.figure.canvas, _is_saving=True), \ 

2150 ExitStack() as stack: 

2151 

2152 for prop in ["facecolor", "edgecolor"]: 

2153 color = locals()[prop] 

2154 if color is None: 

2155 color = rcParams[f"savefig.{prop}"] 

2156 if not cbook._str_equal(color, "auto"): 

2157 stack.enter_context(self.figure._cm_set(**{prop: color})) 

2158 

2159 if bbox_inches is None: 

2160 bbox_inches = rcParams['savefig.bbox'] 

2161 

2162 layout_engine = self.figure.get_layout_engine() 

2163 if layout_engine is not None or bbox_inches == "tight": 

2164 # we need to trigger a draw before printing to make sure 

2165 # CL works. "tight" also needs a draw to get the right 

2166 # locations: 

2167 renderer = _get_renderer( 

2168 self.figure, 

2169 functools.partial( 

2170 print_method, orientation=orientation) 

2171 ) 

2172 # we do this instead of `self.figure.draw_without_rendering` 

2173 # so that we can inject the orientation 

2174 with getattr(renderer, "_draw_disabled", nullcontext)(): 

2175 self.figure.draw(renderer) 

2176 if bbox_inches: 

2177 if bbox_inches == "tight": 

2178 bbox_inches = self.figure.get_tightbbox( 

2179 renderer, bbox_extra_artists=bbox_extra_artists) 

2180 if (isinstance(layout_engine, ConstrainedLayoutEngine) and 

2181 pad_inches == "layout"): 

2182 h_pad = layout_engine.get()["h_pad"] 

2183 w_pad = layout_engine.get()["w_pad"] 

2184 else: 

2185 if pad_inches in [None, "layout"]: 

2186 pad_inches = rcParams['savefig.pad_inches'] 

2187 h_pad = w_pad = pad_inches 

2188 bbox_inches = bbox_inches.padded(w_pad, h_pad) 

2189 

2190 # call adjust_bbox to save only the given area 

2191 restore_bbox = _tight_bbox.adjust_bbox( 

2192 self.figure, bbox_inches, self.figure.canvas.fixed_dpi) 

2193 

2194 _bbox_inches_restore = (bbox_inches, restore_bbox) 

2195 else: 

2196 _bbox_inches_restore = None 

2197 

2198 # we have already done layout above, so turn it off: 

2199 stack.enter_context(self.figure._cm_set(layout_engine='none')) 

2200 try: 

2201 # _get_renderer may change the figure dpi (as vector formats 

2202 # force the figure dpi to 72), so we need to set it again here. 

2203 with cbook._setattr_cm(self.figure, dpi=dpi): 

2204 result = print_method( 

2205 filename, 

2206 facecolor=facecolor, 

2207 edgecolor=edgecolor, 

2208 orientation=orientation, 

2209 bbox_inches_restore=_bbox_inches_restore, 

2210 **kwargs) 

2211 finally: 

2212 if bbox_inches and restore_bbox: 

2213 restore_bbox() 

2214 

2215 return result 

2216 

2217 @classmethod 

2218 def get_default_filetype(cls): 

2219 """ 

2220 Return the default savefig file format as specified in 

2221 :rc:`savefig.format`. 

2222 

2223 The returned string does not include a period. This method is 

2224 overridden in backends that only support a single file type. 

2225 """ 

2226 return rcParams['savefig.format'] 

2227 

2228 def get_default_filename(self): 

2229 """ 

2230 Return a string, which includes extension, suitable for use as 

2231 a default filename. 

2232 """ 

2233 basename = (self.manager.get_window_title() if self.manager is not None 

2234 else '') 

2235 basename = (basename or 'image').replace(' ', '_') 

2236 filetype = self.get_default_filetype() 

2237 filename = basename + '.' + filetype 

2238 return filename 

2239 

2240 @_api.deprecated("3.8") 

2241 def switch_backends(self, FigureCanvasClass): 

2242 """ 

2243 Instantiate an instance of FigureCanvasClass 

2244 

2245 This is used for backend switching, e.g., to instantiate a 

2246 FigureCanvasPS from a FigureCanvasGTK. Note, deep copying is 

2247 not done, so any changes to one of the instances (e.g., setting 

2248 figure size or line props), will be reflected in the other 

2249 """ 

2250 newCanvas = FigureCanvasClass(self.figure) 

2251 newCanvas._is_saving = self._is_saving 

2252 return newCanvas 

2253 

2254 def mpl_connect(self, s, func): 

2255 """ 

2256 Bind function *func* to event *s*. 

2257 

2258 Parameters 

2259 ---------- 

2260 s : str 

2261 One of the following events ids: 

2262 

2263 - 'button_press_event' 

2264 - 'button_release_event' 

2265 - 'draw_event' 

2266 - 'key_press_event' 

2267 - 'key_release_event' 

2268 - 'motion_notify_event' 

2269 - 'pick_event' 

2270 - 'resize_event' 

2271 - 'scroll_event' 

2272 - 'figure_enter_event', 

2273 - 'figure_leave_event', 

2274 - 'axes_enter_event', 

2275 - 'axes_leave_event' 

2276 - 'close_event'. 

2277 

2278 func : callable 

2279 The callback function to be executed, which must have the 

2280 signature:: 

2281 

2282 def func(event: Event) -> Any 

2283 

2284 For the location events (button and key press/release), if the 

2285 mouse is over the Axes, the ``inaxes`` attribute of the event will 

2286 be set to the `~matplotlib.axes.Axes` the event occurs is over, and 

2287 additionally, the variables ``xdata`` and ``ydata`` attributes will 

2288 be set to the mouse location in data coordinates. See `.KeyEvent` 

2289 and `.MouseEvent` for more info. 

2290 

2291 .. note:: 

2292 

2293 If func is a method, this only stores a weak reference to the 

2294 method. Thus, the figure does not influence the lifetime of 

2295 the associated object. Usually, you want to make sure that the 

2296 object is kept alive throughout the lifetime of the figure by 

2297 holding a reference to it. 

2298 

2299 Returns 

2300 ------- 

2301 cid 

2302 A connection id that can be used with 

2303 `.FigureCanvasBase.mpl_disconnect`. 

2304 

2305 Examples 

2306 -------- 

2307 :: 

2308 

2309 def on_press(event): 

2310 print('you pressed', event.button, event.xdata, event.ydata) 

2311 

2312 cid = canvas.mpl_connect('button_press_event', on_press) 

2313 """ 

2314 

2315 return self.callbacks.connect(s, func) 

2316 

2317 def mpl_disconnect(self, cid): 

2318 """ 

2319 Disconnect the callback with id *cid*. 

2320 

2321 Examples 

2322 -------- 

2323 :: 

2324 

2325 cid = canvas.mpl_connect('button_press_event', on_press) 

2326 # ... later 

2327 canvas.mpl_disconnect(cid) 

2328 """ 

2329 self.callbacks.disconnect(cid) 

2330 

2331 # Internal subclasses can override _timer_cls instead of new_timer, though 

2332 # this is not a public API for third-party subclasses. 

2333 _timer_cls = TimerBase 

2334 

2335 def new_timer(self, interval=None, callbacks=None): 

2336 """ 

2337 Create a new backend-specific subclass of `.Timer`. 

2338 

2339 This is useful for getting periodic events through the backend's native 

2340 event loop. Implemented only for backends with GUIs. 

2341 

2342 Parameters 

2343 ---------- 

2344 interval : int 

2345 Timer interval in milliseconds. 

2346 

2347 callbacks : list[tuple[callable, tuple, dict]] 

2348 Sequence of (func, args, kwargs) where ``func(*args, **kwargs)`` 

2349 will be executed by the timer every *interval*. 

2350 

2351 Callbacks which return ``False`` or ``0`` will be removed from the 

2352 timer. 

2353 

2354 Examples 

2355 -------- 

2356 >>> timer = fig.canvas.new_timer(callbacks=[(f1, (1,), {'a': 3})]) 

2357 """ 

2358 return self._timer_cls(interval=interval, callbacks=callbacks) 

2359 

2360 def flush_events(self): 

2361 """ 

2362 Flush the GUI events for the figure. 

2363 

2364 Interactive backends need to reimplement this method. 

2365 """ 

2366 

2367 def start_event_loop(self, timeout=0): 

2368 """ 

2369 Start a blocking event loop. 

2370 

2371 Such an event loop is used by interactive functions, such as 

2372 `~.Figure.ginput` and `~.Figure.waitforbuttonpress`, to wait for 

2373 events. 

2374 

2375 The event loop blocks until a callback function triggers 

2376 `stop_event_loop`, or *timeout* is reached. 

2377 

2378 If *timeout* is 0 or negative, never timeout. 

2379 

2380 Only interactive backends need to reimplement this method and it relies 

2381 on `flush_events` being properly implemented. 

2382 

2383 Interactive backends should implement this in a more native way. 

2384 """ 

2385 if timeout <= 0: 

2386 timeout = np.inf 

2387 timestep = 0.01 

2388 counter = 0 

2389 self._looping = True 

2390 while self._looping and counter * timestep < timeout: 

2391 self.flush_events() 

2392 time.sleep(timestep) 

2393 counter += 1 

2394 

2395 def stop_event_loop(self): 

2396 """ 

2397 Stop the current blocking event loop. 

2398 

2399 Interactive backends need to reimplement this to match 

2400 `start_event_loop` 

2401 """ 

2402 self._looping = False 

2403 

2404 

2405def key_press_handler(event, canvas=None, toolbar=None): 

2406 """ 

2407 Implement the default Matplotlib key bindings for the canvas and toolbar 

2408 described at :ref:`key-event-handling`. 

2409 

2410 Parameters 

2411 ---------- 

2412 event : `KeyEvent` 

2413 A key press/release event. 

2414 canvas : `FigureCanvasBase`, default: ``event.canvas`` 

2415 The backend-specific canvas instance. This parameter is kept for 

2416 back-compatibility, but, if set, should always be equal to 

2417 ``event.canvas``. 

2418 toolbar : `NavigationToolbar2`, default: ``event.canvas.toolbar`` 

2419 The navigation cursor toolbar. This parameter is kept for 

2420 back-compatibility, but, if set, should always be equal to 

2421 ``event.canvas.toolbar``. 

2422 """ 

2423 if event.key is None: 

2424 return 

2425 if canvas is None: 

2426 canvas = event.canvas 

2427 if toolbar is None: 

2428 toolbar = canvas.toolbar 

2429 

2430 # toggle fullscreen mode (default key 'f', 'ctrl + f') 

2431 if event.key in rcParams['keymap.fullscreen']: 

2432 try: 

2433 canvas.manager.full_screen_toggle() 

2434 except AttributeError: 

2435 pass 

2436 

2437 # quit the figure (default key 'ctrl+w') 

2438 if event.key in rcParams['keymap.quit']: 

2439 Gcf.destroy_fig(canvas.figure) 

2440 if event.key in rcParams['keymap.quit_all']: 

2441 Gcf.destroy_all() 

2442 

2443 if toolbar is not None: 

2444 # home or reset mnemonic (default key 'h', 'home' and 'r') 

2445 if event.key in rcParams['keymap.home']: 

2446 toolbar.home() 

2447 # forward / backward keys to enable left handed quick navigation 

2448 # (default key for backward: 'left', 'backspace' and 'c') 

2449 elif event.key in rcParams['keymap.back']: 

2450 toolbar.back() 

2451 # (default key for forward: 'right' and 'v') 

2452 elif event.key in rcParams['keymap.forward']: 

2453 toolbar.forward() 

2454 # pan mnemonic (default key 'p') 

2455 elif event.key in rcParams['keymap.pan']: 

2456 toolbar.pan() 

2457 toolbar._update_cursor(event) 

2458 # zoom mnemonic (default key 'o') 

2459 elif event.key in rcParams['keymap.zoom']: 

2460 toolbar.zoom() 

2461 toolbar._update_cursor(event) 

2462 # saving current figure (default key 's') 

2463 elif event.key in rcParams['keymap.save']: 

2464 toolbar.save_figure() 

2465 

2466 if event.inaxes is None: 

2467 return 

2468 

2469 # these bindings require the mouse to be over an Axes to trigger 

2470 def _get_uniform_gridstate(ticks): 

2471 # Return True/False if all grid lines are on or off, None if they are 

2472 # not all in the same state. 

2473 return (True if all(tick.gridline.get_visible() for tick in ticks) else 

2474 False if not any(tick.gridline.get_visible() for tick in ticks) else 

2475 None) 

2476 

2477 ax = event.inaxes 

2478 # toggle major grids in current Axes (default key 'g') 

2479 # Both here and below (for 'G'), we do nothing if *any* grid (major or 

2480 # minor, x or y) is not in a uniform state, to avoid messing up user 

2481 # customization. 

2482 if (event.key in rcParams['keymap.grid'] 

2483 # Exclude minor grids not in a uniform state. 

2484 and None not in [_get_uniform_gridstate(ax.xaxis.minorTicks), 

2485 _get_uniform_gridstate(ax.yaxis.minorTicks)]): 

2486 x_state = _get_uniform_gridstate(ax.xaxis.majorTicks) 

2487 y_state = _get_uniform_gridstate(ax.yaxis.majorTicks) 

2488 cycle = [(False, False), (True, False), (True, True), (False, True)] 

2489 try: 

2490 x_state, y_state = ( 

2491 cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)]) 

2492 except ValueError: 

2493 # Exclude major grids not in a uniform state. 

2494 pass 

2495 else: 

2496 # If turning major grids off, also turn minor grids off. 

2497 ax.grid(x_state, which="major" if x_state else "both", axis="x") 

2498 ax.grid(y_state, which="major" if y_state else "both", axis="y") 

2499 canvas.draw_idle() 

2500 # toggle major and minor grids in current Axes (default key 'G') 

2501 if (event.key in rcParams['keymap.grid_minor'] 

2502 # Exclude major grids not in a uniform state. 

2503 and None not in [_get_uniform_gridstate(ax.xaxis.majorTicks), 

2504 _get_uniform_gridstate(ax.yaxis.majorTicks)]): 

2505 x_state = _get_uniform_gridstate(ax.xaxis.minorTicks) 

2506 y_state = _get_uniform_gridstate(ax.yaxis.minorTicks) 

2507 cycle = [(False, False), (True, False), (True, True), (False, True)] 

2508 try: 

2509 x_state, y_state = ( 

2510 cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)]) 

2511 except ValueError: 

2512 # Exclude minor grids not in a uniform state. 

2513 pass 

2514 else: 

2515 ax.grid(x_state, which="both", axis="x") 

2516 ax.grid(y_state, which="both", axis="y") 

2517 canvas.draw_idle() 

2518 # toggle scaling of y-axes between 'log and 'linear' (default key 'l') 

2519 elif event.key in rcParams['keymap.yscale']: 

2520 scale = ax.get_yscale() 

2521 if scale == 'log': 

2522 ax.set_yscale('linear') 

2523 ax.figure.canvas.draw_idle() 

2524 elif scale == 'linear': 

2525 try: 

2526 ax.set_yscale('log') 

2527 except ValueError as exc: 

2528 _log.warning(str(exc)) 

2529 ax.set_yscale('linear') 

2530 ax.figure.canvas.draw_idle() 

2531 # toggle scaling of x-axes between 'log and 'linear' (default key 'k') 

2532 elif event.key in rcParams['keymap.xscale']: 

2533 scalex = ax.get_xscale() 

2534 if scalex == 'log': 

2535 ax.set_xscale('linear') 

2536 ax.figure.canvas.draw_idle() 

2537 elif scalex == 'linear': 

2538 try: 

2539 ax.set_xscale('log') 

2540 except ValueError as exc: 

2541 _log.warning(str(exc)) 

2542 ax.set_xscale('linear') 

2543 ax.figure.canvas.draw_idle() 

2544 

2545 

2546def button_press_handler(event, canvas=None, toolbar=None): 

2547 """ 

2548 The default Matplotlib button actions for extra mouse buttons. 

2549 

2550 Parameters are as for `key_press_handler`, except that *event* is a 

2551 `MouseEvent`. 

2552 """ 

2553 if canvas is None: 

2554 canvas = event.canvas 

2555 if toolbar is None: 

2556 toolbar = canvas.toolbar 

2557 if toolbar is not None: 

2558 button_name = str(MouseButton(event.button)) 

2559 if button_name in rcParams['keymap.back']: 

2560 toolbar.back() 

2561 elif button_name in rcParams['keymap.forward']: 

2562 toolbar.forward() 

2563 

2564 

2565class NonGuiException(Exception): 

2566 """Raised when trying show a figure in a non-GUI backend.""" 

2567 pass 

2568 

2569 

2570class FigureManagerBase: 

2571 """ 

2572 A backend-independent abstraction of a figure container and controller. 

2573 

2574 The figure manager is used by pyplot to interact with the window in a 

2575 backend-independent way. It's an adapter for the real (GUI) framework that 

2576 represents the visual figure on screen. 

2577 

2578 The figure manager is connected to a specific canvas instance, which in turn 

2579 is connected to a specific figure instance. To access a figure manager for 

2580 a given figure in user code, you typically use ``fig.canvas.manager``. 

2581 

2582 GUI backends derive from this class to translate common operations such 

2583 as *show* or *resize* to the GUI-specific code. Non-GUI backends do not 

2584 support these operations and can just use the base class. 

2585 

2586 This following basic operations are accessible: 

2587 

2588 **Window operations** 

2589 

2590 - `~.FigureManagerBase.show` 

2591 - `~.FigureManagerBase.destroy` 

2592 - `~.FigureManagerBase.full_screen_toggle` 

2593 - `~.FigureManagerBase.resize` 

2594 - `~.FigureManagerBase.get_window_title` 

2595 - `~.FigureManagerBase.set_window_title` 

2596 

2597 **Key and mouse button press handling** 

2598 

2599 The figure manager sets up default key and mouse button press handling by 

2600 hooking up the `.key_press_handler` to the matplotlib event system. This 

2601 ensures the same shortcuts and mouse actions across backends. 

2602 

2603 **Other operations** 

2604 

2605 Subclasses will have additional attributes and functions to access 

2606 additional functionality. This is of course backend-specific. For example, 

2607 most GUI backends have ``window`` and ``toolbar`` attributes that give 

2608 access to the native GUI widgets of the respective framework. 

2609 

2610 Attributes 

2611 ---------- 

2612 canvas : `FigureCanvasBase` 

2613 The backend-specific canvas instance. 

2614 

2615 num : int or str 

2616 The figure number. 

2617 

2618 key_press_handler_id : int 

2619 The default key handler cid, when using the toolmanager. 

2620 To disable the default key press handling use:: 

2621 

2622 figure.canvas.mpl_disconnect( 

2623 figure.canvas.manager.key_press_handler_id) 

2624 

2625 button_press_handler_id : int 

2626 The default mouse button handler cid, when using the toolmanager. 

2627 To disable the default button press handling use:: 

2628 

2629 figure.canvas.mpl_disconnect( 

2630 figure.canvas.manager.button_press_handler_id) 

2631 """ 

2632 

2633 _toolbar2_class = None 

2634 _toolmanager_toolbar_class = None 

2635 

2636 def __init__(self, canvas, num): 

2637 self.canvas = canvas 

2638 canvas.manager = self # store a pointer to parent 

2639 self.num = num 

2640 self.set_window_title(f"Figure {num:d}") 

2641 

2642 self.key_press_handler_id = None 

2643 self.button_press_handler_id = None 

2644 if rcParams['toolbar'] != 'toolmanager': 

2645 self.key_press_handler_id = self.canvas.mpl_connect( 

2646 'key_press_event', key_press_handler) 

2647 self.button_press_handler_id = self.canvas.mpl_connect( 

2648 'button_press_event', button_press_handler) 

2649 

2650 self.toolmanager = (ToolManager(canvas.figure) 

2651 if mpl.rcParams['toolbar'] == 'toolmanager' 

2652 else None) 

2653 if (mpl.rcParams["toolbar"] == "toolbar2" 

2654 and self._toolbar2_class): 

2655 self.toolbar = self._toolbar2_class(self.canvas) 

2656 elif (mpl.rcParams["toolbar"] == "toolmanager" 

2657 and self._toolmanager_toolbar_class): 

2658 self.toolbar = self._toolmanager_toolbar_class(self.toolmanager) 

2659 else: 

2660 self.toolbar = None 

2661 

2662 if self.toolmanager: 

2663 tools.add_tools_to_manager(self.toolmanager) 

2664 if self.toolbar: 

2665 tools.add_tools_to_container(self.toolbar) 

2666 

2667 @self.canvas.figure.add_axobserver 

2668 def notify_axes_change(fig): 

2669 # Called whenever the current Axes is changed. 

2670 if self.toolmanager is None and self.toolbar is not None: 

2671 self.toolbar.update() 

2672 

2673 @classmethod 

2674 def create_with_canvas(cls, canvas_class, figure, num): 

2675 """ 

2676 Create a manager for a given *figure* using a specific *canvas_class*. 

2677 

2678 Backends should override this method if they have specific needs for 

2679 setting up the canvas or the manager. 

2680 """ 

2681 return cls(canvas_class(figure), num) 

2682 

2683 @classmethod 

2684 def start_main_loop(cls): 

2685 """ 

2686 Start the main event loop. 

2687 

2688 This method is called by `.FigureManagerBase.pyplot_show`, which is the 

2689 implementation of `.pyplot.show`. To customize the behavior of 

2690 `.pyplot.show`, interactive backends should usually override 

2691 `~.FigureManagerBase.start_main_loop`; if more customized logic is 

2692 necessary, `~.FigureManagerBase.pyplot_show` can also be overridden. 

2693 """ 

2694 

2695 @classmethod 

2696 def pyplot_show(cls, *, block=None): 

2697 """ 

2698 Show all figures. This method is the implementation of `.pyplot.show`. 

2699 

2700 To customize the behavior of `.pyplot.show`, interactive backends 

2701 should usually override `~.FigureManagerBase.start_main_loop`; if more 

2702 customized logic is necessary, `~.FigureManagerBase.pyplot_show` can 

2703 also be overridden. 

2704 

2705 Parameters 

2706 ---------- 

2707 block : bool, optional 

2708 Whether to block by calling ``start_main_loop``. The default, 

2709 None, means to block if we are neither in IPython's ``%pylab`` mode 

2710 nor in ``interactive`` mode. 

2711 """ 

2712 managers = Gcf.get_all_fig_managers() 

2713 if not managers: 

2714 return 

2715 for manager in managers: 

2716 try: 

2717 manager.show() # Emits a warning for non-interactive backend. 

2718 except NonGuiException as exc: 

2719 _api.warn_external(str(exc)) 

2720 if block is None: 

2721 # Hack: Are we in IPython's %pylab mode? In pylab mode, IPython 

2722 # (>= 0.10) tacks a _needmain attribute onto pyplot.show (always 

2723 # set to False). 

2724 pyplot_show = getattr(sys.modules.get("matplotlib.pyplot"), "show", None) 

2725 ipython_pylab = hasattr(pyplot_show, "_needmain") 

2726 block = not ipython_pylab and not is_interactive() 

2727 if block: 

2728 cls.start_main_loop() 

2729 

2730 def show(self): 

2731 """ 

2732 For GUI backends, show the figure window and redraw. 

2733 For non-GUI backends, raise an exception, unless running headless (i.e. 

2734 on Linux with an unset DISPLAY); this exception is converted to a 

2735 warning in `.Figure.show`. 

2736 """ 

2737 # This should be overridden in GUI backends. 

2738 if sys.platform == "linux" and not os.environ.get("DISPLAY"): 

2739 # We cannot check _get_running_interactive_framework() == 

2740 # "headless" because that would also suppress the warning when 

2741 # $DISPLAY exists but is invalid, which is more likely an error and 

2742 # thus warrants a warning. 

2743 return 

2744 raise NonGuiException( 

2745 f"{type(self.canvas).__name__} is non-interactive, and thus cannot be " 

2746 f"shown") 

2747 

2748 def destroy(self): 

2749 pass 

2750 

2751 def full_screen_toggle(self): 

2752 pass 

2753 

2754 def resize(self, w, h): 

2755 """For GUI backends, resize the window (in physical pixels).""" 

2756 

2757 def get_window_title(self): 

2758 """ 

2759 Return the title text of the window containing the figure, or None 

2760 if there is no window (e.g., a PS backend). 

2761 """ 

2762 return 'image' 

2763 

2764 def set_window_title(self, title): 

2765 """ 

2766 Set the title text of the window containing the figure. 

2767 

2768 This has no effect for non-GUI (e.g., PS) backends. 

2769 

2770 Examples 

2771 -------- 

2772 >>> fig = plt.figure() 

2773 >>> fig.canvas.manager.set_window_title('My figure') 

2774 """ 

2775 

2776 

2777cursors = tools.cursors 

2778 

2779 

2780class _Mode(str, Enum): 

2781 NONE = "" 

2782 PAN = "pan/zoom" 

2783 ZOOM = "zoom rect" 

2784 

2785 def __str__(self): 

2786 return self.value 

2787 

2788 @property 

2789 def _navigate_mode(self): 

2790 return self.name if self is not _Mode.NONE else None 

2791 

2792 

2793class NavigationToolbar2: 

2794 """ 

2795 Base class for the navigation cursor, version 2. 

2796 

2797 Backends must implement a canvas that handles connections for 

2798 'button_press_event' and 'button_release_event'. See 

2799 :meth:`FigureCanvasBase.mpl_connect` for more information. 

2800 

2801 They must also define 

2802 

2803 :meth:`save_figure` 

2804 Save the current figure. 

2805 

2806 :meth:`draw_rubberband` (optional) 

2807 Draw the zoom to rect "rubberband" rectangle. 

2808 

2809 :meth:`set_message` (optional) 

2810 Display message. 

2811 

2812 :meth:`set_history_buttons` (optional) 

2813 You can change the history back / forward buttons to indicate disabled / enabled 

2814 state. 

2815 

2816 and override ``__init__`` to set up the toolbar -- without forgetting to 

2817 call the base-class init. Typically, ``__init__`` needs to set up toolbar 

2818 buttons connected to the `home`, `back`, `forward`, `pan`, `zoom`, and 

2819 `save_figure` methods and using standard icons in the "images" subdirectory 

2820 of the data path. 

2821 

2822 That's it, we'll do the rest! 

2823 """ 

2824 

2825 # list of toolitems to add to the toolbar, format is: 

2826 # ( 

2827 # text, # the text of the button (often not visible to users) 

2828 # tooltip_text, # the tooltip shown on hover (where possible) 

2829 # image_file, # name of the image for the button (without the extension) 

2830 # name_of_method, # name of the method in NavigationToolbar2 to call 

2831 # ) 

2832 toolitems = ( 

2833 ('Home', 'Reset original view', 'home', 'home'), 

2834 ('Back', 'Back to previous view', 'back', 'back'), 

2835 ('Forward', 'Forward to next view', 'forward', 'forward'), 

2836 (None, None, None, None), 

2837 ('Pan', 

2838 'Left button pans, Right button zooms\n' 

2839 'x/y fixes axis, CTRL fixes aspect', 

2840 'move', 'pan'), 

2841 ('Zoom', 'Zoom to rectangle\nx/y fixes axis', 'zoom_to_rect', 'zoom'), 

2842 ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'), 

2843 (None, None, None, None), 

2844 ('Save', 'Save the figure', 'filesave', 'save_figure'), 

2845 ) 

2846 

2847 def __init__(self, canvas): 

2848 self.canvas = canvas 

2849 canvas.toolbar = self 

2850 self._nav_stack = cbook._Stack() 

2851 # This cursor will be set after the initial draw. 

2852 self._last_cursor = tools.Cursors.POINTER 

2853 

2854 self._id_press = self.canvas.mpl_connect( 

2855 'button_press_event', self._zoom_pan_handler) 

2856 self._id_release = self.canvas.mpl_connect( 

2857 'button_release_event', self._zoom_pan_handler) 

2858 self._id_drag = self.canvas.mpl_connect( 

2859 'motion_notify_event', self.mouse_move) 

2860 self._pan_info = None 

2861 self._zoom_info = None 

2862 

2863 self.mode = _Mode.NONE # a mode string for the status bar 

2864 self.set_history_buttons() 

2865 

2866 def set_message(self, s): 

2867 """Display a message on toolbar or in status bar.""" 

2868 

2869 def draw_rubberband(self, event, x0, y0, x1, y1): 

2870 """ 

2871 Draw a rectangle rubberband to indicate zoom limits. 

2872 

2873 Note that it is not guaranteed that ``x0 <= x1`` and ``y0 <= y1``. 

2874 """ 

2875 

2876 def remove_rubberband(self): 

2877 """Remove the rubberband.""" 

2878 

2879 def home(self, *args): 

2880 """ 

2881 Restore the original view. 

2882 

2883 For convenience of being directly connected as a GUI callback, which 

2884 often get passed additional parameters, this method accepts arbitrary 

2885 parameters, but does not use them. 

2886 """ 

2887 self._nav_stack.home() 

2888 self.set_history_buttons() 

2889 self._update_view() 

2890 

2891 def back(self, *args): 

2892 """ 

2893 Move back up the view lim stack. 

2894 

2895 For convenience of being directly connected as a GUI callback, which 

2896 often get passed additional parameters, this method accepts arbitrary 

2897 parameters, but does not use them. 

2898 """ 

2899 self._nav_stack.back() 

2900 self.set_history_buttons() 

2901 self._update_view() 

2902 

2903 def forward(self, *args): 

2904 """ 

2905 Move forward in the view lim stack. 

2906 

2907 For convenience of being directly connected as a GUI callback, which 

2908 often get passed additional parameters, this method accepts arbitrary 

2909 parameters, but does not use them. 

2910 """ 

2911 self._nav_stack.forward() 

2912 self.set_history_buttons() 

2913 self._update_view() 

2914 

2915 def _update_cursor(self, event): 

2916 """ 

2917 Update the cursor after a mouse move event or a tool (de)activation. 

2918 """ 

2919 if self.mode and event.inaxes and event.inaxes.get_navigate(): 

2920 if (self.mode == _Mode.ZOOM 

2921 and self._last_cursor != tools.Cursors.SELECT_REGION): 

2922 self.canvas.set_cursor(tools.Cursors.SELECT_REGION) 

2923 self._last_cursor = tools.Cursors.SELECT_REGION 

2924 elif (self.mode == _Mode.PAN 

2925 and self._last_cursor != tools.Cursors.MOVE): 

2926 self.canvas.set_cursor(tools.Cursors.MOVE) 

2927 self._last_cursor = tools.Cursors.MOVE 

2928 elif self._last_cursor != tools.Cursors.POINTER: 

2929 self.canvas.set_cursor(tools.Cursors.POINTER) 

2930 self._last_cursor = tools.Cursors.POINTER 

2931 

2932 @contextmanager 

2933 def _wait_cursor_for_draw_cm(self): 

2934 """ 

2935 Set the cursor to a wait cursor when drawing the canvas. 

2936 

2937 In order to avoid constantly changing the cursor when the canvas 

2938 changes frequently, do nothing if this context was triggered during the 

2939 last second. (Optimally we'd prefer only setting the wait cursor if 

2940 the *current* draw takes too long, but the current draw blocks the GUI 

2941 thread). 

2942 """ 

2943 self._draw_time, last_draw_time = ( 

2944 time.time(), getattr(self, "_draw_time", -np.inf)) 

2945 if self._draw_time - last_draw_time > 1: 

2946 try: 

2947 self.canvas.set_cursor(tools.Cursors.WAIT) 

2948 yield 

2949 finally: 

2950 self.canvas.set_cursor(self._last_cursor) 

2951 else: 

2952 yield 

2953 

2954 @staticmethod 

2955 def _mouse_event_to_message(event): 

2956 if event.inaxes and event.inaxes.get_navigate(): 

2957 try: 

2958 s = event.inaxes.format_coord(event.xdata, event.ydata) 

2959 except (ValueError, OverflowError): 

2960 pass 

2961 else: 

2962 s = s.rstrip() 

2963 artists = [a for a in event.inaxes._mouseover_set 

2964 if a.contains(event)[0] and a.get_visible()] 

2965 if artists: 

2966 a = cbook._topmost_artist(artists) 

2967 if a is not event.inaxes.patch: 

2968 data = a.get_cursor_data(event) 

2969 if data is not None: 

2970 data_str = a.format_cursor_data(data).rstrip() 

2971 if data_str: 

2972 s = s + '\n' + data_str 

2973 return s 

2974 return "" 

2975 

2976 def mouse_move(self, event): 

2977 self._update_cursor(event) 

2978 self.set_message(self._mouse_event_to_message(event)) 

2979 

2980 def _zoom_pan_handler(self, event): 

2981 if self.mode == _Mode.PAN: 

2982 if event.name == "button_press_event": 

2983 self.press_pan(event) 

2984 elif event.name == "button_release_event": 

2985 self.release_pan(event) 

2986 if self.mode == _Mode.ZOOM: 

2987 if event.name == "button_press_event": 

2988 self.press_zoom(event) 

2989 elif event.name == "button_release_event": 

2990 self.release_zoom(event) 

2991 

2992 def _start_event_axes_interaction(self, event, *, method): 

2993 

2994 def _ax_filter(ax): 

2995 return (ax.in_axes(event) and 

2996 ax.get_navigate() and 

2997 getattr(ax, f"can_{method}")() 

2998 ) 

2999 

3000 def _capture_events(ax): 

3001 f = ax.get_forward_navigation_events() 

3002 if f == "auto": # (capture = patch visibility) 

3003 f = not ax.patch.get_visible() 

3004 return not f 

3005 

3006 # get all relevant axes for the event 

3007 axes = list(filter(_ax_filter, self.canvas.figure.get_axes())) 

3008 

3009 if len(axes) == 0: 

3010 return [] 

3011 

3012 if self._nav_stack() is None: 

3013 self.push_current() # Set the home button to this view. 

3014 

3015 # group axes by zorder (reverse to trigger later axes first) 

3016 grps = dict() 

3017 for ax in reversed(axes): 

3018 grps.setdefault(ax.get_zorder(), []).append(ax) 

3019 

3020 axes_to_trigger = [] 

3021 # go through zorders in reverse until we hit a capturing axes 

3022 for zorder in sorted(grps, reverse=True): 

3023 for ax in grps[zorder]: 

3024 axes_to_trigger.append(ax) 

3025 # NOTE: shared axes are automatically triggered, but twin-axes not! 

3026 axes_to_trigger.extend(ax._twinned_axes.get_siblings(ax)) 

3027 

3028 if _capture_events(ax): 

3029 break # break if we hit a capturing axes 

3030 else: 

3031 # If the inner loop finished without an explicit break, 

3032 # (e.g. no capturing axes was found) continue the 

3033 # outer loop to the next zorder. 

3034 continue 

3035 

3036 # If the inner loop was terminated with an explicit break, 

3037 # terminate the outer loop as well. 

3038 break 

3039 

3040 # avoid duplicated triggers (but keep order of list) 

3041 axes_to_trigger = list(dict.fromkeys(axes_to_trigger)) 

3042 

3043 return axes_to_trigger 

3044 

3045 def pan(self, *args): 

3046 """ 

3047 Toggle the pan/zoom tool. 

3048 

3049 Pan with left button, zoom with right. 

3050 """ 

3051 if not self.canvas.widgetlock.available(self): 

3052 self.set_message("pan unavailable") 

3053 return 

3054 if self.mode == _Mode.PAN: 

3055 self.mode = _Mode.NONE 

3056 self.canvas.widgetlock.release(self) 

3057 else: 

3058 self.mode = _Mode.PAN 

3059 self.canvas.widgetlock(self) 

3060 for a in self.canvas.figure.get_axes(): 

3061 a.set_navigate_mode(self.mode._navigate_mode) 

3062 

3063 _PanInfo = namedtuple("_PanInfo", "button axes cid") 

3064 

3065 def press_pan(self, event): 

3066 """Callback for mouse button press in pan/zoom mode.""" 

3067 if (event.button not in [MouseButton.LEFT, MouseButton.RIGHT] 

3068 or event.x is None or event.y is None): 

3069 return 

3070 

3071 axes = self._start_event_axes_interaction(event, method="pan") 

3072 if not axes: 

3073 return 

3074 

3075 # call "ax.start_pan(..)" on all relevant axes of an event 

3076 for ax in axes: 

3077 ax.start_pan(event.x, event.y, event.button) 

3078 

3079 self.canvas.mpl_disconnect(self._id_drag) 

3080 id_drag = self.canvas.mpl_connect("motion_notify_event", self.drag_pan) 

3081 

3082 self._pan_info = self._PanInfo( 

3083 button=event.button, axes=axes, cid=id_drag) 

3084 

3085 def drag_pan(self, event): 

3086 """Callback for dragging in pan/zoom mode.""" 

3087 for ax in self._pan_info.axes: 

3088 # Using the recorded button at the press is safer than the current 

3089 # button, as multiple buttons can get pressed during motion. 

3090 ax.drag_pan(self._pan_info.button, event.key, event.x, event.y) 

3091 self.canvas.draw_idle() 

3092 

3093 def release_pan(self, event): 

3094 """Callback for mouse button release in pan/zoom mode.""" 

3095 if self._pan_info is None: 

3096 return 

3097 self.canvas.mpl_disconnect(self._pan_info.cid) 

3098 self._id_drag = self.canvas.mpl_connect( 

3099 'motion_notify_event', self.mouse_move) 

3100 for ax in self._pan_info.axes: 

3101 ax.end_pan() 

3102 self.canvas.draw_idle() 

3103 self._pan_info = None 

3104 self.push_current() 

3105 

3106 def zoom(self, *args): 

3107 if not self.canvas.widgetlock.available(self): 

3108 self.set_message("zoom unavailable") 

3109 return 

3110 """Toggle zoom to rect mode.""" 

3111 if self.mode == _Mode.ZOOM: 

3112 self.mode = _Mode.NONE 

3113 self.canvas.widgetlock.release(self) 

3114 else: 

3115 self.mode = _Mode.ZOOM 

3116 self.canvas.widgetlock(self) 

3117 for a in self.canvas.figure.get_axes(): 

3118 a.set_navigate_mode(self.mode._navigate_mode) 

3119 

3120 _ZoomInfo = namedtuple("_ZoomInfo", "direction start_xy axes cid cbar") 

3121 

3122 def press_zoom(self, event): 

3123 """Callback for mouse button press in zoom to rect mode.""" 

3124 if (event.button not in [MouseButton.LEFT, MouseButton.RIGHT] 

3125 or event.x is None or event.y is None): 

3126 return 

3127 

3128 axes = self._start_event_axes_interaction(event, method="zoom") 

3129 if not axes: 

3130 return 

3131 

3132 id_zoom = self.canvas.mpl_connect( 

3133 "motion_notify_event", self.drag_zoom) 

3134 

3135 # A colorbar is one-dimensional, so we extend the zoom rectangle out 

3136 # to the edge of the Axes bbox in the other dimension. To do that we 

3137 # store the orientation of the colorbar for later. 

3138 parent_ax = axes[0] 

3139 if hasattr(parent_ax, "_colorbar"): 

3140 cbar = parent_ax._colorbar.orientation 

3141 else: 

3142 cbar = None 

3143 

3144 self._zoom_info = self._ZoomInfo( 

3145 direction="in" if event.button == 1 else "out", 

3146 start_xy=(event.x, event.y), axes=axes, cid=id_zoom, cbar=cbar) 

3147 

3148 def drag_zoom(self, event): 

3149 """Callback for dragging in zoom mode.""" 

3150 start_xy = self._zoom_info.start_xy 

3151 ax = self._zoom_info.axes[0] 

3152 (x1, y1), (x2, y2) = np.clip( 

3153 [start_xy, [event.x, event.y]], ax.bbox.min, ax.bbox.max) 

3154 key = event.key 

3155 # Force the key on colorbars to extend the short-axis bbox 

3156 if self._zoom_info.cbar == "horizontal": 

3157 key = "x" 

3158 elif self._zoom_info.cbar == "vertical": 

3159 key = "y" 

3160 if key == "x": 

3161 y1, y2 = ax.bbox.intervaly 

3162 elif key == "y": 

3163 x1, x2 = ax.bbox.intervalx 

3164 

3165 self.draw_rubberband(event, x1, y1, x2, y2) 

3166 

3167 def release_zoom(self, event): 

3168 """Callback for mouse button release in zoom to rect mode.""" 

3169 if self._zoom_info is None: 

3170 return 

3171 

3172 # We don't check the event button here, so that zooms can be cancelled 

3173 # by (pressing and) releasing another mouse button. 

3174 self.canvas.mpl_disconnect(self._zoom_info.cid) 

3175 self.remove_rubberband() 

3176 

3177 start_x, start_y = self._zoom_info.start_xy 

3178 key = event.key 

3179 # Force the key on colorbars to ignore the zoom-cancel on the 

3180 # short-axis side 

3181 if self._zoom_info.cbar == "horizontal": 

3182 key = "x" 

3183 elif self._zoom_info.cbar == "vertical": 

3184 key = "y" 

3185 # Ignore single clicks: 5 pixels is a threshold that allows the user to 

3186 # "cancel" a zoom action by zooming by less than 5 pixels. 

3187 if ((abs(event.x - start_x) < 5 and key != "y") or 

3188 (abs(event.y - start_y) < 5 and key != "x")): 

3189 self.canvas.draw_idle() 

3190 self._zoom_info = None 

3191 return 

3192 

3193 for i, ax in enumerate(self._zoom_info.axes): 

3194 # Detect whether this Axes is twinned with an earlier Axes in the 

3195 # list of zoomed Axes, to avoid double zooming. 

3196 twinx = any(ax.get_shared_x_axes().joined(ax, prev) 

3197 for prev in self._zoom_info.axes[:i]) 

3198 twiny = any(ax.get_shared_y_axes().joined(ax, prev) 

3199 for prev in self._zoom_info.axes[:i]) 

3200 ax._set_view_from_bbox( 

3201 (start_x, start_y, event.x, event.y), 

3202 self._zoom_info.direction, key, twinx, twiny) 

3203 

3204 self.canvas.draw_idle() 

3205 self._zoom_info = None 

3206 self.push_current() 

3207 

3208 def push_current(self): 

3209 """Push the current view limits and position onto the stack.""" 

3210 self._nav_stack.push( 

3211 WeakKeyDictionary( 

3212 {ax: (ax._get_view(), 

3213 # Store both the original and modified positions. 

3214 (ax.get_position(True).frozen(), 

3215 ax.get_position().frozen())) 

3216 for ax in self.canvas.figure.axes})) 

3217 self.set_history_buttons() 

3218 

3219 def _update_view(self): 

3220 """ 

3221 Update the viewlim and position from the view and position stack for 

3222 each Axes. 

3223 """ 

3224 nav_info = self._nav_stack() 

3225 if nav_info is None: 

3226 return 

3227 # Retrieve all items at once to avoid any risk of GC deleting an Axes 

3228 # while in the middle of the loop below. 

3229 items = list(nav_info.items()) 

3230 for ax, (view, (pos_orig, pos_active)) in items: 

3231 ax._set_view(view) 

3232 # Restore both the original and modified positions 

3233 ax._set_position(pos_orig, 'original') 

3234 ax._set_position(pos_active, 'active') 

3235 self.canvas.draw_idle() 

3236 

3237 def configure_subplots(self, *args): 

3238 if hasattr(self, "subplot_tool"): 

3239 self.subplot_tool.figure.canvas.manager.show() 

3240 return 

3241 # This import needs to happen here due to circular imports. 

3242 from matplotlib.figure import Figure 

3243 with mpl.rc_context({"toolbar": "none"}): # No navbar for the toolfig. 

3244 manager = type(self.canvas).new_manager(Figure(figsize=(6, 3)), -1) 

3245 manager.set_window_title("Subplot configuration tool") 

3246 tool_fig = manager.canvas.figure 

3247 tool_fig.subplots_adjust(top=0.9) 

3248 self.subplot_tool = widgets.SubplotTool(self.canvas.figure, tool_fig) 

3249 cid = self.canvas.mpl_connect( 

3250 "close_event", lambda e: manager.destroy()) 

3251 

3252 def on_tool_fig_close(e): 

3253 self.canvas.mpl_disconnect(cid) 

3254 del self.subplot_tool 

3255 

3256 tool_fig.canvas.mpl_connect("close_event", on_tool_fig_close) 

3257 manager.show() 

3258 return self.subplot_tool 

3259 

3260 def save_figure(self, *args): 

3261 """Save the current figure.""" 

3262 raise NotImplementedError 

3263 

3264 def update(self): 

3265 """Reset the Axes stack.""" 

3266 self._nav_stack.clear() 

3267 self.set_history_buttons() 

3268 

3269 def set_history_buttons(self): 

3270 """Enable or disable the back/forward button.""" 

3271 

3272 

3273class ToolContainerBase: 

3274 """ 

3275 Base class for all tool containers, e.g. toolbars. 

3276 

3277 Attributes 

3278 ---------- 

3279 toolmanager : `.ToolManager` 

3280 The tools with which this `ToolContainer` wants to communicate. 

3281 """ 

3282 

3283 _icon_extension = '.png' 

3284 """ 

3285 Toolcontainer button icon image format extension 

3286 

3287 **String**: Image extension 

3288 """ 

3289 

3290 def __init__(self, toolmanager): 

3291 self.toolmanager = toolmanager 

3292 toolmanager.toolmanager_connect( 

3293 'tool_message_event', 

3294 lambda event: self.set_message(event.message)) 

3295 toolmanager.toolmanager_connect( 

3296 'tool_removed_event', 

3297 lambda event: self.remove_toolitem(event.tool.name)) 

3298 

3299 def _tool_toggled_cbk(self, event): 

3300 """ 

3301 Capture the 'tool_trigger_[name]' 

3302 

3303 This only gets used for toggled tools. 

3304 """ 

3305 self.toggle_toolitem(event.tool.name, event.tool.toggled) 

3306 

3307 def add_tool(self, tool, group, position=-1): 

3308 """ 

3309 Add a tool to this container. 

3310 

3311 Parameters 

3312 ---------- 

3313 tool : tool_like 

3314 The tool to add, see `.ToolManager.get_tool`. 

3315 group : str 

3316 The name of the group to add this tool to. 

3317 position : int, default: -1 

3318 The position within the group to place this tool. 

3319 """ 

3320 tool = self.toolmanager.get_tool(tool) 

3321 image = self._get_image_filename(tool) 

3322 toggle = getattr(tool, 'toggled', None) is not None 

3323 self.add_toolitem(tool.name, group, position, 

3324 image, tool.description, toggle) 

3325 if toggle: 

3326 self.toolmanager.toolmanager_connect('tool_trigger_%s' % tool.name, 

3327 self._tool_toggled_cbk) 

3328 # If initially toggled 

3329 if tool.toggled: 

3330 self.toggle_toolitem(tool.name, True) 

3331 

3332 def _get_image_filename(self, tool): 

3333 """Resolve a tool icon's filename.""" 

3334 if not tool.image: 

3335 return None 

3336 if os.path.isabs(tool.image): 

3337 filename = tool.image 

3338 else: 

3339 if "image" in getattr(tool, "__dict__", {}): 

3340 raise ValueError("If 'tool.image' is an instance variable, " 

3341 "it must be an absolute path") 

3342 for cls in type(tool).__mro__: 

3343 if "image" in vars(cls): 

3344 try: 

3345 src = inspect.getfile(cls) 

3346 break 

3347 except (OSError, TypeError): 

3348 raise ValueError("Failed to locate source file " 

3349 "where 'tool.image' is defined") from None 

3350 else: 

3351 raise ValueError("Failed to find parent class defining 'tool.image'") 

3352 filename = str(pathlib.Path(src).parent / tool.image) 

3353 for filename in [filename, filename + self._icon_extension]: 

3354 if os.path.isfile(filename): 

3355 return os.path.abspath(filename) 

3356 for fname in [ # Fallback; once deprecation elapses. 

3357 tool.image, 

3358 tool.image + self._icon_extension, 

3359 cbook._get_data_path("images", tool.image), 

3360 cbook._get_data_path("images", tool.image + self._icon_extension), 

3361 ]: 

3362 if os.path.isfile(fname): 

3363 _api.warn_deprecated( 

3364 "3.9", message=f"Loading icon {tool.image!r} from the current " 

3365 "directory or from Matplotlib's image directory. This behavior " 

3366 "is deprecated since %(since)s and will be removed %(removal)s; " 

3367 "Tool.image should be set to a path relative to the Tool's source " 

3368 "file, or to an absolute path.") 

3369 return os.path.abspath(fname) 

3370 

3371 def trigger_tool(self, name): 

3372 """ 

3373 Trigger the tool. 

3374 

3375 Parameters 

3376 ---------- 

3377 name : str 

3378 Name (id) of the tool triggered from within the container. 

3379 """ 

3380 self.toolmanager.trigger_tool(name, sender=self) 

3381 

3382 def add_toolitem(self, name, group, position, image, description, toggle): 

3383 """ 

3384 A hook to add a toolitem to the container. 

3385 

3386 This hook must be implemented in each backend and contains the 

3387 backend-specific code to add an element to the toolbar. 

3388 

3389 .. warning:: 

3390 This is part of the backend implementation and should 

3391 not be called by end-users. They should instead call 

3392 `.ToolContainerBase.add_tool`. 

3393 

3394 The callback associated with the button click event 

3395 must be *exactly* ``self.trigger_tool(name)``. 

3396 

3397 Parameters 

3398 ---------- 

3399 name : str 

3400 Name of the tool to add, this gets used as the tool's ID and as the 

3401 default label of the buttons. 

3402 group : str 

3403 Name of the group that this tool belongs to. 

3404 position : int 

3405 Position of the tool within its group, if -1 it goes at the end. 

3406 image : str 

3407 Filename of the image for the button or `None`. 

3408 description : str 

3409 Description of the tool, used for the tooltips. 

3410 toggle : bool 

3411 * `True` : The button is a toggle (change the pressed/unpressed 

3412 state between consecutive clicks). 

3413 * `False` : The button is a normal button (returns to unpressed 

3414 state after release). 

3415 """ 

3416 raise NotImplementedError 

3417 

3418 def toggle_toolitem(self, name, toggled): 

3419 """ 

3420 A hook to toggle a toolitem without firing an event. 

3421 

3422 This hook must be implemented in each backend and contains the 

3423 backend-specific code to silently toggle a toolbar element. 

3424 

3425 .. warning:: 

3426 This is part of the backend implementation and should 

3427 not be called by end-users. They should instead call 

3428 `.ToolManager.trigger_tool` or `.ToolContainerBase.trigger_tool` 

3429 (which are equivalent). 

3430 

3431 Parameters 

3432 ---------- 

3433 name : str 

3434 Id of the tool to toggle. 

3435 toggled : bool 

3436 Whether to set this tool as toggled or not. 

3437 """ 

3438 raise NotImplementedError 

3439 

3440 def remove_toolitem(self, name): 

3441 """ 

3442 A hook to remove a toolitem from the container. 

3443 

3444 This hook must be implemented in each backend and contains the 

3445 backend-specific code to remove an element from the toolbar; it is 

3446 called when `.ToolManager` emits a `tool_removed_event`. 

3447 

3448 Because some tools are present only on the `.ToolManager` but not on 

3449 the `ToolContainer`, this method must be a no-op when called on a tool 

3450 absent from the container. 

3451 

3452 .. warning:: 

3453 This is part of the backend implementation and should 

3454 not be called by end-users. They should instead call 

3455 `.ToolManager.remove_tool`. 

3456 

3457 Parameters 

3458 ---------- 

3459 name : str 

3460 Name of the tool to remove. 

3461 """ 

3462 raise NotImplementedError 

3463 

3464 def set_message(self, s): 

3465 """ 

3466 Display a message on the toolbar. 

3467 

3468 Parameters 

3469 ---------- 

3470 s : str 

3471 Message text. 

3472 """ 

3473 raise NotImplementedError 

3474 

3475 

3476class _Backend: 

3477 # A backend can be defined by using the following pattern: 

3478 # 

3479 # @_Backend.export 

3480 # class FooBackend(_Backend): 

3481 # # override the attributes and methods documented below. 

3482 

3483 # `backend_version` may be overridden by the subclass. 

3484 backend_version = "unknown" 

3485 

3486 # The `FigureCanvas` class must be defined. 

3487 FigureCanvas = None 

3488 

3489 # For interactive backends, the `FigureManager` class must be overridden. 

3490 FigureManager = FigureManagerBase 

3491 

3492 # For interactive backends, `mainloop` should be a function taking no 

3493 # argument and starting the backend main loop. It should be left as None 

3494 # for non-interactive backends. 

3495 mainloop = None 

3496 

3497 # The following methods will be automatically defined and exported, but 

3498 # can be overridden. 

3499 

3500 @classmethod 

3501 def new_figure_manager(cls, num, *args, **kwargs): 

3502 """Create a new figure manager instance.""" 

3503 # This import needs to happen here due to circular imports. 

3504 from matplotlib.figure import Figure 

3505 fig_cls = kwargs.pop('FigureClass', Figure) 

3506 fig = fig_cls(*args, **kwargs) 

3507 return cls.new_figure_manager_given_figure(num, fig) 

3508 

3509 @classmethod 

3510 def new_figure_manager_given_figure(cls, num, figure): 

3511 """Create a new figure manager instance for the given figure.""" 

3512 return cls.FigureCanvas.new_manager(figure, num) 

3513 

3514 @classmethod 

3515 def draw_if_interactive(cls): 

3516 manager_class = cls.FigureCanvas.manager_class 

3517 # Interactive backends reimplement start_main_loop or pyplot_show. 

3518 backend_is_interactive = ( 

3519 manager_class.start_main_loop != FigureManagerBase.start_main_loop 

3520 or manager_class.pyplot_show != FigureManagerBase.pyplot_show) 

3521 if backend_is_interactive and is_interactive(): 

3522 manager = Gcf.get_active() 

3523 if manager: 

3524 manager.canvas.draw_idle() 

3525 

3526 @classmethod 

3527 def show(cls, *, block=None): 

3528 """ 

3529 Show all figures. 

3530 

3531 `show` blocks by calling `mainloop` if *block* is ``True``, or if it is 

3532 ``None`` and we are not in `interactive` mode and if IPython's 

3533 ``%matplotlib`` integration has not been activated. 

3534 """ 

3535 managers = Gcf.get_all_fig_managers() 

3536 if not managers: 

3537 return 

3538 for manager in managers: 

3539 try: 

3540 manager.show() # Emits a warning for non-interactive backend. 

3541 except NonGuiException as exc: 

3542 _api.warn_external(str(exc)) 

3543 if cls.mainloop is None: 

3544 return 

3545 if block is None: 

3546 # Hack: Is IPython's %matplotlib integration activated? If so, 

3547 # IPython's activate_matplotlib (>= 0.10) tacks a _needmain 

3548 # attribute onto pyplot.show (always set to False). 

3549 pyplot_show = getattr(sys.modules.get("matplotlib.pyplot"), "show", None) 

3550 ipython_pylab = hasattr(pyplot_show, "_needmain") 

3551 block = not ipython_pylab and not is_interactive() 

3552 if block: 

3553 cls.mainloop() 

3554 

3555 # This method is the one actually exporting the required methods. 

3556 

3557 @staticmethod 

3558 def export(cls): 

3559 for name in [ 

3560 "backend_version", 

3561 "FigureCanvas", 

3562 "FigureManager", 

3563 "new_figure_manager", 

3564 "new_figure_manager_given_figure", 

3565 "draw_if_interactive", 

3566 "show", 

3567 ]: 

3568 setattr(sys.modules[cls.__module__], name, getattr(cls, name)) 

3569 

3570 # For back-compatibility, generate a shim `Show` class. 

3571 

3572 class Show(ShowBase): 

3573 def mainloop(self): 

3574 return cls.mainloop() 

3575 

3576 setattr(sys.modules[cls.__module__], "Show", Show) 

3577 return cls 

3578 

3579 

3580class ShowBase(_Backend): 

3581 """ 

3582 Simple base class to generate a ``show()`` function in backends. 

3583 

3584 Subclass must override ``mainloop()`` method. 

3585 """ 

3586 

3587 def __call__(self, block=None): 

3588 return self.show(block=block)