Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Abstract base classes define the primitives that renderers and 

3graphics contexts must implement to serve as a matplotlib backend 

4 

5:class:`RendererBase` 

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

7 

8:class:`FigureCanvasBase` 

9 The abstraction layer that separates the 

10 :class:`matplotlib.figure.Figure` from the backend specific 

11 details like a user interface drawing area 

12 

13:class:`GraphicsContextBase` 

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

15 

16:class:`Event` 

17 The base class for all of the matplotlib event 

18 handling. Derived classes such as :class:`KeyEvent` and 

19 :class:`MouseEvent` store the meta data like keys and buttons 

20 pressed, x and y locations in pixel and 

21 :class:`~matplotlib.axes.Axes` coordinates. 

22 

23:class:`ShowBase` 

24 The base class for the Show class of each interactive backend; 

25 the 'show' callable is then set to Show.__call__, inherited from 

26 ShowBase. 

27 

28:class:`ToolContainerBase` 

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

30 

31:class:`StatusbarBase` 

32 The base class for the messaging area. 

33""" 

34 

35from contextlib import contextmanager, suppress 

36from enum import IntEnum 

37import functools 

38import importlib 

39import io 

40import logging 

41import os 

42import sys 

43import time 

44from weakref import WeakKeyDictionary 

45 

46import numpy as np 

47 

48import matplotlib as mpl 

49from matplotlib import ( 

50 backend_tools as tools, cbook, colors, textpath, tight_bbox, transforms, 

51 widgets, get_backend, is_interactive, rcParams) 

52from matplotlib._pylab_helpers import Gcf 

53from matplotlib.transforms import Affine2D 

54from matplotlib.path import Path 

55from matplotlib.cbook import _setattr_cm 

56 

57try: 

58 from PIL import __version__ as PILLOW_VERSION 

59 from distutils.version import LooseVersion 

60 if LooseVersion(PILLOW_VERSION) >= "3.4": 

61 _has_pil = True 

62 else: 

63 _has_pil = False 

64 del PILLOW_VERSION 

65except ImportError: 

66 _has_pil = False 

67 

68_log = logging.getLogger(__name__) 

69 

70_default_filetypes = { 

71 'ps': 'Postscript', 

72 'eps': 'Encapsulated Postscript', 

73 'pdf': 'Portable Document Format', 

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

75 'png': 'Portable Network Graphics', 

76 'raw': 'Raw RGBA bitmap', 

77 'rgba': 'Raw RGBA bitmap', 

78 'svg': 'Scalable Vector Graphics', 

79 'svgz': 'Scalable Vector Graphics' 

80} 

81 

82 

83_default_backends = { 

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

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

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

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

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

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

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

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

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

93} 

94 

95 

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

97 """ 

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

99 

100 Parameters 

101 ---------- 

102 format : str 

103 File extension 

104 

105 backend : module string or canvas class 

106 Backend for handling file output 

107 

108 description : str, default: "" 

109 Description of the file type. 

110 """ 

111 if description is None: 

112 description = '' 

113 _default_backends[format] = backend 

114 _default_filetypes[format] = description 

115 

116 

117def get_registered_canvas_class(format): 

118 """ 

119 Return the registered default canvas for given file format. 

120 Handles deferred import of required backend. 

121 """ 

122 if format not in _default_backends: 

123 return None 

124 backend_class = _default_backends[format] 

125 if isinstance(backend_class, str): 

126 backend_class = importlib.import_module(backend_class).FigureCanvas 

127 _default_backends[format] = backend_class 

128 return backend_class 

129 

130 

131class RendererBase: 

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 :meth:`draw_path` alone would 

136 give a highly capable backend): 

137 

138 * :meth:`draw_path` 

139 * :meth:`draw_image` 

140 * :meth:`draw_gouraud_triangle` 

141 

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

143 optimization reasons: 

144 

145 * :meth:`draw_text` 

146 * :meth:`draw_markers` 

147 * :meth:`draw_path_collection` 

148 * :meth:`draw_quad_mesh` 

149 """ 

150 

151 def __init__(self): 

152 self._texmanager = None 

153 self._text2path = textpath.TextToPath() 

154 

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

156 """ 

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

158 

159 Only used by the SVG renderer. 

160 """ 

161 

162 def close_group(self, s): 

163 """ 

164 Close a grouping element with label *s*. 

165 

166 Only used by the SVG renderer. 

167 """ 

168 

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

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

171 raise NotImplementedError 

172 

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

174 trans, rgbFace=None): 

175 """ 

176 Draw a marker at each of the vertices in path. 

177 

178 This includes all vertices, including control points on curves. 

179 To avoid that behavior, those vertices should be removed before 

180 calling this function. 

181 

182 This provides a fallback implementation of draw_markers that 

183 makes multiple calls to :meth:`draw_path`. Some backends may 

184 want to override this method in order to draw the marker only 

185 once and reuse it multiple times. 

186 

187 Parameters 

188 ---------- 

189 gc : `GraphicsContextBase` 

190 The graphics context. 

191 

192 marker_trans : `matplotlib.transforms.Transform` 

193 An affine transform applied to the marker. 

194 

195 trans : `matplotlib.transforms.Transform` 

196 An affine transform applied to the path. 

197 

198 """ 

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

200 if len(vertices): 

201 x, y = vertices[-2:] 

202 self.draw_path(gc, marker_path, 

203 marker_trans + 

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

205 rgbFace) 

206 

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

208 offsets, offsetTrans, facecolors, edgecolors, 

209 linewidths, linestyles, antialiaseds, urls, 

210 offset_position): 

211 """ 

212 Draw a collection of paths selecting drawing properties from 

213 the lists *facecolors*, *edgecolors*, *linewidths*, 

214 *linestyles* and *antialiaseds*. *offsets* is a list of 

215 offsets to apply to each of the paths. The offsets in 

216 *offsets* are first transformed by *offsetTrans* before being 

217 applied. *offset_position* may be either "screen" or "data" 

218 depending on the space that the offsets are in. 

219 

220 This provides a fallback implementation of 

221 :meth:`draw_path_collection` that makes multiple calls to 

222 :meth:`draw_path`. Some backends may want to override this in 

223 order to render each set of path data only once, and then 

224 reference that path multiple times with the different offsets, 

225 colors, styles etc. The generator methods 

226 :meth:`_iter_collection_raw_paths` and 

227 :meth:`_iter_collection` are provided to help with (and 

228 standardize) the implementation across backends. It is highly 

229 recommended to use those generators, so that changes to the 

230 behavior of :meth:`draw_path_collection` can be made globally. 

231 """ 

232 path_ids = [ 

233 (path, transforms.Affine2D(transform)) 

234 for path, transform in self._iter_collection_raw_paths( 

235 master_transform, paths, all_transforms)] 

236 

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

238 gc, master_transform, all_transforms, path_ids, offsets, 

239 offsetTrans, facecolors, edgecolors, linewidths, linestyles, 

240 antialiaseds, urls, offset_position): 

241 path, transform = path_id 

242 transform = transforms.Affine2D( 

243 transform.get_matrix()).translate(xo, yo) 

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

245 

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

247 coordinates, offsets, offsetTrans, facecolors, 

248 antialiased, edgecolors): 

249 """ 

250 This provides a fallback implementation of 

251 :meth:`draw_quad_mesh` that generates paths and then calls 

252 :meth:`draw_path_collection`. 

253 """ 

254 

255 from matplotlib.collections import QuadMesh 

256 paths = QuadMesh.convert_mesh_to_paths( 

257 meshWidth, meshHeight, coordinates) 

258 

259 if edgecolors is None: 

260 edgecolors = facecolors 

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

262 

263 return self.draw_path_collection( 

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

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

266 

267 def draw_gouraud_triangle(self, gc, points, colors, transform): 

268 """ 

269 Draw a Gouraud-shaded triangle. 

270 

271 Parameters 

272 ---------- 

273 points : array-like, shape=(3, 2) 

274 Array of (x, y) points for the triangle. 

275 

276 colors : array-like, shape=(3, 4) 

277 RGBA colors for each point of the triangle. 

278 

279 transform : `matplotlib.transforms.Transform` 

280 An affine transform to apply to the points. 

281 

282 """ 

283 raise NotImplementedError 

284 

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

286 transform): 

287 """ 

288 Draw a series of Gouraud triangles. 

289 

290 Parameters 

291 ---------- 

292 points : array-like, shape=(N, 3, 2) 

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

294 

295 colors : array-like, shape=(N, 3, 4) 

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

297 

298 transform : `matplotlib.transforms.Transform` 

299 An affine transform to apply to the points. 

300 """ 

301 transform = transform.frozen() 

302 for tri, col in zip(triangles_array, colors_array): 

303 self.draw_gouraud_triangle(gc, tri, col, transform) 

304 

305 def _iter_collection_raw_paths(self, master_transform, paths, 

306 all_transforms): 

307 """ 

308 This is a helper method (along with :meth:`_iter_collection`) to make 

309 it easier to write a space-efficient :meth:`draw_path_collection` 

310 implementation in a backend. 

311 

312 This method yields all of the base path/transform 

313 combinations, given a master transform, a list of paths and 

314 list of transforms. 

315 

316 The arguments should be exactly what is passed in to 

317 :meth:`draw_path_collection`. 

318 

319 The backend should take each yielded path and transform and 

320 create an object that can be referenced (reused) later. 

321 """ 

322 Npaths = len(paths) 

323 Ntransforms = len(all_transforms) 

324 N = max(Npaths, Ntransforms) 

325 

326 if Npaths == 0: 

327 return 

328 

329 transform = transforms.IdentityTransform() 

330 for i in range(N): 

331 path = paths[i % Npaths] 

332 if Ntransforms: 

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

334 yield path, transform + master_transform 

335 

336 def _iter_collection_uses_per_path(self, paths, all_transforms, 

337 offsets, facecolors, edgecolors): 

338 """ 

339 Compute how many times each raw path object returned by 

340 _iter_collection_raw_paths would be used when calling 

341 _iter_collection. This is intended for the backend to decide 

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

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

344 is not the same for every path. 

345 """ 

346 Npaths = len(paths) 

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

348 return 0 

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

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

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

352 

353 def _iter_collection(self, gc, master_transform, all_transforms, 

354 path_ids, offsets, offsetTrans, facecolors, 

355 edgecolors, linewidths, linestyles, 

356 antialiaseds, urls, offset_position): 

357 """ 

358 This is a helper method (along with 

359 :meth:`_iter_collection_raw_paths`) to make it easier to write 

360 a space-efficient :meth:`draw_path_collection` implementation in a 

361 backend. 

362 

363 This method yields all of the path, offset and graphics 

364 context combinations to draw the path collection. The caller 

365 should already have looped over the results of 

366 :meth:`_iter_collection_raw_paths` to draw this collection. 

367 

368 The arguments should be the same as that passed into 

369 :meth:`draw_path_collection`, with the exception of 

370 *path_ids*, which is a list of arbitrary objects that the 

371 backend will use to reference one of the paths created in the 

372 :meth:`_iter_collection_raw_paths` stage. 

373 

374 Each yielded result is of the form:: 

375 

376 xo, yo, path_id, gc, rgbFace 

377 

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

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

380 use for filling the path. 

381 """ 

382 Ntransforms = len(all_transforms) 

383 Npaths = len(path_ids) 

384 Noffsets = len(offsets) 

385 N = max(Npaths, Noffsets) 

386 Nfacecolors = len(facecolors) 

387 Nedgecolors = len(edgecolors) 

388 Nlinewidths = len(linewidths) 

389 Nlinestyles = len(linestyles) 

390 Naa = len(antialiaseds) 

391 Nurls = len(urls) 

392 

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

394 return 

395 if Noffsets: 

396 toffsets = offsetTrans.transform(offsets) 

397 

398 gc0 = self.new_gc() 

399 gc0.copy_properties(gc) 

400 

401 if Nfacecolors == 0: 

402 rgbFace = None 

403 

404 if Nedgecolors == 0: 

405 gc0.set_linewidth(0.0) 

406 

407 xo, yo = 0, 0 

408 for i in range(N): 

409 path_id = path_ids[i % Npaths] 

410 if Noffsets: 

411 xo, yo = toffsets[i % Noffsets] 

412 if offset_position == 'data': 

413 if Ntransforms: 

414 transform = ( 

415 Affine2D(all_transforms[i % Ntransforms]) + 

416 master_transform) 

417 else: 

418 transform = master_transform 

419 (xo, yo), (xp, yp) = transform.transform( 

420 [(xo, yo), (0, 0)]) 

421 xo = -(xp - xo) 

422 yo = -(yp - yo) 

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

424 continue 

425 if Nfacecolors: 

426 rgbFace = facecolors[i % Nfacecolors] 

427 if Nedgecolors: 

428 if Nlinewidths: 

429 gc0.set_linewidth(linewidths[i % Nlinewidths]) 

430 if Nlinestyles: 

431 gc0.set_dashes(*linestyles[i % Nlinestyles]) 

