Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 487ac80

Browse files
committed
ENH: reuse gridspec if possible
1 parent aaa9213 commit 487ac80

File tree

5 files changed

+88
-40
lines changed

5 files changed

+88
-40
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Subplot and subplot2grid can now work with constrained layout
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
``constrained_layout`` depends on a single ``GridSpec``
5+
for each logical layout on a figure. Previously, ``plt.subplot`` and
6+
``plt.subplot2grid`` added a new ``GridSpec`` each time they were called and
7+
were therefore incompatible with ``constrained_layout``.
8+
9+
Now ``plt.subplot`` attempts to reuse the ``GridSpec`` if the number of rows
10+
and columns is the same as the top level gridspec already in the figure.
11+
i.e. ``plt.subplot(2, 1, 2)`` will use the same gridspec as
12+
``plt.subplot(2, 1, 1)`` and the ``constrained_layout=True`` option to
13+
`~.figure.Figure` will work.
14+
15+
In contrast, mixing ``nrows`` and ``ncols`` will *not* work with
16+
``constrained_lyaout``: ``plt.subplot(2, 2, 1)`` followed by
17+
``plt.subplots(2, 1, 2)`` will still produce two gridspecs, and
18+
``constrained_layout=True`` will give bad results. In order to get the
19+
desired effect, the second call can specify the cells the second axes is meant
20+
to cover: ``plt.subplots(2, 2, (2, 4))``, or the more pythonic
21+
``plt.subplot2grid((2, 2), (0, 1), rowspan=2)`` can be used.

lib/matplotlib/gridspec.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,28 @@ def get_grid_positions(self, fig, raw=False):
204204
fig_lefts, fig_rights = (left + cell_ws).reshape((-1, 2)).T
205205
return fig_bottoms, fig_tops, fig_lefts, fig_rights
206206

207+
@staticmethod
208+
def _check_gridspec_exists(figure, nrows, ncols):
209+
"""
210+
Check if the figure already has a gridspec with these dimensions,
211+
or create a new one
212+
"""
213+
for ax in figure.get_axes():
214+
if hasattr(ax, 'get_subplotspec'):
215+
gs = ax.get_subplotspec().get_gridspec()
216+
if hasattr(gs, 'get_topmost_subplotspec'):
217+
# This is needed for colorbar gridspec layouts.
218+
# This is probably OK becase this whole logic tree
219+
# is for when the user is doing simple things with the
220+
# add_subplot command. For complicated layouts
221+
# like subgridspecs the proper gridspec is passed in...
222+
gs = gs.get_topmost_subplotspec().get_gridspec()
223+
if gs.get_geometry() == (nrows, ncols):
224+
return gs
225+
# else gridspec not found:
226+
return GridSpec(nrows, ncols, figure=figure)
227+
228+
207229
def __getitem__(self, key):
208230
"""Create and return a `.SubplotSpec` instance."""
209231
nrows, ncols = self.get_geometry()
@@ -666,8 +688,7 @@ def _from_subplot_args(figure, args):
666688
raise ValueError(
667689
f"Single argument to subplot must be a three-digit "
668690
f"integer, not {arg}") from None
669-
# num - 1 for converting from MATLAB to python indexing
670-
return GridSpec(rows, cols, figure=figure)[num - 1]
691+
i = j = num
671692
elif len(args) == 3:
672693
rows, cols, num = args
673694
if not (isinstance(rows, Integral) and isinstance(cols, Integral)):
@@ -680,19 +701,23 @@ def _from_subplot_args(figure, args):
680701
i, j = map(int, num)
681702
else:
682703
i, j = num
683-
return gs[i-1:j]
684704
else:
685705
if not isinstance(num, Integral):
686706
cbook.warn_deprecated("3.3", message=message)
687707
num = int(num)
688708
if num < 1 or num > rows*cols:
689709
raise ValueError(
690710
f"num must be 1 <= num <= {rows*cols}, not {num}")
691-
return gs[num - 1] # -1 due to MATLAB indexing.
711+
i = j = num
692712
else:
693713
raise TypeError(f"subplot() takes 1 or 3 positional arguments but "
694714
f"{len(args)} were given")
695715

716+
gs = GridSpec._check_gridspec_exists(figure, rows, cols)
717+
if gs is None:
718+
gs = GridSpec(rows, cols, figure=figure)
719+
return gs[i-1:j]
720+
696721
# num2 is a property only to handle the case where it is None and someone
697722
# mutates num1.
698723

lib/matplotlib/pyplot.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,10 +1399,10 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs):
13991399
if fig is None:
14001400
fig = gcf()
14011401

