How to animate the colorbar in matplotlib
<p>I have an animation where the range of the data varies a lot. I would like to have a <code>colorbar</code> which tracks the max and the min of the data (i.e. I would like it not to be fixed). The question is how to do this.</p>

<p>Ideally I would like the <code>colorbar</code> to be on its own axis.</p>

<p>I have tried the following four things</p>

<h2>1. Naive approach</h2>

<p>The problem: A new colorbar is plottet for each frame</p>

<pre><code>#!/usr/bin/env python
"""
An animated image
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig = plt.figure()
ax = fig.add_subplot(111)


def f(x, y):
    return np.exp(x) + np.sin(y)

x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)

frames = []

for i in range(10):
    x       += 1
    curVals  = f(x, y)
    vmax     = np.max(curVals)
    vmin     = np.min(curVals)
    levels   = np.linspace(vmin, vmax, 200, endpoint = True)
    frame    = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
    cbar     = fig.colorbar(frame)
    frames.append(frame.collections)

ani = animation.ArtistAnimation(fig, frames, blit=False)

plt.show()
</code></pre>

<h2>2. Adding to the images</h2>

<p>Changing the for loop above to</p>

<pre><code>initFrame = ax.contourf(f(x,y)) 
cbar      = fig.colorbar(initFrame)
for i in range(10):
    x       += 1
    curVals  = f(x, y)
    vmax     = np.max(curVals)      
    vmin     = np.min(curVals)      
    levels   = np.linspace(vmin, vmax, 200, endpoint = True)
    frame    = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
    cbar.set_clim(vmin = vmin, vmax = vmax)
    cbar.draw_all()
    frames.append(frame.collections + [cbar])
</code></pre>

<p>The problem: This raises</p>

<pre><code>AttributeError: 'Colorbar' object has no attribute 'set_visible'
</code></pre>

<h2>3. Plotting on its own axis</h2>

<p>The problem: The <code>colorbar</code> is not updated.</p>

<pre><code> #!/usr/bin/env python
 """
 An animated image
 """
 import numpy as np
 import matplotlib.pyplot as plt
 import matplotlib.animation as animation

 fig = plt.figure()
 ax1 = fig.add_subplot(121)
 ax2 = fig.add_subplot(122)


 def f(x, y):
     return np.exp(x) + np.sin(y)

 x = np.linspace(0, 1, 120)
 y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)

 frames = []

 for i in range(10):
     x       += 1
     curVals  = f(x, y)
     vmax     = np.max(curVals)
     vmin     = np.min(curVals)
     levels   = np.linspace(vmin, vmax, 200, endpoint = True)
     frame    = ax1.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
     cbar     = fig.colorbar(frame, cax=ax2) # Colorbar does not update
     frames.append(frame.collections)

 ani = animation.ArtistAnimation(fig, frames, blit=False)

 plt.show()
</code></pre>

<h2>A combination of 2. and 4.</h2>

<p>The problem: The <code>colorbar</code> is constant.</p>

<p>A similar question is posted <a href="http://stackoverflow.com/questions/31562176/matplotlib-animation-with-changing-colorbar-and-title-for-each-frame">here</a>, but it looks like the OP is satisfied with a fixed <code>colorbar</code>.</p>

<p>While I'm not sure how to do this specifically using an <code>ArtistAnimation</code>, using a <code>FuncAnimation</code> is fairly straightforward.  If I make the following modifications to your "naive" version 1 it works.</p>

<p><strong>Modified Version 1</strong></p>

<pre><code>import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable

fig = plt.figure()
ax = fig.add_subplot(111)

# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')

def f(x, y):
    return np.exp(x) + np.sin(y)

x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)

frames = []
for i in range(10):
    x       += 1
    curVals  = f(x, y)
    frames.append(curVals)

cv0 = frames[0]
cf = ax.contourf(cv0, 200)
cb = fig.colorbar(cf, cax=cax)
tx = ax.set_title('Frame 0')

def animate(i):
    arr = frames[i]
    vmax     = np.max(arr)
    vmin     = np.min(arr)
    levels   = np.linspace(vmin, vmax, 200, endpoint = True)
    cf = ax.contourf(arr, vmax=vmax, vmin=vmin, levels=levels)
    cax.cla()
    fig.colorbar(cf, cax=cax)
    tx.set_text('Frame {0}'.format(i))

ani = animation.FuncAnimation(fig, animate, frames=10)

plt.show()
</code></pre>

<p>The main difference is that I do the levels calculations and contouring in a function instead of creating a list of artists.  The colorbar works because you can clear the axes from the previous frame and redo it every frame.</p>

<p>Doing this redo is necessary when using <code>contour</code> or <code>contourf</code>, because you can't just dynamically change the data.  However, as you have plotted so many contour levels and the result looks smooth, I think you may be better off using <code>imshow</code> instead - it means you can actually just use the same artist and change the data, and the colorbar updates itself automatically.  It's also much faster!</p>

<p><strong>Better Version</strong></p>

<pre><code>import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable

fig = plt.figure()
ax = fig.add_subplot(111)

# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')

def f(x, y):
    return np.exp(x) + np.sin(y)

x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)

# This is now a list of arrays rather than a list of artists
frames = []
for i in range(10):
    x       += 1
    curVals  = f(x, y)
    frames.append(curVals)

cv0 = frames[0]
im = ax.imshow(cv0, origin='lower') # Here make an AxesImage rather than contour
cb = fig.colorbar(im, cax=cax)
tx = ax.set_title('Frame 0')

def animate(i):
    arr = frames[i]
    vmax     = np.max(arr)
    vmin     = np.min(arr)
    im.set_data(arr)
    im.set_clim(vmin, vmax)
    tx.set_text('Frame {0}'.format(i))
    # In this version you don't have to do anything to the colorbar,
    # it updates itself when the mappable it watches (im) changes

ani = animation.FuncAnimation(fig, animate, frames=10)

plt.show()
</code></pre>

python animation matplotlib colorbar