Skip to content

Commit

Permalink
Merge pull request #1175 from pelson/pickle2
Browse files Browse the repository at this point in the history
Pickling support added. Various whitespace fixes as a result of reading *lots* of code.
  • Loading branch information
efiring committed Sep 1, 2012
2 parents cf7618c + 1e08190 commit 4c1e36d
Show file tree
Hide file tree
Showing 47 changed files with 1,073 additions and 517 deletions.
11 changes: 11 additions & 0 deletions doc/users/whats_new.rst
Expand Up @@ -100,6 +100,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
Expand Up @@ -1085,6 +1085,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
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 @@ -132,6 +132,7 @@ def set_active(manager):
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
Expand Up @@ -103,6 +103,13 @@ def __init__(self):
self._gid = None
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 @@ -122,7 +129,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
79 changes: 60 additions & 19 deletions lib/matplotlib/axes.py
Expand Up @@ -153,9 +153,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 @@ -171,6 +170,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 @@ -281,7 +288,7 @@ def _plot_args(self, tup, kwargs):
linestyle, marker, color = _process_plot_format(tup[-1])
tup = tup[:-1]
elif len(tup) == 3:
raise ValueError, 'third arg must be a format string'
raise ValueError('third arg must be a format string')
else:
linestyle, marker, color = None, None, None
kw = {}
Expand Down Expand Up @@ -354,6 +361,7 @@ class Axes(martist.Artist):

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 @@ -488,6 +496,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 @@ -1472,7 +1489,7 @@ def _update_line_limits(self, line):
return

line_trans = line.get_transform()

if line_trans == self.transData:
data_path = path

Expand All @@ -1491,8 +1508,8 @@ def _update_line_limits(self, line):
else:
data_path = trans_to_data.transform_path(path)
else:
# for backwards compatibility we update the dataLim with the
# coordinate range of the given path, even though the coordinate
# for backwards compatibility we update the dataLim with the
# coordinate range of the given path, even though the coordinate
# systems are completely different. This may occur in situations
# such as when ax.transAxes is passed through for absolute
# positioning.
Expand All @@ -1502,7 +1519,7 @@ def _update_line_limits(self, line):
updatex, updatey = line_trans.contains_branch_seperately(
self.transData
)
self.dataLim.update_from_path(data_path,
self.dataLim.update_from_path(data_path,
self.ignore_existing_data_limits,
updatex=updatex,
updatey=updatey)
Expand Down Expand Up @@ -2199,11 +2216,11 @@ def ticklabel_format(self, **kwargs):
cb = False
else:
cb = True
raise NotImplementedError, "comma style remains to be added"
raise NotImplementedError("comma style remains to be added")
elif style == '':
sb = None
else:
raise ValueError, "%s is not a valid style value"
raise ValueError("%s is not a valid style value")
try:
if sb is not None:
if axis == 'both' or axis == 'x':
Expand Down Expand Up @@ -3706,9 +3723,9 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid',
xmax = np.resize( xmax, y.shape )

if len(xmin)!=len(y):
raise ValueError, 'xmin and y are unequal sized sequences'
raise ValueError('xmin and y are unequal sized sequences')
if len(xmax)!=len(y):
raise ValueError, 'xmax and y are unequal sized sequences'
raise ValueError('xmax and y are unequal sized sequences')

verts = [ ((thisxmin, thisy), (thisxmax, thisy))
for thisxmin, thisxmax, thisy in zip(xmin, xmax, y)]
Expand Down Expand Up @@ -3785,9 +3802,9 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid',
ymax = np.resize( ymax, x.shape )

if len(ymin)!=len(x):
raise ValueError, 'ymin and x are unequal sized sequences'
raise ValueError('ymin and x are unequal sized sequences')
if len(ymax)!=len(x):
raise ValueError, 'ymax and x are unequal sized sequences'
raise ValueError('ymax and x are unequal sized sequences')

Y = np.array([ymin, ymax]).T

Expand Down Expand Up @@ -4768,7 +4785,7 @@ def make_iterable(x):
if len(height) == 1:
height *= nbars
else:
raise ValueError, 'invalid orientation: %s' % orientation
raise ValueError('invalid orientation: %s' % orientation)

if len(linewidth) < nbars:
linewidth *= nbars
Expand Down Expand Up @@ -4826,7 +4843,7 @@ def make_iterable(x):
bottom = [bottom[i] - height[i]/2. for i in xrange(len(bottom))]

else:
raise ValueError, 'invalid alignment: %s' % align
raise ValueError('invalid alignment: %s' % align)

args = zip(left, bottom, width, height, color, edgecolor, linewidth)
for l, b, w, h, c, e, lw in args:
Expand Down Expand Up @@ -5701,7 +5718,7 @@ def computeConfInterval(data, med, iq, bootstrap):
else:
x = [x[:,i] for i in xrange(nc)]
else:
raise ValueError, "input x can have no more than 2 dimensions"
raise ValueError("input x can have no more than 2 dimensions")
if not hasattr(x[0], '__len__'):
x = [x]
col = len(x)
Expand Down Expand Up @@ -7069,10 +7086,10 @@ def _pcolorargs(self, funcname, *args):

Nx = X.shape[-1]
Ny = Y.shape[0]
if len(X.shape) <> 2 or X.shape[0] == 1:
if len(X.shape) != 2 or X.shape[0] == 1:
x = X.reshape(1,Nx)
X = x.repeat(Ny, axis=0)
if len(Y.shape) <> 2 or Y.shape[1] == 1:
if len(Y.shape) != 2 or Y.shape[1] == 1:
y = Y.reshape(Ny, 1)
Y = y.repeat(Nx, axis=1)
if X.shape != Y.shape:
Expand Down Expand Up @@ -8815,7 +8832,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
not_subplotbase = lambda c: issubclass(c, Axes) and \
not issubclass(c, SubplotBase)
axes_class = [c for c in self.__class__.mro() if not_subplotbase(c)][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 @@ -8897,6 +8922,22 @@ 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
Expand Up @@ -597,7 +597,6 @@ class Ticker:
formatter = None



class Axis(artist.Artist):

"""
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/backends/__init__.py
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
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

0 comments on commit 4c1e36d

Please sign in to comment.