{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n.. redirect-from:: /tutorials/intermediate/constrainedlayout_guide\n\n\n# Constrained layout guide\n\nUse *constrained layout* to fit plots within your figure cleanly.\n\n*Constrained layout* automatically adjusts subplots so that decorations like tick\nlabels, legends, and colorbars do not overlap, while still preserving the\nlogical layout requested by the user.\n\n*Constrained layout* is similar to `Tight\nlayout`, but is substantially more\nflexible. It handles colorbars placed on multiple Axes\n(`colorbar_placement`) nested layouts (`~.Figure.subfigures`) and Axes that\nspan rows or columns (`~.pyplot.subplot_mosaic`), striving to align spines from\nAxes in the same row or column. In addition, `Compressed layout\n` will try and move fixed aspect-ratio Axes closer together.\nThese features are described in this document, as well as some\n`implementation details ` discussed at the end.\n\n*Constrained layout* typically needs to be activated before any Axes are added to\na figure. Two ways of doing so are\n\n* using the respective argument to `~.pyplot.subplots`,\n `~.pyplot.figure`, `~.pyplot.subplot_mosaic` e.g.::\n\n plt.subplots(layout=\"constrained\")\n\n* activate it via `rcParams`, like::\n\n plt.rcParams['figure.constrained_layout.use'] = True\n\nThose are described in detail throughout the following sections.\n\n

Warning

Calling `~.pyplot.tight_layout` will turn off *constrained layout*!

\n\n## Simple example\n\nWith the default Axes positioning, the axes title, axis labels, or tick labels\ncan sometimes go outside the figure area, and thus get clipped.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\nimport numpy as np\n\nimport matplotlib.colors as mcolors\nimport matplotlib.gridspec as gridspec\n\nplt.rcParams['savefig.facecolor'] = \"0.8\"\nplt.rcParams['figure.figsize'] = 4.5, 4.\nplt.rcParams['figure.max_open_warning'] = 50\n\n\ndef example_plot(ax, fontsize=12, hide_labels=False):\n ax.plot([1, 2])\n\n ax.locator_params(nbins=3)\n if hide_labels:\n ax.set_xticklabels([])\n ax.set_yticklabels([])\n else:\n ax.set_xlabel('x-label', fontsize=fontsize)\n ax.set_ylabel('y-label', fontsize=fontsize)\n ax.set_title('Title', fontsize=fontsize)\n\nfig, ax = plt.subplots(layout=None)\nexample_plot(ax, fontsize=24)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To prevent this, the location of Axes needs to be adjusted. For\nsubplots, this can be done manually by adjusting the subplot parameters\nusing `.Figure.subplots_adjust`. However, specifying your figure with the\n``layout=\"constrained\"`` keyword argument will do the adjusting\nautomatically.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(layout=\"constrained\")\nexample_plot(ax, fontsize=24)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When you have multiple subplots, often you see labels of different\nAxes overlapping each other.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, layout=None)\nfor ax in axs.flat:\n example_plot(ax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specifying ``layout=\"constrained\"`` in the call to ``plt.subplots``\ncauses the layout to be properly constrained.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, layout=\"constrained\")\nfor ax in axs.flat:\n example_plot(ax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Colorbars\n\nIf you create a colorbar with `.Figure.colorbar`, you need to make room for\nit. *Constrained layout* does this automatically. Note that if you\nspecify ``use_gridspec=True`` it will be ignored because this option is made\nfor improving the layout via ``tight_layout``.\n\n

Note

For the `~.axes.Axes.pcolormesh` keyword arguments (``pc_kwargs``) we use a\n dictionary to keep the calls consistent across this document.

\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "arr = np.arange(100).reshape((10, 10))\nnorm = mcolors.Normalize(vmin=0., vmax=100.)\n# see note above: this makes all pcolormesh calls consistent:\npc_kwargs = {'rasterized': True, 'cmap': 'viridis', 'norm': norm}\nfig, ax = plt.subplots(figsize=(4, 4), layout=\"constrained\")\nim = ax.pcolormesh(arr, **pc_kwargs)\nfig.colorbar(im, ax=ax, shrink=0.6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you specify a list of Axes (or other iterable container) to the\n``ax`` argument of ``colorbar``, *constrained layout* will take space from\nthe specified Axes.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, figsize=(4, 4), layout=\"constrained\")\nfor ax in axs.flat:\n im = ax.pcolormesh(arr, **pc_kwargs)\nfig.colorbar(im, ax=axs, shrink=0.6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you specify a list of Axes from inside a grid of Axes, the colorbar\nwill steal space appropriately, and leave a gap, but all subplots will\nstill be the same size.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(3, 3, figsize=(4, 4), layout=\"constrained\")\nfor ax in axs.flat:\n im = ax.pcolormesh(arr, **pc_kwargs)\nfig.colorbar(im, ax=axs[1:, 1], shrink=0.8)\nfig.colorbar(im, ax=axs[:, -1], shrink=0.6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Suptitle\n\n*Constrained layout* can also make room for `~.Figure.suptitle`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, figsize=(4, 4), layout=\"constrained\")\nfor ax in axs.flat:\n im = ax.pcolormesh(arr, **pc_kwargs)\nfig.colorbar(im, ax=axs, shrink=0.6)\nfig.suptitle('Big Suptitle')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Legends\n\nLegends can be placed outside of their parent axis.\n*Constrained layout* is designed to handle this for :meth:`.Axes.legend`.\nHowever, *constrained layout* does *not* handle legends being created via\n:meth:`.Figure.legend` (yet).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(layout=\"constrained\")\nax.plot(np.arange(10), label='This is a plot')\nax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, this will steal space from a subplot layout:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(1, 2, figsize=(4, 2), layout=\"constrained\")\naxs[0].plot(np.arange(10))\naxs[1].plot(np.arange(10), label='This is a plot')\naxs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order for a legend or other artist to *not* steal space\nfrom the subplot layout, we can ``leg.set_in_layout(False)``.\nOf course this can mean the legend ends up\ncropped, but can be useful if the plot is subsequently called\nwith ``fig.savefig('outname.png', bbox_inches='tight')``. Note,\nhowever, that the legend's ``get_in_layout`` status will have to be\ntoggled again to make the saved file work, and we must manually\ntrigger a draw if we want *constrained layout* to adjust the size\nof the Axes before printing.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(1, 2, figsize=(4, 2), layout=\"constrained\")\n\naxs[0].plot(np.arange(10))\naxs[1].plot(np.arange(10), label='This is a plot')\nleg = axs[1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5))\nleg.set_in_layout(False)\n# trigger a draw so that constrained layout is executed once\n# before we turn it off when printing....\nfig.canvas.draw()\n# we want the legend included in the bbox_inches='tight' calcs.\nleg.set_in_layout(True)\n# we don't want the layout to change at this point.\nfig.set_layout_engine('none')\ntry:\n fig.savefig('../../../doc/_static/constrained_layout_1b.png',\n bbox_inches='tight', dpi=100)\nexcept FileNotFoundError:\n # this allows the script to keep going if run interactively and\n # the directory above doesn't exist\n pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The saved file looks like:\n\n\n\nA better way to get around this awkwardness is to simply\nuse the legend method provided by `.Figure.legend`:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(1, 2, figsize=(4, 2), layout=\"constrained\")\naxs[0].plot(np.arange(10))\nlines = axs[1].plot(np.arange(10), label='This is a plot')\nlabels = [l.get_label() for l in lines]\nleg = fig.legend(lines, labels, loc='center left',\n bbox_to_anchor=(0.8, 0.5), bbox_transform=axs[1].transAxes)\ntry:\n fig.savefig('../../../doc/_static/constrained_layout_2b.png',\n bbox_inches='tight', dpi=100)\nexcept FileNotFoundError:\n # this allows the script to keep going if run interactively and\n # the directory above doesn't exist\n pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The saved file looks like:\n\n\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Padding and spacing\n\nPadding between Axes is controlled in the horizontal by *w_pad* and\n*wspace*, and vertical by *h_pad* and *hspace*. These can be edited\nvia `~.layout_engine.ConstrainedLayoutEngine.set`. *w/h_pad* are\nthe minimum space around the Axes in units of inches:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, layout=\"constrained\")\nfor ax in axs.flat:\n example_plot(ax, hide_labels=True)\nfig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0,\n wspace=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Spacing between subplots is further set by *wspace* and *hspace*. These\nare specified as a fraction of the size of the subplot group as a whole.\nIf these values are smaller than *w_pad* or *h_pad*, then the fixed pads are\nused instead. Note in the below how the space at the edges doesn't change\nfrom the above, but the space between subplots does.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, layout=\"constrained\")\nfor ax in axs.flat:\n example_plot(ax, hide_labels=True)\nfig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0.2,\n wspace=0.2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If there are more than two columns, the *wspace* is shared between them,\nso here the wspace is divided in two, with a *wspace* of 0.1 between each\ncolumn:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 3, layout=\"constrained\")\nfor ax in axs.flat:\n example_plot(ax, hide_labels=True)\nfig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0.2,\n wspace=0.2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "GridSpecs also have optional *hspace* and *wspace* keyword arguments,\nthat will be used instead of the pads set by *constrained layout*:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, layout=\"constrained\",\n gridspec_kw={'wspace': 0.3, 'hspace': 0.2})\nfor ax in axs.flat:\n example_plot(ax, hide_labels=True)\n# this has no effect because the space set in the gridspec trumps the\n# space set in *constrained layout*.\nfig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72, hspace=0.0,\n wspace=0.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Spacing with colorbars\n\nColorbars are placed a distance *pad* from their parent, where *pad*\nis a fraction of the width of the parent(s). The spacing to the\nnext subplot is then given by *w/hspace*.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, layout=\"constrained\")\npads = [0, 0.05, 0.1, 0.2]\nfor pad, ax in zip(pads, axs.flat):\n pc = ax.pcolormesh(arr, **pc_kwargs)\n fig.colorbar(pc, ax=ax, shrink=0.6, pad=pad)\n ax.set_xticklabels([])\n ax.set_yticklabels([])\n ax.set_title(f'pad: {pad}')\nfig.get_layout_engine().set(w_pad=2 / 72, h_pad=2 / 72, hspace=0.2,\n wspace=0.2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## rcParams\n\nThere are five `rcParams`\nthat can be set, either in a script or in the :file:`matplotlibrc`\nfile. They all have the prefix ``figure.constrained_layout``:\n\n- *use*: Whether to use *constrained layout*. Default is False\n- *w_pad*, *h_pad*: Padding around Axes objects.\n Float representing inches. Default is 3./72. inches (3 pts)\n- *wspace*, *hspace*: Space between subplot groups.\n Float representing a fraction of the subplot widths being separated.\n Default is 0.02.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "plt.rcParams['figure.constrained_layout.use'] = True\nfig, axs = plt.subplots(2, 2, figsize=(3, 3))\nfor ax in axs.flat:\n example_plot(ax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use with GridSpec\n\n*Constrained layout* is meant to be used\nwith :func:`~matplotlib.figure.Figure.subplots`,\n:func:`~matplotlib.figure.Figure.subplot_mosaic`, or\n:func:`~matplotlib.gridspec.GridSpec` with\n:func:`~matplotlib.figure.Figure.add_subplot`.\n\nNote that in what follows ``layout=\"constrained\"``\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "plt.rcParams['figure.constrained_layout.use'] = False\nfig = plt.figure(layout=\"constrained\")\n\ngs1 = gridspec.GridSpec(2, 1, figure=fig)\nax1 = fig.add_subplot(gs1[0])\nax2 = fig.add_subplot(gs1[1])\n\nexample_plot(ax1)\nexample_plot(ax2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More complicated gridspec layouts are possible. Note here we use the\nconvenience functions `~.Figure.add_gridspec` and\n`~.SubplotSpec.subgridspec`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\n\ngs0 = fig.add_gridspec(1, 2)\n\ngs1 = gs0[0].subgridspec(2, 1)\nax1 = fig.add_subplot(gs1[0])\nax2 = fig.add_subplot(gs1[1])\n\nexample_plot(ax1)\nexample_plot(ax2)\n\ngs2 = gs0[1].subgridspec(3, 1)\n\nfor ss in gs2:\n ax = fig.add_subplot(ss)\n example_plot(ax)\n ax.set_title(\"\")\n ax.set_xlabel(\"\")\n\nax.set_xlabel(\"x-label\", fontsize=12)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that in the above the left and right columns don't have the same\nvertical extent. If we want the top and bottom of the two grids to line up\nthen they need to be in the same gridspec. We need to make this figure\nlarger as well in order for the Axes not to collapse to zero height:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(figsize=(4, 6), layout=\"constrained\")\n\ngs0 = fig.add_gridspec(6, 2)\n\nax1 = fig.add_subplot(gs0[:3, 0])\nax2 = fig.add_subplot(gs0[3:, 0])\n\nexample_plot(ax1)\nexample_plot(ax2)\n\nax = fig.add_subplot(gs0[0:2, 1])\nexample_plot(ax, hide_labels=True)\nax = fig.add_subplot(gs0[2:4, 1])\nexample_plot(ax, hide_labels=True)\nax = fig.add_subplot(gs0[4:, 1])\nexample_plot(ax, hide_labels=True)\nfig.suptitle('Overlapping Gridspecs')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example uses two gridspecs to have the colorbar only pertain to\none set of pcolors. Note how the left column is wider than the\ntwo right-hand columns because of this. Of course, if you wanted the\nsubplots to be the same size you only needed one gridspec. Note that\nthe same effect can be achieved using `~.Figure.subfigures`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\ngs0 = fig.add_gridspec(1, 2, figure=fig, width_ratios=[1, 2])\ngs_left = gs0[0].subgridspec(2, 1)\ngs_right = gs0[1].subgridspec(2, 2)\n\nfor gs in gs_left:\n ax = fig.add_subplot(gs)\n example_plot(ax)\naxs = []\nfor gs in gs_right:\n ax = fig.add_subplot(gs)\n pcm = ax.pcolormesh(arr, **pc_kwargs)\n ax.set_xlabel('x-label')\n ax.set_ylabel('y-label')\n ax.set_title('title')\n axs += [ax]\nfig.suptitle('Nested plots using subgridspec')\nfig.colorbar(pcm, ax=axs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rather than using subgridspecs, Matplotlib now provides `~.Figure.subfigures`\nwhich also work with *constrained layout*:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\nsfigs = fig.subfigures(1, 2, width_ratios=[1, 2])\n\naxs_left = sfigs[0].subplots(2, 1)\nfor ax in axs_left.flat:\n example_plot(ax)\n\naxs_right = sfigs[1].subplots(2, 2)\nfor ax in axs_right.flat:\n pcm = ax.pcolormesh(arr, **pc_kwargs)\n ax.set_xlabel('x-label')\n ax.set_ylabel('y-label')\n ax.set_title('title')\nfig.colorbar(pcm, ax=axs_right)\nfig.suptitle('Nested plots using subfigures')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manually setting Axes positions\n\nThere can be good reasons to manually set an Axes position. A manual call\nto `~.axes.Axes.set_position` will set the Axes so *constrained layout* has\nno effect on it anymore. (Note that *constrained layout* still leaves the\nspace for the Axes that is moved).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(1, 2, layout=\"constrained\")\nexample_plot(axs[0], fontsize=12)\naxs[1].set_position([0.2, 0.2, 0.4, 0.4])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n## Grids of fixed aspect-ratio Axes: \"compressed\" layout\n\n*Constrained layout* operates on the grid of \"original\" positions for\nAxes. However, when Axes have fixed aspect ratios, one side is usually made\nshorter, and leaves large gaps in the shortened direction. In the following,\nthe Axes are square, but the figure quite wide so there is a horizontal gap:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, figsize=(5, 3),\n sharex=True, sharey=True, layout=\"constrained\")\nfor ax in axs.flat:\n ax.imshow(arr)\nfig.suptitle(\"fixed-aspect plots, layout='constrained'\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One obvious way of fixing this is to make the figure size more square,\nhowever, closing the gaps exactly requires trial and error. For simple grids\nof Axes we can use ``layout=\"compressed\"`` to do the job for us:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, figsize=(5, 3),\n sharex=True, sharey=True, layout='compressed')\nfor ax in axs.flat:\n ax.imshow(arr)\nfig.suptitle(\"fixed-aspect plots, layout='compressed'\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manually turning off *constrained layout*\n\n*Constrained layout* usually adjusts the Axes positions on each draw\nof the figure. If you want to get the spacing provided by\n*constrained layout* but not have it update, then do the initial\ndraw and then call ``fig.set_layout_engine('none')``.\nThis is potentially useful for animations where the tick labels may\nchange length.\n\nNote that *constrained layout* is turned off for ``ZOOM`` and ``PAN``\nGUI events for the backends that use the toolbar. This prevents the\nAxes from changing position during zooming and panning.\n\n\n## Limitations\n\n### Incompatible functions\n\n*Constrained layout* will work with `.pyplot.subplot`, but only if the\nnumber of rows and columns is the same for each call.\nThe reason is that each call to `.pyplot.subplot` will create a new\n`.GridSpec` instance if the geometry is not the same, and\n*constrained layout*. So the following works fine:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\n\nax1 = plt.subplot(2, 2, 1)\nax2 = plt.subplot(2, 2, 3)\n# third Axes that spans both rows in second column:\nax3 = plt.subplot(2, 2, (2, 4))\n\nexample_plot(ax1)\nexample_plot(ax2)\nexample_plot(ax3)\nplt.suptitle('Homogeneous nrows, ncols')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "but the following leads to a poor layout:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\n\nax1 = plt.subplot(2, 2, 1)\nax2 = plt.subplot(2, 2, 3)\nax3 = plt.subplot(1, 2, 2)\n\nexample_plot(ax1)\nexample_plot(ax2)\nexample_plot(ax3)\nplt.suptitle('Mixed nrows, ncols')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly,\n`~matplotlib.pyplot.subplot2grid` works with the same limitation\nthat nrows and ncols cannot change for the layout to look good.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\n\nax1 = plt.subplot2grid((3, 3), (0, 0))\nax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2)\nax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)\nax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)\n\nexample_plot(ax1)\nexample_plot(ax2)\nexample_plot(ax3)\nexample_plot(ax4)\nfig.suptitle('subplot2grid')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Other caveats\n\n* *Constrained layout* only considers ticklabels, axis labels, titles, and\n legends. Thus, other artists may be clipped and also may overlap.\n\n* It assumes that the extra space needed for ticklabels, axis labels,\n and titles is independent of original location of Axes. This is\n often true, but there are rare cases where it is not.\n\n* There are small differences in how the backends handle rendering fonts,\n so the results will not be pixel-identical.\n\n* An artist using Axes coordinates that extend beyond the Axes\n boundary will result in unusual layouts when added to an\n Axes. This can be avoided by adding the artist directly to the\n :class:`~matplotlib.figure.Figure` using\n :meth:`~matplotlib.figure.Figure.add_artist`. See\n :class:`~matplotlib.patches.ConnectionPatch` for an example.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Debugging\n\n*Constrained layout* can fail in somewhat unexpected ways. Because it uses\na constraint solver the solver can find solutions that are mathematically\ncorrect, but that aren't at all what the user wants. The usual failure\nmode is for all sizes to collapse to their smallest allowable value. If\nthis happens, it is for one of two reasons:\n\n1. There was not enough room for the elements you were requesting to draw.\n2. There is a bug - in which case open an issue at\n https://github.com/matplotlib/matplotlib/issues.\n\nIf there is a bug, please report with a self-contained example that does\nnot require outside data or dependencies (other than numpy).\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n## Notes on the algorithm\n\nThe algorithm for the constraint is relatively straightforward, but\nhas some complexity due to the complex ways we can lay out a figure.\n\nLayout in Matplotlib is carried out with gridspecs\nvia the `.GridSpec` class. A gridspec is a logical division of the figure\ninto rows and columns, with the relative width of the Axes in those\nrows and columns set by *width_ratios* and *height_ratios*.\n\nIn *constrained layout*, each gridspec gets a *layoutgrid* associated with\nit. The *layoutgrid* has a series of ``left`` and ``right`` variables\nfor each column, and ``bottom`` and ``top`` variables for each row, and\nfurther it has a margin for each of left, right, bottom and top. In each\nrow, the bottom/top margins are widened until all the decorators\nin that row are accommodated. Similarly, for columns and the left/right\nmargins.\n\n\n### Simple case: one Axes\n\nFor a single Axes the layout is straight forward. There is one parent\nlayoutgrid for the figure consisting of one column and row, and\na child layoutgrid for the gridspec that contains the Axes, again\nconsisting of one row and column. Space is made for the \"decorations\" on\neach side of the Axes. In the code, this is accomplished by the entries in\n``do_constrained_layout()`` like::\n\n gridspec._layoutgrid[0, 0].edit_margin_min('left',\n -bbox.x0 + pos.x0 + w_pad)\n\nwhere ``bbox`` is the tight bounding box of the Axes, and ``pos`` its\nposition. Note how the four margins encompass the Axes decorations.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from matplotlib._layoutgrid import plot_children\n\nfig, ax = plt.subplots(layout=\"constrained\")\nexample_plot(ax, fontsize=24)\nplot_children(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Simple case: two Axes\nWhen there are multiple Axes they have their layouts bound in\nsimple ways. In this example the left Axes has much larger decorations\nthan the right, but they share a bottom margin, which is made large\nenough to accommodate the larger xlabel. Same with the shared top\nmargin. The left and right margins are not shared, and hence are\nallowed to be different.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(1, 2, layout=\"constrained\")\nexample_plot(ax[0], fontsize=32)\nexample_plot(ax[1], fontsize=8)\nplot_children(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Two Axes and colorbar\n\nA colorbar is simply another item that expands the margin of the parent\nlayoutgrid cell:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, ax = plt.subplots(1, 2, layout=\"constrained\")\nim = ax[0].pcolormesh(arr, **pc_kwargs)\nfig.colorbar(im, ax=ax[0], shrink=0.6)\nim = ax[1].pcolormesh(arr, **pc_kwargs)\nplot_children(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Colorbar associated with a Gridspec\n\nIf a colorbar belongs to more than one cell of the grid, then\nit makes a larger margin for each:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig, axs = plt.subplots(2, 2, layout=\"constrained\")\nfor ax in axs.flat:\n im = ax.pcolormesh(arr, **pc_kwargs)\nfig.colorbar(im, ax=axs, shrink=0.6)\nplot_children(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Uneven sized Axes\n\nThere are two ways to make Axes have an uneven size in a\nGridspec layout, either by specifying them to cross Gridspecs rows\nor columns, or by specifying width and height ratios.\n\nThe first method is used here. Note that the middle ``top`` and\n``bottom`` margins are not affected by the left-hand column. This\nis a conscious decision of the algorithm, and leads to the case where\nthe two right-hand Axes have the same height, but it is not 1/2 the height\nof the left-hand Axes. This is consistent with how ``gridspec`` works\nwithout *constrained layout*.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\ngs = gridspec.GridSpec(2, 2, figure=fig)\nax = fig.add_subplot(gs[:, 0])\nim = ax.pcolormesh(arr, **pc_kwargs)\nax = fig.add_subplot(gs[0, 1])\nim = ax.pcolormesh(arr, **pc_kwargs)\nax = fig.add_subplot(gs[1, 1])\nim = ax.pcolormesh(arr, **pc_kwargs)\nplot_children(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One case that requires finessing is if margins do not have any artists\nconstraining their width. In the case below, the right margin for column 0\nand the left margin for column 3 have no margin artists to set their width,\nso we take the maximum width of the margin widths that do have artists.\nThis makes all the Axes have the same size:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plt.figure(layout=\"constrained\")\ngs = fig.add_gridspec(2, 4)\nax00 = fig.add_subplot(gs[0, 0:2])\nax01 = fig.add_subplot(gs[0, 2:])\nax10 = fig.add_subplot(gs[1, 1:3])\nexample_plot(ax10, fontsize=14)\nplot_children(fig)\nplt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.2" } }, "nbformat": 4, "nbformat_minor": 0 }