Wrong colorbar positioning when using subplots (matplotlib) - python

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.

Related

colorbar changes the size of subplot in python

I use the following code to generate side-by-size images and I need to add colorbar only to the second image in the row. I use the following code for it
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.gridspec as gridspec
def plotting(x):
gs1 = gridspec.GridSpec(1, 2)
gs1.update(wspace=0.005, hspace=0.005)
plt.subplot(gs1[0])
plt.imshow(x)
plt.axis('off')
plt.title('dog')
ax1 = plt.subplot(gs1[1])
imc = plt.imshow(x, cmap='hot', interpolation='nearest')
plt.axis('off')
plt.title('dog')
divider = make_axes_locatable(ax1)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(imc, cax=cax)
plt.tight_layout()
plt.show()
However it comes out the size of side-by-side images are not equal. I wonder how I could fix this issue?
You can use ImageGrid, which was created exactly for this purpose:
from mpl_toolkits.axes_grid1 import ImageGrid
x = np.random.random(size=(10,10))
fig = plt.figure()
grid = ImageGrid(fig, 111,
nrows_ncols = (1,2),
axes_pad = 0.05,
cbar_location = "right",
cbar_mode="single",
cbar_size="5%",
cbar_pad=0.05
)
grid[0].imshow(x)
grid[0].axis('off')
grid[0].set_title('dog')
imc = grid[1].imshow(x, cmap='hot', interpolation='nearest')
grid[1].axis('off')
grid[1].set_title('dog')
plt.colorbar(imc, cax=grid.cbar_axes[0])

matplotlib set_aspect(num) on an axis doesn't resize display box in a gridspec

I'm plotting an image with their two projections (x and y) in a GridSpec. When I use the set_aspect on the central image, the image size box isn't resized for its minimal size (without blank) as you can see below. Does somebody have a solution to resolve this case?
Matplotlib 3.0.2, Python 3.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
plt.rcParams['toolbar'] = 'toolmanager'
matplotlib.use('Qt5Agg')
ldata = np.random.random((256, 256))
xhisto = np.sum(ldata, axis=0)
yhisto = np.sum(ldata, axis=1)
fig = plt.figure()
gs = plt.GridSpec(2, 2, height_ratios=[10,1], width_ratios=[3,30], wspace=0.1, hspace=0.1)
ax_image = plt.subplot(gs[1])
ax_histoy = plt.subplot(gs[0], sharey=ax_image)
ax_histox = plt.subplot(gs[3], sharex=ax_image)
plt.subplots_adjust(right=0.8)
colorAx = plt.axes([0.85, 0.4, 0.02, 0.45])
im = ax_image.imshow(ldata, cmap='jet', interpolation='none', aspect='auto')
ax_histox.plot(xhisto)
ax_histoy.plot(yhisto, range(256))
ax_image.invert_yaxis()
ax_image.tick_params(labelbottom=False, labelleft=False)
ax_histoy.spines['right'].set_visible(False)
ax_histoy.spines['bottom'].set_visible(False)
ax_histox.spines['right'].set_visible(False)
ax_histox.spines['top'].set_visible(False)
ax_histoy.set_ylim(1,256)
ax_histox.set_xlim(1,256)
ax_histox.set_xlabel('X')
ax_histoy.set_ylabel('Y')
ax_image.set_title('Matplotlib - Plot 2D')
ax_histoy.tick_params(axis='x',labelsize=8,labelrotation=90)
ax_histox.tick_params(axis='y',labelsize=8)
ax_histoy.xaxis.tick_top()
ax_histox.yaxis.tick_left()
plt.colorbar(im, cax = colorAx)
ax_image.set_aspect(0.5)
plt.show()
I try to find a solution for resizing the height of the projection on the left
Using the example as you explain give the save result with a ratio different as 1:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# Fixing random state for reproducibility
np.random.seed(19680801)
# the random data
x = np.random.randn(1000)
y = np.random.randn(1000)
fig, axScatter = plt.subplots(figsize=(5.5, 5.5))
# the scatter plot:
axScatter.scatter(x, y)
axScatter.set_aspect(0.3)
# create new axes on the right and on the top of the current axes
# The first argument of the new_vertical(new_horizontal) method is
# the height (width) of the axes to be created in inches.
divider = make_axes_locatable(axScatter)
axHistx = divider.append_axes("top", 1.2, pad=0.1, sharex=axScatter)
axHisty = divider.append_axes("right", 1.2, pad=0.1, sharey=axScatter)
# make some labels invisible
axHistx.xaxis.set_tick_params(labelbottom=False)
axHisty.yaxis.set_tick_params(labelleft=False)
# now determine nice limits by hand:
binwidth = 0.25
xymax = max(np.max(np.abs(x)), np.max(np.abs(y)))
lim = (int(xymax/binwidth) + 1)*binwidth
bins = np.arange(-lim, lim + binwidth, binwidth)
axHistx.hist(x, bins=bins)
axHisty.hist(y, bins=bins, orientation='horizontal')
# the xaxis of axHistx and yaxis of axHisty are shared with axScatter,
# thus there is no need to manually adjust the xlim and ylim of these
# axis.
axHistx.set_yticks([0, 50, 100])
axHisty.set_xticks([0, 50, 100])
plt.show()
Result with axes_grid
Could it solved with axes_grid ???

Why this code for colorbar labeling works with Matplotlib 2.2.3 but not with Matplotlib 3.0.1?

