可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've spent entirely too long researching how to get two subplots to share the same y-axis with a single colorbar shared between the two in Matplotlib.
What was happening was that when I called the colorbar()
function in either subplot1
or subplot2
, it would autoscale the plot such that the colorbar plus the plot would fit inside the 'subplot' bounding box, causing the two side-by-side plots to be two very different sizes.
To get around this, I tried to create a third subplot which I then hacked to render no plot with just a colorbar present. The only problem is, now the heights and widths of the two plots are uneven, and I can't figure out how to make it look okay.
Here is my code:
from __future__ import division import matplotlib.pyplot as plt import numpy as np from matplotlib import patches from matplotlib.ticker import NullFormatter # SIS Functions TE = 1 # Einstein radius g1 = lambda x,y: (TE/2) * (y**2-x**2)/((x**2+y**2)**(3/2)) g2 = lambda x,y: -1*TE*x*y / ((x**2+y**2)**(3/2)) kappa = lambda x,y: TE / (2*np.sqrt(x**2+y**2)) coords = np.linspace(-2,2,400) X,Y = np.meshgrid(coords,coords) g1out = g1(X,Y) g2out = g2(X,Y) kappaout = kappa(X,Y) for i in range(len(coords)): for j in range(len(coords)): if np.sqrt(coords[i]**2+coords[j]**2) <= TE: g1out[i][j]=0 g2out[i][j]=0 fig = plt.figure() fig.subplots_adjust(wspace=0,hspace=0) # subplot number 1 ax1 = fig.add_subplot(1,2,1,aspect='equal',xlim=[-2,2],ylim=[-2,2]) plt.title(r"$\gamma_{1}$",fontsize="18") plt.xlabel(r"x ($\theta_{E}$)",fontsize="15") plt.ylabel(r"y ($\theta_{E}$)",rotation='horizontal',fontsize="15") plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5]) plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5]) plt.imshow(g1out,extent=(-2,2,-2,2)) plt.axhline(y=0,linewidth=2,color='k',linestyle="--") plt.axvline(x=0,linewidth=2,color='k',linestyle="--") e1 = patches.Ellipse((0,0),2,2,color='white') ax1.add_patch(e1) # subplot number 2 ax2 = fig.add_subplot(1,2,2,sharey=ax1,xlim=[-2,2],ylim=[-2,2]) plt.title(r"$\gamma_{2}$",fontsize="18") plt.xlabel(r"x ($\theta_{E}$)",fontsize="15") ax2.yaxis.set_major_formatter( NullFormatter() ) plt.axhline(y=0,linewidth=2,color='k',linestyle="--") plt.axvline(x=0,linewidth=2,color='k',linestyle="--") plt.imshow(g2out,extent=(-2,2,-2,2)) e2 = patches.Ellipse((0,0),2,2,color='white') ax2.add_patch(e2) # subplot for colorbar ax3 = fig.add_subplot(1,1,1) ax3.axis('off') cbar = plt.colorbar(ax=ax2) plt.show()
回答1:
Just place the colorbar in its own axis and use subplots_adjust
to make room for it.
As a quick example:
import numpy as np import matplotlib.pyplot as plt fig, axes = plt.subplots(nrows=2, ncols=2) for ax in axes.flat: im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1) fig.subplots_adjust(right=0.8) cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) fig.colorbar(im, cax=cbar_ax) plt.show()
回答2:
You can simplify Joe Kington's code using the ax
parameter of figure.colorbar()
with a list of axes. From the documentation:
ax
None | parent axes object(s) from which space for a new colorbar axes will be stolen. If a list of axes is given they will all be resized to make room for the colorbar axes.
import numpy as np import matplotlib.pyplot as plt fig, axes = plt.subplots(nrows=2, ncols=2) for ax in axes.flat: im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1) fig.colorbar(im, ax=axes.ravel().tolist()) plt.show()
回答3:
Using make_axes
is even easier and gives a better result. It also provides possibilities to customise the positioning of the colorbar. Also note the option of subplots
to share x and y axes.
import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl fig, axes = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True) for ax in axes.flat: im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1) cax,kw = mpl.colorbar.make_axes([ax for ax in axes.flat]) plt.colorbar(im, cax=cax, **kw) plt.show()
回答4:
This solution does not require manual tweaking of axes locations or colorbar size, works with multi-row and single-row layouts, and works with tight_layout()
. It is adapted from a gallery example, using ImageGrid
from matplotlib's AxesGrid Toolbox.
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import ImageGrid # Set up figure and image grid fig = plt.figure(figsize=(9.75, 3)) grid = ImageGrid(fig, 111, # as in plt.subplot(111) nrows_ncols=(1,3), axes_pad=0.15, share_all=True, cbar_location="right", cbar_mode="single", cbar_size="7%", cbar_pad=0.15, ) # Add data to image grid for ax in grid: im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1) # Colorbar ax.cax.colorbar(im) ax.cax.toggle_label(True) #plt.tight_layout() # Works, but may still require rect paramater to keep colorbar labels visible plt.show()

