diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index 2407b573c4aa..57e6d985af22 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -1,36 +1,41 @@ """ -Manage figures for pyplot interface. +Manage figures for the pyplot interface. """ import atexit +from collections import OrderedDict import gc class Gcf: """ - Singleton to manage a set of integer-numbered figures. + Singleton to maintain the relation between figures and their managers, and + keep track of and "active" figure and manager. - This class is never instantiated; it consists of two class - attributes (a list and a dictionary), and a set of static - methods that operate on those attributes, accessing them - directly as class attributes. + The canvas of a figure created through pyplot is associated with a figure + manager, which handles the interaction between the figure and the backend. + pyplot keeps track of figure managers using an identifier, the "figure + number" or "manager number" (which can actually be any hashable value); + this number is available as the :attr:`number` attribute of the manager. + + This class is never instantiated; it consists of an `OrderedDict` mapping + figure/manager numbers to managers, and a set of class methods that + manipulate this `OrderedDict`. Attributes ---------- - figs - dictionary of the form {*num*: *manager*, ...} - _activeQue - list of *managers*, with active one at the end - + figs : OrderedDict + `OrderedDict` mapping numbers to managers; the active manager is at the + end. """ - _activeQue = [] - figs = {} + + figs = OrderedDict() @classmethod def get_fig_manager(cls, num): """ - If figure manager *num* exists, make it the active - figure and return the manager; otherwise return *None*. + If manager number *num* exists, make it the active one and return it; + otherwise return *None*. """ manager = cls.figs.get(num, None) if manager is not None: @@ -40,90 +45,73 @@ def get_fig_manager(cls, num): @classmethod def destroy(cls, num): """ - Try to remove all traces of figure *num*. + Destroy figure number *num*. - In the interactive backends, this is bound to the - window "destroy" and "delete" events. + In the interactive backends, this is bound to the window "destroy" and + "delete" events. """ if not cls.has_fignum(num): return - manager = cls.figs[num] + manager = cls.figs.pop(num) manager.canvas.mpl_disconnect(manager._cidgcf) - cls._activeQue.remove(manager) - del cls.figs[num] manager.destroy() gc.collect(1) @classmethod def destroy_fig(cls, fig): - "*fig* is a Figure instance" - num = next((manager.num for manager in cls.figs.values() - if manager.canvas.figure == fig), None) - if num is not None: - cls.destroy(num) + """Destroy figure *fig*.""" + canvas = getattr(fig, "canvas", None) + manager = getattr(canvas, "manager", None) + num = getattr(manager, "num", None) + cls.destroy(num) @classmethod def destroy_all(cls): - # this is need to ensure that gc is available in corner cases - # where modules are being torn down after install with easy_install - import gc # noqa + """Destroy all figures.""" + # Reimport gc in case the module globals have already been removed + # during interpreter shutdown. + import gc for manager in list(cls.figs.values()): manager.canvas.mpl_disconnect(manager._cidgcf) manager.destroy() - - cls._activeQue = [] cls.figs.clear() gc.collect(1) @classmethod def has_fignum(cls, num): - """ - Return *True* if figure *num* exists. - """ + """Return whether figure number *num* exists.""" return num in cls.figs @classmethod def get_all_fig_managers(cls): - """ - Return a list of figure managers. - """ + """Return a list of figure managers.""" return list(cls.figs.values()) @classmethod def get_num_fig_managers(cls): - """ - Return the number of figures being managed. - """ + """Return the number of figures being managed.""" return len(cls.figs) @classmethod def get_active(cls): - """ - Return the manager of the active figure, or *None*. - """ - if len(cls._activeQue) == 0: - return None - else: - return cls._activeQue[-1] + """Return the active manager, or *None* if there is no manager.""" + return next(reversed(cls.figs.values())) if cls.figs else None @classmethod def set_active(cls, manager): - """ - Make the figure corresponding to *manager* the active one. - """ - oldQue = cls._activeQue[:] - cls._activeQue = [m for m in oldQue if m != manager] - cls._activeQue.append(manager) + """Make *manager* the active manager.""" cls.figs[manager.num] = manager + cls.figs.move_to_end(manager.num) @classmethod def draw_all(cls, force=False): """ - Redraw all figures registered with the pyplot - state machine. + Redraw all stale managed figures, or, if *force* is True, all managed + figures. """ - for f_mgr in cls.get_all_fig_managers(): - if force or f_mgr.canvas.figure.stale: - f_mgr.canvas.draw_idle() + for manager in cls.get_all_fig_managers(): + if force or manager.canvas.figure.stale: + manager.canvas.draw_idle() + atexit.register(Gcf.destroy_all)