From a36157436e8827655e1ff58deb0a98138f922ed2 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:54:54 +0100 Subject: [PATCH] Backport PR #26914: DOC: add a couple more placement examples, crosslink axes_grid [ci doc] --- .../demo_colorbar_with_axes_divider.py | 6 + .../demo_colorbar_with_inset_locator.py | 10 +- .../users_explain/axes/colorbar_placement.py | 113 ++++++++++++++++-- .../axes/constrainedlayout_guide.py | 4 +- 4 files changed, 118 insertions(+), 15 deletions(-) diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py index e314c2dcea21..9e4611c65bb7 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py @@ -1,4 +1,6 @@ """ +.. _demo-colorbar-with-axes-divider: + ========================= Colorbar with AxesDivider ========================= @@ -8,6 +10,10 @@ method of the `.AxesDivider` can then be used to create a new axes on a given side ("top", "right", "bottom", or "left") of the original axes. This example uses `.append_axes` to add colorbars next to axes. + +Users should consider simply passing the main axes to the *ax* keyword argument of +`~.Figure.colorbar` instead of creating a locatable axes manually like this. +See :ref:`colorbar_placement`. """ import matplotlib.pyplot as plt diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py index 0c8d48e23101..8ec7d0e7271b 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py @@ -1,15 +1,21 @@ """ +.. _demo-colorbar-with-inset-locator: + ============================================================== Controlling the position and size of colorbars with Inset Axes ============================================================== -This example shows how to control the position, height, and width of -colorbars using `~mpl_toolkits.axes_grid1.inset_locator.inset_axes`. +This example shows how to control the position, height, and width of colorbars +using `~mpl_toolkits.axes_grid1.inset_locator.inset_axes`. Inset axes placement is controlled as for legends: either by providing a *loc* option ("upper right", "best", ...), or by providing a locator with respect to the parent bbox. Parameters such as *bbox_to_anchor* and *borderpad* likewise work in the same way, and are also demonstrated here. + +Users should consider using `.Axes.inset_axes` instead (see +:ref:`colorbar_placement`). + """ import matplotlib.pyplot as plt diff --git a/galleries/users_explain/axes/colorbar_placement.py b/galleries/users_explain/axes/colorbar_placement.py index de767a4fa130..1e43d4940a98 100644 --- a/galleries/users_explain/axes/colorbar_placement.py +++ b/galleries/users_explain/axes/colorbar_placement.py @@ -10,7 +10,11 @@ Colorbars indicate the quantitative extent of image data. Placing in a figure is non-trivial because room needs to be made for them. -The simplest case is just attaching a colorbar to each axes: +Automatic placement of colorbars +================================ + +The simplest case is just attaching a colorbar to each axes. Note in this +example that the colorbars steal some space from the parent axes. """ import matplotlib.pyplot as plt import numpy as np @@ -28,9 +32,9 @@ fig.colorbar(pcm, ax=ax) # %% -# The first column has the same type of data in both rows, so it may -# be desirable to combine the colorbar which we do by calling -# `.Figure.colorbar` with a list of axes instead of a single axes. +# The first column has the same type of data in both rows, so it may be +# desirable to have just one colorbar. We do this by passing `.Figure.colorbar` +# a list of axes with the *ax* kwarg. fig, axs = plt.subplots(2, 2) cmaps = ['RdBu_r', 'viridis'] @@ -41,6 +45,27 @@ cmap=cmaps[col]) fig.colorbar(pcm, ax=axs[:, col], shrink=0.6) +# %% +# The stolen space can lead to axes in the same subplot layout +# being different sizes, which is often undesired if the the +# x-axis on each plot is meant to be comparable as in the following: + +fig, axs = plt.subplots(2, 1, figsize=(4, 5), sharex=True) +X = np.random.randn(20, 20) +axs[0].plot(np.sum(X, axis=0)) +axs[1].pcolormesh(X) +fig.colorbar(pcm, ax=axs[1], shrink=0.6) + +# %% +# This is usually undesired, and can be worked around in various ways, e.g. +# adding a colorbar to the other axes and then removing it. However, the most +# straightforward is to use :ref:`constrained layout `: + +fig, axs = plt.subplots(2, 1, figsize=(4, 5), sharex=True, layout='constrained') +axs[0].plot(np.sum(X, axis=0)) +axs[1].pcolormesh(X) +fig.colorbar(pcm, ax=axs[1], shrink=0.6) + # %% # Relatively complicated colorbar layouts are possible using this # paradigm. Note that this example works far better with @@ -56,8 +81,67 @@ fig.colorbar(pcm, ax=[axs[2, 1]], location='left') # %% -# Colorbars with fixed-aspect-ratio axes -# ====================================== +# Adjusting the spacing between colorbars and parent axes +# ======================================================= +# +# The distance a colorbar is from the parent axes can be adjusted with the +# *pad* keyword argument. This is in units of fraction of the parent axes +# width, and the default for a vertical axes is 0.05 (or 0.15 for a horizontal +# axes). + +fig, axs = plt.subplots(3, 1, layout='constrained', figsize=(5, 5)) +for ax, pad in zip(axs, [0.025, 0.05, 0.1]): + pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') + fig.colorbar(pcm, ax=ax, pad=pad, label=f'pad: {pad}') +fig.suptitle("layout='constrained'") + +# %% +# Note that if you do not use constrained layout, the pad command makes the +# parent axes shrink: + +fig, axs = plt.subplots(3, 1, figsize=(5, 5)) +for ax, pad in zip(axs, [0.025, 0.05, 0.1]): + pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') + fig.colorbar(pcm, ax=ax, pad=pad, label=f'pad: {pad}') +fig.suptitle("No layout manager") + +# %% +# Manual placement of colorbars +# ============================= +# +# Sometimes the automatic placement provided by ``colorbar`` does not +# give the desired effect. We can manually create an axes and tell +# ``colorbar`` to use that axes by passing the axes to the *cax* keyword +# argument. +# +# Using ``inset_axes`` +# -------------------- +# +# We can manually create any type of axes for the colorbar to use, but an +# `.Axes.inset_axes` is useful because it is a child of the parent axes and can +# be positioned relative to the parent. Here we add a colorbar centered near +# the bottom of the parent axes. + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) +pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') +ax.set_ylim([-4, 20]) +cax = ax.inset_axes([0.3, 0.07, 0.4, 0.04]) +fig.colorbar(pcm, cax=cax, orientation='horizontal') + +# %% +# `.Axes.inset_axes` can also specify its position in data coordinates +# using the *transform* keyword argument if you want your axes at a +# certain data position on the graph: + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) +pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') +ax.set_ylim([-4, 20]) +cax = ax.inset_axes([7.5, -1.7, 5, 1.2], transform=ax.transData) +fig.colorbar(pcm, cax=cax, orientation='horizontal') + +# %% +# Colorbars attached to fixed-aspect-ratio axes +# --------------------------------------------- # # Placing colorbars for axes with a fixed aspect ratio pose a particular # challenge as the parent axes changes size depending on the data view. @@ -77,9 +161,10 @@ fig.colorbar(pcm, ax=ax, shrink=0.6) # %% -# One way around this issue is to use an `.Axes.inset_axes` to locate the -# axes in axes coordinates. Note that if you zoom in on the axes, and -# change the shape of the axes, the colorbar will also change position. +# We solve this problem using `.Axes.inset_axes` to locate the axes in "axes +# coordinates" (see :ref:`transforms_tutorial`). Note that if you zoom in on +# the parent axes, and thus change the shape of it, the colorbar will also +# change position. fig, axs = plt.subplots(2, 2, layout='constrained') cmaps = ['RdBu_r', 'viridis'] @@ -94,6 +179,12 @@ ax.set_aspect(1/2) if row == 1: cax = ax.inset_axes([1.04, 0.2, 0.05, 0.6]) - fig.colorbar(pcm, ax=ax, cax=cax) + fig.colorbar(pcm, cax=cax) -plt.show() +# %% +# .. seealso:: +# +# :ref:`axes_grid` has methods for manually creating colorbar axes as well: +# +# - :ref:`demo-colorbar-with-inset-locator` +# - :ref:`demo-colorbar-with-axes-divider` diff --git a/galleries/users_explain/axes/constrainedlayout_guide.py b/galleries/users_explain/axes/constrainedlayout_guide.py index 0a2752674c6a..260a4f76bf71 100644 --- a/galleries/users_explain/axes/constrainedlayout_guide.py +++ b/galleries/users_explain/axes/constrainedlayout_guide.py @@ -4,9 +4,9 @@ .. _constrainedlayout_guide: -================================ +======================== Constrained Layout Guide -================================ +======================== Use *constrained layout* to fit plots within your figure cleanly.