Reputation: 5541
I cannot get the colorbar
on imshow
graphs like this one to be the same height as the graph, short of using Photoshop after the fact. How do I get the heights to match?
Upvotes: 287
Views: 340628
Reputation: 609
The answers provided so far are very helpful, but they may have shortcomings depending on the situation. For example, they:
fig.tight_layout()
(4).An alternative that avoids the above shortcomings is to add an inset/child axis via ax.inset_axes
:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 1, 70)
y = np.linspace(0, 1, 100)
data = (np.stack(np.meshgrid(x, y)) ** 2).sum(0)
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
# specify lower-left corner, width and height of the colorbar,
# in coordinates relative to its parent axis
cax = axs[1].inset_axes((1.05, 0, 0.08, 1.0))
fig.colorbar(hm, cax=cax)
fig.suptitle(r"$\bf{Add~axis~with~relative~(axis)~coordinates}$", y=0.87)
fig.tight_layout()
With this alternative, the coordinates and size of the colorbar are relative to its parent axis, ensuring predictable results regardless of the parent axis's shape or the arrangement of other subplots. Result:
These are the results obtained with the most upvoted answers so far (code to reproduce is at the end).
Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
plt.close("all")
x = np.linspace(0, 1, 70)
y = np.linspace(0, 1, 100)
data = (np.stack(np.meshgrid(x, y)) ** 2).sum(0)
# using Axes divider
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
divider = make_axes_locatable(axs[1])
cax = divider.append_axes("right", size="5%", pad=0.05)
fig.colorbar(hm, cax=cax)
fig.suptitle(r"$\bf{Axes~divider}$" + "\nsteals axis space", y=0.92)
fig.tight_layout()
# using fraction + pad
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
fig.colorbar(hm, ax=axs[1], fraction=0.046, pad=0.04)
fig.suptitle(
r"$\bf{fraction+pad}$" + "\nsteals axis space + trial and error positioning", y=0.92
)
fig.tight_layout()
# adding axis in absolute (figure) coordinates
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
cax = fig.add_axes(
(
axs[1].get_position().x1 + 0.01,
axs[1].get_position().y0,
0.02,
axs[1].get_position().y1 - axs[1].get_position().y0,
),
)
fig.colorbar(hm, cax=cax)
fig.suptitle(
r"$\bf{Add~axis~with~Figure~coordinates}$"
+ "\nTrial and error + hard to predict after tight_layout",
y=1.02,
)
fig.tight_layout()
# using shrink=0.7, aspect=20*0.7
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
fig.colorbar(hm, ax=axs[1], shrink=0.7, aspect=20 * 0.7)
fig.suptitle(
r"$\bf{shrink=0.7,~aspect=20*0.7}$" + "\nsteals axis space + trial and error",
y=0.92,
)
fig.tight_layout()
# adding axis in relative (axis) coordinates
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
cax = axs[1].inset_axes((1.05, 0, 0.08, 1.0))
fig.colorbar(hm, cax=cax)
fig.suptitle(r"$\bf{Add~axis~with~relative~(axis)~coordinates}$", y=0.87)
fig.tight_layout()
Upvotes: 1
Reputation: 29
The best fix to this problem I found is explained in detail in this page.
Basically, once you initiate figure:
fig, myplot = plt.subplots((1,1), figsize = (12,10), layout = 'constrained')
supplying the argument:
layout = 'constrained'
worked for me.
Upvotes: -1
Reputation: 611
For these types of plots I like the ImageGrid
API from mpl_toolkits.axes_grid1
. It's designed for managing multiple fixed aspect plots, but works just fine for a single image.
from matplotlib import pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
fig = plt.figure()
plot = ImageGrid(fig, 111, (1, 1),
cbar_mode='single',
cbar_location='right',
cbar_size='3%',
cbar_pad='5%')
im = plot[0].imshow(np.random.randn(2**4, 2**6))
cbar = fig.colorbar(im, cax=plot.cbar_axes[0])
Upvotes: 0
Reputation: 23021
axes_grid1.axes_divider
is the prescribed method for this task (matplotlib even has a demo) but by adding the colorbar, it makes the image smaller. If you want to retain the original image size, then the following offers one way (based on Fei Yao's answer).
data = [(1,2,3,4,5),(4,5,6,7,8),(7,8,9,10,11)]
im = plt.imshow(data, cmap='RdBu')
l, b, w, h = plt.gca().get_position().bounds
cax = plt.gcf().add_axes([l + w + 0.03, b, 0.03, h])
plt.colorbar(im, cax=cax)
A convenient function wrapper.
import matplotlib.pyplot as plt
def add_colorbar(im, width=None, pad=None, **kwargs):
l, b, w, h = im.axes.get_position().bounds # get boundaries
width = width or 0.1 * w # get width of the colorbar
pad = pad or width # get pad between im and cbar
fig = im.axes.figure # get figure of image
cax = fig.add_axes([l + w + pad, b, width, h]) # define cbar Axes
return fig.colorbar(im, cax=cax, **kwargs) # draw cbar
data = [(1,2,3,4,5),(4,5,6,7,8),(7,8,9,10,11)]
# an example usage
im = plt.imshow(data, cmap='RdBu')
add_colorbar(im)
Upvotes: 2
Reputation: 94
I encountered this problem recently, I used ax.twinx()
to solve it. For example:
from matplotlib import pyplot as plt
# Some other code you've written
...
# Your data generation goes here
xdata = ...
ydata = ...
colordata = function(xdata, ydata)
# Your plotting stuff begins here
fig, ax = plt.subplots(1)
im = ax.scatterplot(xdata, ydata, c=colordata)
# Create a new axis which will be the parent for the colour bar
# Note that this solution is independent of the 'fig' object
ax2 = ax.twinx()
ax2.tick_params(which="both", right=False, labelright=False)
# Add the colour bar itself
plt.colorbar(im, ax=ax2)
# More of your code
...
plt.show()
I found this particularly useful when creating functions that take in matplotlib Axes
objects as arguments, draw on them, and return the object because I then don't need to pass in a separate axis I had to generate from the figure
object, or pass the figure
object itself.
Upvotes: 1
Reputation: 19
If you don't want to declare another set of axes, the simplest solution I have found is changing the figure size with the figsize call.
In the above example, I would start with
fig = plt.figure(figsize = (12,6))
and then just re-render with different proportions until the colorbar no longer dwarfs the main plot.
Upvotes: -1
Reputation: 19169
You can do this easily with a matplotlib AxisDivider.
The example from the linked page also works without using subplots:
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)))
# 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)
Upvotes: 294
Reputation: 584
An alternative is
shrink=0.7, aspect=20*0.7
shrink
scales the height and width, but the aspect
argument restores the original width. Default aspect ratio is 20. The 0.7
is empirically determined.
Upvotes: 15
Reputation: 1742
I appreciate all the answers above. However, like some answers and comments pointed out, the axes_grid1
module cannot address GeoAxes, whereas adjusting fraction
, pad
, shrink
, and other similar parameters cannot necessarily give the very precise order, which really bothers me. I believe that giving the colorbar
its own axes
might be a better solution to address all the issues that have been mentioned.
import matplotlib.pyplot as plt
import numpy as np
fig=plt.figure()
ax = plt.axes()
im = ax.imshow(np.arange(100).reshape((10,10)))
# Create an axes for colorbar. The position of the axes is calculated based on the position of ax.
# You can change 0.01 to adjust the distance between the main image and the colorbar.
# You can change 0.02 to adjust the width of the colorbar.
# This practice is universal for both subplots and GeoAxes.
cax = fig.add_axes([ax.get_position().x1+0.01,ax.get_position().y0,0.02,ax.get_position().height])
plt.colorbar(im, cax=cax) # Similar to fig.colorbar(im, cax = cax)
Later on, I find matplotlib.pyplot.colorbar
official documentation also gives ax
option, which are existing axes that will provide room for the colorbar. Therefore, it is useful for multiple subplots, see following.
fig, ax = plt.subplots(2,1,figsize=(12,8)) # Caution, figsize will also influence positions.
im1 = ax[0].imshow(np.arange(100).reshape((10,10)), vmin = -100, vmax =100)
im2 = ax[1].imshow(np.arange(-100,0).reshape((10,10)), vmin = -100, vmax =100)
fig.colorbar(im1, ax=ax)
Again, you can also achieve similar effects by specifying cax, a more accurate way from my perspective.
fig, ax = plt.subplots(2,1,figsize=(12,8))
im1 = ax[0].imshow(np.arange(100).reshape((10,10)), vmin = -100, vmax =100)
im2 = ax[1].imshow(np.arange(-100,0).reshape((10,10)), vmin = -100, vmax =100)
cax = fig.add_axes([ax[1].get_position().x1-0.25,ax[1].get_position().y0,0.02,ax[0].get_position().y1-ax[1].get_position().y0])
fig.colorbar(im1, cax=cax)
Upvotes: 88
Reputation: 4894
@bogatron already gave the answer suggested by the matplotlib docs, which produces the right height, but it introduces a different problem. Now the width of the colorbar (as well as the space between colorbar and plot) changes with the width of the plot. In other words, the aspect ratio of the colorbar is not fixed anymore.
To get both the right height and a given aspect ratio, you have to dig a bit deeper into the mysterious axes_grid1
module.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
import numpy as np
aspect = 20
pad_fraction = 0.5
ax = plt.gca()
im = ax.imshow(np.arange(200).reshape((20, 10)))
divider = make_axes_locatable(ax)
width = axes_size.AxesY(ax, aspect=1./aspect)
pad = axes_size.Fraction(pad_fraction, width)
cax = divider.append_axes("right", size=width, pad=pad)
plt.colorbar(im, cax=cax)
Note that this specifies the width of the colorbar w.r.t. the height of the plot (in contrast to the width of the figure, as it was before).
The spacing between colorbar and plot can now be specified as a fraction of the width of the colorbar, which is IMHO a much more meaningful number than a fraction of the figure width.
UPDATE:
I created an IPython notebook on the topic, where I packed the above code into an easily re-usable function:
import matplotlib.pyplot as plt
from mpl_toolkits import axes_grid1
def add_colorbar(im, aspect=20, pad_fraction=0.5, **kwargs):
"""Add a vertical color bar to an image plot."""
divider = axes_grid1.make_axes_locatable(im.axes)
width = axes_grid1.axes_size.AxesY(im.axes, aspect=1./aspect)
pad = axes_grid1.axes_size.Fraction(pad_fraction, width)
current_ax = plt.gca()
cax = divider.append_axes("right", size=width, pad=pad)
plt.sca(current_ax)
return im.axes.figure.colorbar(im, cax=cax, **kwargs)
It can be used like this:
im = plt.imshow(np.arange(200).reshape((20, 10)))
add_colorbar(im)
Upvotes: 49
Reputation: 601
All the above solutions are good, but I like @Steve's and @bejota's the best as they do not involve fancy calls and are universal.
By universal I mean that works with any type of axes including GeoAxes
. For example, it you have projected axes for mapping:
projection = cartopy.crs.UTM(zone='17N')
ax = plt.axes(projection=projection)
im = ax.imshow(np.arange(200).reshape((20, 10)))
a call to
cax = divider.append_axes("right", size=width, pad=pad)
will fail with: KeyException: map_projection
Therefore, the only universal way of dealing colorbar size with all types of axes is:
ax.colorbar(im, fraction=0.046, pad=0.04)
Work with fraction from 0.035 to 0.046 to get your best size. However, the values for the fraction and paddig will need to be adjusted to get the best fit for your plot and will differ depending if the orientation of the colorbar is in vertical position or horizontal.
Upvotes: 16
Reputation: 4479
This combination (and values near to these) seems to "magically" work for me to keep the colorbar scaled to the plot, no matter what size the display.
plt.colorbar(im,fraction=0.046, pad=0.04)
It also does not require sharing the axis which can get the plot out of square.
Upvotes: 349
Reputation: 28370
When you create the colorbar
try using the fraction and/or shrink parameters.
From the documents:
fraction 0.15; fraction of original axes to use for colorbar
shrink 1.0; fraction by which to shrink the colorbar
Upvotes: 16