35

all. I want to update the colorbar of a figure when the imagedata is changed. So something like:

img = misc.lena()
fig = plt.figure()
ax = plt.imshow(im)
plt.colorbar(ax)
newimg = img+10*np.randn(512,512)

def update_colorbar(fig,ax,newimg):
    cbar = fig.axes[1]
    ax.set_data(newimg)
    cbar.update_normal(ax)
    plt.draw()

but it seems that returned results from fig.axes() does not have the colorbar instance like I expected. I can probably just pass the colorbar instance as an argument to the update function, but I thought just passing one fig parameter may be good enough. Can anyone explain a little bit on how to retrieve the colorbar from the figure? Or why 'fig.axes()' doesn't return the AxesImage or Colobar instance but just the Axes or AxesSubplot? I think I just need more understanding of the Axes/Figure stuff.Thank you!

Wins
  • 377
  • 1
  • 3
  • 9

2 Answers2

56

Sometimes it can be useful to retrieve a colorbar even if it was not held in a variable.

In this case, it is possible to retrieve the colorbar from the plot with:

# Create an example image and colourbar
img = np.arange(20).reshape(5,4)
plt.imshow(img)
plt.colorbar()

# Get the current axis 
ax = plt.gca()        

# Get the images on an axis
im = ax.images        

# Assume colorbar was plotted last one plotted last
cb = im[-1].colorbar   

# Do any actions on the colorbar object (e.g. remove it)
cb.remove()

EDIT:

or, equivalently, the one liner:

plt.gca().images[-1].colorbar.remove()

N.B.: see also comments for the use of ax.collections[-1] instead of ax.images[-1]. For me it always worked only the first way, I don't know what depends on, maybe the type of data or plot.


Now you can operate on cb as if it were stored using commands described in the colorbar API. For instance you could change xlim or call update as explained in other comments. You could remove it with cb.remove() and recreate it with plt.colorbar().

plt.draw() or show should be called after to update plot.

As the image is the mappable associated to the colorbar and can be obtained with cb.mappable.

Vincenzooo
  • 2,013
  • 1
  • 19
  • 33
  • 2
    Are there any problems with this solution (why downvote)? It seems that this answers the question exactly. – aplavin Jan 08 '16 at 14:20
  • 12
    In my case `ax.images` is an empty list, even though there's a color bar there. – DanielSank Mar 03 '16 at 18:41
  • 1
    hard to say without seeing the code, but in my opinion this can happen only if (either one): 1) you are not retrieving the correct axes; 2) you didn't plot any image (e.g. you made a scatter plot or drawn the colorbar without image). – Vincenzooo Mar 03 '16 at 21:55
  • 16
    @DanielSank I had the same problem, but was able to use `ax.collections[-1].colorbar` instead – Matthias May 08 '19 at 08:38
  • 5
    ``ax.collections[-1].colorbar`` also worked for me - with a hexbin created through pandas. – Pietro Battiston Apr 09 '20 at 13:39
29

First off, I think you're getting a bit confused between the axes (basically, the plot), the figure, the scalar mappable (the image, in this case), and the colorbar instance.

The figure is the window that the plot is in. It's the top-level container.

Each figure usually has one or more axes. These are the plots/subplots.

Colorbars are also inside the figure. Adding a colorbar creates a new axes (unless you specify otherwise) for the colorbar to be displayed in. (It can't normally be displayed in the same axes as the image, because the colorbar needs to have its own x and y limits, etc.)

Some of your confusion is due to the fact that you're mixing the state-machine interface and the OO interface. It's fine to do this, but you need to understand the OO interface.

fig.axes[1] isn't the colorbar instance. It's the axes that the colorbar is plotted in. (Also, fig.axes[1] is just the second axes in the figure. It happens to be the axes that the colorbar is in for a figure with one subplot and one colorbar, but that won't generally be the case.)

If you want to update the colorbar, you'll need to hold on to the colorbar instance that colorbar returns.

Here's an example of how you'd normally approach things:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.random((10,10)) # Generate some random data to plot

fig, ax = plt.subplots() # Create a figure with a single axes.
im = ax.imshow(data)     # Display the image data
cbar = fig.colorbar(im)  # Add a colorbar to the figure based on the image

If you're going to use update_normal to update the colorbar, it expects a ScalarMappable (e.g. an image created by imshow, the collection that scatter creates, the ContourSet that contour creates, etc) to be passed in. (There are other ways to do it, as well. Often you just want to update the limits, rather than the whole thing.) In the case of the code above, you'd call cbar.update_normal(im).

However, you haven't created a new AxesImage, you've just changed it's data. Therefore, you probably just want to do:

cbar.set_clim(newimg.min(), newimg.max())
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Thanks! That makes things much clearer! So it means once the colorbar or plot is placed onto the `axes`, the `axes` won't hold those instances inside it? One more thing I am curious is that can I add `axes` to `axes`, just to group some plots together? Thanks again! – Wins Nov 06 '13 at 16:55
  • Well, the `axes` holds all of its "child" artists (e.g. anything plotted on it), but a colorbar is usually plotted on a separate axes. Does that help? I'm confused by your second question... If you want to "group" plots, just plot them on the same axes. You can add axes on top of each other (this is what things like `twinx` do), but that's probably not what you want to do. – Joe Kington Nov 06 '13 at 16:57
  • 1
    Yeah, it is really helpful! @Joe. I understand holding the colorbar instance to update is probably the best way to do things, I am just wondering if I can retrieve the object by calling the `get_children()` of the `axes` the colorbar is plotted onto. For the second question, sorry I didn't make it quite clear. I am thinking of manipulating several plots together, like moving or shrinking the image axes and the colorbar axes together as a group while maintaining their relative position/size, etc. Thanks for your time! – Wins Nov 07 '13 at 18:34
  • 3
    Yes, how does one retrieve a color bar after it's been made? Supposing I have the axes onto which the color bar was drawn, how does one get that color bar? – DanielSank Mar 03 '16 at 18:40
  • 5
    @DanielSank - The colorbar is linked to the artist it was created from, as opposed to a particular axes. If you have the artist (e.g. `im` in the example above) the colorbar will be `im.colorbar`. Of course, it's better to save a reference to the colorbar directly when it's created, but that's not always possible. At any rate, if you're using the `pyplot` interface, you can use `plt.gci().colorbar` to get the most recently created colorbar. If you're not, the equivalent is `fig._gci().colorbar`. – Joe Kington Mar 03 '16 at 19:24