moving colorbar with gridspec - python

I am making a plot with multiple figures and corresponding colorbars. This page http://www.sc.eso.org/~bdias/pycoffee/codes/20160407/gridspec_demo.html claims to know the best way of doing so. I am inclined to believe them.
However, I have run into issues adressing the kwags of the colorbar when I do it their way:
from matplotlib.colorbar import Colorbar
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import numpy as np
median = np.zeros((100,100))
map0 = np.zeros((100,100))
map1 = map0
map2 = map0
fig = plt.figure()
plt.tight_layout()
gs = gridspec.GridSpec(2,6)
ax = plt.subplot(gs[0,0])
cbax = plt.subplot(gs[0,1])
ax1 = plt.subplot(gs[0,2])
cbax1 = plt.subplot(gs[0,3])
ax2 = plt.subplot(gs[0,4])
cbax2 = plt.subplot(gs[0,5])
ax3 = plt.subplot(gs[1,0])
cbax3 = plt.subplot(gs[1,1])
ax4 = plt.subplot(gs[1,2])
cbax4 = plt.subplot(gs[1,3])
ax5 = plt.subplot(gs[1,4])
cbax5 = plt.subplot(gs[1,5])
cax = ax.imshow(map0)
ax.contour(median)
cb = Colorbar(ax = cbax,mappable = cax,shrink=0.8)
cax1 = ax1.imshow(map1)
ax1.contour(median)
cb1 = Colorbar(ax = cbax1,mappable = cax1)
cax2 = ax2.imshow(map2)
ax2.contour(median)
cb2 = Colorbar(ax = cbax2,mappable = cax2)
cax3 = ax3.imshow(map0/median)
ax3.contour(median)
cb3 = Colorbar(ax = cbax3,mappable = cax3)
cax4 = ax4.imshow(map1/median)
ax4.contour(median)
cb4 = Colorbar(ax = cbax4,mappable = cax4)
cax5 = ax5.imshow(map2)
ax5.contour(median)
cb5 = Colorbar(ax = cbax5,mappable = cax5)
When I now call the kwargs shrink and or pad I get the following message:
Traceback (most recent call last):
File "plot_integratedMaps.py", line 173, in <module>
main()
File "plot_integratedMaps.py", line 171, in main
plot_integratedMaps(map630,map869,mapTot,median)
File "plot_integratedMaps.py", line 129, in plot_integratedMaps
cb = Colorbar(ax = cbax,mappable = cax,shrink=0.8)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/colorbar.py", line 943, in __init__
ColorbarBase.__init__(self, ax, **kw)
TypeError: __init__() got an unexpected keyword argument 'shrink'
I guess it makes sense that I cant pad the colorbar in in the gs[0,1] and have to movegs[0,1] instead. But I don't get why shrink doesn't work?
am using Python 2.7.12

I would not consider it useful to create Colorbar directly like in the link; instead one could use fig.colorbar(). However, this is only tangential to the problem.
First consider creating a colorbar next to a plot.
import matplotlib.pyplot as plt
import numpy as np
median = np.zeros((100,100))
map0 = np.zeros((100,100))
fig, ax = plt.subplots()
im = ax.imshow(map0)
ax.contour(median)
cb = fig.colorbar(im, ax=ax, shrink=0.8)
plt.show()
Here, shrink works fine because you want the axes in which the colorbar resides to be a factor 0.8 smaller than the axes ax to which it belongs.
Now, if you specify the axes in which the colorbar should reside, shrink does not make any sense, because the axes does not need to be created inside the colorbar function, but you supply it externally.
import matplotlib.pyplot as plt
import numpy as np
median = np.zeros((100,100))
map0 = np.zeros((100,100))
fig, (ax,cax) = plt.subplots(ncols=2)
im = ax.imshow(map0)
ax.contour(median)
#using `shrink` here would produce an error,
# because the colorbar axes (cax) already exists
# instead of
# cb = fig.colorbar(im, cax=cax, shrink=0.8)
# you need
cb = fig.colorbar(im, cax=cax)
plt.show()
Note that this is independend of gridspec. Whether or not you want to use gridspec is also a question of taste, but surely not needed for simple plots.
If you have more plots, it again depends what you want to show. The edited example from the question looks more like a regular grid. Here creating a colorbar axes for each subplot could be efficiently done via make_axes_locatable.
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.pyplot as plt
import numpy as np
data = np.random.rand(6,6)
fig, axes = plt.subplots(nrows=2, ncols=3)
for ax in axes.flatten():
im = ax.imshow(data)
div = make_axes_locatable(ax)
cax = div.append_axes("right", size="5%", pad=0.1)
cbar = fig.colorbar(im, cax=cax)
plt.tight_layout()
plt.show()
Taking the above, you may shrink the colorbars by not using this axes divider, but as usual, create your colorbar and use the shrink argument.
import matplotlib.pyplot as plt
import numpy as np
data = np.random.rand(6,6)
fig, axes = plt.subplots(nrows=2, ncols=3)
for ax in axes.flatten():
im = ax.imshow(data)
cbar = fig.colorbar(im, ax=ax, shrink=0.4)
plt.tight_layout()
plt.show()

