I took a stab at this during the weekends and a patch is attached.

1) introduces a new command subplot2grid. e.g.,

subplot2grid(shape=(3,3), loc=(0,0), colspan=3)

it still creates a Subplot instance.

2) subplot command can take a SubplotSpec instance which is created
using the GridSpec.

gs = GridSpec(3,3) # shape=3,3
subplot(gs.new_subplotspec(loc=(0,0), colspan=3))

or

subplot(gs[0,:])

or

subplot(gs[0:3]) # supermongo-like

For suplot with a single cell,

subplot(gs[0])  # => subplot(331)

or

subplot(gs[0,0])


3) Each GridSpec can have associated subplot parameters (subplot
params of the figure is used if not set).


Also see the example (demo_gridspec.py).

We may further try to improve it (during the sprint maybe) if others
are happy with the patch.

Regards,

-JJ



On Sun, Aug 2, 2009 at 1:00 AM, John Hunter<jdh2...@gmail.com> wrote:
> On Sat, Aug 1, 2009 at 7:32 PM, Alan G Isaac<alan.is...@gmail.com> wrote:
>> On 8/1/2009 4:07 PM Thomas Robitaille apparently wrote:
>>> Since matplotlib is about to hit 0.99,
>>
>>
>> Which reminds me, was there a decision on subplot2grid etc?
>> <URL:http://sourceforge.net/mailarchive/message.php?msg_name=6e8d907b0905172009j21b5077fp242c7598ee9fb2c9%40mail.gmail.com>
>
> There are lots of good suggestions in that thread -- on this issue,
> all the best people will be at scipy and/or participating in the
> sprint (Andrew who wrote the mpl sizer toolkit, JJ who does more
> strange and wonderful things than anyone, Ryan May who has thought
> through the issues and has done a lot of great work).  So we'll
> definitely bring it up and see if we can do something about it.  There
> are two pieces to this thread: the non-pythonic 1 based addressing of
> the current subplot command ("don't blame me, talk to the mathworks"),
> and the ability to easily specify column or row spans across the grid.
>  The former is a minor wart that is unlikely to change, the latter is
> a significant feature that we should definitely support. Maybe you can
> join us via skype if not in person in  Pasadena, and we can try an
> improve the current implementation.  I don't imagine adding support
> for spans would be too hard,
>
> JDH
>
> ------------------------------------------------------------------------------
> Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day
> trial. Simplify your report design, integration and deployment - and focus on
> what you do best, core application coding. Discover what's new with
> Crystal Reports now.  http://p.sf.net/sfu/bobj-july
> _______________________________________________
> Matplotlib-users mailing list
> Matplotlib-users@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/matplotlib-users
>
diff --git examples/pylab_examples/demo_gridspec.py examples/pylab_examples/demo_gridspec.py
new file mode 100644
index 0000000..4d0751a
--- /dev/null
+++ examples/pylab_examples/demo_gridspec.py
@@ -0,0 +1,43 @@
+import matplotlib.pyplot as plt
+
+# demo 1 : subplot2grid
+
+plt.figure(1)
+ax1 = plt.subplot2grid((3,3), (0,0), colspan=3)
+ax2 = plt.subplot2grid((3,3), (1,0), colspan=2)
+ax3 = plt.subplot2grid((3,3), (1, 2), rowspan=2)
+ax4 = plt.subplot2grid((3,3), (2, 0))
+ax5 = plt.subplot2grid((3,3), (2, 1))
+
+# demo 2 : gridspec with python indexing
+
+plt.figure(2)
+
+from matplotlib.axes import GridSpec
+
+gs = GridSpec(3, 3)
+ax1 = plt.subplot(gs[0, :])
+# identical to ax1 = plt.subplot(gs.new_subplotspec((0,0), colspan=3))
+ax2 = plt.subplot(gs[1,:-1])
+ax3 = plt.subplot(gs[1:, -1])
+ax4 = plt.subplot(gs[-1,0])
+ax5 = plt.subplot(gs[-1,-2])
+
+
+# demo 3 : gridspec with subplotpars set.
+
+f = plt.figure(3)
+
+gs1 = GridSpec(3, 3)
+ax1 = f.add_subplot(gs1[:-1, :])
+ax2 = f.add_subplot(gs1[-1, :-1])
+ax2 = f.add_subplot(gs1[-1, -1])
+gs1.update(left=0.05, right=0.48, wspace=0.05)
+
+gs2 = GridSpec(3, 3)
+ax1 = f.add_subplot(gs2[:, :-1])
+ax2 = f.add_subplot(gs2[:-1, -1])
+ax2 = f.add_subplot(gs2[-1, -1])
+gs2.update(left=0.55, right=0.98, hspace=0.05)
+
+plt.show()
diff --git lib/matplotlib/axes.py lib/matplotlib/axes.py
index 77e9cc4..0af5254 100644
--- lib/matplotlib/axes.py
+++ lib/matplotlib/axes.py
@@ -7749,6 +7749,200 @@ class Axes(martist.Artist):
         self.yaxis.set_minor_locator(mticker.NullLocator())
 
 
