Python Matplotlib 2 个子图,1 个颜色条

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13784201/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 09:36:30  来源:igfitidea点击:

Matplotlib 2 Subplots, 1 Colorbar

pythonmatplotlibsubplotcolorbar

提问by astromax

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.

我花了很长时间研究如何让两个子图共享相同的 y 轴,并且在 Matplotlib 中两者之间共享一个颜色条。

What was happening was that when I called the colorbar()function in either subplot1or 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.

发生的事情是,当我colorbar()subplot1or 中调用该函数时subplot2,它会自动缩放绘图,以便颜色条和绘图适合“子图”边界框内,从而导致两个并排的图成为两个非常不同的图尺寸。

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()

采纳答案by Joe Kington

Just place the colorbar in its own axis and use subplots_adjustto make room for it.

只需将颜色条放在它自己的轴上并用来subplots_adjust为它腾出空间。

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()

enter image description here

在此处输入图片说明

Note that the color range will be set by the last image plotted (that gave rise to im) even if the range of values is set by vminand vmax. If another plot has, for example, a higher max value, points with higher values than the max of imwill show in uniform color.

请注意,im即使值的范围是由vmin和设置的,颜色范围也将由最后绘制的图像(产生)设置vmax。例如,如果另一个图具有更高的最大值,则值高于最大值的点im将以统一颜色显示。

回答by kch

Using make_axesis even easier and gives a better result. It also provides possibilities to customise the positioning of the colorbar. Also note the option of subplotsto share x and y axes.

使用起来make_axes更容易,效果也更好。它还提供了自定义颜色条定位的可能性。还要注意subplots共享 x 和 y 轴的选项。

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()

回答by abevieiramota

You can simplify Joe Kington's code using the axparameter of figure.colorbar()with a list of axes. From the documentation:

您可以使用带有轴列表的ax参数来简化 Joe Kington 的代码figure.colorbar()。从文档

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()

1

1

回答by spinup

The solution of using a list of axes by abevieiramotaworks very well until you use only one row of images, as pointed out in the comments. Using a reasonable aspect ratio for figsizehelps, but is still far from perfect. For example:

正如评论中指出的那样,使用abevieiramota的轴列表的解决方案非常有效,直到您只使用一行图像。使用合理的纵横比来figsize帮助,但仍远非完美。例如:

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()

1 x 3 image array

1 x 3 图像阵列

The colorbar functionprovides the shrinkparameter which is a scaling factor for the size of the colorbar axes. It does require some manual trial and error. For example:

彩条的功能提供了shrink这对于颜色条轴的尺寸的比例因子的参数。它确实需要一些手动试错。例如:

fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)

1 x 3 image array with shrunk colorbar

1 x 3 图像阵列,带有缩小的颜色条

回答by spinup

This solution does not require manual tweaking of axes locations or colorbar size, works with multi-row andsingle-row layouts, and works with tight_layout(). It is adapted from a gallery example, using ImageGridfrom matplotlib's AxesGrid Toolbox.

此解决方案不需要手动调整轴位置或颜色条大小,适用于多行单行布局,并且适用于tight_layout(). 它改编自图库示例,使用ImageGrid来自 matplotlib 的AxesGrid 工具箱

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()

image grid

图像网格

回答by RChapman

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):

作为偶然发现这个线程的初学者,我想添加一个 python-for- dummies改编abevieiramota非常简洁的答案(因为我处于必须查找“ravel”才能弄清楚什么的水平)他们的代码正在做):

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.

更少的pythonic,对于像我这样的菜鸟来说更容易看到这里实际发生的事情。

回答by ImportanceOfBeingErnest

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.

正如其他答案中所指出的,这个想法通常是为颜色条定义一个轴。这样做的方法有很多种;尚未提及的一种方法是在创建子图时直接指定颜色条轴plt.subplots()。优点是轴位置不需要手动设置,并且在所有自动方面的情况下,颜色条将与子图的高度完全相同。即使在许多使用图像的情况下,结果也会令人满意,如下所示。

When using plt.subplots(), the use of gridspec_kwargument allows to make the colorbar axes much smaller than the other axes.

使用时plt.subplots(),使用gridspec_kw参数允许使颜色条轴比其他轴小得多。

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()

enter image description here

在此处输入图片说明

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.

如果绘图的方面是自动缩放的,或者由于它们在宽度方向上的方面(如上所示)而缩小了图像,这很有效。但是,如果图像比高更宽,结果将如下所示,这可能是不希望的。

enter image description here

在此处输入图片说明

A solution to fix the colorbar height to the subplot heightwould be to use mpl_toolkits.axes_grid1.inset_locator.InsetPositionto set the colorbar axes relative to the image subplot axes.

将颜色条高度固定到子图高度的解决方案是使用mpl_toolkits.axes_grid1.inset_locator.InsetPosition相对于图像子图轴设置颜色条轴。

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()

enter image description here

在此处输入图片说明

回答by ImportanceOfBeingErnest

I noticed that almost every solution posted involved ax.imshow(im, ...)and did not normalize the colors displayed to the colorbar for the multiple subfigures. The immappable is taken from the last instance, but what if the values of the multiple im-s are different? (I'm assuming these mappables are treated in the same way that the contour-sets and surface-sets are treated.) I have an example using a 3d surface plot below that creates two colorbars for a 2x2 subplot (one colorbar per one row). Although the question asks explicitly for a different arrangement, I think the example helps clarify some things. I haven't found a way to do this using plt.subplots(...)yet because of the 3D axes unfortunately.

我注意到几乎所有发布的解决方案都涉及ax.imshow(im, ...)并且没有标准化显示到多个子图的颜色条的颜色。该im可映射从最后一个实例采取,但如果多个值im-s有什么不同?(我假设这些可映射对象的处理方式与处理轮廓集和表面集的方式相同。)我有一个使用下面的 3d 表面图的示例,该示例为 2x2 子图创建两个颜色条(每行一个颜色条) )。虽然这个问题明确要求不同的安排,但我认为这个例子有助于澄清一些事情。plt.subplots(...)不幸的是,由于 3D 轴,我还没有找到一种方法来使用它。