Related

Matplotlib - Tight layout of multiple subplots with colorbar

I have a series of subplots in a single row, all sharing the same colorbar and I would like to use plt.tight_layout().
However when used naively, the colorbar messes everything up. Luckily, I found this in the matplotlib documentation, but it works only for one subplot.
Minimal Working Example
I tried to adapt it to multiple subplots but the subplot to which the colorbar is assigned to ends up being smaller.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
plt.close('all')
arr = np.arange(100).reshape((10, 10))
fig, ax = plt.subplots(ncols=2, figsize=(8, 4))
im0 = ax[0].imshow(arr, interpolation="none")
im1 = ax[1].imshow(arr, interpolation='none')
divider = make_axes_locatable(plt.gca())
cax = divider.append_axes("right", "5%", pad="3%")
plt.colorbar(im0, cax=cax)
plt.tight_layout()
This is what the result looks like.
With the newest matplotlib (3.6), there is a new option layout='compressed' for this situation:
import matplotlib.pyplot as plt
import numpy as np
arr = np.arange(100).reshape((10, 10))
fig, ax = plt.subplots(ncols=2, figsize=(4, 2), layout='compressed')
im0 = ax[0].imshow(arr)
im1 = ax[1].imshow(arr)
plt.colorbar(im0, ax=ax)
plt.show()

twinx messes up colorbar of pcolormesh plot

I have encountered a problem when trying to plot some values on top of an image. The problem is that I cannot really place the colorbar properly. With properly I mean an image where I overlay a line plot. This line plot should have its yaxis on the right with its label and then further to the right should be the colorbar of the image.
Here is the reduced code that shows the problem:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
image = np.random.randint(10, size=100).reshape(10, 10)
fig, ax = plt.subplots()
# ax2 = ax.twinx() # -> Calling this messes up the colorbar
# ax2.plot(image.sum(0), 'r') # what I actually want to do but not needed for the error
im = ax.pcolormesh(image)
cax = make_axes_locatable(ax).append_axes('right', size="5%", pad=0.4)
cbar = fig.colorbar(im, cax=cax)
Below you can see the effect of ax2 = ax.twinx() on the colorbar (I do not have enough reputation for the images so stackoverflow replaced it with links).
without ax2 = ax.twinx()
with ax2 = ax.twinx()
I have tried make_axes_locatable(ax).append_axes() and also a combination with make_axes_locatable(ax).new_horizontal() inspired by the answer to this question: Positioning the colorbar.
Looking into documentation of fig.colorbar() I found the arguments ax and cax and played with them around. They do a lot, but not what I would like to.
I'm not sure what I'm doing wrong, could not find out on the internet and I'm thankful for any advice.
Did you try a normal colorbar with constrained_layout:
import matplotlib.pyplot as plt
import numpy as np
image = np.random.randint(10, size=100).reshape(10, 10)
fig, ax = plt.subplots(constrained_layout=True)
ax2 = ax.twinx()
ax2.plot(image.sum(0), 'r')
im = ax.pcolormesh(image)
cbar = fig.colorbar(im, ax=ax)
plt.show()

Why has subplot of matplotlib not the same size? [duplicate]

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

Undesired colorbar and axis labels/plot titles interaction