432 fg = edgecolors[i % Nedgecolors] 

433 if len(fg) == 4: 

434 if fg[3] == 0.0: 

435 gc0.set_linewidth(0) 

436 else: 

437 gc0.set_foreground(fg) 

438 else: 

439 gc0.set_foreground(fg) 

440 if rgbFace is not None and len(rgbFace) == 4: 

441 if rgbFace[3] == 0: 

442 rgbFace = None 

443 gc0.set_antialiased(antialiaseds[i % Naa]) 

444 if Nurls: 

445 gc0.set_url(urls[i % Nurls]) 

446 

447 yield xo, yo, path_id, gc0, rgbFace 

448 gc0.restore() 

449 

450 def get_image_magnification(self): 

451 """ 

452 Get the factor by which to magnify images passed to :meth:`draw_image`. 

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

454 artists. 

455 """ 

456 return 1.0 

457 

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

459 """ 

460 Draw an RGBA image. 

461 

462 Parameters 

463 ---------- 

464 gc : `GraphicsContextBase` 

465 A graphics context with clipping information. 

466 

467 x : scalar 

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

469 hand side of the canvas. 

470 

471 y : scalar 

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

473 bottom side of the canvas. 

474 

475 im : array-like, shape=(N, M, 4), dtype=np.uint8 

476 An array of RGBA pixels. 

477 

478 transform : `matplotlib.transforms.Affine2DBase` 

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

480 :meth:`option_scale_image` returns ``True``, an affine 

481 transformation *may* be passed to :meth:`draw_image`. It takes the 

482 form of a :class:`~matplotlib.transforms.Affine2DBase` instance. 

483 The translation vector of the transformation is given in physical 

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

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

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

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

488 """ 

489 raise NotImplementedError 

490 

491 def option_image_nocomposite(self): 

492 """ 

493 Return whether image composition by Matplotlib should be skipped. 

494 

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

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

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

498 """ 

499 return False 

500 

501 def option_scale_image(self): 

502 """ 

503 Return whether arbitrary affine transformations in :meth:`draw_image` 

504 are supported (True for most vector backends). 

505 """ 

506 return False 

507 

508 def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None): 

509 """ 

510 """ 

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

512 

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

514 """ 

515 Draw the text instance. 

516 

517 Parameters 

518 ---------- 

519 gc : `GraphicsContextBase` 

520 The graphics context. 

521 x : float 

522 The x location of the text in display coords. 

523 y : float 

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

525 s : str 

526 The text string. 

527 prop : `matplotlib.font_manager.FontProperties` 

528 The font properties. 

529 angle : float 

530 The rotation angle in degrees anti-clockwise. 

531 mtext : `matplotlib.text.Text` 

532 The original text object to be rendered. 

533 

534 Notes 

535 ----- 

536 **Note for backend implementers:** 

537 

538 When you are trying to determine if you have gotten your bounding box 

539 right (which is what enables the text layout/alignment to work 

540 properly), it helps to change the line in text.py:: 

541 

542 if 0: bbox_artist(self, renderer) 

543 

544 to if 1, and then the actual bounding box will be plotted along with 

545 your text. 

546 """ 

547 

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

549 

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

551 """ 

552 Return the text path and transform. 

553 

554 Parameters 

555 ---------- 

556 prop : `matplotlib.font_manager.FontProperties` 

557 The font property. 

558 s : str 

559 The text to be converted. 

560 ismath : bool or "TeX" 

561 If True, use mathtext parser. If "TeX", use *usetex* mode. 

562 """ 

563 

564 text2path = self._text2path 

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

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

567 

568 path = Path(verts, codes) 

569 angle = np.deg2rad(angle) 

570 if self.flipy(): 

571 width, height = self.get_canvas_width_height() 

572 transform = (Affine2D() 

573 .scale(fontsize / text2path.FONT_SCALE) 

574 .rotate(angle) 

575 .translate(x, height - y)) 

576 else: 

577 transform = (Affine2D() 

578 .scale(fontsize / text2path.FONT_SCALE) 

579 .rotate(angle) 

580 .translate(x, y)) 

581 

582 return path, transform 

583 

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

585 """ 

586 Draw the text by converting them to paths using textpath module. 

587 

588 Parameters 

589 ---------- 

590 prop : `matplotlib.font_manager.FontProperties` 

591 The font property. 

592 s : str 

593 The text to be converted. 

594 usetex : bool 

595 Whether to use matplotlib usetex mode. 

596 ismath : bool or "TeX" 

597 If True, use mathtext parser. If "TeX", use *usetex* mode. 

598 """ 

