Revision: 6804
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6804&view=rev
Author:   leejjoon
Date:     2009-01-17 10:28:48 +0000 (Sat, 17 Jan 2009)

Log Message:
-----------
bbox_inches option for savefig

Modified Paths:
--------------
    trunk/matplotlib/CHANGELOG
    trunk/matplotlib/lib/matplotlib/backend_bases.py
    trunk/matplotlib/lib/matplotlib/figure.py
    trunk/matplotlib/lib/matplotlib/transforms.py

Modified: trunk/matplotlib/CHANGELOG
===================================================================
--- trunk/matplotlib/CHANGELOG  2009-01-17 05:47:42 UTC (rev 6803)
+++ trunk/matplotlib/CHANGELOG  2009-01-17 10:28:48 UTC (rev 6804)
@@ -1,3 +1,6 @@
+2009-01-16 Implement bbox_inches option for savefig. If bbox_inches is
+           "tight", try to determine the tight bounding box. - JJL
+
 2009-01-16 Fix bug in is_string_like so it doesn't raise an
            unnecessary exception. - EF
 

Modified: trunk/matplotlib/lib/matplotlib/backend_bases.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backend_bases.py    2009-01-17 05:47:42 UTC 
(rev 6803)
+++ trunk/matplotlib/lib/matplotlib/backend_bases.py    2009-01-17 10:28:48 UTC 
(rev 6804)
@@ -33,6 +33,9 @@
 import matplotlib.path as path
 from matplotlib import rcParams
 
+from matplotlib.transforms import Bbox, TransformedBbox, Affine2D
+import cStringIO
+
 class RendererBase:
     """An abstract base class to handle drawing/rendering operations.
 
@@ -1419,6 +1422,28 @@
         self.figure.set_facecolor(facecolor)
         self.figure.set_edgecolor(edgecolor)
 
+        bbox_inches = kwargs.pop("bbox_inches", None)
+
+        if bbox_inches:
+            # call adjust_bbox to save only the given area
+            if bbox_inches == "tight":
+                # save the figure to estimate the bounding box
+                result = getattr(self, method_name)(
+                    cStringIO.StringIO(),
+                    dpi=dpi,
+                    facecolor=facecolor,
+                    edgecolor=edgecolor,
+                    orientation=orientation,
+                    **kwargs)
+                renderer = self.figure._cachedRenderer
+                bbox_inches = self.figure.get_tightbbox(renderer)
+                pad = kwargs.pop("pad_inches", 0.1)
+                bbox_inches = bbox_inches.padded(pad)
+
+            restore_bbox = self._adjust_bbox(self.figure, format,
+                                             bbox_inches)
+
+
         try:
             result = getattr(self, method_name)(
                 filename,
@@ -1428,6 +1453,9 @@
                 orientation=orientation,
                 **kwargs)
         finally:
+            if bbox_inches and restore_bbox:
+                restore_bbox()
+
             self.figure.dpi = origDPI
             self.figure.set_facecolor(origfacecolor)
             self.figure.set_edgecolor(origedgecolor)
@@ -1435,6 +1463,91 @@
             #self.figure.canvas.draw() ## seems superfluous
         return result
 
+
+    def _adjust_bbox(self, fig, format, bbox_inches):
+        """
+        Temporarily adjust the figure so that only the specified area
+        (bbox_inches) is saved.
+
+        It modifies fig.bbox, fig.bbox_inches,
+        fig.transFigure._boxout, and fig.patch.  While the figure size
+        changes, the scale of the original figure is conserved.  A
+        function whitch restores the original values are returned.
+        """
+
+        origBbox = fig.bbox
+        origBboxInches = fig.bbox_inches
+        _boxout = fig.transFigure._boxout
+
+        def restore_bbox():
+            fig.bbox = origBbox
+            fig.bbox_inches = origBboxInches
+            fig.transFigure._boxout = _boxout
+            fig.transFigure.invalidate()
+            fig.patch.set_bounds(0, 0, 1, 1)
+
+        if format in ["png", "raw", "rgba"]:
+            self._adjust_bbox_png(fig, bbox_inches)
+            return restore_bbox
+        elif format in ["pdf", "eps"]:
+            self._adjust_bbox_pdf(fig, bbox_inches)
+            return restore_bbox
+        else:
+            warnings.warn("bbox_inches option for %s backend is not 
implemented yet." % (format))
+            return None
+
+
+    def _adjust_bbox_png(self, fig, bbox_inches):
+        """
+        _adjust_bbox for png (Agg) format
+        """
+
+        tr = fig.dpi_scale_trans
+
+        _bbox = TransformedBbox(bbox_inches,
+                                tr)
+        x0, y0 = _bbox.x0, _bbox.y0
+        fig.bbox_inches = Bbox.from_bounds(0, 0,
+                                           bbox_inches.width,
+                                           bbox_inches.height)
+
+        x0, y0 = _bbox.x0, _bbox.y0
+        w1, h1 = fig.bbox.width, fig.bbox.height
+        self.figure.transFigure._boxout = Bbox.from_bounds(-x0, -y0,
+                                                           w1, h1)
+        self.figure.transFigure.invalidate()
+
+        fig.bbox = TransformedBbox(fig.bbox_inches, tr)
+
+        fig.patch.set_bounds(x0/w1, y0/h1,
+                             fig.bbox.width/w1, fig.bbox.height/h1)
+
+
+    def _adjust_bbox_pdf(self, fig, bbox_inches):
+        """
+        _adjust_bbox for pdf & eps format
+        """
+
+        tr = Affine2D().scale(72)
+
+        _bbox = TransformedBbox(bbox_inches, tr)
+
+        fig.bbox_inches = Bbox.from_bounds(0, 0,
+                                           bbox_inches.width,
+                                           bbox_inches.height)
+        x0, y0 = _bbox.x0, _bbox.y0
+        f = 72. / fig.dpi
+        w1, h1 = fig.bbox.width*f, fig.bbox.height*f
+        self.figure.transFigure._boxout = Bbox.from_bounds(-x0, -y0,
+                                                           w1, h1)
+        self.figure.transFigure.invalidate()
+
+        fig.bbox = TransformedBbox(fig.bbox_inches, tr)
+
+        fig.patch.set_bounds(x0/w1, y0/h1,
+                             fig.bbox.width/w1, fig.bbox.height/h1)
+
+
     def get_default_filetype(self):
         raise NotImplementedError
 

Modified: trunk/matplotlib/lib/matplotlib/figure.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/figure.py   2009-01-17 05:47:42 UTC (rev 
6803)
+++ trunk/matplotlib/lib/matplotlib/figure.py   2009-01-17 10:28:48 UTC (rev 
6804)
@@ -33,6 +33,7 @@
 
 import matplotlib.cbook as cbook
 
+
 class SubplotParams:
     """
     A class to hold the parameters for a subplot
