Skip to content

Commit

Permalink
Pickling support added. Various whitespace fixes as a result of readi…
Browse files Browse the repository at this point in the history
…ng *lots* of code.
  • Loading branch information
pelson committed Aug 30, 2012
1 parent 0c7cdaa commit 7885d94
Show file tree
Hide file tree
Showing 46 changed files with 1,048 additions and 500 deletions.
11 changes: 11 additions & 0 deletions doc/users/whats_new.rst
Expand Up @@ -100,6 +100,17 @@ minimum and maximum colorbar extensions.
plt.show() 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 Set default bounding box in matplotlibrc
------------------------------------------ ------------------------------------------


Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/__init__.py
Expand Up @@ -1085,6 +1085,7 @@ def tk_window_focus():
'matplotlib.tests.test_mathtext', 'matplotlib.tests.test_mathtext',
'matplotlib.tests.test_mlab', 'matplotlib.tests.test_mlab',
'matplotlib.tests.test_patches', 'matplotlib.tests.test_patches',
'matplotlib.tests.test_pickle',
'matplotlib.tests.test_rcparams', 'matplotlib.tests.test_rcparams',
'matplotlib.tests.test_simplification', 'matplotlib.tests.test_simplification',
'matplotlib.tests.test_spines', 'matplotlib.tests.test_spines',
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/_pylab_helpers.py
Expand Up @@ -14,7 +14,7 @@ def error_msg(msg):


class Gcf(object): 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 This class is never instantiated; it consists of two class
attributes (a list and a dictionary), and a set of static attributes (a list and a dictionary), and a set of static
Expand Down Expand Up @@ -132,6 +132,7 @@ def set_active(manager):
Gcf._activeQue.append(manager) Gcf._activeQue.append(manager)
Gcf.figs[manager.num] = manager Gcf.figs[manager.num] = manager



atexit.register(Gcf.destroy_all) atexit.register(Gcf.destroy_all)




Expand Down
9 changes: 8 additions & 1 deletion lib/matplotlib/artist.py
Expand Up @@ -103,6 +103,13 @@ def __init__(self):
self._gid = None self._gid = None
self._snap = 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): def remove(self):
""" """
Remove the artist from the figure if possible. The effect Remove the artist from the figure if possible. The effect
Expand All @@ -122,7 +129,7 @@ def remove(self):
# the _remove_method attribute directly. This would be a protected # the _remove_method attribute directly. This would be a protected
# attribute if Python supported that sort of thing. The callback # attribute if Python supported that sort of thing. The callback
# has one parameter, which is the child to be removed. # 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) self._remove_method(self)
else: else:
raise NotImplementedError('cannot remove artist') raise NotImplementedError('cannot remove artist')
Expand Down
47 changes: 44 additions & 3 deletions lib/matplotlib/axes.py
Expand Up @@ -153,9 +153,8 @@ def set_default_color_cycle(clist):
DeprecationWarning) DeprecationWarning)




class _process_plot_var_args: class _process_plot_var_args(object):
""" """
Process variable length arguments to the plot command, so that Process variable length arguments to the plot command, so that
plot commands like the following are supported:: plot commands like the following are supported::
Expand All @@ -171,6 +170,14 @@ def __init__(self, axes, command='plot'):
self.command = command self.command = command
self.set_color_cycle() 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): def set_color_cycle(self, clist=None):
if clist is None: if clist is None:
clist = rcParams['axes.color_cycle'] clist = rcParams['axes.color_cycle']
Expand Down Expand Up @@ -354,6 +361,7 @@ class Axes(martist.Artist):


def __str__(self): def __str__(self):
return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds) return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds)