599 path, transform = self._get_text_path_transform( 

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

601 color = gc.get_rgb() 

602 gc.set_linewidth(0.0) 

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

604 

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

606 """ 

607 Get the width, height, and descent (offset from the bottom 

608 to the baseline), in display coords, of the string *s* with 

609 :class:`~matplotlib.font_manager.FontProperties` *prop* 

610 """ 

611 if ismath == 'TeX': 

612 # todo: handle props 

613 texmanager = self._text2path.get_texmanager() 

614 fontsize = prop.get_size_in_points() 

615 w, h, d = texmanager.get_text_width_height_descent( 

616 s, fontsize, renderer=self) 

617 return w, h, d 

618 

619 dpi = self.points_to_pixels(72) 

620 if ismath: 

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

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

623 

624 flags = self._text2path._get_hinting_flag() 

625 font = self._text2path._get_font(prop) 

626 size = prop.get_size_in_points() 

627 font.set_size(size, dpi) 

628 # the width and height of unrotated string 

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

630 w, h = font.get_width_height() 

631 d = font.get_descent() 

632 w /= 64.0 # convert from subpixels 

633 h /= 64.0 

634 d /= 64.0 

635 return w, h, d 

636 

637 def flipy(self): 

638 """ 

639 Return whether y values increase from top to bottom. 

640 

641 Note that this only affects drawing of texts and images. 

642 """ 

643 return True 

644 

645 def get_canvas_width_height(self): 

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

647 return 1, 1 

648 

649 def get_texmanager(self): 

650 """Return the `.TexManager` instance.""" 

651 if self._texmanager is None: 

652 from matplotlib.texmanager import TexManager 

653 self._texmanager = TexManager() 

654 return self._texmanager 

655 

656 def new_gc(self): 

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

658 return GraphicsContextBase() 

659 

660 def points_to_pixels(self, points): 

661 """ 

662 Convert points to display units. 

663 

664 You need to override this function (unless your backend 

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

666 systems assume some value for pixels per inch:: 

667 

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

669 

670 Parameters 

671 ---------- 

672 points : float or array-like 

673 a float or a numpy array of float 

674 

675 Returns 

676 ------- 

677 Points converted to pixels 

678 """ 

679 return points 

680 

681 @cbook.deprecated("3.1", alternative="cbook.strip_math") 

682 def strip_math(self, s): 

683 return cbook.strip_math(s) 

684 

685 def start_rasterizing(self): 

686 """ 

687 Switch to the raster renderer. 

688 

689 Used by `MixedModeRenderer`. 

690 """ 

691 

692 def stop_rasterizing(self): 

693 """ 

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

695 renderer as an image on the vector renderer. 

696 

697 Used by `MixedModeRenderer`. 

698 """ 

699 

700 def start_filter(self): 

701 """ 

702 Switch to a temporary renderer for image filtering effects. 

703 

704 Currently only supported by the agg renderer. 

705 """ 

706 

707 def stop_filter(self, filter_func): 

708 """ 

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

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

711 original renderer as an image. 

712 

713 Currently only supported by the agg renderer. 

714 """ 

715 

716 def _draw_disabled(self): 

717 """ 

718 Context manager to temporary disable drawing. 

719 

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

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

722 cost of the draw_XYZ calls on the canvas. 

723 """ 

724 no_ops = { 

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

726 for meth_name in dir(RendererBase) 

727 if (meth_name.startswith("draw_") 

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

729 } 

730 

731 return _setattr_cm(self, **no_ops) 

732 

733 

734class GraphicsContextBase: 

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

736 

737 def __init__(self): 

738 self._alpha = 1.0 

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

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

741 self._capstyle = 'butt' 

742 self._cliprect = None 

743 self._clippath = None 

744 self._dashes = None, None 

745 self._joinstyle = 'round' 

746 self._linestyle = 'solid' 

747 self._linewidth = 1 

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

749 self._hatch = None 

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

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

752 self._url = None 

753 self._gid = None 

754 self._snap = None 

755 self._sketch = None 

756 

757 def copy_properties(self, gc): 

758 'Copy properties from gc to self' 

759 self._alpha = gc._alpha 

760 self._forced_alpha = gc._forced_alpha 

761 self._antialiased = gc._antialiased 

762 self._capstyle = gc._capstyle 

763 self._cliprect = gc._cliprect 

764 self._clippath = gc._clippath 

765 self._dashes = gc._dashes 

766 self._joinstyle = gc._joinstyle 

767 self._linestyle = gc._linestyle 

768 self._linewidth = gc._linewidth 

769 self._rgb = gc._rgb 

770 self._hatch = gc._hatch 

771 self._hatch_color = gc._hatch_color 

772 self._hatch_linewidth = gc._hatch_linewidth 

773 self._url = gc._url 

774 self._gid = gc._gid 

775 self._snap = gc._snap 

776 self._sketch = gc._sketch 

777 

778 def restore(self): 

779 """ 

780 Restore the graphics context from the stack - needed only 

781 for backends that save graphics contexts on a stack. 

782 """ 

783 

784 def get_alpha(self): 

785 """ 

786 Return the alpha value used for blending - not supported on 

787 all backends. 

788 """ 

789 return self._alpha 

790 

791 def get_antialiased(self): 

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

793 return self._antialiased 

794 

795 def get_capstyle(self): 

796 """ 

797 Return the capstyle as a string in ('butt', 'round', 'projecting'). 

798 """ 

799 return self._capstyle 

800 

801 def get_clip_rectangle(self): 

802 """ 

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

804 """ 

805 return self._cliprect 

806 

807 def get_clip_path(self): 

808 """ 

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

810 is a :class:`~matplotlib.path.Path` instance, and transform is 

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

812 """ 

813 if self._clippath is not None: 

814 return self._clippath.get_transformed_path_and_affine() 

815 return None, None 

816 

817 def get_dashes(self): 

818 """ 

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

820 

821 The dash list is a even-length list that gives the ink on, ink off in 

822 points. See p. 107 of to PostScript `blue book`_ for more info. 

823 

824 Default value is (None, None). 

825 

826 .. _blue book: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF 

827 """ 

828 return self._dashes 

829 

830 def get_forced_alpha(self): 

831 """ 

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

833 override any other alpha-channel values. 

834 """ 

835 return self._forced_alpha 

836 

837 def get_joinstyle(self): 

838 """Return the line join style as one of ('miter', 'round', 'bevel').""" 

839 return self._joinstyle 

840 

841 def get_linewidth(self): 

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

843 return self._linewidth 

844 

845 def get_rgb(self): 

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

847 return self._rgb 

848 

849 def get_url(self): 

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

851 return self._url 

852 

853 def get_gid(self): 

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

855 return self._gid 

856 

857 def get_snap(self): 

858 """ 

859 Returns the snap setting, which can be: 

860 

861 * True: snap vertices to the nearest pixel center 

862 * False: leave vertices as-is 

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

864 round to the nearest pixel center 

865 """ 

866 return self._snap 

867 

868 def set_alpha(self, alpha): 

869 """ 

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

871 

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

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

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

875 them. 

876 """ 

877 if alpha is not None: 

878 self._alpha = alpha 

879 self._forced_alpha = True 

880 else: 

881 self._alpha = 1.0 

882 self._forced_alpha = False 

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

884 

885 def set_antialiased(self, b): 

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

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

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

889 

890 def set_capstyle(self, cs): 

891 """Set the capstyle to be one of ('butt', 'round', 'projecting').""" 

892 cbook._check_in_list(['butt', 'round', 'projecting'], cs=cs) 

893 self._capstyle = cs 

894 

895 def set_clip_rectangle(self, rectangle): 

896 """ 

897 Set the clip rectangle with sequence (left, bottom, width, height) 

898 """ 

899 self._cliprect = rectangle 

900 

901 def set_clip_path(self, path): 

902 """ 

903 Set the clip path and transformation. 

904 

905 Parameters 

906 ---------- 

907 path : `~matplotlib.transforms.TransformedPath` or None 

908 """ 

909 cbook._check_isinstance((transforms.TransformedPath, None), path=path) 

910 self._clippath = path 

911 

912 def set_dashes(self, dash_offset, dash_list): 

913 """ 

914 Set the dash style for the gc. 

915 

916 Parameters 

917 ---------- 

918 dash_offset : float or None 

919 The offset (usually 0). 

920 dash_list : array-like or None 

921 The on-off sequence as points. 

922 

923 Notes 

924 ----- 

925 ``(None, None)`` specifies a solid line. 

926 

927 See p. 107 of to PostScript `blue book`_ for more info. 

928 

929 .. _blue book: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF 

930 """ 

931 if dash_list is not None: 

932 dl = np.asarray(dash_list) 

933 if np.any(dl < 0.0): 

934 raise ValueError( 

935 "All values in the dash list must be positive") 

936 self._dashes = dash_offset, dash_list 

937 

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

939 """ 

940 Set the foreground color. 

941 

942 Parameters 

943 ---------- 

944 fg : color 

945 isRGBA : bool 

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

947 set to True to improve performance. 

948 """ 

949 if self._forced_alpha and isRGBA: 

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

951 elif self._forced_alpha: 

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

953 elif isRGBA: 

954 self._rgb = fg 

955 else: 

956 self._rgb = colors.to_rgba(fg) 

957 

958 def set_joinstyle(self, js): 

959 """Set the join style to be one of ('miter', 'round', 'bevel').""" 

960 cbook._check_in_list(['miter', 'round', 'bevel'], js=js) 

961 self._joinstyle = js 

962 

963 def set_linewidth(self, w): 

964 """Set the linewidth in points.""" 

965 self._linewidth = float(w) 

966 

967 def set_url(self, url): 

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

969 self._url = url 

970 

971 def set_gid(self, id): 

972 """Set the id.""" 

973 self._gid = id 

974 

975 def set_snap(self, snap): 

976 """ 

977 Set the snap setting which may be: 

978 

979 * True: snap vertices to the nearest pixel center 

980 * False: leave vertices as-is 

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

982 round to the nearest pixel center 

983 """ 

984 self._snap = snap 

985 

986 def set_hatch(self, hatch): 

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

988 self._hatch = hatch 

989 

990 def get_hatch(self): 

991 """Get the current hatch style.""" 

992 return self._hatch 

993 

994 def get_hatch_path(self, density=6.0): 

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

996 hatch = self.get_hatch() 

997 if hatch is None: 

998 return None 

999 return Path.hatch(hatch, density) 

1000 

1001 def get_hatch_color(self): 

1002 """Get the hatch color.""" 

1003 return self._hatch_color 

1004 

1005 def set_hatch_color(self, hatch_color): 

1006 """Set the hatch color.""" 

1007 self._hatch_color = hatch_color 

1008 

1009 def get_hatch_linewidth(self): 

1010 """Get the hatch linewidth.""" 

1011 return self._hatch_linewidth 

1012 

1013 def get_sketch_params(self): 

1014 """ 

1015 Return the sketch parameters for the artist. 

1016 

1017 Returns 

1018 ------- 

1019 sketch_params : tuple or `None` 

1020 

1021 A 3-tuple with the following elements: 

1022 

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

1024 source line. 

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

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

1027 shrunken or expanded. 

1028 

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

1030 """ 

1031 return self._sketch 

1032 

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

1034 """ 

1035 Set the sketch parameters. 

1036 

1037 Parameters 

1038 ---------- 

1039 scale : float, optional 

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

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

1042 be provided. 

1043 length : float, default: 128 

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

1045 randomness : float, default: 16 

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

1047 """ 

1048 self._sketch = ( 

1049 None if scale is None 

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

1051 

1052 

1053class TimerBase: 

1054 """ 

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

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

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

1058 event loops. 

1059 

1060 Subclasses must override the following methods: 

1061 

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

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

1064 

1065 Subclasses may additionally override the following methods: 

1066 

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

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

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

1070 overridden to support such behavior. 

1071 

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

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

1074 

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

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

1077 

1078 Attributes 

1079 ---------- 

1080 interval : scalar 

1081 The time between timer events in milliseconds. Default is 1000 ms. 

1082 

1083 single_shot : bool 

1084 Boolean flag indicating whether this timer should operate as single 

1085 shot (run once and then stop). Defaults to `False`. 

1086 

1087 callbacks : List[Tuple[callable, Tuple, Dict]] 

1088 Stores list of (func, args, kwargs) tuples that will be called upon 

1089 timer events. This list can be manipulated directly, or the 

1090 functions `add_callback` and `remove_callback` can be used. 

1091 """ 

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

1093 #Initialize empty callbacks list and setup default settings if necssary 

1094 if callbacks is None: 

1095 self.callbacks = [] 

1096 else: 

1097 self.callbacks = callbacks[:] # Create a copy 

1098 

1099 if interval is None: 

1100 self._interval = 1000 

1101 else: 

1102 self._interval = interval 

1103 

1104 self._single = False 

1105 

1106 # Default attribute for holding the GUI-specific timer object 

1107 self._timer = None 

1108 

1109 def __del__(self): 

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

1111 self._timer_stop() 

1112 

1113 def start(self, interval=None): 

1114 """ 

1115 Start the timer object. 

1116 

1117 Parameters 

1118 ---------- 

1119 interval : int, optional 

1120 Timer interval in milliseconds; overrides a previously set interval 

1121 if provided. 

1122 """ 

1123 if interval is not None: 

1124 self.interval = interval 

1125 self._timer_start() 

1126 

1127 def stop(self): 

1128 """Stop the timer.""" 

1129 self._timer_stop() 

1130 

1131 def _timer_start(self): 

1132 pass 

1133 

1134 def _timer_stop(self): 

1135 pass 

1136 

1137 @property 

1138 def interval(self): 

1139 return self._interval 

1140 

1141 @interval.setter 

1142 def interval(self, interval): 

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

1144 # milliseconds, and some error or give warnings. 

1145 interval = int(interval) 

1146 self._interval = interval 

1147 self._timer_set_interval() 

1148 

1149 @property 

1150 def single_shot(self): 

1151 return self._single 

1152 

1153 @single_shot.setter 

1154 def single_shot(self, ss): 

1155 self._single = ss 

1156 self._timer_set_single_shot() 

1157 

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

1159 """ 

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

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

1162 

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

1164 decorator. 

1165 """ 

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

1167 return func 

1168 

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

1170 """ 

1171 Remove *func* from list of callbacks. 

1172 

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

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

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

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

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

1178 """ 

1179 if args or kwargs: 

1180 cbook.warn_deprecated( 

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

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

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

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

1185 "functools.partial object.") 

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

1187 else: 

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

1189 if func in funcs: 

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

1191 

1192 def _timer_set_interval(self): 

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

1194 

1195 def _timer_set_single_shot(self): 

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

1197 

1198 def _on_timer(self): 

1199 """ 

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

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

1202 are no callbacks, the timer is automatically stopped. 

1203 """ 

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

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

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

1207 # instead of `if not ret`. 

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

1209 # but does not annoy the linters 

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

1211 if ret == 0: 

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

1213 

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

1215 self.stop() 

1216 

1217 

1218class Event: 

1219 """ 

1220 A matplotlib event. Attach additional attributes as defined in 

1221 :meth:`FigureCanvasBase.mpl_connect`. The following attributes 

1222 are defined and shown with their default values 

1223 

1224 Attributes 

1225 ---------- 

1226 name : str 

1227 the event name 

1228 

1229 canvas : `FigureCanvasBase` 

1230 the backend-specific canvas instance generating the event 

1231 

1232 guiEvent 

1233 the GUI event that triggered the matplotlib event 

1234 

1235 """ 

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

1237 self.name = name 

1238 self.canvas = canvas 

1239 self.guiEvent = guiEvent 

1240 

1241 

1242class DrawEvent(Event): 

1243 """ 

1244 An event triggered by a draw operation on the canvas 

1245 

1246 In most backends callbacks subscribed to this callback will be 

1247 fired after the rendering is complete but before the screen is 

1248 updated. Any extra artists drawn to the canvas's renderer will 

1249 be reflected without an explicit call to ``blit``. 

1250 

1251 .. warning:: 

1252 

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

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

1255 

1256 In addition to the :class:`Event` attributes, the following event 

1257 attributes are defined: 

1258 

1259 Attributes 

1260 ---------- 

1261 renderer : `RendererBase` 

1262 the renderer for the draw event 

1263 

1264 """ 

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

1266 Event.__init__(self, name, canvas) 

1267 self.renderer = renderer 

1268 

1269 

1270class ResizeEvent(Event): 

1271 """ 

1272 An event triggered by a canvas resize 

1273 

1274 In addition to the :class:`Event` attributes, the following event 

1275 attributes are defined: 

1276 

1277 Attributes 

1278 ---------- 

1279 width : int 

1280 Width of the canvas in pixels. 

1281 height : int 

1282 Height of the canvas in pixels. 

1283 """ 

1284 def __init__(self, name, canvas): 

1285 Event.__init__(self, name, canvas) 

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

1287 

1288 

1289class CloseEvent(Event): 

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

1291 

1292 

1293class LocationEvent(Event): 

1294 """ 

1295 An event that has a screen location. 

1296 

1297 The following additional attributes are defined and shown with 

1298 their default values. 

1299 

1300 In addition to the :class:`Event` attributes, the following 

1301 event attributes are defined: 

1302 

1303 Attributes 

1304 ---------- 

1305 x : int 

1306 x position - pixels from left of canvas. 

1307 y : int 

1308 y position - pixels from bottom of canvas. 

1309 inaxes : `~.axes.Axes` or None 

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

1311 xdata : float or None 

1312 x data coordinate of the mouse. 

1313 ydata : float or None 

1314 y data coordinate of the mouse. 

1315 """ 

1316 

1317 lastevent = None # the last event that was triggered before this one 

1318 

1319 def __init__(self, name, canvas, x, y, guiEvent=None): 

1320 """ 

1321 (*x*, *y*) in figure coords ((0, 0) = bottom left). 

1322 """ 

1323 Event.__init__(self, name, canvas, guiEvent=guiEvent) 

1324 # x position - pixels from left of canvas 

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

1326 # y position - pixels from right of canvas 

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

1328 self.inaxes = None # the Axes instance if mouse us over axes 

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

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

1331 

1332 if x is None or y is None: 

1333 # cannot check if event was in axes if no (x, y) info 

1334 self._update_enter_leave() 

1335 return 

1336 

1337 if self.canvas.mouse_grabber is None: 

1338 self.inaxes = self.canvas.inaxes((x, y)) 

1339 else: 

1340 self.inaxes = self.canvas.mouse_grabber 

1341 

1342 if self.inaxes is not None: 

1343 try: 

1344 trans = self.inaxes.transData.inverted() 

1345 xdata, ydata = trans.transform((x, y)) 

1346 except ValueError: 

1347 pass 

1348 else: 

1349 self.xdata = xdata 

1350 self.ydata = ydata 

1351 

1352 self._update_enter_leave() 

1353 

1354 def _update_enter_leave(self): 

1355 'process the figure/axes enter leave events' 

1356 if LocationEvent.lastevent is not None: 

1357 last = LocationEvent.lastevent 

1358 if last.inaxes != self.inaxes: 

1359 # process axes enter/leave events 

1360 try: 

1361 if last.inaxes is not None: 

1362 last.canvas.callbacks.process('axes_leave_event', last) 

1363 except Exception: 

1364 pass 

1365 # See ticket 2901582. 

1366 # I think this is a valid exception to the rule 

1367 # against catching all exceptions; if anything goes 

1368 # wrong, we simply want to move on and process the 

1369 # current event. 

1370 if self.inaxes is not None: 

1371 self.canvas.callbacks.process('axes_enter_event', self) 

1372 

1373 else: 

1374 # process a figure enter event 

1375 if self.inaxes is not None: 

1376 self.canvas.callbacks.process('axes_enter_event', self) 

1377 

1378 LocationEvent.lastevent = self 

1379 

1380 

1381class MouseButton(IntEnum): 

1382 LEFT = 1 

1383 MIDDLE = 2 

1384 RIGHT = 3 

1385 BACK = 8 

1386 FORWARD = 9 

1387 

1388 

1389class MouseEvent(LocationEvent): 

1390 """ 

1391 A mouse event ('button_press_event', 

1392 'button_release_event', 

1393 'scroll_event', 

1394 'motion_notify_event'). 

1395 

1396 In addition to the :class:`Event` and :class:`LocationEvent` 

1397 attributes, the following attributes are defined: 

1398 

1399 Attributes 

1400 ---------- 

1401 button : {None, MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT, \ 

1402'up', 'down'} 

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

1404 Note that in the nbagg backend, both the middle and right clicks 

1405 return RIGHT since right clicking will bring up the context menu in 

1406 some browsers. 

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

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

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

1410 physically on the right. 

1411 

1412 key : None or str 

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

1414 See `KeyEvent`. 

1415 

1416 step : int 

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

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

1419 

1420 dblclick : bool 

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

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

1423 not used in 'button_release_event'. 

1424 

1425 Examples 

1426 -------- 

1427 :: 

1428 

1429 def on_press(event): 

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

1431 

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

1433 """ 

1434 

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

1436 step=0, dblclick=False, guiEvent=None): 

1437 """ 

1438 (*x*, *y*) in figure coords ((0, 0) = bottom left) 

1439 button pressed None, 1, 2, 3, 'up', 'down' 

1440 """ 

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

1442 button = MouseButton(button) 

1443 self.button = button 

1444 self.key = key 

1445 self.step = step 

1446 self.dblclick = dblclick 

1447 

1448 # super-init is deferred to the end because it calls back on 

1449 # 'axes_enter_event', which requires a fully initialized event. 

1450 LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) 

1451 

1452 def __str__(self): 

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

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

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

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

1457 

1458 

1459class PickEvent(Event): 

1460 """ 

1461 a pick event, fired when the user picks a location on the canvas 

1462 sufficiently close to an artist. 

1463 

1464 Attrs: all the :class:`Event` attributes plus 

1465 

1466 Attributes 

1467 ---------- 

1468 mouseevent : `MouseEvent` 

1469 the mouse event that generated the pick 

1470 

1471 artist : `matplotlib.artist.Artist` 

1472 the picked artist 

1473 

1474 other 

1475 extra class dependent attrs -- e.g., a 

1476 :class:`~matplotlib.lines.Line2D` pick may define different 

1477 extra attributes than a 

1478 :class:`~matplotlib.collections.PatchCollection` pick event 

1479 

1480 Examples 

1481 -------- 

1482 :: 

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

1484 

1485 def on_pick(event): 

1486 line = event.artist 

1487 xdata, ydata = line.get_data() 

1488 ind = event.ind 

1489 print('on pick line:', np.array([xdata[ind], ydata[ind]]).T) 

1490 

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

1492 """ 

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

1494 guiEvent=None, **kwargs): 

