diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst
index a6fce09ef9ff..b8c458142344 100644
--- a/doc/users/whats_new.rst
+++ b/doc/users/whats_new.rst
@@ -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
------------------------------------------
diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index d8e014c845e0..33fe760d19b1 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -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',
diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py
index f6b61868cecf..afef43f917f1 100644
--- a/lib/matplotlib/_pylab_helpers.py
+++ b/lib/matplotlib/_pylab_helpers.py
@@ -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
@@ -132,6 +132,7 @@ def set_active(manager):
Gcf._activeQue.append(manager)
Gcf.figs[manager.num] = manager
+
atexit.register(Gcf.destroy_all)
diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py
index d5375189ea49..68699a211342 100644
--- a/lib/matplotlib/artist.py
+++ b/lib/matplotlib/artist.py
@@ -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
@@ -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')
diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py
index 71bf4ca86a11..da78d16d9061 100644
--- a/lib/matplotlib/axes.py
+++ b/lib/matplotlib/axes.py
@@ -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::
@@ -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']
@@ -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 = {}
@@ -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,
@@ -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
@@ -1472,7 +1489,7 @@ def _update_line_limits(self, line):
return
line_trans = line.get_transform()
-
+
if line_trans == self.transData:
data_path = path
@@ -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.
@@ -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)
@@ -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':
@@ -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)]
@@ -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
@@ -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
@@ -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:
@@ -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)
@@ -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:
@@ -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"""
@@ -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))
diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py
index 3723845e7b5d..2533a51b1581 100644
--- a/lib/matplotlib/axis.py
+++ b/lib/matplotlib/axis.py
@@ -597,7 +597,6 @@ class Ticker:
formatter = None
-
class Axis(artist.Artist):
"""
diff --git a/lib/matplotlib/backends/__init__.py b/lib/matplotlib/backends/__init__.py
index f8ffd36ef7bd..2cebf1f4c925 100644
--- a/lib/matplotlib/backends/__init__.py
+++ b/lib/matplotlib/backends/__init__.py
@@ -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
diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py
index 9c585d0c7d6f..e221989a6dbd 100644
--- a/lib/matplotlib/backends/backend_agg.py
+++ b/lib/matplotlib/backends/backend_agg.py
@@ -385,7 +385,6 @@ def post_processing(image, dpi):
image)
-
def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
@@ -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
diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py
index eeb91713d6f4..966e618fa408 100644
--- a/lib/matplotlib/backends/backend_cairo.py
+++ b/lib/matplotlib/backends/backend_cairo.py
@@ -26,15 +26,15 @@
def _fn_name(): return sys._getframe(1).f_code.co_name
try:
- import cairo
+ import cairo
except ImportError:
- raise ImportError("Cairo backend requires that pycairo is installed.")
+ raise ImportError("Cairo backend requires that pycairo is installed.")
_version_required = (1,2,0)
if cairo.version_info < _version_required:
- raise ImportError ("Pycairo %d.%d.%d is installed\n"
- "Pycairo %d.%d.%d or later is required"
- % (cairo.version_info + _version_required))
+ raise ImportError ("Pycairo %d.%d.%d is installed\n"
+ "Pycairo %d.%d.%d or later is required"
+ % (cairo.version_info + _version_required))
backend_version = cairo.version
del _version_required
@@ -183,27 +183,27 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))
if ismath:
- self._draw_mathtext(gc, x, y, s, prop, angle)
+ self._draw_mathtext(gc, x, y, s, prop, angle)
else:
- ctx = gc.ctx
- ctx.new_path()
- ctx.move_to (x, y)
- ctx.select_font_face (prop.get_name(),
- self.fontangles [prop.get_style()],
- self.fontweights[prop.get_weight()])
-
- size = prop.get_size_in_points() * self.dpi / 72.0
-
- ctx.save()
- if angle:
- ctx.rotate (-angle * np.pi / 180)
- ctx.set_font_size (size)
- if sys.version_info[0] < 3:
- ctx.show_text (s.encode("utf-8"))
- else:
- ctx.show_text (s)
- ctx.restore()
+ ctx = gc.ctx
+ ctx.new_path()
+ ctx.move_to (x, y)
+ ctx.select_font_face (prop.get_name(),
+ self.fontangles [prop.get_style()],
+ self.fontweights[prop.get_weight()])
+
+ size = prop.get_size_in_points() * self.dpi / 72.0
+
+ ctx.save()
+ if angle:
+ ctx.rotate (-angle * np.pi / 180)
+ ctx.set_font_size (size)
+ if sys.version_info[0] < 3:
+ ctx.show_text (s.encode("utf-8"))
+ else:
+ ctx.show_text (s)
+ ctx.restore()
def _draw_mathtext(self, gc, x, y, s, prop, angle):
if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))
@@ -215,28 +215,28 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle):
ctx.save()
ctx.translate(x, y)
if angle:
- ctx.rotate (-angle * np.pi / 180)
+ ctx.rotate (-angle * np.pi / 180)
for font, fontsize, s, ox, oy in glyphs:
- ctx.new_path()
- ctx.move_to(ox, oy)
-
- fontProp = ttfFontProperty(font)
- ctx.save()
- ctx.select_font_face (fontProp.name,
- self.fontangles [fontProp.style],
- self.fontweights[fontProp.weight])
-
- size = fontsize * self.dpi / 72.0
- ctx.set_font_size(size)
- ctx.show_text(s.encode("utf-8"))
- ctx.restore()
+ ctx.new_path()
+ ctx.move_to(ox, oy)
+
+ fontProp = ttfFontProperty(font)
+ ctx.save()
+ ctx.select_font_face (fontProp.name,
+ self.fontangles [fontProp.style],
+ self.fontweights[fontProp.weight])
+
+ size = fontsize * self.dpi / 72.0
+ ctx.set_font_size(size)
+ ctx.show_text(s.encode("utf-8"))
+ ctx.restore()
for ox, oy, w, h in rects:
- ctx.new_path()
- ctx.rectangle (ox, oy, w, h)
- ctx.set_source_rgb (0, 0, 0)
- ctx.fill_preserve()
+ ctx.new_path()
+ ctx.rectangle (ox, oy, w, h)
+ ctx.set_source_rgb (0, 0, 0)
+ ctx.fill_preserve()
ctx.restore()
@@ -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()' % (_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
diff --git a/lib/matplotlib/backends/backend_cocoaagg.py b/lib/matplotlib/backends/backend_cocoaagg.py
index 7a21f079833a..5c9c04938874 100644
--- a/lib/matplotlib/backends/backend_cocoaagg.py
+++ b/lib/matplotlib/backends/backend_cocoaagg.py
@@ -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():
diff --git a/lib/matplotlib/backends/backend_emf.py b/lib/matplotlib/backends/backend_emf.py
index 580798f641f0..50da4fea706f 100644
--- a/lib/matplotlib/backends/backend_emf.py
+++ b/lib/matplotlib/backends/backend_emf.py
@@ -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
diff --git a/lib/matplotlib/backends/backend_fltkagg.py b/lib/matplotlib/backends/backend_fltkagg.py
index fe31d0f81191..7a2a7f258f2c 100644
--- a/lib/matplotlib/backends/backend_fltkagg.py
+++ b/lib/matplotlib/backends/backend_fltkagg.py
@@ -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()
diff --git a/lib/matplotlib/backends/backend_gdk.py b/lib/matplotlib/backends/backend_gdk.py
index 7d7f7e323d20..c0790c6591bb 100644
--- a/lib/matplotlib/backends/backend_gdk.py
+++ b/lib/matplotlib/backends/backend_gdk.py
@@ -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
diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py
index b7199890225a..8336ddaecf8e 100644
--- a/lib/matplotlib/backends/backend_gtk.py
+++ b/lib/matplotlib/backends/backend_gtk.py
@@ -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
diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py
index da14f356ba0a..c05e0266631a 100644
--- a/lib/matplotlib/backends/backend_gtk3agg.py
+++ b/lib/matplotlib/backends/backend_gtk3agg.py
@@ -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
diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py
index 21abae2a4762..2b1fefc04d4b 100644
--- a/lib/matplotlib/backends/backend_gtk3cairo.py
+++ b/lib/matplotlib/backends/backend_gtk3cairo.py
@@ -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
diff --git a/lib/matplotlib/backends/backend_gtkagg.py b/lib/matplotlib/backends/backend_gtkagg.py
index c8f359f8c0f6..aa7c42a68027 100644
--- a/lib/matplotlib/backends/backend_gtkagg.py
+++ b/lib/matplotlib/backends/backend_gtkagg.py
@@ -33,6 +33,7 @@ def _get_toolbar(self, canvas):
toolbar = None
return toolbar
+
def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
@@ -40,10 +41,18 @@ def new_figure_manager(num, *args, **kwargs):
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)
diff --git a/lib/matplotlib/backends/backend_gtkcairo.py b/lib/matplotlib/backends/backend_gtkcairo.py
index 616a3dc8ba71..2cd41ba720eb 100644
--- a/lib/matplotlib/backends/backend_gtkcairo.py
+++ b/lib/matplotlib/backends/backend_gtkcairo.py
@@ -26,7 +26,14 @@ def new_figure_manager(num, *args, **kwargs):
if _debug: print('backend_gtkcairo.%s()' % fn_name())
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
- canvas = FigureCanvasGTKCairo(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 = FigureCanvasGTKCairo(figure)
return FigureManagerGTK(canvas, num)
diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py
index 7e402c302392..a8fb1b758fed 100644
--- a/lib/matplotlib/backends/backend_macosx.py
+++ b/lib/matplotlib/backends/backend_macosx.py
@@ -233,9 +233,20 @@ def new_figure_manager(num, *args, **kwargs):
"""
if not _macosx.verify_main_display():
import warnings
- warnings.warn("Python is not installed as a framework. The MacOSX backend may not work correctly if Python is not installed as a framework. Please see the Python documentation for more information on installing Python as a framework on Mac OS X")
+ warnings.warn("Python is not installed as a framework. The MacOSX "
+ "backend may not work correctly if Python is not "
+ "installed as a framework. Please see the Python "
+ "documentation for more information on installing "
+ "Python as a framework on Mac OS X")
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.
+ """
canvas = FigureCanvasMac(figure)
manager = FigureManagerMac(canvas, num)
return manager
diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py
index f2baf9ba5deb..06f9b1d30156 100644
--- a/lib/matplotlib/backends/backend_pdf.py
+++ b/lib/matplotlib/backends/backend_pdf.py
@@ -2182,7 +2182,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 = FigureCanvasPdf(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 = FigureCanvasPdf(figure)
manager = FigureManagerPdf(canvas, num)
return manager
diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py
index 9909f6b09223..cd5d520022ac 100644
--- a/lib/matplotlib/backends/backend_pgf.py
+++ b/lib/matplotlib/backends/backend_pgf.py
@@ -598,6 +598,7 @@ class GraphicsContextPgf(GraphicsContextBase):
def draw_if_interactive():
pass
+
def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
@@ -608,7 +609,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 = FigureCanvasPgf(thisFig)
+ return new_figure_manager_given_figure(thisFig)
+
+
+def new_figure_manager_given_figure(num, figure):
+ """
+ Create a new figure manager instance for the given figure.
+ """
+ canvas = FigureCanvasPgf(figure)
manager = FigureManagerPgf(canvas, num)
return manager
diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py
index 2af23224f145..d604028c554c 100644
--- a/lib/matplotlib/backends/backend_ps.py
+++ b/lib/matplotlib/backends/backend_ps.py
@@ -944,7 +944,14 @@ def shouldstroke(self):
def new_figure_manager(num, *args, **kwargs):
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
- canvas = FigureCanvasPS(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 = FigureCanvasPS(figure)
manager = FigureManagerPS(canvas, num)
return manager
diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py
index 6acca884e6a0..0e1836a81acf 100644
--- a/lib/matplotlib/backends/backend_qt.py
+++ b/lib/matplotlib/backends/backend_qt.py
@@ -69,9 +69,16 @@ def new_figure_manager( num, *args, **kwargs ):
Create a new figure manager instance
"""
FigureClass = kwargs.pop('FigureClass', Figure)
- thisFig = FigureClass( *args, **kwargs )
- canvas = FigureCanvasQT( thisFig )
- manager = FigureManagerQT( canvas, num )
+ thisFig = FigureClass(*args, **kwargs)
+ 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 = FigureCanvasQT(figure)
+ manager = FigureManagerQT(canvas, num)
return manager
diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py
index 45fe444743d8..f64bfa4245ff 100644
--- a/lib/matplotlib/backends/backend_qt4.py
+++ b/lib/matplotlib/backends/backend_qt4.py
@@ -72,9 +72,16 @@ def new_figure_manager( num, *args, **kwargs ):
"""
Create a new figure manager instance
"""
- thisFig = Figure( *args, **kwargs )
- canvas = FigureCanvasQT( thisFig )
- manager = FigureManagerQT( canvas, num )
+ thisFig = Figure(*args, **kwargs)
+ 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 = FigureCanvasQT(figure)
+ manager = FigureManagerQT(canvas, num)
return manager
diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py
index c47584da4a6f..07017818e853 100644
--- a/lib/matplotlib/backends/backend_qt4agg.py
+++ b/lib/matplotlib/backends/backend_qt4agg.py
@@ -23,9 +23,17 @@ def new_figure_manager( num, *args, **kwargs ):
if DEBUG: print('backend_qtagg.new_figure_manager')
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass( *args, **kwargs )
- canvas = FigureCanvasQTAgg( 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 = FigureCanvasQTAgg(figure)
return FigureManagerQT( canvas, num )
+
class NavigationToolbar2QTAgg(NavigationToolbar2QT):
def _get_canvas(self, fig):
return FigureCanvasQTAgg(fig)
diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py
index e7a3d89910b8..91a9cd47133e 100644
--- a/lib/matplotlib/backends/backend_qtagg.py
+++ b/lib/matplotlib/backends/backend_qtagg.py
@@ -23,8 +23,16 @@ def new_figure_manager( num, *args, **kwargs ):
if DEBUG: print('backend_qtagg.new_figure_manager')
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass( *args, **kwargs )
- canvas = FigureCanvasQTAgg( thisFig )
- return FigureManagerQTAgg( canvas, num )
+ 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 = FigureCanvasQTAgg(figure)
+ return FigureManagerQTAgg(canvas, num)
+
class NavigationToolbar2QTAgg(NavigationToolbar2QT):
def _get_canvas(self, fig):
diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py
index 0b9b7ab51701..aa117ca9bacb 100644
--- a/lib/matplotlib/backends/backend_svg.py
+++ b/lib/matplotlib/backends/backend_svg.py
@@ -1151,10 +1151,18 @@ class FigureManagerSVG(FigureManagerBase):
def new_figure_manager(num, *args, **kwargs):
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
- canvas = FigureCanvasSVG(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 = FigureCanvasSVG(figure)
manager = FigureManagerSVG(canvas, num)
return manager
+
svgProlog = u"""\
= 8.5:
diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py
index c70b25e78bb5..3c61d1e412bd 100644
--- a/lib/matplotlib/backends/backend_wx.py
+++ b/lib/matplotlib/backends/backend_wx.py
@@ -1456,6 +1456,14 @@ def new_figure_manager(num, *args, **kwargs):
FigureClass = kwargs.pop('FigureClass', Figure)
fig = FigureClass(*args, **kwargs)
+ return new_figure_manager_given_figure(num, fig)
+
+
+def new_figure_manager_given_figure(num, figure):
+ """
+ Create a new figure manager instance for the given figure.
+ """
+ fig = figure
frame = FigureFrameWx(num, fig)
figmgr = frame.get_figure_manager()
if matplotlib.is_interactive():
@@ -1463,6 +1471,7 @@ def new_figure_manager(num, *args, **kwargs):
return figmgr
+
class FigureFrameWx(wx.Frame):
def __init__(self, num, fig):
# On non-Windows platform, explicitly set the position - fix
diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py
index fa498ba0f3d4..5b301a045f04 100644
--- a/lib/matplotlib/backends/backend_wxagg.py
+++ b/lib/matplotlib/backends/backend_wxagg.py
@@ -121,14 +121,20 @@ def new_figure_manager(num, *args, **kwargs):
FigureClass = kwargs.pop('FigureClass', Figure)
fig = FigureClass(*args, **kwargs)
- frame = FigureFrameWxAgg(num, fig)
+
+ return new_figure_manager_given_figure(num, fig)
+
+def new_figure_manager_given_figure(num, figure):
+ """
+ Create a new figure manager instance for the given figure.
+ """
+ frame = FigureFrameWxAgg(num, figure)
figmgr = frame.get_figure_manager()
if matplotlib.is_interactive():
figmgr.frame.Show()
return figmgr
-
#
# agg/wxPython image conversion functions (wxPython >= 2.8)
#
diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py
index 7026614ea84e..0cbfb85dc0b6 100644
--- a/lib/matplotlib/cbook.py
+++ b/lib/matplotlib/cbook.py
@@ -152,6 +152,90 @@ def __call__(self, s):
if self.is_missing(s): return self.missingval
return int(s)
+
+class _BoundMethodProxy(object):
+ '''
+ Our own proxy object which enables weak references to bound and unbound
+ methods and arbitrary callables. Pulls information about the function,
+ class, and instance out of a bound method. Stores a weak reference to the
+ instance to support garbage collection.
+
+ @organization: IBM Corporation
+ @copyright: Copyright (c) 2005, 2006 IBM Corporation
+ @license: The BSD License
+
+ Minor bugfixes by Michael Droettboom
+ '''
+ def __init__(self, cb):
+ try:
+ try:
+ self.inst = ref(cb.im_self)
+ except TypeError:
+ self.inst = None
+ self.func = cb.im_func
+ self.klass = cb.im_class
+ except AttributeError:
+ self.inst = None
+ self.func = cb
+ self.klass = None
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ # de-weak reference inst
+ inst = d['inst']
+ if inst is not None:
+ d['inst'] = inst()
+ return d
+
+ def __setstate__(self, statedict):
+ self.__dict__ = statedict
+ inst = statedict['inst']
+ # turn inst back into a weakref
+ if inst is not None:
+ self.inst = ref(inst)
+
+ def __call__(self, *args, **kwargs):
+ '''
+ Proxy for a call to the weak referenced object. Take
+ arbitrary params to pass to the callable.
+
+ Raises `ReferenceError`: When the weak reference refers to
+ a dead object
+ '''
+ if self.inst is not None and self.inst() is None:
+ raise ReferenceError
+ elif self.inst is not None:
+ # build a new instance method with a strong reference to the instance
+ if sys.version_info[0] >= 3:
+ mtd = types.MethodType(self.func, self.inst())
+ else:
+ mtd = new.instancemethod(self.func, self.inst(), self.klass)
+ else:
+ # not a bound method, just return the func
+ mtd = self.func
+ # invoke the callable and return the result
+ return mtd(*args, **kwargs)
+
+ def __eq__(self, other):
+ '''
+ Compare the held function and instance with that held by
+ another proxy.
+ '''
+ try:
+ if self.inst is None:
+ return self.func == other.func and other.inst is None
+ else:
+ return self.func == other.func and self.inst() == other.inst()
+ except Exception:
+ return False
+
+ def __ne__(self, other):
+ '''
+ Inverse of __eq__.
+ '''
+ return not self.__eq__(other)
+
+
class CallbackRegistry:
"""
Handle registering and disconnecting for a set of signals and
@@ -190,73 +274,6 @@ def ondrink(x):
`"Mindtrove" blog
`_.
"""
- class BoundMethodProxy(object):
- '''
- Our own proxy object which enables weak references to bound and unbound
- methods and arbitrary callables. Pulls information about the function,
- class, and instance out of a bound method. Stores a weak reference to the
- instance to support garbage collection.
-
- @organization: IBM Corporation
- @copyright: Copyright (c) 2005, 2006 IBM Corporation
- @license: The BSD License
-
- Minor bugfixes by Michael Droettboom
- '''
- def __init__(self, cb):
- try:
- try:
- self.inst = ref(cb.im_self)
- except TypeError:
- self.inst = None
- self.func = cb.im_func
- self.klass = cb.im_class
- except AttributeError:
- self.inst = None
- self.func = cb
- self.klass = None
-
- def __call__(self, *args, **kwargs):
- '''
- Proxy for a call to the weak referenced object. Take
- arbitrary params to pass to the callable.
-
- Raises `ReferenceError`: When the weak reference refers to
- a dead object
- '''
- if self.inst is not None and self.inst() is None:
- raise ReferenceError
- elif self.inst is not None:
- # build a new instance method with a strong reference to the instance
- if sys.version_info[0] >= 3:
- mtd = types.MethodType(self.func, self.inst())
- else:
- mtd = new.instancemethod(self.func, self.inst(), self.klass)
- else:
- # not a bound method, just return the func
- mtd = self.func
- # invoke the callable and return the result
- return mtd(*args, **kwargs)
-
- def __eq__(self, other):
- '''
- Compare the held function and instance with that held by
- another proxy.
- '''
- try:
- if self.inst is None:
- return self.func == other.func and other.inst is None
- else:
- return self.func == other.func and self.inst() == other.inst()
- except Exception:
- return False
-
- def __ne__(self, other):
- '''
- Inverse of __eq__.
- '''
- return not self.__eq__(other)
-
def __init__(self, *args):
if len(args):
warnings.warn(
@@ -266,6 +283,15 @@ def __init__(self, *args):
self._cid = 0
self._func_cid_map = {}
+ def __getstate__(self):
+ # We cannot currently pickle the callables in the registry, so
+ # return an empty dictionary.
+ return {}
+
+ def __setstate__(self, state):
+ # re-initialise an empty callback registry
+ self.__init__()
+
def connect(self, s, func):
"""
register *func* to be called when a signal *s* is generated
@@ -279,7 +305,7 @@ def connect(self, s, func):
cid = self._cid
self._func_cid_map[s][func] = cid
self.callbacks.setdefault(s, dict())
- proxy = self.BoundMethodProxy(func)
+ proxy = _BoundMethodProxy(func)
self.callbacks[s][cid] = proxy
return cid
@@ -375,7 +401,7 @@ class silent_list(list):
"""
override repr when returning a list of matplotlib artists to
prevent long, meaningless output. This is meant to be used for a
- homogeneous list of a give type
+ homogeneous list of a given type
"""
def __init__(self, type, seq=None):
self.type = type
@@ -385,7 +411,15 @@ def __repr__(self):
return '' % (len(self), self.type)
def __str__(self):
- return '' % (len(self), self.type)
+ return repr(self)
+
+ def __getstate__(self):
+ # store a dictionary of this SilentList's state
+ return {'type': self.type, 'seq': self[:]}
+
+ def __setstate__(self, state):
+ self.type = state['type']
+ self.extend(state['seq'])
def strip_math(s):
'remove latex formatting from mathtext'
@@ -1879,6 +1913,41 @@ def is_math_text(s):
return even_dollars
+
+class _NestedClassGetter(object):
+ # recipe from http://stackoverflow.com/a/11493777/741316
+ """
+ When called with the containing class as the first argument,
+ and the name of the nested class as the second argument,
+ returns an instance of the nested class.
+ """
+ def __call__(self, containing_class, class_name):
+ nested_class = getattr(containing_class, class_name)
+
+ # make an instance of a simple object (this one will do), for which we
+ # can change the __class__ later on.
+ nested_instance = _NestedClassGetter()
+
+ # set the class of the instance, the __init__ will never be called on
+ # the class but the original state will be set later on by pickle.
+ nested_instance.__class__ = nested_class
+ return nested_instance
+
+
+class _InstanceMethodPickler(object):
+ """
+ Pickle cannot handle instancemethod saving. _InstanceMethodPickler
+ provides a solution to this.
+ """
+ def __init__(self, instancemethod):
+ """Takes an instancemethod as its only argument."""
+ self.parent_obj = instancemethod.im_self
+ self.instancemethod_name = instancemethod.im_func.__name__
+
+ def get_instancemethod(self):
+ return getattr(self.parent_obj, self.instancemethod_name)
+
+
# Numpy > 1.6.x deprecates putmask in favor of the new copyto.
# So long as we support versions 1.6.x and less, we need the
# following local version of putmask. We choose to make a
diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py
index e600828d9712..1f67d676d4b2 100644
--- a/lib/matplotlib/colorbar.py
+++ b/lib/matplotlib/colorbar.py
@@ -185,6 +185,12 @@
docstring.interpd.update(colorbar_doc=colorbar_doc)
+def _set_ticks_on_axis_warn(*args, **kw):
+ # a top level function which gets put in at the axes'
+ # set_xticks set_yticks by _patch_ax
+ warnings.warn("Use the colorbar set_ticks() method instead.")
+
+
class ColorbarBase(cm.ScalarMappable):
'''
Draw a colorbar in an existing axes.
@@ -287,11 +293,10 @@ def _extend_upper(self):
return self.extend in ('both', 'max')
def _patch_ax(self):
- def _warn(*args, **kw):
- warnings.warn("Use the colorbar set_ticks() method instead.")
-
- self.ax.set_xticks = _warn
- self.ax.set_yticks = _warn
+ # bind some methods to the axes to warn users
+ # against using those methods.
+ self.ax.set_xticks = _set_ticks_on_axis_warn
+ self.ax.set_yticks = _set_ticks_on_axis_warn
def draw_all(self):
'''
@@ -531,16 +536,13 @@ def _ticker(self):
intv = self._values[0], self._values[-1]
else:
intv = self.vmin, self.vmax
- locator.create_dummy_axis()
- formatter.create_dummy_axis()
+ locator.create_dummy_axis(minpos=intv[0])
+ formatter.create_dummy_axis(minpos=intv[0])
locator.set_view_interval(*intv)
locator.set_data_interval(*intv)
formatter.set_view_interval(*intv)
formatter.set_data_interval(*intv)
- # the dummy axis is expecting a minpos
- locator.axis.get_minpos = lambda : intv[0]
- formatter.axis.get_minpos = lambda : intv[0]
b = np.array(locator())
ticks = self._locate(b)
inrange = (ticks > -0.001) & (ticks < 1.001)
diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py
index ceee95a37145..916a608afc27 100644
--- a/lib/matplotlib/contour.py
+++ b/lib/matplotlib/contour.py
@@ -851,6 +851,13 @@ def __init__(self, ax, *args, **kwargs):
self.collections.append(col)
self.changed() # set the colors
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ # the C object Cntr cannot currently be pickled. This isn't a big issue
+ # as it is not actually used once the contour has been calculated
+ state['Cntr'] = None
+ return state
+
def legend_elements(self, variable_name='x', str_format=str):
"""
Return a list of artist and labels suitable for passing through
diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index 6dc6e0361529..b6a23aab420f 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -17,12 +17,15 @@
import numpy as np
-from matplotlib import rcParams, docstring
+from matplotlib import rcParams
+from matplotlib import docstring
+from matplotlib import __version__ as _mpl_version
import matplotlib.artist as martist
from matplotlib.artist import Artist, allow_rasterization
import matplotlib.cbook as cbook
+
from matplotlib.cbook import Stack, iterable
from matplotlib import _image
@@ -114,7 +117,6 @@ def add(self, key, a):
a_existing = self.get(key)
if a_existing is not None:
Stack.remove(self, (key, a_existing))
- import warnings
warnings.warn(
"key %s already existed; Axes is being replaced" % key)
# I don't think the above should ever happen.
@@ -1170,11 +1172,74 @@ def _gci(self):
return im
return None
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ # the axobservers cannot currently be pickled.
+ # Additionally, the canvas cannot currently be pickled, but this has
+ # the benefit of meaning that a figure can be detached from one canvas,
+ # and re-attached to another.
+ for attr_to_pop in ('_axobservers', 'show', 'canvas', '_cachedRenderer') :
+ state.pop(attr_to_pop, None)
+
+ # add version information to the state
+ state['__mpl_version__'] = _mpl_version
+
+ # check to see if the figure has a manager and whether it is registered
+ # with pyplot
+ if getattr(self.canvas, 'manager', None) is not None:
+ manager = self.canvas.manager
+ import matplotlib._pylab_helpers
+ if manager in matplotlib._pylab_helpers.Gcf.figs.viewvalues():
+ state['_restore_to_pylab'] = True
+
+ return state
+
+ def __setstate__(self, state):
+ version = state.pop('__mpl_version__')
+ restore_to_pylab = state.pop('_restore_to_pylab', False)
+
+ if version != _mpl_version:
+ import warnings
+ warnings.warn("This figure was saved with matplotlib version %s "
+ "and is unlikely to function correctly." %
+ (version, ))
+
+ self.__dict__ = state
+
+ # re-initialise some of the unstored state information
+ self._axobservers = []
+ self.canvas = None
+
+ if restore_to_pylab:
+ # lazy import to avoid circularity
+ import matplotlib.pyplot as plt
+ import matplotlib._pylab_helpers as pylab_helpers
+ allnums = plt.get_fignums()
+ num = max(allnums) + 1 if allnums else 1
+ mgr = plt._backend_mod.new_figure_manager_given_figure(num, self)
+
+ # XXX The following is a copy and paste from pyplot. Consider
+ # factoring to pylab_helpers
+
+ if self.get_label():
+ mgr.set_window_title(self.get_label())
+
+ # make this figure current on button press event
+ def make_active(event):
+ pylab_helpers.Gcf.set_active(mgr)
+
+ mgr._cidgcf = mgr.canvas.mpl_connect('button_press_event',
+ make_active)
+
+ pylab_helpers.Gcf.set_active(mgr)
+ self.number = num
+
+ plt.draw_if_interactive()
+
def add_axobserver(self, func):
'whenever the axes state change, ``func(self)`` will be called'
self._axobservers.append(func)
-
def savefig(self, *args, **kwargs):
"""
Save the current figure.
diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py
index 0c50fa460d54..7fbf3d963139 100644
--- a/lib/matplotlib/image.py
+++ b/lib/matplotlib/image.py
@@ -1075,10 +1075,10 @@ def get_window_extent(self, renderer=None):
else:
raise ValueError("unknown type of bbox")
-
def contains(self, mouseevent):
"""Test whether the mouse event occured within the image."""
- if callable(self._contains): return self._contains(self,mouseevent)
+ if callable(self._contains):
+ return self._contains(self, mouseevent)
if not self.get_visible():# or self.get_figure()._renderer is None:
return False,{}
@@ -1137,7 +1137,7 @@ def make_image(self, renderer, magnification=1.0):
numrows, numcols = self._A.shape[:2]
if not self.interp_at_native and widthDisplay==numcols and heightDisplay==numrows:
- im.set_interpolation(0)
+ im.set_interpolation(0)
# resize viewport to display
rx = widthDisplay / numcols
diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py
index 0b4738a214d7..dd464273e808 100644
--- a/lib/matplotlib/legend.py
+++ b/lib/matplotlib/legend.py
@@ -236,7 +236,6 @@ def __init__(self, parent, handles, labels,
self.legendHandles = []
self._legend_title_box = None
-
self._handler_map = handler_map
localdict = locals()
@@ -386,7 +385,6 @@ def _set_artist_props(self, a):
a.set_axes(self.axes)
a.set_transform(self.get_transform())
-
def _set_loc(self, loc):
# find_offset function will be provided to _legend_box and
# _legend_box will draw itself at the location of the return
@@ -415,7 +413,7 @@ def _findoffset_best(self, width, height, xdescent, ydescent, renderer):
return ox+xdescent, oy+ydescent
def _findoffset_loc(self, width, height, xdescent, ydescent, renderer):
- "Heper function to locate the legend using the location code"
+ "Helper function to locate the legend using the location code"
if iterable(self._loc) and len(self._loc)==2:
# when loc is a tuple of axes(or figure) coordinates.
@@ -433,10 +431,8 @@ def draw(self, renderer):
"Draw everything that belongs to the legend"
if not self.get_visible(): return
-
renderer.open_group('legend')
-
fontsize = renderer.points_to_pixels(self._fontsize)
# if mode == fill, set the width of the legend_box to the
@@ -463,7 +459,6 @@ def draw(self, renderer):
renderer.close_group('legend')
-
def _approx_text_height(self, renderer=None):
"""
Return the approximate height of the text. This is used to place
@@ -577,7 +572,6 @@ def _init_legend_box(self, handles, labels):
# is an instance of offsetbox.TextArea which contains legend
# text.
-
text_list = [] # the list of text instances
handle_list = [] # the list of text instances
@@ -589,14 +583,13 @@ def _init_legend_box(self, handles, labels):
labelboxes = []
handleboxes = []
-
# The approximate height and descent of text. These values are
# only used for plotting the legend handle.
descent = 0.35*self._approx_text_height()*(self.handleheight - 0.7)
# 0.35 and 0.7 are just heuristic numbers. this may need to be improbed
height = self._approx_text_height() * self.handleheight - descent
# each handle needs to be drawn inside a box of (x, y, w, h) =
- # (0, -descent, width, height). And their corrdinates should
+ # (0, -descent, width, height). And their coordinates should
# be given in the display coordinates.
# The transformation of each handle will be automatically set
@@ -604,7 +597,6 @@ def _init_legend_box(self, handles, labels):
# default trasnform (eg, Collections), you need to
# manually set their transform to the self.get_transform().
-
legend_handler_map = self.get_legend_handler_map()
for orig_handle, lab in zip(handles, labels):
@@ -632,12 +624,8 @@ def _init_legend_box(self, handles, labels):
handlebox)
handle_list.append(handle)
-
-
-
handleboxes.append(handlebox)
-
if len(handleboxes) > 0:
# We calculate number of rows in each column. The first
diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py
index 777270e68e49..c5f285f75452 100644
--- a/lib/matplotlib/legend_handler.py
+++ b/lib/matplotlib/legend_handler.py
@@ -154,10 +154,9 @@ def get_xdata(self, legend, xdescent, ydescent, width, height, fontsize):
return xdata, xdata_marker
-
class HandlerNpointsYoffsets(HandlerNpoints):
def __init__(self, numpoints=None, yoffsets=None, **kw):
- HandlerNpoints.__init__(self,numpoints=numpoints, **kw)
+ HandlerNpoints.__init__(self, numpoints=numpoints, **kw)
self._yoffsets = yoffsets
def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
@@ -169,9 +168,6 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
return ydata
-
-
-
class HandlerLine2D(HandlerNpoints):
"""
Handler for Line2D instances
@@ -179,7 +175,6 @@ class HandlerLine2D(HandlerNpoints):
def __init__(self, marker_pad=0.3, numpoints=None, **kw):
HandlerNpoints.__init__(self, marker_pad=marker_pad, numpoints=numpoints, **kw)
-
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize,
trans):
@@ -198,7 +193,6 @@ def create_artists(self, legend, orig_handle,
legline.set_drawstyle('default')
legline.set_marker("")
-
legline_marker = Line2D(xdata_marker, ydata[:len(xdata_marker)])
self.update_prop(legline_marker, orig_handle, legend)
#legline_marker.update_from(orig_handle)
@@ -217,7 +211,6 @@ def create_artists(self, legend, orig_handle,
return [legline, legline_marker]
-
class HandlerPatch(HandlerBase):
"""
Handler for Patches
@@ -250,7 +243,6 @@ def create_artists(self, legend, orig_handle,
return [p]
-
class HandlerLineCollection(HandlerLine2D):
"""
Handler for LineCollections
@@ -285,7 +277,6 @@ def create_artists(self, legend, orig_handle,
return [legline]
-
class HandlerRegularPolyCollection(HandlerNpointsYoffsets):
"""
Handler for RegularPolyCollections.
@@ -340,11 +331,9 @@ def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize,
trans):
-
xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
width, height, fontsize)
-
ydata = self.get_ydata(legend, xdescent, ydescent,
width, height, fontsize)
@@ -362,6 +351,7 @@ def create_artists(self, legend, orig_handle,
return [p]
+
class HandlerPathCollection(HandlerRegularPolyCollection):
"""
Handler for PathCollections, which are used by scatter
@@ -410,8 +400,6 @@ def get_err_size(self, legend, xdescent, ydescent, width, height, fontsize):
return xerr_size, yerr_size
-
-
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize,
trans):
@@ -431,7 +419,6 @@ def create_artists(self, legend, orig_handle,
xerr_size, yerr_size = self.get_err_size(legend, xdescent, ydescent,
width, height, fontsize)
-
legline_marker = Line2D(xdata_marker, ydata_marker)
# when plotlines are None (only errorbars are drawn), we just
@@ -452,7 +439,6 @@ def create_artists(self, legend, orig_handle,
newsz = legline_marker.get_markersize()*legend.markerscale
legline_marker.set_markersize(newsz)
-
handle_barlinecols = []
handle_caplines = []
@@ -501,7 +487,6 @@ def create_artists(self, legend, orig_handle,
return artists
-
class HandlerStem(HandlerNpointsYoffsets):
"""
Handler for Errorbars
@@ -516,7 +501,6 @@ def __init__(self, marker_pad=0.3, numpoints=None,
self._bottom = bottom
-
def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
if self._yoffsets is None:
ydata = height*(0.5*legend._scatteryoffsets + 0.5)
@@ -525,7 +509,6 @@ def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
return ydata
-
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize,
trans):
diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py
index 73850af48dac..0579c3e67565 100644
--- a/lib/matplotlib/markers.py
+++ b/lib/matplotlib/markers.py
@@ -113,6 +113,16 @@ def __init__(self, marker=None, fillstyle='full'):
self.set_marker(marker)
self.set_fillstyle(fillstyle)
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d.pop('_marker_function')
+ return d
+
+ def __setstate__(self, statedict):
+ self.__dict__ = statedict
+ self.set_marker(self._marker)
+ self._recache()
+
def _recache(self):
self._path = Path(np.empty((0,2)))
self._transform = IdentityTransform()
diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py
index 8a6030100b80..2f96dee80796 100644
--- a/lib/matplotlib/offsetbox.py
+++ b/lib/matplotlib/offsetbox.py
@@ -131,7 +131,6 @@ def _get_aligned_offsets(hd_list, height, align="baseline"):
return height, descent, offsets
-
class OffsetBox(martist.Artist):
"""
The OffsetBox is a simple container artist. The child artist are meant
@@ -144,6 +143,24 @@ def __init__(self, *args, **kwargs):
self._children = []
self._offset = (0, 0)
+ def __getstate__(self):
+ state = martist.Artist.__getstate__(self)
+
+ # pickle cannot save instancemethods, so handle them here
+ from cbook import _InstanceMethodPickler
+ import inspect
+
+ offset = state['_offset']
+ if inspect.ismethod(offset):
+ state['_offset'] = _InstanceMethodPickler(offset)
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ from cbook import _InstanceMethodPickler
+ if isinstance(self._offset, _InstanceMethodPickler):
+ self._offset = self._offset.get_instancemethod()
+
def set_figure(self, fig):
"""
Set the figure
@@ -343,7 +360,7 @@ def get_extent_offsets(self, renderer):
class HPacker(PackerBase):
"""
The HPacker has its children packed horizontally. It automatically
- adjust the relative positions of children in the drawing time.
+ adjusts the relative positions of children at draw time.
"""
def __init__(self, pad=None, sep=None, width=None, height=None,
align="baseline", mode="fixed",
@@ -364,12 +381,10 @@ def __init__(self, pad=None, sep=None, width=None, height=None,
super(HPacker, self).__init__(pad, sep, width, height,
align, mode, children)
-
def get_extent_offsets(self, renderer):
"""
update offset of children and return the extents of the box
"""
-
dpicor = renderer.points_to_pixels(1.)
pad = self.pad * dpicor
sep = self.sep * dpicor
@@ -391,7 +406,6 @@ def get_extent_offsets(self, renderer):
self.height,
self.align)
-
pack_list = [(w, xd) for w,h,xd,yd in whd_list]
width, xoffsets_ = _get_packed_offsets(pack_list, self.width,
sep, self.mode)
@@ -406,8 +420,6 @@ def get_extent_offsets(self, renderer):
zip(xoffsets, yoffsets)
-
-
class PaddedBox(OffsetBox):
def __init__(self, child, pad=None, draw_frame=False, patch_attrs=None):
"""
@@ -438,7 +450,6 @@ def __init__(self, child, pad=None, draw_frame=False, patch_attrs=None):
self._drawFrame = draw_frame
-
def get_extent_offsets(self, renderer):
"""
update offset of childrens and return the extents of the box
@@ -453,7 +464,6 @@ def get_extent_offsets(self, renderer):
xd+pad, yd+pad, \
[(0, 0)]
-
def draw(self, renderer):
"""
Update the location of children if necessary and draw them
@@ -517,8 +527,6 @@ def __init__(self, width, height, xdescent=0.,
self.dpi_transform = mtransforms.Affine2D()
-
-
def get_transform(self):
"""
Return the :class:`~matplotlib.transforms.Transform` applied
@@ -532,7 +540,6 @@ def set_transform(self, t):
"""
pass
-
def set_offset(self, xy):
"""
set offset of the container.
@@ -544,14 +551,12 @@ def set_offset(self, xy):
self.offset_transform.clear()
self.offset_transform.translate(xy[0], xy[1])
-
def get_offset(self):
"""
return offset of the container.
"""
return self._offset
-
def get_window_extent(self, renderer):
'''
get the bounding box in display space.
@@ -560,7 +565,6 @@ def get_window_extent(self, renderer):
ox, oy = self.get_offset() #w, h, xd, yd)
return mtransforms.Bbox.from_bounds(ox-xd, oy-yd, w, h)
-
def get_extent(self, renderer):
"""
Return with, height, xdescent, ydescent of box
@@ -571,14 +575,12 @@ def get_extent(self, renderer):
return self.width*dpi_cor, self.height*dpi_cor, \
self.xdescent*dpi_cor, self.ydescent*dpi_cor
-
def add_artist(self, a):
'Add any :class:`~matplotlib.artist.Artist` to the container box'
self._children.append(a)
if not a.is_transform_set():
a.set_transform(self.get_transform())
-
def draw(self, renderer):
"""
Draw the children
@@ -601,9 +603,6 @@ class TextArea(OffsetBox):
of the TextArea instance is the width and height of the its child
text.
"""
-
-
-
def __init__(self, s,
textprops=None,
multilinebaseline=None,
@@ -639,7 +638,6 @@ def __init__(self, s,
self._multilinebaseline = multilinebaseline
self._minimumdescent = minimumdescent
-
def set_text(self, s):
"set text"
self._text.set_text(s)
@@ -658,14 +656,12 @@ def set_multilinebaseline(self, t):
"""
self._multilinebaseline = t
-
def get_multilinebaseline(self):
"""
get multilinebaseline .
"""
return self._multilinebaseline
-
def set_minimumdescent(self, t):
"""
Set minimumdescent .
@@ -675,21 +671,18 @@ def set_minimumdescent(self, t):
"""
self._minimumdescent = t
-
def get_minimumdescent(self):
"""
get minimumdescent.
"""
return self._minimumdescent
-
def set_transform(self, t):
"""
set_transform is ignored.
"""
pass
-
def set_offset(self, xy):
"""
set offset of the container.
@@ -701,14 +694,12 @@ def set_offset(self, xy):
self.offset_transform.clear()
self.offset_transform.translate(xy[0], xy[1])
-
def get_offset(self):
"""
return offset of the container.
"""
return self._offset
-
def get_window_extent(self, renderer):
'''
get the bounding box in display space.
@@ -717,7 +708,6 @@ def get_window_extent(self, renderer):
ox, oy = self.get_offset() #w, h, xd, yd)
return mtransforms.Bbox.from_bounds(ox-xd, oy-yd, w, h)
-
def get_extent(self, renderer):
clean_line, ismath = self._text.is_math_text(self._text._text)
_, h_, d_ = renderer.get_text_width_height_descent(
@@ -733,7 +723,6 @@ def get_extent(self, renderer):
self._baseline_transform.clear()
-
if len(info) > 1 and self._multilinebaseline:
d_new = 0.5 * h - 0.5 * (h_ - d_)
self._baseline_transform.translate(0, d - d_new)
@@ -754,7 +743,6 @@ def get_extent(self, renderer):
return w, h, 0., d
-
def draw(self, renderer):
"""
Draw the children
@@ -765,7 +753,6 @@ def draw(self, renderer):
bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
-
class AuxTransformBox(OffsetBox):
"""
Offset Box with the aux_transform . Its children will be
@@ -780,7 +767,6 @@ class AuxTransformBox(OffsetBox):
children. Furthermore, the extent of the children will be
calculated in the transformed coordinate.
"""
-
def __init__(self, aux_transform):
self.aux_transform = aux_transform
OffsetBox.__init__(self)
@@ -805,7 +791,6 @@ def get_transform(self):
Return the :class:`~matplotlib.transforms.Transform` applied
to the children
"""
-
return self.aux_transform + \
self.ref_offset_transform + \
self.offset_transform
@@ -816,7 +801,6 @@ def set_transform(self, t):
"""
pass
-
def set_offset(self, xy):
"""
set offset of the container.
@@ -828,14 +812,12 @@ def set_offset(self, xy):
self.offset_transform.clear()
self.offset_transform.translate(xy[0], xy[1])
-
def get_offset(self):
"""
return offset of the container.
"""
return self._offset
-
def get_window_extent(self, renderer):
'''
get the bounding box in display space.
@@ -844,7 +826,6 @@ def get_window_extent(self, renderer):
ox, oy = self.get_offset() #w, h, xd, yd)
return mtransforms.Bbox.from_bounds(ox-xd, oy-yd, w, h)
-
def get_extent(self, renderer):
# clear the offset transforms
@@ -856,7 +837,6 @@ def get_extent(self, renderer):
bboxes = [c.get_window_extent(renderer) for c in self._children]
ub = mtransforms.Bbox.union(bboxes)
-
# adjust ref_offset_tansform
self.ref_offset_transform.translate(-ub.x0, -ub.y0)
@@ -866,7 +846,6 @@ def get_extent(self, renderer):
return ub.width, ub.height, 0., 0.
-
def draw(self, renderer):
"""
Draw the children
@@ -878,7 +857,6 @@ def draw(self, renderer):
bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
-
class AnchoredOffsetbox(OffsetBox):
"""
An offset box placed according to the legend location
@@ -887,7 +865,6 @@ class AnchoredOffsetbox(OffsetBox):
the offset box is anchored against its parent axes. You may
explicitly specify the bbox_to_anchor.
"""
-
zorder = 5 # zorder of the legend
def __init__(self, loc,
@@ -927,7 +904,6 @@ def __init__(self, loc,
bbox_transform : with which the bbox_to_anchor will be transformed.
"""
-
super(AnchoredOffsetbox, self).__init__(**kwargs)
self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
@@ -955,9 +931,6 @@ def __init__(self, loc,
self.patch.set_boxstyle("square",pad=0)
self._drawFrame = frameon
-
-
-
def set_child(self, child):
"set the child to be anchored"
self._child = child
@@ -970,7 +943,6 @@ def get_children(self):
"return the list of children"
return [self._child]
-
def get_extent(self, renderer):
"""
return the extent of the artist. The extent of the child
@@ -982,7 +954,6 @@ def get_extent(self, renderer):
return w+2*pad, h+2*pad, xd+pad, yd+pad
-
def get_bbox_to_anchor(self):
"""
return the bbox that the legend will be anchored
@@ -997,9 +968,6 @@ def get_bbox_to_anchor(self):
return TransformedBbox(self._bbox_to_anchor,
transform)
-
-
-
def set_bbox_to_anchor(self, bbox, transform=None):
"""
set the bbox that the child will be anchored.
@@ -1024,7 +992,6 @@ def set_bbox_to_anchor(self, bbox, transform=None):
self._bbox_to_anchor_transform = transform
-
def get_window_extent(self, renderer):
'''
get the bounding box in display space.
@@ -1034,7 +1001,6 @@ def get_window_extent(self, renderer):
ox, oy = self.get_offset(w, h, xd, yd, renderer)
return Bbox.from_bounds(ox-xd, oy-yd, w, h)
-
def _update_offset_func(self, renderer, fontsize=None):
"""
Update the offset func which depends on the dpi of the
@@ -1053,10 +1019,9 @@ def _offset(w, h, xd, yd, renderer, fontsize=fontsize, self=self):
bbox_to_anchor,
borderpad)
return x0+xd, y0+yd
-
+
self.set_offset(_offset)
-
def update_frame(self, bbox, fontsize=None):
self.patch.set_bounds(bbox.x0, bbox.y0,
bbox.width, bbox.height)
@@ -1086,8 +1051,6 @@ def draw(self, renderer):
self.get_child().set_offset((px, py))
self.get_child().draw(renderer)
-
-
def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad):
"""
return the position of the bbox anchored at the parentbbox
@@ -1141,7 +1104,6 @@ def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, **kwargs):
**kwargs)
-
class OffsetImage(OffsetBox):
def __init__(self, arr,
zoom=1,
@@ -1176,7 +1138,6 @@ def __init__(self, arr,
OffsetBox.__init__(self)
-
def set_data(self, arr):
self._data = np.asarray(arr)
self.image.set_data(self._data)
@@ -1205,8 +1166,6 @@ def get_zoom(self):
# self.offset_transform.clear()
# self.offset_transform.translate(xy[0], xy[1])
-
-
def get_offset(self):
"""
return offset of the container.
@@ -1224,7 +1183,6 @@ def get_window_extent(self, renderer):
ox, oy = self.get_offset()
return mtransforms.Bbox.from_bounds(ox-xd, oy-yd, w, h)
-
def get_extent(self, renderer):
if self._dpi_cor: # True, do correction
@@ -1239,26 +1197,21 @@ def get_extent(self, renderer):
return w, h, 0, 0
-
-
def draw(self, renderer):
"""
Draw the children
"""
-
self.image.draw(renderer)
-
#bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
-
from matplotlib.text import _AnnotationBase
+
class AnnotationBbox(martist.Artist, _AnnotationBase):
"""
Annotation-like class, but with offsetbox instead of Text.
"""
-
zorder = 3
def __str__(self):
@@ -1328,7 +1281,6 @@ def __init__(self, offsetbox, xy,
self.patch.set(**bboxprops)
self._drawFrame = frameon
-
def contains(self,event):
t,tinfo = self.offsetbox.contains(event)
#if self.arrow_patch is not None:
@@ -1339,7 +1291,6 @@ def contains(self,event):
return t,tinfo
-
def get_children(self):
children = [self.offsetbox, self.patch]
if self.arrow_patch:
@@ -1379,7 +1330,6 @@ def update_positions(self, renderer):
if self.arrow_patch:
self.arrow_patch.set_mutation_scale(mutation_scale)
-
def _update_position_xybox(self, renderer, xy_pixel):
"Update the pixel positions of the annotation text and the arrow patch."
@@ -1437,8 +1387,6 @@ def _update_position_xybox(self, renderer, xy_pixel):
patchA = d.pop("patchA", self.patch)
self.arrow_patch.set_patchA(patchA)
-
-
def draw(self, renderer):
"""
Draw the :class:`Annotation` object to the given *renderer*.
@@ -1466,7 +1414,6 @@ def draw(self, renderer):
self.offsetbox.draw(renderer)
-
class DraggableBase(object):
"""
helper code for a draggable artist (legend, offsetbox)
@@ -1635,7 +1582,7 @@ def finalize_offset(self):
if __name__ == "__main__":
-
+ import matplotlib.pyplot as plt
fig = plt.figure(1)
fig.clf()
ax = plt.subplot(121)
@@ -1672,7 +1619,6 @@ def finalize_offset(self):
)
ax.add_artist(ann)
-
+
plt.draw()
plt.show()
-
diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py
index 793052567fb8..70fe014f5f32 100644
--- a/lib/matplotlib/patches.py
+++ b/lib/matplotlib/patches.py
@@ -1629,7 +1629,6 @@ def pprint_styles(klass):
"""
return _pprint_styles(klass._style_list)
-
@classmethod
def register(klass, name, style):
"""
@@ -1699,9 +1698,6 @@ def __init__(self):
"""
super(BoxStyle._Base, self).__init__()
-
-
-
def transmute(self, x0, y0, width, height, mutation_size):
"""
The transmute method is a very core of the
@@ -1713,8 +1709,6 @@ def transmute(self, x0, y0, width, height, mutation_size):
"""
raise NotImplementedError('Derived must override')
-
-
def __call__(self, x0, y0, width, height, mutation_size,
aspect_ratio=1.):
"""
@@ -1740,6 +1734,14 @@ def __call__(self, x0, y0, width, height, mutation_size,
else:
return self.transmute(x0, y0, width, height, mutation_size)
+ def __reduce__(self):
+ # because we have decided to nest thes classes, we need to
+ # add some more information to allow instance pickling.
+ import matplotlib.cbook as cbook
+ return (cbook._NestedClassGetter(),
+ (BoxStyle, self.__class__.__name__),
+ self.__dict__
+ )
class Square(_Base):
@@ -2308,9 +2310,6 @@ def get_bbox(self):
return transforms.Bbox.from_bounds(self._x, self._y, self._width, self._height)
-
-
-
from matplotlib.bezier import split_bezier_intersecting_with_closedpath
from matplotlib.bezier import get_intersection, inside_circle, get_parallels
from matplotlib.bezier import make_wedged_bezier2
@@ -2413,7 +2412,6 @@ def insideB(xy_display):
return path
-
def _shrink(self, path, shrinkA, shrinkB):
"""
Shrink the path by fixed size (in points) with shrinkA and shrinkB
@@ -2454,6 +2452,15 @@ def __call__(self, posA, posB,
return shrinked_path
+ def __reduce__(self):
+ # because we have decided to nest thes classes, we need to
+ # add some more information to allow instance pickling.
+ import matplotlib.cbook as cbook
+ return (cbook._NestedClassGetter(),
+ (ConnectionStyle, self.__class__.__name__),
+ self.__dict__
+ )
+
class Arc3(_Base):
"""
@@ -2783,7 +2790,6 @@ def connect(self, posA, posB):
{"AvailableConnectorstyles": _pprint_styles(_style_list)}
-
class ArrowStyle(_Style):
"""
:class:`ArrowStyle` is a container class which defines several
@@ -2879,8 +2885,6 @@ class and must be overriden in the subclasses. It receives
raise NotImplementedError('Derived must override')
-
-
def __call__(self, path, mutation_size, linewidth,
aspect_ratio=1.):
"""
@@ -2914,6 +2918,14 @@ def __call__(self, path, mutation_size, linewidth,
else:
return self.transmute(path, mutation_size, linewidth)
+ def __reduce__(self):
+ # because we have decided to nest thes classes, we need to
+ # add some more information to allow instance pickling.
+ import matplotlib.cbook as cbook
+ return (cbook._NestedClassGetter(),
+ (ArrowStyle, self.__class__.__name__),
+ self.__dict__
+ )
class _Curve(_Base):
@@ -3060,7 +3072,6 @@ def __init__(self):
_style_list["-"] = Curve
-
class CurveA(_Curve):
"""
An arrow with a head at its begin point.
@@ -3099,7 +3110,6 @@ def __init__(self, head_length=.4, head_width=.2):
beginarrow=False, endarrow=True,
head_length=head_length, head_width=head_width )
- #_style_list["->"] = CurveB
_style_list["->"] = CurveB
@@ -3121,11 +3131,9 @@ def __init__(self, head_length=.4, head_width=.2):
beginarrow=True, endarrow=True,
head_length=head_length, head_width=head_width )
- #_style_list["<->"] = CurveAB
_style_list["<->"] = CurveAB
-
class CurveFilledA(_Curve):
"""
An arrow with filled triangle head at the begin.
diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py
index 126c59289a42..60702ff841ad 100644
--- a/lib/matplotlib/projections/polar.py
+++ b/lib/matplotlib/projections/polar.py
@@ -18,201 +18,209 @@
ScaledTranslation, blended_transform_factory, BboxTransformToMaxOnly
import matplotlib.spines as mspines
-class PolarAxes(Axes):
- """
- A polar graph projection, where the input dimensions are *theta*, *r*.
- Theta starts pointing east and goes anti-clockwise.
+class PolarTransform(Transform):
"""
- name = 'polar'
-
- class PolarTransform(Transform):
- """
- The base polar transform. This handles projection *theta* and
- *r* into Cartesian coordinate space *x* and *y*, but does not
- perform the ultimate affine transformation into the correct
- position.
- """
- input_dims = 2
- output_dims = 2
- is_separable = False
-
- def __init__(self, axis=None, use_rmin=True):
- Transform.__init__(self)
- self._axis = axis
- self._use_rmin = use_rmin
-
- def transform_non_affine(self, tr):
- xy = np.empty(tr.shape, np.float_)
- if self._axis is not None:
- if self._use_rmin:
- rmin = self._axis.viewLim.ymin
- else:
- rmin = 0
- theta_offset = self._axis.get_theta_offset()
- theta_direction = self._axis.get_theta_direction()
+ The base polar transform. This handles projection *theta* and
+ *r* into Cartesian coordinate space *x* and *y*, but does not
+ perform the ultimate affine transformation into the correct
+ position.
+ """
+ input_dims = 2
+ output_dims = 2
+ is_separable = False
+
+ def __init__(self, axis=None, use_rmin=True):
+ Transform.__init__(self)
+ self._axis = axis
+ self._use_rmin = use_rmin
+
+ def transform_non_affine(self, tr):
+ xy = np.empty(tr.shape, np.float_)
+ if self._axis is not None:
+ if self._use_rmin:
+ rmin = self._axis.viewLim.ymin
else:
rmin = 0
- theta_offset = 0
- theta_direction = 1
-
- t = tr[:, 0:1]
- r = tr[:, 1:2]
- x = xy[:, 0:1]
- y = xy[:, 1:2]
-
- t *= theta_direction
- t += theta_offset
-
- if rmin != 0:
- r = r - rmin
- mask = r < 0
- x[:] = np.where(mask, np.nan, r * np.cos(t))
- y[:] = np.where(mask, np.nan, r * np.sin(t))
- else:
- x[:] = r * np.cos(t)
- y[:] = r * np.sin(t)
+ theta_offset = self._axis.get_theta_offset()
+ theta_direction = self._axis.get_theta_direction()
+ else:
+ rmin = 0
+ theta_offset = 0
+ theta_direction = 1
+
+ t = tr[:, 0:1]
+ r = tr[:, 1:2]
+ x = xy[:, 0:1]
+ y = xy[:, 1:2]
+
+ t *= theta_direction
+ t += theta_offset
+
+ if rmin != 0:
+ r = r - rmin
+ mask = r < 0
+ x[:] = np.where(mask, np.nan, r * np.cos(t))
+ y[:] = np.where(mask, np.nan, r * np.sin(t))
+ else:
+ x[:] = r * np.cos(t)
+ y[:] = r * np.sin(t)
- return xy
- transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
+ return xy
+ transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
- def transform_path_non_affine(self, path):
- vertices = path.vertices
- if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]:
- return Path(self.transform(vertices), path.codes)
- ipath = path.interpolated(path._interpolation_steps)
- return Path(self.transform(ipath.vertices), ipath.codes)
- transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__
+ def transform_path_non_affine(self, path):
+ vertices = path.vertices
+ if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]:
+ return Path(self.transform(vertices), path.codes)
+ ipath = path.interpolated(path._interpolation_steps)
+ return Path(self.transform(ipath.vertices), ipath.codes)
+ transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__
- def inverted(self):
- return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin)
- inverted.__doc__ = Transform.inverted.__doc__
+ def inverted(self):
+ return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin)
+ inverted.__doc__ = Transform.inverted.__doc__
- class PolarAffine(Affine2DBase):
- """
- The affine part of the polar projection. Scales the output so
- that maximum radius rests on the edge of the axes circle.
- """
- def __init__(self, scale_transform, limits):
- """
- *limits* is the view limit of the data. The only part of
- its bounds that is used is ymax (for the radius maximum).
- The theta range is always fixed to (0, 2pi).
- """
- Affine2DBase.__init__(self)
- self._scale_transform = scale_transform
- self._limits = limits
- self.set_children(scale_transform, limits)
- self._mtx = None
-
- def get_matrix(self):
- if self._invalid:
- limits_scaled = self._limits.transformed(self._scale_transform)
- yscale = limits_scaled.ymax - limits_scaled.ymin
- affine = Affine2D() \
- .scale(0.5 / yscale) \
- .translate(0.5, 0.5)
- self._mtx = affine.get_matrix()
- self._inverted = None
- self._invalid = 0
- return self._mtx
- get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__
-
- class InvertedPolarTransform(Transform):
- """
- The inverse of the polar transform, mapping Cartesian
- coordinate space *x* and *y* back to *theta* and *r*.
- """
- input_dims = 2
- output_dims = 2
- is_separable = False
-
- def __init__(self, axis=None, use_rmin=True):
- Transform.__init__(self)
- self._axis = axis
- self._use_rmin = use_rmin
-
- def transform_non_affine(self, xy):
- if self._axis is not None:
- if self._use_rmin:
- rmin = self._axis.viewLim.ymin
- else:
- rmin = 0
- theta_offset = self._axis.get_theta_offset()
- theta_direction = self._axis.get_theta_direction()
+
+class PolarAffine(Affine2DBase):
+ """
+ The affine part of the polar projection. Scales the output so
+ that maximum radius rests on the edge of the axes circle.
+ """
+ def __init__(self, scale_transform, limits):
+ """
+ *limits* is the view limit of the data. The only part of
+ its bounds that is used is ymax (for the radius maximum).
+ The theta range is always fixed to (0, 2pi).
+ """
+ Affine2DBase.__init__(self)
+ self._scale_transform = scale_transform
+ self._limits = limits
+ self.set_children(scale_transform, limits)
+ self._mtx = None
+
+ def get_matrix(self):
+ if self._invalid:
+ limits_scaled = self._limits.transformed(self._scale_transform)
+ yscale = limits_scaled.ymax - limits_scaled.ymin
+ affine = Affine2D() \
+ .scale(0.5 / yscale) \
+ .translate(0.5, 0.5)
+ self._mtx = affine.get_matrix()
+ self._inverted = None
+ self._invalid = 0
+ return self._mtx
+ get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__
+
+ def __getstate__(self):
+ return {}
+
+
+class InvertedPolarTransform(Transform):
+ """
+ The inverse of the polar transform, mapping Cartesian
+ coordinate space *x* and *y* back to *theta* and *r*.
+ """
+ input_dims = 2
+ output_dims = 2
+ is_separable = False
+
+ def __init__(self, axis=None, use_rmin=True):
+ Transform.__init__(self)
+ self._axis = axis
+ self._use_rmin = use_rmin
+
+ def transform_non_affine(self, xy):
+ if self._axis is not None:
+ if self._use_rmin:
+ rmin = self._axis.viewLim.ymin
else:
rmin = 0
- theta_offset = 0
- theta_direction = 1
+ theta_offset = self._axis.get_theta_offset()
+ theta_direction = self._axis.get_theta_direction()
+ else:
+ rmin = 0
+ theta_offset = 0
+ theta_direction = 1
- x = xy[:, 0:1]
- y = xy[:, 1:]
- r = np.sqrt(x*x + y*y)
- theta = np.arccos(x / r)
- theta = np.where(y < 0, 2 * np.pi - theta, theta)
+ x = xy[:, 0:1]
+ y = xy[:, 1:]
+ r = np.sqrt(x*x + y*y)
+ theta = np.arccos(x / r)
+ theta = np.where(y < 0, 2 * np.pi - theta, theta)
- theta -= theta_offset
- theta *= theta_direction
+ theta -= theta_offset
+ theta *= theta_direction
- r += rmin
+ r += rmin
- return np.concatenate((theta, r), 1)
- transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
+ return np.concatenate((theta, r), 1)
+ transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
- def inverted(self):
- return PolarAxes.PolarTransform(self._axis, self._use_rmin)
- inverted.__doc__ = Transform.inverted.__doc__
+ def inverted(self):
+ return PolarAxes.PolarTransform(self._axis, self._use_rmin)
+ inverted.__doc__ = Transform.inverted.__doc__
- class ThetaFormatter(Formatter):
- """
- Used to format the *theta* tick labels. Converts the native
- unit of radians into degrees and adds a degree symbol.
- """
- def __call__(self, x, pos=None):
- # \u00b0 : degree symbol
- if rcParams['text.usetex'] and not rcParams['text.latex.unicode']:
- return r"$%0.0f^\circ$" % ((x / np.pi) * 180.0)
- else:
- # we use unicode, rather than mathtext with \circ, so
- # that it will work correctly with any arbitrary font
- # (assuming it has a degree sign), whereas $5\circ$
- # will only work correctly with one of the supported
- # math fonts (Computer Modern and STIX)
- return u"%0.0f\u00b0" % ((x / np.pi) * 180.0)
-
- class RadialLocator(Locator):
- """
- Used to locate radius ticks.
- Ensures that all ticks are strictly positive. For all other
- tasks, it delegates to the base
- :class:`~matplotlib.ticker.Locator` (which may be different
- depending on the scale of the *r*-axis.
- """
- def __init__(self, base):
- self.base = base
+class ThetaFormatter(Formatter):
+ """
+ Used to format the *theta* tick labels. Converts the native
+ unit of radians into degrees and adds a degree symbol.
+ """
+ def __call__(self, x, pos=None):
+ # \u00b0 : degree symbol
+ if rcParams['text.usetex'] and not rcParams['text.latex.unicode']:
+ return r"$%0.0f^\circ$" % ((x / np.pi) * 180.0)
+ else:
+ # we use unicode, rather than mathtext with \circ, so
+ # that it will work correctly with any arbitrary font
+ # (assuming it has a degree sign), whereas $5\circ$
+ # will only work correctly with one of the supported
+ # math fonts (Computer Modern and STIX)
+ return u"%0.0f\u00b0" % ((x / np.pi) * 180.0)
+
+
+class RadialLocator(Locator):
+ """
+ Used to locate radius ticks.
+
+ Ensures that all ticks are strictly positive. For all other
+ tasks, it delegates to the base
+ :class:`~matplotlib.ticker.Locator` (which may be different
+ depending on the scale of the *r*-axis.
+ """
+ def __init__(self, base):
+ self.base = base
- def __call__(self):
- ticks = self.base()
- return [x for x in ticks if x > 0]
+ def __call__(self):
+ ticks = self.base()
+ return [x for x in ticks if x > 0]
- def autoscale(self):
- return self.base.autoscale()
+ def autoscale(self):
+ return self.base.autoscale()
- def pan(self, numsteps):
- return self.base.pan(numsteps)
+ def pan(self, numsteps):
+ return self.base.pan(numsteps)
- def zoom(self, direction):
- return self.base.zoom(direction)
+ def zoom(self, direction):
+ return self.base.zoom(direction)
- def refresh(self):
- return self.base.refresh()
+ def refresh(self):
+ return self.base.refresh()
- def view_limits(self, vmin, vmax):
- vmin, vmax = self.base.view_limits(vmin, vmax)
- return 0, vmax
+ def view_limits(self, vmin, vmax):
+ vmin, vmax = self.base.view_limits(vmin, vmax)
+ return 0, vmax
+class PolarAxes(Axes):
+ """
+ A polar graph projection, where the input dimensions are *theta*, *r*.
+
+ Theta starts pointing east and goes anti-clockwise.
+ """
+ name = 'polar'
+
def __init__(self, *args, **kwargs):
"""
Create a new Polar Axes for a polar plot.
@@ -451,8 +459,10 @@ def set_yscale(self, *args, **kwargs):
self.yaxis.set_major_locator(
self.RadialLocator(self.yaxis.get_major_locator()))
- set_rscale = Axes.set_yscale
- set_rticks = Axes.set_yticks
+ def set_rscale(self, *args, **kwargs):
+ return Axes.set_yscale(self, *args, **kwargs)
+ def set_rticks(self, *args, **kwargs):
+ return Axes.set_yticks(self, *args, **kwargs)
@docstring.dedent_interpd
def set_thetagrids(self, angles, labels=None, frac=None, fmt=None,
@@ -647,6 +657,17 @@ def drag_pan(self, button, key, x, y):
scale = r / startr
self.set_rmax(p.rmax / scale)
+
+# to keep things all self contained, we can put aliases to the Polar classes
+# defined above. This isn't strictly necessary, but it makes some of the
+# code more readable (and provides a backwards compatible Polar API)
+PolarAxes.PolarTransform = PolarTransform
+PolarAxes.PolarAffine = PolarAffine
+PolarAxes.InvertedPolarTransform = InvertedPolarTransform
+PolarAxes.ThetaFormatter = ThetaFormatter
+PolarAxes.RadialLocator = RadialLocator
+
+
# These are a couple of aborted attempts to project a polar plot using
# cubic bezier curves.
diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py
index dcd46f87beb6..effc065083f2 100644
--- a/lib/matplotlib/pyplot.py
+++ b/lib/matplotlib/pyplot.py
@@ -94,7 +94,7 @@ def _backend_selection():
## Global ##
from matplotlib.backends import pylab_setup
-new_figure_manager, draw_if_interactive, _show = pylab_setup()
+_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup()
@docstring.copy_dedent(Artist.findobj)
def findobj(o=None, match=None):
@@ -102,6 +102,7 @@ def findobj(o=None, match=None):
o = gcf()
return o.findobj(match)
+
def switch_backend(newbackend):
"""
Switch the default backend. This feature is **experimental**, and
@@ -115,10 +116,10 @@ def switch_backend(newbackend):
Calling this command will close all open windows.
"""
close('all')
- global new_figure_manager, draw_if_interactive, _show
+ global _backend_mod, new_figure_manager, draw_if_interactive, _show
matplotlib.use(newbackend, warn=False, force=True)
from matplotlib.backends import pylab_setup
- new_figure_manager, draw_if_interactive, _show = pylab_setup()
+ _backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup()
def show(*args, **kw):
@@ -312,22 +313,17 @@ class that will be passed on to :meth:`new_figure_manager` in the
if edgecolor is None : edgecolor = rcParams['figure.edgecolor']
allnums = get_fignums()
+ next_num = max(allnums) + 1 if allnums else 1
figLabel = ''
if num is None:
- if allnums:
- num = max(allnums) + 1
- else:
- num = 1
+ num = next_num
elif is_string_like(num):
figLabel = num
allLabels = get_figlabels()
if figLabel not in allLabels:
if figLabel == 'all':
warnings.warn("close('all') closes all existing figures")
- if len(allLabels):
- num = max(allnums) + 1
- else:
- num = 1
+ num = next_num
else:
inum = allLabels.index(figLabel)
num = allnums[inum]
@@ -363,6 +359,7 @@ def make_active(event):
draw_if_interactive()
return figManager.canvas.figure
+
def gcf():
"Return a reference to the current figure."
diff --git a/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png b/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png
new file mode 100644
index 000000000000..1e15b6d94fd4
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png differ
diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py
new file mode 100644
index 000000000000..5655b6e7e68f
--- /dev/null
+++ b/lib/matplotlib/tests/test_pickle.py
@@ -0,0 +1,199 @@
+from __future__ import print_function
+
+import numpy as np
+
+from matplotlib.testing.decorators import cleanup, image_comparison
+import matplotlib.pyplot as plt
+
+from nose.tools import assert_equal, assert_not_equal
+
+# cpickle is faster, pickle gives better exceptions
+import cPickle as pickle
+#import pickle
+
+from cStringIO import StringIO
+
+
+def depth_getter(obj,
+ current_depth=0,
+ depth_stack=None,
+ nest_info='top level object'):
+ """
+ Returns a dictionary mapping:
+
+ id(obj): (shallowest_depth, obj, nest_info)
+
+ for the given object (and its subordinates).
+
+ This, in conjunction with recursive_pickle, can be used to debug
+ pickling issues, although finding others is sometimes a case of
+ trial and error.
+
+ """
+ if depth_stack is None:
+ depth_stack = {}
+
+ if id(obj) in depth_stack:
+ stack = depth_stack[id(obj)]
+ if stack[0] > current_depth:
+ del depth_stack[id(obj)]
+ else:
+ return depth_stack
+
+ depth_stack[id(obj)] = (current_depth, obj, nest_info)
+
+ if isinstance(obj, (list, tuple)):
+ for i, item in enumerate(obj):
+ depth_getter(item, current_depth=current_depth+1,
+ depth_stack=depth_stack,
+ nest_info='list/tuple item #%s in (%s)' % (i, nest_info))
+ else:
+ if isinstance(obj, dict):
+ state = obj
+ elif hasattr(obj, '__getstate__'):
+ state = obj.__getstate__()
+ if not isinstance(state, dict):
+ state = {}
+ elif hasattr(obj, '__dict__'):
+ state = obj.__dict__
+ else:
+ state = {}
+
+ for key, value in state.iteritems():
+ depth_getter(value, current_depth=current_depth+1,
+ depth_stack=depth_stack,
+ nest_info='attribute "%s" in (%s)' % (key, nest_info))
+
+ # for instancemethod picklability (and some other issues), uncommenting
+ # the following may be helpful
+# print([(name, dobj.__class__) for name, dobj in state.iteritems()], ': ', nest_info, ';', type(obj))
+
+ return depth_stack
+
+
+def recursive_pickle(top_obj):
+ """
+ Recursively pickle all of the given objects subordinates, starting with
+ the deepest first. **Very** handy for debugging pickling issues, but
+ also very slow (as it literally pickles each object in turn).
+
+ Handles circular object references gracefully.
+
+ """
+ objs = depth_getter(top_obj)
+ # sort by depth then by nest_info
+ objs = sorted(objs.itervalues(), key=lambda val: (-val[0], val[2]))
+
+ for _, obj, location in objs:
+# print('trying %s' % location)
+ try:
+ pickle.dump(obj, StringIO(), pickle.HIGHEST_PROTOCOL)
+ except Exception, err:
+ print(obj)
+ print('Failed to pickle %s. \n Type: %s. Traceback follows:' % (location, type(obj)))
+ raise
+
+
+@cleanup
+def test_simple():
+ fig = plt.figure()
+ # un-comment to debug
+# recursive_pickle(fig)
+ pickle.dump(fig, StringIO(), pickle.HIGHEST_PROTOCOL)
+
+ ax = plt.subplot(121)
+ pickle.dump(ax, StringIO(), pickle.HIGHEST_PROTOCOL)
+
+ ax = plt.axes(projection='polar')
+ plt.plot(range(10), label='foobar')
+ plt.legend()
+
+# recursive_pickle(fig)
+ pickle.dump(ax, StringIO(), pickle.HIGHEST_PROTOCOL)
+
+# ax = plt.subplot(121, projection='hammer')
+# recursive_pickle(ax, 'figure')
+# pickle.dump(ax, StringIO(), pickle.HIGHEST_PROTOCOL)
+
+
+@image_comparison(baseline_images=['multi_pickle'],
+ extensions=['png'])
+def test_complete():
+ fig = plt.figure('Figure with a label?', figsize=(10, 6))
+
+ plt.suptitle('Can you fit any more in a figure?')
+
+ # make some arbitrary data
+ x, y = np.arange(8), np.arange(10)
+ data = u = v = np.linspace(0, 10, 80).reshape(10, 8)
+ v = np.sin(v * -0.6)
+
+ plt.subplot(3,3,1)
+ plt.plot(range(10))
+
+ plt.subplot(3, 3, 2)
+ plt.contourf(data, hatches=['//', 'ooo'])
+ plt.colorbar()
+
+ plt.subplot(3, 3, 3)
+ plt.pcolormesh(data)
+
+
+ plt.subplot(3, 3, 4)
+ plt.imshow(data)
+
+ plt.subplot(3, 3, 5)
+ plt.pcolor(data)
+
+ plt.subplot(3, 3, 6)
+ plt.streamplot(x, y, u, v)
+
+ plt.subplot(3, 3, 7)
+ plt.quiver(x, y, u, v)
+
+ plt.subplot(3, 3, 8)
+ plt.scatter(x, x**2, label='$x^2$')
+ plt.legend(loc='upper left')
+
+ plt.subplot(3, 3, 9)
+ plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4)
+
+ ###### plotting is done, now test its pickle-ability #########
+
+ # Uncomment to debug any unpicklable objects. This is slow (~200 seconds).
+# recursive_pickle(fig)
+
+ result_fh = StringIO()
+ pickle.dump(fig, result_fh, pickle.HIGHEST_PROTOCOL)
+
+ plt.close('all')
+
+ # make doubly sure that there are no figures left
+ assert_equal(plt._pylab_helpers.Gcf.figs, {})
+
+ # wind back the fh and load in the figure
+ result_fh.seek(0)
+ fig = pickle.load(result_fh)
+
+ # make sure there is now a figure manager
+ assert_not_equal(plt._pylab_helpers.Gcf.figs, {})
+
+ assert_equal(fig.get_label(), 'Figure with a label?')
+
+
+def test_no_pyplot():
+ # tests pickle-ability of a figure not created with pyplot
+
+ import pickle as p
+ from matplotlib.backends.backend_pdf import FigureCanvasPdf as fc
+ from matplotlib.figure import Figure
+
+ fig = Figure()
+ can = fc(fig)
+ ax = fig.add_subplot(1, 1, 1)
+ ax.plot([1, 2, 3], [1, 2, 3])
+
+ # Uncomment to debug any unpicklable objects. This is slow so is not
+ # uncommented by default.
+# recursive_pickle(fig)
+ pickle.dump(fig, StringIO(), pickle.HIGHEST_PROTOCOL)
diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py
index c6f45119570d..620223b16c0b 100644
--- a/lib/matplotlib/ticker.py
+++ b/lib/matplotlib/ticker.py
@@ -133,33 +133,37 @@
from matplotlib import transforms as mtransforms
+class _DummyAxis(object):
+ def __init__(self, minpos=0):
+ self.dataLim = mtransforms.Bbox.unit()
+ self.viewLim = mtransforms.Bbox.unit()
+ self._minpos = minpos
+
+ def get_view_interval(self):
+ return self.viewLim.intervalx
-class TickHelper:
- axis = None
- class DummyAxis:
- def __init__(self):
- self.dataLim = mtransforms.Bbox.unit()
- self.viewLim = mtransforms.Bbox.unit()
-
- def get_view_interval(self):
- return self.viewLim.intervalx
+ def set_view_interval(self, vmin, vmax):
+ self.viewLim.intervalx = vmin, vmax
- def set_view_interval(self, vmin, vmax):
- self.viewLim.intervalx = vmin, vmax
+ def get_minpos(self):
+ return self._minpos
- def get_data_interval(self):
- return self.dataLim.intervalx
+ def get_data_interval(self):
+ return self.dataLim.intervalx
- def set_data_interval(self, vmin, vmax):
- self.dataLim.intervalx = vmin, vmax
+ def set_data_interval(self, vmin, vmax):
+ self.dataLim.intervalx = vmin, vmax
+class TickHelper(object):
+ axis = None
+
def set_axis(self, axis):
self.axis = axis
- def create_dummy_axis(self):
+ def create_dummy_axis(self, **kwargs):
if self.axis is None:
- self.axis = self.DummyAxis()
+ self.axis = _DummyAxis(**kwargs)
def set_view_interval(self, vmin, vmax):
self.axis.set_view_interval(vmin, vmax)
@@ -176,7 +180,6 @@ class Formatter(TickHelper):
"""
Convert the tick location to a string
"""
-
# some classes want to see all the locs to help format
# individual ones
locs = []
@@ -214,6 +217,7 @@ def fix_minus(self, s):
"""
return s
+
class IndexFormatter(Formatter):
"""
format the position x to the nearest i-th label where i=int(x+0.5)
@@ -239,6 +243,7 @@ def __call__(self, x, pos=None):
'Return the format for tick val *x* at position *pos*'
return ''
+
class FixedFormatter(Formatter):
'Return fixed strings for tick labels'
def __init__(self, seq):
@@ -260,6 +265,7 @@ def get_offset(self):
def set_offset_string(self, ofs):
self.offset_string = ofs
+
class FuncFormatter(Formatter):
"""
User defined function for formatting
@@ -283,6 +289,7 @@ def __call__(self, x, pos=None):
'Return the format for tick val *x* at position *pos*'
return self.fmt % x
+
class OldScalarFormatter(Formatter):
"""
Tick location is a plain old number.
diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py
index f884881708bb..2b12d0d79ed9 100644
--- a/lib/matplotlib/transforms.py
+++ b/lib/matplotlib/transforms.py
@@ -86,7 +86,7 @@ def __init__(self, shorthand_name=None):
other than to improve the readability of
``str(transform)`` when DEBUG=True.
"""
- # Parents are stored in a WeakKeyDictionary, so that if the
+ # Parents are stored in a WeakValueDictionary, so that if the
# parents are deleted, references from the children won't keep
# them alive.
self._parents = WeakValueDictionary()
@@ -101,6 +101,17 @@ def __str__(self):
# either just return the name of this TransformNode, or it's repr
return self._shorthand_name or repr(self)
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ # turn the weakkey dictionary into a normal dictionary
+ d['_parents'] = dict(self._parents.iteritems())
+ return d
+
+ def __setstate__(self, data_dict):
+ self.__dict__ = data_dict
+ # turn the normal dictionary back into a WeakValueDictionary
+ self._parents = WeakValueDictionary(self._parents)
+
def __copy__(self, *args):
raise NotImplementedError(
"TransformNode instances can not be copied. " +
@@ -1398,7 +1409,6 @@ def __init__(self, child):
be replaced with :meth:`set`.
"""
assert isinstance(child, Transform)
-
Transform.__init__(self)
self.input_dims = child.input_dims
self.output_dims = child.output_dims
@@ -1412,6 +1422,14 @@ def __eq__(self, other):
def __str__(self):
return str(self._child)
+ def __getstate__(self):
+ # only store the child
+ return {'child': self._child}
+
+ def __setstate__(self, state):
+ # re-initialise the TransformWrapper with the state's child
+ self.__init__(state['child'])
+
def __repr__(self):
return "TransformWrapper(%r)" % self._child