@@ -971,6 +972,16 @@
             a plot on top of a colored background on a web page.  The
             transparency of these patches will be restored to their
             original values upon exit of this function.
+
+          *bbox_inches*:
+            Bbox in inches. Only the given portion of the figure is
+            saved. If 'tight', try to figure out the tight bbox of
+            the figure.
+
+          *pad_inches*:
+            Amount of padding around the figure when bbox_inches is
+            'tight'.
+
         """
 
         for key in ('dpi', 'facecolor', 'edgecolor'):
@@ -1091,6 +1102,41 @@
         return blocking_input(timeout=timeout)
 
 
+
+    def get_tightbbox(self, renderer):
+        """
+        Return a (tight) bounding box of the figure in inches.
+
+        It only accounts axes title, axis labels, and axis
+        ticklabels. Needs improvement.
+        """
+
+        artists = []
+        bb = []
+        for ax in self.axes:
+
+            artists.append(ax.xaxis.label)
+            artists.append(ax.yaxis.label)
+            artists.append(ax.title)
+            artists.append(ax)
+
+            bbx1, bbx2 = ax.xaxis.get_ticklabel_extents(renderer)
+            bby1, bby2 = ax.yaxis.get_ticklabel_extents(renderer)
+            bb.extend([bbx1, bbx2, bby1, bby2])
+
+
+        bb.extend([c.get_window_extent(renderer) for c in artists \
+                   if c.get_visible()])
+
+        _bbox = Bbox.union([b for b in bb if b.width!=0 or b.height!=0])
+
+        bbox_inches = TransformedBbox(_bbox,
+                                      Affine2D().scale(1./self.dpi))
+
+        return bbox_inches
+
+
+
 def figaspect(arg):
     """
     Create a figure with specified aspect ratio.  If *arg* is a number,

Modified: trunk/matplotlib/lib/matplotlib/transforms.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/transforms.py       2009-01-17 05:47:42 UTC 
(rev 6803)
+++ trunk/matplotlib/lib/matplotlib/transforms.py       2009-01-17 10:28:48 UTC 
(rev 6804)
@@ -635,7 +635,7 @@
         Return a new :class:`Bbox` that is padded on all four sides by
         the given value.
         """
-        points = self._points
+        points = self.get_points()
         return Bbox(points + [[-p, -p], [p, p]])
 
     def translated(self, tx, ty):


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
This SF.net email is sponsored by:
SourcForge Community
SourceForge wants to tell your story.
http://p.sf.net/sfu/sf-spreadtheword
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to