1495 Event.__init__(self, name, canvas, guiEvent) 

1496 self.mouseevent = mouseevent 

1497 self.artist = artist 

1498 self.__dict__.update(kwargs) 

1499 

1500 

1501class KeyEvent(LocationEvent): 

1502 """ 

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

1504 

1505 Attach additional attributes as defined in 

1506 :meth:`FigureCanvasBase.mpl_connect`. 

1507 

1508 In addition to the :class:`Event` and :class:`LocationEvent` 

1509 attributes, the following attributes are defined: 

1510 

1511 Attributes 

1512 ---------- 

1513 key : None or str 

1514 the key(s) pressed. Could be **None**, a single case sensitive ascii 

1515 character ("g", "G", "#", etc.), a special key 

1516 ("control", "shift", "f1", "up", etc.) or a 

1517 combination of the above (e.g., "ctrl+alt+g", "ctrl+alt+G"). 

1518 

1519 Notes 

1520 ----- 

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

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

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

1524 be valid key values. 

1525 

1526 Examples 

1527 -------- 

1528 :: 

1529 

1530 def on_key(event): 

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

1532 

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

1534 """ 

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

1536 self.key = key 

1537 # super-init deferred to the end: callback errors if called before 

1538 LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) 

1539 

1540 

1541def _get_renderer(figure, print_method): 

1542 """ 

1543 Get the renderer that would be used to save a `~.Figure`, and cache it on 

1544 the figure. 

1545 

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

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

1548 

1549 """ 

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

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

1552 

1553 class Done(Exception): 

1554 pass 

1555 

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

1557 

1558 with cbook._setattr_cm(figure, draw=_draw): 

1559 try: 

1560 print_method(io.BytesIO()) 

1561 except Done as exc: 

1562 renderer, = figure._cachedRenderer, = exc.args 

1563 

1564 return renderer 

1565 

1566 

1567def _is_non_interactive_terminal_ipython(ip): 

1568 """ 

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

1570 

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

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

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

1574 interactive), we do. 

1575 """ 

1576 return (hasattr(ip, 'parent') 

1577 and (ip.parent is not None) 

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

1579 

1580 

1581class FigureCanvasBase: 

1582 """ 

1583 The canvas the figure renders into. 

1584 

1585 Public attributes 

1586 

1587 Attributes 

1588 ---------- 

1589 figure : `matplotlib.figure.Figure` 

1590 A high-level figure instance 

1591 """ 

1592 

1593 # Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an 

1594 # interactive framework is required, or None otherwise. 

1595 required_interactive_framework = None 

1596 

1597 events = [ 

1598 'resize_event', 

1599 'draw_event', 

1600 'key_press_event', 

1601 'key_release_event', 

1602 'button_press_event', 

1603 'button_release_event', 

1604 'scroll_event', 

1605 'motion_notify_event', 

1606 'pick_event', 

1607 'idle_event', 

1608 'figure_enter_event', 

1609 'figure_leave_event', 

1610 'axes_enter_event', 

1611 'axes_leave_event', 

1612 'close_event' 

1613 ] 

1614 

1615 fixed_dpi = None 

1616 

1617 filetypes = _default_filetypes 

1618 if _has_pil: 

1619 # JPEG support 

1620 register_backend('jpg', 'matplotlib.backends.backend_agg', 

1621 'Joint Photographic Experts Group') 

1622 register_backend('jpeg', 'matplotlib.backends.backend_agg', 

1623 'Joint Photographic Experts Group') 

1624 # TIFF support 

1625 register_backend('tif', 'matplotlib.backends.backend_agg', 

1626 'Tagged Image File Format') 

1627 register_backend('tiff', 'matplotlib.backends.backend_agg', 

1628 'Tagged Image File Format') 

1629 

1630 @cbook._classproperty 

1631 def supports_blit(cls): 

1632 return (hasattr(cls, "copy_from_bbox") 

1633 and hasattr(cls, "restore_region")) 

1634 

1635 def __init__(self, figure): 

1636 self._fix_ipython_backend2gui() 

1637 self._is_idle_drawing = True 

1638 self._is_saving = False 

1639 figure.set_canvas(self) 

1640 self.figure = figure 

1641 # a dictionary from event name to a dictionary that maps cid->func 

1642 self.callbacks = cbook.CallbackRegistry() 

1643 self.widgetlock = widgets.LockDraw() 

1644 self._button = None # the button pressed 

1645 self._key = None # the key pressed 

1646 self._lastx, self._lasty = None, None 

1647 self.button_pick_id = self.mpl_connect('button_press_event', self.pick) 

1648 self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick) 

1649 self.mouse_grabber = None # the axes currently grabbing mouse 

1650 self.toolbar = None # NavigationToolbar2 will set me 

1651 self._is_idle_drawing = False 

1652 

1653 @classmethod 

1654 @functools.lru_cache() 

1655 def _fix_ipython_backend2gui(cls): 

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

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

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

1659 # be done once per class (hence the `lru_cache(1)`). 

1660 if "IPython" not in sys.modules: 

1661 return 

1662 import IPython 

1663 ip = IPython.get_ipython() 

1664 if not ip: 

1665 return 

1666 from IPython.core import pylabtools as pt 

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

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

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

1670 # don't break on our side. 

1671 return 

1672 rif = getattr(cls, "required_interactive_framework", None) 

1673 backend2gui_rif = {"qt5": "qt", "qt4": "qt", "gtk3": "gtk3", 

1674 "wx": "wx", "macosx": "osx"}.get(rif) 

1675 if backend2gui_rif: 

1676 if _is_non_interactive_terminal_ipython(ip): 

1677 ip.enable_gui(backend2gui_rif) 

1678 

1679 @contextmanager 

1680 def _idle_draw_cntx(self): 

1681 self._is_idle_drawing = True 

1682 try: 

1683 yield 

1684 finally: 

1685 self._is_idle_drawing = False 

1686 

1687 def is_saving(self): 

1688 """ 

1689 Returns whether the renderer is in the process of saving 

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

1691 """ 

1692 return self._is_saving 

1693 

1694 def pick(self, mouseevent): 

1695 if not self.widgetlock.locked(): 

1696 self.figure.pick(mouseevent) 

1697 

1698 def blit(self, bbox=None): 

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

1700 

1701 def resize(self, w, h): 

1702 """Set the canvas size in pixels.""" 

1703 

1704 def draw_event(self, renderer): 

1705 """Pass a `DrawEvent` to all functions connected to ``draw_event``.""" 

1706 s = 'draw_event' 

1707 event = DrawEvent(s, self, renderer) 

1708 self.callbacks.process(s, event) 

1709 

1710 def resize_event(self): 

1711 """ 

1712 Pass a `ResizeEvent` to all functions connected to ``resize_event``. 

1713 """ 

1714 s = 'resize_event' 

1715 event = ResizeEvent(s, self) 

1716 self.callbacks.process(s, event) 

1717 self.draw_idle() 

1718 

1719 def close_event(self, guiEvent=None): 

1720 """ 

1721 Pass a `CloseEvent` to all functions connected to ``close_event``. 

1722 """ 

1723 s = 'close_event' 

1724 try: 

1725 event = CloseEvent(s, self, guiEvent=guiEvent) 

1726 self.callbacks.process(s, event) 

1727 except (TypeError, AttributeError): 

1728 pass 

1729 # Suppress the TypeError when the python session is being killed. 

1730 # It may be that a better solution would be a mechanism to 

1731 # disconnect all callbacks upon shutdown. 

1732 # AttributeError occurs on OSX with qt4agg upon exiting 

1733 # with an open window; 'callbacks' attribute no longer exists. 

1734 

1735 def key_press_event(self, key, guiEvent=None): 

1736 """ 

1737 Pass a `KeyEvent` to all functions connected to ``key_press_event``. 

1738 """ 

1739 self._key = key 

1740 s = 'key_press_event' 

1741 event = KeyEvent( 

1742 s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) 

1743 self.callbacks.process(s, event) 

1744 

1745 def key_release_event(self, key, guiEvent=None): 

1746 """ 

1747 Pass a `KeyEvent` to all functions connected to ``key_release_event``. 

1748 """ 

1749 s = 'key_release_event' 

1750 event = KeyEvent( 

1751 s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) 

1752 self.callbacks.process(s, event) 

1753 self._key = None 

1754 

1755 def pick_event(self, mouseevent, artist, **kwargs): 

1756 """ 

1757 This method will be called by artists who are picked and will 

1758 fire off :class:`PickEvent` callbacks registered listeners 

1759 """ 

1760 s = 'pick_event' 

1761 event = PickEvent(s, self, mouseevent, artist, 

1762 guiEvent=mouseevent.guiEvent, 

1763 **kwargs) 

1764 self.callbacks.process(s, event) 

1765 

1766 def scroll_event(self, x, y, step, guiEvent=None): 

1767 """ 

1768 Backend derived classes should call this function on any 

1769 scroll wheel event. (*x*, *y*) are the canvas coords ((0, 0) is lower 

1770 left). button and key are as defined in MouseEvent. 

1771 

1772 This method will be call all functions connected to the 

1773 'scroll_event' with a :class:`MouseEvent` instance. 

1774 """ 

1775 if step >= 0: 

1776 self._button = 'up' 

1777 else: 

1778 self._button = 'down' 

1779 s = 'scroll_event' 

1780 mouseevent = MouseEvent(s, self, x, y, self._button, self._key, 

1781 step=step, guiEvent=guiEvent) 

1782 self.callbacks.process(s, mouseevent) 

1783 

1784 def button_press_event(self, x, y, button, dblclick=False, guiEvent=None): 

1785 """ 

1786 Backend derived classes should call this function on any mouse 

1787 button press. (*x*, *y*) are the canvas coords ((0, 0) is lower left). 

1788 button and key are as defined in :class:`MouseEvent`. 

1789 

1790 This method will be call all functions connected to the 

1791 'button_press_event' with a :class:`MouseEvent` instance. 

1792 """ 

1793 self._button = button 

1794 s = 'button_press_event' 

1795 mouseevent = MouseEvent(s, self, x, y, button, self._key, 

1796 dblclick=dblclick, guiEvent=guiEvent) 

1797 self.callbacks.process(s, mouseevent) 

1798 

1799 def button_release_event(self, x, y, button, guiEvent=None): 

1800 """ 

1801 Backend derived classes should call this function on any mouse 

1802 button release. 

1803 

1804 This method will call all functions connected to the 

1805 'button_release_event' with a :class:`MouseEvent` instance. 

1806 

1807 Parameters 

1808 ---------- 

1809 x : float 

1810 The canvas coordinates where 0=left. 

1811 y : float 

1812 The canvas coordinates where 0=bottom. 

1813 guiEvent 

1814 The native UI event that generated the Matplotlib event. 

1815 """ 

1816 s = 'button_release_event' 

1817 event = MouseEvent(s, self, x, y, button, self._key, guiEvent=guiEvent) 

1818 self.callbacks.process(s, event) 

1819 self._button = None 

1820 

1821 def motion_notify_event(self, x, y, guiEvent=None): 

1822 """ 

1823 Backend derived classes should call this function on any 

1824 motion-notify-event. 

1825 

1826 This method will call all functions connected to the 

1827 'motion_notify_event' with a :class:`MouseEvent` instance. 

1828 

1829 Parameters 

1830 ---------- 

1831 x : float 

1832 The canvas coordinates where 0=left. 

1833 y : float 

1834 The canvas coordinates where 0=bottom. 

1835 guiEvent 

1836 The native UI event that generated the Matplotlib event. 

1837 """ 

