Skip to content

Commit 740a1e9

Browse files
committed
Add GridSpec.subplots()
so that one can separately pass `gridspec_kw` directly to the gridspec constructor.
1 parent 431e993 commit 740a1e9

File tree

7 files changed

+159
-81
lines changed

7 files changed

+159
-81
lines changed
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
GridSpec.subplots()
2+
```````````````````
3+
4+
The `GridSpec` class gained a `~.GridSpec.subplots` method, so that one can
5+
write ::
6+
7+
fig.add_gridspec(2, 2, height_ratios=[3, 1]).subplots()
8+
9+
as an alternative to ::
10+
11+
fig.subplots(2, 2, gridspec_kw={"height_ratios": [3, 1]})

examples/lines_bars_and_markers/linestyles.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ def plot_linestyles(ax, linestyles, title):
6666
color="blue", fontsize=8, ha="right", family="monospace")
6767

6868

69-
fig, (ax0, ax1) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 3]},
70-
figsize=(10, 8))
69+
ax0, ax1 = (plt.figure(figsize=(10, 8))
70+
.add_gridspec(2, 1, height_ratios=[1, 3])
71+
.subplots())
7172

7273
plot_linestyles(ax0, linestyle_str[::-1], title='Named linestyles')
7374
plot_linestyles(ax1, linestyle_tuple[::-1], title='Parametrized linestyles')

examples/subplots_axes_and_figures/subplots_demo.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,16 @@
143143
# labels of inner Axes are automatically removed by *sharex* and *sharey*.
144144
# Still there remains an unused empty space between the subplots.
145145
#
146-
# The parameter *gridspec_kw* of `.pyplot.subplots` controls the grid
147-
# properties (see also `.GridSpec`). For example, we can reduce the height
148-
# between vertical subplots using ``gridspec_kw={'hspace': 0}``.
146+
# To precisely control the positioning of the subplots, one can explicitly
147+
# create a `.GridSpec` with `.add_gridspec`, and then call its
148+
# `~.GridSpec.subplots` method. For example, we can reduce the height
149+
# between vertical subplots using ``add_gridspec(hspace=0)``.
149150
#
150151
# `.label_outer` is a handy method to remove labels and ticks from subplots
151152
# that are not at the edge of the grid.
152153

153-
fig, axs = plt.subplots(3, sharex=True, sharey=True, gridspec_kw={'hspace': 0})
154+
fig = plt.figure()
155+
axs = fig.add_gridspec(3, hspace=0).subplots(sharex=True, sharey=True)
154156
fig.suptitle('Sharing both axes')
155157
axs[0].plot(x, y ** 2)
156158
axs[1].plot(x, 0.3 * y, 'o')
@@ -164,8 +166,9 @@
164166
# Apart from ``True`` and ``False``, both *sharex* and *sharey* accept the
165167
# values 'row' and 'col' to share the values only per row or column.
166168

167-
fig, axs = plt.subplots(2, 2, sharex='col', sharey='row',
168-
gridspec_kw={'hspace': 0, 'wspace': 0})
169+
fig = plt.figure()
170+
axs = (fig.add_gridspec(2, 2, hspace=0, wspace=0)
171+
.subplots(sharex='col', sharey='row'))
169172
(ax1, ax2), (ax3, ax4) = axs
170173
fig.suptitle('Sharing x per column, y per row')
171174
ax1.plot(x, y)

examples/userdemo/demo_gridspec06.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ def squiggle_xy(a, b, c, d):
2222
for b in range(4):
2323
# gridspec inside gridspec
2424
inner_grid = outer_grid[a, b].subgridspec(3, 3, wspace=0, hspace=0)
25-
for c in range(3):
26-
for d in range(3):
27-
ax = fig.add_subplot(inner_grid[c, d])
28-
ax.plot(*squiggle_xy(a + 1, b + 1, c + 1, d + 1))
29-
ax.set(xticks=[], yticks=[])
25+
axs = inner_grid.subplots() # Create all subplots for the inner grid.
26+
for (c, d), ax in np.ndenumerate(axs):
27+
ax.plot(*squiggle_xy(a + 1, b + 1, c + 1, d + 1))
28+
ax.set(xticks=[], yticks=[])
3029

3130
# show only the outside spines
3231
for ax in fig.get_axes():

lib/matplotlib/figure.py

+6-63
Original file line numberDiff line numberDiff line change
@@ -1528,68 +1528,11 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False,
15281528
# Note that this is the same as
15291529
fig.subplots(2, 2, sharex=True, sharey=True)
15301530
"""
1531-
1532-
if isinstance(sharex, bool):
1533-
sharex = "all" if sharex else "none"
1534-
if isinstance(sharey, bool):
1535-
sharey = "all" if sharey else "none"
1536-
# This check was added because it is very easy to type
1537-
# `subplots(1, 2, 1)` when `subplot(1, 2, 1)` was intended.
1538-
# In most cases, no error will ever occur, but mysterious behavior
1539-
# will result because what was intended to be the subplot index is
1540-
# instead treated as a bool for sharex.
1541-
if isinstance(sharex, Integral):
1542-
cbook._warn_external(
1543-
"sharex argument to subplots() was an integer. Did you "
1544-
"intend to use subplot() (without 's')?")
1545-
cbook._check_in_list(["all", "row", "col", "none"],
1546-
sharex=sharex, sharey=sharey)
1547-
if subplot_kw is None:
1548-
subplot_kw = {}
15491531
if gridspec_kw is None:
15501532
gridspec_kw = {}
1551-
# don't mutate kwargs passed by user...
1552-
subplot_kw = subplot_kw.copy()
1553-
gridspec_kw = gridspec_kw.copy()
1554-
1555-
if self.get_constrained_layout():
1556-
gs = GridSpec(nrows, ncols, figure=self, **gridspec_kw)
1557-
else:
1558-
# this should turn constrained_layout off if we don't want it
1559-
gs = GridSpec(nrows, ncols, figure=None, **gridspec_kw)
1560-
self._gridspecs.append(gs)
1561-
1562-
# Create array to hold all axes.
1563-
axarr = np.empty((nrows, ncols), dtype=object)
1564-
for row in range(nrows):
1565-
for col in range(ncols):
1566-
shared_with = {"none": None, "all": axarr[0, 0],
1567-
"row": axarr[row, 0], "col": axarr[0, col]}
1568-
subplot_kw["sharex"] = shared_with[sharex]
1569-
subplot_kw["sharey"] = shared_with[sharey]
1570-
axarr[row, col] = self.add_subplot(gs[row, col], **subplot_kw)
1571-
1572-
# turn off redundant tick labeling
1573-
if sharex in ["col", "all"]:
1574-
# turn off all but the bottom row
1575-
for ax in axarr[:-1, :].flat:
1576-
ax.xaxis.set_tick_params(which='both',
1577-
labelbottom=False, labeltop=False)
1578-
ax.xaxis.offsetText.set_visible(False)
1579-
if sharey in ["row", "all"]:
1580-
# turn off all but the first column
1581-
for ax in axarr[:, 1:].flat:
1582-
ax.yaxis.set_tick_params(which='both',
1583-
labelleft=False, labelright=False)
1584-
ax.yaxis.offsetText.set_visible(False)
1585-
1586-
if squeeze:
1587-
# Discarding unneeded dimensions that equal 1. If we only have one
1588-
# subplot, just return it instead of a 1-element array.
1589-
return axarr.item() if axarr.size == 1 else axarr.squeeze()
1590-
else:
1591-
# Returned axis array will be always 2-d, even if nrows=ncols=1.
1592-
return axarr
1533+
return (self.add_gridspec(nrows, ncols, figure=self, **gridspec_kw)
1534+
.subplots(sharex=sharex, sharey=sharey, squeeze=squeeze,
1535+
subplot_kw=subplot_kw))
15931536

15941537
def delaxes(self, ax):
15951538
"""
@@ -2599,17 +2542,17 @@ def align_labels(self, axs=None):
25992542
self.align_xlabels(axs=axs)
26002543
self.align_ylabels(axs=axs)
26012544

2602-
def add_gridspec(self, nrows, ncols, **kwargs):
2545+
def add_gridspec(self, nrows=1, ncols=1, **kwargs):
26032546
"""
26042547
Return a `.GridSpec` that has this figure as a parent. This allows
26052548
complex layout of axes in the figure.
26062549
26072550
Parameters
26082551
----------
2609-
nrows : int
2552+
nrows : int, default: 1
26102553
Number of rows in grid.
26112554
2612-
ncols : int
2555+
ncols : int, default: 1
26132556
Number or columns in grid.
26142557
26152558
Returns

lib/matplotlib/gridspec.py

+122
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import copy
1313
import logging
14+
from numbers import Integral
1415

1516
import numpy as np
1617

@@ -234,6 +235,127 @@ def _normalize(key, size, axis): # Includes last index.
234235

235236
return SubplotSpec(self, num1, num2)
236237

238+
def subplots(self, sharex=False, sharey=False, squeeze=True,
239+
subplot_kw=None):
240+
"""
241+
Add all subplots specified by this `GridSpec` to its parent figure.
242+
243+
This utility wrapper makes it convenient to create common layouts of
244+
subplots in a single call.
245+
246+
Parameters
247+
----------
248+
sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False
249+
Controls sharing of properties among x (`sharex`) or y (`sharey`)
250+
axes:
251+
252+
- True or 'all': x- or y-axis will be shared among all
253+
subplots.
254+
- False or 'none': each subplot x- or y-axis will be
255+
independent.
256+
- 'row': each subplot row will share an x- or y-axis.
257+
- 'col': each subplot column will share an x- or y-axis.
258+
259+
When subplots have a shared x-axis along a column, only the x tick
260+
labels of the bottom subplot are created. Similarly, when subplots
261+
have a shared y-axis along a row, only the y tick labels of the
262+
first column subplot are created. To later turn other subplots'
263+
ticklabels on, use `~matplotlib.axes.Axes.tick_params`.
264+
265+
squeeze : bool, optional, default: True
266+
- If True, extra dimensions are squeezed out from the returned
267+
array of Axes:
268+
269+
- if only one subplot is constructed (nrows=ncols=1), the
270+
resulting single Axes object is returned as a scalar.
271+
- for Nx1 or 1xM subplots, the returned object is a 1D numpy
272+
object array of Axes objects.
273+
- for NxM, subplots with N>1 and M>1 are returned
274+
as a 2D array.
275+
276+
- If False, no squeezing at all is done: the returned Axes object
277+
is always a 2D array containing Axes instances, even if it ends
278+
up being 1x1.
279+
280+
subplot_kw : dict, optional
281+
Dict with keywords passed to the
282+
:meth:`~matplotlib.figure.Figure.add_subplot` call used to create
283+
each subplot.
284+
285+
Returns
286+
-------
287+
ax : `~.axes.Axes` object or array of Axes objects.
288+
*ax* can be either a single `~matplotlib.axes.Axes` object or
289+
an array of Axes objects if more than one subplot was created. The
290+
dimensions of the resulting array can be controlled with the
291+
squeeze keyword, see above.
292+
293+
See Also
294+
--------
295+
.pyplot.subplots
296+
.Figure.add_subplot
297+
.pyplot.subplot
298+
"""
299+
300+
figure = self[0, 0].get_topmost_subplotspec().get_gridspec().figure
301+
302+
if figure is None:
303+
raise ValueError("GridSpec.subplots() only works for GridSpecs "
304+
"created with a parent figure")
305+
306+
if isinstance(sharex, bool):
307+
sharex = "all" if sharex else "none"
308+
if isinstance(sharey, bool):
309+
sharey = "all" if sharey else "none"
310+
# This check was added because it is very easy to type
311+
# `subplots(1, 2, 1)` when `subplot(1, 2, 1)` was intended.
312+
# In most cases, no error will ever occur, but mysterious behavior
313+
# will result because what was intended to be the subplot index is
314+
# instead treated as a bool for sharex.
315+
if isinstance(sharex, Integral):
316+
cbook._warn_external(
317+
"sharex argument to subplots() was an integer. Did you "
318+
"intend to use subplot() (without 's')?")
319+
cbook._check_in_list(["all", "row", "col", "none"],
320+
sharex=sharex, sharey=sharey)
321+
if subplot_kw is None:
322+
subplot_kw = {}
323+
# don't mutate kwargs passed by user...
324+
subplot_kw = subplot_kw.copy()
325+
326+
# Create array to hold all axes.
327+
axarr = np.empty((self._nrows, self._ncols), dtype=object)
328+
for row in range(self._nrows):
329+
for col in range(self._ncols):
330+
shared_with = {"none": None, "all": axarr[0, 0],
331+
"row": axarr[row, 0], "col": axarr[0, col]}
332+
subplot_kw["sharex"] = shared_with[sharex]
333+
subplot_kw["sharey"] = shared_with[sharey]
334+
axarr[row, col] = figure.add_subplot(
335+
self[row, col], **subplot_kw)
336+
337+
# turn off redundant tick labeling
338+
if sharex in ["col", "all"]:
339+
# turn off all but the bottom row
340+
for ax in axarr[:-1, :].flat:
341+
ax.xaxis.set_tick_params(which='both',
342+
labelbottom=False, labeltop=False)
343+
ax.xaxis.offsetText.set_visible(False)
344+
if sharey in ["row", "all"]:
345+
# turn off all but the first column
346+
for ax in axarr[:, 1:].flat:
347+
ax.yaxis.set_tick_params(which='both',
348+
labelleft=False, labelright=False)
349+
ax.yaxis.offsetText.set_visible(False)
350+
351+
if squeeze:
352+
# Discarding unneeded dimensions that equal 1. If we only have one
353+
# subplot, just return it instead of a 1-element array.
354+
return axarr.item() if axarr.size == 1 else axarr.squeeze()
355+
else:
356+
# Returned axis array will be always 2-d, even if nrows=ncols=1.
357+
return axarr
358+
237359

238360
class GridSpec(GridSpecBase):
239361
"""

tutorials/intermediate/gridspec.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,10 @@ def squiggle_xy(a, b, c, d, i=np.arange(0.0, 2*np.pi, 0.05)):
239239
for b in range(4):
240240
# gridspec inside gridspec
241241
inner_grid = outer_grid[a, b].subgridspec(3, 3, wspace=0, hspace=0)
242-
for c in range(3):
243-
for d in range(3):
244-
ax = fig11.add_subplot(inner_grid[c, d])
245-
ax.plot(*squiggle_xy(a + 1, b + 1, c + 1, d + 1))
246-
ax.set(xticks=[], yticks=[])
242+
axs = inner_grid.subplots() # Create all subplots for the inner grid.
243+
for (c, d), ax in np.ndenumerate(axs):
244+
ax.plot(*squiggle_xy(a + 1, b + 1, c + 1, d + 1))
245+
ax.set(xticks=[], yticks=[])
247246

248247
# show only the outside spines
249248
for ax in fig11.get_axes():

0 commit comments

Comments
 (0)