Dear fellow Python users,
I try to produce colormaps by combining LinearSegmentedColormap in combination with Imshow, using matplotlib library, and I'm having a hard time with the actual colorbar.
The colorbar does not behaves the way I want by default, that is, it is much too big for my graph. By default, I get this:
So, I used the following code lines to fix colorbar height, in reference to this post:
ax = plt.gca()
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(img2, cax=cax)
An then I got this very strange result:
I'm not able to figure out why the added code lines interact with my axis and plot titles... Why do they go with the colorbar?
Here is the complete code:
import matplotlib.pyplot as plt
import matplotlib.pylab as pylab
import matplotlib.colors as colors
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
#random data for example
clmap = np.random.rand(30,500)
#plot 2D color map
fig = plt.figure()
cmap2 = colors.LinearSegmentedColormap.from_list('my_colormap', ['blue','green','red'], 256)
img2 = plt.imshow(clmap,interpolation='nearest',cmap = cmap2,origin='lower')
ax = plt.gca()
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
fig.colorbar(img2, cmap = cmap2, cax=cax)
plt.title('color map of atom probab at iteration 1')
plt.xlabel('atom id')
plt.ylabel('layer')
fig.savefig("map_p_1.png")
plt.gcf().clear()
You're mixing the pyplot state machine (plt) with the object oriented API.
After creating the second axes object (cax), it will be the current axes. All pyplot commands comming afterwards are applied to this axes.
The easiest way out is to apply the pyplot commands before creating the new axes:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
#random data for example
clmap = np.random.rand(30,500)
#plot 2D color map
fig = plt.figure()
plt.title('color map of atom probab at iteration 1')
plt.xlabel('atom id')
plt.ylabel('layer')
cmap2 = colors.LinearSegmentedColormap.from_list('my_colormap', ['blue','green','red'], 256)
img2 = plt.imshow(clmap,interpolation='nearest',cmap = cmap2,origin='lower')
ax = plt.gca()
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
fig.colorbar(img2, cmap = cmap2, cax=cax)
fig.savefig("map_p_1.png")
plt.show()

Wrong colorbar positioning when using subplots (matplotlib)

I want to create a figure consisting of nine subplots. I really hated the fact that I needed to create ax1 to ax9 separately so I created a for loop to do so. However, when I want to include a colorbar, the colorbar is positioned right of the last subplot. This is also illustrated in the following figure:
What is going wrong and how can I fix this?
The image has been generated with the following code:
import numpy
import layout
import matplotlib.pylab as plt
data = numpy.random.random((10, 10))
test = ["ax1", "ax2", "ax3", "ax4", "ax5", "ax6", "ax7", "ax8", "ax9"]
fig = plt.figure(1)
for idx in range(len(test)):
vars()[test[idx]] = fig.add_subplot(3, 3, (idx + 1))
im = ax1.imshow(data)
plt.colorbar(im)
im2 = ax3.imshow(data)
plt.colorbar(im2)
plt.show()
colorbar takes an argument ax the "parent axes object(s) from which space for a new colorbar axes will be stolen." In your code you could do something like this to add a color bar next to an an axes:
im = ax1.imshow(data)
plt.colorbar(im, ax = ax1)
I found the answer to my question, resulting in the correct colorbar vs subplot spacing. Notice that if the spacing between subplot and colorbar does not matter, the answer of Molly is correct.
import numpy
import layout
import matplotlib.pylab as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
data = numpy.random.random((10, 10))
test = ["ax1", "ax2", "ax3", "ax4", "ax5", "ax6", "ax7", "ax8", "ax9"]
fig = plt.figure(1)
for idx in range(len(test)):
vars()[test[idx]] = fig.add_subplot(3, 3, (idx + 1))
divider = make_axes_locatable(vars()[test[idx]])
vars()["c" + test[idx]] = divider.append_axes("right", size = "5%", pad = 0.05)
im1 = ax1.imshow(data)
plt.colorbar(im1, cax = cax1)
im2 = ax2.imshow(data)
plt.colorbar(im2, cax = cax2)
im3 = ax3.imshow(data)
plt.colorbar(im3, cax = cax3)
im4 = ax4.imshow(data)
plt.colorbar(im4, cax = cax4)
im5 = ax5.imshow(data)
plt.colorbar(im5, cax = cax5)
im6 = ax6.imshow(data)
plt.colorbar(im6, cax = cax6)
im7 = ax7.imshow(data)
plt.colorbar(im7, cax = cax7)
im8 = ax8.imshow(data)
plt.colorbar(im8, cax = cax8)
im9 = ax9.imshow(data)
plt.colorbar(im9, cax = cax9)
plt.show()
This results in:
Dude's answer is great. However I would prefer avoid copy-paste by using this:
import numpy
import matplotlib.pylab as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
data = numpy.random.random((10, 10))
test = ["ax1", "ax2", "ax3", "ax4", "ax5", "ax6", "ax7", "ax8", "ax9"]
fig = plt.figure(1)
for idx in range(len(test)):
vars()[test[idx]] = fig.add_subplot(3, 3, (idx + 1))
divider = make_axes_locatable(vars()[test[idx]])
vars()["c" + test[idx]] = divider.append_axes("right", size = "5%", pad = 0.05)
vars()["im" + str(idx)] = vars()[test[idx]].imshow(data)
plt.colorbar(vars()["im" + str(idx)], cax = vars()["c" + test[idx]])
plt.show()
The result is the same.

Categories

Resources