1"""
2Abstract base classes define the primitives that renderers and
3graphics contexts must implement to serve as a matplotlib backend
4
5:class:`RendererBase`
6    An abstract base class to handle drawing/rendering operations.
7
8:class:`FigureCanvasBase`
9    The abstraction layer that separates the
10    :class:`matplotlib.figure.Figure` from the backend specific
11    details like a user interface drawing area
12
13:class:`GraphicsContextBase`
14    An abstract base class that provides color, line styles, etc...
15
16:class:`Event`
17    The base class for all of the matplotlib event
18    handling.  Derived classes such as :class:`KeyEvent` and
19    :class:`MouseEvent` store the meta data like keys and buttons
20    pressed, x and y locations in pixel and
21    :class:`~matplotlib.axes.Axes` coordinates.
22
23:class:`ShowBase`
24    The base class for the Show class of each interactive backend;
25    the 'show' callable is then set to Show.__call__, inherited from
26    ShowBase.
27
28:class:`ToolContainerBase`
29     The base class for the Toolbar class of each interactive backend.
30
31:class:`StatusbarBase`
32    The base class for the messaging area.
33"""
34
35from __future__ import (absolute_import, division, print_function,
36                        unicode_literals)
37
38import six
39from six.moves import xrange
40
41from contextlib import contextmanager
42from functools import partial
43import importlib
44import io
45import os
46import sys
47import time
48import warnings
49from weakref import WeakKeyDictionary
50
51import numpy as np
52
53from matplotlib import (
54    backend_tools as tools, cbook, colors, textpath, tight_bbox, transforms,
55    widgets, get_backend, is_interactive, rcParams)
56from matplotlib._pylab_helpers import Gcf
57from matplotlib.transforms import Bbox, TransformedBbox, Affine2D
58from matplotlib.path import Path
59
60try:
61    from PIL import Image
62    _has_pil = True
63    del Image
64except ImportError:
65    _has_pil = False
66
67
68_default_filetypes = {
69    'ps': 'Postscript',
70    'eps': 'Encapsulated Postscript',
71    'pdf': 'Portable Document Format',
72    'pgf': 'PGF code for LaTeX',
73    'png': 'Portable Network Graphics',
74    'raw': 'Raw RGBA bitmap',
75    'rgba': 'Raw RGBA bitmap',
76    'svg': 'Scalable Vector Graphics',
77    'svgz': 'Scalable Vector Graphics'
78}
79
80
81_default_backends = {
82    'ps': 'matplotlib.backends.backend_ps',
83    'eps': 'matplotlib.backends.backend_ps',
84    'pdf': 'matplotlib.backends.backend_pdf',
85    'pgf': 'matplotlib.backends.backend_pgf',
86    'png': 'matplotlib.backends.backend_agg',
87    'raw': 'matplotlib.backends.backend_agg',
88    'rgba': 'matplotlib.backends.backend_agg',
89    'svg': 'matplotlib.backends.backend_svg',
90    'svgz': 'matplotlib.backends.backend_svg',
91}
92
93
94def register_backend(format, backend, description=None):
95    """
96    Register a backend for saving to a given file format.
97
98    Parameters
99    ----------
100    format : str
101        File extension
102
103    backend : module string or canvas class
104        Backend for handling file output
105
106    description : str, optional
107        Description of the file type.  Defaults to an empty string
108    """
109    if description is None:
110        description = ''
111    _default_backends[format] = backend
112    _default_filetypes[format] = description
113
114
115def get_registered_canvas_class(format):
116    """
117    Return the registered default canvas for given file format.
118    Handles deferred import of required backend.
119    """
120    if format not in _default_backends:
121        return None
122    backend_class = _default_backends[format]
123    if isinstance(backend_class, six.string_types):
124        backend_class = importlib.import_module(backend_class).FigureCanvas
125        _default_backends[format] = backend_class
126    return backend_class
127
128
129class _Backend(object):
130    # A backend can be defined by using the following pattern:
131    #
132    # @_Backend.export
133    # class FooBackend(_Backend):
134    #     # override the attributes and methods documented below.
135
136    # The following attributes and methods must be overridden by subclasses.
137
138    # The `FigureCanvas` and `FigureManager` classes must be defined.
139    FigureCanvas = None
140    FigureManager = None
141
142    # The following methods must be left as None for non-interactive backends.
143    # For interactive backends, `trigger_manager_draw` should be a function
144    # taking a manager as argument and triggering a canvas draw, and `mainloop`
145    # should be a function taking no argument and starting the backend main
146    # loop.
147    trigger_manager_draw = None
148    mainloop = None
149
150    # The following methods will be automatically defined and exported, but
151    # can be overridden.
152
153    @classmethod
154    def new_figure_manager(cls, num, *args, **kwargs):
155        """Create a new figure manager instance.
156        """
157        # This import needs to happen here due to circular imports.
158        from matplotlib.figure import Figure
159        fig_cls = kwargs.pop('FigureClass', Figure)
160        fig = fig_cls(*args, **kwargs)
161        return cls.new_figure_manager_given_figure(num, fig)
162
163    @classmethod
164    def new_figure_manager_given_figure(cls, num, figure):
165        """Create a new figure manager instance for the given figure.
166        """
167        canvas = cls.FigureCanvas(figure)
168        manager = cls.FigureManager(canvas, num)
169        return manager
170
171    @classmethod
172    def draw_if_interactive(cls):
173        if cls.trigger_manager_draw is not None and is_interactive():
174            manager = Gcf.get_active()
175            if manager:
176                cls.trigger_manager_draw(manager)
177
178    @classmethod
179    def show(cls, block=None):
180        """Show all figures.
181
182        `show` blocks by calling `mainloop` if *block* is ``True``, or if it
183        is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
184        `interactive` mode.
185        """
186        if cls.mainloop is None:
187            return
188        managers = Gcf.get_all_fig_managers()
189        if not managers:
190            return
191        for manager in managers:
192            manager.show()
193        if block is None:
194            # Hack: Are we in IPython's pylab mode?
195            from matplotlib import pyplot
196            try:
197                # IPython versions >= 0.10 tack the _needmain attribute onto
198                # pyplot.show, and always set it to False, when in %pylab mode.
199                ipython_pylab = not pyplot.show._needmain
200            except AttributeError:
201                ipython_pylab = False
202            block = not ipython_pylab and not is_interactive()
203            # TODO: The above is a hack to get the WebAgg backend working with
204            # ipython's `%pylab` mode until proper integration is implemented.
205            if get_backend() == "WebAgg":
206                block = True
207        if block:
208            cls.mainloop()
209
210    # This method is the one actually exporting the required methods.
211
212    @staticmethod
213    def export(cls):
214        for name in ["FigureCanvas",
215                     "FigureManager",
216                     "new_figure_manager",
217                     "new_figure_manager_given_figure",
218                     "draw_if_interactive",
219                     "show"]:
220            setattr(sys.modules[cls.__module__], name, getattr(cls, name))
221
222        # For back-compatibility, generate a shim `Show` class.
223
224        class Show(ShowBase):
225            def mainloop(self):
226                return cls.mainloop()
227
228        setattr(sys.modules[cls.__module__], "Show", Show)
229        return cls
230
231
232class ShowBase(_Backend):
233    """
234    Simple base class to generate a show() callable in backends.
235
236    Subclass must override mainloop() method.
237    """
238
239    def __call__(self, block=None):
240        return self.show(block=block)
241
242
243class RendererBase(object):
244    """An abstract base class to handle drawing/rendering operations.
245
246    The following methods must be implemented in the backend for full
247    functionality (though just implementing :meth:`draw_path` alone would
248    give a highly capable backend):
249
250    * :meth:`draw_path`
251    * :meth:`draw_image`
252    * :meth:`draw_gouraud_triangle`
253
254    The following methods *should* be implemented in the backend for
255    optimization reasons:
256
257    * :meth:`draw_text`
258    * :meth:`draw_markers`
259    * :meth:`draw_path_collection`
260    * :meth:`draw_quad_mesh`
261
262    """
263    def __init__(self):
264        self._texmanager = None
265        self._text2path = textpath.TextToPath()
266
267    def open_group(self, s, gid=None):
268        """
269        Open a grouping element with label *s*. If *gid* is given, use
270        *gid* as the id of the group. Is only currently used by
271        :mod:`~matplotlib.backends.backend_svg`.
272        """
273
274    def close_group(self, s):
275        """
276        Close a grouping element with label *s*
277        Is only currently used by :mod:`~matplotlib.backends.backend_svg`
278        """
279
280    def draw_path(self, gc, path, transform, rgbFace=None):
281        """
282        Draws a :class:`~matplotlib.path.Path` instance using the
283        given affine transform.
284        """
285        raise NotImplementedError
286
287    def draw_markers(self, gc, marker_path, marker_trans, path,
288                     trans, rgbFace=None):
289        """
290        Draws a marker at each of the vertices in path.  This includes
291        all vertices, including control points on curves.  To avoid
292        that behavior, those vertices should be removed before calling
293        this function.
294
295        This provides a fallback implementation of draw_markers that
296        makes multiple calls to :meth:`draw_path`.  Some backends may
297        want to override this method in order to draw the marker only
298        once and reuse it multiple times.
299
300        Parameters
301        ----------
302        gc : `GraphicsContextBase`
303            The graphics context
304
305        marker_trans : `matplotlib.transforms.Transform`
306            An affine transform applied to the marker.
307
308        trans : `matplotlib.transforms.Transform`
309            An affine transform applied to the path.
310
311        """
312        for vertices, codes in path.iter_segments(trans, simplify=False):
313            if len(vertices):
314                x, y = vertices[-2:]
315                self.draw_path(gc, marker_path,
316                               marker_trans +
317                               transforms.Affine2D().translate(x, y),
318                               rgbFace)
319
320    def draw_path_collection(self, gc, master_transform, paths, all_transforms,
321                             offsets, offsetTrans, facecolors, edgecolors,
322                             linewidths, linestyles, antialiaseds, urls,
323                             offset_position):
324        """
325        Draws a collection of paths selecting drawing properties from
326        the lists *facecolors*, *edgecolors*, *linewidths*,
327        *linestyles* and *antialiaseds*. *offsets* is a list of
328        offsets to apply to each of the paths.  The offsets in
329        *offsets* are first transformed by *offsetTrans* before being
330        applied.  *offset_position* may be either "screen" or "data"
331        depending on the space that the offsets are in.
332
333        This provides a fallback implementation of
334        :meth:`draw_path_collection` that makes multiple calls to
335        :meth:`draw_path`.  Some backends may want to override this in
336        order to render each set of path data only once, and then
337        reference that path multiple times with the different offsets,
338        colors, styles etc.  The generator methods
339        :meth:`_iter_collection_raw_paths` and
340        :meth:`_iter_collection` are provided to help with (and
341        standardize) the implementation across backends.  It is highly
342        recommended to use those generators, so that changes to the
343        behavior of :meth:`draw_path_collection` can be made globally.
344        """
345        path_ids = []
346        for path, transform in self._iter_collection_raw_paths(
347                master_transform, paths, all_transforms):
348            path_ids.append((path, transforms.Affine2D(transform)))
349
350        for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
351                gc, master_transform, all_transforms, path_ids, offsets,
352                offsetTrans, facecolors, edgecolors, linewidths, linestyles,
353                antialiaseds, urls, offset_position):
354            path, transform = path_id
355            transform = transforms.Affine2D(
356                            transform.get_matrix()).translate(xo, yo)
357            self.draw_path(gc0, path, transform, rgbFace)
358
359    def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
360                       coordinates, offsets, offsetTrans, facecolors,
361                       antialiased, edgecolors):
362        """
363        This provides a fallback implementation of
364        :meth:`draw_quad_mesh` that generates paths and then calls
365        :meth:`draw_path_collection`.
366        """
367
368        from matplotlib.collections import QuadMesh
369        paths = QuadMesh.convert_mesh_to_paths(
370            meshWidth, meshHeight, coordinates)
371
372        if edgecolors is None:
373            edgecolors = facecolors
374        linewidths = np.array([gc.get_linewidth()], float)
375
376        return self.draw_path_collection(
377            gc, master_transform, paths, [], offsets, offsetTrans, facecolors,
378            edgecolors, linewidths, [], [antialiased], [None], 'screen')
379
380    def draw_gouraud_triangle(self, gc, points, colors, transform):
381        """
382        Draw a Gouraud-shaded triangle.
383
384        Parameters
385        ----------
386        points : array_like, shape=(3, 2)
387            Array of (x, y) points for the triangle.
388
389        colors : array_like, shape=(3, 4)
390            RGBA colors for each point of the triangle.
391
392        transform : `matplotlib.transforms.Transform`
393            An affine transform to apply to the points.
394
395        """
396        raise NotImplementedError
397
398    def draw_gouraud_triangles(self, gc, triangles_array, colors_array,
399                               transform):
400        """
401        Draws a series of Gouraud triangles.
402
403        Parameters
404        ----------
405        points : array_like, shape=(N, 3, 2)
406            Array of *N* (x, y) points for the triangles.
407
408        colors : array_like, shape=(N, 3, 4)
409            Array of *N* RGBA colors for each point of the triangles.
410
411        transform : `matplotlib.transforms.Transform`
412            An affine transform to apply to the points.
413        """
414        transform = transform.frozen()
415        for tri, col in zip(triangles_array, colors_array):
416            self.draw_gouraud_triangle(gc, tri, col, transform)
417
418    def _iter_collection_raw_paths(self, master_transform, paths,
419                                   all_transforms):
420        """
421        This is a helper method (along with :meth:`_iter_collection`) to make
422        it easier to write a space-efficient :meth:`draw_path_collection`
423        implementation in a backend.
424
425        This method yields all of the base path/transform
426        combinations, given a master transform, a list of paths and
427        list of transforms.
428
429        The arguments should be exactly what is passed in to
430        :meth:`draw_path_collection`.
431
432        The backend should take each yielded path and transform and
433        create an object that can be referenced (reused) later.
434        """
435        Npaths = len(paths)
436        Ntransforms = len(all_transforms)
437        N = max(Npaths, Ntransforms)
438
439        if Npaths == 0:
440            return
441
442        transform = transforms.IdentityTransform()
443        for i in xrange(N):
444            path = paths[i % Npaths]
445            if Ntransforms:
446                transform = Affine2D(all_transforms[i % Ntransforms])
447            yield path, transform + master_transform
448
449    def _iter_collection_uses_per_path(self, paths, all_transforms,
450                                       offsets, facecolors, edgecolors):
451        """
452        Compute how many times each raw path object returned by
453        _iter_collection_raw_paths would be used when calling
454        _iter_collection. This is intended for the backend to decide
455        on the tradeoff between using the paths in-line and storing
456        them once and reusing. Rounds up in case the number of uses
457        is not the same for every path.
458        """
459        Npaths = len(paths)
460        if Npaths == 0 or (len(facecolors) == 0 and len(edgecolors) == 0):
461            return 0
462        Npath_ids = max(Npaths, len(all_transforms))
463        N = max(Npath_ids, len(offsets))
464        return (N + Npath_ids - 1) // Npath_ids
465
466    def _iter_collection(self, gc, master_transform, all_transforms,
467                         path_ids, offsets, offsetTrans, facecolors,
468                         edgecolors, linewidths, linestyles,
469                         antialiaseds, urls, offset_position):
470        """
471        This is a helper method (along with
472        :meth:`_iter_collection_raw_paths`) to make it easier to write
473        a space-efficient :meth:`draw_path_collection` implementation in a
474        backend.
475
476        This method yields all of the path, offset and graphics
477        context combinations to draw the path collection.  The caller
478        should already have looped over the results of
479        :meth:`_iter_collection_raw_paths` to draw this collection.
480
481        The arguments should be the same as that passed into
482        :meth:`draw_path_collection`, with the exception of
483        *path_ids*, which is a list of arbitrary objects that the
484        backend will use to reference one of the paths created in the
485        :meth:`_iter_collection_raw_paths` stage.
486
487        Each yielded result is of the form::
488
489           xo, yo, path_id, gc, rgbFace
490
491        where *xo*, *yo* is an offset; *path_id* is one of the elements of
492        *path_ids*; *gc* is a graphics context and *rgbFace* is a color to
493        use for filling the path.
494        """
495        Ntransforms = len(all_transforms)
496        Npaths = len(path_ids)
497        Noffsets = len(offsets)
498        N = max(Npaths, Noffsets)
499        Nfacecolors = len(facecolors)
500        Nedgecolors = len(edgecolors)
501        Nlinewidths = len(linewidths)
502        Nlinestyles = len(linestyles)
503        Naa = len(antialiaseds)
504        Nurls = len(urls)
505
506        if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0:
507            return
508        if Noffsets:
509            toffsets = offsetTrans.transform(offsets)
510
511        gc0 = self.new_gc()
512        gc0.copy_properties(gc)
513
514        if Nfacecolors == 0:
515            rgbFace = None
516
517        if Nedgecolors == 0:
518            gc0.set_linewidth(0.0)
519
520        xo, yo = 0, 0
521        for i in xrange(N):
522            path_id = path_ids[i % Npaths]
523            if Noffsets:
524                xo, yo = toffsets[i % Noffsets]
525                if offset_position == 'data':
526                    if Ntransforms:
527                        transform = (
528                            Affine2D(all_transforms[i % Ntransforms]) +
529                            master_transform)
530                    else:
531                        transform = master_transform
532                    xo, yo = transform.transform_point((xo, yo))
533                    xp, yp = transform.transform_point((0, 0))
534                    xo = -(xp - xo)
535                    yo = -(yp - yo)
536            if not (np.isfinite(xo) and np.isfinite(yo)):
537                continue
538            if Nfacecolors:
539                rgbFace = facecolors[i % Nfacecolors]
540            if Nedgecolors:
541                if Nlinewidths:
542                    gc0.set_linewidth(linewidths[i % Nlinewidths])
543                if Nlinestyles:
544                    gc0.set_dashes(*linestyles[i % Nlinestyles])
545                fg = edgecolors[i % Nedgecolors]
546                if len(fg) == 4:
547                    if fg[3] == 0.0:
548                        gc0.set_linewidth(0)
549                    else:
550                        gc0.set_foreground(fg)
551                else:
552                    gc0.set_foreground(fg)
553            if rgbFace is not None and len(rgbFace) == 4:
554                if rgbFace[3] == 0:
555                    rgbFace = None
556            gc0.set_antialiased(antialiaseds[i % Naa])
557            if Nurls:
558                gc0.set_url(urls[i % Nurls])
559
560            yield xo, yo, path_id, gc0, rgbFace
561        gc0.restore()
562
563    def get_image_magnification(self):
564        """
565        Get the factor by which to magnify images passed to :meth:`draw_image`.
566        Allows a backend to have images at a different resolution to other
567        artists.
568        """
569        return 1.0
570
571    def draw_image(self, gc, x, y, im, transform=None):
572        """
573        Draw an RGBA image.
574
575        Parameters
576        ----------
577        gc : `GraphicsContextBase`
578            a graphics context with clipping information.
579
580        x : scalar
581            the distance in physical units (i.e., dots or pixels) from the left
582            hand side of the canvas.
583
584        y : scalar
585            the distance in physical units (i.e., dots or pixels) from the
586            bottom side of the canvas.
587
588        im : array_like, shape=(N, M, 4), dtype=np.uint8
589            An array of RGBA pixels.
590
591        transform : `matplotlib.transforms.Affine2DBase`
592            If and only if the concrete backend is written such that
593            :meth:`option_scale_image` returns ``True``, an affine
594            transformation *may* be passed to :meth:`draw_image`. It takes the
595            form of a :class:`~matplotlib.transforms.Affine2DBase` instance.
596            The translation vector of the transformation is given in physical
597            units (i.e., dots or pixels). Note that the transformation does not
598            override `x` and `y`, and has to be applied *before* translating
599            the result by `x` and `y` (this can be accomplished by adding `x`
600            and `y` to the translation vector defined by `transform`).
601        """
602        raise NotImplementedError
603
604    def option_image_nocomposite(self):
605        """
606        override this method for renderers that do not necessarily always
607        want to rescale and composite raster images. (like SVG, PDF, or PS)
608        """
609        return False
610
611    def option_scale_image(self):
612        """
613        override this method for renderers that support arbitrary affine
614        transformations in :meth:`draw_image` (most vector backends).
615        """
616        return False
617
618    def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
619        """
620        """
621        self._draw_text_as_path(gc, x, y, s, prop, angle, ismath="TeX")
622
623    def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
624        """
625        Draw the text instance
626
627        Parameters
628        ----------
629        gc : `GraphicsContextBase`
630            the graphics context
631
632        x : scalar
633            the x location of the text in display coords
634
635        y : scalar
636            the y location of the text baseline in display coords
637
638        s : str
639            the text string
640
641        prop : `matplotlib.font_manager.FontProperties`
642            font properties
643
644        angle : scalar
645            the rotation angle in degrees
646
647        mtext : `matplotlib.text.Text`
648            the original text object to be rendered
649
650        Notes
651        -----
652        **backend implementers note**
653
654        When you are trying to determine if you have gotten your bounding box
655        right (which is what enables the text layout/alignment to work
656        properly), it helps to change the line in text.py::
657
658            if 0: bbox_artist(self, renderer)
659
660        to if 1, and then the actual bounding box will be plotted along with
661        your text.
662        """
663
664        self._draw_text_as_path(gc, x, y, s, prop, angle, ismath)
665
666    def _get_text_path_transform(self, x, y, s, prop, angle, ismath):
667        """
668        return the text path and transform
669
670        Parameters
671        ----------
672        prop : `matplotlib.font_manager.FontProperties`
673          font property
674
675        s : str
676          text to be converted
677
678        usetex : bool
679          If True, use matplotlib usetex mode.
680
681        ismath : bool
682          If True, use mathtext parser. If "TeX", use *usetex* mode.
683        """
684
685        text2path = self._text2path
686        fontsize = self.points_to_pixels(prop.get_size_in_points())
687
688        if ismath == "TeX":
689            verts, codes = text2path.get_text_path(prop, s, ismath=False,
690                                                   usetex=True)
691        else:
692            verts, codes = text2path.get_text_path(prop, s, ismath=ismath,
693                                                   usetex=False)
694
695        path = Path(verts, codes)
696        angle = np.deg2rad(angle)
697        if self.flipy():
698            transform = Affine2D().scale(fontsize / text2path.FONT_SCALE,
699                                         fontsize / text2path.FONT_SCALE)
700            transform = transform.rotate(angle).translate(x, self.height - y)
701        else:
702            transform = Affine2D().scale(fontsize / text2path.FONT_SCALE,
703                                         fontsize / text2path.FONT_SCALE)
704            transform = transform.rotate(angle).translate(x, y)
705
706        return path, transform
707
708    def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
709        """
710        draw the text by converting them to paths using textpath module.
711
712        Parameters
713        ----------
714        prop : `matplotlib.font_manager.FontProperties`
715          font property
716
717        s : str
718          text to be converted
719
720        usetex : bool
721          If True, use matplotlib usetex mode.
722
723        ismath : bool
724          If True, use mathtext parser. If "TeX", use *usetex* mode.
725        """
726        path, transform = self._get_text_path_transform(
727            x, y, s, prop, angle, ismath)
728        color = gc.get_rgb()
729
730        gc.set_linewidth(0.0)
731        self.draw_path(gc, path, transform, rgbFace=color)
732
733    def get_text_width_height_descent(self, s, prop, ismath):
734        """
735        Get the width, height, and descent (offset from the bottom
736        to the baseline), in display coords, of the string *s* with
737        :class:`~matplotlib.font_manager.FontProperties` *prop*
738        """
739        if ismath == 'TeX':
740            # todo: handle props
741            size = prop.get_size_in_points()
742            texmanager = self._text2path.get_texmanager()
743            fontsize = prop.get_size_in_points()
744            w, h, d = texmanager.get_text_width_height_descent(
745                s, fontsize, renderer=self)
746            return w, h, d
747
748        dpi = self.points_to_pixels(72)
749        if ismath:
750            dims = self._text2path.mathtext_parser.parse(s, dpi, prop)
751            return dims[0:3]  # return width, height, descent
752
753        flags = self._text2path._get_hinting_flag()
754        font = self._text2path._get_font(prop)
755        size = prop.get_size_in_points()
756        font.set_size(size, dpi)
757        # the width and height of unrotated string
758        font.set_text(s, 0.0, flags=flags)
759        w, h = font.get_width_height()
760        d = font.get_descent()
761        w /= 64.0  # convert from subpixels
762        h /= 64.0
763        d /= 64.0
764        return w, h, d
765
766    def flipy(self):
767        """
768        Return true if y small numbers are top for renderer Is used
769        for drawing text (:mod:`matplotlib.text`) and images
770        (:mod:`matplotlib.image`) only
771        """
772        return True
773
774    def get_canvas_width_height(self):
775        'return the canvas width and height in display coords'
776        return 1, 1
777
778    def get_texmanager(self):
779        """
780        return the :class:`matplotlib.texmanager.TexManager` instance
781        """
782        if self._texmanager is None:
783            from matplotlib.texmanager import TexManager
784            self._texmanager = TexManager()
785        return self._texmanager
786
787    def new_gc(self):
788        """
789        Return an instance of a :class:`GraphicsContextBase`
790        """
791        return GraphicsContextBase()
792
793    def points_to_pixels(self, points):
794        """
795        Convert points to display units
796
797        You need to override this function (unless your backend
798        doesn't have a dpi, e.g., postscript or svg).  Some imaging
799        systems assume some value for pixels per inch::
800
801            points to pixels = points * pixels_per_inch/72.0 * dpi/72.0
802
803        Parameters
804        ----------
805        points : scalar or array_like
806            a float or a numpy array of float
807
808        Returns
809        -------
810        Points converted to pixels
811        """
812        return points
813
814    def strip_math(self, s):
815        return cbook.strip_math(s)
816
817    def start_rasterizing(self):
818        """
819        Used in MixedModeRenderer. Switch to the raster renderer.
820        """
821
822    def stop_rasterizing(self):
823        """
824        Used in MixedModeRenderer. Switch back to the vector renderer
825        and draw the contents of the raster renderer as an image on
826        the vector renderer.
827        """
828
829    def start_filter(self):
830        """
831        Used in AggRenderer. Switch to a temporary renderer for image
832        filtering effects.
833        """
834
835    def stop_filter(self, filter_func):
836        """
837        Used in AggRenderer. Switch back to the original renderer.
838        The contents of the temporary renderer is processed with the
839        *filter_func* and is drawn on the original renderer as an
840        image.
841        """
842
843
844class GraphicsContextBase(object):
845    """
846    An abstract base class that provides color, line styles, etc...
847    """
848
849    def __init__(self):
850        self._alpha = 1.0
851        self._forced_alpha = False  # if True, _alpha overrides A from RGBA
852        self._antialiased = 1  # use 0,1 not True, False for extension code
853        self._capstyle = 'butt'
854        self._cliprect = None
855        self._clippath = None
856        self._dashes = None, None
857        self._joinstyle = 'round'
858        self._linestyle = 'solid'
859        self._linewidth = 1
860        self._rgb = (0.0, 0.0, 0.0, 1.0)
861        self._hatch = None
862        self._hatch_color = colors.to_rgba(rcParams['hatch.color'])
863        self._hatch_linewidth = rcParams['hatch.linewidth']
864        self._url = None
865        self._gid = None
866        self._snap = None
867        self._sketch = None
868
869    def copy_properties(self, gc):
870        'Copy properties from gc to self'
871        self._alpha = gc._alpha
872        self._forced_alpha = gc._forced_alpha
873        self._antialiased = gc._antialiased
874        self._capstyle = gc._capstyle
875        self._cliprect = gc._cliprect
876        self._clippath = gc._clippath
877        self._dashes = gc._dashes
878        self._joinstyle = gc._joinstyle
879        self._linestyle = gc._linestyle
880        self._linewidth = gc._linewidth
881        self._rgb = gc._rgb
882        self._hatch = gc._hatch
883        self._hatch_color = gc._hatch_color
884        self._hatch_linewidth = gc._hatch_linewidth
885        self._url = gc._url
886        self._gid = gc._gid
887        self._snap = gc._snap
888        self._sketch = gc._sketch
889
890    def restore(self):
891        """
892        Restore the graphics context from the stack - needed only
893        for backends that save graphics contexts on a stack
894        """
895
896    def get_alpha(self):
897        """
898        Return the alpha value used for blending - not supported on
899        all backends
900        """
901        return self._alpha
902
903    def get_antialiased(self):
904        "Return true if the object should try to do antialiased rendering"
905        return self._antialiased
906
907    def get_capstyle(self):
908        """
909        Return the capstyle as a string in ('butt', 'round', 'projecting')
910        """
911        return self._capstyle
912
913    def get_clip_rectangle(self):
914        """
915        Return the clip rectangle as a :class:`~matplotlib.transforms.Bbox`
916        instance
917        """
918        return self._cliprect
919
920    def get_clip_path(self):
921        """
922        Return the clip path in the form (path, transform), where path
923        is a :class:`~matplotlib.path.Path` instance, and transform is
924        an affine transform to apply to the path before clipping.
925        """
926        if self._clippath is not None:
927            return self._clippath.get_transformed_path_and_affine()
928        return None, None
929
930    def get_dashes(self):
931        """
932        Return the dash information as an offset dashlist tuple.
933
934        The dash list is a even size list that gives the ink on, ink
935        off in pixels.
936
937        See p107 of to PostScript `BLUEBOOK
938        <https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF>`_
939        for more info.
940
941        Default value is None
942        """
943        return self._dashes
944
945    def get_forced_alpha(self):
946        """
947        Return whether the value given by get_alpha() should be used to
948        override any other alpha-channel values.
949        """
950        return self._forced_alpha
951
952    def get_joinstyle(self):
953        """
954        Return the line join style as one of ('miter', 'round', 'bevel')
955        """
956        return self._joinstyle
957
958    @cbook.deprecated("2.1")
959    def get_linestyle(self):
960        """
961        Return the linestyle: one of ('solid', 'dashed', 'dashdot',
962        'dotted').
963        """
964        return self._linestyle
965
966    def get_linewidth(self):
967        """
968        Return the line width in points as a scalar
969        """
970        return self._linewidth
971
972    def get_rgb(self):
973        """
974        returns a tuple of three or four floats from 0-1.
975        """
976        return self._rgb
977
978    def get_url(self):
979        """
980        returns a url if one is set, None otherwise
981        """
982        return self._url
983
984    def get_gid(self):
985        """
986        Return the object identifier if one is set, None otherwise.
987        """
988        return self._gid
989
990    def get_snap(self):
991        """
992        returns the snap setting which may be:
993
994          * True: snap vertices to the nearest pixel center
995
996          * False: leave vertices as-is
997
998          * None: (auto) If the path contains only rectilinear line
999            segments, round to the nearest pixel center
1000        """
1001        return self._snap
1002
1003    def set_alpha(self, alpha):
1004        """
1005        Set the alpha value used for blending - not supported on all backends.
1006        If ``alpha=None`` (the default), the alpha components of the
1007        foreground and fill colors will be used to set their respective
1008        transparencies (where applicable); otherwise, ``alpha`` will override
1009        them.
1010        """
1011        if alpha is not None:
1012            self._alpha = alpha
1013            self._forced_alpha = True
1014        else:
1015            self._alpha = 1.0
1016            self._forced_alpha = False
1017        self.set_foreground(self._rgb, isRGBA=True)
1018
1019    def set_antialiased(self, b):
1020        """
1021        True if object should be drawn with antialiased rendering
1022        """
1023
1024        # use 0, 1 to make life easier on extension code trying to read the gc
1025        if b:
1026            self._antialiased = 1
1027        else:
1028            self._antialiased = 0
1029
1030    def set_capstyle(self, cs):
1031        """
1032        Set the capstyle as a string in ('butt', 'round', 'projecting')
1033        """
1034        if cs in ('butt', 'round', 'projecting'):
1035            self._capstyle = cs
1036        else:
1037            raise ValueError('Unrecognized cap style.  Found %s' % cs)
1038
1039    def set_clip_rectangle(self, rectangle):
1040        """
1041        Set the clip rectangle with sequence (left, bottom, width, height)
1042        """
1043        self._cliprect = rectangle
1044
1045    def set_clip_path(self, path):
1046        """
1047        Set the clip path and transformation.  Path should be a
1048        :class:`~matplotlib.transforms.TransformedPath` instance.
1049        """
1050        if (path is not None
1051                and not isinstance(path, transforms.TransformedPath)):
1052            raise ValueError("Path should be a "
1053                             "matplotlib.transforms.TransformedPath instance")
1054        self._clippath = path
1055
1056    def set_dashes(self, dash_offset, dash_list):
1057        """
1058        Set the dash style for the gc.
1059
1060        Parameters
1061        ----------
1062        dash_offset : float
1063            is the offset (usually 0).
1064
1065        dash_list : array_like
1066            specifies the on-off sequence as points.
1067            ``(None, None)`` specifies a solid line
1068
1069        """
1070        if dash_list is not None:
1071            dl = np.asarray(dash_list)
1072            if np.any(dl < 0.0):
1073                raise ValueError(
1074                    "All values in the dash list must be positive")
1075        self._dashes = dash_offset, dash_list
1076
1077    def set_foreground(self, fg, isRGBA=False):
1078        """
1079        Set the foreground color.  fg can be a MATLAB format string, a
1080        html hex color string, an rgb or rgba unit tuple, or a float between 0
1081        and 1.  In the latter case, grayscale is used.
1082
1083        If you know fg is rgba, set ``isRGBA=True`` for efficiency.
1084        """
1085        if self._forced_alpha and isRGBA:
1086            self._rgb = fg[:3] + (self._alpha,)
1087        elif self._forced_alpha:
1088            self._rgb = colors.to_rgba(fg, self._alpha)
1089        elif isRGBA:
1090            self._rgb = fg
1091        else:
1092            self._rgb = colors.to_rgba(fg)
1093
1094    def set_joinstyle(self, js):
1095        """
1096        Set the join style to be one of ('miter', 'round', 'bevel')
1097        """
1098        if js in ('miter', 'round', 'bevel'):
1099            self._joinstyle = js
1100        else:
1101            raise ValueError('Unrecognized join style.  Found %s' % js)
1102
1103    def set_linewidth(self, w):
1104        """
1105        Set the linewidth in points
1106        """
1107        self._linewidth = float(w)
1108
1109    @cbook.deprecated("2.1")
1110    def set_linestyle(self, style):
1111        """
1112        Set the linestyle to be one of ('solid', 'dashed', 'dashdot',
1113        'dotted'). These are defined in the rcParams
1114        `lines.dashed_pattern`, `lines.dashdot_pattern` and
1115        `lines.dotted_pattern`.  One may also specify customized dash
1116        styles by providing a tuple of (offset, dash pairs).
1117        """
1118        self._linestyle = style
1119
1120    def set_url(self, url):
1121        """
1122        Sets the url for links in compatible backends
1123        """
1124        self._url = url
1125
1126    def set_gid(self, id):
1127        """
1128        Sets the id.
1129        """
1130        self._gid = id
1131
1132    def set_snap(self, snap):
1133        """
1134        Sets the snap setting which may be:
1135
1136          * True: snap vertices to the nearest pixel center
1137
1138          * False: leave vertices as-is
1139
1140          * None: (auto) If the path contains only rectilinear line
1141            segments, round to the nearest pixel center
1142        """
1143        self._snap = snap
1144
1145    def set_hatch(self, hatch):
1146        """
1147        Sets the hatch style for filling
1148        """
1149        self._hatch = hatch
1150
1151    def get_hatch(self):
1152        """
1153        Gets the current hatch style
1154        """
1155        return self._hatch
1156
1157    def get_hatch_path(self, density=6.0):
1158        """
1159        Returns a Path for the current hatch.
1160        """
1161        hatch = self.get_hatch()
1162        if hatch is None:
1163            return None
1164        return Path.hatch(hatch, density)
1165
1166    def get_hatch_color(self):
1167        """
1168        Gets the color to use for hatching.
1169        """
1170        return self._hatch_color
1171
1172    def set_hatch_color(self, hatch_color):
1173        """
1174        sets the color to use for hatching.
1175        """
1176        self._hatch_color = hatch_color
1177
1178    def get_hatch_linewidth(self):
1179        """
1180        Gets the linewidth to use for hatching.
1181        """
1182        return self._hatch_linewidth
1183
1184    def get_sketch_params(self):
1185        """
1186        Returns the sketch parameters for the artist.
1187
1188        Returns
1189        -------
1190        sketch_params : tuple or `None`
1191
1192        A 3-tuple with the following elements:
1193
1194          * `scale`: The amplitude of the wiggle perpendicular to the
1195            source line.
1196
1197          * `length`: The length of the wiggle along the line.
1198
1199          * `randomness`: The scale factor by which the length is
1200            shrunken or expanded.
1201
1202        May return `None` if no sketch parameters were set.
1203        """
1204        return self._sketch
1205
1206    def set_sketch_params(self, scale=None, length=None, randomness=None):
1207        """
1208        Sets the sketch parameters.
1209
1210        Parameters
1211        ----------
1212
1213        scale : float, optional
1214            The amplitude of the wiggle perpendicular to the source
1215            line, in pixels.  If scale is `None`, or not provided, no
1216            sketch filter will be provided.
1217
1218        length : float, optional
1219             The length of the wiggle along the line, in pixels
1220             (default 128)
1221
1222        randomness : float, optional
1223            The scale factor by which the length is shrunken or
1224            expanded (default 16)
1225        """
1226        self._sketch = (
1227            None if scale is None
1228            else (scale, length or 128., randomness or 16.))
1229
1230
1231class TimerBase(object):
1232    '''
1233    A base class for providing timer events, useful for things animations.
1234    Backends need to implement a few specific methods in order to use their
1235    own timing mechanisms so that the timer events are integrated into their
1236    event loops.
1237
1238    Mandatory functions that must be implemented:
1239
1240        * `_timer_start`: Contains backend-specific code for starting
1241          the timer
1242
1243        * `_timer_stop`: Contains backend-specific code for stopping
1244          the timer
1245
1246    Optional overrides:
1247
1248        * `_timer_set_single_shot`: Code for setting the timer to
1249          single shot operating mode, if supported by the timer
1250          object. If not, the `Timer` class itself will store the flag
1251          and the `_on_timer` method should be overridden to support
1252          such behavior.
1253
1254        * `_timer_set_interval`: Code for setting the interval on the
1255          timer, if there is a method for doing so on the timer
1256          object.
1257
1258        * `_on_timer`: This is the internal function that any timer
1259          object should call, which will handle the task of running
1260          all callbacks that have been set.
1261
1262    Attributes
1263    ----------
1264    interval : scalar
1265        The time between timer events in milliseconds. Default is 1000 ms.
1266
1267    single_shot : bool
1268        Boolean flag indicating whether this timer should operate as single
1269        shot (run once and then stop). Defaults to `False`.
1270
1271    callbacks : List[Tuple[callable, Tuple, Dict]]
1272        Stores list of (func, args, kwargs) tuples that will be called upon
1273        timer events. This list can be manipulated directly, or the
1274        functions `add_callback` and `remove_callback` can be used.
1275
1276    '''
1277    def __init__(self, interval=None, callbacks=None):
1278        #Initialize empty callbacks list and setup default settings if necssary
1279        if callbacks is None:
1280            self.callbacks = []
1281        else:
1282            self.callbacks = callbacks[:]  # Create a copy
1283
1284        if interval is None:
1285            self._interval = 1000
1286        else:
1287            self._interval = interval
1288
1289        self._single = False
1290
1291        # Default attribute for holding the GUI-specific timer object
1292        self._timer = None
1293
1294    def __del__(self):
1295        'Need to stop timer and possibly disconnect timer.'
1296        self._timer_stop()
1297
1298    def start(self, interval=None):
1299        '''
1300        Start the timer object. `interval` is optional and will be used
1301        to reset the timer interval first if provided.
1302        '''
1303        if interval is not None:
1304            self._set_interval(interval)
1305        self._timer_start()
1306
1307    def stop(self):
1308        '''
1309        Stop the timer.
1310        '''
1311        self._timer_stop()
1312
1313    def _timer_start(self):
1314        pass
1315
1316    def _timer_stop(self):
1317        pass
1318
1319    def _get_interval(self):
1320        return self._interval
1321
1322    def _set_interval(self, interval):
1323        # Force to int since none of the backends actually support fractional
1324        # milliseconds, and some error or give warnings.
1325        interval = int(interval)
1326        self._interval = interval
1327        self._timer_set_interval()
1328
1329    interval = property(_get_interval, _set_interval)
1330
1331    def _get_single_shot(self):
1332        return self._single
1333
1334    def _set_single_shot(self, ss=True):
1335        self._single = ss
1336        self._timer_set_single_shot()
1337
1338    single_shot = property(_get_single_shot, _set_single_shot)
1339
1340    def add_callback(self, func, *args, **kwargs):
1341        '''
1342        Register `func` to be called by timer when the event fires. Any
1343        additional arguments provided will be passed to `func`.
1344        '''
1345        self.callbacks.append((func, args, kwargs))
1346
1347    def remove_callback(self, func, *args, **kwargs):
1348        '''
1349        Remove `func` from list of callbacks. `args` and `kwargs` are optional
1350        and used to distinguish between copies of the same function registered
1351        to be called with different arguments.
1352        '''
1353        if args or kwargs:
1354            self.callbacks.remove((func, args, kwargs))
1355        else:
1356            funcs = [c[0] for c in self.callbacks]
1357            if func in funcs:
1358                self.callbacks.pop(funcs.index(func))
1359
1360    def _timer_set_interval(self):
1361        """Used to set interval on underlying timer object."""
1362
1363    def _timer_set_single_shot(self):
1364        """Used to set single shot on underlying timer object."""
1365
1366    def _on_timer(self):
1367        '''
1368        Runs all function that have been registered as callbacks. Functions
1369        can return False (or 0) if they should not be called any more. If there
1370        are no callbacks, the timer is automatically stopped.
1371        '''
1372        for func, args, kwargs in self.callbacks:
1373            ret = func(*args, **kwargs)
1374            # docstring above explains why we use `if ret == 0` here,
1375            # instead of `if not ret`.
1376            # This will also catch `ret == False` as `False == 0`
1377            # but does not annoy the linters
1378            # https://docs.python.org/3/library/stdtypes.html#boolean-values
1379            if ret == 0:
1380                self.callbacks.remove((func, args, kwargs))
1381
1382        if len(self.callbacks) == 0:
1383            self.stop()
1384
1385
1386class Event(object):
1387    """
1388    A matplotlib event.  Attach additional attributes as defined in
1389    :meth:`FigureCanvasBase.mpl_connect`.  The following attributes
1390    are defined and shown with their default values
1391
1392    Attributes
1393    ----------
1394    name : str
1395        the event name
1396
1397    canvas : `FigureCanvasBase`
1398        the backend-specific canvas instance generating the event
1399
1400    guiEvent
1401        the GUI event that triggered the matplotlib event
1402
1403    """
1404    def __init__(self, name, canvas, guiEvent=None):
1405        self.name = name
1406        self.canvas = canvas
1407        self.guiEvent = guiEvent
1408
1409
1410@cbook.deprecated("2.1")
1411class IdleEvent(Event):
1412    """
1413    An event triggered by the GUI backend when it is idle -- useful
1414    for passive animation
1415    """
1416
1417
1418class DrawEvent(Event):
1419    """
1420    An event triggered by a draw operation on the canvas
1421
1422    In most backends callbacks subscribed to this callback will be
1423    fired after the rendering is complete but before the screen is
1424    updated.  Any extra artists drawn to the canvas's renderer will
1425    be reflected without an explicit call to ``blit``.
1426
1427    .. warning ::
1428
1429       Calling ``canvas.draw`` and ``canvas.blit`` in these callbacks may
1430       not be safe with all backends and may cause infinite recursion.
1431
1432    In addition to the :class:`Event` attributes, the following event
1433    attributes are defined:
1434
1435    Attributes
1436    ----------
1437    renderer : `RendererBase`
1438        the renderer for the draw event
1439
1440    """
1441    def __init__(self, name, canvas, renderer):
1442        Event.__init__(self, name, canvas)
1443        self.renderer = renderer
1444
1445
1446class ResizeEvent(Event):
1447    """
1448    An event triggered by a canvas resize
1449
1450    In addition to the :class:`Event` attributes, the following event
1451    attributes are defined:
1452
1453    Attributes
1454    ----------
1455    width : scalar
1456        width of the canvas in pixels
1457
1458    height : scalar
1459        height of the canvas in pixels
1460
1461    """
1462    def __init__(self, name, canvas):
1463        Event.__init__(self, name, canvas)
1464        self.width, self.height = canvas.get_width_height()
1465
1466
1467class CloseEvent(Event):
1468    """
1469    An event triggered by a figure being closed
1470
1471    """
1472    def __init__(self, name, canvas, guiEvent=None):
1473        Event.__init__(self, name, canvas, guiEvent)
1474
1475
1476class LocationEvent(Event):
1477    """
1478    An event that has a screen location
1479
1480    The following additional attributes are defined and shown with
1481    their default values.
1482
1483    In addition to the :class:`Event` attributes, the following
1484    event attributes are defined:
1485
1486    Attributes
1487    ----------
1488    x : scalar
1489        x position - pixels from left of canvas
1490
1491    y : scalar
1492        y position - pixels from bottom of canvas
1493
1494    inaxes : bool
1495        the :class:`~matplotlib.axes.Axes` instance if mouse is over axes
1496
1497    xdata : scalar
1498        x coord of mouse in data coords
1499
1500    ydata : scalar
1501        y coord of mouse in data coords
1502
1503    """
1504    x = None       # x position - pixels from left of canvas
1505    y = None       # y position - pixels from right of canvas
1506    inaxes = None  # the Axes instance if mouse us over axes
1507    xdata = None   # x coord of mouse in data coords
1508    ydata = None   # y coord of mouse in data coords
1509
1510    # the last event that was triggered before this one
1511    lastevent = None
1512
1513    def __init__(self, name, canvas, x, y, guiEvent=None):
1514        """
1515        *x*, *y* in figure coords, 0,0 = bottom, left
1516        """
1517        Event.__init__(self, name, canvas, guiEvent=guiEvent)
1518        self.x = x
1519        self.y = y
1520
1521        if x is None or y is None:
1522            # cannot check if event was in axes if no x,y info
1523            self.inaxes = None
1524            self._update_enter_leave()
1525            return
1526
1527        # Find all axes containing the mouse
1528        if self.canvas.mouse_grabber is None:
1529            axes_list = [a for a in self.canvas.figure.get_axes()
1530                         if a.in_axes(self)]
1531        else:
1532            axes_list = [self.canvas.mouse_grabber]
1533
1534        if axes_list:
1535            self.inaxes = cbook._topmost_artist(axes_list)
1536            try:
1537                trans = self.inaxes.transData.inverted()
1538                xdata, ydata = trans.transform_point((x, y))
1539            except ValueError:
1540                self.xdata = None
1541                self.ydata = None
1542            else:
1543                self.xdata = xdata
1544                self.ydata = ydata
1545        else:
1546            self.inaxes = None
1547
1548        self._update_enter_leave()
1549
1550    def _update_enter_leave(self):
1551        'process the figure/axes enter leave events'
1552        if LocationEvent.lastevent is not None:
1553            last = LocationEvent.lastevent
1554            if last.inaxes != self.inaxes:
1555                # process axes enter/leave events
1556                try:
1557                    if last.inaxes is not None:
1558                        last.canvas.callbacks.process('axes_leave_event', last)
1559                except:
1560                    pass
1561                    # See ticket 2901582.
1562                    # I think this is a valid exception to the rule
1563                    # against catching all exceptions; if anything goes
1564                    # wrong, we simply want to move on and process the
1565                    # current event.
1566                if self.inaxes is not None:
1567                    self.canvas.callbacks.process('axes_enter_event', self)
1568
1569        else:
1570            # process a figure enter event
1571            if self.inaxes is not None:
1572                self.canvas.callbacks.process('axes_enter_event', self)
1573
1574        LocationEvent.lastevent = self
1575
1576
1577class MouseEvent(LocationEvent):
1578    """
1579    A mouse event ('button_press_event',
1580                   'button_release_event',
1581                   'scroll_event',
1582                   'motion_notify_event').
1583
1584    In addition to the :class:`Event` and :class:`LocationEvent`
1585    attributes, the following attributes are defined:
1586
1587    Attributes
1588    ----------
1589    button : None, scalar, or str
1590        button pressed None, 1, 2, 3, 'up', 'down' (up and down are used
1591        for scroll events).  Note that in the nbagg backend, both the
1592        middle and right clicks return 3 since right clicking will bring
1593        up the context menu in some browsers.
1594
1595    key : None, or str
1596        the key depressed when the mouse event triggered (see
1597        :class:`KeyEvent`)
1598
1599    step : scalar
1600        number of scroll steps (positive for 'up', negative for 'down')
1601
1602    Examples
1603    --------
1604    Usage::
1605
1606        def on_press(event):
1607            print('you pressed', event.button, event.xdata, event.ydata)
1608
1609        cid = fig.canvas.mpl_connect('button_press_event', on_press)
1610
1611    """
1612    x = None         # x position - pixels from left of canvas
1613    y = None         # y position - pixels from right of canvas
1614    button = None    # button pressed None, 1, 2, 3
1615    dblclick = None  # whether or not the event is the result of a double click
1616    inaxes = None    # the Axes instance if mouse us over axes
1617    xdata = None     # x coord of mouse in data coords
1618    ydata = None     # y coord of mouse in data coords
1619    step = None      # scroll steps for scroll events
1620
1621    def __init__(self, name, canvas, x, y, button=None, key=None,
1622                 step=0, dblclick=False, guiEvent=None):
1623        """
1624        x, y in figure coords, 0,0 = bottom, left
1625        button pressed None, 1, 2, 3, 'up', 'down'
1626        """
1627        LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
1628        self.button = button
1629        self.key = key
1630        self.step = step
1631        self.dblclick = dblclick
1632
1633    def __str__(self):
1634        return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%s " +
1635                "dblclick=%s inaxes=%s") % (self.x, self.y, self.xdata,
1636                                            self.ydata, self.button,
1637                                            self.dblclick, self.inaxes)
1638
1639
1640class PickEvent(Event):
1641    """
1642    a pick event, fired when the user picks a location on the canvas
1643    sufficiently close to an artist.
1644
1645    Attrs: all the :class:`Event` attributes plus
1646
1647    Attributes
1648    ----------
1649    mouseevent : `MouseEvent`
1650        the mouse event that generated the pick
1651
1652    artist : `matplotlib.artist.Artist`
1653        the picked artist
1654
1655    other
1656        extra class dependent attrs -- e.g., a
1657        :class:`~matplotlib.lines.Line2D` pick may define different
1658        extra attributes than a
1659        :class:`~matplotlib.collections.PatchCollection` pick event
1660
1661    Examples
1662    --------
1663    Usage::
1664
1665        ax.plot(np.rand(100), 'o', picker=5)  # 5 points tolerance
1666
1667        def on_pick(event):
1668            line = event.artist
1669            xdata, ydata = line.get_data()
1670            ind = event.ind
1671            print('on pick line:', np.array([xdata[ind], ydata[ind]]).T)
1672
1673        cid = fig.canvas.mpl_connect('pick_event', on_pick)
1674
1675    """
1676    def __init__(self, name, canvas, mouseevent, artist,
1677                 guiEvent=None, **kwargs):
1678        Event.__init__(self, name, canvas, guiEvent)
1679        self.mouseevent = mouseevent
1680        self.artist = artist
1681        self.__dict__.update(kwargs)
1682
1683
1684class KeyEvent(LocationEvent):
1685    """
1686    A key event (key press, key release).
1687
1688    Attach additional attributes as defined in
1689    :meth:`FigureCanvasBase.mpl_connect`.
1690
1691    In addition to the :class:`Event` and :class:`LocationEvent`
1692    attributes, the following attributes are defined:
1693
1694    Attributes
1695    ----------
1696    key : None or str
1697        the key(s) pressed. Could be **None**, a single case sensitive ascii
1698        character ("g", "G", "#", etc.), a special key
1699        ("control", "shift", "f1", "up", etc.) or a
1700        combination of the above (e.g., "ctrl+alt+g", "ctrl+alt+G").
1701
1702    Notes
1703    -----
1704    Modifier keys will be prefixed to the pressed key and will be in the order
1705    "ctrl", "alt", "super". The exception to this rule is when the pressed key
1706    is itself a modifier key, therefore "ctrl+alt" and "alt+control" can both
1707    be valid key values.
1708
1709    Examples
1710    --------
1711    Usage::
1712
1713        def on_key(event):
1714            print('you pressed', event.key, event.xdata, event.ydata)
1715
1716        cid = fig.canvas.mpl_connect('key_press_event', on_key)
1717
1718    """
1719    def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
1720        LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
1721        self.key = key
1722
1723
1724class FigureCanvasBase(object):
1725    """
1726    The canvas the figure renders into.
1727
1728    Public attributes
1729
1730    Attributes
1731    ----------
1732    figure : `matplotlib.figure.Figure`
1733        A high-level figure instance
1734
1735    """
1736    events = [
1737        'resize_event',
1738        'draw_event',
1739        'key_press_event',
1740        'key_release_event',
1741        'button_press_event',
1742        'button_release_event',
1743        'scroll_event',
1744        'motion_notify_event',
1745        'pick_event',
1746        'idle_event',
1747        'figure_enter_event',
1748        'figure_leave_event',
1749        'axes_enter_event',
1750        'axes_leave_event',
1751        'close_event'
1752    ]
1753
1754    supports_blit = True
1755    fixed_dpi = None
1756
1757    filetypes = _default_filetypes
1758    if _has_pil:
1759        # JPEG support
1760        register_backend('jpg', 'matplotlib.backends.backend_agg',
1761                         'Joint Photographic Experts Group')
1762        register_backend('jpeg', 'matplotlib.backends.backend_agg',
1763                         'Joint Photographic Experts Group')
1764        # TIFF support
1765        register_backend('tif', 'matplotlib.backends.backend_agg',
1766                         'Tagged Image File Format')
1767        register_backend('tiff', 'matplotlib.backends.backend_agg',
1768                         'Tagged Image File Format')
1769
1770    def __init__(self, figure):
1771        self._is_idle_drawing = True
1772        self._is_saving = False
1773        figure.set_canvas(self)
1774        self.figure = figure
1775        # a dictionary from event name to a dictionary that maps cid->func
1776        self.callbacks = cbook.CallbackRegistry()
1777        self.widgetlock = widgets.LockDraw()
1778        self._button = None  # the button pressed
1779        self._key = None  # the key pressed
1780        self._lastx, self._lasty = None, None
1781        self.button_pick_id = self.mpl_connect('button_press_event', self.pick)
1782        self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick)
1783        self.mouse_grabber = None  # the axes currently grabbing mouse
1784        self.toolbar = None  # NavigationToolbar2 will set me
1785        self._is_idle_drawing = False
1786
1787    @contextmanager
1788    def _idle_draw_cntx(self):
1789        self._is_idle_drawing = True
1790        yield
1791        self._is_idle_drawing = False
1792
1793    def is_saving(self):
1794        """
1795        Returns whether the renderer is in the process of saving
1796        to a file, rather than rendering for an on-screen buffer.
1797        """
1798        return self._is_saving
1799
1800    @cbook.deprecated("2.2")
1801    def onRemove(self, ev):
1802        """
1803        Mouse event processor which removes the top artist
1804        under the cursor.  Connect this to the 'mouse_press_event'
1805        using::
1806
1807            canvas.mpl_connect('mouse_press_event',canvas.onRemove)
1808        """
1809        # Find the top artist under the cursor
1810        under = cbook._topmost_artist(self.figure.hitlist(ev))
1811        h = None
1812        if under:
1813            h = under[-1]
1814
1815        # Try deleting that artist, or its parent if you
1816        # can't delete the artist
1817        while h:
1818            if h.remove():
1819                self.draw_idle()
1820                break
1821            parent = None
1822            for p in under:
1823                if h in p.get_children():
1824                    parent = p
1825                    break
1826            h = parent
1827
1828    def pick(self, mouseevent):
1829        if not self.widgetlock.locked():
1830            self.figure.pick(mouseevent)
1831
1832    def blit(self, bbox=None):
1833        """Blit the canvas in bbox (default entire canvas)."""
1834
1835    def resize(self, w, h):
1836        """Set the canvas size in pixels."""
1837
1838    def draw_event(self, renderer):
1839        """Pass a `DrawEvent` to all functions connected to ``draw_event``."""
1840        s = 'draw_event'
1841        event = DrawEvent(s, self, renderer)
1842        self.callbacks.process(s, event)
1843
1844    def resize_event(self):
1845        """Pass a `ResizeEvent` to all functions connected to ``resize_event``.
1846        """
1847        s = 'resize_event'
1848        event = ResizeEvent(s, self)
1849        self.callbacks.process(s, event)
1850        self.draw_idle()
1851
1852    def close_event(self, guiEvent=None):
1853        """Pass a `CloseEvent` to all functions connected to ``close_event``.
1854        """
1855        s = 'close_event'
1856        try:
1857            event = CloseEvent(s, self, guiEvent=guiEvent)
1858            self.callbacks.process(s, event)
1859        except (TypeError, AttributeError):
1860            pass
1861            # Suppress the TypeError when the python session is being killed.
1862            # It may be that a better solution would be a mechanism to
1863            # disconnect all callbacks upon shutdown.
1864            # AttributeError occurs on OSX with qt4agg upon exiting
1865            # with an open window; 'callbacks' attribute no longer exists.
1866
1867    def key_press_event(self, key, guiEvent=None):
1868        """Pass a `KeyEvent` to all functions connected to ``key_press_event``.
1869        """
1870        self._key = key
1871        s = 'key_press_event'
1872        event = KeyEvent(
1873            s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
1874        self.callbacks.process(s, event)
1875
1876    def key_release_event(self, key, guiEvent=None):
1877        """
1878        Pass a `KeyEvent` to all functions connected to ``key_release_event``.
1879        """
1880        s = 'key_release_event'
1881        event = KeyEvent(
1882            s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
1883        self.callbacks.process(s, event)
1884        self._key = None
1885
1886    def pick_event(self, mouseevent, artist, **kwargs):
1887        """
1888        This method will be called by artists who are picked and will
1889        fire off :class:`PickEvent` callbacks registered listeners
1890        """
1891        s = 'pick_event'
1892        event = PickEvent(s, self, mouseevent, artist,
1893                          guiEvent=mouseevent.guiEvent,
1894                          **kwargs)
1895        self.callbacks.process(s, event)
1896
1897    def scroll_event(self, x, y, step, guiEvent=None):
1898        """
1899        Backend derived classes should call this function on any
1900        scroll wheel event.  x,y are the canvas coords: 0,0 is lower,
1901        left.  button and key are as defined in MouseEvent.
1902
1903        This method will be call all functions connected to the
1904        'scroll_event' with a :class:`MouseEvent` instance.
1905        """
1906        if step >= 0:
1907            self._button = 'up'
1908        else:
1909            self._button = 'down'
1910        s = 'scroll_event'
1911        mouseevent = MouseEvent(s, self, x, y, self._button, self._key,
1912                                step=step, guiEvent=guiEvent)
1913        self.callbacks.process(s, mouseevent)
1914
1915    def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
1916        """
1917        Backend derived classes should call this function on any mouse
1918        button press.  x,y are the canvas coords: 0,0 is lower, left.
1919        button and key are as defined in :class:`MouseEvent`.
1920
1921        This method will be call all functions connected to the
1922        'button_press_event' with a :class:`MouseEvent` instance.
1923        """
1924        self._button = button
1925        s = 'button_press_event'
1926        mouseevent = MouseEvent(s, self, x, y, button, self._key,
1927                                dblclick=dblclick, guiEvent=guiEvent)
1928        self.callbacks.process(s, mouseevent)
1929
1930    def button_release_event(self, x, y, button, guiEvent=None):
1931        """
1932        Backend derived classes should call this function on any mouse
1933        button release.
1934
1935        This method will call all functions connected to the
1936        'button_release_event' with a :class:`MouseEvent` instance.
1937
1938        Parameters
1939        ----------
1940        x : scalar
1941            the canvas coordinates where 0=left
1942
1943        y : scalar
1944            the canvas coordinates where 0=bottom
1945
1946        guiEvent
1947            the native UI event that generated the mpl event
1948
1949        """
1950        s = 'button_release_event'
1951        event = MouseEvent(s, self, x, y, button, self._key, guiEvent=guiEvent)
1952        self.callbacks.process(s, event)
1953        self._button = None
1954
1955    def motion_notify_event(self, x, y, guiEvent=None):
1956        """
1957        Backend derived classes should call this function on any
1958        motion-notify-event.
1959
1960        This method will call all functions connected to the
1961        'motion_notify_event' with a :class:`MouseEvent` instance.
1962
1963        Parameters
1964        ----------
1965        x : scalar
1966            the canvas coordinates where 0=left
1967
1968        y : scalar
1969            the canvas coordinates where 0=bottom
1970
1971        guiEvent
1972            the native UI event that generated the mpl event
1973
1974        """
1975        self._lastx, self._lasty = x, y
1976        s = 'motion_notify_event'
1977        event = MouseEvent(s, self, x, y, self._button, self._key,
1978                           guiEvent=guiEvent)
1979        self.callbacks.process(s, event)
1980
1981    def leave_notify_event(self, guiEvent=None):
1982        """
1983        Backend derived classes should call this function when leaving
1984        canvas
1985
1986        Parameters
1987        ----------
1988        guiEvent
1989            the native UI event that generated the mpl event
1990
1991        """
1992
1993        self.callbacks.process('figure_leave_event', LocationEvent.lastevent)
1994        LocationEvent.lastevent = None
1995        self._lastx, self._lasty = None, None
1996
1997    def enter_notify_event(self, guiEvent=None, xy=None):
1998        """
1999        Backend derived classes should call this function when entering
2000        canvas
2001
2002        Parameters
2003        ----------
2004        guiEvent
2005            the native UI event that generated the mpl event
2006        xy : tuple of 2 scalars
2007            the coordinate location of the pointer when the canvas is
2008            entered
2009
2010        """
2011        if xy is not None:
2012            x, y = xy
2013            self._lastx, self._lasty = x, y
2014
2015        event = Event('figure_enter_event', self, guiEvent)
2016        self.callbacks.process('figure_enter_event', event)
2017
2018    @cbook.deprecated("2.1")
2019    def idle_event(self, guiEvent=None):
2020        """Called when GUI is idle."""
2021        s = 'idle_event'
2022        event = IdleEvent(s, self, guiEvent=guiEvent)
2023        self.callbacks.process(s, event)
2024
2025    def grab_mouse(self, ax):
2026        """
2027        Set the child axes which are currently grabbing the mouse events.
2028        Usually called by the widgets themselves.
2029        It is an error to call this if the mouse is already grabbed by
2030        another axes.
2031        """
2032        if self.mouse_grabber not in (None, ax):
2033            raise RuntimeError("Another Axes already grabs mouse input")
2034        self.mouse_grabber = ax
2035
2036    def release_mouse(self, ax):
2037        """
2038        Release the mouse grab held by the axes, ax.
2039        Usually called by the widgets.
2040        It is ok to call this even if you ax doesn't have the mouse
2041        grab currently.
2042        """
2043        if self.mouse_grabber is ax:
2044            self.mouse_grabber = None
2045
2046    def draw(self, *args, **kwargs):
2047        """Render the :class:`~matplotlib.figure.Figure`."""
2048
2049    def draw_idle(self, *args, **kwargs):
2050        """
2051        :meth:`draw` only if idle; defaults to draw but backends can override
2052        """
2053        if not self._is_idle_drawing:
2054            with self._idle_draw_cntx():
2055                self.draw(*args, **kwargs)
2056
2057    def draw_cursor(self, event):
2058        """
2059        Draw a cursor in the event.axes if inaxes is not None.  Use
2060        native GUI drawing for efficiency if possible
2061        """
2062
2063    def get_width_height(self):
2064        """
2065        Return the figure width and height in points or pixels
2066        (depending on the backend), truncated to integers
2067        """
2068        return int(self.figure.bbox.width), int(self.figure.bbox.height)
2069
2070    @classmethod
2071    def get_supported_filetypes(cls):
2072        """Return dict of savefig file formats supported by this backend"""
2073        return cls.filetypes
2074
2075    @classmethod
2076    def get_supported_filetypes_grouped(cls):
2077        """Return a dict of savefig file formats supported by this backend,
2078        where the keys are a file type name, such as 'Joint Photographic
2079        Experts Group', and the values are a list of filename extensions used
2080        for that filetype, such as ['jpg', 'jpeg']."""
2081        groupings = {}
2082        for ext, name in six.iteritems(cls.filetypes):
2083            groupings.setdefault(name, []).append(ext)
2084            groupings[name].sort()
2085        return groupings
2086
2087    def _get_output_canvas(self, fmt):
2088        """
2089        Return a canvas suitable for saving figures to a specified file format.
2090
2091        If necessary, this function will switch to a registered backend that
2092        supports the format.
2093        """
2094        method_name = 'print_%s' % fmt
2095        # Return the current canvas if it supports the requested format.
2096        if hasattr(self, method_name):
2097            return self
2098        # Return a default canvas for the requested format, if it exists.
2099        canvas_class = get_registered_canvas_class(fmt)
2100        if canvas_class:
2101            return self.switch_backends(canvas_class)
2102        # Else report error for unsupported format.
2103        raise ValueError(
2104            "Format {!r} is not supported (supported formats: {})"
2105            .format(fmt, ", ".join(sorted(self.get_supported_filetypes()))))
2106
2107    def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
2108                     orientation='portrait', format=None, **kwargs):
2109        """
2110        Render the figure to hardcopy. Set the figure patch face and edge
2111        colors.  This is useful because some of the GUIs have a gray figure
2112        face color background and you'll probably want to override this on
2113        hardcopy.
2114
2115        Parameters
2116        ----------
2117        filename
2118            can also be a file object on image backends
2119
2120        orientation : {'landscape', 'portrait'}, optional
2121            only currently applies to PostScript printing.
2122
2123        dpi : scalar, optional
2124            the dots per inch to save the figure in; if None, use savefig.dpi
2125
2126        facecolor : color spec or None, optional
2127            the facecolor of the figure; if None, defaults to savefig.facecolor
2128
2129        edgecolor : color spec or None, optional
2130            the edgecolor of the figure; if None, defaults to savefig.edgecolor
2131
2132        format : str, optional
2133            when set, forcibly set the file format to save to
2134
2135        bbox_inches : str or `~matplotlib.transforms.Bbox`, optional
2136            Bbox in inches. Only the given portion of the figure is
2137            saved. If 'tight', try to figure out the tight bbox of
2138            the figure. If None, use savefig.bbox
2139
2140        pad_inches : scalar, optional
2141            Amount of padding around the figure when bbox_inches is
2142            'tight'. If None, use savefig.pad_inches
2143
2144        bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional
2145            A list of extra artists that will be considered when the
2146            tight bbox is calculated.
2147
2148        """
2149        self._is_saving = True
2150        # Remove the figure manager, if any, to avoid resizing the GUI widget.
2151        # Having *no* manager and a *None* manager are currently different (see
2152        # Figure.show); should probably be normalized to None at some point.
2153        _no_manager = object()
2154        if hasattr(self, 'manager'):
2155            manager = self.manager
2156            del self.manager
2157        else:
2158            manager = _no_manager
2159
2160        if format is None:
2161            # get format from filename, or from backend's default filetype
2162            if isinstance(filename, getattr(os, "PathLike", ())):
2163                filename = os.fspath(filename)
2164            if isinstance(filename, six.string_types):
2165                format = os.path.splitext(filename)[1][1:]
2166            if format is None or format == '':
2167                format = self.get_default_filetype()
2168                if isinstance(filename, six.string_types):
2169                    filename = filename.rstrip('.') + '.' + format
2170        format = format.lower()
2171
2172        # get canvas object and print method for format
2173        canvas = self._get_output_canvas(format)
2174        print_method = getattr(canvas, 'print_%s' % format)
2175
2176        if dpi is None:
2177            dpi = rcParams['savefig.dpi']
2178
2179        if dpi == 'figure':
2180            dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)
2181
2182        if facecolor is None:
2183            facecolor = rcParams['savefig.facecolor']
2184        if edgecolor is None:
2185            edgecolor = rcParams['savefig.edgecolor']
2186
2187        origDPI = self.figure.dpi
2188        origfacecolor = self.figure.get_facecolor()
2189        origedgecolor = self.figure.get_edgecolor()
2190
2191        self.figure.dpi = dpi
2192        self.figure.set_facecolor(facecolor)
2193        self.figure.set_edgecolor(edgecolor)
2194
2195        bbox_inches = kwargs.pop("bbox_inches", None)
2196        if bbox_inches is None:
2197            bbox_inches = rcParams['savefig.bbox']
2198
2199        if bbox_inches:
2200            # call adjust_bbox to save only the given area
2201            if bbox_inches == "tight":
2202                # When bbox_inches == "tight", it saves the figure twice.  The
2203                # first save command (to a BytesIO) is just to estimate the
2204                # bounding box of the figure.
2205                result = print_method(
2206                    io.BytesIO(),
2207                    dpi=dpi,
2208                    facecolor=facecolor,
2209                    edgecolor=edgecolor,
2210                    orientation=orientation,
2211                    dryrun=True,
2212                    **kwargs)
2213                renderer = self.figure._cachedRenderer
2214                bbox_inches = self.figure.get_tightbbox(renderer)
2215
2216                bbox_artists = kwargs.pop("bbox_extra_artists", None)
2217                if bbox_artists is None:
2218                    bbox_artists = self.figure.get_default_bbox_extra_artists()
2219
2220                bbox_filtered = []
2221                for a in bbox_artists:
2222                    bbox = a.get_window_extent(renderer)
2223                    if a.get_clip_on():
2224                        clip_box = a.get_clip_box()
2225                        if clip_box is not None:
2226                            bbox = Bbox.intersection(bbox, clip_box)
2227                        clip_path = a.get_clip_path()
2228                        if clip_path is not None and bbox is not None:
2229                            clip_path = clip_path.get_fully_transformed_path()
2230                            bbox = Bbox.intersection(bbox,
2231                                                     clip_path.get_extents())
2232                    if bbox is not None and (bbox.width != 0 or
2233                                             bbox.height != 0):
2234                        bbox_filtered.append(bbox)
2235
2236                if bbox_filtered:
2237                    _bbox = Bbox.union(bbox_filtered)
2238                    trans = Affine2D().scale(1.0 / self.figure.dpi)
2239                    bbox_extra = TransformedBbox(_bbox, trans)
2240                    bbox_inches = Bbox.union([bbox_inches, bbox_extra])
2241
2242                pad = kwargs.pop("pad_inches", None)
2243                if pad is None:
2244                    pad = rcParams['savefig.pad_inches']
2245
2246                bbox_inches = bbox_inches.padded(pad)
2247
2248            restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches,
2249                                                  canvas.fixed_dpi)
2250
2251            _bbox_inches_restore = (bbox_inches, restore_bbox)
2252        else:
2253            _bbox_inches_restore = None
2254
2255        try:
2256            result = print_method(
2257                filename,
2258                dpi=dpi,
2259                facecolor=facecolor,
2260                edgecolor=edgecolor,
2261                orientation=orientation,
2262                bbox_inches_restore=_bbox_inches_restore,
2263                **kwargs)
2264        finally:
2265            if bbox_inches and restore_bbox:
2266                restore_bbox()
2267
2268            self.figure.dpi = origDPI
2269            self.figure.set_facecolor(origfacecolor)
2270            self.figure.set_edgecolor(origedgecolor)
2271            self.figure.set_canvas(self)
2272            if manager is not _no_manager:
2273                self.manager = manager
2274            self._is_saving = False
2275        return result
2276
2277    @classmethod
2278    def get_default_filetype(cls):
2279        """
2280        Get the default savefig file format as specified in rcParam
2281        ``savefig.format``. Returned string excludes period. Overridden
2282        in backends that only support a single file type.
2283        """
2284        return rcParams['savefig.format']
2285
2286    def get_window_title(self):
2287        """
2288        Get the title text of the window containing the figure.
2289        Return None if there is no window (e.g., a PS backend).
2290        """
2291        if hasattr(self, "manager"):
2292            return self.manager.get_window_title()
2293
2294    def set_window_title(self, title):
2295        """
2296        Set the title text of the window containing the figure.  Note that
2297        this has no effect if there is no window (e.g., a PS backend).
2298        """
2299        if hasattr(self, "manager"):
2300            self.manager.set_window_title(title)
2301
2302    def get_default_filename(self):
2303        """
2304        Return a string, which includes extension, suitable for use as
2305        a default filename.
2306        """
2307        default_basename = self.get_window_title() or 'image'
2308        default_basename = default_basename.replace(' ', '_')
2309        default_filetype = self.get_default_filetype()
2310        default_filename = default_basename + '.' + default_filetype
2311
2312        save_dir = os.path.expanduser(rcParams['savefig.directory'])
2313
2314        # ensure non-existing filename in save dir
2315        i = 1
2316        while os.path.isfile(os.path.join(save_dir, default_filename)):
2317            # attach numerical count to basename
2318            default_filename = (
2319                '{}-{}.{}'.format(default_basename, i, default_filetype))
2320            i += 1
2321
2322        return default_filename
2323
2324    def switch_backends(self, FigureCanvasClass):
2325        """
2326        Instantiate an instance of FigureCanvasClass
2327
2328        This is used for backend switching, e.g., to instantiate a
2329        FigureCanvasPS from a FigureCanvasGTK.  Note, deep copying is
2330        not done, so any changes to one of the instances (e.g., setting
2331        figure size or line props), will be reflected in the other
2332        """
2333        newCanvas = FigureCanvasClass(self.figure)
2334        newCanvas._is_saving = self._is_saving
2335        return newCanvas
2336
2337    def mpl_connect(self, s, func):
2338        """
2339        Connect event with string *s* to *func*.  The signature of *func* is::
2340
2341          def func(event)
2342
2343        where event is a :class:`matplotlib.backend_bases.Event`.  The
2344        following events are recognized
2345
2346        - 'button_press_event'
2347        - 'button_release_event'
2348        - 'draw_event'
2349        - 'key_press_event'
2350        - 'key_release_event'
2351        - 'motion_notify_event'
2352        - 'pick_event'
2353        - 'resize_event'
2354        - 'scroll_event'
2355        - 'figure_enter_event',
2356        - 'figure_leave_event',
2357        - 'axes_enter_event',
2358        - 'axes_leave_event'
2359        - 'close_event'
2360
2361        For the location events (button and key press/release), if the
2362        mouse is over the axes, the variable ``event.inaxes`` will be
2363        set to the :class:`~matplotlib.axes.Axes` the event occurs is
2364        over, and additionally, the variables ``event.xdata`` and
2365        ``event.ydata`` will be defined.  This is the mouse location
2366        in data coords.  See
2367        :class:`~matplotlib.backend_bases.KeyEvent` and
2368        :class:`~matplotlib.backend_bases.MouseEvent` for more info.
2369
2370        Return value is a connection id that can be used with
2371        :meth:`~matplotlib.backend_bases.Event.mpl_disconnect`.
2372
2373        Examples
2374        --------
2375        Usage::
2376
2377            def on_press(event):
2378                print('you pressed', event.button, event.xdata, event.ydata)
2379
2380            cid = canvas.mpl_connect('button_press_event', on_press)
2381
2382        """
2383        if s == 'idle_event':
2384            cbook.warn_deprecated(1.5,
2385                "idle_event is only implemented for the wx backend, and will "
2386                "be removed in matplotlib 2.1. Use the animations module "
2387                "instead.")
2388
2389        return self.callbacks.connect(s, func)
2390
2391    def mpl_disconnect(self, cid):
2392        """
2393        Disconnect callback id cid
2394
2395        Examples
2396        --------
2397        Usage::
2398
2399            cid = canvas.mpl_connect('button_press_event', on_press)
2400            #...later
2401            canvas.mpl_disconnect(cid)
2402        """
2403        return self.callbacks.disconnect(cid)
2404
2405    def new_timer(self, *args, **kwargs):
2406        """
2407        Creates a new backend-specific subclass of
2408        :class:`backend_bases.Timer`. This is useful for getting periodic
2409        events through the backend's native event loop. Implemented only for
2410        backends with GUIs.
2411
2412        Other Parameters
2413        ----------------
2414        interval : scalar
2415            Timer interval in milliseconds
2416
2417        callbacks : List[Tuple[callable, Tuple, Dict]]
2418            Sequence of (func, args, kwargs) where ``func(*args, **kwargs)``
2419            will be executed by the timer every *interval*.
2420
2421            callbacks which return ``False`` or ``0`` will be removed from the
2422            timer.
2423
2424        Examples
2425        --------
2426
2427        >>> timer = fig.canvas.new_timer(callbacks=[(f1, (1, ), {'a': 3}),])
2428
2429        """
2430        return TimerBase(*args, **kwargs)
2431
2432    def flush_events(self):
2433        """Flush the GUI events for the figure.
2434
2435        Interactive backends need to reimplement this method.
2436        """
2437
2438    def start_event_loop(self, timeout=0):
2439        """Start a blocking event loop.
2440
2441        Such an event loop is used by interactive functions, such as `ginput`
2442        and `waitforbuttonpress`, to wait for events.
2443
2444        The event loop blocks until a callback function triggers
2445        `stop_event_loop`, or *timeout* is reached.
2446
2447        If *timeout* is negative, never timeout.
2448
2449        Only interactive backends need to reimplement this method and it relies
2450        on `flush_events` being properly implemented.
2451
2452        Interactive backends should implement this in a more native way.
2453        """
2454        if timeout <= 0:
2455            timeout = np.inf
2456        timestep = 0.01
2457        counter = 0
2458        self._looping = True
2459        while self._looping and counter * timestep < timeout:
2460            self.flush_events()
2461            time.sleep(timestep)
2462            counter += 1
2463
2464    def stop_event_loop(self):
2465        """Stop the current blocking event loop.
2466
2467        Interactive backends need to reimplement this to match
2468        `start_event_loop`
2469        """
2470        self._looping = False
2471
2472    start_event_loop_default = cbook.deprecated(
2473        "2.1", name="start_event_loop_default")(start_event_loop)
2474    stop_event_loop_default = cbook.deprecated(
2475        "2.1", name="stop_event_loop_default")(stop_event_loop)
2476
2477
2478def key_press_handler(event, canvas, toolbar=None):
2479    """
2480    Implement the default mpl key bindings for the canvas and toolbar
2481    described at :ref:`key-event-handling`
2482
2483    Parameters
2484    ----------
2485    event : :class:`KeyEvent`
2486        a key press/release event
2487    canvas : :class:`FigureCanvasBase`
2488        the backend-specific canvas instance
2489    toolbar : :class:`NavigationToolbar2`
2490        the navigation cursor toolbar
2491
2492    """
2493    # these bindings happen whether you are over an axes or not
2494
2495    if event.key is None:
2496        return
2497
2498    # Load key-mappings from your matplotlibrc file.
2499    fullscreen_keys = rcParams['keymap.fullscreen']
2500    home_keys = rcParams['keymap.home']
2501    back_keys = rcParams['keymap.back']
2502    forward_keys = rcParams['keymap.forward']
2503    pan_keys = rcParams['keymap.pan']
2504    zoom_keys = rcParams['keymap.zoom']
2505    save_keys = rcParams['keymap.save']
2506    quit_keys = rcParams['keymap.quit']
2507    grid_keys = rcParams['keymap.grid']
2508    grid_minor_keys = rcParams['keymap.grid_minor']
2509    toggle_yscale_keys = rcParams['keymap.yscale']
2510    toggle_xscale_keys = rcParams['keymap.xscale']
2511    all_keys = rcParams['keymap.all_axes']
2512
2513    # toggle fullscreen mode ('f', 'ctrl + f')
2514    if event.key in fullscreen_keys:
2515        try:
2516            canvas.manager.full_screen_toggle()
2517        except AttributeError:
2518            pass
2519
2520    # quit the figure (default key 'ctrl+w')
2521    if event.key in quit_keys:
2522        Gcf.destroy_fig(canvas.figure)
2523
2524    if toolbar is not None:
2525        # home or reset mnemonic  (default key 'h', 'home' and 'r')
2526        if event.key in home_keys:
2527            toolbar.home()
2528        # forward / backward keys to enable left handed quick navigation
2529        # (default key for backward: 'left', 'backspace' and 'c')
2530        elif event.key in back_keys:
2531            toolbar.back()
2532        # (default key for forward: 'right' and 'v')
2533        elif event.key in forward_keys:
2534            toolbar.forward()
2535        # pan mnemonic (default key 'p')
2536        elif event.key in pan_keys:
2537            toolbar.pan()
2538            toolbar._set_cursor(event)
2539        # zoom mnemonic (default key 'o')
2540        elif event.key in zoom_keys:
2541            toolbar.zoom()
2542            toolbar._set_cursor(event)
2543        # saving current figure (default key 's')
2544        elif event.key in save_keys:
2545            toolbar.save_figure()
2546
2547    if event.inaxes is None:
2548        return
2549
2550    # these bindings require the mouse to be over an axes to trigger
2551    def _get_uniform_gridstate(ticks):
2552        # Return True/False if all grid lines are on or off, None if they are
2553        # not all in the same state.
2554        if all(tick.gridOn for tick in ticks):
2555            return True
2556        elif not any(tick.gridOn for tick in ticks):
2557            return False
2558        else:
2559            return None
2560
2561    ax = event.inaxes
2562    # toggle major grids in current axes (default key 'g')
2563    # Both here and below (for 'G'), we do nothing if *any* grid (major or
2564    # minor, x or y) is not in a uniform state, to avoid messing up user
2565    # customization.
2566    if (event.key in grid_keys
2567            # Exclude minor grids not in a uniform state.
2568            and None not in [_get_uniform_gridstate(ax.xaxis.minorTicks),
2569                             _get_uniform_gridstate(ax.yaxis.minorTicks)]):
2570        x_state = _get_uniform_gridstate(ax.xaxis.majorTicks)
2571        y_state = _get_uniform_gridstate(ax.yaxis.majorTicks)
2572        cycle = [(False, False), (True, False), (True, True), (False, True)]
2573        try:
2574            x_state, y_state = (
2575                cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
2576        except ValueError:
2577            # Exclude major grids not in a uniform state.
2578            pass
2579        else:
2580            # If turning major grids off, also turn minor grids off.
2581            ax.grid(x_state, which="major" if x_state else "both", axis="x")
2582            ax.grid(y_state, which="major" if y_state else "both", axis="y")
2583            canvas.draw_idle()
2584    # toggle major and minor grids in current axes (default key 'G')
2585    if (event.key in grid_minor_keys
2586            # Exclude major grids not in a uniform state.
2587            and None not in [_get_uniform_gridstate(ax.xaxis.majorTicks),
2588                             _get_uniform_gridstate(ax.yaxis.majorTicks)]):
2589        x_state = _get_uniform_gridstate(ax.xaxis.minorTicks)
2590        y_state = _get_uniform_gridstate(ax.yaxis.minorTicks)
2591        cycle = [(False, False), (True, False), (True, True), (False, True)]
2592        try:
2593            x_state, y_state = (
2594                cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
2595        except ValueError:
2596            # Exclude minor grids not in a uniform state.
2597            pass
2598        else:
2599            ax.grid(x_state, which="both", axis="x")
2600            ax.grid(y_state, which="both", axis="y")
2601            canvas.draw_idle()
2602    # toggle scaling of y-axes between 'log and 'linear' (default key 'l')
2603    elif event.key in toggle_yscale_keys:
2604        scale = ax.get_yscale()
2605        if scale == 'log':
2606            ax.set_yscale('linear')
2607            ax.figure.canvas.draw_idle()
2608        elif scale == 'linear':
2609            try:
2610                ax.set_yscale('log')
2611            except ValueError as exc:
2612                warnings.warn(str(exc))
2613                ax.set_yscale('linear')
2614            ax.figure.canvas.draw_idle()
2615    # toggle scaling of x-axes between 'log and 'linear' (default key 'k')
2616    elif event.key in toggle_xscale_keys:
2617        scalex = ax.get_xscale()
2618        if scalex == 'log':
2619            ax.set_xscale('linear')
2620            ax.figure.canvas.draw_idle()
2621        elif scalex == 'linear':
2622            try:
2623                ax.set_xscale('log')
2624            except ValueError as exc:
2625                warnings.warn(str(exc))
2626                ax.set_xscale('linear')
2627            ax.figure.canvas.draw_idle()
2628
2629    elif (event.key.isdigit() and event.key != '0') or event.key in all_keys:
2630        # keys in list 'all' enables all axes (default key 'a'),
2631        # otherwise if key is a number only enable this particular axes
2632        # if it was the axes, where the event was raised
2633        if not (event.key in all_keys):
2634            n = int(event.key) - 1
2635        for i, a in enumerate(canvas.figure.get_axes()):
2636            # consider axes, in which the event was raised
2637            # FIXME: Why only this axes?
2638            if event.x is not None and event.y is not None \
2639                    and a.in_axes(event):
2640                if event.key in all_keys:
2641                    a.set_navigate(True)
2642                else:
2643                    a.set_navigate(i == n)
2644
2645
2646class NonGuiException(Exception):
2647    pass
2648
2649
2650class FigureManagerBase(object):
2651    """
2652    Helper class for pyplot mode, wraps everything up into a neat bundle
2653
2654    Attributes
2655    ----------
2656    canvas : :class:`FigureCanvasBase`
2657        The backend-specific canvas instance
2658
2659    num : int or str
2660        The figure number
2661
2662    key_press_handler_id : int
2663        The default key handler cid, when using the toolmanager.  Can be used
2664        to disable default key press handling ::
2665
2666            figure.canvas.mpl_disconnect(
2667                figure.canvas.manager.key_press_handler_id)
2668    """
2669    def __init__(self, canvas, num):
2670        self.canvas = canvas
2671        canvas.manager = self  # store a pointer to parent
2672        self.num = num
2673
2674        self.key_press_handler_id = None
2675        if rcParams['toolbar'] != 'toolmanager':
2676            self.key_press_handler_id = self.canvas.mpl_connect(
2677                'key_press_event',
2678                self.key_press)
2679
2680    def show(self):
2681        """
2682        For GUI backends, show the figure window and redraw.
2683        For non-GUI backends, raise an exception to be caught
2684        by :meth:`~matplotlib.figure.Figure.show`, for an
2685        optional warning.
2686        """
2687        raise NonGuiException()
2688
2689    def destroy(self):
2690        pass
2691
2692    def full_screen_toggle(self):
2693        pass
2694
2695    def resize(self, w, h):
2696        """"For GUI backends, resize the window (in pixels)."""
2697
2698    def key_press(self, event):
2699        """
2700        Implement the default mpl key bindings defined at
2701        :ref:`key-event-handling`
2702        """
2703        if rcParams['toolbar'] != 'toolmanager':
2704            key_press_handler(event, self.canvas, self.canvas.toolbar)
2705
2706    @cbook.deprecated("2.2")
2707    def show_popup(self, msg):
2708        """Display message in a popup -- GUI only."""
2709
2710    def get_window_title(self):
2711        """Get the title text of the window containing the figure.
2712
2713        Return None for non-GUI (e.g., PS) backends.
2714        """
2715        return 'image'
2716
2717    def set_window_title(self, title):
2718        """Set the title text of the window containing the figure.
2719
2720        This has no effect for non-GUI (e.g., PS) backends.
2721        """
2722
2723
2724cursors = tools.cursors
2725
2726
2727class NavigationToolbar2(object):
2728    """
2729    Base class for the navigation cursor, version 2
2730
2731    backends must implement a canvas that handles connections for
2732    'button_press_event' and 'button_release_event'.  See
2733    :meth:`FigureCanvasBase.mpl_connect` for more information
2734
2735
2736    They must also define
2737
2738      :meth:`save_figure`
2739         save the current figure
2740
2741      :meth:`set_cursor`
2742         if you want the pointer icon to change
2743
2744      :meth:`_init_toolbar`
2745         create your toolbar widget
2746
2747      :meth:`draw_rubberband` (optional)
2748         draw the zoom to rect "rubberband" rectangle
2749
2750      :meth:`press`  (optional)
2751         whenever a mouse button is pressed, you'll be notified with
2752         the event
2753
2754      :meth:`release` (optional)
2755         whenever a mouse button is released, you'll be notified with
2756         the event
2757
2758      :meth:`set_message` (optional)
2759         display message
2760
2761      :meth:`set_history_buttons` (optional)
2762         you can change the history back / forward buttons to
2763         indicate disabled / enabled state.
2764
2765    That's it, we'll do the rest!
2766    """
2767
2768    # list of toolitems to add to the toolbar, format is:
2769    # (
2770    #   text, # the text of the button (often not visible to users)
2771    #   tooltip_text, # the tooltip shown on hover (where possible)
2772    #   image_file, # name of the image for the button (without the extension)
2773    #   name_of_method, # name of the method in NavigationToolbar2 to call
2774    # )
2775    toolitems = (
2776        ('Home', 'Reset original view', 'home', 'home'),
2777        ('Back', 'Back to  previous view', 'back', 'back'),
2778        ('Forward', 'Forward to next view', 'forward', 'forward'),
2779        (None, None, None, None),
2780        ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
2781        ('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),
2782        ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'),
2783        (None, None, None, None),
2784        ('Save', 'Save the figure', 'filesave', 'save_figure'),
2785      )
2786
2787    def __init__(self, canvas):
2788        self.canvas = canvas
2789        canvas.toolbar = self
2790        self._nav_stack = cbook.Stack()
2791        self._xypress = None  # the location and axis info at the time
2792                              # of the press
2793        self._idPress = None
2794        self._idRelease = None
2795        self._active = None
2796        # This cursor will be set after the initial draw.
2797        self._lastCursor = cursors.POINTER
2798        self._init_toolbar()
2799        self._idDrag = self.canvas.mpl_connect(
2800            'motion_notify_event', self.mouse_move)
2801
2802        self._ids_zoom = []
2803        self._zoom_mode = None
2804
2805        self._button_pressed = None  # determined by the button pressed
2806                                     # at start
2807
2808        self.mode = ''  # a mode string for the status bar
2809        self.set_history_buttons()
2810
2811    def set_message(self, s):
2812        """Display a message on toolbar or in status bar."""
2813
2814    def back(self, *args):
2815        """move back up the view lim stack"""
2816        self._nav_stack.back()
2817        self.set_history_buttons()
2818        self._update_view()
2819
2820    @cbook.deprecated("2.1", alternative="canvas.draw_idle")
2821    def dynamic_update(self):
2822        self.canvas.draw_idle()
2823
2824    def draw_rubberband(self, event, x0, y0, x1, y1):
2825        """Draw a rectangle rubberband to indicate zoom limits.
2826
2827        Note that it is not guaranteed that ``x0 <= x1`` and ``y0 <= y1``.
2828        """
2829
2830    def remove_rubberband(self):
2831        """Remove the rubberband."""
2832
2833    def forward(self, *args):
2834        """Move forward in the view lim stack."""
2835        self._nav_stack.forward()
2836        self.set_history_buttons()
2837        self._update_view()
2838
2839    def home(self, *args):
2840        """Restore the original view."""
2841        self._nav_stack.home()
2842        self.set_history_buttons()
2843        self._update_view()
2844
2845    def _init_toolbar(self):
2846        """
2847        This is where you actually build the GUI widgets (called by
2848        __init__).  The icons ``home.xpm``, ``back.xpm``, ``forward.xpm``,
2849        ``hand.xpm``, ``zoom_to_rect.xpm`` and ``filesave.xpm`` are standard
2850        across backends (there are ppm versions in CVS also).
2851
2852        You just need to set the callbacks
2853
2854        home         : self.home
2855        back         : self.back
2856        forward      : self.forward
2857        hand         : self.pan
2858        zoom_to_rect : self.zoom
2859        filesave     : self.save_figure
2860
2861        You only need to define the last one - the others are in the base
2862        class implementation.
2863
2864        """
2865        raise NotImplementedError
2866
2867    def _set_cursor(self, event):
2868        if not event.inaxes or not self._active:
2869            if self._lastCursor != cursors.POINTER:
2870                self.set_cursor(cursors.POINTER)
2871                self._lastCursor = cursors.POINTER
2872        else:
2873            if (self._active == 'ZOOM'
2874                    and self._lastCursor != cursors.SELECT_REGION):
2875                self.set_cursor(cursors.SELECT_REGION)
2876                self._lastCursor = cursors.SELECT_REGION
2877            elif (self._active == 'PAN' and
2878                  self._lastCursor != cursors.MOVE):
2879                self.set_cursor(cursors.MOVE)
2880                self._lastCursor = cursors.MOVE
2881
2882    def mouse_move(self, event):
2883        self._set_cursor(event)
2884
2885        if event.inaxes and event.inaxes.get_navigate():
2886
2887            try:
2888                s = event.inaxes.format_coord(event.xdata, event.ydata)
2889            except (ValueError, OverflowError):
2890                pass
2891            else:
2892                artists = [a for a in event.inaxes.mouseover_set
2893                           if a.contains(event) and a.get_visible()]
2894
2895                if artists:
2896                    a = cbook._topmost_artist(artists)
2897                    if a is not event.inaxes.patch:
2898                        data = a.get_cursor_data(event)
2899                        if data is not None:
2900                            s += ' [%s]' % a.format_cursor_data(data)
2901
2902                if len(self.mode):
2903                    self.set_message('%s, %s' % (self.mode, s))
2904                else:
2905                    self.set_message(s)
2906        else:
2907            self.set_message(self.mode)
2908
2909    def pan(self, *args):
2910        """Activate the pan/zoom tool. pan with left button, zoom with right"""
2911        # set the pointer icon and button press funcs to the
2912        # appropriate callbacks
2913
2914        if self._active == 'PAN':
2915            self._active = None
2916        else:
2917            self._active = 'PAN'
2918        if self._idPress is not None:
2919            self._idPress = self.canvas.mpl_disconnect(self._idPress)
2920            self.mode = ''
2921
2922        if self._idRelease is not None:
2923            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
2924            self.mode = ''
2925
2926        if self._active:
2927            self._idPress = self.canvas.mpl_connect(
2928                'button_press_event', self.press_pan)
2929            self._idRelease = self.canvas.mpl_connect(
2930                'button_release_event', self.release_pan)
2931            self.mode = 'pan/zoom'
2932            self.canvas.widgetlock(self)
2933        else:
2934            self.canvas.widgetlock.release(self)
2935
2936        for a in self.canvas.figure.get_axes():
2937            a.set_navigate_mode(self._active)
2938
2939        self.set_message(self.mode)
2940
2941    def press(self, event):
2942        """Called whenever a mouse button is pressed."""
2943
2944    def press_pan(self, event):
2945        """Callback for mouse button press in pan/zoom mode."""
2946
2947        if event.button == 1:
2948            self._button_pressed = 1
2949        elif event.button == 3:
2950            self._button_pressed = 3
2951        else:
2952            self._button_pressed = None
2953            return
2954
2955        if self._nav_stack() is None:
2956            # set the home button to this view
2957            self.push_current()
2958
2959        x, y = event.x, event.y
2960        self._xypress = []
2961        for i, a in enumerate(self.canvas.figure.get_axes()):
2962            if (x is not None and y is not None and a.in_axes(event) and
2963                    a.get_navigate() and a.can_pan()):
2964                a.start_pan(x, y, event.button)
2965                self._xypress.append((a, i))
2966                self.canvas.mpl_disconnect(self._idDrag)
2967                self._idDrag = self.canvas.mpl_connect('motion_notify_event',
2968                                                       self.drag_pan)
2969
2970        self.press(event)
2971
2972    def press_zoom(self, event):
2973        """Callback for mouse button press in zoom to rect mode."""
2974        # If we're already in the middle of a zoom, pressing another
2975        # button works to "cancel"
2976        if self._ids_zoom != []:
2977            for zoom_id in self._ids_zoom:
2978                self.canvas.mpl_disconnect(zoom_id)
2979            self.release(event)
2980            self.draw()
2981            self._xypress = None
2982            self._button_pressed = None
2983            self._ids_zoom = []
2984            return
2985
2986        if event.button == 1:
2987            self._button_pressed = 1
2988        elif event.button == 3:
2989            self._button_pressed = 3
2990        else:
2991            self._button_pressed = None
2992            return
2993
2994        if self._nav_stack() is None:
2995            # set the home button to this view
2996            self.push_current()
2997
2998        x, y = event.x, event.y
2999        self._xypress = []
3000        for i, a in enumerate(self.canvas.figure.get_axes()):
3001            if (x is not None and y is not None and a.in_axes(event) and
3002                    a.get_navigate() and a.can_zoom()):
3003                self._xypress.append((x, y, a, i, a._get_view()))
3004
3005        id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom)
3006        id2 = self.canvas.mpl_connect('key_press_event',
3007                                      self._switch_on_zoom_mode)
3008        id3 = self.canvas.mpl_connect('key_release_event',
3009                                      self._switch_off_zoom_mode)
3010
3011        self._ids_zoom = id1, id2, id3
3012        self._zoom_mode = event.key
3013
3014        self.press(event)
3015
3016    def _switch_on_zoom_mode(self, event):
3017        self._zoom_mode = event.key
3018        self.mouse_move(event)
3019
3020    def _switch_off_zoom_mode(self, event):
3021        self._zoom_mode = None
3022        self.mouse_move(event)
3023
3024    def push_current(self):
3025        """Push the current view limits and position onto the stack."""
3026        self._nav_stack.push(
3027            WeakKeyDictionary(
3028                {ax: (ax._get_view(),
3029                      # Store both the original and modified positions.
3030                      (ax.get_position(True).frozen(),
3031                       ax.get_position().frozen()))
3032                 for ax in self.canvas.figure.axes}))
3033        self.set_history_buttons()
3034
3035    def release(self, event):
3036        """Callback for mouse button release."""
3037
3038    def release_pan(self, event):
3039        """Callback for mouse button release in pan/zoom mode."""
3040
3041        if self._button_pressed is None:
3042            return
3043        self.canvas.mpl_disconnect(self._idDrag)
3044        self._idDrag = self.canvas.mpl_connect(
3045            'motion_notify_event', self.mouse_move)
3046        for a, ind in self._xypress:
3047            a.end_pan()
3048        if not self._xypress:
3049            return
3050        self._xypress = []
3051        self._button_pressed = None
3052        self.push_current()
3053        self.release(event)
3054        self.draw()
3055
3056    def drag_pan(self, event):
3057        """Callback for dragging in pan/zoom mode."""
3058        for a, ind in self._xypress:
3059            #safer to use the recorded button at the press than current button:
3060            #multiple button can get pressed during motion...
3061            a.drag_pan(self._button_pressed, event.key, event.x, event.y)
3062        self.canvas.draw_idle()
3063
3064    def drag_zoom(self, event):
3065        """Callback for dragging in zoom mode."""
3066        if self._xypress:
3067            x, y = event.x, event.y
3068            lastx, lasty, a, ind, view = self._xypress[0]
3069            (x1, y1), (x2, y2) = np.clip(
3070                [[lastx, lasty], [x, y]], a.bbox.min, a.bbox.max)
3071            if self._zoom_mode == "x":
3072                y1, y2 = a.bbox.intervaly
3073            elif self._zoom_mode == "y":
3074                x1, x2 = a.bbox.intervalx
3075            self.draw_rubberband(event, x1, y1, x2, y2)
3076
3077    def release_zoom(self, event):
3078        """Callback for mouse button release in zoom to rect mode."""
3079        for zoom_id in self._ids_zoom:
3080            self.canvas.mpl_disconnect(zoom_id)
3081        self._ids_zoom = []
3082
3083        self.remove_rubberband()
3084
3085        if not self._xypress:
3086            return
3087
3088        last_a = []
3089
3090        for cur_xypress in self._xypress:
3091            x, y = event.x, event.y
3092            lastx, lasty, a, ind, view = cur_xypress
3093            # ignore singular clicks - 5 pixels is a threshold
3094            # allows the user to "cancel" a zoom action
3095            # by zooming by less than 5 pixels
3096            if ((abs(x - lastx) < 5 and self._zoom_mode!="y") or
3097                    (abs(y - lasty) < 5 and self._zoom_mode!="x")):
3098                self._xypress = None
3099                self.release(event)
3100                self.draw()
3101                return
3102
3103            # detect twinx,y axes and avoid double zooming
3104            twinx, twiny = False, False
3105            if last_a:
3106                for la in last_a:
3107                    if a.get_shared_x_axes().joined(a, la):
3108                        twinx = True
3109                    if a.get_shared_y_axes().joined(a, la):
3110                        twiny = True
3111            last_a.append(a)
3112
3113            if self._button_pressed == 1:
3114                direction = 'in'
3115            elif self._button_pressed == 3:
3116                direction = 'out'
3117            else:
3118                continue
3119
3120            a._set_view_from_bbox((lastx, lasty, x, y), direction,
3121                                  self._zoom_mode, twinx, twiny)
3122
3123        self.draw()
3124        self._xypress = None
3125        self._button_pressed = None
3126
3127        self._zoom_mode = None
3128
3129        self.push_current()
3130        self.release(event)
3131
3132    def draw(self):
3133        """Redraw the canvases, update the locators."""
3134        for a in self.canvas.figure.get_axes():
3135            xaxis = getattr(a, 'xaxis', None)
3136            yaxis = getattr(a, 'yaxis', None)
3137            locators = []
3138            if xaxis is not None:
3139                locators.append(xaxis.get_major_locator())
3140                locators.append(xaxis.get_minor_locator())
3141            if yaxis is not None:
3142                locators.append(yaxis.get_major_locator())
3143                locators.append(yaxis.get_minor_locator())
3144
3145            for loc in locators:
3146                loc.refresh()
3147        self.canvas.draw_idle()
3148
3149    def _update_view(self):
3150        """Update the viewlim and position from the view and
3151        position stack for each axes.
3152        """
3153        nav_info = self._nav_stack()
3154        if nav_info is None:
3155            return
3156        # Retrieve all items at once to avoid any risk of GC deleting an Axes
3157        # while in the middle of the loop below.
3158        items = list(nav_info.items())
3159        for ax, (view, (pos_orig, pos_active)) in items:
3160            ax._set_view(view)
3161            # Restore both the original and modified positions
3162            ax._set_position(pos_orig, 'original')
3163            ax._set_position(pos_active, 'active')
3164        self.canvas.draw_idle()
3165
3166    def save_figure(self, *args):
3167        """Save the current figure."""
3168        raise NotImplementedError
3169
3170    def set_cursor(self, cursor):
3171        """Set the current cursor to one of the :class:`Cursors` enums values.
3172
3173        If required by the backend, this method should trigger an update in
3174        the backend event loop after the cursor is set, as this method may be
3175        called e.g. before a long-running task during which the GUI is not
3176        updated.
3177        """
3178
3179    def update(self):
3180        """Reset the axes stack."""
3181        self._nav_stack.clear()
3182        self.set_history_buttons()
3183
3184    def zoom(self, *args):
3185        """Activate zoom to rect mode."""
3186        if self._active == 'ZOOM':
3187            self._active = None
3188        else:
3189            self._active = 'ZOOM'
3190
3191        if self._idPress is not None:
3192            self._idPress = self.canvas.mpl_disconnect(self._idPress)
3193            self.mode = ''
3194
3195        if self._idRelease is not None:
3196            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
3197            self.mode = ''
3198
3199        if self._active:
3200            self._idPress = self.canvas.mpl_connect('button_press_event',
3201                                                    self.press_zoom)
3202            self._idRelease = self.canvas.mpl_connect('button_release_event',
3203                                                      self.release_zoom)
3204            self.mode = 'zoom rect'
3205            self.canvas.widgetlock(self)
3206        else:
3207            self.canvas.widgetlock.release(self)
3208
3209        for a in self.canvas.figure.get_axes():
3210            a.set_navigate_mode(self._active)
3211
3212        self.set_message(self.mode)
3213
3214    def set_history_buttons(self):
3215        """Enable or disable the back/forward button."""
3216
3217
3218class ToolContainerBase(object):
3219    """
3220    Base class for all tool containers, e.g. toolbars.
3221
3222    Attributes
3223    ----------
3224    toolmanager : `ToolManager`
3225        The tools with which this `ToolContainer` wants to communicate.
3226    """
3227
3228    _icon_extension = '.png'
3229    """
3230    Toolcontainer button icon image format extension
3231
3232    **String**: Image extension
3233    """
3234
3235    def __init__(self, toolmanager):
3236        self.toolmanager = toolmanager
3237        self.toolmanager.toolmanager_connect('tool_removed_event',
3238                                             self._remove_tool_cbk)
3239
3240    def _tool_toggled_cbk(self, event):
3241        """
3242        Captures the 'tool_trigger_[name]'
3243
3244        This only gets used for toggled tools
3245        """
3246        self.toggle_toolitem(event.tool.name, event.tool.toggled)
3247
3248    def add_tool(self, tool, group, position=-1):
3249        """
3250        Adds a tool to this container
3251
3252        Parameters
3253        ----------
3254        tool : tool_like
3255            The tool to add, see `ToolManager.get_tool`.
3256        group : str
3257            The name of the group to add this tool to.
3258        position : int (optional)
3259            The position within the group to place this tool.  Defaults to end.
3260        """
3261        tool = self.toolmanager.get_tool(tool)
3262        image = self._get_image_filename(tool.image)
3263        toggle = getattr(tool, 'toggled', None) is not None
3264        self.add_toolitem(tool.name, group, position,
3265                          image, tool.description, toggle)
3266        if toggle:
3267            self.toolmanager.toolmanager_connect('tool_trigger_%s' % tool.name,
3268                                                 self._tool_toggled_cbk)
3269            # If initially toggled
3270            if tool.toggled:
3271                self.toggle_toolitem(tool.name, True)
3272
3273    def _remove_tool_cbk(self, event):
3274        """Captures the 'tool_removed_event' signal and removes the tool."""
3275        self.remove_toolitem(event.tool.name)
3276
3277    def _get_image_filename(self, image):
3278        """Find the image based on its name."""
3279        if not image:
3280            return None
3281
3282        basedir = os.path.join(rcParams['datapath'], 'images')
3283        possible_images = (
3284            image,
3285            image + self._icon_extension,
3286            os.path.join(basedir, image),
3287            os.path.join(basedir, image) + self._icon_extension)
3288
3289        for fname in possible_images:
3290            if os.path.isfile(fname):
3291                return fname
3292
3293    def trigger_tool(self, name):
3294        """
3295        Trigger the tool
3296
3297        Parameters
3298        ----------
3299        name : String
3300            Name (id) of the tool triggered from within the container
3301        """
3302        self.toolmanager.trigger_tool(name, sender=self)
3303
3304    def add_toolitem(self, name, group, position, image, description, toggle):
3305        """
3306        Add a toolitem to the container
3307
3308        This method must get implemented per backend
3309
3310        The callback associated with the button click event,
3311        must be **EXACTLY** `self.trigger_tool(name)`
3312
3313        Parameters
3314        ----------
3315        name : string
3316            Name of the tool to add, this gets used as the tool's ID and as the
3317            default label of the buttons
3318        group : String
3319            Name of the group that this tool belongs to
3320        position : Int
3321            Position of the tool within its group, if -1 it goes at the End
3322        image_file : String
3323            Filename of the image for the button or `None`
3324        description : String
3325            Description of the tool, used for the tooltips
3326        toggle : Bool
3327            * `True` : The button is a toggle (change the pressed/unpressed
3328              state between consecutive clicks)
3329            * `False` : The button is a normal button (returns to unpressed
3330              state after release)
3331        """
3332        raise NotImplementedError
3333
3334    def toggle_toolitem(self, name, toggled):
3335        """
3336        Toggle the toolitem without firing event
3337
3338        Parameters
3339        ----------
3340        name : String
3341            Id of the tool to toggle
3342        toggled : bool
3343            Whether to set this tool as toggled or not.
3344        """
3345        raise NotImplementedError
3346
3347    def remove_toolitem(self, name):
3348        """
3349        Remove a toolitem from the `ToolContainer`
3350
3351        This method must get implemented per backend
3352
3353        Called when `ToolManager` emits a `tool_removed_event`
3354
3355        Parameters
3356        ----------
3357        name : string
3358            Name of the tool to remove
3359        """
3360        raise NotImplementedError
3361
3362
3363class StatusbarBase(object):
3364    """Base class for the statusbar"""
3365    def __init__(self, toolmanager):
3366        self.toolmanager = toolmanager
3367        self.toolmanager.toolmanager_connect('tool_message_event',
3368                                             self._message_cbk)
3369
3370    def _message_cbk(self, event):
3371        """Captures the 'tool_message_event' and set the message"""
3372        self.set_message(event.message)
3373
3374    def set_message(self, s):
3375        """
3376        Display a message on toolbar or in status bar
3377
3378        Parameters
3379        ----------
3380        s : str
3381            Message text
3382        """
3383        pass
3384