def __init__(self, fig, rect, def __init__(self, fig, rect,
axisbg = None, # defaults to rc axes.facecolor axisbg = None, # defaults to rc axes.facecolor
frameon = True, frameon = True,
Expand Down Expand Up @@ -488,6 +496,15 @@ def __init__(self, fig, rect,
self._ycid = self.yaxis.callbacks.connect('units finalize', self._ycid = self.yaxis.callbacks.connect('units finalize',
self.relim) 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): def get_window_extent(self, *args, **kwargs):
""" """
get the axes bounding box in display space; *args* and get the axes bounding box in display space; *args* and
Expand Down Expand Up @@ -8815,7 +8832,15 @@ def __init__(self, fig, *args, **kwargs):
# _axes_class is set in the subplot_class_factory # _axes_class is set in the subplot_class_factory
self._axes_class.__init__(self, fig, self.figbox, **kwargs) 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): def get_geometry(self):
"""get the subplot geometry, eg 2,2,3""" """get the subplot geometry, eg 2,2,3"""
Expand Down Expand Up @@ -8897,6 +8922,22 @@ def subplot_class_factory(axes_class=None):
# This is provided for backward compatibility # This is provided for backward compatibility
Subplot = subplot_class_factory() 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(Axes=martist.kwdoc(Axes))
docstring.interpd.update(Subplot=martist.kwdoc(Axes)) docstring.interpd.update(Subplot=martist.kwdoc(Axes))


Expand Down
1 change: 0 additions & 1 deletion lib/matplotlib/axis.py
Expand Up @@ -597,7 +597,6 @@ class Ticker:
formatter = None formatter = None





class Axis(artist.Artist): class Axis(artist.Artist):


""" """
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/backends/__init__.py
Expand Up @@ -52,6 +52,6 @@ def do_nothing(*args, **kwargs): pass


matplotlib.verbose.report('backend %s version %s' % (backend,backend_version)) matplotlib.verbose.report('backend %s version %s' % (backend,backend_version))


return new_figure_manager, draw_if_interactive, show return backend_mod, new_figure_manager, draw_if_interactive, show




10 changes: 8 additions & 2 deletions lib/matplotlib/backends/backend_agg.py
Expand Up @@ -385,7 +385,6 @@ def post_processing(image, dpi):
image) image)





def new_figure_manager(num, *args, **kwargs): def new_figure_manager(num, *args, **kwargs):
""" """
Create a new figure manager instance Create a new figure manager instance
Expand All @@ -396,7 +395,14 @@ def new_figure_manager(num, *args, **kwargs):


FigureClass = kwargs.pop('FigureClass', Figure) FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs) 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) manager = FigureManagerBase(canvas, num)
return manager return manager


Expand Down
95 changes: 51 additions & 44 deletions lib/matplotlib/backends/backend_cairo.py
Expand Up @@ -26,15 +26,15 @@
def _fn_name(): return sys._getframe(1).f_code.co_name def _fn_name(): return sys._getframe(1).f_code.co_name


try: try:
import cairo import cairo
except ImportError: 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) _version_required = (1,2,0)
if cairo.version_info < _version_required: if cairo.version_info < _version_required:
raise ImportError ("Pycairo %d.%d.%d is installed\n" raise ImportError ("Pycairo %d.%d.%d is installed\n"
"Pycairo %d.%d.%d or later is required" "Pycairo %d.%d.%d or later is required"
% (cairo.version_info + _version_required)) % (cairo.version_info + _version_required))
backend_version = cairo.version backend_version = cairo.version
del _version_required del _version_required


Expand Down Expand Up @@ -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 _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))


if ismath: if ismath:
self._draw_mathtext(gc, x, y, s, prop, angle) self._draw_mathtext(gc, x, y, s, prop, angle)


else: else:
ctx = gc.ctx ctx = gc.ctx
ctx.new_path() ctx.new_path()
ctx.move_to (x, y) ctx.move_to (x, y)
ctx.select_font_face (prop.get_name(), ctx.select_font_face (prop.get_name(),
self.fontangles [prop.get_style()], self.fontangles [prop.get_style()],
self.fontweights[prop.get_weight()]) self.fontweights[prop.get_weight()])

size = prop.get_size_in_points() * self.dpi / 72.0 size = prop.get_size_in_points() * self.dpi / 72.0

ctx.save() ctx.save()
if angle: if angle:
ctx.rotate (-angle * np.pi / 180) ctx.rotate (-angle * np.pi / 180)
ctx.set_font_size (size) ctx.set_font_size (size)
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
ctx.show_text (s.encode("utf-8")) ctx.show_text (s.encode("utf-8"))
else: else:
ctx.show_text (s) ctx.show_text (s)
ctx.restore() ctx.restore()


