diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst
index b7a2f890662d..64299430243c 100644
--- a/doc/users/whats_new.rst
+++ b/doc/users/whats_new.rst
@@ -70,6 +70,17 @@ minimum and maximum colorbar extensions.
plt.show()
+Figures are picklable
+---------------------
+
+Philip Elson added an experimental feature to make figures picklable
+for quick and easy short-term storage of plots. Pickle files
+are not designed for long term storage, are unsupported when restoring a pickle
+saved in another matplotlib version and are insecure when restoring a pickle
+from an untrusted source. Having said this, they are useful for short term
+storage for later modification inside matplotlib.
+
+
Set default bounding box in matplotlibrc
------------------------------------------
diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index 232ac22618cf..deb568e3dcaa 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -1068,6 +1068,7 @@ def tk_window_focus():
'matplotlib.tests.test_mathtext',
'matplotlib.tests.test_mlab',
'matplotlib.tests.test_patches',
+ 'matplotlib.tests.test_pickle',
'matplotlib.tests.test_rcparams',
'matplotlib.tests.test_simplification',
'matplotlib.tests.test_spines',
diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py
index f6b61868cecf..6901e0482170 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
@@ -131,6 +131,7 @@ def set_active(manager):
if m != manager: Gcf._activeQue.append(m)
Gcf._activeQue.append(manager)
Gcf.figs[manager.num] = manager
+
atexit.register(Gcf.destroy_all)
diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py
index 6ce12b083748..afe5ec9e9e64 100644
--- a/lib/matplotlib/artist.py
+++ b/lib/matplotlib/artist.py
@@ -104,6 +104,13 @@ def __init__(self):
self.y_isdata = True # with y
self._snap = None
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ # remove the unpicklable remove method, this will get re-added on load
+ # (by the axes) if the artist lives on an axes.
+ d['_remove_method'] = None
+ return d
+
def remove(self):
"""
Remove the artist from the figure if possible. The effect
@@ -123,7 +130,7 @@ def remove(self):
# the _remove_method attribute directly. This would be a protected
# attribute if Python supported that sort of thing. The callback
# has one parameter, which is the child to be removed.
- if self._remove_method != None:
+ if self._remove_method is not None:
self._remove_method(self)
else:
raise NotImplementedError('cannot remove artist')
diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py
index 6c7e833e09dd..2b504444326e 100644
--- a/lib/matplotlib/axes.py
+++ b/lib/matplotlib/axes.py
@@ -154,9 +154,8 @@ def set_default_color_cycle(clist):
DeprecationWarning)
-class _process_plot_var_args:
+class _process_plot_var_args(object):
"""
-
Process variable length arguments to the plot command, so that
plot commands like the following are supported::
@@ -172,6 +171,14 @@ def __init__(self, axes, command='plot'):
self.command = command
self.set_color_cycle()
+ def __getstate__(self):
+ # note: it is not possible to pickle a itertools.cycle instance
+ return {'axes': self.axes, 'command': self.command}
+
+ def __setstate__(self, state):
+ self.__dict__ = state.copy()
+ self.set_color_cycle()
+
def set_color_cycle(self, clist=None):
if clist is None:
clist = rcParams['axes.color_cycle']
@@ -332,7 +339,7 @@ def _grab_next_args(self, *args, **kwargs):
for seg in self._plot_args(remaining[:isplit], kwargs):
yield seg
remaining=remaining[isplit:]
-
+
class Axes(martist.Artist):
"""
@@ -352,9 +359,10 @@ class Axes(martist.Artist):
_shared_x_axes = cbook.Grouper()
_shared_y_axes = cbook.Grouper()
-
+
def __str__(self):
return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds)
+
def __init__(self, fig, rect,
axisbg = None, # defaults to rc axes.facecolor
frameon = True,
@@ -489,6 +497,15 @@ def __init__(self, fig, rect,
self._ycid = self.yaxis.callbacks.connect('units finalize',
self.relim)
+ def __setstate__(self, state):
+ self.__dict__ = state
+ # put the _remove_method back on all artists contained within the axes
+ for container_name in ['lines', 'collections', 'tables', 'patches',
+ 'texts', 'images']:
+ container = getattr(self, container_name)
+ for artist in container:
+ artist._remove_method = container.remove
+
def get_window_extent(self, *args, **kwargs):
"""
get the axes bounding box in display space; *args* and
@@ -1599,13 +1616,13 @@ def _process_unit_info(self, xdata=None, ydata=None, kwargs=None):
if xdata is not None:
# we only need to update if there is nothing set yet.
if not self.xaxis.have_units():
- self.xaxis.update_units(xdata)
+ self.xaxis.update_units(xdata)
#print '\tset from xdata', self.xaxis.units
if ydata is not None:
# we only need to update if there is nothing set yet.
if not self.yaxis.have_units():
- self.yaxis.update_units(ydata)
+ self.yaxis.update_units(ydata)
#print '\tset from ydata', self.yaxis.units
# process kwargs 2nd since these will override default units
@@ -8770,7 +8787,15 @@ def __init__(self, fig, *args, **kwargs):
# _axes_class is set in the subplot_class_factory
self._axes_class.__init__(self, fig, self.figbox, **kwargs)
-
+ def __reduce__(self):
+ # get the first axes class which does not inherit from a subplotbase
+ axes_class = filter(lambda klass: (issubclass(klass, Axes) and
+ not issubclass(klass, SubplotBase)),
+ self.__class__.mro())[0]
+ r = [_PicklableSubplotClassConstructor(),
+ (axes_class,),
+ self.__getstate__()]
+ return tuple(r)
def get_geometry(self):
"""get the subplot geometry, eg 2,2,3"""
@@ -8852,6 +8877,21 @@ def subplot_class_factory(axes_class=None):
# This is provided for backward compatibility
Subplot = subplot_class_factory()
+
+class _PicklableSubplotClassConstructor(object):
+ """
+ This stub class exists to return the appropriate subplot
+ class when __call__-ed with an axes class. This is purely to
+ allow Pickling of Axes and Subplots."""
+ def __call__(self, axes_class):
+ # create a dummy object instance
+ subplot_instance = _PicklableSubplotClassConstructor()
+ subplot_class = subplot_class_factory(axes_class)
+ # update the class to the desired subplot class
+ subplot_instance.__class__ = subplot_class
+ return subplot_instance
+
+
docstring.interpd.update(Axes=martist.kwdoc(Axes))
docstring.interpd.update(Subplot=martist.kwdoc(Axes))
diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py
index 43db8b9b7f9f..db658e060b73 100644
--- a/lib/matplotlib/axis.py
+++ b/lib/matplotlib/axis.py
@@ -595,7 +595,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..da2cff90f38f 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..3694d521542f 100644
--- a/lib/matplotlib/backends/backend_cairo.py
+++ b/lib/matplotlib/backends/backend_cairo.py
@@ -397,10 +397,17 @@ def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py
"""
Create a new figure manager instance
"""
- if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))
+ if _debug: print('%s.%s()' % (_fn_name()))
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
- canvas = FigureCanvasCairo(thisFig)
+ return new_figure_manager_given_figure(num, thisFig)
+
+
+def new_figure_manager_given_figure(num, figure):
+ """
+ Create a new figure manager instance for the given figure.
+ """
+ canvas = FigureCanvasCairo(figure)
manager = FigureManagerBase(canvas, num)
return manager
diff --git a/lib/matplotlib/backends/backend_cocoaagg.py b/lib/matplotlib/backends/backend_cocoaagg.py
index 7a21f079833a..e4743cec9eda 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..cf112b848b40 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..13900b7c9071 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..a94ca1fd11d0 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..7cebe9877c04 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..fcf9a7914f2d 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..bf3e34d09142 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..5abc56b3612d 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..baa51c54e438 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..694baf8ec886 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 4cd830829eff..2e3e7bccf9ef 100644
--- a/lib/matplotlib/backends/backend_pdf.py
+++ b/lib/matplotlib/backends/backend_pdf.py
@@ -2171,7 +2171,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_ps.py b/lib/matplotlib/backends/backend_ps.py
index 2af23224f145..6ca5df558e37 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..8aae6b18bfde 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..ff72226bb694 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..20cd7ad60480 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..000d7bd4a3db 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..ae83208d4d9f 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..da1dfc16e71e 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..c48dd28222bd 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..922aa9f748f8 100644
--- a/lib/matplotlib/cbook.py
+++ b/lib/matplotlib/cbook.py
@@ -266,6 +266,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
@@ -375,7 +384,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 +394,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 +1896,27 @@ 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
+
+
# 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 e476c5a539b3..537e8714b4ac 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.
@@ -277,7 +283,7 @@ def __init__(self, ax, cmap=None,
# The rest is in a method so we can recalculate when clim changes.
self.config_axis()
self.draw_all()
-
+
def _extend_lower(self):
"""Returns whether the lower limit is open ended."""
return self.extend in ('both', 'min')
@@ -285,13 +291,12 @@ def _extend_lower(self):
def _extend_upper(self):
"""Returns whether the uper limit is open ended."""
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):
'''
diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py
index 7abcbb91f7e6..77f8cfda4e71 100644
--- a/lib/matplotlib/contour.py
+++ b/lib/matplotlib/contour.py
@@ -847,6 +847,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 ce8a11f6945d..264838a5b8e5 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -33,6 +33,7 @@
import matplotlib.cbook as cbook
from matplotlib import docstring
+from matplotlib import __version__ as _mpl_version
from operator import itemgetter
import os.path
@@ -1131,11 +1132,73 @@ 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 self.canvas is not None and self.canvas.manager 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/markers.py b/lib/matplotlib/markers.py
index 73850af48dac..dd18254a93a0 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/patches.py b/lib/matplotlib/patches.py
index b03cddcbfbf8..082f427b9e5e 100644
--- a/lib/matplotlib/patches.py
+++ b/lib/matplotlib/patches.py
@@ -1617,7 +1617,6 @@ def pprint_styles(klass):
"""
return _pprint_styles(klass._style_list)
-
@classmethod
def register(klass, name, style):
"""
@@ -1687,9 +1686,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
@@ -1701,8 +1697,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.):
"""
@@ -1728,7 +1722,15 @@ 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):
"""
@@ -2296,9 +2298,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
@@ -2359,7 +2358,7 @@ class _Base(object):
points. This base class defines a __call__ method, and few
helper methods.
"""
-
+
class SimpleEvent:
def __init__(self, xy):
self.x, self.y = xy
@@ -2401,7 +2400,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
@@ -2441,6 +2439,15 @@ def __call__(self, posA, posB,
shrinked_path = self._shrink(clipped_path, shrinkA, shrinkB)
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):
@@ -2771,7 +2778,6 @@ def connect(self, posA, posB):
{"AvailableConnectorstyles": _pprint_styles(_style_list)}
-
class ArrowStyle(_Style):
"""
:class:`ArrowStyle` is a container class which defines several
@@ -2867,8 +2873,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.):
"""
@@ -2901,7 +2905,15 @@ def __call__(self, path, mutation_size, linewidth,
return path_mutated, fillable
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):
@@ -3048,7 +3060,6 @@ def __init__(self):
_style_list["-"] = Curve
-
class CurveA(_Curve):
"""
An arrow with a head at its begin point.
@@ -3087,7 +3098,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
@@ -3109,11 +3119,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 37c9494a1329..d47f303db97b 100644
--- a/lib/matplotlib/projections/polar.py
+++ b/lib/matplotlib/projections/polar.py
@@ -18,206 +18,211 @@
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(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(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.__doc__ = Transform.transform.__doc__
+ return xy
+ transform.__doc__ = Transform.transform.__doc__
- transform_non_affine = transform
- transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
+ transform_non_affine = transform
+ transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
- def transform_path(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.__doc__ = Transform.transform_path.__doc__
+ def transform_path(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.__doc__ = Transform.transform_path.__doc__
- transform_path_non_affine = transform_path
- transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__
+ transform_path_non_affine = transform_path
+ 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(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__
+
+
+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(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.__doc__ = Transform.transform.__doc__
+ return np.concatenate((theta, r), 1)
+ transform.__doc__ = Transform.transform.__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):
"""
@@ -653,6 +658,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 15a309307dab..1e64ae8697b6 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..d60ff2275d48
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..b2b3d9588dcd
--- /dev/null
+++ b/lib/matplotlib/tests/test_pickle.py
@@ -0,0 +1,157 @@
+from __future__ import print_function
+
+import numpy as np
+
+import matplotlib
+matplotlib.use('tkagg')
+
+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 recursive_pickle(obj, nested_info='top level object', memo=None):
+ """
+ Pickle the object's attributes recursively, storing a memo of the object
+ which have already been pickled.
+
+ If any pickling issues occur, a pickle.Pickle error will be raised with details.
+
+ This is not a completely general purpose routine, but will be useful for
+ debugging some pickle issues. HINT: cPickle is less verbose than Pickle
+
+
+ """
+ if memo is None:
+ memo = {}
+
+ if id(obj) in memo:
+ return
+
+ # put this object in the memo
+ memo[id(obj)] = obj
+
+ # start by pickling all of the object's attributes/contents
+
+ if isinstance(obj, list):
+ for i, item in enumerate(obj):
+ recursive_pickle(item, memo=memo, nested_info='list item #%s in (%s)' % (i, nested_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():
+ recursive_pickle(value, memo=memo, nested_info='attribute "%s" in (%s)' % (key, nested_info))
+
+# print(id(obj), type(obj), nested_info)
+
+ # finally, try picking the object itself
+ try:
+ pickle.dump(obj, StringIO())#, pickle.HIGHEST_PROTOCOL)
+ except (pickle.PickleError, AssertionError), err:
+ print(pickle.PickleError('Pickling failed with nested info: [(%s) %s].'
+ '\nException: %s' % (type(obj),
+ nested_info,
+ err)))
+ # re-raise the exception for full traceback
+ raise
+
+
+@cleanup
+def test_simple():
+ fig = plt.figure()
+ # un-comment to debug
+ recursive_pickle(fig, 'figure')
+ pickle.dump(fig, StringIO(), pickle.HIGHEST_PROTOCOL)
+
+ ax = plt.subplot(121)
+# recursive_pickle(ax, 'ax')
+ pickle.dump(ax, StringIO(), pickle.HIGHEST_PROTOCOL)
+
+ ax = plt.axes(projection='polar')
+# recursive_pickle(ax, 'ax')
+ 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?')
+
+ 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() # sadly, colorbar is currently failing. This might be an easy fix once
+ # its been identified what the problem is. (lambda functions in colorbar)
+
+ plt.subplot(3, 3, 3)
+ plt.pcolormesh(data)
+# cb = plt.colorbar()
+
+ 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()
+
+ plt.subplot(3, 3, 9)
+ plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4)
+
+
+ result_fh = StringIO()
+# recursive_pickle(fig, 'figure')
+ 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?')
+
+
\ No newline at end of file
diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py
index c6f45119570d..a827cf9b8145 100644
--- a/lib/matplotlib/ticker.py
+++ b/lib/matplotlib/ticker.py
@@ -133,33 +133,31 @@
from matplotlib import transforms as mtransforms
+class _DummyAxis(object):
+ def __init__(self):
+ self.dataLim = mtransforms.Bbox.unit()
+ self.viewLim = mtransforms.Bbox.unit()
-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 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_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):
def set_axis(self, axis):
self.axis = axis
def create_dummy_axis(self):
if self.axis is None:
- self.axis = self.DummyAxis()
+ self.axis = _DummyAxis()
def set_view_interval(self, vmin, vmax):
self.axis.set_view_interval(vmin, vmax)
@@ -176,7 +174,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 +211,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 +237,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 +259,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 +283,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 bac5d963b3ca..7e1b20df2848 100644
--- a/lib/matplotlib/transforms.py
+++ b/lib/matplotlib/transforms.py
@@ -91,6 +91,17 @@ def __init__(self):
# computed for the first time.
self._invalid = 1
+ 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 WeakKeyDict
+ self._parents = WeakKeyDictionary(self._parents)
+
def __copy__(self, *args):
raise NotImplementedError(
"TransformNode instances can not be copied. " +
@@ -1275,12 +1286,19 @@ 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
self._set(child)
self._invalid = 0
+
+ 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