Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Picklable figures #1020

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ minimum and maximum colorbar extensions.
plt.show()


Figures are picklable
---------------------

Philip Elson added an experimental feature to make figures picklable
for quick and easy short-term storage of plots. Pickle files
are not designed for long term storage, are unsupported when restoring a pickle
saved in another matplotlib version and are insecure when restoring a pickle
from an untrusted source. Having said this, they are useful for short term
storage for later modification inside matplotlib.


Set default bounding box in matplotlibrc
------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,7 @@ def tk_window_focus():
'matplotlib.tests.test_mathtext',
'matplotlib.tests.test_mlab',
'matplotlib.tests.test_patches',
'matplotlib.tests.test_pickle',
'matplotlib.tests.test_rcparams',
'matplotlib.tests.test_simplification',
'matplotlib.tests.test_spines',
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/_pylab_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def error_msg(msg):

class Gcf(object):
"""
Manage a set of integer-numbered figures.
Singleton to manage a set of integer-numbered figures.

This class is never instantiated; it consists of two class
attributes (a list and a dictionary), and a set of static
Expand Down Expand Up @@ -131,6 +131,7 @@ def set_active(manager):
if m != manager: Gcf._activeQue.append(m)
Gcf._activeQue.append(manager)
Gcf.figs[manager.num] = manager


atexit.register(Gcf.destroy_all)

Expand Down
9 changes: 8 additions & 1 deletion lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ def __init__(self):
self.y_isdata = True # with y
self._snap = None

def __getstate__(self):
d = self.__dict__.copy()
# remove the unpicklable remove method, this will get re-added on load
# (by the axes) if the artist lives on an axes.
d['_remove_method'] = None
return d

def remove(self):
"""
Remove the artist from the figure if possible. The effect
Expand All @@ -123,7 +130,7 @@ def remove(self):
# the _remove_method attribute directly. This would be a protected
# attribute if Python supported that sort of thing. The callback
# has one parameter, which is the child to be removed.
if self._remove_method != None:
if self._remove_method is not None:
self._remove_method(self)
else:
raise NotImplementedError('cannot remove artist')
Expand Down
54 changes: 47 additions & 7 deletions lib/matplotlib/axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,8 @@ def set_default_color_cycle(clist):
DeprecationWarning)


class _process_plot_var_args:
class _process_plot_var_args(object):
"""

Process variable length arguments to the plot command, so that
plot commands like the following are supported::

Expand All @@ -172,6 +171,14 @@ def __init__(self, axes, command='plot'):
self.command = command
self.set_color_cycle()

def __getstate__(self):
# note: it is not possible to pickle a itertools.cycle instance
return {'axes': self.axes, 'command': self.command}

def __setstate__(self, state):
self.__dict__ = state.copy()
self.set_color_cycle()

def set_color_cycle(self, clist=None):
if clist is None:
clist = rcParams['axes.color_cycle']
Expand Down Expand Up @@ -332,7 +339,7 @@ def _grab_next_args(self, *args, **kwargs):
for seg in self._plot_args(remaining[:isplit], kwargs):
yield seg
remaining=remaining[isplit:]


class Axes(martist.Artist):
"""
Expand All @@ -352,9 +359,10 @@ class Axes(martist.Artist):

_shared_x_axes = cbook.Grouper()
_shared_y_axes = cbook.Grouper()

def __str__(self):
return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds)