I have the following code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
arr = np.random.randint(0, 100, (2, 3, 4))
fig, ax = plt.subplots(1, 1)
pax = ax.imshow(arr, vmin=0, vmax=100)
cbar_kws=dict(ticks=(0, 100))
cbar_txt='arb. units'
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0.05)
cbar = ax.figure.colorbar(pax, cax=cax, **dict(cbar_kws))
# cbar = ax.figure.colorbar(plot, ax=ax, **dict(cbar_kws))
if cbar_txt is not None:
only_extremes = 'ticks' in cbar_kws and len(cbar_kws['ticks']) == 2
if only_extremes:
cbar.ax.text(
2.0, 0.5, cbar_txt, fontsize='medium', rotation=90,
va='center', ha='left')
else:
cbar.set_label(cbar_txt)
plt.tight_layout()
plt.show()
This works fine for Matplotlib 2.2.3 where I get a text in the middle of the colorbar (on the right):
But does not work the same way for Matplotlib 3.0.1, where the text gets rendered at the bottom of the colorbar:
Why? Any suggestion for obtaining the same behavior with both versions?
How
Using cbar.ax.text seems to be a workaround for some other problem. The recommended way to set a label to the colorbar is either via the colorbar call itself, or via cbar.set_label("label").
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
arr = np.random.randint(0, 100, (2, 3))
fig, ax = plt.subplots(1, 1)
pax = ax.imshow(arr, vmin=0, vmax=100)
cbar_kws=dict(ticks=(0, 100))
cbar_txt='arb. units'
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0.05)
cbar = ax.figure.colorbar(pax, cax=cax, **dict(cbar_kws))
cbar.set_label(cbar_txt, labelpad=-12)
plt.tight_layout()
plt.show()
The result is the same in matplotlib 2.2.3 and 3.0.1:
To have the label distance independent of the length of the colorbar labels you may label the left side of the colorbar and shift the label even more.
cbar.set_label(cbar_txt, labelpad=-36)
cbar.ax.yaxis.set_label_position("left")
Finally, you may indeed use a text on the axes, but position it in axes coordinates instead of data coordinates,
cbar.ax.text(2, 0.5, cbar_txt, fontsize='medium', rotation=90,
va='center', ha='left', transform=cbar.ax.transAxes)
Why
As to why cbar.ax.text works differently between the versions: The internal units of the colorbar have changed. This shouldn't affect any external application, but makes it easier to apply different locators to colorbars. In fact it has become more consistent. E.g. if the colorbar range is 0 to 100, and you place a text at y=0.5, it'll appear very close to 0.
Why not use the label directly? Edit: didn't see answer below. See for better explanation.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
arr = np.random.randint(0, 100, (2, 3, 4))
fig, ax = plt.subplots(1, 1)
pax = ax.imshow(arr, vmin=0, vmax=100)
cbar_txt='arb. units'
cbar_kws=dict(ticks=(0, 100))
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0.05)
cbar = ax.figure.colorbar(pax, cax=cax, **dict(cbar_kws))
cbar.set_label(cbar_txt, size = 20)
cbar.ax.tick_params(labelsize = 10)
plt.tight_layout()
plt.show()

Matplotlib Axes3D Ratio Savefig

I have a little problem with matplotlib.
The size assignment is good when displaying (plt.show()) a 2D or 3D visualization.
But does not happen if it's a 3D visualization during the save (Fig.savefig(...))
The easiest way is to show the result.
Have you an Idea ?
FILES :
2DVisualisationFile
3DVisualisationFile
PYTHON SCRIPT
# coding: utf8
import os
import numpy as np
from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import
import matplotlib.pyplot as plt
DPI = 150
FIG_SIZE = (8.60, 5.40)
mu = 1
sigma = 0.75
S = np.random.normal(mu, sigma, size=(1000,3))
# 2D Visualisation
Fig = plt.figure(figsize = FIG_SIZE, dpi = DPI)
ax = Fig.add_subplot(111)
ax.scatter(S[:,0], S[:,1], alpha=0.5)
print(Fig.get_size_inches())
plt.show()
Fig.savefig(os.path.dirname(__file__) + "/Samples_Test.png", transparent = False, bbox_inches = 'tight', dpi=DPI)
plt.close('all')
# 3D Visualisation
Fig = plt.figure(figsize = FIG_SIZE, dpi = DPI)
ax = Fig.add_subplot(111, projection='3d')
ax.scatter(S[:,0], S[:,1], S[:,2], alpha=0.5)
print(Fig.get_size_inches())
plt.show()
Fig.savefig(os.path.dirname(__file__) + "/Samples_Test2.png", transparent = False, bbox_inches = 'tight', dpi=DPI)
plt.close('all')
The problem is, that with bbox_inches = 'tight' the figure size can be changed during saving, see matplotlib.pyplot.savefig.
Instead use bbox_inches = None and the saved figure will have the correct size.
import matplotlib.pyplot as plt
FIG_SIZE = (6, 3)
fig = plt.figure(figsize = FIG_SIZE)
ax = fig.add_subplot(111, projection='3d')
ax.plot([0,1], [0,1], [0,1])
plt.show()
fig.savefig("bbox_inches_tight.png", bbox_inches = 'tight')
fig.savefig("bbox_inches_None.png", bbox_inches = None)
plt.close('all')

moving colorbar with gridspec

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

Categories

Resources