1402-
s1, s2 = shape
1403-
subplotspec = GridSpec(s1, s2).new_subplotspec(loc,
1404-
rowspan=rowspan,
1405-
colspan=colspan)
1402+
rows, cols = shape
1403+
gs = GridSpec._check_gridspec_exists(fig, rows, cols)
1404+
1405+
subplotspec = gs.new_subplotspec(loc, rowspan=rowspan, colspan=colspan)
14061406
ax = fig.add_subplot(subplotspec, **kwargs)
14071407
bbox = ax.bbox
14081408
axes_to_delete = []

lib/matplotlib/tests/test_figure.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,3 +778,18 @@ def test_fail(self, x, match):
778778
def test_hashable_keys(self, fig_test, fig_ref):
779779
fig_test.subplot_mosaic([[object(), object()]])
780780
fig_ref.subplot_mosaic([["A", "B"]])
781+
782+
783+
def test_reused_gridspec():
784+
"""Test that these all use the same gridspec"""
785+
fig = plt.figure()
786+
ax1 = fig.add_subplot(3, 2, (3, 5))
787+
ax2 = fig.add_subplot(3, 2, 4)
788+
ax3 = plt.subplot2grid((3, 2), (2, 1), colspan=2, fig=fig)
789+
790+
gs1 = ax1.get_subplotspec().get_gridspec()
791+
gs2 = ax2.get_subplotspec().get_gridspec()
792+
gs3 = ax3.get_subplotspec().get_gridspec()
793+
794+
assert gs1 == gs2
795+
assert gs1 == gs3

tutorials/intermediate/constrainedlayout_guide.py

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -492,40 +492,43 @@ def docomplicated(suptitle=None):
492492
# Incompatible functions
493493
# ----------------------
494494
#
495-
# ``constrained_layout`` will not work on subplots created via
496-
# `.pyplot.subplot`. The reason is that each call to `.pyplot.subplot` creates
497-
# a separate `.GridSpec` instance and ``constrained_layout`` uses (nested)
498-
# gridspecs to carry out the layout. So the following fails to yield a nice
499-
# layout:
495+
# ``constrained_layout`` will work with `.pyplot.subplot`, but only if the
496+
# number of rows and columns is the same for each call.
497+
# The reason is that each call to `.pyplot.subplot` will create a new
498+
# `.GridSpec` instance if the geometry is not the same, and
499+
# ``constrained_layout``. So the following works fine:
500+
500501

501502
fig = plt.figure()
502503

503-
ax1 = plt.subplot(221)
504-
ax2 = plt.subplot(223)
505-
ax3 = plt.subplot(122)
504+
ax1 = plt.subplot(2, 2, 1)
505+
ax2 = plt.subplot(2, 2, 3)
506+
# third axes that spans both rows in second column:
507+
ax3 = plt.subplot(2, 2, (2, 4))
506508

507509
example_plot(ax1)
508510
example_plot(ax2)
509511
example_plot(ax3)
512+
plt.suptitle('Homogenous nrows, ncols')
510513

511514
###############################################################################
512-
# Of course that layout is possible using a gridspec:
515+
# but the following leads to a poor layout:
513516

514517
fig = plt.figure()
515-
gs = fig.add_gridspec(2, 2)
516518

517-
ax1 = fig.add_subplot(gs[0, 0])
518-
ax2 = fig.add_subplot(gs[1, 0])
519-
ax3 = fig.add_subplot(gs[:, 1])
519+
ax1 = plt.subplot(2, 2, 1)
520+
ax2 = plt.subplot(2, 2, 3)
521+
ax3 = plt.subplot(1, 2, 2)
520522

521523
example_plot(ax1)
522524
example_plot(ax2)
523525
example_plot(ax3)
526+
plt.suptitle('Mixed nrows, ncols')
524527

525528
###############################################################################
526529
# Similarly,
527-
# :func:`~matplotlib.pyplot.subplot2grid` doesn't work for the same reason:
528-
# each call creates a different parent gridspec.
530+
# :func:`~matplotlib.pyplot.subplot2grid` works with the same limitation
531+
# that nrows and ncols cannot change for the layout to look good.
529532

530533
fig = plt.figure()
531534

@@ -538,23 +541,7 @@ def docomplicated(suptitle=None):
538541
example_plot(ax2)
539542
example_plot(ax3)
540543
example_plot(ax4)
541-
542-
###############################################################################
543-
# The way to make this plot compatible with ``constrained_layout`` is again
544-
# to use ``gridspec`` directly
545-
546-
fig = plt.figure()
547-
gs = fig.add_gridspec(3, 3)
548-
549-
ax1 = fig.add_subplot(gs[0, 0])
550-
ax2 = fig.add_subplot(gs[0, 1:])
551-
ax3 = fig.add_subplot(gs[1:, 0:2])
552-
ax4 = fig.add_subplot(gs[1:, -1])
553-
554-
example_plot(ax1)
555-
example_plot(ax2)
556-
example_plot(ax3)
557-
example_plot(ax4)
544+
fig.suptitle('subplot2grid')
558545

559546
###############################################################################
560547
# Other Caveats

0 commit comments

Comments
 (0)