From 0db9429b5a3fd57b19b62e6eb23dcff1f12b5d03 Mon Sep 17 00:00:00 2001 From: pelson Date: Thu, 5 Jul 2012 10:08:47 +0100 Subject: [PATCH 1/5] First pass pickle support for figures, transforms & artists. --- lib/matplotlib/artist.py | 9 +- lib/matplotlib/axes.py | 80 +++++++++++++--- lib/matplotlib/axis.py | 1 - lib/matplotlib/cbook.py | 172 +++++++++++++++++++++-------------- lib/matplotlib/contour.py | 7 ++ lib/matplotlib/figure.py | 26 ++++++ lib/matplotlib/markers.py | 10 ++ lib/matplotlib/ticker.py | 30 +++--- lib/matplotlib/transforms.py | 20 +++- 9 files changed, 255 insertions(+), 100 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 6ce12b083748..5373315f35ad 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -104,6 +104,13 @@ def __init__(self): self.y_isdata = True # with y self._snap = None + def __getstate__(self): + d = self.__dict__.copy() + # remove the unpicklable remove method, this will get re-added on load + d.pop('_remove_method') +# axes_artist_collections = ['lines', 'collections', 'tables', ''] + return d + def remove(self): """ Remove the artist from the figure if possible. The effect @@ -123,7 +130,7 @@ def remove(self): # the _remove_method attribute directly. This would be a protected # attribute if Python supported that sort of thing. The callback # has one parameter, which is the child to be removed. - if self._remove_method != None: + if self._remove_method is not None: self._remove_method(self) else: raise NotImplementedError('cannot remove artist') diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index 6c7e833e09dd..805890653297 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -172,6 +172,13 @@ def __init__(self, axes, command='plot'): self.command = command self.set_color_cycle() + def __getinitargs__(self): + # means that the color cycle will be lost. + return (self.axes, self.command) + + def __getstate__(self): + return False + def set_color_cycle(self, clist=None): if clist is None: clist = rcParams['axes.color_cycle'] @@ -332,7 +339,7 @@ def _grab_next_args(self, *args, **kwargs): for seg in self._plot_args(remaining[:isplit], kwargs): yield seg remaining=remaining[isplit:] - + class Axes(martist.Artist): """ @@ -352,9 +359,10 @@ class Axes(martist.Artist): _shared_x_axes = cbook.Grouper() _shared_y_axes = cbook.Grouper() - + def __str__(self): return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds) + def __init__(self, fig, rect, axisbg = None, # defaults to rc axes.facecolor frameon = True, @@ -1423,7 +1431,9 @@ def add_artist(self, a): self.artists.append(a) self._set_artist_props(a) a.set_clip_path(self.patch) - a._remove_method = lambda h: self.artists.remove(h) + def remove_fn(artist): + self.artists.remove(artist) + a._remove_method = remove_fn #lambda h: self.artists.remove(h) return a def add_collection(self, collection, autolim=True): @@ -1445,7 +1455,11 @@ def add_collection(self, collection, autolim=True): if collection._paths and len(collection._paths): self.update_datalim(collection.get_datalim(self.transData)) - collection._remove_method = lambda h: self.collections.remove(h) + # XXX back to start + def remove_fn(artist): + self.collections.remove(artist) + + collection._remove_method = remove_fn #lambda h: self.collections.remove(h) return collection def add_line(self, line): @@ -1463,7 +1477,10 @@ def add_line(self, line): if not line.get_label(): line.set_label('_line%d'%len(self.lines)) self.lines.append(line) - line._remove_method = lambda h: self.lines.remove(h) +# def remove_fn(artist): +# self.lines.remove(artist) +# line._remove_method = remove_fn #lambda h: self.lines.remove(h) + line._remove_method = self.lines.remove return line def _update_line_limits(self, line): @@ -1489,7 +1506,9 @@ def add_patch(self, p): p.set_clip_path(self.patch) self._update_patch_limits(p) self.patches.append(p) - p._remove_method = lambda h: self.patches.remove(h) + def remove_fn(artist): + self.patches.remove(artist) + p._remove_method = remove_fn #lambda h: self.patches.remove(h) return p def _update_patch_limits(self, patch): @@ -1524,7 +1543,9 @@ def add_table(self, tab): self._set_artist_props(tab) self.tables.append(tab) tab.set_clip_path(self.patch) - tab._remove_method = lambda h: self.tables.remove(h) + def remove_fn(artist): + self.tables.remove(artist) + tab._remove_method = remove_fn #lambda h: self.tables.remove(h) return tab def add_container(self, container): @@ -1538,7 +1559,9 @@ def add_container(self, container): if not label: container.set_label('_container%d'%len(self.containers)) self.containers.append(container) - container.set_remove_method(lambda h: self.containers.remove(container)) + def remove_fn(artist): + self.containers.remove(artist) + container.set_remove_method(remove_fn) return container @@ -1599,13 +1622,13 @@ def _process_unit_info(self, xdata=None, ydata=None, kwargs=None): if xdata is not None: # we only need to update if there is nothing set yet. if not self.xaxis.have_units(): - self.xaxis.update_units(xdata) + self.xaxis.update_units(xdata) #print '\tset from xdata', self.xaxis.units if ydata is not None: # we only need to update if there is nothing set yet. if not self.yaxis.have_units(): - self.yaxis.update_units(ydata) + self.yaxis.update_units(ydata) #print '\tset from ydata', self.yaxis.units # process kwargs 2nd since these will override default units @@ -3330,7 +3353,9 @@ def text(self, x, y, s, fontdict=None, if fontdict is not None: t.update(fontdict) t.update(kwargs) self.texts.append(t) - t._remove_method = lambda h: self.texts.remove(h) + def remove_fn(artist): + self.texts.remove(artist) + t._remove_method = remove_fn #lambda h: self.texts.remove(h) #if t.get_clip_on(): t.set_clip_box(self.bbox) @@ -3359,7 +3384,9 @@ def annotate(self, *args, **kwargs): self._set_artist_props(a) if kwargs.has_key('clip_on'): a.set_clip_path(self.patch) self.texts.append(a) - a._remove_method = lambda h: self.texts.remove(h) + def remove_fn(artist): + self.texts.remove(artist) + a._remove_method = remove_fn #lambda h: self.texts.remove(h) return a #### Lines and spans @@ -7022,7 +7049,9 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, im.set_extent(im.get_extent()) self.images.append(im) - im._remove_method = lambda h: self.images.remove(h) + def remove_fn(artist): + self.images.remove(artist) + im._remove_method = remove_fn #lambda h: self.images.remove(h) return im @@ -8770,7 +8799,15 @@ def __init__(self, fig, *args, **kwargs): # _axes_class is set in the subplot_class_factory self._axes_class.__init__(self, fig, self.figbox, **kwargs) - + def __reduce__(self): + # get the first axes class which does not inherit from a subplotbase + axes_class = filter(lambda klass: (issubclass(klass, Axes) and + not issubclass(klass, SubplotBase)), + self.__class__.mro())[0] + r = [_PicklableSubplotClassConstructor(), + (axes_class,), + self.__getstate__()] + return tuple(r) def get_geometry(self): """get the subplot geometry, eg 2,2,3""" @@ -8852,6 +8889,21 @@ def subplot_class_factory(axes_class=None): # This is provided for backward compatibility Subplot = subplot_class_factory() + +class _PicklableSubplotClassConstructor(object): + """ + This stub class exists to return the appropriate subplot + class when __call__-ed with an axes class. This is purely to + allow Picking of Axes and Subplots.""" + def __call__(self, axes_class): + # create a dummy object instance + subplot_instance = _PicklableSubplotClassConstructor() + subplot_class = subplot_class_factory(axes_class) + # update the class to the desired subplot class + subplot_instance.__class__ = subplot_class + return subplot_instance + + docstring.interpd.update(Axes=martist.kwdoc(Axes)) docstring.interpd.update(Subplot=martist.kwdoc(Axes)) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 43db8b9b7f9f..db658e060b73 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -595,7 +595,6 @@ class Ticker: formatter = None - class Axis(artist.Artist): """ diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 7026614ea84e..858d4a734290 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,72 +274,7 @@ 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): @@ -266,6 +285,13 @@ def __init__(self, *args): self._cid = 0 self._func_cid_map = {} + def __getstate__(self): + # pickling of callbacks not yet handled/may never be handlable + return {'callbacks': {}, + '_cid': self._cid, + '_func_cid_map': {}, + } + 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,16 @@ 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' @@ -394,6 +429,7 @@ def strip_math(s): for r in remove: s = s.replace(r,'') return s + class Bunch: """ Often we want to just collect a bunch of stuff together, naming each diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 7abcbb91f7e6..77f8cfda4e71 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -847,6 +847,13 @@ def __init__(self, ax, *args, **kwargs): self.collections.append(col) self.changed() # set the colors + def __getstate__(self): + state = self.__dict__.copy() + # the C object Cntr cannot currently be pickled. This isn't a big issue + # as it is not actually used once the contour has been calculated + state['Cntr'] = None + return state + def legend_elements(self, variable_name='x', str_format=str): """ Return a list of artist and labels suitable for passing through diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ce8a11f6945d..31329921f93f 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -33,6 +33,7 @@ import matplotlib.cbook as cbook from matplotlib import docstring +from matplotlib import __version__ as _mpl_version from operator import itemgetter import os.path @@ -1131,6 +1132,31 @@ 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') : + state.pop(attr_to_pop) + + # add version information to the state + state['__mpl_version__'] = _mpl_version + + return state + + def __setstate__(self, state): + version = state.pop('__mpl_version__') + 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 + self._axobservers = [] + self.canvas = None + def add_axobserver(self, func): 'whenever the axes state change, ``func(self)`` will be called' self._axobservers.append(func) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 73850af48dac..dd18254a93a0 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -113,6 +113,16 @@ def __init__(self, marker=None, fillstyle='full'): self.set_marker(marker) self.set_fillstyle(fillstyle) + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_marker_function') + return d + + def __setstate__(self, statedict): + self.__dict__ = statedict + self.set_marker(self._marker) + self._recache() + def _recache(self): self._path = Path(np.empty((0,2))) self._transform = IdentityTransform() diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index c6f45119570d..9e8b423d8764 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -133,33 +133,33 @@ from matplotlib import transforms as mtransforms +class _DummyAxis: + def __init__(self): + self.dataLim = mtransforms.Bbox.unit() + self.viewLim = mtransforms.Bbox.unit() -class TickHelper: - axis = None - class DummyAxis: - def __init__(self): - self.dataLim = mtransforms.Bbox.unit() - self.viewLim = mtransforms.Bbox.unit() + def get_view_interval(self): + return self.viewLim.intervalx - def get_view_interval(self): - return self.viewLim.intervalx + def set_view_interval(self, vmin, vmax): + self.viewLim.intervalx = vmin, vmax - def set_view_interval(self, vmin, vmax): - self.viewLim.intervalx = vmin, vmax + def get_data_interval(self): + return self.dataLim.intervalx - def get_data_interval(self): - return self.dataLim.intervalx + def set_data_interval(self, vmin, vmax): + self.dataLim.intervalx = vmin, vmax - def set_data_interval(self, vmin, vmax): - self.dataLim.intervalx = vmin, vmax +class TickHelper: + axis = None def set_axis(self, axis): self.axis = axis def create_dummy_axis(self): if self.axis is None: - self.axis = self.DummyAxis() + self.axis = _DummyAxis() def set_view_interval(self, vmin, vmax): self.axis.set_view_interval(vmin, vmax) diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index bac5d963b3ca..7e1b20df2848 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -91,6 +91,17 @@ def __init__(self): # computed for the first time. self._invalid = 1 + def __getstate__(self): + d = self.__dict__.copy() + # turn the weakkey dictionary into a normal dictionary + d['_parents'] = dict(self._parents.iteritems()) + return d + + def __setstate__(self, data_dict): + self.__dict__ = data_dict + # turn the normal dictionary back into a WeakKeyDict + self._parents = WeakKeyDictionary(self._parents) + def __copy__(self, *args): raise NotImplementedError( "TransformNode instances can not be copied. " + @@ -1275,12 +1286,19 @@ def __init__(self, child): be replaced with :meth:`set`. """ assert isinstance(child, Transform) - Transform.__init__(self) self.input_dims = child.input_dims self.output_dims = child.output_dims self._set(child) self._invalid = 0 + + def __getstate__(self): + # only store the child + return {'child': self._child} + + def __setstate__(self, state): + # re-initialise the TransformWrapper with the state's child + self.__init__(state['child']) def __repr__(self): return "TransformWrapper(%r)" % self._child From 3ec63ad863a69ce8a5098bd31cb9434e646137c8 Mon Sep 17 00:00:00 2001 From: pelson Date: Tue, 17 Jul 2012 14:01:55 +0100 Subject: [PATCH 2/5] pyplot restore support added. Further backends will need modification to mirror TkAgg. --- doc/users/whats_new.rst | 10 ++ lib/matplotlib/_pylab_helpers.py | 3 +- lib/matplotlib/artist.py | 4 +- lib/matplotlib/axes.py | 52 +++---- lib/matplotlib/backends/__init__.py | 2 +- lib/matplotlib/backends/backend_tkagg.py | 9 +- lib/matplotlib/cbook.py | 186 +++++++++++------------ lib/matplotlib/figure.py | 43 +++++- lib/matplotlib/projections/polar.py | 46 +++++- lib/matplotlib/pyplot.py | 19 +-- lib/matplotlib/ticker.py | 9 +- 11 files changed, 236 insertions(+), 147 deletions(-) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index b7a2f890662d..0e6459802c13 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -70,6 +70,16 @@ minimum and maximum colorbar extensions. plt.show() +Figures are picklable +--------------------- + +Philip Elson made figures picklable for quick 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/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index f6b61868cecf..6901e0482170 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -14,7 +14,7 @@ def error_msg(msg): class Gcf(object): """ - Manage a set of integer-numbered figures. + Singleton to manage a set of integer-numbered figures. This class is never instantiated; it consists of two class attributes (a list and a dictionary), and a set of static @@ -131,6 +131,7 @@ def set_active(manager): if m != manager: Gcf._activeQue.append(m) Gcf._activeQue.append(manager) Gcf.figs[manager.num] = manager + atexit.register(Gcf.destroy_all) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 5373315f35ad..72f91f0bc13f 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -107,8 +107,8 @@ def __init__(self): def __getstate__(self): d = self.__dict__.copy() # remove the unpicklable remove method, this will get re-added on load - d.pop('_remove_method') -# axes_artist_collections = ['lines', 'collections', 'tables', ''] + # if the artist lives on an axes. + d['_remove_method'] = None return d def remove(self): diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index 805890653297..793a6fba1e26 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -173,10 +173,12 @@ def __init__(self, axes, command='plot'): self.set_color_cycle() def __getinitargs__(self): + # note: __getinitargs__ only works for old-style classes # means that the color cycle will be lost. return (self.axes, self.command) def __getstate__(self): + # We don't need any state as we have the init args return False def set_color_cycle(self, clist=None): @@ -497,6 +499,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 @@ -1431,9 +1442,7 @@ def add_artist(self, a): self.artists.append(a) self._set_artist_props(a) a.set_clip_path(self.patch) - def remove_fn(artist): - self.artists.remove(artist) - a._remove_method = remove_fn #lambda h: self.artists.remove(h) + a._remove_method = lambda h: self.artists.remove(h) return a def add_collection(self, collection, autolim=True): @@ -1455,11 +1464,7 @@ def add_collection(self, collection, autolim=True): if collection._paths and len(collection._paths): self.update_datalim(collection.get_datalim(self.transData)) - # XXX back to start - def remove_fn(artist): - self.collections.remove(artist) - - collection._remove_method = remove_fn #lambda h: self.collections.remove(h) + collection._remove_method = lambda h: self.collections.remove(h) return collection def add_line(self, line): @@ -1477,10 +1482,7 @@ def add_line(self, line): if not line.get_label(): line.set_label('_line%d'%len(self.lines)) self.lines.append(line) -# def remove_fn(artist): -# self.lines.remove(artist) -# line._remove_method = remove_fn #lambda h: self.lines.remove(h) - line._remove_method = self.lines.remove + line._remove_method = lambda h: self.lines.remove(h) return line def _update_line_limits(self, line): @@ -1506,9 +1508,7 @@ def add_patch(self, p): p.set_clip_path(self.patch) self._update_patch_limits(p) self.patches.append(p) - def remove_fn(artist): - self.patches.remove(artist) - p._remove_method = remove_fn #lambda h: self.patches.remove(h) + p._remove_method = lambda h: self.patches.remove(h) return p def _update_patch_limits(self, patch): @@ -1543,9 +1543,7 @@ def add_table(self, tab): self._set_artist_props(tab) self.tables.append(tab) tab.set_clip_path(self.patch) - def remove_fn(artist): - self.tables.remove(artist) - tab._remove_method = remove_fn #lambda h: self.tables.remove(h) + tab._remove_method = lambda h: self.tables.remove(h) return tab def add_container(self, container): @@ -1559,9 +1557,7 @@ def add_container(self, container): if not label: container.set_label('_container%d'%len(self.containers)) self.containers.append(container) - def remove_fn(artist): - self.containers.remove(artist) - container.set_remove_method(remove_fn) + container.set_remove_method(lambda h: self.containers.remove(container)) return container @@ -3353,9 +3349,7 @@ def text(self, x, y, s, fontdict=None, if fontdict is not None: t.update(fontdict) t.update(kwargs) self.texts.append(t) - def remove_fn(artist): - self.texts.remove(artist) - t._remove_method = remove_fn #lambda h: self.texts.remove(h) + t._remove_method = lambda h: self.texts.remove(h) #if t.get_clip_on(): t.set_clip_box(self.bbox) @@ -3384,9 +3378,7 @@ def annotate(self, *args, **kwargs): self._set_artist_props(a) if kwargs.has_key('clip_on'): a.set_clip_path(self.patch) self.texts.append(a) - def remove_fn(artist): - self.texts.remove(artist) - a._remove_method = remove_fn #lambda h: self.texts.remove(h) + a._remove_method = lambda h: self.texts.remove(h) return a #### Lines and spans @@ -7049,9 +7041,7 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, im.set_extent(im.get_extent()) self.images.append(im) - def remove_fn(artist): - self.images.remove(artist) - im._remove_method = remove_fn #lambda h: self.images.remove(h) + im._remove_method = lambda h: self.images.remove(h) return im @@ -8894,7 +8884,7 @@ 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 Picking of Axes and Subplots.""" + allow Pickling of Axes and Subplots.""" def __call__(self, axes_class): # create a dummy object instance subplot_instance = _PicklableSubplotClassConstructor() 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_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index ac21bb094cf4..a2c7c95fe760 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -74,9 +74,16 @@ def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ - _focus = windowing.FocusManager() 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. + """ + _focus = windowing.FocusManager() window = Tk.Tk() if Tk.TkVersion >= 8.5: diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 858d4a734290..d5fcd17c90c2 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -152,90 +152,6 @@ 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 @@ -274,7 +190,72 @@ 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): @@ -286,12 +267,12 @@ def __init__(self, *args): self._func_cid_map = {} def __getstate__(self): - # pickling of callbacks not yet handled/may never be handlable - return {'callbacks': {}, - '_cid': self._cid, - '_func_cid_map': {}, - } + 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 @@ -305,7 +286,7 @@ def connect(self, s, func): cid = self._cid self._func_cid_map[s][func] = cid self.callbacks.setdefault(s, dict()) - proxy = _BoundMethodProxy(func) + proxy = self.BoundMethodProxy(func) self.callbacks[s][cid] = proxy return cid @@ -416,12 +397,11 @@ def __str__(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' remove = (r'\mathdefault', r'\rm', r'\cal', r'\tt', r'\it', '\\', '{', '}') @@ -429,7 +409,6 @@ def strip_math(s): for r in remove: s = s.replace(r,'') return s - class Bunch: """ Often we want to just collect a bunch of stuff together, naming each @@ -1915,6 +1894,27 @@ def is_math_text(s): return even_dollars + +class _NestedClassGetter(object): + # recipe from http://stackoverflow.com/a/11493777/741316 + """ + When called with the containing class as the first argument, + and the name of the nested class as the second argument, + returns an instance of the nested class. + """ + def __call__(self, containing_class, class_name): + nested_class = getattr(containing_class, class_name) + + # make an instance of a simple object (this one will do), for which we + # can change the __class__ later on. + nested_instance = _NestedClassGetter() + + # set the class of the instance, the __init__ will never be called on + # the class but the original state will be set later on by pickle. + nested_instance.__class__ = nested_class + return nested_instance + + # Numpy > 1.6.x deprecates putmask in favor of the new copyto. # So long as we support versions 1.6.x and less, we need the # following local version of putmask. We choose to make a diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 31329921f93f..264838a5b8e5 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1138,30 +1138,67 @@ def __getstate__(self): # 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') : - state.pop(attr_to_pop) + for attr_to_pop in ('_axobservers', 'show', 'canvas', '_cachedRenderer') : + state.pop(attr_to_pop, None) # add version information to the state state['__mpl_version__'] = _mpl_version + # check to see if the figure has a manager and whether it is registered + # with pyplot + if self.canvas is not None and self.canvas.manager is not None: + manager = self.canvas.manager + import matplotlib._pylab_helpers + if manager in matplotlib._pylab_helpers.Gcf.figs.viewvalues(): + state['_restore_to_pylab'] = True return state def __setstate__(self, state): version = state.pop('__mpl_version__') + restore_to_pylab = state.pop('_restore_to_pylab', False) + if version != _mpl_version: import warnings warnings.warn("This figure was saved with matplotlib version %s " "and is unlikely to function correctly." % (version, )) + self.__dict__ = state + + # re-initialise some of the unstored state information self._axobservers = [] self.canvas = None + + if restore_to_pylab: + # lazy import to avoid circularity + import matplotlib.pyplot as plt + import matplotlib._pylab_helpers as pylab_helpers + allnums = plt.get_fignums() + num = max(allnums) + 1 if allnums else 1 + mgr = plt._backend_mod.new_figure_manager_given_figure(num, self) + + # XXX The following is a copy and paste from pyplot. Consider + # factoring to pylab_helpers + + if self.get_label(): + mgr.set_window_title(self.get_label()) + + # make this figure current on button press event + def make_active(event): + pylab_helpers.Gcf.set_active(mgr) + + mgr._cidgcf = mgr.canvas.mpl_connect('button_press_event', + make_active) + + pylab_helpers.Gcf.set_active(mgr) + self.number = num + + plt.draw_if_interactive() def add_axobserver(self, func): 'whenever the axes state change, ``func(self)`` will be called' self._axobservers.append(func) - def savefig(self, *args, **kwargs): """ Save the current figure. diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 37c9494a1329..ee2d4613fc39 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -93,6 +93,15 @@ def transform_path(self, path): def inverted(self): return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin) inverted.__doc__ = Transform.inverted.__doc__ + + def __reduce__(self): + # because we have decided to nest the Transform classes, we need to + # add some more information to allow Pickling of polar plots. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (PolarAxes, self.__class__.__name__), + self.__getstate__(), + ) class PolarAffine(Affine2DBase): """ @@ -123,6 +132,15 @@ def get_matrix(self): self._invalid = 0 return self._mtx get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ + + def __reduce__(self): + # because we have decided to nest the Transform classes, we need to + # add some more information to allow Pickling of polar plots. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (PolarAxes, self.__class__.__name__), + self.__getstate__(), + ) class InvertedPolarTransform(Transform): """ @@ -168,6 +186,15 @@ def transform(self, xy): def inverted(self): return PolarAxes.PolarTransform(self._axis, self._use_rmin) inverted.__doc__ = Transform.inverted.__doc__ + + def __reduce__(self): + # because we have decided to nest the Transform classes, we need to + # add some more information to allow Pickling of polar plots. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (PolarAxes, self.__class__.__name__), + self.__getstate__(), + ) class ThetaFormatter(Formatter): """ @@ -185,6 +212,15 @@ def __call__(self, x, pos=None): # 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) + + def __reduce__(self): + # because we have decided to nest the ThetaFormatter class, we need + # to add some more information to allow Pickling of polar plots. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (PolarAxes, self.__class__.__name__), + self.__dict__.copy(), + ) class RadialLocator(Locator): """ @@ -217,7 +253,15 @@ def refresh(self): def view_limits(self, vmin, vmax): vmin, vmax = self.base.view_limits(vmin, vmax) return 0, vmax - + + def __reduce__(self): + # because we have decided to nest the RadialLocator class, we need + # to add some more information to allow Pickling of polar plots. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (PolarAxes, self.__class__.__name__), + self.__dict__.copy(), + ) def __init__(self, *args, **kwargs): """ diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 15a309307dab..1e64ae8697b6 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -94,7 +94,7 @@ def _backend_selection(): ## Global ## from matplotlib.backends import pylab_setup -new_figure_manager, draw_if_interactive, _show = pylab_setup() +_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup() @docstring.copy_dedent(Artist.findobj) def findobj(o=None, match=None): @@ -102,6 +102,7 @@ def findobj(o=None, match=None): o = gcf() return o.findobj(match) + def switch_backend(newbackend): """ Switch the default backend. This feature is **experimental**, and @@ -115,10 +116,10 @@ def switch_backend(newbackend): Calling this command will close all open windows. """ close('all') - global new_figure_manager, draw_if_interactive, _show + global _backend_mod, new_figure_manager, draw_if_interactive, _show matplotlib.use(newbackend, warn=False, force=True) from matplotlib.backends import pylab_setup - new_figure_manager, draw_if_interactive, _show = pylab_setup() + _backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup() def show(*args, **kw): @@ -312,22 +313,17 @@ class that will be passed on to :meth:`new_figure_manager` in the if edgecolor is None : edgecolor = rcParams['figure.edgecolor'] allnums = get_fignums() + next_num = max(allnums) + 1 if allnums else 1 figLabel = '' if num is None: - if allnums: - num = max(allnums) + 1 - else: - num = 1 + num = next_num elif is_string_like(num): figLabel = num allLabels = get_figlabels() if figLabel not in allLabels: if figLabel == 'all': warnings.warn("close('all') closes all existing figures") - if len(allLabels): - num = max(allnums) + 1 - else: - num = 1 + num = next_num else: inum = allLabels.index(figLabel) num = allnums[inum] @@ -363,6 +359,7 @@ def make_active(event): draw_if_interactive() return figManager.canvas.figure + def gcf(): "Return a reference to the current figure." diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 9e8b423d8764..6e65c1495955 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -133,7 +133,7 @@ from matplotlib import transforms as mtransforms -class _DummyAxis: +class _DummyAxis(object): def __init__(self): self.dataLim = mtransforms.Bbox.unit() self.viewLim = mtransforms.Bbox.unit() @@ -151,7 +151,7 @@ def set_data_interval(self, vmin, vmax): self.dataLim.intervalx = vmin, vmax -class TickHelper: +class TickHelper(object): axis = None def set_axis(self, axis): @@ -176,7 +176,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 +213,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 +239,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 +261,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 +285,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. From 1257bf927688bbaaf44b480f878fee2de5877be3 Mon Sep 17 00:00:00 2001 From: pelson Date: Mon, 20 Aug 2012 15:30:12 +0100 Subject: [PATCH 3/5] Added pickle test, still some outstanding problems (colorbar, legend and geo axes known so far) --- lib/matplotlib/__init__.py | 1 + lib/matplotlib/artist.py | 2 +- lib/matplotlib/axes.py | 16 +- lib/matplotlib/colorbar.py | 19 ++- lib/matplotlib/patches.py | 48 +++--- .../test_pickle/multi_pickle.png | Bin 0 -> 68952 bytes lib/matplotlib/tests/test_pickle.py | 157 ++++++++++++++++++ 7 files changed, 206 insertions(+), 37 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/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 232ac22618cf..deb568e3dcaa 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1068,6 +1068,7 @@ def tk_window_focus(): 'matplotlib.tests.test_mathtext', 'matplotlib.tests.test_mlab', 'matplotlib.tests.test_patches', + 'matplotlib.tests.test_pickle', 'matplotlib.tests.test_rcparams', 'matplotlib.tests.test_simplification', 'matplotlib.tests.test_spines', diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 72f91f0bc13f..afe5ec9e9e64 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -107,7 +107,7 @@ def __init__(self): def __getstate__(self): d = self.__dict__.copy() # remove the unpicklable remove method, this will get re-added on load - # if the artist lives on an axes. + # (by the axes) if the artist lives on an axes. d['_remove_method'] = None return d diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index 793a6fba1e26..2b504444326e 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -154,9 +154,8 @@ def set_default_color_cycle(clist): DeprecationWarning) -class _process_plot_var_args: +class _process_plot_var_args(object): """ - Process variable length arguments to the plot command, so that plot commands like the following are supported:: @@ -172,14 +171,13 @@ def __init__(self, axes, command='plot'): self.command = command self.set_color_cycle() - def __getinitargs__(self): - # note: __getinitargs__ only works for old-style classes - # means that the color cycle will be lost. - return (self.axes, self.command) - def __getstate__(self): - # We don't need any state as we have the init args - return False + # 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: diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index e476c5a539b3..537e8714b4ac 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -185,6 +185,12 @@ docstring.interpd.update(colorbar_doc=colorbar_doc) +def _set_ticks_on_axis_warn(*args, **kw): + # a top level function which gets put in at the axes' + # set_xticks set_yticks by _patch_ax + warnings.warn("Use the colorbar set_ticks() method instead.") + + class ColorbarBase(cm.ScalarMappable): ''' Draw a colorbar in an existing axes. @@ -277,7 +283,7 @@ def __init__(self, ax, cmap=None, # The rest is in a method so we can recalculate when clim changes. self.config_axis() self.draw_all() - + def _extend_lower(self): """Returns whether the lower limit is open ended.""" return self.extend in ('both', 'min') @@ -285,13 +291,12 @@ def _extend_lower(self): def _extend_upper(self): """Returns whether the uper limit is open ended.""" return self.extend in ('both', 'max') - + def _patch_ax(self): - def _warn(*args, **kw): - warnings.warn("Use the colorbar set_ticks() method instead.") - - self.ax.set_xticks = _warn - self.ax.set_yticks = _warn + # bind some methods to the axes to warn users + # against using those methods. + self.ax.set_xticks = _set_ticks_on_axis_warn + self.ax.set_yticks = _set_ticks_on_axis_warn def draw_all(self): ''' diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index b03cddcbfbf8..082f427b9e5e 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1617,7 +1617,6 @@ def pprint_styles(klass): """ return _pprint_styles(klass._style_list) - @classmethod def register(klass, name, style): """ @@ -1687,9 +1686,6 @@ def __init__(self): """ super(BoxStyle._Base, self).__init__() - - - def transmute(self, x0, y0, width, height, mutation_size): """ The transmute method is a very core of the @@ -1701,8 +1697,6 @@ def transmute(self, x0, y0, width, height, mutation_size): """ raise NotImplementedError('Derived must override') - - def __call__(self, x0, y0, width, height, mutation_size, aspect_ratio=1.): """ @@ -1728,7 +1722,15 @@ def __call__(self, x0, y0, width, height, mutation_size, else: return self.transmute(x0, y0, width, height, mutation_size) - + def __reduce__(self): + # because we have decided to nest thes classes, we need to + # add some more information to allow instance pickling. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (BoxStyle, self.__class__.__name__), + self.__dict__ + ) + class Square(_Base): """ @@ -2296,9 +2298,6 @@ def get_bbox(self): return transforms.Bbox.from_bounds(self._x, self._y, self._width, self._height) - - - from matplotlib.bezier import split_bezier_intersecting_with_closedpath from matplotlib.bezier import get_intersection, inside_circle, get_parallels from matplotlib.bezier import make_wedged_bezier2 @@ -2359,7 +2358,7 @@ class _Base(object): points. This base class defines a __call__ method, and few helper methods. """ - + class SimpleEvent: def __init__(self, xy): self.x, self.y = xy @@ -2401,7 +2400,6 @@ def insideB(xy_display): return path - def _shrink(self, path, shrinkA, shrinkB): """ Shrink the path by fixed size (in points) with shrinkA and shrinkB @@ -2441,6 +2439,15 @@ def __call__(self, posA, posB, shrinked_path = self._shrink(clipped_path, shrinkA, shrinkB) return shrinked_path + + def __reduce__(self): + # because we have decided to nest thes classes, we need to + # add some more information to allow instance pickling. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (ConnectionStyle, self.__class__.__name__), + self.__dict__ + ) class Arc3(_Base): @@ -2771,7 +2778,6 @@ def connect(self, posA, posB): {"AvailableConnectorstyles": _pprint_styles(_style_list)} - class ArrowStyle(_Style): """ :class:`ArrowStyle` is a container class which defines several @@ -2867,8 +2873,6 @@ class and must be overriden in the subclasses. It receives raise NotImplementedError('Derived must override') - - def __call__(self, path, mutation_size, linewidth, aspect_ratio=1.): """ @@ -2901,7 +2905,15 @@ def __call__(self, path, mutation_size, linewidth, return path_mutated, fillable else: return self.transmute(path, mutation_size, linewidth) - + + def __reduce__(self): + # because we have decided to nest thes classes, we need to + # add some more information to allow instance pickling. + import matplotlib.cbook as cbook + return (cbook._NestedClassGetter(), + (ArrowStyle, self.__class__.__name__), + self.__dict__ + ) class _Curve(_Base): @@ -3048,7 +3060,6 @@ def __init__(self): _style_list["-"] = Curve - class CurveA(_Curve): """ An arrow with a head at its begin point. @@ -3087,7 +3098,6 @@ def __init__(self, head_length=.4, head_width=.2): beginarrow=False, endarrow=True, head_length=head_length, head_width=head_width ) - #_style_list["->"] = CurveB _style_list["->"] = CurveB @@ -3109,11 +3119,9 @@ def __init__(self, head_length=.4, head_width=.2): beginarrow=True, endarrow=True, head_length=head_length, head_width=head_width ) - #_style_list["<->"] = CurveAB _style_list["<->"] = CurveAB - class CurveFilledA(_Curve): """ An arrow with filled triangle head at the begin. diff --git a/lib/matplotlib/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..d60ff2275d48ed6e4e164e2509a3ac60ba111a24 GIT binary patch literal 68952 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

