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