matplotlib imshow fixed aspect and vertical colorbar matching master axis height
Question:
I need to plot a mesh grid with “temperature map” values, currently I’m using imshow
, with a colormap. This is described in the Matplotlib overview, so I modified the example to force custom aspect of the figure:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
plt.figure()
ax = plt.gca()
im = ax.imshow(np.arange(100).reshape((10,10)), aspect=0.5)
# create an axes on the right side of ax. The width of cax will be 5%
# of ax and the padding between cax and ax will be fixed at 0.05 inch.
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(im, cax=cax)
plt.savefig("test.png")
But the result is not what I want, the colorbar is higher than the master axis:
Interestingly, when the colormap is horizontal, it is scaled properly:
cax = divider.append_axes("bottom", size="5%", pad=0.05)
plt.colorbar(im, cax=cax, orientation="horizontal")
Answers:
What happens here is that you applied an aspect of 0.5 to the imshow image. This divides the vertical extend of your image by 2, while the colorbar keeps the original extent. I see 2 solutions
you can manually set the size of the colorbar using:
cax = fig.add_axes([0.85, 0.3, 0.04, 0.4])
…or you can apply an aspect to cax to keep its y-dimension consequent with the image. In your case as you set size to 5%, setting aspect=1 would give you an image with 1/20 of the original vertical-extent. To obtain 1/2 as for the image set aspect to 20*0.5= 10. You could create a variable for aspect, if you want to experiment with changing the aspect on the figure, the colorbar will follow.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
fig = plt.figure()
ax = plt.gca()
im = ax.imshow(np.arange(100).reshape((10,10)), aspect=0.5)
# create an axes on the right side of ax. The width of cax will be 5%
# of ax and the padding between cax and ax will be fixed at 0.05 inch.
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05, aspect=10)
#cax = fig.add_axes([0.85, 0.3, 0.04, 0.4])
plt.colorbar(im, cax=cax)
plt.show()
You can match the size of the colorbar to the size of the axes containing your data using the following snippet.
def match_colorbar(cb, ax=None):
"""
Match the size of the colorbar with the size of the axes.
Args:
ax: Axes from which the colorbar "stole" space.
cb: Colorbar to match to `ax`.
"""
ax = ax or plt.gca()
bbox = ax.get_position()
cb_bbox = cb.ax.get_position()
if cb.orientation == "vertical":
# Update bottom and height.
left = cb_bbox.xmin
width = cb_bbox.width
bottom = bbox.ymin
height = bbox.height
else:
# Update left and width.
left = bbox.xmin
width = bbox.width
bottom = cb_bbox.ymin
height = cb_bbox.height
pos = [left, bottom, width, height]
cb.ax.set_position(pos)
Here is an example.
fig, ax = plt.subplots()
im = ax.imshow(np.arange(100).reshape((10,10)), aspect=0.5)
cb = fig.colorbar(im)
match_colorbar(cb)
I need to plot a mesh grid with “temperature map” values, currently I’m using imshow
, with a colormap. This is described in the Matplotlib overview, so I modified the example to force custom aspect of the figure:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
plt.figure()
ax = plt.gca()
im = ax.imshow(np.arange(100).reshape((10,10)), aspect=0.5)
# create an axes on the right side of ax. The width of cax will be 5%
# of ax and the padding between cax and ax will be fixed at 0.05 inch.
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(im, cax=cax)
plt.savefig("test.png")
But the result is not what I want, the colorbar is higher than the master axis:
Interestingly, when the colormap is horizontal, it is scaled properly:
cax = divider.append_axes("bottom", size="5%", pad=0.05)
plt.colorbar(im, cax=cax, orientation="horizontal")
What happens here is that you applied an aspect of 0.5 to the imshow image. This divides the vertical extend of your image by 2, while the colorbar keeps the original extent. I see 2 solutions
you can manually set the size of the colorbar using:
cax = fig.add_axes([0.85, 0.3, 0.04, 0.4])
…or you can apply an aspect to cax to keep its y-dimension consequent with the image. In your case as you set size to 5%, setting aspect=1 would give you an image with 1/20 of the original vertical-extent. To obtain 1/2 as for the image set aspect to 20*0.5= 10. You could create a variable for aspect, if you want to experiment with changing the aspect on the figure, the colorbar will follow.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
fig = plt.figure()
ax = plt.gca()
im = ax.imshow(np.arange(100).reshape((10,10)), aspect=0.5)
# create an axes on the right side of ax. The width of cax will be 5%
# of ax and the padding between cax and ax will be fixed at 0.05 inch.
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05, aspect=10)
#cax = fig.add_axes([0.85, 0.3, 0.04, 0.4])
plt.colorbar(im, cax=cax)
plt.show()
You can match the size of the colorbar to the size of the axes containing your data using the following snippet.
def match_colorbar(cb, ax=None):
"""
Match the size of the colorbar with the size of the axes.
Args:
ax: Axes from which the colorbar "stole" space.
cb: Colorbar to match to `ax`.
"""
ax = ax or plt.gca()
bbox = ax.get_position()
cb_bbox = cb.ax.get_position()
if cb.orientation == "vertical":
# Update bottom and height.
left = cb_bbox.xmin
width = cb_bbox.width
bottom = bbox.ymin
height = bbox.height
else:
# Update left and width.
left = bbox.xmin
width = bbox.width
bottom = cb_bbox.ymin
height = cb_bbox.height
pos = [left, bottom, width, height]
cb.ax.set_position(pos)
Here is an example.
fig, ax = plt.subplots()
im = ax.imshow(np.arange(100).reshape((10,10)), aspect=0.5)
cb = fig.colorbar(im)
match_colorbar(cb)