【问题标题】:How to have one colorbar for all subplots如何为所有子图设置一个颜色条
【发布时间】:2018-01-18 21:22:27
【问题描述】:

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

发生的情况是,当我在 subplot1subplot2 中调用 colorbar() 函数时,它会自动缩放绘图,使颜色条加上绘图适合“子绘图”边界框,导致两个并排的地块是两个非常不同的大小。

为了解决这个问题,我尝试创建第三个子图,然后我破解了它以仅呈现颜色条来渲染任何图。 唯一的问题是,现在这两个地块的高度和宽度不均匀,我不知道如何让它看起来不错。

这是我的代码:

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

【问题讨论】:

    标签: python matplotlib subplot colorbar


    【解决方案1】:

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

    举个简单的例子:

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

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

    【讨论】:

    • ImageGrid 对于这个确切的目的也非常有用。
    • 如果你需要使用tight_layout(),你需要在tight_layout之后的subplots_adjust之后做所有事情,然后手动调整subplots_adjust和add_axes的坐标。
    • 我怎样才能为已有的两个不同散点图设置一个颜色条?我在上面尝试过,但我不知道如何用适当的变量替换“im”。假设我的散点图是 plot1=pylib.scatter(x,y,z) 和 plot2=pylib.scatter(a,b,c)
    • 这对其他人来说可能很明显,但我想指出,为了使颜色条准确地表示所有图中的颜色,vminvmax 参数至关重要。它们控制每个子图的颜色范围。如果你有真实数据,你可能需要先通过这个来找到最小值和最大值。
    • 如果图的值范围不同,颜色条范围只会显示最后一个图的范围,对吗?有什么建议吗?
    【解决方案2】:

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

    【讨论】:

    • 当子图不是正方形时,此方法不起作用。如果您更改nrows=1,颜色条将再次大于子图。
    • 你的 matplotlib 默认是什么?看起来很棒!
    【解决方案3】:

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

    斧头

    无 |父坐标轴对象,新颜色条坐标轴的空间将从该对象中被盗。如果给出了一个轴列表,它们都将被调整大小,以便为颜色条轴腾出空间。

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

    【讨论】:

    • 这个解决方案在这里效果很好,似乎是最简单的一个。
    • 如果将 nrows 更改为 1,则两个图都比颜色条更清晰。那么,如何解决这个问题呢?
    • 遗憾的是它不适用于tight_layout,但仍然是一个很好的解决方案。
    • 记住...我喜欢这个解决方案! Tinha que ser cearense!
    • 这个答案的关键部分是fig.colorbar(im, ax=axes.ravel().tolist())。如果省略ax=axes.ravel().tolist(),颜色条将放置在一个子图中。
    【解决方案4】:

    abevieiramota 使用轴列表的解决方案非常有效,直到您只使用一行图像,正如 cmets 中所指出的那样。为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()
    

    colorbar function 提供了shrink 参数,它是颜色条轴大小的比例因子。它确实需要一些手动试验和错误。例如:

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

    【讨论】:

      【解决方案5】:

      此解决方案不需要手动调整轴位置或颜色条大小,适用于多行 单行布局,并适用于 tight_layout()。它改编自 gallery example,使用来自 matplotlib 的 AxesGrid ToolboxImageGrid

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

      【讨论】:

      • @TomCho 要设置标签,您可以在实例化它时抓住颜色条的句柄,如:thecb = ax.cax.colorbar(im)。然后你可以做thecb.set_label_text("foo")
      • 如何更改颜色图?
      • @Sigur 我相信你现在已经弄清楚了,但是对于其他人,你可以在声明 im 时更改 cmap:im = ax.imshow(data, vmin=0, vmax=1, cmap='your_cmap_here')
      • 嗨!我真的很喜欢这个答案。更新 matplotlib 后,我收到以下警告:'MatplotlibDeprecationWarning: The mpl_toolkits.axes_grid1.colorbar 模块在 Matplotlib 3.2 中已弃用,将在两个次要版本后删除。改用 matplotlib.colorbar 。但是,我现在不知道如何替换这些行:ax.cax.colorbar(im) ax.cax.toggle_label(True)
      • @all 以防万一有人感兴趣,我找到了一个解决方案:将行 ax.cax.colorbar(im) 替换为 ax.cax.cla() matplotlib.colorbar.Colorbar(ax.cax,im)。当然,matplotlib.colorbar必须在开头导入。如果有人想抑制颜色条的标签,请使用ax.cax.toggle_label(False) 并添加ax.cax.tick_params(size=0)
      【解决方案6】:

      作为一个偶然发现这个线程的初学者,我想添加一个 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()
      

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

      【讨论】:

        【解决方案7】:

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

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

        fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3), 
                          gridspec_kw={"width_ratios":[1,1, 0.05]})
        

        例子:

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

        如果绘图的纵横比是自动缩放的,或者图像由于宽度方向上的纵横比(如上所示)而缩小,则此方法效果很好。但是,如果图像宽然后高,结果将如下所示,这可能是不希望的。

        将颜色条高度固定为子图高度的解决方案是使用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()
        

        【讨论】:

        • 我不确定我是否可以在这里问这个问题,但是有没有办法使用ax = fig.add_subplot() 来实现这个解决方案?我问是因为我不知道如何将它与底图一起使用。
        • @lanadaquenada 是的,这是可能的,但在这种情况下,您需要向add_subplot() 提供GridSpec
        【解决方案8】:

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

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

        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
        

        【讨论】:

        • 如果来自多个 ims 的值不同,它们应该使用相同的颜色条,所以原来的问题不会真正适用
        【解决方案9】:

        要添加到@abevieiramota 的出色答案,您可以使用 constrained_layout 获得相同的tight_layout。如果你使用imshow 而不是pcolormesh,你仍然会得到很大的水平间隙,因为imshow 强加了1:1 的纵横比。

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

        【讨论】:

          【解决方案10】:

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

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

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

          【讨论】:

            【解决方案11】:

            共享颜色图颜色条

            这适用于更复杂的情况,即值不仅在 0 和 1 之间; cmap 需要共享,而不是只使用最后一个。

            import numpy as np
            from matplotlib.colors import Normalize
            import matplotlib.pyplot as plt
            import matplotlib.cm as cm
            fig, axes = plt.subplots(nrows=2, ncols=2)
            cmap=cm.get_cmap('viridis')
            normalizer=Normalize(0,4)
            im=cm.ScalarMappable(norm=normalizer)
            for i,ax in enumerate(axes.flat):
                ax.imshow(i+np.random.random((10,10)),cmap=cmap,norm=normalizer)
                ax.set_title(str(i))
            fig.colorbar(im, ax=axes.ravel().tolist())
            plt.show()
            

            【讨论】:

            • 这很好,但是为了使用除 viridis 之外的颜色图,您需要将 cmap=cmap 添加到 ScalarMappable 的创建中。它应该是im=cm.ScalarMappable(norm=normalizer, cmap=cmap)
            • 对我来说这段代码抛出,TypeError: You must first set_array for mappable
            【解决方案12】:

            matplotlib 3.4.0 中的新功能

            现在可以使用 subfigures:

            实现共享颜色条

            新的Figure.subfiguresFigure.add_subfigure 允许...本地化的图形艺术家(例如,颜色条和字幕)仅与每个子图形相关。

            matplotlib 库包含 how to plot subfigures 上的演示。

            这是一个包含 2 个子图的最小示例,每个子图都有一个共享的颜色条:

            fig = plt.figure(constrained_layout=True)
            (subfig_l, subfig_r) = fig.subfigures(nrows=1, ncols=2)
            
            axes_l = subfig_l.subplots(nrows=1, ncols=2, sharey=True)
            for ax in axes_l:
                im = ax.imshow(np.random.random((10, 10)), vmin=0, vmax=1)
            
            # shared colorbar for left subfigure
            subfig_l.colorbar(im, ax=axes_l, location='bottom')
            
            axes_r = subfig_r.subplots(nrows=3, ncols=1, sharex=True)
            for ax in axes_r:
                mesh = ax.pcolormesh(np.random.randn(30, 30), vmin=-2.5, vmax=2.5)
            
            # shared colorbar for right subfigure
            subfig_r.colorbar(mesh, ax=axes_r)
            

            【讨论】:

              猜你喜欢
              • 2021-05-22
              相关资源
              最近更新 更多