1838 self._lastx, self._lasty = x, y 

1839 s = 'motion_notify_event' 

1840 event = MouseEvent(s, self, x, y, self._button, self._key, 

1841 guiEvent=guiEvent) 

1842 self.callbacks.process(s, event) 

1843 

1844 def leave_notify_event(self, guiEvent=None): 

1845 """ 

1846 Backend derived classes should call this function when leaving 

1847 canvas 

1848 

1849 Parameters 

1850 ---------- 

1851 guiEvent 

1852 The native UI event that generated the Matplotlib event. 

1853 """ 

1854 self.callbacks.process('figure_leave_event', LocationEvent.lastevent) 

1855 LocationEvent.lastevent = None 

1856 self._lastx, self._lasty = None, None 

1857 

1858 def enter_notify_event(self, guiEvent=None, xy=None): 

1859 """ 

1860 Backend derived classes should call this function when entering 

1861 canvas 

1862 

1863 Parameters 

1864 ---------- 

1865 guiEvent 

1866 The native UI event that generated the Matplotlib event. 

1867 xy : (float, float) 

1868 The coordinate location of the pointer when the canvas is entered. 

1869 """ 

1870 if xy is not None: 

1871 x, y = xy 

1872 self._lastx, self._lasty = x, y 

1873 else: 

1874 x = None 

1875 y = None 

1876 cbook.warn_deprecated( 

1877 '3.0', message='enter_notify_event expects a location but ' 

1878 'your backend did not pass one.') 

1879 

1880 event = LocationEvent('figure_enter_event', self, x, y, guiEvent) 

1881 self.callbacks.process('figure_enter_event', event) 

1882 

1883 def inaxes(self, xy): 

1884 """ 

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

1886 

1887 Parameters 

1888 ---------- 

1889 xy : tuple or list 

1890 (x, y) coordinates. 

1891 x position - pixels from left of canvas. 

1892 y position - pixels from bottom of canvas. 

1893 

1894 Returns 

1895 ------- 

1896 axes : `~matplotlib.axes.Axes` or None 

1897 The topmost visible axes containing the point, or None if no axes. 

1898 """ 

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

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

1901 if axes_list: 

1902 axes = cbook._topmost_artist(axes_list) 

1903 else: 

1904 axes = None 

1905 

1906 return axes 

1907 

1908 def grab_mouse(self, ax): 

1909 """ 

1910 Set the child axes which are currently grabbing the mouse events. 

1911 Usually called by the widgets themselves. 

1912 It is an error to call this if the mouse is already grabbed by 

1913 another axes. 

1914 """ 

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

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

1917 self.mouse_grabber = ax 

1918 

1919 def release_mouse(self, ax): 

1920 """ 

1921 Release the mouse grab held by the axes, ax. 

1922 Usually called by the widgets. 

1923 It is ok to call this even if you ax doesn't have the mouse 

1924 grab currently. 

1925 """ 

1926 if self.mouse_grabber is ax: 

1927 self.mouse_grabber = None 

1928 

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

1930 """Render the :class:`~matplotlib.figure.Figure`.""" 

1931 

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

1933 """ 

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

1935 

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

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

1938 

1939 Notes 

1940 ----- 

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

1942 strategy to prevent multiple renderings. 

1943 

1944 """ 

1945 if not self._is_idle_drawing: 

1946 with self._idle_draw_cntx(): 

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

1948 

1949 @cbook.deprecated("3.2") 

1950 def draw_cursor(self, event): 

1951 """ 

1952 Draw a cursor in the event.axes if inaxes is not None. Use 

1953 native GUI drawing for efficiency if possible 

1954 """ 

1955 

1956 def get_width_height(self): 

1957 """ 

1958 Return the figure width and height in points or pixels 

1959 (depending on the backend), truncated to integers 

1960 """ 

1961 return int(self.figure.bbox.width), int(self.figure.bbox.height) 

1962 

1963 @classmethod 

1964 def get_supported_filetypes(cls): 

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

1966 return cls.filetypes 

1967 

1968 @classmethod 

1969 def get_supported_filetypes_grouped(cls): 

1970 """ 

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

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

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

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

1975 """ 

1976 groupings = {} 

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

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

1979 groupings[name].sort() 

1980 return groupings 

1981 

1982 def _get_output_canvas(self, fmt): 

1983 """ 

1984 Return a canvas suitable for saving figures to a specified file format. 

1985 

1986 If necessary, this function will switch to a registered backend that 

1987 supports the format. 

1988 """ 

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

1990 if hasattr(self, 'print_{}'.format(fmt)): 

1991 return self 

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

1993 canvas_class = get_registered_canvas_class(fmt) 

1994 if canvas_class: 

1995 return self.switch_backends(canvas_class) 

1996 # Else report error for unsupported format. 

1997 raise ValueError( 

1998 "Format {!r} is not supported (supported formats: {})" 

1999 .format(fmt, ", ".join(sorted(self.get_supported_filetypes())))) 

2000 

2001 def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, 

2002 orientation='portrait', format=None, 

2003 *, bbox_inches=None, **kwargs): 

2004 """ 

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

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

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

2008 hardcopy. 

2009 

2010 Parameters 

2011 ---------- 

2012 filename 

2013 can also be a file object on image backends 

2014 

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

2016 only currently applies to PostScript printing. 

2017 

2018 dpi : scalar, optional 

2019 the dots per inch to save the figure in; if None, use savefig.dpi 

2020 

2021 facecolor : color, default: :rc:`savefig.facecolor` 

2022 The facecolor of the figure. 

2023 

2024 edgecolor : color, default: :rc:`savefig.edgecolor` 

2025 The edgecolor of the figure. 

2026 

2027 format : str, optional 

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

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

2030 :rc:`savefig.format`. 

2031 

2032 bbox_inches : 'tight' or `~matplotlib.transforms.Bbox`, \ 

2033default: :rc:`savefig.bbox` 

2034 Bbox in inches. Only the given portion of the figure is 

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

2036 the figure. 

2037 

2038 pad_inches : float, default: :rc:`savefig.pad_inches` 

2039 Amount of padding around the figure when *bbox_inches* is 'tight'. 

2040 

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

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

2043 tight bbox is calculated. 

2044 

2045 """ 

2046 if format is None: 

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

2048 if isinstance(filename, os.PathLike): 

2049 filename = os.fspath(filename) 

2050 if isinstance(filename, str): 

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

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

2053 format = self.get_default_filetype() 

2054 if isinstance(filename, str): 

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

2056 format = format.lower() 

2057 

2058 # get canvas object and print method for format 

2059 canvas = self._get_output_canvas(format) 

2060 print_method = getattr(canvas, 'print_%s' % format) 

2061 

2062 if dpi is None: 

2063 dpi = rcParams['savefig.dpi'] 

2064 if dpi == 'figure': 

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

2066 

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

2068 # Some code (e.g. Figure.show) differentiates between having *no* 

2069 # manager and a *None* manager, which should be fixed at some point, 

2070 # but this should be fine. 

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

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

2073 cbook._setattr_cm(canvas, _is_saving=True): 

2074 

2075 if facecolor is None: 

2076 facecolor = rcParams['savefig.facecolor'] 

2077 if edgecolor is None: 

2078 edgecolor = rcParams['savefig.edgecolor'] 

2079 

2080 origfacecolor = self.figure.get_facecolor() 

2081 origedgecolor = self.figure.get_edgecolor() 

2082 

2083 self.figure.set_facecolor(facecolor) 

2084 self.figure.set_edgecolor(edgecolor) 

2085 

2086 if bbox_inches is None: 

2087 bbox_inches = rcParams['savefig.bbox'] 

2088 

2089 if bbox_inches: 

2090 if bbox_inches == "tight": 

2091 renderer = _get_renderer( 

2092 self.figure, 

2093 functools.partial( 

2094 print_method, dpi=dpi, orientation=orientation) 

2095 ) 

2096 ctx = (renderer._draw_disabled() 

2097 if hasattr(renderer, '_draw_disabled') 

2098 else suppress()) 

2099 with ctx: 

2100 self.figure.draw(renderer) 

2101 bbox_artists = kwargs.pop("bbox_extra_artists", None) 

2102 bbox_inches = self.figure.get_tightbbox(renderer, 

2103 bbox_extra_artists=bbox_artists) 

2104 pad = kwargs.pop("pad_inches", None) 

2105 if pad is None: 

2106 pad = rcParams['savefig.pad_inches'] 

2107 

2108 bbox_inches = bbox_inches.padded(pad) 

2109 

2110 # call adjust_bbox to save only the given area 

2111 restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, 

2112 canvas.fixed_dpi) 

2113 

2114 _bbox_inches_restore = (bbox_inches, restore_bbox) 

2115 else: 

2116 _bbox_inches_restore = None 

2117 

2118 try: 

2119 result = print_method( 

2120 filename, 

2121 dpi=dpi, 

2122 facecolor=facecolor, 

2123 edgecolor=edgecolor, 

2124 orientation=orientation, 

2125 bbox_inches_restore=_bbox_inches_restore, 

2126 **kwargs) 

2127 finally: 

2128 if bbox_inches and restore_bbox: 

2129 restore_bbox() 

2130 

2131 self.figure.set_facecolor(origfacecolor) 

2132 self.figure.set_edgecolor(origedgecolor) 

2133 self.figure.set_canvas(self) 

2134 return result 

2135 

2136 @classmethod 

2137 def get_default_filetype(cls): 

2138 """ 

2139 Get the default savefig file format as specified in rcParam 

2140 ``savefig.format``. Returned string excludes period. Overridden 

2141 in backends that only support a single file type. 

2142 """ 

2143 return rcParams['savefig.format'] 

2144 

2145 def get_window_title(self): 

2146 """ 

2147 Get the title text of the window containing the figure. 

2148 Return None if there is no window (e.g., a PS backend). 

2149 """ 

2150 if hasattr(self, "manager"): 

2151 return self.manager.get_window_title() 

2152 

2153 def set_window_title(self, title): 

2154 """ 

2155 Set the title text of the window containing the figure. Note that 

2156 this has no effect if there is no window (e.g., a PS backend). 

2157 """ 

2158 if hasattr(self, "manager"): 

2159 self.manager.set_window_title(title) 

2160 

2161 def get_default_filename(self): 

2162 """ 

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

2164 a default filename. 

2165 """ 

2166 default_basename = self.get_window_title() or 'image' 

2167 default_basename = default_basename.replace(' ', '_') 

2168 default_filetype = self.get_default_filetype() 

2169 default_filename = default_basename + '.' + default_filetype 

2170 return default_filename 

2171 

2172 def switch_backends(self, FigureCanvasClass): 

2173 """ 

2174 Instantiate an instance of FigureCanvasClass 

2175 

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

2177 FigureCanvasPS from a FigureCanvasGTK. Note, deep copying is 

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

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

2180 """ 

2181 newCanvas = FigureCanvasClass(self.figure) 

2182 newCanvas._is_saving = self._is_saving 

2183 return newCanvas 

2184 

2185 def mpl_connect(self, s, func): 

2186 """ 

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

2188 

2189 Parameters 

2190 ---------- 

2191 s : str 

2192 One of the following events ids: 

2193 

2194 - 'button_press_event' 

2195 - 'button_release_event' 

2196 - 'draw_event' 

2197 - 'key_press_event' 

2198 - 'key_release_event' 

2199 - 'motion_notify_event' 

2200 - 'pick_event' 

2201 - 'resize_event' 

2202 - 'scroll_event' 

2203 - 'figure_enter_event', 

2204 - 'figure_leave_event', 

2205 - 'axes_enter_event', 

2206 - 'axes_leave_event' 

2207 - 'close_event'. 

2208 

2209 func : callable 

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

2211 signature:: 

2212 

2213 def func(event: Event) -> Any 

2214 

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

2216 mouse is over the axes, the ``inaxes`` attribute of the event will 

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

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

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

2220 and `.MouseEvent` for more info. 

2221 

2222 Returns 

2223 ------- 

2224 cid 

2225 A connection id that can be used with 

2226 `.FigureCanvasBase.mpl_disconnect`. 

2227 

2228 Examples 

2229 -------- 

2230 :: 

2231 

2232 def on_press(event): 

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

2234 

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

2236 """ 

2237 

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

2239 

2240 def mpl_disconnect(self, cid): 

2241 """ 

2242 Disconnect the callback with id *cid*. 

2243 

2244 Examples 

2245 -------- 

2246 :: 

2247 

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

2249 # ... later 

2250 canvas.mpl_disconnect(cid) 

2251 """ 

2252 return self.callbacks.disconnect(cid) 

2253 

2254 def new_timer(self, *args, **kwargs): 

2255 """ 

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

2257 

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

2259 event loop. Implemented only for backends with GUIs. 

2260 

2261 Other Parameters 

2262 ---------------- 

2263 interval : scalar 

2264 Timer interval in milliseconds 

2265 

2266 callbacks : List[Tuple[callable, Tuple, Dict]] 

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

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

2269 

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

2271 timer. 

2272 

2273 Examples 

2274 -------- 

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

2276 """ 