def _draw_mathtext(self, gc, x, y, s, prop, angle): def _draw_mathtext(self, gc, x, y, s, prop, angle):
if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))
Expand All @@ -215,28 +215,28 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle):
ctx.save() ctx.save()
ctx.translate(x, y) ctx.translate(x, y)
if angle: if angle:
ctx.rotate (-angle * np.pi / 180) ctx.rotate (-angle * np.pi / 180)


for font, fontsize, s, ox, oy in glyphs: for font, fontsize, s, ox, oy in glyphs:
ctx.new_path() ctx.new_path()
ctx.move_to(ox, oy) ctx.move_to(ox, oy)

fontProp = ttfFontProperty(font) fontProp = ttfFontProperty(font)
ctx.save() ctx.save()
ctx.select_font_face (fontProp.name, ctx.select_font_face (fontProp.name,
self.fontangles [fontProp.style], self.fontangles [fontProp.style],
self.fontweights[fontProp.weight]) self.fontweights[fontProp.weight])

size = fontsize * self.dpi / 72.0 size = fontsize * self.dpi / 72.0
ctx.set_font_size(size) ctx.set_font_size(size)
ctx.show_text(s.encode("utf-8")) ctx.show_text(s.encode("utf-8"))
ctx.restore() ctx.restore()


for ox, oy, w, h in rects: for ox, oy, w, h in rects:
ctx.new_path() ctx.new_path()
ctx.rectangle (ox, oy, w, h) ctx.rectangle (ox, oy, w, h)
ctx.set_source_rgb (0, 0, 0) ctx.set_source_rgb (0, 0, 0)
ctx.fill_preserve() ctx.fill_preserve()


ctx.restore() ctx.restore()


Expand Down Expand Up @@ -397,10 +397,17 @@ def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py
""" """
Create a new figure manager instance 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) FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs) 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) manager = FigureManagerBase(canvas, num)
return manager return manager


Expand Down
11 changes: 10 additions & 1 deletion lib/matplotlib/backends/backend_cocoaagg.py
Expand Up @@ -35,12 +35,21 @@


mplBundle = NSBundle.bundleWithPath_(os.path.dirname(__file__)) mplBundle = NSBundle.bundleWithPath_(os.path.dirname(__file__))



def new_figure_manager(num, *args, **kwargs): def new_figure_manager(num, *args, **kwargs):
FigureClass = kwargs.pop('FigureClass', Figure) FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass( *args, **kwargs ) 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) return FigureManagerCocoaAgg(canvas, num)



## Below is the original show() function: ## Below is the original show() function:
#def show(): #def show():
# for manager in Gcf.get_all_fig_managers(): # for manager in Gcf.get_all_fig_managers():
Expand Down
9 changes: 8 additions & 1 deletion lib/matplotlib/backends/backend_emf.py
Expand Up @@ -688,7 +688,14 @@ def new_figure_manager(num, *args, **kwargs):
# main-level app (egg backend_gtk, backend_gtkagg) for pylab # main-level app (egg backend_gtk, backend_gtkagg) for pylab
FigureClass = kwargs.pop('FigureClass', Figure) FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs) 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) manager = FigureManagerEMF(canvas, num)
return manager return manager


Expand Down
7 changes: 7 additions & 0 deletions lib/matplotlib/backends/backend_fltkagg.py
Expand Up @@ -78,6 +78,13 @@ def new_figure_manager(num, *args, **kwargs):
""" """
FigureClass = kwargs.pop('FigureClass', Figure) FigureClass = kwargs.pop('FigureClass', Figure)
figure = FigureClass(*args, **kwargs) 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) window = Fltk.Fl_Double_Window(10,10,30,30)
canvas = FigureCanvasFltkAgg(figure) canvas = FigureCanvasFltkAgg(figure)
window.end() window.end()
Expand Down

0 comments on commit 7885d94

Please sign in to comment.