343

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()
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
astromax
  • 6,001
  • 10
  • 36
  • 47

13 Answers13

425

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

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 vmin and vmax. If another plot has, for example, a higher max value, points with higher values than the max of im will show in uniform color.

drevicko
  • 14,382
  • 15
  • 75
  • 97
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • 5
    ImageGrid is also very useful for this exact purpose. – Phillip Cloud Apr 17 '13 at 23:37
  • 11
    if you need to use tight_layout(), you will want to do everything after subplots_adjust after tight_layout, and then tweak the coordinates for subplots_adjust and add_axes manually. – user1748155 Sep 26 '13 at 22:08
  • 4
    How can I have a single color bar for two different scatter plots that I already have? I tried above but I don't know how to substitute "im" with appropriate variables. Let say my scatter plots are plot1=pylib.scatter(x,y,z) and plot2=pylib.scatter(a,b,c) – Rotail Jul 29 '14 at 01:57
  • How do you do this if you need to loop over a dataset to generate the individual plots? e.g. I'd have `for i in range(4):; subplot(2,2,i); plot(data[i])` - how does that work with the axes loop? – naught101 Aug 19 '15 at 01:38
  • 83
    This may have been obvious to others, but I wanted to point out that in order that the colourbar does accurately represent the colour in all the plots, the `vmin` and `vmax` arguments are critical. They control the colour range of each subplot. If you have real data, you may need to do a pass through this to find the min and max values first. – James Owers Sep 03 '15 at 10:01
  • 1
    @kungfujam How would you set the `vmin` and `vmax` parameters in this case? The `colorbar` method doesn't support these 2 parameters. – CMCDragonkai Dec 11 '16 at 08:34
  • @CMCDragonkai they are arguments for `imshow` method, not `colorbar`. In the answer they are set to 0 and 1. Another handy tip for `imshow` here is to set the argument `interpolation='none'` so the plot is not blurred. [See here for examples](http://matplotlib.org/examples/images_contours_and_fields/interpolation_methods.html) – James Owers Dec 11 '16 at 15:48
  • 1
    I thought it was possible to set a color bar for multiple subplots that don't have the same range of values (mine were all different), so I just decided to have multiple color bars. – CMCDragonkai Dec 12 '16 at 14:53
  • 3
    if the range of values of the plots is different, the colorbar range would only show the last plot's range, right? any suggestions? – Lukas Jun 07 '19 at 08:18
  • @JamesOwers : as per the [imshow docs](https://matplotlib.org/3.5.1/api/_as_gen/matplotlib.pyplot.imshow.html), `interpolation=None` is already the default. – pas-calc Mar 09 '22 at 13:54
  • @pas-calc the default is indeed `None` which then [defaults to `antialiased`](https://matplotlib.org/stable/gallery/images_contours_and_fields/interpolation_methods.html#interpolations-for-imshow). Still need to specify `interpolation="none"` for no interpolation. – Kyle Mar 30 '22 at 20:38
225

You can simplify Joe Kington's code using the axparameter 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()

1

buzjwa
  • 2,632
  • 2
  • 24
  • 37
abevieiramota
  • 2,481
  • 1
  • 11
  • 3
  • 5
    This solution worked very well here, and seems to be the easiest one. – Kknd Mar 31 '15 at 18:36
  • 11
    If you change nrows to 1, both plots are shoter than colorbar. so, how can solve this problem? – Jin Jul 19 '16 at 08:41
  • 13
    Pity it doesn't work with tight_layout, but good solution nonetheless. – Mark Dec 15 '16 at 15:11
  • 1
    Just to remember... I love this solution! Tinha que ser cearense! – iury simoes-sousa Feb 06 '19 at 13:40
  • This doesn't work for me. The colorbar is on top of the right column in a 2x2 configuration – Bensge Jun 26 '19 at 15:18
  • I have the same problem as Bensge. How does one fix this? – Rylan Schaeffer Jan 27 '20 at 19:09
  • 1
    The crucial part of this answer is `fig.colorbar(im, ax=axes.ravel().tolist())`. If you omit `ax=axes.ravel().tolist()`, the colorbar will be placed within one subplot. – nyanpasu64 Feb 13 '20 at 08:57
  • 1
    @RylanSchaeffer and, most importantly, to many other people with different problems: _"How does one fix this?"_ Post a new question where ① you reference the answer that is not working for you, ② you show your code and the results you've got, ③ you compare those results with your expectations. – gboffi Nov 21 '20 at 09:42
  • 1
    @Mark The answer works with `fig = plt.figure(..., constrained_layout=True)` ፨ in general using `constrained_layout` provides results similar to the use of `plt.tight_layout()` but simply better and more reliable. – gboffi Nov 21 '20 at 09:46
  • @gboffi Oh that's good to know, it seems to be experimental for now and didn't exist back in 2016, but seems like a good improvement. – Mark Nov 21 '20 at 10:03
  • In matplotlib 3.6.1 (and maybe before), you can just use `ax=axs` instead of `ax=axs.ravel().tolist()`. – thomaskeefe Oct 22 '22 at 20:18
  • I wonder why no one has mentioned it so far, but one has to be careful with this answer. Tthe colorbar drawn by this solution only covers the data range of the last plot. i.e., if the min/max of other subplots are beyond the min/max of the very last one, they will be shown "saturated". This may not be the intended behavior when sharing colorbar across subplots. – arash Aug 16 '23 at 21:59
75

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

image grid

SpinUp __ A Davis
  • 5,016
  • 1
  • 30
  • 25
  • Double +1, this is a great approach – Brett Sep 08 '16 at 22:17
  • Indeed works with tight_layout, but I have no idea how to add a label to that colorbar. It doesn't accept the kws label, title, text... anything! And the docs don't help much. – TomCho Jul 21 '17 at 21:30
  • 6
    @TomCho To set a label, you can grab the colorbar's handle when you instantiate it, as: `thecb = ax.cax.colorbar(im)`. Then you can do `thecb.set_label_text("foo")` – SpinUp __ A Davis Jul 22 '17 at 04:06
  • @Chiel The `rect` parameter specifies the bounding box of the figure elements with the figure area. It takes normalized figure co-ords and the default is `[0,0,1,1]`. So specifying `rect=[0,0,0.9,1]` as an argument to `tight_layout` would force the figure elements that tight_layout knows about to fit within 90% of the figure width, leaving 10% for your label. See [doc](https://matplotlib.org/users/tight_layout_guide.html). – SpinUp __ A Davis Aug 09 '17 at 19:56
  • 1
    How to change the colormap? – Sigur Dec 06 '17 at 00:57
  • I think this is the most complete, yet simple solution. – Irene May 28 '18 at 14:02
  • 1
    @Sigur I am sure you have figured it out by now, but for others, you can change the cmap when declaring im: im = ax.imshow(data, vmin=0, vmax=1, cmap='your_cmap_here') – Shaun Lowis Apr 01 '20 at 22:25
  • 1
    Hi! I really like this answer. After updating matplotlib, I get this warning: 'MatplotlibDeprecationWarning: The mpl_toolkits.axes_grid1.colorbar module was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use matplotlib.colorbar instead.' However, I didn't figure out how to replace these lines now: `ax.cax.colorbar(im) ax.cax.toggle_label(True)` – Isi Aug 13 '20 at 10:13
  • 4
    @all Just in case anyone is interested, I found a solution: Replace the line `ax.cax.colorbar(im)` by `ax.cax.cla()` `matplotlib.colorbar.Colorbar(ax.cax,im)`. Of course, `matplotlib.colorbar` must be imported in the beginning. In case anyone wants to suppress the labels of the colorbar, use `ax.cax.toggle_label(False)` and add `ax.cax.tick_params(size=0)`. – Isi Oct 02 '20 at 06:55
42

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

kch
  • 678
  • 6
  • 10
32

Shared colormap and colorbar

This is for the more complex case where the values are not just between 0 and 1; the cmap needs to be shared instead of just using the last one.

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

philn
  • 654
  • 6
  • 17
  • 8
    This is great, but in order to use colormaps other than viridis you need to add `cmap=cmap` to the creation of the `ScalarMappable`. It should read `im=cm.ScalarMappable(norm=normalizer, cmap=cmap)` – Ianhi Jan 27 '21 at 05:38
  • For me this code throws, `TypeError: You must first set_array for mappable` – Vik Feb 17 '21 at 20:25
31

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.

RChapman
  • 423
  • 4
  • 7
24

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

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

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • I'm not sure if I'm allowed to ask this here, but is there a way to implement this solution using `ax = fig.add_subplot()` instead? I'm asking because I can't figure out how to use it with basemap. – lanadaquenada May 23 '18 at 16:10
  • 1
    @lanadaquenada Yes that is possible, but you would need to supply a `GridSpec` to `add_subplot()` in that case. – ImportanceOfBeingErnest May 23 '18 at 16:16
22

New in matplotlib 3.4.0

Shared colorbars can now be implemented using subfigures:

New Figure.subfigures and Figure.add_subfigure allow ... localized figure artists (e.g., colorbars and suptitles) that only pertain to each subfigure.

The matplotlib gallery includes demos on how to plot subfigures.

Here is a minimal example with 2 subfigures, each with a shared colorbar:

subfigure colorbars

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)
tdy
  • 36,675
  • 19
  • 86
  • 83
13

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

1 x 3 image array

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)

1 x 3 image array with shrunk colorbar

SpinUp __ A Davis
  • 5,016
  • 1
  • 30
  • 25
9

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 imshow instead of pcolormesh because of the 1:1 aspect ratio imposed by 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

Jody Klymak
  • 4,979
  • 2
  • 15
  • 31
7

This topic is well covered but I still would like to propose another approach in a slightly different 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

Enzoupi
  • 541
  • 5
  • 10
6

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 im mappable 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.

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
  • If the values from the multiple `im`s are different, they should *not* use the same colorbar, so the original question wouldn't really apply – SpinUp __ A Davis Jun 06 '19 at 14:55
4

The answers above are great, but most of them use the fig.colobar() method applied to a fig object. This example shows how to use the plt.colobar() function, applied directly to pyplot:

def shared_colorbar_example():
    fig, axs = plt.subplots(nrows=3, ncols=3)
    for ax in axs.flat:
        plt.sca(ax)
        color = np.random.random((10))
        plt.scatter(range(10), range(10), c=color, cmap='viridis', vmin=0, vmax=1)
    plt.colorbar(ax=axs.ravel().tolist(), shrink=0.6)
    plt.show()

shared_colorbar_example()

Since most answers above demonstrated usage on 2D matrices, I went with a simple scatter plot. The shrink keyword is optional and resizes the colorbar.

If vmin and vmax are not specified this approach will automatically analyze all of the subplots for the minimum and maximum value to be used on the colorbar. The above approaches when using fig.colorbar(im) scan only the image passed as argument for min and max values of the colorbar.

Result:

Result of running the code snippet above

waykiki
  • 914
  • 2
  • 9
  • 19