2277 return TimerBase(*args, **kwargs) 

2278 

2279 def flush_events(self): 

2280 """ 

2281 Flush the GUI events for the figure. 

2282 

2283 Interactive backends need to reimplement this method. 

2284 """ 

2285 

2286 def start_event_loop(self, timeout=0): 

2287 """ 

2288 Start a blocking event loop. 

2289 

2290 Such an event loop is used by interactive functions, such as `ginput` 

2291 and `waitforbuttonpress`, to wait for events. 

2292 

2293 The event loop blocks until a callback function triggers 

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

2295 

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

2297 

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

2299 on `flush_events` being properly implemented. 

2300 

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

2302 """ 

2303 if timeout <= 0: 

2304 timeout = np.inf 

2305 timestep = 0.01 

2306 counter = 0 

2307 self._looping = True 

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

2309 self.flush_events() 

2310 time.sleep(timestep) 

2311 counter += 1 

2312 

2313 def stop_event_loop(self): 

2314 """ 

2315 Stop the current blocking event loop. 

2316 

2317 Interactive backends need to reimplement this to match 

2318 `start_event_loop` 

2319 """ 

2320 self._looping = False 

2321 

2322 

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

2324 """ 

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

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

2327 

2328 Parameters 

2329 ---------- 

2330 event : :class:`KeyEvent` 

2331 a key press/release event 

2332 canvas : :class:`FigureCanvasBase` 

2333 the backend-specific canvas instance 

2334 toolbar : :class:`NavigationToolbar2` 

2335 the navigation cursor toolbar 

2336 """ 

2337 # these bindings happen whether you are over an axes or not 

2338 

2339 if event.key is None: 

2340 return 

2341 

2342 # Load key-mappings from rcParams. 

2343 fullscreen_keys = rcParams['keymap.fullscreen'] 

2344 home_keys = rcParams['keymap.home'] 

2345 back_keys = rcParams['keymap.back'] 

2346 forward_keys = rcParams['keymap.forward'] 

2347 pan_keys = rcParams['keymap.pan'] 

2348 zoom_keys = rcParams['keymap.zoom'] 

2349 save_keys = rcParams['keymap.save'] 

2350 quit_keys = rcParams['keymap.quit'] 

2351 grid_keys = rcParams['keymap.grid'] 

2352 grid_minor_keys = rcParams['keymap.grid_minor'] 

2353 toggle_yscale_keys = rcParams['keymap.yscale'] 

2354 toggle_xscale_keys = rcParams['keymap.xscale'] 

2355 all_keys = rcParams['keymap.all_axes'] 

2356 

2357 # toggle fullscreen mode ('f', 'ctrl + f') 

2358 if event.key in fullscreen_keys: 

2359 try: 

2360 canvas.manager.full_screen_toggle() 

2361 except AttributeError: 

2362 pass 

2363 

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

2365 if event.key in quit_keys: 

2366 Gcf.destroy_fig(canvas.figure) 

2367 

2368 if toolbar is not None: 

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

2370 if event.key in home_keys: 

2371 toolbar.home() 

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

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

2374 elif event.key in back_keys: 

2375 toolbar.back() 

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

2377 elif event.key in forward_keys: 

2378 toolbar.forward() 

2379 # pan mnemonic (default key 'p') 

2380 elif event.key in pan_keys: 

2381 toolbar.pan() 

2382 toolbar._update_cursor(event) 

2383 # zoom mnemonic (default key 'o') 

2384 elif event.key in zoom_keys: 

2385 toolbar.zoom() 

2386 toolbar._update_cursor(event) 

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

2388 elif event.key in save_keys: 

2389 toolbar.save_figure() 

2390 

2391 if event.inaxes is None: 

2392 return 

2393 

2394 # these bindings require the mouse to be over an axes to trigger 

2395 def _get_uniform_gridstate(ticks): 

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

2397 # not all in the same state. 

2398 if all(tick.gridline.get_visible() for tick in ticks): 

2399 return True 

2400 elif not any(tick.gridline.get_visible() for tick in ticks): 

2401 return False 

2402 else: 

2403 return None 

2404 

2405 ax = event.inaxes 

2406 # toggle major grids in current axes (default key 'g') 

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

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

2409 # customization. 