+
+class GridSpec(object):
+    def __init__(self, nrows, ncols, 
+                 left=None, bottom=None, right=None, top=None,
+                 wspace=None, hspace=None):
+        #self.figure = figure
+        self._nrows , self._ncols = nrows, ncols
+        self.left=left
+        self.bottom=bottom
+        self.right=right
+        self.top=top
+        self.wspace=wspace
+        self.hspace=hspace
+
+    def get_geometry(self):
+        return self._nrows, self._ncols
+    
+    _AllowedKeys = ["left", "bottom", "right", "top", "wspace", "hspace"]
+    
+    def update(self, **kwargs):
+        """
+        Update the current values.  If any kwarg is None, default to
+        the current value, if set, otherwise to rc
+
+        """
+
+        for k, v in kwargs.items():
+            if k in self._AllowedKeys:
+                setattr(self, k, v)
+            else:
+                raise AttributeError("%s is unknown keyword" % (k,))
+
+
+        from matplotlib import _pylab_helpers
+        for figmanager in _pylab_helpers.Gcf.figs.values():
+            for ax in figmanager.canvas.figure.axes:
+                # copied from Figure.subplots_adjust
+                if not isinstance(ax, SubplotBase):
+                    # Check if sharing a subplots axis
+                    if ax._sharex is not None and isinstance(ax._sharex, SubplotBase):
+                        ax._sharex.update_params()
+                        ax.set_position(ax._sharex.figbox)
+                    elif ax._sharey is not None and isinstance(ax._sharey,SubplotBase):
+                        ax._sharey.update_params()
+                        ax.set_position(ax._sharey.figbox)
+                else:
+                    ax.update_params()
+                    ax.set_position(ax.figbox)
+                    
+            
+    def get_subplot_params(self, fig=None):
+        from matplotlib.figure import SubplotParams
+        import copy
+        if fig is None:
+            kw = dict([(k, rcParams["figure.subplot."+k]) \
+                       for k in self._AllowedKeys])
+            subplotpars = SubplotParams(**kw)
+        else:
+            subplotpars = copy.copy(fig.subplotpars)
+
+        update_kw = dict([(k, getattr(self, k)) for k in self._AllowedKeys])
+        subplotpars.update(**update_kw)
+
+        return subplotpars
+
+
+    def new_subplotspec(self, loc, rowspan=1, colspan=1):
+        """
+        return SuplotSpec instance
+        """
+        loc1, loc2 = loc
+        subplotspec = self[loc1:loc1+rowspan, loc2:loc2+colspan]
+        return subplotspec
+    
+
+    def __getitem__(self, key):
+        """
+        return SuplotSpec instance
+        """
+        nrows, ncols = self.get_geometry()
+        total = nrows*ncols
+        
+        if isinstance(key, tuple):
+            try:
+                k1, k2 = key
+            except ValueError:
+                raise ValueError("unrecognized subplot spec")
+
+            if isinstance(k1, slice):
+                row1, row2, _ = k1.indices(nrows)
+            else:
+                if k1 < 0:
+                    k1 += nrows
+                row1, row2 = k1, k1+1
+
+
+            if isinstance(k2, slice):
+                col1, col2, _ = k2.indices(ncols)
+            else:
+                if k2 < 0:
+                    k2 += ncols
+                col1, col2 = k2, k2+1
+
+
+            num1 = row1*nrows + col1
+            num2 = (row2-1)*nrows + (col2-1)
+
+        # single key
+        else:
+            if isinstance(key, slice):
+                num1, num2, _ = key.indices(total)
+                num2 -= 1
+            else:
+                if key < 0:
+                    key += total
+                num1, num2 = key, None
+
+
+        return SubplotSpec(self, num1, num2)
+
+
+
+class SubplotSpec(object):
+    def __init__(self, gridspec, num1, num2=None):
+
+        rows, cols = gridspec.get_geometry()
+        total = rows*cols
+
+        self._gridspec = gridspec
+        self.num1 = num1
+        self.num2 = num2
+
+    def get_gridspec(self):
+        return self._gridspec
+
+    
+    def get_geometry(self):
+        'get the subplot geometry, eg 2,2,3. Unlike SuplorParams, index is 0-based'
+        rows, cols = self.get_gridspec().get_geometry()
+        #if (self.num2 is None) or (self.num1 == self.num2):
+        return rows, cols, self.num1, self.num2
+            
+
+    def get_position(self, fig, return_all=False):
+        'update the subplot position from fig.subplotpars'
+
+        gridspec = self.get_gridspec()
+        rows, cols = gridspec.get_geometry()
+
+        subplot_params = gridspec.get_subplot_params(fig)
+        left = subplot_params.left
+        right = subplot_params.right
+        bottom = subplot_params.bottom
+        top = subplot_params.top
+        wspace = subplot_params.wspace
+        hspace = subplot_params.hspace
+        totWidth = right-left
+        totHeight = top-bottom
+
+        figH = totHeight/(rows + hspace*(rows-1))
+        sepH = hspace*figH
+
+        figW = totWidth/(cols + wspace*(cols-1))
+        sepW = wspace*figW
+
+        rowNum, colNum =  divmod(self.num1, cols)
+        figBottom = top - (rowNum+1)*figH - rowNum*sepH
+        figLeft = left + colNum*(figW + sepW)
+        figTop = figBottom + figH
+        figRight = figLeft + figW
+
+        if self.num2 is not None:
+
+            rowNum2, colNum2 =  divmod(self.num2, cols)
+            figBottom2 = top - (rowNum2+1)*figH - rowNum2*sepH
+            figLeft2 = left + colNum2*(figW + sepW)
+            figTop2 = figBottom2 + figH
+            figRight2 = figLeft2 + figW
+
+            figBottom = min(figBottom, figBottom2)
+            figLeft = min(figLeft, figLeft2)
+            figTop = max(figTop, figTop2)
+            figRight = max(figRight, figRight2)
+            
+        figbox = mtransforms.Bbox.from_extents(figLeft, figBottom,
+                                               figRight, figTop)
+
+
+        if return_all:
+            return figbox, rowNum, colNum, rows, cols
+        else:
+            return figbox
+
+
 class SubplotBase:
     """
     Base class for subplots, which are :class:`Axes` instances with
@@ -7772,25 +7966,28 @@ class SubplotBase:
 
         self.figure = fig
 
-        if len(args)==1:
-            s = str(args[0])
-            if len(s) != 3:
-                raise ValueError('Argument to subplot must be a 3 digits long')
-            rows, cols, num = map(int, s)
+        if len(args) == 1:
+            if isinstance(args[0], SubplotSpec):
+                self._subplotspec = args[0]
+            
+            else:
+                s = str(args[0])
+                if len(s) != 3:
+                    raise ValueError('Argument to subplot must be a 3 digits long')
+                rows, cols, num = map(int, s)
+                self._subplotspec = GridSpec(rows, cols)[num-1]
+                # num - 1 for converting from matlab to python indexing
         elif len(args)==3:
             rows, cols, num = args
+            self._subplotspec = GridSpec(rows, cols)[num-1]
+                # num - 1 for converting from matlab to python indexing
         else:
             raise ValueError(  'Illegal argument to subplot')
 
 
-        total = rows*cols
-        num -= 1    # convert from matlab to python indexing
-                    # ie num in range(0,total)
-        if num >= total:
-            raise ValueError( 'Subplot number exceeds total subplots')
-        self._rows = rows
-        self._cols = cols
-        self._num = num
+        #self._rows = rows
+        #self._cols = cols
+        #self._num = num
 
         self.update_params()
 
@@ -7799,63 +7996,31 @@ class SubplotBase:
 
     def get_geometry(self):
         'get the subplot geometry, eg 2,2,3'
-        return self._rows, self._cols, self._num+1
+        rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
+        return rows, cols, num1+1 # for compatibility
 
     # COVERAGE NOTE: Never used internally or from examples
     def change_geometry(self, numrows, numcols, num):
         'change subplot geometry, eg. from 1,1,1 to 2,2,3'
-        self._rows = numrows
-        self._cols = numcols
-        self._num = num-1
+        #self._rows = numrows
+        #self._cols = numcols
+        #self._num = num-1
+        self._subplotspec = GridSpec(numrows, numcols)[num-1]
         self.update_params()
         self.set_position(self.figbox)
 
+    def get_subplotspec(self):
+        return self._subplotspec
+
+    def set_subplotspec(self, subplotspec):
+        self._subplotspec = subplotspec
+    
     def update_params(self):
         'update the subplot position from fig.subplotpars'
 
-        rows = self._rows
-        cols = self._cols
-        num = self._num
-
-        pars = self.figure.subplotpars
-        left = pars.left
-        right = pars.right
-        bottom = pars.bottom
-        top = pars.top
-        wspace = pars.wspace
-        hspace = pars.hspace
-        totWidth = right-left
-        totHeight = top-bottom
-
-        figH = totHeight/(rows + hspace*(rows-1))
-        sepH = hspace*figH
-
-        figW = totWidth/(cols + wspace*(cols-1))
-        sepW = wspace*figW
-
-        rowNum, colNum =  divmod(num, cols)
-
-        figBottom = top - (rowNum+1)*figH - rowNum*sepH
-        figLeft = left + colNum*(figW + sepW)
-
-        self.figbox = mtransforms.Bbox.from_bounds(figLeft, figBottom,
-                                                   figW, figH)
-        self.rowNum = rowNum
-        self.colNum = colNum
-        self.numRows = rows
-        self.numCols = cols
-
-        if 0:
-            print 'rcn', rows, cols, num
-            print 'lbrt', left, bottom, right, top
-            print 'self.figBottom', self.figBottom
-            print 'self.figLeft', self.figLeft
-            print 'self.figW', self.figW
-            print 'self.figH', self.figH
-            print 'self.rowNum', self.rowNum
-            print 'self.colNum', self.colNum
-            print 'self.numRows', self.numRows
-            print 'self.numCols', self.numCols
+        self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \
+                     self.get_subplotspec().get_position(self.figure,
+                                                         return_all=True)
 
 
     def is_first_col(self):
diff --git lib/matplotlib/pyplot.py lib/matplotlib/pyplot.py
index 03b3977..b62b8cb 100644
--- lib/matplotlib/pyplot.py
+++ lib/matplotlib/pyplot.py
@@ -648,6 +648,42 @@ def subplot(*args, **kwargs):
     draw_if_interactive()
     return a
 
+from matplotlib.axes import GridSpec
+def subplot2grid(shape, loc, rowspan=1, colspan=1, **kwargs):
+    """
+
+    It creates a subplot in a grid of *shape*, at location of *loc*, 
+    spanning *rowspan*, *colspan* cells in each direction.
+    The index for loc is 0-based. ::
+
+      subplot2grid(shape, loc, rowspan=1, colspan=1)
+
+    is identical to ::
+
+      gridspec=GridSpec(shape[0], shape[2])
+      subplotspec=gridspec.new_subplotspec(loc, rowspan, colspan)
+      subplot(subplotspec)
+
+
+    """
+
+    fig = gcf()
+    s1, s2 = shape
+    subplotspec = GridSpec(s1, s2).new_subplotspec(loc,
+                                                   rowspan=rowspan,
+                                                   colspan=colspan)
+    a = fig.add_subplot(subplotspec, **kwargs)
+    bbox = a.bbox
+    byebye = []
+    for other in fig.axes:
+        if other==a: continue
+        if bbox.fully_overlaps(other.bbox):
+            byebye.append(other)
+    for ax in byebye: delaxes(ax)
+
+    draw_if_interactive()
+    return a
+
 
 def twinx(ax=None):
     """
------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with 
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Reply via email to