Skip to content

Commit c6d05cf

Browse files
committed
Add GridSpec.subplots()
so that one can separately pass `gridspec_kw` directly to the gridspec constructor.
1 parent 39192d5 commit c6d05cf

File tree

5 files changed

+146
-70
lines changed

5 files changed

+146
-70
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)

lib/matplotlib/figure.py

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

16021543
def _remove_ax(self, ax):
16031544
def _reset_loc_form(axis):

lib/matplotlib/gridspec.py

+120
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import copy
1717
import logging
18+
from numbers import Integral
1819

1920
import numpy as np
2021

@@ -316,6 +317,125 @@ def get_subplot_params(self, figure=None):
316317
def locally_modified_subplot_params(self):
317318
return [k for k in self._AllowedKeys if getattr(self, k)]
318319

320+
def subplots(self, sharex=False, sharey=False, squeeze=True,
321+
subplot_kw=None):
322+
"""
323+
Add all subplots specified by this `GridSpec` to its parent figure.
324+
325+
This utility wrapper makes it convenient to create common layouts of
326+
subplots in a single call.
327+
328+
Parameters
329+
----------
330+
sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False
331+
Controls sharing of properties among x (`sharex`) or y (`sharey`)
332+
axes:
333+
334+
- True or 'all': x- or y-axis will be shared among all
335+
subplots.
336+
- False or 'none': each subplot x- or y-axis will be
337+
independent.
338+
- 'row': each subplot row will share an x- or y-axis.
339+
- 'col': each subplot column will share an x- or y-axis.
340+
341+
When subplots have a shared x-axis along a column, only the x tick
342+
labels of the bottom subplot are created. Similarly, when subplots
343+
have a shared y-axis along a row, only the y tick labels of the
344+
first column subplot are created. To later turn other subplots'
345+
ticklabels on, use `~matplotlib.axes.Axes.tick_params`.
346+
347+
squeeze : bool, optional, default: True
348+
- If True, extra dimensions are squeezed out from the returned
349+
array of Axes:
350+
351+
- if only one subplot is constructed (nrows=ncols=1), the
352+
resulting single Axes object is returned as a scalar.
353+
- for Nx1 or 1xM subplots, the returned object is a 1D numpy
354+
object array of Axes objects.
355+
- for NxM, subplots with N>1 and M>1 are returned
356+
as a 2D array.
357+
358+
- If False, no squeezing at all is done: the returned Axes object
359+
is always a 2D array containing Axes instances, even if it ends
360+
up being 1x1.
361+
362+
subplot_kw : dict, optional
363+
Dict with keywords passed to the
364+
:meth:`~matplotlib.figure.Figure.add_subplot` call used to create
365+
each subplot.
366+
367+
Returns
368+
-------
369+
ax : `~.axes.Axes` object or array of Axes objects.
370+
*ax* can be either a single `~matplotlib.axes.Axes` object or
371+
an array of Axes objects if more than one subplot was created. The
372+
dimensions of the resulting array can be controlled with the
373+
squeeze keyword, see above.
374+
375+
See Also
376+
--------
377+
.pyplot.subplots
378+
.Figure.add_subplot
379+
.pyplot.subplot
380+
"""
381+
382+
if self.figure is None:
383+
raise ValueError("GridSpec.subplots() only works for GridSpecs "
384+
"created with a parent figure")
385+
386+
if isinstance(sharex, bool):
387+
sharex = "all" if sharex else "none"
388+
if isinstance(sharey, bool):
389+
sharey = "all" if sharey else "none"
390+
# This check was added because it is very easy to type
391+
# `subplots(1, 2, 1)` when `subplot(1, 2, 1)` was intended.
392+
# In most cases, no error will ever occur, but mysterious behavior
393+
# will result because what was intended to be the subplot index is
394+
# instead treated as a bool for sharex.
395+
if isinstance(sharex, Integral):
396+
cbook._warn_external(
397+
"sharex argument to subplots() was an integer. Did you "
398+
"intend to use subplot() (without 's')?")
399+
cbook._check_in_list(["all", "row", "col", "none"],
400+
sharex=sharex, sharey=sharey)
401+
if subplot_kw is None:
402+
subplot_kw = {}
403+
# don't mutate kwargs passed by user...
404+
subplot_kw = subplot_kw.copy()
405+
406+
# Create array to hold all axes.
407+
axarr = np.empty((self._nrows, self._ncols), dtype=object)
408+
for row in range(self._nrows):
409+
for col in range(self._ncols):
410+
shared_with = {"none": None, "all": axarr[0, 0],
411+
"row": axarr[row, 0], "col": axarr[0, col]}
412+
subplot_kw["sharex"] = shared_with[sharex]
413+
subplot_kw["sharey"] = shared_with[sharey]
414+
axarr[row, col] = self.figure.add_subplot(
415+
self[row, col], **subplot_kw)
416+
417+
# turn off redundant tick labeling
418+
if sharex in ["col", "all"]:
419+
# turn off all but the bottom row
420+
for ax in axarr[:-1, :].flat:
421+
ax.xaxis.set_tick_params(which='both',
422+
labelbottom=False, labeltop=False)
423+
ax.xaxis.offsetText.set_visible(False)
424+
if sharey in ["row", "all"]:
425+
# turn off all but the first column
426+
for ax in axarr[:, 1:].flat:
427+
ax.yaxis.set_tick_params(which='both',
428+
labelleft=False, labelright=False)
429+
ax.yaxis.offsetText.set_visible(False)
430+
431+
if squeeze:
432+
# Discarding unneeded dimensions that equal 1. If we only have one
433+
# subplot, just return it instead of a 1-element array.
434+
return axarr.item() if axarr.size == 1 else axarr.squeeze()
435+
else:
436+
# Returned axis array will be always 2-d, even if nrows=ncols=1.
437+
return axarr
438+
319439
def tight_layout(self, figure, renderer=None,
320440
pad=1.08, h_pad=None, w_pad=None, rect=None):
321441
"""

0 commit comments

Comments
 (0)