g7~ 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

aarW}rG$hh*SEhV!%688mwAb} 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 z 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 Date: Mon, 20 Aug 2012 15:50:53 +0100 Subject: [PATCH 4/5] Updated all backends for new figure manager creation (given a figure instance) API --- lib/matplotlib/backends/backend_agg.py | 10 ++++++++-- lib/matplotlib/backends/backend_cairo.py | 11 +++++++++-- 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_wx.py | 9 +++++++++ lib/matplotlib/backends/backend_wxagg.py | 10 ++++++++-- 22 files changed, 195 insertions(+), 34 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 9c585d0c7d6f..da2cff90f38f 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -385,7 +385,6 @@ def post_processing(image, dpi): image) - def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance @@ -396,7 +395,14 @@ def new_figure_manager(num, *args, **kwargs): FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasAgg(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasAgg(figure) manager = FigureManagerBase(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index eeb91713d6f4..3694d521542f 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -397,10 +397,17 @@ def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py """ Create a new figure manager instance """ - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) + if _debug: print('%s.%s()' % (_fn_name())) FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasCairo(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasCairo(figure) manager = FigureManagerBase(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_cocoaagg.py b/lib/matplotlib/backends/backend_cocoaagg.py index 7a21f079833a..e4743cec9eda 100644 --- a/lib/matplotlib/backends/backend_cocoaagg.py +++ b/lib/matplotlib/backends/backend_cocoaagg.py @@ -35,12 +35,21 @@ mplBundle = NSBundle.bundleWithPath_(os.path.dirname(__file__)) + def new_figure_manager(num, *args, **kwargs): FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass( *args, **kwargs ) - canvas = FigureCanvasCocoaAgg(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasCocoaAgg(figure) return FigureManagerCocoaAgg(canvas, num) + ## Below is the original show() function: #def show(): # for manager in Gcf.get_all_fig_managers(): diff --git a/lib/matplotlib/backends/backend_emf.py b/lib/matplotlib/backends/backend_emf.py index 580798f641f0..cf112b848b40 100644 --- a/lib/matplotlib/backends/backend_emf.py +++ b/lib/matplotlib/backends/backend_emf.py @@ -688,7 +688,14 @@ def new_figure_manager(num, *args, **kwargs): # main-level app (egg backend_gtk, backend_gtkagg) for pylab FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasEMF(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasEMF(figure) manager = FigureManagerEMF(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_fltkagg.py b/lib/matplotlib/backends/backend_fltkagg.py index fe31d0f81191..13900b7c9071 100644 --- a/lib/matplotlib/backends/backend_fltkagg.py +++ b/lib/matplotlib/backends/backend_fltkagg.py @@ -78,6 +78,13 @@ def new_figure_manager(num, *args, **kwargs): """ FigureClass = kwargs.pop('FigureClass', Figure) figure = FigureClass(*args, **kwargs) + return new_figure_manager_given_figure(num, figure) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ window = Fltk.Fl_Double_Window(10,10,30,30) canvas = FigureCanvasFltkAgg(figure) window.end() diff --git a/lib/matplotlib/backends/backend_gdk.py b/lib/matplotlib/backends/backend_gdk.py index 7d7f7e323d20..a94ca1fd11d0 100644 --- a/lib/matplotlib/backends/backend_gdk.py +++ b/lib/matplotlib/backends/backend_gdk.py @@ -422,11 +422,15 @@ def new_figure_manager(num, *args, **kwargs): """ FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasGDK(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasGDK(figure) manager = FigureManagerBase(canvas, num) - # equals: - #manager = FigureManagerBase (FigureCanvasGDK (Figure(*args, **kwargs), - # num) return manager diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index b7199890225a..7cebe9877c04 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -90,10 +90,15 @@ def new_figure_manager(num, *args, **kwargs): """ FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasGTK(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasGTK(figure) manager = FigureManagerGTK(canvas, num) - # equals: - #manager = FigureManagerGTK(FigureCanvasGTK(Figure(*args, **kwargs), num) return manager diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index da14f356ba0a..fcf9a7914f2d 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -78,7 +78,14 @@ def new_figure_manager(num, *args, **kwargs): """ FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasGTK3Agg(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasGTK3Agg(figure) manager = FigureManagerGTK3Agg(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 21abae2a4762..bf3e34d09142 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -45,7 +45,14 @@ def new_figure_manager(num, *args, **kwargs): """ FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasGTK3Cairo(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasGTK3Cairo(figure) manager = FigureManagerGTK3Cairo(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_gtkagg.py b/lib/matplotlib/backends/backend_gtkagg.py index c8f359f8c0f6..5abc56b3612d 100644 --- a/lib/matplotlib/backends/backend_gtkagg.py +++ b/lib/matplotlib/backends/backend_gtkagg.py @@ -33,6 +33,7 @@ def _get_toolbar(self, canvas): toolbar = None return toolbar + def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance @@ -40,10 +41,18 @@ def new_figure_manager(num, *args, **kwargs): if DEBUG: print('backend_gtkagg.new_figure_manager') FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasGTKAgg(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasGTKAgg(figure) return FigureManagerGTKAgg(canvas, num) if DEBUG: print('backend_gtkagg.new_figure_manager done') + class FigureCanvasGTKAgg(FigureCanvasGTK, FigureCanvasAgg): filetypes = FigureCanvasGTK.filetypes.copy() filetypes.update(FigureCanvasAgg.filetypes) diff --git a/lib/matplotlib/backends/backend_gtkcairo.py b/lib/matplotlib/backends/backend_gtkcairo.py index 616a3dc8ba71..baa51c54e438 100644 --- a/lib/matplotlib/backends/backend_gtkcairo.py +++ b/lib/matplotlib/backends/backend_gtkcairo.py @@ -26,7 +26,14 @@ def new_figure_manager(num, *args, **kwargs): if _debug: print('backend_gtkcairo.%s()' % fn_name()) FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasGTKCairo(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasGTKCairo(figure) return FigureManagerGTK(canvas, num) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 7e402c302392..694baf8ec886 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -233,9 +233,20 @@ def new_figure_manager(num, *args, **kwargs): """ if not _macosx.verify_main_display(): import warnings - warnings.warn("Python is not installed as a framework. The MacOSX backend may not work correctly if Python is not installed as a framework. Please see the Python documentation for more information on installing Python as a framework on Mac OS X") + warnings.warn("Python is not installed as a framework. The MacOSX " + "backend may not work correctly if Python is not " + "installed as a framework. Please see the Python " + "documentation for more information on installing " + "Python as a framework on Mac OS X") FigureClass = kwargs.pop('FigureClass', Figure) figure = FigureClass(*args, **kwargs) + return new_figure_manager_given_figure(num, figure) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ canvas = FigureCanvasMac(figure) manager = FigureManagerMac(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 4cd830829eff..2e3e7bccf9ef 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2171,7 +2171,14 @@ def new_figure_manager(num, *args, **kwargs): # main-level app (egg backend_gtk, backend_gtkagg) for pylab FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasPdf(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasPdf(figure) manager = FigureManagerPdf(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 2af23224f145..6ca5df558e37 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -944,7 +944,14 @@ def shouldstroke(self): def new_figure_manager(num, *args, **kwargs): FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasPS(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasPS(figure) manager = FigureManagerPS(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 6acca884e6a0..8aae6b18bfde 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -69,9 +69,16 @@ def new_figure_manager( num, *args, **kwargs ): Create a new figure manager instance """ FigureClass = kwargs.pop('FigureClass', Figure) - thisFig = FigureClass( *args, **kwargs ) - canvas = FigureCanvasQT( thisFig ) - manager = FigureManagerQT( canvas, num ) + thisFig = FigureClass(*args, **kwargs) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasQT(figure) + manager = FigureManagerQT(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 45fe444743d8..ff72226bb694 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -72,9 +72,16 @@ def new_figure_manager( num, *args, **kwargs ): """ Create a new figure manager instance """ - thisFig = Figure( *args, **kwargs ) - canvas = FigureCanvasQT( thisFig ) - manager = FigureManagerQT( canvas, num ) + thisFig = Figure(*args, **kwargs) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasQT(figure) + manager = FigureManagerQT(canvas, num) return manager diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py index c47584da4a6f..20cd7ad60480 100644 --- a/lib/matplotlib/backends/backend_qt4agg.py +++ b/lib/matplotlib/backends/backend_qt4agg.py @@ -23,9 +23,17 @@ def new_figure_manager( num, *args, **kwargs ): if DEBUG: print('backend_qtagg.new_figure_manager') FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass( *args, **kwargs ) - canvas = FigureCanvasQTAgg( thisFig ) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasQTAgg(figure) return FigureManagerQT( canvas, num ) + class NavigationToolbar2QTAgg(NavigationToolbar2QT): def _get_canvas(self, fig): return FigureCanvasQTAgg(fig) diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py index e7a3d89910b8..000d7bd4a3db 100644 --- a/lib/matplotlib/backends/backend_qtagg.py +++ b/lib/matplotlib/backends/backend_qtagg.py @@ -23,8 +23,16 @@ def new_figure_manager( num, *args, **kwargs ): if DEBUG: print('backend_qtagg.new_figure_manager') FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass( *args, **kwargs ) - canvas = FigureCanvasQTAgg( thisFig ) - return FigureManagerQTAgg( canvas, num ) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasQTAgg(figure) + return FigureManagerQTAgg(canvas, num) + class NavigationToolbar2QTAgg(NavigationToolbar2QT): def _get_canvas(self, fig): diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 0b9b7ab51701..ae83208d4d9f 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -1151,10 +1151,18 @@ class FigureManagerSVG(FigureManagerBase): def new_figure_manager(num, *args, **kwargs): FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) - canvas = FigureCanvasSVG(thisFig) + return new_figure_manager_given_figure(num, thisFig) + + +def new_figure_manager_given_figure(num, figure): + """ + Create a new figure manager instance for the given figure. + """ + canvas = FigureCanvasSVG(figure) manager = FigureManagerSVG(canvas, num) return manager + svgProlog = u"""\ = 2.8) # From d68f49c925d1ea84f1b361bcb346d47004199851 Mon Sep 17 00:00:00 2001 From: pelson Date: Mon, 20 Aug 2012 16:40:52 +0100 Subject: [PATCH 5/5] Final changes to pickling support. A few outstanding known issues (colorbar/legend/geoaxes). --- doc/users/whats_new.rst | 3 +- lib/matplotlib/cbook.py | 2 + lib/matplotlib/projections/polar.py | 444 +++++++++++++--------------- lib/matplotlib/ticker.py | 2 - 4 files changed, 212 insertions(+), 239 deletions(-) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 0e6459802c13..64299430243c 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -73,7 +73,8 @@ minimum and maximum colorbar extensions. Figures are picklable --------------------- -Philip Elson made figures picklable for quick storage of plots. Pickle files +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 diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index d5fcd17c90c2..922aa9f748f8 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -267,6 +267,8 @@ def __init__(self, *args): 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): diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index ee2d4613fc39..d47f303db97b 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -18,250 +18,211 @@ ScaledTranslation, blended_transform_factory, BboxTransformToMaxOnly import matplotlib.spines as mspines -class PolarAxes(Axes): - """ - A polar graph projection, where the input dimensions are *theta*, *r*. - Theta starts pointing east and goes anti-clockwise. +class PolarTransform(Transform): """ - name = 'polar' - - class PolarTransform(Transform): - """ - The base polar transform. This handles projection *theta* and - *r* into Cartesian coordinate space *x* and *y*, but does not - perform the ultimate affine transformation into the correct - position. - """ - input_dims = 2 - output_dims = 2 - is_separable = False - - def __init__(self, axis=None, use_rmin=True): - Transform.__init__(self) - self._axis = axis - self._use_rmin = use_rmin - - def transform(self, tr): - xy = np.empty(tr.shape, np.float_) - if self._axis is not None: - if self._use_rmin: - rmin = self._axis.viewLim.ymin - else: - rmin = 0 - theta_offset = self._axis.get_theta_offset() - theta_direction = self._axis.get_theta_direction() + The base polar transform. This handles projection *theta* and + *r* into Cartesian coordinate space *x* and *y*, but does not + perform the ultimate affine transformation into the correct + position. + """ + input_dims = 2 + output_dims = 2 + is_separable = False + + def __init__(self, axis=None, use_rmin=True): + Transform.__init__(self) + self._axis = axis + self._use_rmin = use_rmin + + def transform(self, tr): + xy = np.empty(tr.shape, np.float_) + if self._axis is not None: + if self._use_rmin: + rmin = self._axis.viewLim.ymin else: rmin = 0 - theta_offset = 0 - theta_direction = 1 - - t = tr[:, 0:1] - r = tr[:, 1:2] - x = xy[:, 0:1] - y = xy[:, 1:2] - - t *= theta_direction - t += theta_offset - - if rmin != 0: - r = r - rmin - mask = r < 0 - x[:] = np.where(mask, np.nan, r * np.cos(t)) - y[:] = np.where(mask, np.nan, r * np.sin(t)) - else: - x[:] = r * np.cos(t) - y[:] = r * np.sin(t) - - return xy - transform.__doc__ = Transform.transform.__doc__ - - transform_non_affine = transform - transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ - - def transform_path(self, path): - vertices = path.vertices - if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]: - return Path(self.transform(vertices), path.codes) - ipath = path.interpolated(path._interpolation_steps) - return Path(self.transform(ipath.vertices), ipath.codes) - transform_path.__doc__ = Transform.transform_path.__doc__ - - transform_path_non_affine = transform_path - transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ - - def inverted(self): - return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin) - inverted.__doc__ = Transform.inverted.__doc__ - - def __reduce__(self): - # because we have decided to nest the Transform classes, we need to - # add some more information to allow Pickling of polar plots. - import matplotlib.cbook as cbook - return (cbook._NestedClassGetter(), - (PolarAxes, self.__class__.__name__), - self.__getstate__(), - ) - - 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 __reduce__(self): - # because we have decided to nest the Transform classes, we need to - # add some more information to allow Pickling of polar plots. - import matplotlib.cbook as cbook - return (cbook._NestedClassGetter(), - (PolarAxes, self.__class__.__name__), - self.__getstate__(), - ) - - class InvertedPolarTransform(Transform): - """ - The inverse of the polar transform, mapping Cartesian - coordinate space *x* and *y* back to *theta* and *r*. - """ - input_dims = 2 - output_dims = 2 - is_separable = False - - def __init__(self, axis=None, use_rmin=True): - Transform.__init__(self) - self._axis = axis - self._use_rmin = use_rmin - - def transform(self, xy): - if self._axis is not None: - if self._use_rmin: - rmin = self._axis.viewLim.ymin - else: - rmin = 0 - theta_offset = self._axis.get_theta_offset() - theta_direction = self._axis.get_theta_direction() + theta_offset = self._axis.get_theta_offset() + theta_direction = self._axis.get_theta_direction() + else: + rmin = 0 + theta_offset = 0 + theta_direction = 1 + + t = tr[:, 0:1] + r = tr[:, 1:2] + x = xy[:, 0:1] + y = xy[:, 1:2] + + t *= theta_direction + t += theta_offset + + if rmin != 0: + r = r - rmin + mask = r < 0 + x[:] = np.where(mask, np.nan, r * np.cos(t)) + y[:] = np.where(mask, np.nan, r * np.sin(t)) + else: + x[:] = r * np.cos(t) + y[:] = r * np.sin(t) + + return xy + transform.__doc__ = Transform.transform.__doc__ + + transform_non_affine = transform + transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ + + def transform_path(self, path): + vertices = path.vertices + if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]: + return Path(self.transform(vertices), path.codes) + ipath = path.interpolated(path._interpolation_steps) + return Path(self.transform(ipath.vertices), ipath.codes) + transform_path.__doc__ = Transform.transform_path.__doc__ + + transform_path_non_affine = transform_path + transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ + + def inverted(self): + return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin) + inverted.__doc__ = Transform.inverted.__doc__ + + +class PolarAffine(Affine2DBase): + """ + The affine part of the polar projection. Scales the output so + that maximum radius rests on the edge of the axes circle. + """ + def __init__(self, scale_transform, limits): + """ + *limits* is the view limit of the data. The only part of + its bounds that is used is ymax (for the radius maximum). + The theta range is always fixed to (0, 2pi). + """ + Affine2DBase.__init__(self) + self._scale_transform = scale_transform + self._limits = limits + self.set_children(scale_transform, limits) + self._mtx = None + + def get_matrix(self): + if self._invalid: + limits_scaled = self._limits.transformed(self._scale_transform) + yscale = limits_scaled.ymax - limits_scaled.ymin + affine = Affine2D() \ + .scale(0.5 / yscale) \ + .translate(0.5, 0.5) + self._mtx = affine.get_matrix() + self._inverted = None + self._invalid = 0 + return self._mtx + get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ + + +class InvertedPolarTransform(Transform): + """ + The inverse of the polar transform, mapping Cartesian + coordinate space *x* and *y* back to *theta* and *r*. + """ + input_dims = 2 + output_dims = 2 + is_separable = False + + def __init__(self, axis=None, use_rmin=True): + Transform.__init__(self) + self._axis = axis + self._use_rmin = use_rmin + + def transform(self, xy): + if self._axis is not None: + if self._use_rmin: + rmin = self._axis.viewLim.ymin else: rmin = 0 - theta_offset = 0 - theta_direction = 1 - - 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 - - r += rmin - - return np.concatenate((theta, r), 1) - transform.__doc__ = Transform.transform.__doc__ - - def inverted(self): - return PolarAxes.PolarTransform(self._axis, self._use_rmin) - inverted.__doc__ = Transform.inverted.__doc__ - - def __reduce__(self): - # because we have decided to nest the Transform classes, we need to - # add some more information to allow Pickling of polar plots. - import matplotlib.cbook as cbook - return (cbook._NestedClassGetter(), - (PolarAxes, self.__class__.__name__), - self.__getstate__(), - ) - - 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) + 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) + + theta -= theta_offset + theta *= theta_direction + + r += rmin + + return np.concatenate((theta, r), 1) + transform.__doc__ = Transform.transform.__doc__ + + def inverted(self): + return PolarAxes.PolarTransform(self._axis, self._use_rmin) + inverted.__doc__ = Transform.inverted.__doc__ + + +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 autoscale(self): + return self.base.autoscale() + + def pan(self, numsteps): + return self.base.pan(numsteps) + + def zoom(self, direction): + return self.base.zoom(direction) + + 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 __reduce__(self): - # because we have decided to nest the ThetaFormatter class, we need - # to add some more information to allow Pickling of polar plots. - import matplotlib.cbook as cbook - return (cbook._NestedClassGetter(), - (PolarAxes, self.__class__.__name__), - self.__dict__.copy(), - ) - - 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 autoscale(self): - return self.base.autoscale() - - def pan(self, numsteps): - return self.base.pan(numsteps) - - def zoom(self, direction): - return self.base.zoom(direction) - - 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 __reduce__(self): - # because we have decided to nest the RadialLocator class, we need - # to add some more information to allow Pickling of polar plots. - import matplotlib.cbook as cbook - return (cbook._NestedClassGetter(), - (PolarAxes, self.__class__.__name__), - self.__dict__.copy(), - ) +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): """ @@ -697,6 +658,17 @@ def drag_pan(self, button, key, x, y): scale = r / startr self.set_rmax(p.rmax / scale) + +# to keep things all self contained, we can put aliases to the Polar classes +# defined above. This isn't strictly necessary, but it makes some of the +# code more readable (and provides a backwards compatible Polar API) +PolarAxes.PolarTransform = PolarTransform +PolarAxes.PolarAffine = PolarAffine +PolarAxes.InvertedPolarTransform = InvertedPolarTransform +PolarAxes.ThetaFormatter = ThetaFormatter +PolarAxes.RadialLocator = RadialLocator + + # These are a couple of aborted attempts to project a polar plot using # cubic bezier curves. diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 6e65c1495955..a827cf9b8145 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -152,8 +152,6 @@ def set_data_interval(self, vmin, vmax): class TickHelper(object): - axis = None - def set_axis(self, axis): self.axis = axis