Set Matplotlib colorbar size to match graph
Asked Answered
N

11

276

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? Example of the colorbar size mismatch

Natashianatassia answered 12/8, 2013 at 20:6 Comment(4)
Have you tried the suggestions from #16702979Photographic
@imjohns3 Nothing in that post seems to do anything to the color bar. It stays the same size no matter what I set. If I set fraction and shrink, though, the size of the graph will change while the color bar stays the same, until we get back to what I have already, then they stop doing anything.Natashianatassia
Check out the docs -- matplotlib.org/api/colorbar_api.html -- and use fraction or shrink args.Guard
Can you use pcolormesh instead of imshow?Retinoscopy
L
286

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)

enter image description here

Launcelot answered 12/8, 2013 at 20:15 Comment(12)
I am not working inside a subplot, so this is not applicable.Natashianatassia
After much tinkering, got it to work. Has a lot of trouble interacting with subplot_adjust, you have to get the calls in just the right places relative to each other.Natashianatassia
This is slightly changing the sizing of the graphs. I have 4 in a 2x2 grid, but only want the two on the right to have bars (scale applies by row). However this makes them not the same size. I tried just not having the colorbar call (with the divider call), but of course this leaves an empty white box and numbers on the side. How do I get them to have a consistent size without putting bars on all of them?Natashianatassia
@Launcelot Unfortunately this does not work with projected axesApnea
If you want to add a title using plt.title, it will be displayed swaped to the right. Is there a way to overcome this?Junji
I'm not aware of a way to automatically re-center the title but plt.title returns a matplotlib.text.Text object, which has get_position and set_position methods. So based on the parameters you give the colorbar axis, you can adjust the title position accordingly.Launcelot
Neat and robust. Salute!Patricapatrice
@user2820579, you could just call the plt.title before adding the colorbar. That will center the title on the main figure at least ...Eire
just remember to put the titles (if you want) right after call imshow, otherwise they will be in the colorbar.Lysander
Does not work. Colorbar extends over the entire chart. :( matplotlib 3.0.0.sth)Chibcha
The answer was working beautifully until I tried to adapt it for use with subplots. Now returns 'AxesSubplot' object has no attribute 'get_array'. Is there a way to get this to work with subplots?Ashley
For reference, this is the example shown in the matplotlib tight_layout guide (at the very bottom).Cherlynchernow
P
342

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.

Punctate answered 3/11, 2014 at 18:18 Comment(9)
This may work in some cases, but in general it doesn't. Try, e.g., plotting something like in the original question, which has a width twice the height.Juryrig
The fraction option still seems to work in the case you mention, if scaled to match the height of the plot. (mpl v1.4.3)Punctate
This is the only universal way of doing it. The solutions with axex_grid1 will not work for projected axes such as GeoAxes.Apnea
In response to @Juryrig comment. You can correct for the case where image is too wide using this trick: im_ratio = data.shape[0]/data.shape[1] plt.colorbar(im,fraction=0.046*im_ratio, pad=0.04) where data is your image.Spearman
@Spearman This only seems to work for aspect ratios > 1. If your picture is wider, e.g. an aspect ratio of 0.4, these magic numbers don't work anymore.Carreno
Doesn't work for me, still gives a long colorbar for wider images!Biddable
The shrink keyword argument, which defaults to 1.0, may also be useful for further fine tuned adjustments. I found that shrink=0.9 helped get it just right when I had two square subplots side by side.Hah
This solution is an approximation at best; you can create a precisely-sized extra axis for the colorbar using the other answer: https://mcmap.net/q/21707/-set-matplotlib-colorbar-size-to-match-graphAlluvial
The increase in significant figures to plt.colorbar(fraction=0.0458, pad=0.04) works slightly better for me.Earsplitting
L
286

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)

enter image description here

