14

I am trying to animate a pcolormesh in matplotlib. I have seen many of the examples using the package animation, most of them using a 1D plot routine, and some of them with imshow(). First, I wan to use the FuncAnimation routine. My problem is, first, that I do not know if I can initialize the plot

fig,ax = plt.subplots()
quad = ax.pcolormesh(X,Y,Z)

I have tried a few simple lines:

fig,ax = plt.subplots()
quad = ax.pcolormesh([])

def init():
    quad.set_array([])
    return quad,

def animate(ktime):  
    quad.set_array(X,Y,np.sin(Z)+ktime)
return quad,

anim = animation.FuncAnimation(fig,animate,init_func=init,frames=Ntime,interval=200,blit=True)

plt.show()

By the way, How do I set labels into and animated plot? Can I animate the title, if it is showing a number that changes in time? Thanks

Thomas Kühn
  • 9,412
  • 3
  • 47
  • 63
AlexNoir
  • 789
  • 3
  • 8
  • 20

4 Answers4

19

The problem was that I was wrongly using set_array() routine. It is very important to note that you must pass a 1D array to this routine. To do so, regarding that color, pcolormesh and so on usually plots multidimensional arrays, you should use .ravel() . One more important thing: In order to animate different plots at the same time, the blitz option at animate.FuncAnimation must be False (See section "Animating selected plot elements" of this link).

Here I post the code that simple program with various subplots:

import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as gridspec
import matplotlib.animation as animation

y, x = np.meshgrid(np.linspace(-10, 10,100), np.linspace(-10, 10,100))

z = np.sin(x)*np.sin(x)+np.sin(y)*np.sin(y)

v = np.linspace(-10, 10,100)
t = np.sin(v)*np.sin(v)
tt = np.cos(v)*np.cos(v)
###########

fig = plt.figure(figsize=(16, 8),facecolor='white')
gs = gridspec.GridSpec(5, 2)
ax1 = plt.subplot(gs[0,0])

line, = ax1.plot([],[],'b-.',linewidth=2)
ax1.set_xlim(-10,10)
ax1.set_ylim(0,1)
ax1.set_xlabel('time')
ax1.set_ylabel('amplitude')
ax1.set_title('Oscillationsssss')
time_text = ax1.text(0.02, 0.95, '', transform=ax1.transAxes)

#############################
ax2 = plt.subplot(gs[1:3,0])
quad1 = ax2.pcolormesh(x,y,z,shading='gouraud')
ax2.set_xlabel('time')
ax2.set_ylabel('amplitude')
cb2 = fig.colorbar(quad1,ax=ax2)

#########################
ax3 = plt.subplot(gs[3:,0])
quad2 = ax3.pcolormesh(x, y, z,shading='gouraud')
ax3.set_xlabel('time')
ax3.set_ylabel('amplitude')
cb3 = fig.colorbar(quad2,ax=ax3)

############################
ax4 = plt.subplot(gs[:,1])
line2, = ax4.plot(v,tt,'b',linewidth=2)
ax4.set_xlim(-10,10)
ax4.set_ylim(0,1)

def init():
    line.set_data([],[])
    line2.set_data([],[])
    quad1.set_array([])
    return line,line2,quad1

def animate(iter):
    t = np.sin(2*v-iter/(2*np.pi))*np.sin(2*v-iter/(2*np.pi))
    tt = np.cos(2*v-iter/(2*np.pi))*np.cos(2*v-iter/(2*np.pi))
    z = np.sin(x-iter/(2*np.pi))*np.sin(x-iter/(2*np.pi))+np.sin(y)*np.sin(y)
    line.set_data(v,t)
    quad1.set_array(z.ravel())
    line2.set_data(v,tt)
    return line,line2,quad1

gs.tight_layout(fig)

anim = animation.FuncAnimation(fig,animate,frames=100,interval=50,blit=False,repeat=False)
plt.show()