2410 if (event.key in grid_keys 

2411 # Exclude minor grids not in a uniform state. 

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

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

2414 x_state = _get_uniform_gridstate(ax.xaxis.majorTicks) 

2415 y_state = _get_uniform_gridstate(ax.yaxis.majorTicks) 

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

2417 try: 

2418 x_state, y_state = ( 

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

2420 except ValueError: 

2421 # Exclude major grids not in a uniform state. 

2422 pass 

2423 else: 

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

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

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

2427 canvas.draw_idle() 

2428 # toggle major and minor grids in current axes (default key 'G') 

2429 if (event.key in grid_minor_keys 

2430 # Exclude major grids not in a uniform state. 

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

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

2433 x_state = _get_uniform_gridstate(ax.xaxis.minorTicks) 

2434 y_state = _get_uniform_gridstate(ax.yaxis.minorTicks) 

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

2436 try: 

2437 x_state, y_state = ( 

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

2439 except ValueError: 

2440 # Exclude minor grids not in a uniform state. 

2441 pass 

2442 else: 

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

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

2445 canvas.draw_idle() 

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

2447 elif event.key in toggle_yscale_keys: 

2448 scale = ax.get_yscale() 

2449 if scale == 'log': 

2450 ax.set_yscale('linear') 

2451 ax.figure.canvas.draw_idle() 

2452 elif scale == 'linear': 

2453 try: 

2454 ax.set_yscale('log') 

2455 except ValueError as exc: 

2456 _log.warning(str(exc)) 

2457 ax.set_yscale('linear') 

2458 ax.figure.canvas.draw_idle() 

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

2460 elif event.key in toggle_xscale_keys: 

2461 scalex = ax.get_xscale() 

2462 if scalex == 'log': 

2463 ax.set_xscale('linear') 

2464 ax.figure.canvas.draw_idle() 

2465 elif scalex == 'linear': 

2466 try: 

2467 ax.set_xscale('log') 

2468 except ValueError as exc: 

2469 _log.warning(str(exc)) 

2470 ax.set_xscale('linear') 

2471 ax.figure.canvas.draw_idle() 

2472 # enable nagivation for all axes that contain the event (default key 'a') 

2473 elif event.key in all_keys: 

2474 for a in canvas.figure.get_axes(): 

2475 if (event.x is not None and event.y is not None 

2476 and a.in_axes(event)): # FIXME: Why only these? 

2477 a.set_navigate(True) 

2478 # enable navigation only for axes with this index (if such an axes exist, 

2479 # otherwise do nothing) 

2480 elif event.key.isdigit() and event.key != '0': 

2481 n = int(event.key) - 1 

2482 if n < len(canvas.figure.get_axes()): 

2483 for i, a in enumerate(canvas.figure.get_axes()): 

2484 if (event.x is not None and event.y is not None 

2485 and a.in_axes(event)): # FIXME: Why only these? 

2486 a.set_navigate(i == n) 

2487 

2488 

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

2490 """ 

2491 The default Matplotlib button actions for extra mouse buttons. 

2492 """ 

2493 if toolbar is not None: 

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

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

2496 toolbar.back() 

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

2498 toolbar.forward() 

2499 

2500 

2501class NonGuiException(Exception): 

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

2503 pass 

2504 

2505 

2506class FigureManagerBase: 

2507 """ 

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

2509 

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

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

2512 represents the visual figure on screen. 

2513 

2514 GUI backends define from this class to translate common operations such 

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

2516 support these operations an can just use the base class. 

2517 

2518 This following basic operations are accessible: 

2519 

2520 **Window operations** 

2521 

2522 - `~.FigureManagerBase.show` 

2523 - `~.FigureManagerBase.destroy` 

2524 - `~.FigureManagerBase.full_screen_toggle` 

2525 - `~.FigureManagerBase.resize` 

2526 - `~.FigureManagerBase.get_window_title` 

2527 - `~.FigureManagerBase.set_window_title` 

2528 

2529 **Key and mouse button press handling** 

2530 

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

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

2533 ensures the same shortcuts and mouse actions across backends. 

2534 

2535 **Other operations** 

2536 

2537 Subclasses will have additional attributes and functions to access 

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

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

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

2541 

2542 Attributes 

2543 ---------- 

2544 canvas : :class:`FigureCanvasBase` 

2545 The backend-specific canvas instance. 

2546 

2547 num : int or str 

2548 The figure number. 

2549 

2550 key_press_handler_id : int 

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

2552 To disable the default key press handling use:: 

2553 

2554 figure.canvas.mpl_disconnect( 

2555 figure.canvas.manager.key_press_handler_id) 

2556 

2557 button_press_handler_id : int 

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

2559 To disable the default button press handling use:: 

2560 

2561 figure.canvas.mpl_disconnect( 

2562 figure.canvas.manager.button_press_handler_id) 

2563 """ 

2564 def __init__(self, canvas, num): 

2565 self.canvas = canvas 

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

2567 self.num = num 

2568 

2569 self.key_press_handler_id = None 

2570 self.button_press_handler_id = None 

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

2572 self.key_press_handler_id = self.canvas.mpl_connect( 

2573 'key_press_event', 

2574 self.key_press) 

2575 self.button_press_handler_id = self.canvas.mpl_connect( 

2576 'button_press_event', 

2577 self.button_press) 

2578 

2579 self.toolmanager = None 

2580 self.toolbar = None 

2581 

2582 @self.canvas.figure.add_axobserver 

2583 def notify_axes_change(fig): 

2584 # Called whenever the current axes is changed. 

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

2586 self.toolbar.update() 

2587 

2588 def show(self): 

2589 """ 

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

2591 For non-GUI backends, raise an exception to be caught 

2592 by :meth:`~matplotlib.figure.Figure.show`, for an 

2593 optional warning. 

2594 """ 

2595 raise NonGuiException() 

2596 

2597 def destroy(self): 

2598 pass 

2599 

2600 def full_screen_toggle(self): 

2601 pass 

2602 

2603 def resize(self, w, h): 

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

2605 

2606 def key_press(self, event): 

2607 """ 

2608 Implement the default Matplotlib key bindings defined at 

2609 :ref:`key-event-handling`. 

2610 """ 

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

2612 key_press_handler(event, self.canvas, self.canvas.toolbar) 

2613 

2614 def button_press(self, event): 

2615 """The default Matplotlib button actions for extra mouse buttons.""" 

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

2617 button_press_handler(event, self.canvas, self.canvas.toolbar) 

2618 

2619 def get_window_title(self): 

2620 """ 

2621 Get the title text of the window containing the figure. 

2622 

2623 Return None for non-GUI (e.g., PS) backends. 

2624 """ 

2625 return 'image' 

2626 

2627 def set_window_title(self, title): 

2628 """ 

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

2630 

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

2632 """ 

2633 

2634 

2635cursors = tools.cursors 

2636 

2637 

2638class NavigationToolbar2: 

2639 """ 

2640 Base class for the navigation cursor, version 2 

2641 

2642 backends must implement a canvas that handles connections for 

2643 'button_press_event' and 'button_release_event'. See 

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

2645 

2646 

2647 They must also define 

2648 

2649 :meth:`save_figure` 

2650 save the current figure 

2651 

2652 :meth:`set_cursor` 

2653 if you want the pointer icon to change 

2654 

2655 :meth:`_init_toolbar` 

2656 create your toolbar widget 

2657 

2658 :meth:`draw_rubberband` (optional) 

2659 draw the zoom to rect "rubberband" rectangle 

2660 

2661 :meth:`press` (optional) 

2662 whenever a mouse button is pressed, you'll be notified with 

2663 the event 

2664 

2665 :meth:`release` (optional) 

2666 whenever a mouse button is released, you'll be notified with 

2667 the event 

2668 

2669 :meth:`set_message` (optional) 

2670 display message 

2671 

2672 :meth:`set_history_buttons` (optional) 

2673 you can change the history back / forward buttons to 

2674 indicate disabled / enabled state. 

2675 

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

2677 """ 

2678 

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

2680 # ( 

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

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

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

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

2685 # ) 

2686 toolitems = ( 

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

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

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

2690 (None, None, None, None), 

2691 ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'), 

2692 ('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'), 

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

2694 (None, None, None, None), 

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

2696 ) 

2697 

2698 def __init__(self, canvas): 

2699 self.canvas = canvas 

2700 canvas.toolbar = self 

2701 self._nav_stack = cbook.Stack() 

2702 self._xypress = None # location and axis info at the time of the press 

2703 self._idPress = None 

2704 self._idRelease = None 

2705 self._active = None 

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

2707 self._lastCursor = cursors.POINTER 

2708 self._init_toolbar() 

2709 self._idDrag = self.canvas.mpl_connect( 

2710 'motion_notify_event', self.mouse_move) 

2711 

2712 self._ids_zoom = [] 

2713 self._zoom_mode = None 

2714 

2715 self._button_pressed = None # determined by button pressed at start 

2716 

2717 self.mode = '' # a mode string for the status bar 

2718 self.set_history_buttons() 

2719 

2720 def set_message(self, s): 

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

2722 

2723 def back(self, *args): 

2724 """Move back up the view lim stack.""" 

2725 self._nav_stack.back() 

2726 self.set_history_buttons() 

2727 self._update_view() 

2728 

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

2730 """ 

2731 Draw a rectangle rubberband to indicate zoom limits. 

2732 

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

2734 """ 

2735 

2736 def remove_rubberband(self): 

2737 """Remove the rubberband.""" 

2738 

2739 def forward(self, *args): 

2740 """Move forward in the view lim stack.""" 

2741 self._nav_stack.forward() 

2742 self.set_history_buttons() 

2743 self._update_view() 

2744 

2745 def home(self, *args): 

2746 """Restore the original view.""" 

2747 self._nav_stack.home() 

2748 self.set_history_buttons() 

2749 self._update_view() 

2750 

2751 def _init_toolbar(self): 

2752 """ 

2753 This is where you actually build the GUI widgets (called by 

2754 __init__). The icons ``home.xpm``, ``back.xpm``, ``forward.xpm``, 

2755 ``hand.xpm``, ``zoom_to_rect.xpm`` and ``filesave.xpm`` are standard 

2756 across backends (there are ppm versions in CVS also). 

2757 

2758 You just need to set the callbacks 

2759 

2760 home : self.home 

2761 back : self.back 

2762 forward : self.forward 

2763 hand : self.pan 

2764 zoom_to_rect : self.zoom 

2765 filesave : self.save_figure 

2766 

2767 You only need to define the last one - the others are in the base 

2768 class implementation. 

2769 

2770 """ 

2771 raise NotImplementedError 

2772 

2773 def _update_cursor(self, event): 

2774 """ 

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

2776 """ 

2777 if not event.inaxes or not self._active: 

2778 if self._lastCursor != cursors.POINTER: 

2779 self.set_cursor(cursors.POINTER) 

2780 self._lastCursor = cursors.POINTER 

2781 else: 

2782 if (self._active == 'ZOOM' 

2783 and self._lastCursor != cursors.SELECT_REGION): 

2784 self.set_cursor(cursors.SELECT_REGION) 

2785 self._lastCursor = cursors.SELECT_REGION 

2786 elif (self._active == 'PAN' and 

2787 self._lastCursor != cursors.MOVE): 

2788 self.set_cursor(cursors.MOVE) 

2789 self._lastCursor = cursors.MOVE 

2790 

2791 @contextmanager 

2792 def _wait_cursor_for_draw_cm(self): 

2793 """ 

2794 Set the cursor to a wait cursor when drawing the canvas. 

2795 

2796 In order to avoid constantly changing the cursor when the canvas 

2797 changes frequently, do nothing if this context was triggered during the 

2798 last second. (Optimally we'd prefer only setting the wait cursor if 

2799 the *current* draw takes too long, but the current draw blocks the GUI 

2800 thread). 

2801 """ 

2802 self._draw_time, last_draw_time = ( 

2803 time.time(), getattr(self, "_draw_time", -np.inf)) 

2804 if self._draw_time - last_draw_time > 1: 

2805 try: 

2806 self.set_cursor(cursors.WAIT) 

2807 yield 

2808 finally: 

2809 self.set_cursor(self._lastCursor) 

2810 else: 

2811 yield 

2812 

2813 def mouse_move(self, event): 

2814 self._update_cursor(event) 

2815 

2816 if event.inaxes and event.inaxes.get_navigate(): 

2817 

2818 try: 

2819 s = event.inaxes.format_coord(event.xdata, event.ydata) 

2820 except (ValueError, OverflowError): 

2821 pass 

2822 else: 

2823 artists = [a for a in event.inaxes._mouseover_set 

2824 if a.contains(event)[0] and a.get_visible()] 

2825 

2826 if artists: 

2827 a = cbook._topmost_artist(artists) 

2828 if a is not event.inaxes.patch: 

2829 data = a.get_cursor_data(event) 

2830 if data is not None: 

2831 data_str = a.format_cursor_data(data) 

2832 if data_str is not None: 

2833 s = s + ' ' + data_str 

2834 

2835 if len(self.mode): 

2836 self.set_message('%s, %s' % (self.mode, s)) 

2837 else: 

2838 self.set_message(s) 

2839 else: 

2840 self.set_message(self.mode) 

2841 

2842 def pan(self, *args): 

2843 """ 

2844 Activate the pan/zoom tool. 

2845 

2846 Pan with left button, zoom with right. 

2847 """ 

2848 # set the pointer icon and button press funcs to the 

2849 # appropriate callbacks 

2850 

2851 if self._active == 'PAN': 

2852 self._active = None 

2853 else: 

2854 self._active = 'PAN' 

2855 if self._idPress is not None: 

2856 self._idPress = self.canvas.mpl_disconnect(self._idPress) 

2857 self.mode = '' 

2858 

2859 if self._idRelease is not None: 

2860 self._idRelease = self.canvas.mpl_disconnect(self._idRelease) 

2861 self.mode = '' 

2862 

2863 if self._active: 

2864 self._idPress = self.canvas.mpl_connect( 

2865 'button_press_event', self.press_pan) 

2866 self._idRelease = self.canvas.mpl_connect( 

2867 'button_release_event', self.release_pan) 

2868 self.mode = 'pan/zoom' 

2869 self.canvas.widgetlock(self) 

2870 else: 

2871 self.canvas.widgetlock.release(self) 

2872 

2873 for a in self.canvas.figure.get_axes(): 

2874 a.set_navigate_mode(self._active) 

2875 

2876 self.set_message(self.mode) 

2877 

2878 def press(self, event): 

2879 """Called whenever a mouse button is pressed.""" 

2880 

2881 def press_pan(self, event): 

2882 """Callback for mouse button press in pan/zoom mode.""" 

2883 

2884 if event.button == 1: 

2885 self._button_pressed = 1 

2886 elif event.button == 3: 

2887 self._button_pressed = 3 

2888 else: 

2889 self._button_pressed = None 

2890 return 

2891 

2892 if self._nav_stack() is None: 

2893 # set the home button to this view 

2894 self.push_current() 

2895 

2896 x, y = event.x, event.y 

2897 self._xypress = [] 

2898 for i, a in enumerate(self.canvas.figure.get_axes()): 

2899 if (x is not None and y is not None and a.in_axes(event) and 

2900 a.get_navigate() and a.can_pan()): 

2901 a.start_pan(x, y, event.button) 

2902 self._xypress.append((a, i)) 

2903 self.canvas.mpl_disconnect(self._idDrag) 

2904 self._idDrag = self.canvas.mpl_connect('motion_notify_event', 

2905 self.drag_pan) 

2906 

2907 self.press(event) 

2908 

2909 def press_zoom(self, event): 

2910 """Callback for mouse button press in zoom to rect mode.""" 

2911 # If we're already in the middle of a zoom, pressing another 

2912 # button works to "cancel" 

2913 if self._ids_zoom != []: 

2914 for zoom_id in self._ids_zoom: 

2915 self.canvas.mpl_disconnect(zoom_id) 

2916 self.release(event) 

2917 self.draw() 

2918 self._xypress = None 

2919 self._button_pressed = None 

2920 self._ids_zoom = [] 

2921 return 

2922 

2923 if event.button == 1: 

2924 self._button_pressed = 1 

2925 elif event.button == 3: 

2926 self._button_pressed = 3 

2927 else: 

2928 self._button_pressed = None 

2929 return 

2930 

2931 if self._nav_stack() is None: 

2932 # set the home button to this view 

2933 self.push_current() 

2934 

2935 x, y = event.x, event.y 

2936 self._xypress = [] 

2937 for i, a in enumerate(self.canvas.figure.get_axes()): 

2938 if (x is not None and y is not None and a.in_axes(event) and 

2939 a.get_navigate() and a.can_zoom()): 

2940 self._xypress.append((x, y, a, i, a._get_view())) 

2941 

2942 id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom) 

2943 id2 = self.canvas.mpl_connect('key_press_event', 

2944 self._switch_on_zoom_mode) 

2945 id3 = self.canvas.mpl_connect('key_release_event', 

2946 self._switch_off_zoom_mode) 

2947 

2948 self._ids_zoom = id1, id2, id3 

2949 self._zoom_mode = event.key 

2950 

2951 self.press(event) 

2952 

2953 def _switch_on_zoom_mode(self, event): 

2954 self._zoom_mode = event.key 

2955 self.mouse_move(event) 

2956 

2957 def _switch_off_zoom_mode(self, event): 

2958 self._zoom_mode = None 

2959 self.mouse_move(event) 

2960 

2961 def push_current(self): 

2962 """Push the current view limits and position onto the stack.""" 

2963 self._nav_stack.push( 

2964 WeakKeyDictionary( 

2965 {ax: (ax._get_view(), 

2966 # Store both the original and modified positions. 

2967 (ax.get_position(True).frozen(), 

2968 ax.get_position().frozen())) 

2969 for ax in self.canvas.figure.axes})) 

2970 self.set_history_buttons() 

2971 

2972 def release(self, event): 

2973 """Callback for mouse button release.""" 

2974 

2975 def release_pan(self, event): 

2976 """Callback for mouse button release in pan/zoom mode.""" 

2977 

2978 if self._button_pressed is None: 

2979 return 

2980 self.canvas.mpl_disconnect(self._idDrag) 

2981 self._idDrag = self.canvas.mpl_connect( 

2982 'motion_notify_event', self.mouse_move) 

2983 for a, ind in self._xypress: 

2984 a.end_pan() 

2985 if not self._xypress: 

2986 return 

2987 self._xypress = [] 

2988 self._button_pressed = None 

2989 self.push_current() 

2990 self.release(event) 

2991 self.draw() 

2992 

2993 def drag_pan(self, event): 

2994 """Callback for dragging in pan/zoom mode.""" 

2995 for a, ind in self._xypress: 

2996 #safer to use the recorded button at the press than current button: 

2997 #multiple button can get pressed during motion... 

2998 a.drag_pan(self._button_pressed, event.key, event.x, event.y) 

2999 self.canvas.draw_idle() 

3000 

3001 def drag_zoom(self, event): 

3002 """Callback for dragging in zoom mode.""" 

3003 if self._xypress: 

3004 x, y = event.x, event.y 

3005 lastx, lasty, a, ind, view = self._xypress[0] 

3006 (x1, y1), (x2, y2) = np.clip( 

3007 [[lastx, lasty], [x, y]], a.bbox.min, a.bbox.max) 

3008 if self._zoom_mode == "x": 

3009 y1, y2 = a.bbox.intervaly 

3010 elif self._zoom_mode == "y": 

3011 x1, x2 = a.bbox.intervalx 

3012 self.draw_rubberband(event, x1, y1, x2, y2) 

3013 

3014 def release_zoom(self, event): 

3015 """Callback for mouse button release in zoom to rect mode.""" 

3016 for zoom_id in self._ids_zoom: 

3017 self.canvas.mpl_disconnect(zoom_id) 

3018 self._ids_zoom = [] 

3019 

3020 self.remove_rubberband() 

3021 

3022 if not self._xypress: 

3023 return 

3024 

3025 last_a = [] 

3026 

3027 for cur_xypress in self._xypress: 

3028 x, y = event.x, event.y 

3029 lastx, lasty, a, ind, view = cur_xypress 

3030 # ignore singular clicks - 5 pixels is a threshold 

3031 # allows the user to "cancel" a zoom action 

3032 # by zooming by less than 5 pixels 

3033 if ((abs(x - lastx) < 5 and self._zoom_mode != "y") or 

3034 (abs(y - lasty) < 5 and self._zoom_mode != "x")): 

3035 self._xypress = None 

3036 self.release(event) 

3037 self.draw() 

3038 return 

3039 

3040 # detect twinx, twiny axes and avoid double zooming 

3041 twinx, twiny = False, False 

3042 if last_a: 

3043 for la in last_a: 

3044 if a.get_shared_x_axes().joined(a, la): 

3045 twinx = True 

3046 if a.get_shared_y_axes().joined(a, la): 

3047 twiny = True 

3048 last_a.append(a) 

3049 

3050 if self._button_pressed == 1: 

3051 direction = 'in' 

3052 elif self._button_pressed == 3: 

3053 direction = 'out' 

3054 else: 

3055 continue 

3056 

3057 a._set_view_from_bbox((lastx, lasty, x, y), direction, 

3058 self._zoom_mode, twinx, twiny) 

3059 

3060 self.draw() 

3061 self._xypress = None 

3062 self._button_pressed = None 

3063 

3064 self._zoom_mode = None 

3065 

3066 self.push_current() 

3067 self.release(event) 

3068 

3069 def draw(self): 

3070 """Redraw the canvases, update the locators.""" 

3071 for a in self.canvas.figure.get_axes(): 

3072 xaxis = getattr(a, 'xaxis', None) 

3073 yaxis = getattr(a, 'yaxis', None) 

3074 locators = [] 

3075 if xaxis is not None: 

3076 locators.append(xaxis.get_major_locator()) 

3077 locators.append(xaxis.get_minor_locator()) 

3078 if yaxis is not None: 

3079 locators.append(yaxis.get_major_locator()) 

3080 locators.append(yaxis.get_minor_locator()) 

3081 

3082 for loc in locators: 

3083 loc.refresh() 

3084 self.canvas.draw_idle() 

3085 

3086 def _update_view(self): 

3087 """ 

3088 Update the viewlim and position from the view and position stack for 

3089 each axes. 

3090 """ 

3091 nav_info = self._nav_stack() 

3092 if nav_info is None: 

3093 return 

3094 # Retrieve all items at once to avoid any risk of GC deleting an Axes 

3095 # while in the middle of the loop below. 

3096 items = list(nav_info.items()) 

3097 for ax, (view, (pos_orig, pos_active)) in items: 

3098 ax._set_view(view) 

3099 # Restore both the original and modified positions 

3100 ax._set_position(pos_orig, 'original') 

3101 ax._set_position(pos_active, 'active') 

3102 self.canvas.draw_idle() 

3103 

3104 def save_figure(self, *args): 

3105 """Save the current figure.""" 

3106 raise NotImplementedError 

3107 

3108 def set_cursor(self, cursor): 

3109 """ 

3110 Set the current cursor to one of the :class:`Cursors` enums values. 

3111 

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

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

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

3115 updated. 

3116 """ 

3117 

3118 def update(self): 

3119 """Reset the axes stack.""" 

3120 self._nav_stack.clear() 

3121 self.set_history_buttons() 

3122 

3123 def zoom(self, *args): 

3124 """Activate zoom to rect mode.""" 

3125 if self._active == 'ZOOM': 

3126 self._active = None 

3127 else: 

3128 self._active = 'ZOOM' 

3129 

3130 if self._idPress is not None: 

3131 self._idPress = self.canvas.mpl_disconnect(self._idPress) 

3132 self.mode = '' 

3133 

3134 if self._idRelease is not None: 

3135 self._idRelease = self.canvas.mpl_disconnect(self._idRelease) 

3136 self.mode = '' 

3137 

3138 if self._active: 

3139 self._idPress = self.canvas.mpl_connect('button_press_event', 

3140 self.press_zoom) 

3141 self._idRelease = self.canvas.mpl_connect('button_release_event', 

3142 self.release_zoom) 

3143 self.mode = 'zoom rect' 

3144 self.canvas.widgetlock(self) 

3145 else: 

3146 self.canvas.widgetlock.release(self) 

3147 

3148 for a in self.canvas.figure.get_axes(): 

3149 a.set_navigate_mode(self._active) 

3150 

3151 self.set_message(self.mode) 

3152 

3153 def set_history_buttons(self): 

3154 """Enable or disable the back/forward button.""" 

3155 

3156 

3157class ToolContainerBase: 

3158 """ 

3159 Base class for all tool containers, e.g. toolbars. 

3160 

3161 Attributes 

3162 ---------- 

3163 toolmanager : `ToolManager` 

3164 The tools with which this `ToolContainer` wants to communicate. 

3165 """ 

3166 

3167 _icon_extension = '.png' 

3168 """ 

3169 Toolcontainer button icon image format extension 

3170 

3171 **String**: Image extension 

3172 """ 

3173 

3174 def __init__(self, toolmanager): 

3175 self.toolmanager = toolmanager 

3176 self.toolmanager.toolmanager_connect('tool_removed_event', 

3177 self._remove_tool_cbk) 

3178 

3179 def _tool_toggled_cbk(self, event): 

3180 """ 

3181 Captures the 'tool_trigger_[name]' 

3182 

3183 This only gets used for toggled tools 

3184 """ 

3185 self.toggle_toolitem(event.tool.name, event.tool.toggled) 

3186 

3187 def add_tool(self, tool, group, position=-1): 

3188 """ 

3189 Adds a tool to this container 

3190 

3191 Parameters 

3192 ---------- 

3193 tool : tool_like 

3194 The tool to add, see `ToolManager.get_tool`. 

3195 group : str 

3196 The name of the group to add this tool to. 

3197 position : int (optional) 

3198 The position within the group to place this tool. Defaults to end. 

3199 """ 

3200 tool = self.toolmanager.get_tool(tool) 

3201 image = self._get_image_filename(tool.image) 

3202 toggle = getattr(tool, 'toggled', None) is not None 

3203 self.add_toolitem(tool.name, group, position, 

3204 image, tool.description, toggle) 

3205 if toggle: 

3206 self.toolmanager.toolmanager_connect('tool_trigger_%s' % tool.name, 

3207 self._tool_toggled_cbk) 

3208 # If initially toggled 

3209 if tool.toggled: 

3210 self.toggle_toolitem(tool.name, True) 

3211 

3212 def _remove_tool_cbk(self, event): 

3213 """Captures the 'tool_removed_event' signal and removes the tool.""" 

3214 self.remove_toolitem(event.tool.name) 

3215 

3216 def _get_image_filename(self, image): 

3217 """Find the image based on its name.""" 

3218 if not image: 

3219 return None 

3220 

3221 basedir = cbook._get_data_path("images") 

3222 for fname in [ 

3223 image, 

3224 image + self._icon_extension, 

3225 str(basedir / image), 

3226 str(basedir / (image + self._icon_extension)), 

3227 ]: 

3228 if os.path.isfile(fname): 

3229 return fname 

3230 

3231 def trigger_tool(self, name): 

3232 """ 

3233 Trigger the tool 

3234 

3235 Parameters 

3236 ---------- 

3237 name : str 

3238 Name (id) of the tool triggered from within the container. 

3239 """ 

3240 self.toolmanager.trigger_tool(name, sender=self) 

3241 

3242 def add_toolitem(self, name, group, position, image, description, toggle): 

3243 """ 

3244 Add a toolitem to the container 

3245 

3246 This method must get implemented per backend 

3247 

3248 The callback associated with the button click event, 

3249 must be **EXACTLY** `self.trigger_tool(name)` 

3250 

3251 Parameters 

3252 ---------- 

3253 name : str 

3254 Name of the tool to add, this gets used as the tool's ID and as the 

3255 default label of the buttons 

3256 group : String 

3257 Name of the group that this tool belongs to 

3258 position : Int 

3259 Position of the tool within its group, if -1 it goes at the End 

3260 image_file : String 

3261 Filename of the image for the button or `None` 

3262 description : String 

3263 Description of the tool, used for the tooltips 

3264 toggle : Bool 

3265 * `True` : The button is a toggle (change the pressed/unpressed 

3266 state between consecutive clicks) 

3267 * `False` : The button is a normal button (returns to unpressed 

3268 state after release) 

3269 """ 

3270 raise NotImplementedError 

3271 

3272 def toggle_toolitem(self, name, toggled): 

3273 """ 

3274 Toggle the toolitem without firing event 

3275 

3276 Parameters 

3277 ---------- 

3278 name : String 

3279 Id of the tool to toggle 

3280 toggled : bool 

3281 Whether to set this tool as toggled or not. 

3282 """ 

3283 raise NotImplementedError 

3284 

3285 def remove_toolitem(self, name): 

3286 """ 

3287 Remove a toolitem from the `ToolContainer`. 

3288 

3289 This method must get implemented per backend. 

3290 

3291 Called when `ToolManager` emits a `tool_removed_event`. 

3292 

3293 Parameters 

3294 ---------- 

3295 name : str 

3296 Name of the tool to remove. 

3297 """ 

3298 raise NotImplementedError 

3299 

3300 

3301class StatusbarBase: 

3302 """Base class for the statusbar.""" 

3303 def __init__(self, toolmanager): 

3304 self.toolmanager = toolmanager 

3305 self.toolmanager.toolmanager_connect('tool_message_event', 

3306 self._message_cbk) 

3307 

3308 def _message_cbk(self, event): 

3309 """Capture the 'tool_message_event' and set the message.""" 

3310 self.set_message(event.message) 

3311 

3312 def set_message(self, s): 

3313 """ 

3314 Display a message on toolbar or in status bar. 

3315 

3316 Parameters 

3317 ---------- 

3318 s : str 

3319 Message text. 

3320 """ 

3321 pass 

3322 

3323 

3324class _Backend: 

3325 # A backend can be defined by using the following pattern: 

3326 # 

3327 # @_Backend.export 

3328 # class FooBackend(_Backend): 

3329 # # override the attributes and methods documented below. 

3330 

3331 # `backend_version` may be overridden by the subclass. 

3332 backend_version = "unknown" 

3333 

3334 # The `FigureCanvas` class must be defined. 

3335 FigureCanvas = None 

3336 

3337 # For interactive backends, the `FigureManager` class must be overridden. 

3338 FigureManager = FigureManagerBase 

3339 

3340 # The following methods must be left as None for non-interactive backends. 

3341 # For interactive backends, `trigger_manager_draw` should be a function 

3342 # taking a manager as argument and triggering a canvas draw, and `mainloop` 

3343 # should be a function taking no argument and starting the backend main 

3344 # loop. 

3345 trigger_manager_draw = None 

3346 mainloop = None 

3347 

3348 # The following methods will be automatically defined and exported, but 

3349 # can be overridden. 

3350 

3351 @classmethod 

3352 def new_figure_manager(cls, num, *args, **kwargs): 

3353 """Create a new figure manager instance.""" 

3354 # This import needs to happen here due to circular imports. 

3355 from matplotlib.figure import Figure 

3356 fig_cls = kwargs.pop('FigureClass', Figure) 

3357 fig = fig_cls(*args, **kwargs) 

3358 return cls.new_figure_manager_given_figure(num, fig) 

3359 

3360 @classmethod 

3361 def new_figure_manager_given_figure(cls, num, figure): 

3362 """Create a new figure manager instance for the given figure.""" 

3363 canvas = cls.FigureCanvas(figure) 

3364 manager = cls.FigureManager(canvas, num) 

3365 return manager 

3366 

3367 @classmethod 

3368 def draw_if_interactive(cls): 

3369 if cls.trigger_manager_draw is not None and is_interactive(): 

3370 manager = Gcf.get_active() 

3371 if manager: 

3372 cls.trigger_manager_draw(manager) 

3373 

3374 @classmethod 

3375 @cbook._make_keyword_only("3.1", "block") 

3376 def show(cls, block=None): 

3377 """ 

3378 Show all figures. 

3379 

3380 `show` blocks by calling `mainloop` if *block* is ``True``, or if it 

3381 is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in 

3382 `interactive` mode. 

3383 """ 

3384 managers = Gcf.get_all_fig_managers() 

3385 if not managers: 

3386 return 

3387 for manager in managers: 

3388 # Emits a warning if the backend is non-interactive. 

3389 manager.canvas.figure.show() 

3390 if cls.mainloop is None: 

3391 return 

3392 if block is None: 

3393 # Hack: Are we in IPython's pylab mode? 

3394 from matplotlib import pyplot 

3395 try: 

3396 # IPython versions >= 0.10 tack the _needmain attribute onto 

3397 # pyplot.show, and always set it to False, when in %pylab mode. 

3398 ipython_pylab = not pyplot.show._needmain 

3399 except AttributeError: 

3400 ipython_pylab = False 

3401 block = not ipython_pylab and not is_interactive() 

3402 # TODO: The above is a hack to get the WebAgg backend working with 

3403 # ipython's `%pylab` mode until proper integration is implemented. 

3404 if get_backend() == "WebAgg": 

3405 block = True 

3406 if block: 

3407 cls.mainloop() 

3408 

3409 # This method is the one actually exporting the required methods. 

3410 

3411 @staticmethod 

3412 def export(cls): 

3413 for name in [ 

3414 "backend_version", 

3415 "FigureCanvas", 

3416 "FigureManager", 

3417 "new_figure_manager", 

3418 "new_figure_manager_given_figure", 

3419 "draw_if_interactive", 

3420 "show", 

3421 ]: 

3422 setattr(sys.modules[cls.__module__], name, getattr(cls, name)) 

3423 

3424 # For back-compatibility, generate a shim `Show` class. 

3425 

3426 class Show(ShowBase): 

3427 def mainloop(self): 

3428 return cls.mainloop() 

3429 

3430 setattr(sys.modules[cls.__module__], "Show", Show) 

3431 return cls 

3432 

3433 

3434class ShowBase(_Backend): 

3435 """ 

3436 Simple base class to generate a ``show()`` function in backends. 

3437 

3438 Subclass must override ``mainloop()`` method. 

3439 """ 

3440 

3441 def __call__(self, block=None): 

3442 return self.show(block=block)