Launcelot answered 12/8, 2013 at 20:15 Comment(12)
I am not working inside a subplot, so this is not applicable.Natashianatassia
After much tinkering, got it to work. Has a lot of trouble interacting with subplot_adjust, you have to get the calls in just the right places relative to each other.Natashianatassia
This is slightly changing the sizing of the graphs. I have 4 in a 2x2 grid, but only want the two on the right to have bars (scale applies by row). However this makes them not the same size. I tried just not having the colorbar call (with the divider call), but of course this leaves an empty white box and numbers on the side. How do I get them to have a consistent size without putting bars on all of them?Natashianatassia
@Launcelot Unfortunately this does not work with projected axesApnea
If you want to add a title using plt.title, it will be displayed swaped to the right. Is there a way to overcome this?Junji
I'm not aware of a way to automatically re-center the title but plt.title returns a matplotlib.text.Text object, which has get_position and set_position methods. So based on the parameters you give the colorbar axis, you can adjust the title position accordingly.Launcelot
Neat and robust. Salute!Patricapatrice
@user2820579, you could just call the plt.title before adding the colorbar. That will center the title on the main figure at least ...Eire
just remember to put the titles (if you want) right after call imshow, otherwise they will be in the colorbar.Lysander
Does not work. Colorbar extends over the entire chart. :( matplotlib 3.0.0.sth)Chibcha
The answer was working beautifully until I tried to adapt it for use with subplots. Now returns 'AxesSubplot' object has no attribute 'get_array'. Is there a way to get this to work with subplots?Ashley
For reference, this is the example shown in the matplotlib tight_layout guide (at the very bottom).Cherlynchernow
M
80

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.

Code

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)

Result

enter image description here

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.

Code

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)

Result

enter image description here

Again, you can also achieve similar effects by specifying cax, a more accurate way from my perspective.

Code

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)

Result

enter image description here

Mencher answered 5/7, 2019 at 10:1 Comment(6)
Really amazing answer. Thank you!!Hoodlum
This is the only answer I found that worked! All the other questions I found that seem to fix everyones issue with the size of colorbar didn't work for me. In my case the plot was too long and the colorbar was too short. This fixed it. Thanks!Trinitroglycerin
Works great for me, I just wonder how I can make it work properly with fullscreen images.Rabbitfish
IMO this is the correct answer, as it entirely bypasses Matplotlib's non-configurable axis-adjusting logic. It's really frustrating that "make the colorbar the same height as the ax= axis" is not a feature, and requires dropping down to low-level figure/axis fiddling like this.Alluvial
The Matplotlib docs suggest using ax.inset_axes to create a "child axis" instead of creating a standalone axis with fig.add_axes: matplotlib.org/stable/gallery/subplots_axes_and_figures/…Alluvial
this will break if the different axis have title right? I mean, it would not be a colorbar with the same height as the plot but with the same hight as the axes (plot + title)Essentialism
J
47

@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.

image plot with colorbar

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)
Juryrig answered 3/11, 2015 at 17:30 Comment(2)
This is a really helpful little function! One word of warning is that it does not work when you want to add multiple colorbars, because they appear on top of each other.Augustineaugustinian
Fantastic answer. With regards to the problem mentioned by @DavidHall to make it work on multiple subplots, just replace current_ax with the axes of the subplot you want to add the color bar to.Indebtedness
M
16

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

Minim answered 12/8, 2013 at 20:23 Comment(3)
If I set shrink 1.0 and fraction to anything, it shrinks the graph, not affecting the colorbar size at all, until changing fraction causes it to be exactly what I already have, at which point changing them stops doing anything.Natashianatassia
Where exactly are you specifying them they have to be parameters to the colorbar() function or method.Minim
Thanks. just need to specify the shrink parameter and it works like magic!Numerate
A
16

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.

Apnea answered 9/10, 2016 at 20:39 Comment(2)
We can also add shrink parameter when fraction together with pad do not produce desired results enough.Mencher
The neatest answer.Galling
G
14

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.

Gsuit answered 3/6, 2021 at 4:3 Comment(1)
This was the fastest road to Rome for my one off plot, and I appreciate it!Prima
V
2

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.

Vermin answered 5/9, 2022 at 13:57 Comment(0)
C
1

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)

result

Caravansary answered 1/6, 2023 at 4:59 Comment(0)
A
0

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.

Armstrong answered 18/8, 2022 at 1:13 Comment(0)
C
0

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

mpl_toolkits.axes_grid1.ImageGrid fixed aspect image with matching colorbar

Constance answered 2/6, 2023 at 12:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.