Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/backend_bases.py : 22%

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
5:class:`RendererBase`
6 An abstract base class to handle drawing/rendering operations.
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
13:class:`GraphicsContextBase`
14 An abstract base class that provides color, line styles, etc...
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.
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.
28:class:`ToolContainerBase`
29 The base class for the Toolbar class of each interactive backend.
31:class:`StatusbarBase`
32 The base class for the messaging area.
33"""
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
46import numpy as np
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
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
68_log = logging.getLogger(__name__)
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}
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}
96def register_backend(format, backend, description=None):
97 """
98 Register a backend for saving to a given file format.
100 Parameters
101 ----------
102 format : str
103 File extension
105 backend : module string or canvas class
106 Backend for handling file output
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
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
131class RendererBase:
132 """An abstract base class to handle drawing/rendering operations.
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):
138 * :meth:`draw_path`
139 * :meth:`draw_image`
140 * :meth:`draw_gouraud_triangle`
142 The following methods *should* be implemented in the backend for
143 optimization reasons:
145 * :meth:`draw_text`
146 * :meth:`draw_markers`
147 * :meth:`draw_path_collection`
148 * :meth:`draw_quad_mesh`
149 """
151 def __init__(self):
152 self._texmanager = None
153 self._text2path = textpath.TextToPath()
155 def open_group(self, s, gid=None):
156 """
157 Open a grouping element with label *s* and *gid* (if set) as id.
159 Only used by the SVG renderer.
160 """
162 def close_group(self, s):
163 """
164 Close a grouping element with label *s*.
166 Only used by the SVG renderer.
167 """
169 def draw_path(self, gc, path, transform, rgbFace=None):
170 """Draw a `~.path.Path` instance using the given affine transform."""
171 raise NotImplementedError
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.
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.
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.
187 Parameters
188 ----------
189 gc : `GraphicsContextBase`
190 The graphics context.
192 marker_trans : `matplotlib.transforms.Transform`
193 An affine transform applied to the marker.
195 trans : `matplotlib.transforms.Transform`
196 An affine transform applied to the path.
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)
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.
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)]
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)
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 """
255 from matplotlib.collections import QuadMesh
256 paths = QuadMesh.convert_mesh_to_paths(
257 meshWidth, meshHeight, coordinates)
259 if edgecolors is None:
260 edgecolors = facecolors
261 linewidths = np.array([gc.get_linewidth()], float)
263 return self.draw_path_collection(
264 gc, master_transform, paths, [], offsets, offsetTrans, facecolors,
265 edgecolors, linewidths, [], [antialiased], [None], 'screen')
267 def draw_gouraud_triangle(self, gc, points, colors, transform):
268 """
269 Draw a Gouraud-shaded triangle.
271 Parameters
272 ----------
273 points : array-like, shape=(3, 2)
274 Array of (x, y) points for the triangle.
276 colors : array-like, shape=(3, 4)
277 RGBA colors for each point of the triangle.
279 transform : `matplotlib.transforms.Transform`
280 An affine transform to apply to the points.
282 """
283 raise NotImplementedError
285 def draw_gouraud_triangles(self, gc, triangles_array, colors_array,
286 transform):
287 """
288 Draw a series of Gouraud triangles.
290 Parameters
291 ----------
292 points : array-like, shape=(N, 3, 2)
293 Array of *N* (x, y) points for the triangles.
295 colors : array-like, shape=(N, 3, 4)
296 Array of *N* RGBA colors for each point of the triangles.
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)
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.
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.
316 The arguments should be exactly what is passed in to
317 :meth:`draw_path_collection`.
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)
326 if Npaths == 0:
327 return
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
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
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.
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.
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.
374 Each yielded result is of the form::
376 xo, yo, path_id, gc, rgbFace
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)
393 if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0:
394 return
395 if Noffsets:
396 toffsets = offsetTrans.transform(offsets)
398 gc0 = self.new_gc()
399 gc0.copy_properties(gc)
401 if Nfacecolors == 0:
402 rgbFace = None
404 if Nedgecolors == 0:
405 gc0.set_linewidth(0.0)
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])
447 yield xo, yo, path_id, gc0, rgbFace
448 gc0.restore()
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
458 def draw_image(self, gc, x, y, im, transform=None):
459 """
460 Draw an RGBA image.
462 Parameters
463 ----------
464 gc : `GraphicsContextBase`
465 A graphics context with clipping information.
467 x : scalar
468 The distance in physical units (i.e., dots or pixels) from the left
469 hand side of the canvas.
471 y : scalar
472 The distance in physical units (i.e., dots or pixels) from the
473 bottom side of the canvas.
475 im : array-like, shape=(N, M, 4), dtype=np.uint8
476 An array of RGBA pixels.
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
491 def option_image_nocomposite(self):
492 """
493 Return whether image composition by Matplotlib should be skipped.
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
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
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")
513 def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
514 """
515 Draw the text instance.
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.
534 Notes
535 -----
536 **Note for backend implementers:**
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::
542 if 0: bbox_artist(self, renderer)
544 to if 1, and then the actual bounding box will be plotted along with
545 your text.
546 """
548 self._draw_text_as_path(gc, x, y, s, prop, angle, ismath)
550 def _get_text_path_transform(self, x, y, s, prop, angle, ismath):
551 """
552 Return the text path and transform.
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 """
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)
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))
582 return path, transform
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.
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)
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
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
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
637 def flipy(self):
638 """
639 Return whether y values increase from top to bottom.
641 Note that this only affects drawing of texts and images.
642 """
643 return True
645 def get_canvas_width_height(self):
646 """Return the canvas width and height in display coords."""
647 return 1, 1
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
656 def new_gc(self):
657 """Return an instance of a `GraphicsContextBase`."""
658 return GraphicsContextBase()
660 def points_to_pixels(self, points):
661 """
662 Convert points to display units.
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::
668 points to pixels = points * pixels_per_inch/72 * dpi/72
670 Parameters
671 ----------
672 points : float or array-like
673 a float or a numpy array of float
675 Returns
676 -------
677 Points converted to pixels
678 """
679 return points
681 @cbook.deprecated("3.1", alternative="cbook.strip_math")
682 def strip_math(self, s):
683 return cbook.strip_math(s)
685 def start_rasterizing(self):
686 """
687 Switch to the raster renderer.
689 Used by `MixedModeRenderer`.
690 """
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.
697 Used by `MixedModeRenderer`.
698 """
700 def start_filter(self):
701 """
702 Switch to a temporary renderer for image filtering effects.
704 Currently only supported by the agg renderer.
705 """
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.
713 Currently only supported by the agg renderer.
714 """
716 def _draw_disabled(self):
717 """
718 Context manager to temporary disable drawing.
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 }
731 return _setattr_cm(self, **no_ops)
734class GraphicsContextBase:
735 """An abstract base class that provides color, line styles, etc."""
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
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
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 """
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
791 def get_antialiased(self):
792 "Return whether the object should try to do antialiased rendering."
793 return self._antialiased
795 def get_capstyle(self):
796 """
797 Return the capstyle as a string in ('butt', 'round', 'projecting').
798 """
799 return self._capstyle
801 def get_clip_rectangle(self):
802 """
803 Return the clip rectangle as a `~matplotlib.transforms.Bbox` instance.
804 """
805 return self._cliprect
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
817 def get_dashes(self):
818 """
819 Return the dash style as an (offset, dash-list) pair.
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.
824 Default value is (None, None).
826 .. _blue book: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
827 """
828 return self._dashes
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
837 def get_joinstyle(self):
838 """Return the line join style as one of ('miter', 'round', 'bevel')."""
839 return self._joinstyle
841 def get_linewidth(self):
842 """Return the line width in points."""
843 return self._linewidth
845 def get_rgb(self):
846 """Return a tuple of three or four floats from 0-1."""
847 return self._rgb
849 def get_url(self):
850 """Return a url if one is set, None otherwise."""
851 return self._url
853 def get_gid(self):
854 """Return the object identifier if one is set, None otherwise."""
855 return self._gid
857 def get_snap(self):
858 """
859 Returns the snap setting, which can be:
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
868 def set_alpha(self, alpha):
869 """
870 Set the alpha value used for blending - not supported on all backends.
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)
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))
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
895 def set_clip_rectangle(self, rectangle):
896 """
897 Set the clip rectangle with sequence (left, bottom, width, height)
898 """
899 self._cliprect = rectangle
901 def set_clip_path(self, path):
902 """
903 Set the clip path and transformation.
905 Parameters
906 ----------
907 path : `~matplotlib.transforms.TransformedPath` or None
908 """
909 cbook._check_isinstance((transforms.TransformedPath, None), path=path)
910 self._clippath = path
912 def set_dashes(self, dash_offset, dash_list):
913 """
914 Set the dash style for the gc.
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.
923 Notes
924 -----
925 ``(None, None)`` specifies a solid line.
927 See p. 107 of to PostScript `blue book`_ for more info.
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
938 def set_foreground(self, fg, isRGBA=False):
939 """
940 Set the foreground color.
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)
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
963 def set_linewidth(self, w):
964 """Set the linewidth in points."""
965 self._linewidth = float(w)
967 def set_url(self, url):
968 """Set the url for links in compatible backends."""
969 self._url = url
971 def set_gid(self, id):
972 """Set the id."""
973 self._gid = id
975 def set_snap(self, snap):
976 """
977 Set the snap setting which may be:
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
986 def set_hatch(self, hatch):
987 """Set the hatch style (for fills)."""
988 self._hatch = hatch
990 def get_hatch(self):
991 """Get the current hatch style."""
992 return self._hatch
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)
1001 def get_hatch_color(self):
1002 """Get the hatch color."""
1003 return self._hatch_color
1005 def set_hatch_color(self, hatch_color):
1006 """Set the hatch color."""
1007 self._hatch_color = hatch_color
1009 def get_hatch_linewidth(self):
1010 """Get the hatch linewidth."""
1011 return self._hatch_linewidth
1013 def get_sketch_params(self):
1014 """
1015 Return the sketch parameters for the artist.
1017 Returns
1018 -------
1019 sketch_params : tuple or `None`
1021 A 3-tuple with the following elements:
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.
1029 May return `None` if no sketch parameters were set.
1030 """
1031 return self._sketch
1033 def set_sketch_params(self, scale=None, length=None, randomness=None):
1034 """
1035 Set the sketch parameters.
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.))
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.
1060 Subclasses must override the following methods:
1062 - ``_timer_start``: Backend-specific code for starting the timer.
1063 - ``_timer_stop``: Backend-specific code for stopping the timer.
1065 Subclasses may additionally override the following methods:
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.
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.
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.
1078 Attributes
1079 ----------
1080 interval : scalar
1081 The time between timer events in milliseconds. Default is 1000 ms.
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`.
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
1099 if interval is None:
1100 self._interval = 1000
1101 else:
1102 self._interval = interval
1104 self._single = False
1106 # Default attribute for holding the GUI-specific timer object
1107 self._timer = None
1109 def __del__(self):
1110 """Need to stop timer and possibly disconnect timer."""
1111 self._timer_stop()
1113 def start(self, interval=None):
1114 """
1115 Start the timer object.
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()
1127 def stop(self):
1128 """Stop the timer."""
1129 self._timer_stop()
1131 def _timer_start(self):
1132 pass
1134 def _timer_stop(self):
1135 pass
1137 @property
1138 def interval(self):
1139 return self._interval
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()
1149 @property
1150 def single_shot(self):
1151 return self._single
1153 @single_shot.setter
1154 def single_shot(self, ss):
1155 self._single = ss
1156 self._timer_set_single_shot()
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*.
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
1169 def remove_callback(self, func, *args, **kwargs):
1170 """
1171 Remove *func* from list of callbacks.
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))
1192 def _timer_set_interval(self):
1193 """Used to set interval on underlying timer object."""
1195 def _timer_set_single_shot(self):
1196 """Used to set single shot on underlying timer object."""
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))
1214 if len(self.callbacks) == 0:
1215 self.stop()
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
1224 Attributes
1225 ----------
1226 name : str
1227 the event name
1229 canvas : `FigureCanvasBase`
1230 the backend-specific canvas instance generating the event
1232 guiEvent
1233 the GUI event that triggered the matplotlib event
1235 """
1236 def __init__(self, name, canvas, guiEvent=None):
1237 self.name = name
1238 self.canvas = canvas
1239 self.guiEvent = guiEvent
1242class DrawEvent(Event):
1243 """
1244 An event triggered by a draw operation on the canvas
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``.
1251 .. warning::
1253 Calling ``canvas.draw`` and ``canvas.blit`` in these callbacks may
1254 not be safe with all backends and may cause infinite recursion.
1256 In addition to the :class:`Event` attributes, the following event
1257 attributes are defined:
1259 Attributes
1260 ----------
1261 renderer : `RendererBase`
1262 the renderer for the draw event
1264 """
1265 def __init__(self, name, canvas, renderer):
1266 Event.__init__(self, name, canvas)
1267 self.renderer = renderer
1270class ResizeEvent(Event):
1271 """
1272 An event triggered by a canvas resize
1274 In addition to the :class:`Event` attributes, the following event
1275 attributes are defined:
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()
1289class CloseEvent(Event):
1290 """An event triggered by a figure being closed."""
1293class LocationEvent(Event):
1294 """
1295 An event that has a screen location.
1297 The following additional attributes are defined and shown with
1298 their default values.
1300 In addition to the :class:`Event` attributes, the following
1301 event attributes are defined:
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 """
1317 lastevent = None # the last event that was triggered before this one
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
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
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
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
1352 self._update_enter_leave()
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)
1373 else:
1374 # process a figure enter event
1375 if self.inaxes is not None:
1376 self.canvas.callbacks.process('axes_enter_event', self)
1378 LocationEvent.lastevent = self
1381class MouseButton(IntEnum):
1382 LEFT = 1
1383 MIDDLE = 2
1384 RIGHT = 3
1385 BACK = 8
1386 FORWARD = 9
1389class MouseEvent(LocationEvent):
1390 """
1391 A mouse event ('button_press_event',
1392 'button_release_event',
1393 'scroll_event',
1394 'motion_notify_event').
1396 In addition to the :class:`Event` and :class:`LocationEvent`
1397 attributes, the following attributes are defined:
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.
1412 key : None or str
1413 The key pressed when the mouse event triggered, e.g. 'shift'.
1414 See `KeyEvent`.
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.
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'.
1425 Examples
1426 --------
1427 ::
1429 def on_press(event):
1430 print('you pressed', event.button, event.xdata, event.ydata)
1432 cid = fig.canvas.mpl_connect('button_press_event', on_press)
1433 """
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
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)
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}")
1459class PickEvent(Event):
1460 """
1461 a pick event, fired when the user picks a location on the canvas
1462 sufficiently close to an artist.
1464 Attrs: all the :class:`Event` attributes plus
1466 Attributes
1467 ----------
1468 mouseevent : `MouseEvent`
1469 the mouse event that generated the pick
1471 artist : `matplotlib.artist.Artist`
1472 the picked artist
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
1480 Examples
1481 --------
1482 ::
1483 ax.plot(np.rand(100), 'o', picker=5) # 5 points tolerance
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)
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)
1501class KeyEvent(LocationEvent):
1502 """
1503 A key event (key press, key release).
1505 Attach additional attributes as defined in
1506 :meth:`FigureCanvasBase.mpl_connect`.
1508 In addition to the :class:`Event` and :class:`LocationEvent`
1509 attributes, the following attributes are defined:
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").
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.
1526 Examples
1527 --------
1528 ::
1530 def on_key(event):
1531 print('you pressed', event.key, event.xdata, event.ydata)
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)
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.
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.
1549 """
1550 # This is implemented by triggering a draw, then immediately jumping out of
1551 # Figure.draw() by raising an exception.
1553 class Done(Exception):
1554 pass
1556 def _draw(renderer): raise Done(renderer)
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
1564 return renderer
1567def _is_non_interactive_terminal_ipython(ip):
1568 """
1569 Return whether we are in a a terminal IPython, but non interactive.
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)
1581class FigureCanvasBase:
1582 """
1583 The canvas the figure renders into.
1585 Public attributes
1587 Attributes
1588 ----------
1589 figure : `matplotlib.figure.Figure`
1590 A high-level figure instance
1591 """
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
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 ]
1615 fixed_dpi = None
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')
1630 @cbook._classproperty
1631 def supports_blit(cls):
1632 return (hasattr(cls, "copy_from_bbox")
1633 and hasattr(cls, "restore_region"))
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
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)
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
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
1694 def pick(self, mouseevent):
1695 if not self.widgetlock.locked():
1696 self.figure.pick(mouseevent)
1698 def blit(self, bbox=None):
1699 """Blit the canvas in bbox (default entire canvas)."""
1701 def resize(self, w, h):
1702 """Set the canvas size in pixels."""
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)
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()
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.
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)
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
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)
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.
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)
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`.
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)
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.
1804 This method will call all functions connected to the
1805 'button_release_event' with a :class:`MouseEvent` instance.
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
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.
1826 This method will call all functions connected to the
1827 'motion_notify_event' with a :class:`MouseEvent` instance.
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)
1844 def leave_notify_event(self, guiEvent=None):
1845 """
1846 Backend derived classes should call this function when leaving
1847 canvas
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
1858 def enter_notify_event(self, guiEvent=None, xy=None):
1859 """
1860 Backend derived classes should call this function when entering
1861 canvas
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.')
1880 event = LocationEvent('figure_enter_event', self, x, y, guiEvent)
1881 self.callbacks.process('figure_enter_event', event)
1883 def inaxes(self, xy):
1884 """
1885 Return the topmost visible `~.axes.Axes` containing the point *xy*.
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.
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
1906 return axes
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
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
1929 def draw(self, *args, **kwargs):
1930 """Render the :class:`~matplotlib.figure.Figure`."""
1932 def draw_idle(self, *args, **kwargs):
1933 """
1934 Request a widget redraw once control returns to the GUI event loop.
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.
1939 Notes
1940 -----
1941 Backends may choose to override the method and implement their own
1942 strategy to prevent multiple renderings.
1944 """
1945 if not self._is_idle_drawing:
1946 with self._idle_draw_cntx():
1947 self.draw(*args, **kwargs)
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 """
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)
1963 @classmethod
1964 def get_supported_filetypes(cls):
1965 """Return dict of savefig file formats supported by this backend."""
1966 return cls.filetypes
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
1982 def _get_output_canvas(self, fmt):
1983 """
1984 Return a canvas suitable for saving figures to a specified file format.
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()))))
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.
2010 Parameters
2011 ----------
2012 filename
2013 can also be a file object on image backends
2015 orientation : {'landscape', 'portrait'}, default: 'portrait'
2016 only currently applies to PostScript printing.
2018 dpi : scalar, optional
2019 the dots per inch to save the figure in; if None, use savefig.dpi
2021 facecolor : color, default: :rc:`savefig.facecolor`
2022 The facecolor of the figure.
2024 edgecolor : color, default: :rc:`savefig.edgecolor`
2025 The edgecolor of the figure.
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`.
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.
2038 pad_inches : float, default: :rc:`savefig.pad_inches`
2039 Amount of padding around the figure when *bbox_inches* is 'tight'.
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.
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()
2058 # get canvas object and print method for format
2059 canvas = self._get_output_canvas(format)
2060 print_method = getattr(canvas, 'print_%s' % format)
2062 if dpi is None:
2063 dpi = rcParams['savefig.dpi']
2064 if dpi == 'figure':
2065 dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)
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):
2075 if facecolor is None:
2076 facecolor = rcParams['savefig.facecolor']
2077 if edgecolor is None:
2078 edgecolor = rcParams['savefig.edgecolor']
2080 origfacecolor = self.figure.get_facecolor()
2081 origedgecolor = self.figure.get_edgecolor()
2083 self.figure.set_facecolor(facecolor)
2084 self.figure.set_edgecolor(edgecolor)
2086 if bbox_inches is None:
2087 bbox_inches = rcParams['savefig.bbox']
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']
2108 bbox_inches = bbox_inches.padded(pad)
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)
2114 _bbox_inches_restore = (bbox_inches, restore_bbox)
2115 else:
2116 _bbox_inches_restore = None
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()
2131 self.figure.set_facecolor(origfacecolor)
2132 self.figure.set_edgecolor(origedgecolor)
2133 self.figure.set_canvas(self)
2134 return result
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']
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()
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)
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
2172 def switch_backends(self, FigureCanvasClass):
2173 """
2174 Instantiate an instance of FigureCanvasClass
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
2185 def mpl_connect(self, s, func):
2186 """
2187 Bind function *func* to event *s*.
2189 Parameters
2190 ----------
2191 s : str
2192 One of the following events ids:
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'.
2209 func : callable
2210 The callback function to be executed, which must have the
2211 signature::
2213 def func(event: Event) -> Any
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.
2222 Returns
2223 -------
2224 cid
2225 A connection id that can be used with
2226 `.FigureCanvasBase.mpl_disconnect`.
2228 Examples
2229 --------
2230 ::
2232 def on_press(event):
2233 print('you pressed', event.button, event.xdata, event.ydata)
2235 cid = canvas.mpl_connect('button_press_event', on_press)
2236 """
2238 return self.callbacks.connect(s, func)
2240 def mpl_disconnect(self, cid):
2241 """
2242 Disconnect the callback with id *cid*.
2244 Examples
2245 --------
2246 ::
2248 cid = canvas.mpl_connect('button_press_event', on_press)
2249 # ... later
2250 canvas.mpl_disconnect(cid)
2251 """
2252 return self.callbacks.disconnect(cid)
2254 def new_timer(self, *args, **kwargs):
2255 """
2256 Create a new backend-specific subclass of `.Timer`.
2258 This is useful for getting periodic events through the backend's native
2259 event loop. Implemented only for backends with GUIs.
2261 Other Parameters
2262 ----------------
2263 interval : scalar
2264 Timer interval in milliseconds
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*.
2270 Callbacks which return ``False`` or ``0`` will be removed from the
2271 timer.
2273 Examples
2274 --------
2275 >>> timer = fig.canvas.new_timer(callbacks=[(f1, (1, ), {'a': 3}),])
2276 """
2277 return TimerBase(*args, **kwargs)
2279 def flush_events(self):
2280 """
2281 Flush the GUI events for the figure.
2283 Interactive backends need to reimplement this method.
2284 """
2286 def start_event_loop(self, timeout=0):
2287 """
2288 Start a blocking event loop.
2290 Such an event loop is used by interactive functions, such as `ginput`
2291 and `waitforbuttonpress`, to wait for events.
2293 The event loop blocks until a callback function triggers
2294 `stop_event_loop`, or *timeout* is reached.
2296 If *timeout* is 0 or negative, never timeout.
2298 Only interactive backends need to reimplement this method and it relies
2299 on `flush_events` being properly implemented.
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
2313 def stop_event_loop(self):
2314 """
2315 Stop the current blocking event loop.
2317 Interactive backends need to reimplement this to match
2318 `start_event_loop`
2319 """
2320 self._looping = False
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`.
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
2339 if event.key is None:
2340 return
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']
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
2364 # quit the figure (default key 'ctrl+w')
2365 if event.key in quit_keys:
2366 Gcf.destroy_fig(canvas.figure)
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()
2391 if event.inaxes is None:
2392 return
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
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)
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()
2501class NonGuiException(Exception):
2502 """Raised when trying show a figure in a non-GUI backend."""
2503 pass
2506class FigureManagerBase:
2507 """
2508 A backend-independent abstraction of a figure container and controller.
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.
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.
2518 This following basic operations are accessible:
2520 **Window operations**
2522 - `~.FigureManagerBase.show`
2523 - `~.FigureManagerBase.destroy`
2524 - `~.FigureManagerBase.full_screen_toggle`
2525 - `~.FigureManagerBase.resize`
2526 - `~.FigureManagerBase.get_window_title`
2527 - `~.FigureManagerBase.set_window_title`
2529 **Key and mouse button press handling**
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.
2535 **Other operations**
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.
2542 Attributes
2543 ----------
2544 canvas : :class:`FigureCanvasBase`
2545 The backend-specific canvas instance.
2547 num : int or str
2548 The figure number.
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::
2554 figure.canvas.mpl_disconnect(
2555 figure.canvas.manager.key_press_handler_id)
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::
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
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)
2579 self.toolmanager = None
2580 self.toolbar = None
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()
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()
2597 def destroy(self):
2598 pass
2600 def full_screen_toggle(self):
2601 pass
2603 def resize(self, w, h):
2604 """For GUI backends, resize the window (in pixels)."""
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)
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)
2619 def get_window_title(self):
2620 """
2621 Get the title text of the window containing the figure.
2623 Return None for non-GUI (e.g., PS) backends.
2624 """
2625 return 'image'
2627 def set_window_title(self, title):
2628 """
2629 Set the title text of the window containing the figure.
2631 This has no effect for non-GUI (e.g., PS) backends.
2632 """
2635cursors = tools.cursors
2638class NavigationToolbar2:
2639 """
2640 Base class for the navigation cursor, version 2
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
2647 They must also define
2649 :meth:`save_figure`
2650 save the current figure
2652 :meth:`set_cursor`
2653 if you want the pointer icon to change
2655 :meth:`_init_toolbar`
2656 create your toolbar widget
2658 :meth:`draw_rubberband` (optional)
2659 draw the zoom to rect "rubberband" rectangle
2661 :meth:`press` (optional)
2662 whenever a mouse button is pressed, you'll be notified with
2663 the event
2665 :meth:`release` (optional)
2666 whenever a mouse button is released, you'll be notified with
2667 the event
2669 :meth:`set_message` (optional)
2670 display message
2672 :meth:`set_history_buttons` (optional)
2673 you can change the history back / forward buttons to
2674 indicate disabled / enabled state.
2676 That's it, we'll do the rest!
2677 """
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 )
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)
2712 self._ids_zoom = []
2713 self._zoom_mode = None
2715 self._button_pressed = None # determined by button pressed at start
2717 self.mode = '' # a mode string for the status bar
2718 self.set_history_buttons()
2720 def set_message(self, s):
2721 """Display a message on toolbar or in status bar."""
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()
2729 def draw_rubberband(self, event, x0, y0, x1, y1):
2730 """
2731 Draw a rectangle rubberband to indicate zoom limits.
2733 Note that it is not guaranteed that ``x0 <= x1`` and ``y0 <= y1``.
2734 """
2736 def remove_rubberband(self):
2737 """Remove the rubberband."""
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()
2745 def home(self, *args):
2746 """Restore the original view."""
2747 self._nav_stack.home()
2748 self.set_history_buttons()
2749 self._update_view()
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).
2758 You just need to set the callbacks
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
2767 You only need to define the last one - the others are in the base
2768 class implementation.
2770 """
2771 raise NotImplementedError
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
2791 @contextmanager
2792 def _wait_cursor_for_draw_cm(self):
2793 """
2794 Set the cursor to a wait cursor when drawing the canvas.
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
2813 def mouse_move(self, event):
2814 self._update_cursor(event)
2816 if event.inaxes and event.inaxes.get_navigate():
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()]
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
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)
2842 def pan(self, *args):
2843 """
2844 Activate the pan/zoom tool.
2846 Pan with left button, zoom with right.
2847 """
2848 # set the pointer icon and button press funcs to the
2849 # appropriate callbacks
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 = ''
2859 if self._idRelease is not None:
2860 self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
2861 self.mode = ''
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)
2873 for a in self.canvas.figure.get_axes():
2874 a.set_navigate_mode(self._active)
2876 self.set_message(self.mode)
2878 def press(self, event):
2879 """Called whenever a mouse button is pressed."""
2881 def press_pan(self, event):
2882 """Callback for mouse button press in pan/zoom mode."""
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
2892 if self._nav_stack() is None:
2893 # set the home button to this view
2894 self.push_current()
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)
2907 self.press(event)
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
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
2931 if self._nav_stack() is None:
2932 # set the home button to this view
2933 self.push_current()
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()))
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)
2948 self._ids_zoom = id1, id2, id3
2949 self._zoom_mode = event.key
2951 self.press(event)
2953 def _switch_on_zoom_mode(self, event):
2954 self._zoom_mode = event.key
2955 self.mouse_move(event)
2957 def _switch_off_zoom_mode(self, event):
2958 self._zoom_mode = None
2959 self.mouse_move(event)
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()
2972 def release(self, event):
2973 """Callback for mouse button release."""
2975 def release_pan(self, event):
2976 """Callback for mouse button release in pan/zoom mode."""
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()
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()
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)
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 = []
3020 self.remove_rubberband()
3022 if not self._xypress:
3023 return
3025 last_a = []
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
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)
3050 if self._button_pressed == 1:
3051 direction = 'in'
3052 elif self._button_pressed == 3:
3053 direction = 'out'
3054 else:
3055 continue
3057 a._set_view_from_bbox((lastx, lasty, x, y), direction,
3058 self._zoom_mode, twinx, twiny)
3060 self.draw()
3061 self._xypress = None
3062 self._button_pressed = None
3064 self._zoom_mode = None
3066 self.push_current()
3067 self.release(event)
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())
3082 for loc in locators:
3083 loc.refresh()
3084 self.canvas.draw_idle()
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()
3104 def save_figure(self, *args):
3105 """Save the current figure."""
3106 raise NotImplementedError
3108 def set_cursor(self, cursor):
3109 """
3110 Set the current cursor to one of the :class:`Cursors` enums values.
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 """
3118 def update(self):
3119 """Reset the axes stack."""
3120 self._nav_stack.clear()
3121 self.set_history_buttons()
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'
3130 if self._idPress is not None:
3131 self._idPress = self.canvas.mpl_disconnect(self._idPress)
3132 self.mode = ''
3134 if self._idRelease is not None:
3135 self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
3136 self.mode = ''
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)
3148 for a in self.canvas.figure.get_axes():
3149 a.set_navigate_mode(self._active)
3151 self.set_message(self.mode)
3153 def set_history_buttons(self):
3154 """Enable or disable the back/forward button."""
3157class ToolContainerBase:
3158 """
3159 Base class for all tool containers, e.g. toolbars.
3161 Attributes
3162 ----------
3163 toolmanager : `ToolManager`
3164 The tools with which this `ToolContainer` wants to communicate.
3165 """
3167 _icon_extension = '.png'
3168 """
3169 Toolcontainer button icon image format extension
3171 **String**: Image extension
3172 """
3174 def __init__(self, toolmanager):
3175 self.toolmanager = toolmanager
3176 self.toolmanager.toolmanager_connect('tool_removed_event',
3177 self._remove_tool_cbk)
3179 def _tool_toggled_cbk(self, event):
3180 """
3181 Captures the 'tool_trigger_[name]'
3183 This only gets used for toggled tools
3184 """
3185 self.toggle_toolitem(event.tool.name, event.tool.toggled)
3187 def add_tool(self, tool, group, position=-1):
3188 """
3189 Adds a tool to this container
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)
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)
3216 def _get_image_filename(self, image):
3217 """Find the image based on its name."""
3218 if not image:
3219 return None
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
3231 def trigger_tool(self, name):
3232 """
3233 Trigger the tool
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)
3242 def add_toolitem(self, name, group, position, image, description, toggle):
3243 """
3244 Add a toolitem to the container
3246 This method must get implemented per backend
3248 The callback associated with the button click event,
3249 must be **EXACTLY** `self.trigger_tool(name)`
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
3272 def toggle_toolitem(self, name, toggled):
3273 """
3274 Toggle the toolitem without firing event
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
3285 def remove_toolitem(self, name):
3286 """
3287 Remove a toolitem from the `ToolContainer`.
3289 This method must get implemented per backend.
3291 Called when `ToolManager` emits a `tool_removed_event`.
3293 Parameters
3294 ----------
3295 name : str
3296 Name of the tool to remove.
3297 """
3298 raise NotImplementedError
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)
3308 def _message_cbk(self, event):
3309 """Capture the 'tool_message_event' and set the message."""
3310 self.set_message(event.message)
3312 def set_message(self, s):
3313 """
3314 Display a message on toolbar or in status bar.
3316 Parameters
3317 ----------
3318 s : str
3319 Message text.
3320 """
3321 pass
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.
3331 # `backend_version` may be overridden by the subclass.
3332 backend_version = "unknown"
3334 # The `FigureCanvas` class must be defined.
3335 FigureCanvas = None
3337 # For interactive backends, the `FigureManager` class must be overridden.
3338 FigureManager = FigureManagerBase
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
3348 # The following methods will be automatically defined and exported, but
3349 # can be overridden.
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)
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
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)
3374 @classmethod
3375 @cbook._make_keyword_only("3.1", "block")
3376 def show(cls, block=None):
3377 """
3378 Show all figures.
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()
3409 # This method is the one actually exporting the required methods.
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))
3424 # For back-compatibility, generate a shim `Show` class.
3426 class Show(ShowBase):
3427 def mainloop(self):
3428 return cls.mainloop()
3430 setattr(sys.modules[cls.__module__], "Show", Show)
3431 return cls
3434class ShowBase(_Backend):
3435 """
3436 Simple base class to generate a ``show()`` function in backends.
3438 Subclass must override ``mainloop()`` method.
3439 """
3441 def __call__(self, block=None):
3442 return self.show(block=block)