def __init__(self, fig, rect,
axisbg = None, # defaults to rc axes.facecolor
frameon = True,
Expand Down Expand Up @@ -489,6 +497,15 @@ def __init__(self, fig, rect,
self._ycid = self.yaxis.callbacks.connect('units finalize',
self.relim)

def __setstate__(self, state):
self.__dict__ = state
# put the _remove_method back on all artists contained within the axes
for container_name in ['lines', 'collections', 'tables', 'patches',
'texts', 'images']:
container = getattr(self, container_name)
for artist in container:
artist._remove_method = container.remove

def get_window_extent(self, *args, **kwargs):
"""
get the axes bounding box in display space; *args* and
Expand Down Expand Up @@ -1599,13 +1616,13 @@ def _process_unit_info(self, xdata=None, ydata=None, kwargs=None):
if xdata is not None:
# we only need to update if there is nothing set yet.
if not self.xaxis.have_units():
self.xaxis.update_units(xdata)
self.xaxis.update_units(xdata)
#print '\tset from xdata', self.xaxis.units

if ydata is not None:
# we only need to update if there is nothing set yet.
if not self.yaxis.have_units():
self.yaxis.update_units(ydata)
self.yaxis.update_units(ydata)
#print '\tset from ydata', self.yaxis.units

# process kwargs 2nd since these will override default units
Expand Down Expand Up @@ -8770,7 +8787,15 @@ def __init__(self, fig, *args, **kwargs):
# _axes_class is set in the subplot_class_factory
self._axes_class.__init__(self, fig, self.figbox, **kwargs)


def __reduce__(self):
# get the first axes class which does not inherit from a subplotbase
axes_class = filter(lambda klass: (issubclass(klass, Axes) and
not issubclass(klass, SubplotBase)),
self.__class__.mro())[0]
r = [_PicklableSubplotClassConstructor(),
(axes_class,),
self.__getstate__()]
return tuple(r)

def get_geometry(self):
"""get the subplot geometry, eg 2,2,3"""
Expand Down Expand Up @@ -8852,6 +8877,21 @@ def subplot_class_factory(axes_class=None):
# This is provided for backward compatibility
Subplot = subplot_class_factory()


class _PicklableSubplotClassConstructor(object):
"""
This stub class exists to return the appropriate subplot
class when __call__-ed with an axes class. This is purely to
allow Pickling of Axes and Subplots."""
def __call__(self, axes_class):
# create a dummy object instance
subplot_instance = _PicklableSubplotClassConstructor()
subplot_class = subplot_class_factory(axes_class)
# update the class to the desired subplot class
subplot_instance.__class__ = subplot_class
return subplot_instance


docstring.interpd.update(Axes=martist.kwdoc(Axes))
docstring.interpd.update(Subplot=martist.kwdoc(Axes))

Expand Down
1 change: 0 additions & 1 deletion lib/matplotlib/axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,6 @@ class Ticker:
formatter = None



class Axis(artist.Artist):

"""
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ def do_nothing(*args, **kwargs): pass

matplotlib.verbose.report('backend %s version %s' % (backend,backend_version))

return new_figure_manager, draw_if_interactive, show
return backend_mod, new_figure_manager, draw_if_interactive, show


10 changes: 8 additions & 2 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,6 @@ def post_processing(image, dpi):
image)



def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
Expand All @@ -396,7 +395,14 @@ def new_figure_manager(num, *args, **kwargs):

FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasAgg(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasAgg(figure)
manager = FigureManagerBase(canvas, num)
return manager

Expand Down
11 changes: 9 additions & 2 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,17 @@ def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py
"""
Create a new figure manager instance
"""
if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))
if _debug: print('%s.%s()' % (_fn_name()))
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasCairo(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasCairo(figure)
manager = FigureManagerBase(canvas, num)
return manager

Expand Down
11 changes: 10 additions & 1 deletion lib/matplotlib/backends/backend_cocoaagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,21 @@

mplBundle = NSBundle.bundleWithPath_(os.path.dirname(__file__))


def new_figure_manager(num, *args, **kwargs):
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass( *args, **kwargs )
canvas = FigureCanvasCocoaAgg(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasCocoaAgg(figure)
return FigureManagerCocoaAgg(canvas, num)


## Below is the original show() function:
#def show():
# for manager in Gcf.get_all_fig_managers():
Expand Down
9 changes: 8 additions & 1 deletion lib/matplotlib/backends/backend_emf.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,14 @@ def new_figure_manager(num, *args, **kwargs):
# main-level app (egg backend_gtk, backend_gtkagg) for pylab
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasEMF(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasEMF(figure)
manager = FigureManagerEMF(canvas, num)
return manager

Expand Down
7 changes: 7 additions & 0 deletions lib/matplotlib/backends/backend_fltkagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ def new_figure_manager(num, *args, **kwargs):
"""
FigureClass = kwargs.pop('FigureClass', Figure)
figure = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, figure)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
window = Fltk.Fl_Double_Window(10,10,30,30)
canvas = FigureCanvasFltkAgg(figure)
window.end()
Expand Down
12 changes: 8 additions & 4 deletions lib/matplotlib/backends/backend_gdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,15 @@ def new_figure_manager(num, *args, **kwargs):
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasGDK(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasGDK(figure)
manager = FigureManagerBase(canvas, num)
# equals:
#manager = FigureManagerBase (FigureCanvasGDK (Figure(*args, **kwargs),
# num)
return manager


Expand Down
11 changes: 8 additions & 3 deletions lib/matplotlib/backends/backend_gtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,15 @@ def new_figure_manager(num, *args, **kwargs):
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasGTK(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasGTK(figure)
manager = FigureManagerGTK(canvas, num)
# equals:
#manager = FigureManagerGTK(FigureCanvasGTK(Figure(*args, **kwargs), num)
return manager


Expand Down
9 changes: 8 additions & 1 deletion lib/matplotlib/backends/backend_gtk3agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,14 @@ def new_figure_manager(num, *args, **kwargs):
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasGTK3Agg(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasGTK3Agg(figure)
manager = FigureManagerGTK3Agg(canvas, num)
return manager

Expand Down
9 changes: 8 additions & 1 deletion lib/matplotlib/backends/backend_gtk3cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ def new_figure_manager(num, *args, **kwargs):
"""
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasGTK3Cairo(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasGTK3Cairo(figure)
manager = FigureManagerGTK3Cairo(canvas, num)
return manager

Expand Down
11 changes: 10 additions & 1 deletion lib/matplotlib/backends/backend_gtkagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,26 @@ def _get_toolbar(self, canvas):
toolbar = None
return toolbar


def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
"""
if DEBUG: print('backend_gtkagg.new_figure_manager')
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
canvas = FigureCanvasGTKAgg(thisFig)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasGTKAgg(figure)
return FigureManagerGTKAgg(canvas, num)
if DEBUG: print('backend_gtkagg.new_figure_manager done')


class FigureCanvasGTKAgg(FigureCanvasGTK, FigureCanvasAgg):
filetypes = FigureCanvasGTK.filetypes.copy()
filetypes.update(FigureCanvasAgg.filetypes)
Expand Down
Loading