回答5:
The solution of using a list of axes by abevieiramota works very well until you use only one row of images, as pointed out in the comments. Using a reasonable aspect ratio for figsize
helps, but is still far from perfect. For example:
import numpy as np import matplotlib.pyplot as plt fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(9.75, 3)) for ax in axes.flat: im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1) fig.colorbar(im, ax=axes.ravel().tolist()) plt.show()

The colorbar function provides the shrink
parameter which is a scaling factor for the size of the colorbar axes. It does require some manual trial and error. For example:
fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)

回答6:
As a beginner who stumbled across this thread, I'd like to add a python-for-dummies adaptation of abevieiramota's very neat answer (because I'm at the level that I had to look up 'ravel' to work out what their code was doing):
import numpy as np import matplotlib.pyplot as plt fig, ((ax1,ax2,ax3),(ax4,ax5,ax6)) = plt.subplots(2,3) axlist = [ax1,ax2,ax3,ax4,ax5,ax6] first = ax1.imshow(np.random.random((10,10)), vmin=0, vmax=1) third = ax3.imshow(np.random.random((12,12)), vmin=0, vmax=1) fig.colorbar(first, ax=axlist) plt.show()
Much less pythonic, much easier for noobs like me to see what's actually happening here.
回答7:
As pointed out in other answers, the idea is usually to define an axes for the colorbar to reside in. There are various ways of doing so; one that hasn't been mentionned yet would be to directly specify the colorbar axes at subplot creation with plt.subplots()
. The advantage is that the axes position does not need to be manually set and in all cases with automatic aspect the colorbar will be exactly the same height as the subplots. Even in many cases where images are used the result will be satisfying as shown below.
When using plt.subplots()
, the use of gridspec_kw
argument allows to make the colorbar axes much smaller than the other axes.
fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3), gridspec_kw={"width_ratios":[1,1, 0.05]})
Example:
import matplotlib.pyplot as plt import numpy as np; np.random.seed(1) fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3), gridspec_kw={"width_ratios":[1,1, 0.05]}) fig.subplots_adjust(wspace=0.3) im = ax.imshow(np.random.rand(11,8), vmin=0, vmax=1) im2 = ax2.imshow(np.random.rand(11,8), vmin=0, vmax=1) ax.set_ylabel("y label") fig.colorbar(im, cax=cax) plt.show()

This works well, if the plots' aspect is autoscaled or the images are shrunk due to their aspect in the width direction (as in the above). If, however, the images are wider then high, the result would look as follows, which might be undesired.

A solution to fix the colorbar height to the subplot height would be to use mpl_toolkits.axes_grid1.inset_locator.InsetPosition
to set the colorbar axes relative to the image subplot axes.
import matplotlib.pyplot as plt import numpy as np; np.random.seed(1) from mpl_toolkits.axes_grid1.inset_locator import InsetPosition fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(7,3), gridspec_kw={"width_ratios":[1,1, 0.05]}) fig.subplots_adjust(wspace=0.3) im = ax.imshow(np.random.rand(11,16), vmin=0, vmax=1) im2 = ax2.imshow(np.random.rand(11,16), vmin=0, vmax=1) ax.set_ylabel("y label") ip = InsetPosition(ax2, [1.05,0,0.05,1]) cax.set_axes_locator(ip) fig.colorbar(im, cax=cax, ax=[ax,ax2]) plt.show()
