From 44df2db6bca5a960a2bd8de5b9a1ab8eacc2f288 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sat, 22 Jan 2022 18:16:31 +0100 Subject: [PATCH] ENH: simple compressed layout Adds compress=True to compressed layout engine. Works for compact axes grids. --- lib/matplotlib/_constrained_layout.py | 60 ++++++++- lib/matplotlib/_layoutgrid.py | 2 +- lib/matplotlib/figure.py | 21 ++- lib/matplotlib/layout_engine.py | 10 +- .../tests/test_constrainedlayout.py | 31 +++++ tutorials/intermediate/arranging_axes.py | 50 +++++-- .../intermediate/constrainedlayout_guide.py | 127 +++++++++++++----- 7 files changed, 241 insertions(+), 60 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index de74303fc9c1..fe5ff1a6a896 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -62,7 +62,8 @@ ###################################################### def do_constrained_layout(fig, h_pad, w_pad, - hspace=None, wspace=None, rect=(0, 0, 1, 1)): + hspace=None, wspace=None, rect=(0, 0, 1, 1), + compress=False): """ Do the constrained_layout. Called at draw time in ``figure.constrained_layout()`` @@ -89,6 +90,11 @@ def do_constrained_layout(fig, h_pad, w_pad, Rectangle in figure coordinates to perform constrained layout in [left, bottom, width, height], each from 0-1. + compress : bool + Whether to shift Axes so that white space in between them is + removed. This is useful for simple grids of fixed-aspect Axes (e.g. + a grid of images). + Returns ------- layoutgrid : private debugging structure @@ -124,13 +130,22 @@ def do_constrained_layout(fig, h_pad, w_pad, # update all the variables in the layout. layoutgrids[fig].update_variables() + warn_collapsed = ('constrained_layout not applied because ' + 'axes sizes collapsed to zero. Try making ' + 'figure larger or axes decorations smaller.') if check_no_collapsed_axes(layoutgrids, fig): reposition_axes(layoutgrids, fig, renderer, h_pad=h_pad, w_pad=w_pad, hspace=hspace, wspace=wspace) + if compress: + layoutgrids = compress_fixed_aspect(layoutgrids, fig) + layoutgrids[fig].update_variables() + if check_no_collapsed_axes(layoutgrids, fig): + reposition_axes(layoutgrids, fig, renderer, h_pad=h_pad, + w_pad=w_pad, hspace=hspace, wspace=wspace) + else: + _api.warn_external(warn_collapsed) else: - _api.warn_external('constrained_layout not applied because ' - 'axes sizes collapsed to zero. Try making ' - 'figure larger or axes decorations smaller.') + _api.warn_external(warn_collapsed) reset_margins(layoutgrids, fig) return layoutgrids @@ -248,6 +263,43 @@ def check_no_collapsed_axes(layoutgrids, fig): return True +def compress_fixed_aspect(layoutgrids, fig): + gs = None + for ax in fig.axes: + if not hasattr(ax, 'get_subplotspec'): + continue + ax.apply_aspect() + sub = ax.get_subplotspec() + _gs = sub.get_gridspec() + if gs is None: + gs = _gs + extraw = np.zeros(gs.ncols) + extrah = np.zeros(gs.nrows) + elif _gs != gs: + raise ValueError('Cannot do compressed layout if axes are not' + 'all from the same gridspec') + orig = ax.get_position(original=True) + actual = ax.get_position(original=False) + dw = orig.width - actual.width + if dw > 0: + extraw[sub.colspan] = np.maximum(extraw[sub.colspan], dw) + dh = orig.height - actual.height + if dh > 0: + extrah[sub.rowspan] = np.maximum(extrah[sub.rowspan], dh) + + if gs is None: + raise ValueError('Cannot do compressed layout if no axes ' + 'are part of a gridspec.') + w = np.sum(extraw) / 2 + layoutgrids[fig].edit_margin_min('left', w) + layoutgrids[fig].edit_margin_min('right', w) + + h = np.sum(extrah) / 2 + layoutgrids[fig].edit_margin_min('top', h) + layoutgrids[fig].edit_margin_min('bottom', h) + return layoutgrids + + def get_margin_from_padding(obj, *, w_pad=0, h_pad=0, hspace=0, wspace=0): diff --git a/lib/matplotlib/_layoutgrid.py b/lib/matplotlib/_layoutgrid.py index 90c7b3210e0d..487dab9152c9 100644 --- a/lib/matplotlib/_layoutgrid.py +++ b/lib/matplotlib/_layoutgrid.py @@ -519,7 +519,7 @@ def plot_children(fig, lg=None, level=0, printit=False): import matplotlib.patches as mpatches if lg is None: - _layoutgrids = fig.execute_constrained_layout() + _layoutgrids = fig.get_layout_engine().execute(fig) lg = _layoutgrids[fig] colors = plt.rcParams["axes.prop_cycle"].by_key()["color"] col = colors[level] diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 0b8ad9828b3d..852e9382d72f 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2245,7 +2245,7 @@ def __init__(self, The use of this parameter is discouraged. Please use ``layout='constrained'`` instead. - layout : {'constrained', 'tight', `.LayoutEngine`, None}, optional + layout : {'constrained', 'compressed', 'tight', `.LayoutEngine`, None} The layout mechanism for positioning of plot elements to avoid overlapping Axes decorations (labels, ticks, etc). Note that layout managers can have significant performance penalties. @@ -2258,6 +2258,10 @@ def __init__(self, See :doc:`/tutorials/intermediate/constrainedlayout_guide` for examples. + - 'compressed': uses the same algorithm as 'constrained', but + removes extra space between fixed-aspect-ratio Axes. Best for + simple grids of axes. + - 'tight': Use the tight layout mechanism. This is a relatively simple algorithm that adjusts the subplot parameters so that decorations do not overlap. See `.Figure.set_tight_layout` for @@ -2388,11 +2392,13 @@ def set_layout_engine(self, layout=None, **kwargs): Parameters ---------- - layout : {'constrained', 'tight'} or `~.LayoutEngine` - 'constrained' will use `~.ConstrainedLayoutEngine`, 'tight' will - use `~.TightLayoutEngine`. Users and libraries can define their - own layout engines as well. - kwargs : dict + layout: {'constrained', 'compressed', 'tight'} or `~.LayoutEngine` + 'constrained' will use `~.ConstrainedLayoutEngine`, + 'compressed' will also use ConstrainedLayoutEngine, but with a + correction that attempts to make a good layout for fixed-aspect + ratio Axes. 'tight' uses `~.TightLayoutEngine`. Users and + libraries can define their own layout engines as well. + kwargs: dict The keyword arguments are passed to the layout engine to set things like padding and margin sizes. Only used if *layout* is a string. """ @@ -2408,6 +2414,9 @@ def set_layout_engine(self, layout=None, **kwargs): new_layout_engine = TightLayoutEngine(**kwargs) elif layout == 'constrained': new_layout_engine = ConstrainedLayoutEngine(**kwargs) + elif layout == 'compressed': + new_layout_engine = ConstrainedLayoutEngine(compress=True, + **kwargs) elif isinstance(layout, LayoutEngine): new_layout_engine = layout else: diff --git a/lib/matplotlib/layout_engine.py b/lib/matplotlib/layout_engine.py index fa4281a2ba02..2bb8852e0f83 100644 --- a/lib/matplotlib/layout_engine.py +++ b/lib/matplotlib/layout_engine.py @@ -180,7 +180,7 @@ class ConstrainedLayoutEngine(LayoutEngine): def __init__(self, *, h_pad=None, w_pad=None, hspace=None, wspace=None, rect=(0, 0, 1, 1), - **kwargs): + compress=False, **kwargs): """ Initialize ``constrained_layout`` settings. @@ -201,6 +201,10 @@ def __init__(self, *, h_pad=None, w_pad=None, rect : tuple of 4 floats Rectangle in figure coordinates to perform constrained layout in (left, bottom, width, height), each from 0-1. + compress : bool + Whether to shift Axes so that white space in between them is + removed. This is useful for simple grids of fixed-aspect Axes (e.g. + a grid of images). See :ref:`compressed_layout`. """ super().__init__(**kwargs) # set the defaults: @@ -212,6 +216,7 @@ def __init__(self, *, h_pad=None, w_pad=None, # set anything that was passed in (None will be ignored): self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace, rect=rect) + self._compress = compress def execute(self, fig): """ @@ -229,7 +234,8 @@ def execute(self, fig): return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad, wspace=self._params['wspace'], hspace=self._params['hspace'], - rect=self._params['rect']) + rect=self._params['rect'], + compress=self._compress) def set(self, *, h_pad=None, w_pad=None, hspace=None, wspace=None, rect=None): diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index 3124e392283e..a955b2812c14 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -624,3 +624,34 @@ def test_rect(): assert ppos.y1 < 0.5 assert ppos.x0 > 0.2 assert ppos.y0 > 0.2 + + +def test_compressed1(): + fig, axs = plt.subplots(3, 2, layout='compressed', + sharex=True, sharey=True) + for ax in axs.flat: + pc = ax.imshow(np.random.randn(20, 20)) + + fig.colorbar(pc, ax=axs) + fig.draw_without_rendering() + + pos = axs[0, 0].get_position() + np.testing.assert_allclose(pos.x0, 0.2344, atol=1e-3) + pos = axs[0, 1].get_position() + np.testing.assert_allclose(pos.x1, 0.7024, atol=1e-3) + + # wider than tall + fig, axs = plt.subplots(2, 3, layout='compressed', + sharex=True, sharey=True, figsize=(5, 4)) + for ax in axs.flat: + pc = ax.imshow(np.random.randn(20, 20)) + + fig.colorbar(pc, ax=axs) + fig.draw_without_rendering() + + pos = axs[0, 0].get_position() + np.testing.assert_allclose(pos.x0, 0.06195, atol=1e-3) + np.testing.assert_allclose(pos.y1, 0.8537, atol=1e-3) + pos = axs[1, 2].get_position() + np.testing.assert_allclose(pos.x1, 0.8618, atol=1e-3) + np.testing.assert_allclose(pos.y0, 0.1934, atol=1e-3) diff --git a/tutorials/intermediate/arranging_axes.py b/tutorials/intermediate/arranging_axes.py index 1c6f6e72f6f9..1ddc793c1055 100644 --- a/tutorials/intermediate/arranging_axes.py +++ b/tutorials/intermediate/arranging_axes.py @@ -100,7 +100,7 @@ import numpy as np fig, axs = plt.subplots(ncols=2, nrows=2, figsize=(5.5, 3.5), - constrained_layout=True) + layout="constrained") # add an artist, in this case a nice label in the middle... for row in range(2): for col in range(2): @@ -129,11 +129,41 @@ def annotate_axes(ax, text, fontsize=18): fig, axd = plt.subplot_mosaic([['upper left', 'upper right'], ['lower left', 'lower right']], - figsize=(5.5, 3.5), constrained_layout=True) + figsize=(5.5, 3.5), layout="constrained") for k in axd: annotate_axes(axd[k], f'axd["{k}"]', fontsize=14) fig.suptitle('plt.subplot_mosaic()') +############################################################################# +# +# Grids of fixed-aspect ratio Axes +# -------------------------------- +# +# Fixed-aspect ratio axes are common for images or maps. However, they +# present a challenge to layout because two sets of constraints are being +# imposed on the size of the Axes - that they fit in the figure and that they +# have a set aspect ratio. This leads to large gaps between Axes by default: +# + +fig, axs = plt.subplots(2, 2, layout="constrained", figsize=(5.5, 3.5)) +for ax in axs.flat: + ax.set_aspect(1) +fig.suptitle('Fixed aspect Axes') + +############################################################################ +# One way to address this is to change the aspect of the figure to be close +# to the aspect ratio of the Axes, however that requires trial and error. +# Matplotlib also supplies ``layout="compressed"``, which will work with +# simple grids to reduce the gaps between Axes. (The ``mpl_toolkits`` also +# provides `~.mpl_toolkits.axes_grid1.axes_grid.ImageGrid` to accomplish +# a similar effect, but with a non-standard Axes class). + +fig, axs = plt.subplots(2, 2, layout="compressed", figsize=(5.5, 3.5)) +for ax in axs.flat: + ax.set_aspect(1) +fig.suptitle('Fixed aspect Axes: compressed') + + ############################################################################ # Axes spanning rows or columns in a grid # --------------------------------------- @@ -145,7 +175,7 @@ def annotate_axes(ax, text, fontsize=18): fig, axd = plt.subplot_mosaic([['upper left', 'right'], ['lower left', 'right']], - figsize=(5.5, 3.5), constrained_layout=True) + figsize=(5.5, 3.5), layout="constrained") for k in axd: annotate_axes(axd[k], f'axd["{k}"]', fontsize=14) fig.suptitle('plt.subplot_mosaic()') @@ -168,7 +198,7 @@ def annotate_axes(ax, text, fontsize=18): fig, axd = plt.subplot_mosaic([['upper left', 'right'], ['lower left', 'right']], gridspec_kw=gs_kw, figsize=(5.5, 3.5), - constrained_layout=True) + layout="constrained") for k in axd: annotate_axes(axd[k], f'axd["{k}"]', fontsize=14) fig.suptitle('plt.subplot_mosaic()') @@ -184,7 +214,7 @@ def annotate_axes(ax, text, fontsize=18): # necessarily aligned. See below for a more verbose way to achieve the same # effect with `~.gridspec.GridSpecFromSubplotSpec`. -fig = plt.figure(constrained_layout=True) +fig = plt.figure(layout="constrained") subfigs = fig.subfigures(1, 2, wspace=0.07, width_ratios=[1.5, 1.]) axs0 = subfigs[0].subplots(2, 2) subfigs[0].set_facecolor('0.9') @@ -207,7 +237,7 @@ def annotate_axes(ax, text, fontsize=18): outer = [['upper left', inner], ['lower left', 'lower right']] -fig, axd = plt.subplot_mosaic(outer, constrained_layout=True) +fig, axd = plt.subplot_mosaic(outer, layout="constrained") for k in axd: annotate_axes(axd[k], f'axd["{k}"]') @@ -230,7 +260,7 @@ def annotate_axes(ax, text, fontsize=18): # We can accomplish a 2x2 grid in the same manner as # ``plt.subplots(2, 2)``: -fig = plt.figure(figsize=(5.5, 3.5), constrained_layout=True) +fig = plt.figure(figsize=(5.5, 3.5), layout="constrained") spec = fig.add_gridspec(ncols=2, nrows=2) ax0 = fig.add_subplot(spec[0, 0]) @@ -256,7 +286,7 @@ def annotate_axes(ax, text, fontsize=18): # and the new Axes will span the slice. This would be the same # as ``fig, axd = plt.subplot_mosaic([['ax0', 'ax0'], ['ax1', 'ax2']], ...)``: -fig = plt.figure(figsize=(5.5, 3.5), constrained_layout=True) +fig = plt.figure(figsize=(5.5, 3.5), layout="constrained") spec = fig.add_gridspec(2, 2) ax0 = fig.add_subplot(spec[0, :]) @@ -284,7 +314,7 @@ def annotate_axes(ax, text, fontsize=18): # These spacing parameters can also be passed to `~.pyplot.subplots` and # `~.pyplot.subplot_mosaic` as the *gridspec_kw* argument. -fig = plt.figure(constrained_layout=False, facecolor='0.9') +fig = plt.figure(layout=None, facecolor='0.9') gs = fig.add_gridspec(nrows=3, ncols=3, left=0.05, right=0.75, hspace=0.1, wspace=0.05) ax0 = fig.add_subplot(gs[:-1, :]) @@ -306,7 +336,7 @@ def annotate_axes(ax, text, fontsize=18): # Note this is also available from the more verbose # `.gridspec.GridSpecFromSubplotSpec`. -fig = plt.figure(constrained_layout=True) +fig = plt.figure(layout="constrained") gs0 = fig.add_gridspec(1, 2) gs00 = gs0[0].subgridspec(2, 2) diff --git a/tutorials/intermediate/constrainedlayout_guide.py b/tutorials/intermediate/constrainedlayout_guide.py index 12a77bf1c762..06ccb2f9b711 100644 --- a/tutorials/intermediate/constrainedlayout_guide.py +++ b/tutorials/intermediate/constrainedlayout_guide.py @@ -195,8 +195,13 @@ def example_plot(ax, fontsize=12, hide_labels=False): leg.set_in_layout(True) # we don't want the layout to change at this point. fig.set_layout_engine(None) -fig.savefig('../../doc/_static/constrained_layout_1b.png', - bbox_inches='tight', dpi=100) +try: + fig.savefig('../../doc/_static/constrained_layout_1b.png', + bbox_inches='tight', dpi=100) +except FileNotFoundError: + # this allows the script to keep going if run interactively and + # the directory above doesn't exist + pass ############################################# # The saved file looks like: @@ -212,8 +217,14 @@ def example_plot(ax, fontsize=12, hide_labels=False): labels = [l.get_label() for l in lines] leg = fig.legend(lines, labels, loc='center left', bbox_to_anchor=(0.8, 0.5), bbox_transform=axs[1].transAxes) -fig.savefig('../../doc/_static/constrained_layout_2b.png', - bbox_inches='tight', dpi=100) +try: + fig.savefig('../../doc/_static/constrained_layout_2b.png', + bbox_inches='tight', dpi=100) +except FileNotFoundError: + # this allows the script to keep going if run interactively and + # the directory above doesn't exist + pass + ############################################# # The saved file looks like: @@ -273,7 +284,6 @@ def example_plot(ax, fontsize=12, hide_labels=False): # space set in constrained_layout. fig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0.0, wspace=0.0) -plt.show() ########################################## # Spacing with colorbars @@ -319,13 +329,15 @@ def example_plot(ax, fontsize=12, hide_labels=False): # ================= # # constrained_layout is meant to be used -# with :func:`~matplotlib.figure.Figure.subplots` or -# :func:`~matplotlib.gridspec.GridSpec` and +# with :func:`~matplotlib.figure.Figure.subplots`, +# :func:`~matplotlib.figure.Figure.subplot_mosaic`, or +# :func:`~matplotlib.gridspec.GridSpec` with # :func:`~matplotlib.figure.Figure.add_subplot`. # # Note that in what follows ``layout="constrained"`` -fig = plt.figure() +plt.rcParams['figure.constrained_layout.use'] = False +fig = plt.figure(layout="constrained") gs1 = gridspec.GridSpec(2, 1, figure=fig) ax1 = fig.add_subplot(gs1[0]) @@ -339,7 +351,7 @@ def example_plot(ax, fontsize=12, hide_labels=False): # convenience functions `~.Figure.add_gridspec` and # `~.SubplotSpec.subgridspec`. -fig = plt.figure() +fig = plt.figure(layout="constrained") gs0 = fig.add_gridspec(1, 2) @@ -366,7 +378,7 @@ def example_plot(ax, fontsize=12, hide_labels=False): # then they need to be in the same gridspec. We need to make this figure # larger as well in order for the axes not to collapse to zero height: -fig = plt.figure(figsize=(4, 6)) +fig = plt.figure(figsize=(4, 6), layout="constrained") gs0 = fig.add_gridspec(6, 2) @@ -384,38 +396,51 @@ def example_plot(ax, fontsize=12, hide_labels=False): example_plot(ax, hide_labels=True) fig.suptitle('Overlapping Gridspecs') - ############################################################################ # This example uses two gridspecs to have the colorbar only pertain to # one set of pcolors. Note how the left column is wider than the # two right-hand columns because of this. Of course, if you wanted the -# subplots to be the same size you only needed one gridspec. +# subplots to be the same size you only needed one gridspec. Note that +# the same effect can be achieved using `~.Figure.subfigures`. +fig = plt.figure(layout="constrained") +gs0 = fig.add_gridspec(1, 2, figure=fig, width_ratios=[1, 2]) +gs_left = gs0[0].subgridspec(2, 1) +gs_right = gs0[1].subgridspec(2, 2) -def docomplicated(suptitle=None): - fig = plt.figure() - gs0 = fig.add_gridspec(1, 2, figure=fig, width_ratios=[1., 2.]) - gsl = gs0[0].subgridspec(2, 1) - gsr = gs0[1].subgridspec(2, 2) +for gs in gs_left: + ax = fig.add_subplot(gs) + example_plot(ax) +axs = [] +for gs in gs_right: + ax = fig.add_subplot(gs) + pcm = ax.pcolormesh(arr, **pc_kwargs) + ax.set_xlabel('x-label') + ax.set_ylabel('y-label') + ax.set_title('title') + axs += [ax] +fig.suptitle('Nested plots using subgridspec') +fig.colorbar(pcm, ax=axs) - for gs in gsl: - ax = fig.add_subplot(gs) - example_plot(ax) - axs = [] - for gs in gsr: - ax = fig.add_subplot(gs) - pcm = ax.pcolormesh(arr, **pc_kwargs) - ax.set_xlabel('x-label') - ax.set_ylabel('y-label') - ax.set_title('title') +############################################################################### +# Rather than using subgridspecs, Matplotlib now provides `~.Figure.subfigures` +# which also work with ``constrained_layout``: - axs += [ax] - fig.colorbar(pcm, ax=axs) - if suptitle is not None: - fig.suptitle(suptitle) +fig = plt.figure(layout="constrained") +sfigs = fig.subfigures(1, 2, width_ratios=[1, 2]) +axs_left = sfigs[0].subplots(2, 1) +for ax in axs_left.flat: + example_plot(ax) -docomplicated() +axs_right = sfigs[1].subplots(2, 2) +for ax in axs_right.flat: + pcm = ax.pcolormesh(arr, **pc_kwargs) + ax.set_xlabel('x-label') + ax.set_ylabel('y-label') + ax.set_title('title') +fig.colorbar(pcm, ax=axs_right) +fig.suptitle('Nested plots using subfigures') ############################################################################### # Manually setting axes positions @@ -426,10 +451,39 @@ def docomplicated(suptitle=None): # no effect on it anymore. (Note that ``constrained_layout`` still leaves the # space for the axes that is moved). -fig, axs = plt.subplots(1, 2) +fig, axs = plt.subplots(1, 2, layout="constrained") example_plot(axs[0], fontsize=12) axs[1].set_position([0.2, 0.2, 0.4, 0.4]) +############################################################################### +# .. _compressed_layout: +# +# Grids of fixed aspect-ratio Axes: "compressed" layout +# ===================================================== +# +# ``constrained_layout`` operates on the grid of "original" positions for +# axes. However, when Axes have fixed aspect ratios, one side is usually made +# shorter, and leaves large gaps in the shortened direction. In the following, +# the Axes are square, but the figure quite wide so there is a horizontal gap: + +fig, axs = plt.subplots(2, 2, figsize=(5, 3), + sharex=True, sharey=True, layout="constrained") +for ax in axs.flat: + ax.imshow(arr) +fig.suptitle("fixed-aspect plots, layout='constrained'") + +############################################################################### +# One obvious way of fixing this is to make the figure size more square, +# however, closing the gaps exactly requires trial and error. For simple grids +# of Axes we can use ``layout="compressed"`` to do the job for us: + +fig, axs = plt.subplots(2, 2, figsize=(5, 3), + sharex=True, sharey=True, layout='compressed') +for ax in axs.flat: + ax.imshow(arr) +fig.suptitle("fixed-aspect plots, layout='compressed'") + + ############################################################################### # Manually turning off ``constrained_layout`` # =========================================== @@ -458,7 +512,7 @@ def docomplicated(suptitle=None): # `.GridSpec` instance if the geometry is not the same, and # ``constrained_layout``. So the following works fine: -fig = plt.figure() +fig = plt.figure(layout="constrained") ax1 = plt.subplot(2, 2, 1) ax2 = plt.subplot(2, 2, 3) @@ -473,7 +527,7 @@ def docomplicated(suptitle=None): ############################################################################### # but the following leads to a poor layout: -fig = plt.figure() +fig = plt.figure(layout="constrained") ax1 = plt.subplot(2, 2, 1) ax2 = plt.subplot(2, 2, 3) @@ -489,7 +543,7 @@ def docomplicated(suptitle=None): # `~matplotlib.pyplot.subplot2grid` works with the same limitation # that nrows and ncols cannot change for the layout to look good. -fig = plt.figure() +fig = plt.figure(layout="constrained") ax1 = plt.subplot2grid((3, 3), (0, 0)) ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2) @@ -501,7 +555,6 @@ def docomplicated(suptitle=None): example_plot(ax3) example_plot(ax4) fig.suptitle('subplot2grid') -plt.show() ############################################################################### # Other Caveats