|
| 1 | +""" |
| 2 | +========================== |
| 3 | +Arranging Axes in a Figure |
| 4 | +========================== |
| 5 | +
|
| 6 | +Often more than one axes is wanted on a figure at a time, and usually |
| 7 | +we want to organize those axes into a regular grid. Matplotlib has a |
| 8 | +variety of tools for working with grids of axes that have evolved over |
| 9 | +the history of the library. Here we will discuss the tools we think |
| 10 | +users should use most often, and then dicuss some of the older tools, |
| 11 | +and the tools that underpin how axes are organized. |
| 12 | +
|
| 13 | +How to create grid-shaped combinations of axes: |
| 14 | +
|
| 15 | +`~matplotlib.pyplot.subplots` |
| 16 | + The primary function used to create figures and a grid of axes. It is |
| 17 | + similar to `.pyplot.subplot`, but creates and places all axes on the |
| 18 | + figure at once, and returns an object array with handles for |
| 19 | + the axes in the grid. See also `.Figure.subplots`. |
| 20 | +
|
| 21 | +or |
| 22 | +
|
| 23 | +`~matplotlib.pyplot.subplot_mosaic` |
| 24 | + A more flexible function used to create figures and a grid of axes, |
| 25 | + with the added flexibility that some of the axes can span rows or |
| 26 | + columns, and that the axes are returned in a labelled dictionary instead |
| 27 | + of an array. See also `.Figure.subplot_mosaic`. |
| 28 | +
|
| 29 | +Sometimes it is natural to have more than one distinct group of axes grids, |
| 30 | +in which case Matplotlib has the concept of `~.figure.SubFigure`: |
| 31 | +
|
| 32 | +`~matplotlib.figure.SubFigure` |
| 33 | + A virtual figure within a figure. |
| 34 | +
|
| 35 | +Underlying these is the concept of a `~.gridspec.GridSpec` and |
| 36 | +`~.SubplotSpec`: |
| 37 | +
|
| 38 | +`~matplotlib.gridspec.GridSpec` |
| 39 | + Specifies the geometry of the grid that a subplot will be |
| 40 | + placed. The number of rows and number of columns of the grid |
| 41 | + need to be set. Optionally, the subplot layout parameters |
| 42 | + (e.g., left, right, etc.) can be tuned. |
| 43 | +
|
| 44 | +`~matplotlib.gridspec.SubplotSpec` |
| 45 | + Specifies the location of the subplot in the given `.GridSpec`. |
| 46 | +
|
| 47 | +An older way to add axes in grids: |
| 48 | +
|
| 49 | +`~matplotlib.pyplot.subplot2grid` |
| 50 | + A helper function that is similar to `.pyplot.subplot`, |
| 51 | + but uses 0-based indexing and let subplot to occupy multiple cells. |
| 52 | + This function is not covered in this tutorial. |
| 53 | +
|
| 54 | +.. redirect-from:: /tutorials/intermediate/gridspec |
| 55 | +
|
| 56 | +""" |
| 57 | + |
| 58 | +import matplotlib.pyplot as plt |
| 59 | + |
| 60 | +import numpy as np |
| 61 | + |
| 62 | +############################################################################ |
| 63 | +# High-level methods for making grids |
| 64 | +# =================================== |
| 65 | +# |
| 66 | +# Basic 2x2 grid |
| 67 | +# -------------- |
| 68 | +# |
| 69 | +# We can create a basic 2-by-2 grid of axes using |
| 70 | +# :func:`~matplotlib.pyplot.subplots`. It returns a |
| 71 | +# :class:`~matplotlib.figure.Figure` instance and an array of |
| 72 | +# :class:`~matplotlib.axes.Axes` objects. The axes objects can |
| 73 | +# be used to access methods to place artists on the axes; here we |
| 74 | +# use `~.Axes.annotate`, but other examples could be `~.Axes.plot`, |
| 75 | +# `~.Axes.pcolormesh`, etc. |
| 76 | + |
| 77 | +fig, axs = plt.subplots(ncols=2, nrows=2, figsize=(4.5, 3.5), |
| 78 | + constrained_layout=True) |
| 79 | +for row in range(2): |
| 80 | + for col in range(2): |
| 81 | + axs[row, col].annotate(f'axs[{row}, {col}]', (0.1, 0.5), |
| 82 | + xycoords='axes fraction', va='center') |
| 83 | +fig.suptitle('subplots') |
| 84 | + |
| 85 | +############################################################################## |
| 86 | +# The same effect can be achieved with `~.pyplot.subplot_mosaic`, |
| 87 | +# but the return type is a dictionary instead of an array, where the user |
| 88 | +# can give the keys useful meanings. Here we provide two lists, each list |
| 89 | +# representing a row, and each element in the list a key representing the |
| 90 | +# column. Note that keys can be any dictionary key, but we typically use |
| 91 | +# strings: |
| 92 | + |
| 93 | +fig, axs = plt.subplot_mosaic([['upleft', 'upright'], ['loleft', 'loright']], |
| 94 | + figsize=(4.5, 3.5), constrained_layout=True) |
| 95 | +for k in axs.keys(): |
| 96 | + axs[k].annotate(f'axs["{k}"]', (0.1, 0.5), |
| 97 | + xycoords='axes fraction', va='center') |
| 98 | +fig.suptitle('subplot_mosaic') |
| 99 | + |
| 100 | +############################################################################ |
| 101 | +# Axes spanning rows or columns in a grid |
| 102 | +# --------------------------------------- |
| 103 | +# |
| 104 | +# Sometimes we want axes to span rows or columns of the grid. |
| 105 | +# There are actually multiple ways to accomplish this, but the most |
| 106 | +# convenient is probably to use `~.pyplot.subplot_mosaic` by repeating one |
| 107 | +# of the keys: |
| 108 | + |
| 109 | +fig, axs = plt.subplot_mosaic([['upleft', 'right'], ['loleft', 'right']], |
| 110 | + figsize=(4.5, 3.5), constrained_layout=True) |
| 111 | +for k in axs.keys(): |
| 112 | + axs[k].annotate(f'axs["{k}"]', (0.1, 0.5), |
| 113 | + xycoords='axes fraction', va='center') |
| 114 | +fig.suptitle('subplot_mosaic') |
| 115 | + |
| 116 | +############################################################################ |
| 117 | +# See below for the description of how to do the same thing using |
| 118 | +# `~matplotlib.gridspec.GridSpec` or ~matplotlib.pyplot.subplot2grid`. |
| 119 | +# |
| 120 | +# Variable widths or heights in a grid |
| 121 | +# ------------------------------------ |
| 122 | +# |
| 123 | +# Both `~.pyplot.subplots` and `~.pyplot.subplot_mosaic` allow the rows |
| 124 | +# in the grid to be different heights, and the columns to be different |
| 125 | +# widths using the *gridspec_kw* keyword argument. |
| 126 | +# Any parameter accepted by :class:`~matplotlib.gridspec.GridSpec` can |
| 127 | +# be passed to `~matplotlib.pyplot.subplots` and |
| 128 | +# `~matplotlib.pyplot.subplot_mosaic`: |
| 129 | + |
| 130 | +gs_kw = dict(width_ratios=[1, 2.2], height_ratios=[1, 2]) |
| 131 | +fig, axs = plt.subplot_mosaic([['upleft', 'right'], ['loleft', 'right']], |
| 132 | + gridspec_kw=gs_kw, figsize=(4.5, 3.5), |
| 133 | + constrained_layout=True) |
| 134 | +for k in axs.keys(): |
| 135 | + axs[k].annotate(f'axs["{k}"]', (0.1, 0.5), |
| 136 | + xycoords='axes fraction', va='center') |
| 137 | +fig.suptitle('subplot_mosaic') |
| 138 | + |
| 139 | +############################################################################ |
| 140 | +# Nested axes layouts |
| 141 | +# ------------------- |
| 142 | +# |
| 143 | +# Sometimes it is helpful to have two or more grids of axes that |
| 144 | +# may not need to be related to one another. The most simple way to |
| 145 | +# accomplish this is to use `.Figure.subfigures`. Note that the alignement |
| 146 | +# of the subfigure layouts is independent with the axes spines in each |
| 147 | +# subfigure having independent positions. See below for a more verbose |
| 148 | +# way to acheive the same effect with `~.gridspec.GridSpecFromSubplotSpec`. |
| 149 | + |
| 150 | +fig = plt.figure(constrained_layout=True) |
| 151 | +subfigs = fig.subfigures(1, 2, wspace=0.07, width_ratios=[1.5, 1.]) |
| 152 | +axs0 = subfigs[0].subplots(2, 2) |
| 153 | +subfigs[0].set_facecolor('0.9') |
| 154 | +subfigs[0].suptitle('subfigs[0]\nLeft side') |
| 155 | +subfigs[0].supxlabel('xlabel for subfigs[0]') |
| 156 | + |
| 157 | +axs1 = subfigs[1].subplots(3, 1) |
| 158 | +subfigs[1].suptitle('subfigs[1]') |
| 159 | +subfigs[1].supylabel('ylabel for subfigs[1]') |
| 160 | + |
| 161 | +############################################################################ |
| 162 | +# Low-level and advanced grid methods |
| 163 | +# =================================== |
| 164 | +# |
| 165 | +# `~.pyplot.subplots` and `~.pyplot.subplot_mosaic` provide high-level |
| 166 | +# interfaces to create `~.gridspec.GridSpec` objects and add associated |
| 167 | +# new axes to a figure at given `~.gridspec.SubplotSpec` inside the |
| 168 | +# *GridSpec*. |
| 169 | +# |
| 170 | +# Basic 2x2 grid |
| 171 | +# -------------- |
| 172 | +# |
| 173 | +# We can accopmplish a 2x2 grid in the same manner as |
| 174 | +# ``plt.subplots(2, 2)``: |
| 175 | + |
| 176 | +fig = plt.figure(figsize=(4.5, 3.5), constrained_layout=True) |
| 177 | +spec = fig.add_gridspec(ncols=2, nrows=2) |
| 178 | +ax0 = fig.add_subplot(spec[0, 0]) |
| 179 | +ax0.annotate('ax0', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 180 | +ax1 = fig.add_subplot(spec[0, 1]) |
| 181 | +ax1.annotate('ax1', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 182 | +ax2 = fig.add_subplot(spec[1, 0]) |
| 183 | +ax2.annotate('ax2', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 184 | +ax3 = fig.add_subplot(spec[1, 1]) |
| 185 | +ax3.annotate('ax3', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 186 | +fig.suptitle('Manually added subplots using add_gridspec') |
| 187 | + |
| 188 | +############################################################################# |
| 189 | +# Axes spanning rows or grids in a grid |
| 190 | +# ------------------------------------- |
| 191 | +# |
| 192 | +# We can index the *spec* array using `NumPy slice syntax |
| 193 | +# <https://numpy.org/doc/stable/reference/arrays.indexing.html>`_ |
| 194 | +# and the new axes will span the slice: |
| 195 | + |
| 196 | +fig = plt.figure(figsize=(4.5, 3.5), constrained_layout=True) |
| 197 | +spec = fig.add_gridspec(2, 2) |
| 198 | +ax0 = fig.add_subplot(spec[0, :]) |
| 199 | +ax0.annotate('ax0', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 200 | +ax10 = fig.add_subplot(spec[1, 0]) |
| 201 | +ax10.annotate('ax10', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 202 | +ax11 = fig.add_subplot(spec[1, 1]) |
| 203 | +ax11.annotate('ax11', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 204 | +fig.suptitle('Manually added subplots, spanning a column') |
| 205 | + |
| 206 | +############################################################################### |
| 207 | +# Manual Adjustments to a Gridspec Layout |
| 208 | +# --------------------------------------- |
| 209 | +# |
| 210 | +# When a GridSpec is explicitly used, you can adjust the layout |
| 211 | +# parameters of subplots that are created from the GridSpec. Note this |
| 212 | +# option is not compatible with ``constrained_layout`` or |
| 213 | +# `.Figure.tight_layout` which both ignore *left* and *right* and adjust |
| 214 | +# subplot sizes to fill the figure. Usually such manual placement |
| 215 | +# requires iterations to make the axes tick labels not overlap the axes. |
| 216 | + |
| 217 | +fig = plt.figure(constrained_layout=False) |
| 218 | +gs = fig.add_gridspec(nrows=3, ncols=3, left=0.05, right=0.75, |
| 219 | + hspace=0.1, wspace=0.05) |
| 220 | +ax0 = fig.add_subplot(gs[:-1, :]) |
| 221 | +ax0.annotate('ax0', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 222 | +ax1 = fig.add_subplot(gs[-1, :-1]) |
| 223 | +ax1.annotate('ax1', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 224 | +ax2 = fig.add_subplot(gs[-1, -1]) |
| 225 | +ax2.annotate('ax2', (0.1, 0.5), xycoords='axes fraction', va='center') |
| 226 | +fig.suptitle('Manual gridspec with right=0.75') |
| 227 | + |
| 228 | +############################################################################### |
| 229 | +# Nested layouts with SubPlotSpec |
| 230 | +# =============================== |
| 231 | +# |
| 232 | +# You can create nested layout similar to `~.Figure.subfigures` using |
| 233 | +# `~.gridspec.SubplotSpec.subgridspec`. Here the axes spines _are_ |
| 234 | +# aligned. |
| 235 | +# |
| 236 | +# Note this is also available from the more verbose |
| 237 | +# `.gridspec.GridSpecFromSubplotSpec`. |
| 238 | + |
| 239 | +fig = plt.figure(constrained_layout=True) |
| 240 | +gs0 = fig.add_gridspec(1, 2) |
| 241 | + |
| 242 | +gs00 = gs0[0].subgridspec(2, 2) |
| 243 | +gs01 = gs0[1].subgridspec(3, 1) |
| 244 | + |
| 245 | +for a in range(2): |
| 246 | + for b in range(2): |
| 247 | + ax = fig.add_subplot(gs00[a, b]) |
| 248 | + ax.annotate(f'axLeft[{a}, {b}]', (0.1, 0.5), xycoords='axes fraction', |
| 249 | + va='center') |
| 250 | + if a == 1 and b == 1: |
| 251 | + ax.set_xlabel('xlabel') |
| 252 | +for a in range(3): |
| 253 | + ax = fig.add_subplot(gs01[a]) |
| 254 | + ax.annotate(f'axRight[{a}]', (0.1, 0.5), xycoords='axes fraction', |
| 255 | + va='center') |
| 256 | + if a == 2: |
| 257 | + ax.set_ylabel('ylabel') |
| 258 | + |
| 259 | +fig.suptitle('nested gridspecs') |
| 260 | + |
| 261 | +############################################################################### |
| 262 | +# Here's a more sophisticated example of nested GridSpec where we put |
| 263 | +# a box around each cell of the outer 4x4 grid, by hiding appropriate |
| 264 | +# spines in each of the inner 3x3 grids. |
| 265 | + |
| 266 | + |
| 267 | +def squiggle_xy(a, b, c, d, i=np.arange(0.0, 2*np.pi, 0.05)): |
| 268 | + return np.sin(i*a)*np.cos(i*b), np.sin(i*c)*np.cos(i*d) |
| 269 | + |
| 270 | +fig = plt.figure(figsize=(8, 8), constrained_layout=False) |
| 271 | +outer_grid = fig.add_gridspec(4, 4, wspace=0, hspace=0) |
| 272 | + |
| 273 | +for a in range(4): |
| 274 | + for b in range(4): |
| 275 | + # gridspec inside gridspec |
| 276 | + inner_grid = outer_grid[a, b].subgridspec(3, 3, wspace=0, hspace=0) |
| 277 | + axs = inner_grid.subplots() # Create all subplots for the inner grid. |
| 278 | + for (c, d), ax in np.ndenumerate(axs): |
| 279 | + ax.plot(*squiggle_xy(a + 1, b + 1, c + 1, d + 1)) |
| 280 | + ax.set(xticks=[], yticks=[]) |
| 281 | + |
| 282 | +# show only the outside spines |
| 283 | +for ax in fig.get_axes(): |
| 284 | + ss = ax.get_subplotspec() |
| 285 | + ax.spines.top.set_visible(ss.is_first_row()) |
| 286 | + ax.spines.bottom.set_visible(ss.is_last_row()) |
| 287 | + ax.spines.left.set_visible(ss.is_first_col()) |
| 288 | + ax.spines.right.set_visible(ss.is_last_col()) |
| 289 | + |
| 290 | +plt.show() |
| 291 | + |
| 292 | +############################################################################# |
| 293 | +# |
| 294 | +# More reading |
| 295 | +# ============ |
| 296 | +# |
| 297 | +# - More details about :doc:`subplot mosaic </tutorials/provisional/mosaic>`. |
| 298 | +# - more details about |
| 299 | +# :doc:`constrained layout |
| 300 | +# </tutorials/intermediate/constrainedlayout_guide>`, used to align |
| 301 | +# spacing in most of these examples. |
| 302 | +# |
| 303 | +# .. admonition:: References |
| 304 | +# |
| 305 | +# The use of the following functions, methods, classes and modules is shown |
| 306 | +# in this example: |
| 307 | +# |
| 308 | +# - `matplotlib.pyplot.subplots` |
| 309 | +# - `matplotlib.pyplot.subplot_mosaic` |
| 310 | +# - `matplotlib.figure.Figure.add_gridspec` |
| 311 | +# - `matplotlib.figure.Figure.add_subplot` |
| 312 | +# - `matplotlib.gridspec.GridSpec` |
| 313 | +# - `matplotlib.gridspec.SubplotSpec.subgridspec` |
| 314 | +# - `matplotlib.gridspec.GridSpecFromSubplotSpec` |
0 commit comments