Example Plot

示例图

If only I could position the colorbars in a better way... (There is probably a much better way to do this, but at least it should be not too difficult to follow.)

如果我能以更好的方式定位颜色条就好了......(可能有更好的方法来做到这一点,但至少它应该不太难理解。)

import matplotlib
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

cmap = 'plasma'
ncontours = 5

def get_data(row, col):
    """ get X, Y, Z, and plot number of subplot
        Z > 0 for top row, Z < 0 for bottom row """
    if row == 0:
        x = np.linspace(1, 10, 10, dtype=int)
        X, Y = np.meshgrid(x, x)
        Z = np.sqrt(X**2 + Y**2)
        if col == 0:
            pnum = 1
        else:
            pnum = 2
    elif row == 1:
        x = np.linspace(1, 10, 10, dtype=int)
        X, Y = np.meshgrid(x, x)
        Z = -np.sqrt(X**2 + Y**2)
        if col == 0:
            pnum = 3
        else:
            pnum = 4
    print("\nPNUM: {}, Zmin = {}, Zmax = {}\n".format(pnum, np.min(Z), np.max(Z)))
    return X, Y, Z, pnum

fig = plt.figure()
nrows, ncols = 2, 2
zz = []
axes = []
for row in range(nrows):
    for col in range(ncols):
        X, Y, Z, pnum = get_data(row, col)
        ax = fig.add_subplot(nrows, ncols, pnum, projection='3d')
        ax.set_title('row = {}, col = {}'.format(row, col))
        fhandle = ax.plot_surface(X, Y, Z, cmap=cmap)
        zz.append(Z)
        axes.append(ax)

## get full range of Z data as flat list for top and bottom rows
zz_top = zz[0].reshape(-1).tolist() + zz[1].reshape(-1).tolist()
zz_btm = zz[2].reshape(-1).tolist() + zz[3].reshape(-1).tolist()
## get top and bottom axes
ax_top = [axes[0], axes[1]]
ax_btm = [axes[2], axes[3]]
## normalize colors to minimum and maximum values of dataset
norm_top = matplotlib.colors.Normalize(vmin=min(zz_top), vmax=max(zz_top))
norm_btm = matplotlib.colors.Normalize(vmin=min(zz_btm), vmax=max(zz_btm))
cmap = cm.get_cmap(cmap, ncontours) # number of colors on colorbar
mtop = cm.ScalarMappable(cmap=cmap, norm=norm_top)
mbtm = cm.ScalarMappable(cmap=cmap, norm=norm_btm)
for m in (mtop, mbtm):
    m.set_array([])

# ## create cax to draw colorbar in
# cax_top = fig.add_axes([0.9, 0.55, 0.05, 0.4])
# cax_btm = fig.add_axes([0.9, 0.05, 0.05, 0.4])
cbar_top = fig.colorbar(mtop, ax=ax_top, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_top)
cbar_top.set_ticks(np.linspace(min(zz_top), max(zz_top), ncontours))
cbar_btm = fig.colorbar(mbtm, ax=ax_btm, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_btm)
cbar_btm.set_ticks(np.linspace(min(zz_btm), max(zz_btm), ncontours))

plt.show()
plt.close(fig)
## orientation of colorbar = 'horizontal' if done by column

回答by Jody Klymak

To add to @abevieiramota's excellent answer, you can get the euqivalent of tight_layout with constrained_layout. You will still get large horizontal gaps if you use imshowinstead of pcolormeshbecause of the 1:1 aspect ratio imposed by imshow.

要添加到@abevieiramota 的优秀答案中,您可以使用 constrained_layout 获得紧密布局的等效项。如果使用imshow代替 ,您仍然会得到很大的水平间隙,pcolormesh因为imshow.

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=2, constrained_layout=True)
for ax in axes.flat:
    im = ax.pcolormesh(np.random.random((10,10)), vmin=0, vmax=1)

fig.colorbar(im, ax=axes.flat)
plt.show()

enter image description here

在此处输入图片说明

回答by Enzoupi

This topic is well covered but I still would like to propose another approach in a slightlydifferent philosophy.

这个主题已经很好地涵盖了,但我仍然想提出另一种稍微不同的哲学的方法。

It is a bit more complex to set-up but it allow (in my opinion) a bit more flexibility. For example, one can play with the respective ratios of each subplots / colorbar:

设置有点复杂,但它允许(在我看来)更多的灵活性。例如,可以使用每个子图/颜色条的相应比率:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec

# Define number of rows and columns you want in your figure
nrow = 2
ncol = 3

# Make a new figure
fig = plt.figure(constrained_layout=True)

# Design your figure properties
widths = [3,4,5,1]
gs = GridSpec(nrow, ncol + 1, figure=fig, width_ratios=widths)

# Fill your figure with desired plots
axes = []
for i in range(nrow):
    for j in range(ncol):
        axes.append(fig.add_subplot(gs[i, j]))
        im = axes[-1].pcolormesh(np.random.random((10,10)))

# Shared colorbar    
axes.append(fig.add_subplot(gs[:, ncol]))
fig.colorbar(im, cax=axes[-1])

plt.show()

enter image description here

在此处输入图片说明