From 7885d944b594a319420be57c3c82f9254996a881 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 30 Aug 2012 10:55:51 +0100 Subject: [PATCH 1/3] Pickling support added. Various whitespace fixes as a result of reading *lots* of code. --- doc/users/whats_new.rst | 11 + lib/matplotlib/__init__.py | 1 + lib/matplotlib/_pylab_helpers.py | 3 +- lib/matplotlib/artist.py | 9 +- lib/matplotlib/axes.py | 47 ++- lib/matplotlib/axis.py | 1 - lib/matplotlib/backends/__init__.py | 2 +- lib/matplotlib/backends/backend_agg.py | 10 +- lib/matplotlib/backends/backend_cairo.py | 95 ++--- lib/matplotlib/backends/backend_cocoaagg.py | 11 +- lib/matplotlib/backends/backend_emf.py | 9 +- lib/matplotlib/backends/backend_fltkagg.py | 7 + lib/matplotlib/backends/backend_gdk.py | 12 +- lib/matplotlib/backends/backend_gtk.py | 11 +- lib/matplotlib/backends/backend_gtk3agg.py | 9 +- lib/matplotlib/backends/backend_gtk3cairo.py | 9 +- lib/matplotlib/backends/backend_gtkagg.py | 11 +- lib/matplotlib/backends/backend_gtkcairo.py | 9 +- lib/matplotlib/backends/backend_macosx.py | 13 +- lib/matplotlib/backends/backend_pdf.py | 9 +- lib/matplotlib/backends/backend_ps.py | 9 +- lib/matplotlib/backends/backend_qt.py | 13 +- lib/matplotlib/backends/backend_qt4.py | 13 +- lib/matplotlib/backends/backend_qt4agg.py | 10 +- lib/matplotlib/backends/backend_qtagg.py | 12 +- lib/matplotlib/backends/backend_svg.py | 10 +- lib/matplotlib/backends/backend_template.py | 12 +- lib/matplotlib/backends/backend_tkagg.py | 9 +- lib/matplotlib/backends/backend_wx.py | 9 + lib/matplotlib/backends/backend_wxagg.py | 10 +- lib/matplotlib/cbook.py | 209 ++++++---- lib/matplotlib/colorbar.py | 22 +- lib/matplotlib/contour.py | 7 + lib/matplotlib/figure.py | 71 +++- lib/matplotlib/image.py | 6 +- lib/matplotlib/legend.py | 16 +- lib/matplotlib/legend_handler.py | 21 +- lib/matplotlib/markers.py | 10 + lib/matplotlib/offsetbox.py | 100 ++--- lib/matplotlib/patches.py | 42 +- lib/matplotlib/projections/polar.py | 365 +++++++++--------- lib/matplotlib/pyplot.py | 19 +- .../test_pickle/multi_pickle.png | Bin 0 -> 77843 bytes lib/matplotlib/tests/test_pickle.py | 199 ++++++++++ lib/matplotlib/ticker.py | 43 ++- lib/matplotlib/transforms.py | 22 +- 46 files changed, 1048 insertions(+), 500 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_pickle/multi_pickle.png create mode 100644 lib/matplotlib/tests/test_pickle.py 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..743b57c433f0 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'] @@ -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 @@ -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 + 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""" @@ -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_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 0000000000000000000000000000000000000000..1e15b6d94fd4661ac44a6b183568d05ec67b434a GIT binary patch literal 77843 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 z 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 literal 0 HcmV?d00001 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 From f4245bb6b593704e1613d8d5aeabc618c8e901cf Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 30 Aug 2012 13:30:12 +0100 Subject: [PATCH 2/3] Added "new_figure_manager_given_figure" function for pgf backend. --- lib/matplotlib/backends/backend_pgf.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 From 1e081904a8881d0501025269cb4d1318769a5866 Mon Sep 17 00:00:00 2001 From: pwuertz Date: Thu, 30 Aug 2012 15:21:28 +0200 Subject: [PATCH 3/3] py3 fixes for axes.py --- lib/matplotlib/axes.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index 743b57c433f0..da78d16d9061 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -288,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 = {} @@ -1489,7 +1489,7 @@ def _update_line_limits(self, line): return line_trans = line.get_transform() - + if line_trans == self.transData: data_path = path @@ -1508,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. @@ -1519,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) @@ -2216,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': @@ -3723,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)] @@ -3802,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 @@ -4785,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 @@ -4843,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: @@ -5718,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) @@ -7086,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: @@ -8834,9 +8834,9 @@ def __init__(self, fig, *args, **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] + 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__()] @@ -8925,8 +8925,8 @@ def subplot_class_factory(axes_class=None): class _PicklableSubplotClassConstructor(object): """ - This stub class exists to return the appropriate subplot - class when __call__-ed with an axes class. This is purely to + 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):