print 'Finished!!'
AlexNoir
  • 789
  • 3
  • 8
  • 20
  • Thanks Alex. For those have trouble... when I tried setting the quad data with `set_array([])` for my implementation I received an error, which was solved by passing an ndarray via `set_array(numpy.array([])`. I also outlined other issues I had in [my solution to this problem](http://stackoverflow.com/a/22714873/943773). – ryanjdillon Mar 28 '14 at 14:00
  • 1
    Note that your solution works only because you use shading='gouraud'. Without this parameter or with shading='flat' you need to add the line `z = z[:-1, :-1]`. – lumbric Jul 18 '15 at 10:54
  • Very nice working (also in 2022) example! I was wondering though, why are you even using an init function? All is working just fine without (it is not called, so it takes the values of the first frame as per documentation), and as you don't put specific values it seems a bit redundant. – Adriaan Jul 13 '22 at 09:37
10

There is an ugly detail you need to take care when using QuadMesh.set_array(). If you intantiate your QuadMesh with X, Y and C you can update the values C by using set_array(). But set_array does not support the same input as the constructor. Reading the source reveals that you need to pass a 1d-array and what is even more puzzling is that depending on the shading setting you might need to cut of your array C.

Edit: There is even a very old bug report about the confusing array size for shading='flat'.

That means:

Using QuadMesh.set_array() with shading = 'flat'

'flat' is default value for shading.

# preperation
import numpy as np
import matplotlib.pyplot as plt
plt.ion()
y = np.linspace(-10, 10, num=1000)
x = np.linspace(-10, 10, num=1000)
X, Y = np.meshgrid(x, y)
C = np.ones((1000, 1000)) * float('nan')

# intantiate empty plot (values = nan)
pcmesh = plt.pcolormesh(X, Y, C, vmin=-100, vmax=100, shading='flat')

# generate some new data
C = X * Y

# necessary for shading='flat'
C = C[:-1, :-1]

# ravel() converts C to a 1d-array
pcmesh.set_array(C.ravel())

# redraw to update plot with new data
plt.draw()

Looks like:

resulting graphic with shadig=flat

Note that if you omit C = C[:-1, :-1] your will get this broken graphic:

broken graphic with shading=flat

Using QuadMesh.set_array() with shading = 'gouraud'

# preperation (same as for 'flat')
import numpy as np
import matplotlib.pyplot as plt
plt.ion()
y = np.linspace(-10, 10, num=1000)
x = np.linspace(-10, 10, num=1000)
X, Y = np.meshgrid(x, y)
C = np.ones((1000, 1000)) * float('nan')

# intantiate empty plot (values = nan)
pcmesh = plt.pcolormesh(X, Y, C, vmin=-100, vmax=100, shading='gouraud')

# generate some new data
C = X * Y

# here no cut of of last row/column!

# ravel() converts C to a 1d-array
pcmesh.set_array(C.ravel())

# redraw to update plot with new data
plt.draw()

If you cut off the last row/column with shade='gouraud' you will get:

ValueError: total size of new array must be unchanged
lumbric
  • 7,644
  • 7
  • 42
  • 53
  • The reason for this behaviour is that in `pcolormesh` the x-y coordinates are the *quadrilateral corners* of the coloured areas [see documentation](https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.pcolormesh.html), so the dimensions of `x` and `y` should be one larger than the data, otherwise the last row and column of the data are (silently) ignored (also mentioned in the documentation). This is different for `gouraud`, because here you specify the values (colours) of the corners, so x,y and the data should all have the same dimensions. – Thomas Kühn Sep 10 '18 at 14:10
2

I am not sure why your quad = ax.pcolormesh(X,Y,Z) function is giving an error. Can you post the error?

Below is what I would do to create a simple animation using pcolormesh:

import matplotlib.pyplot as plt
import numpy as np

y, x = np.meshgrid(np.linspace(-3, 3,100), np.linspace(-3, 3,100))

z = np.sin(x**2+y**2)
z = z[:-1, :-1]

ax = plt.subplot(111)

quad = plt.pcolormesh(x, y, z)

plt.colorbar()

plt.ion()
plt.show()

for phase in np.linspace(0,10*np.pi,200):
    z = np.sin(np.sqrt(x**2+y**2) + phase)
    z = z[:-1, :-1]

    quad.set_array(z.ravel())
    plt.title('Phase: %.2f'%phase)
    plt.draw()

plt.ioff()
plt.show()

One of the frames: enter image description here

Does this help? If not, maybe you can clarify the question.

DanHickstein
  • 6,588
  • 13
  • 54
  • 90
  • Thank you. Your answer is helpful. It is the way I tried first (iterating the plot inside a loop), but then I saw people recommending the animation package of pyplot. I see that you use pot.ion() and pot.ioff(). You also use `set_array(z.ravel())` with one argument, z, not x,y,z. What is ravel() ? – AlexNoir Sep 14 '13 at 11:44
  • 1
    Ravel takes the 2D array and makes it 1D: http://docs.scipy.org/doc/numpy/reference/generated/numpy.ravel.html – DanHickstein Sep 14 '13 at 15:57
  • And are you saying that you want to update the quad object with X,Y,and Z for each iteration? I was assuming that X and Y were staying the same. I'm not sure if you can update all at once. An alternate approach might be to use scipy.interpolate.griddata to generate a regular grid which could be plotted using imshow. – DanHickstein Sep 14 '13 at 16:55
  • Thank you, you solve my problem. At the end, all I need is to add `set_array(z.ravel())` , because I did not know that `set_array()` only accepts 1D arrays! I am going to post my solution using FuncAnimation. – AlexNoir Sep 15 '13 at 01:11
0

There is another answer presented here that looks simpler thus better (IMHO)

Here is a copy & paste of the alternative solution :

import matplotlib.pylab as plt
from matplotlib import animation

fig = plt.figure()

plt.hold(True)
#We need to prime the pump, so to speak and create a quadmesh for plt to work with
plt.pcolormesh(X[0:1], Y[0:1], C[0:1])

anim = animation.FuncAnimation(fig, animate, frames = range(2,155), blit = False)

plt.show()
plt.hold(False)

def animate( self, i):
    plt.title('Ray: %.2f'%i)
    #This is where new data is inserted into the plot.
    plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])
Gael Lorieul
  • 3,006
  • 4
  • 25
  • 50