I am trying to use matplotlib.ArtistAnimation to animate two subplots. I want the x-axis to increase in value as the animation progresses, such that the total length of the animation is 100 but at any time the subplot is only presenting me with the time values from 0-24 and then iterates up to 100.
A great example is given here. The link uses FuncAnimation and updates the x-axis labels in a rolling fashion using plot().axes.set_xlim() and incrementing the x-values. The code is available via the link below the YouTube video in the link provided.
I have appended code below that shows my attempts to replicate these results but the x-limits seem to take on their final values instead of incrementing with time. I have also tried incrementing the solution (as opposed to the axis) by only plotting the values in the window that will be seen in the subplot, but that does not increment the x-axis values. I also tried to implement autoscaling but the x-axis still does not update.
I also found this question which is virtually the same problem, but the question was never answered.
Here is my code:
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1=fig.add_subplot(1,2,1)
ax2=fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
ims=[]
for time in xrange(np.shape(image)[0]):
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
lim = ax2.set_xlim(time-repeat_length,time)
ims.append([im, im2])
#run animation
ani = anim.ArtistAnimation(fig,ims, interval=50,blit=False)
plt.show()
I only want the second subplot (ax2) to update the x-axis values.
Any help would be much appreciated.
If you don't need blitting
import matplotlib.pylab as plt
import matplotlib.animation as animation
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
im = ax1.imshow(image[0,:,:])
im2, = ax2.plot([], [], color=(0,0,1))
def func(n):
im.set_data(image[n,:,:])
im2.set_xdata(np.arange(n))
im2.set_ydata(image[0:n, 5, 5])
if n>repeat_length:
lim = ax2.set_xlim(n-repeat_length, n)
else:
# makes it look ok when the animation loops
lim = ax2.set_xlim(0, repeat_length)
return im, im2
ani = animation.FuncAnimation(fig, func, frames=image.shape[0], interval=30, blit=False)
plt.show()
will work.
If you need to run faster, you will need to play games with the bounding box used for blitting so that the axes labels are updated.
If you are using blitting, you can call pyplot.draw() to redraw the entire figure, each time you change y/x axis.
This updates whole figure, so is relatively slow, but it's acceptable if you don't call it many items.
This moves your axis, but is very slow.
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
image = np.random.rand(100,10,10)
repeat_length = (np.shape(image)[0]+1)/4
fig = plt.figure()
ax1 = ax1=fig.add_subplot(1,2,1)
im = ax1.imshow(image[0,:,:])
ax2 = plt.subplot(122)
ax2.set_xlim([0,repeat_length])
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
im2, = ax2.plot(image[0:0,5,5],color=(0,0,1))
canvas = ax2.figure.canvas
def init():
im = ax1.imshow(image[0,:,:])
im2.set_data([], [])
return im,im2,
def animate(time):
time = time%len(image)
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
print time
im2.axes.set_xlim(time-repeat_length,time)
plt.draw()
return im,im2,
ax2.get_yaxis().set_animated(True)
# call the animator. blit=True means only re-draw the parts that have changed.
animate = anim.FuncAnimation(fig, animate, init_func=init,
interval=0, blit=True, repeat=True)
plt.show()
Related
I'm working on a way to visualize data that changes drastically over a few years, and I'm using the code below to do so:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import pandas as pd
data = pd.read_pickle('/path/to/pickle file')
y = data['Total'].values
x = list(range(len(y)))
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot([],[], '-')
#ax.set_xlim(np.min(x), np.max(x))
#ax.set_ylim(np.min(y), np.max(y))
def animate(i):
ax.relim()
ax.autoscale_view()
line.set_xdata(x[:i])
line.set_ydata(y[:i])
return line,
ani = animation.FuncAnimation(fig, animate, interval=10, blit=True)
plt.show()
Everything above essentially works as intended, however when actually graphing the data, I get the correct autoscaling of the x and y axes as time progresses, but no line can be seen in the final picture of the graph below:
https://imgur.com/a/yHcencq
However, zooming in (even slightly) allows the lines to appear (below):
https://imgur.com/a/cRztEOM
It's not an issue with line width, as even setting it to an absurdly large number still doesn't display the graph until zoomed in, so I'm not entirely sure what's going on/how to fix this.
I'd like to be able to see a 'seemingly' fixed width line appear during the animation like the zoomed in picture above.
I am trying to automatically update a scatter plot.
The source of my X and Y values is external, and the data is pushed automatically into my code in a non-predicted time intervals (rounds).
I have only managed to plot all the data when the whole process ended, whereas I am trying to constantly add and plot data into my canvas.
What I DO get (at the end of the whole run) is this:
Whereas, what I am after is this:
A simplified version of my code:
import matplotlib.pyplot as plt
def read_data():
#This function gets the values of xAxis and yAxis
xAxis = [some values] #these valuers change in each run
yAxis = [other values] #these valuers change in each run
plt.scatter(xAxis,yAxis, label = 'myPlot', color = 'k', s=50)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
There are several ways to animate a matplotlib plot. In the following let's look at two minimal examples using a scatter plot.
(a) use interactive mode plt.ion()
For an animation to take place we need an event loop. One way of getting the event loop is to use plt.ion() ("interactive on"). One then needs to first draw the figure and can then update the plot in a loop. Inside the loop, we need to draw the canvas and introduce a little pause for the window to process other events (like the mouse interactions etc.). Without this pause the window would freeze. Finally we call plt.waitforbuttonpress() to let the window stay open even after the animation has finished.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
plt.draw()
for i in range(1000):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
fig.canvas.draw_idle()
plt.pause(0.1)
plt.waitforbuttonpress()
(b) using FuncAnimation
Much of the above can be automated using matplotlib.animation.FuncAnimation. The FuncAnimation will take care of the loop and the redrawing and will constantly call a function (in this case animate()) after a given time interval. The animation will only start once plt.show() is called, thereby automatically running in the plot window's event loop.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
def animate(i):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=100, repeat=True)
plt.show()
From what I understand, you want to update interactively your plot. If so, you can use plot instead of scatter plot and update the data of your plot like this.
import numpy
import matplotlib.pyplot as plt
fig = plt.figure()
axe = fig.add_subplot(111)
X,Y = [],[]
sp, = axe.plot([],[],label='toto',ms=10,color='k',marker='o',ls='')
fig.show()
for iter in range(5):
X.append(numpy.random.rand())
Y.append(numpy.random.rand())
sp.set_data(X,Y)
axe.set_xlim(min(X),max(X))
axe.set_ylim(min(Y),max(Y))
raw_input('...')
fig.canvas.draw()
If this is the behaviour your are looking for, you just need to create a function appending the data of sp, and get in that function the new points you want to plot (either with I/O management or whatever the communication process you're using).
I hope it helps.
I'm using matplotlib to animate a planets movements around a star.
I draw a simple small circle that represents the planet then i use funcanimation with an animate() function that changes the circles center each time, as is done on this website : https://nickcharlton.net/posts/drawing-animating-shapes-matplotlib.html.
Now I'm trying to use an image file instead of a circle but I barely know how to draw the image on the plot and really don't see how i can make it move on it
Any ideas ?
Thanks
Something like this will work:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.image import BboxImage
from matplotlib.transforms import Bbox, TransformedBbox
# make figure + Axes
fig, ax = plt.subplots()
# make initial bounding box
bbox0 = Bbox.from_bounds(0, 0, 1, 1)
# use the `ax.transData` transform to tell the bounding box we have given
# it position + size in data. If you want to specify in Axes fraction
# use ax.transAxes
bbox = TransformedBbox(bbox0, ax.transData)
# make image Artist
bbox_image = BboxImage(bbox,
cmap=plt.get_cmap('winter'),
norm=None,
origin=None,
**kwargs
)
# shove in some data
a = np.arange(256).reshape(1, 256)/256.
bbox_image.set_data(a)
# add the Artist to the Axes
ax.add_artist(bbox_image)
# set limits
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
# loop over new positions
for j in range(50):
x = j % 10
y = j // 10
# make a new bounding box
bbox0 = Bbox.from_bounds(x, y, 1, 1)
bbox = TransformedBbox(bbox0, ax.transData)
bbox_image.bbox = bbox
# re-draw the plot
plt.draw()
# pause so the gui can catch up
plt.pause(.1)
It is probably a bit more complicated than it needs to be and you really should use the animation framework rather than pause.
I wanna give you a +1 but my reputation doesn't allow it yet.
Thank's alot for the code, I succeeded in putting an imported image in the artist you use by modifying this line :
bbox_image.set_data(mpimg.imread("C:\\image.png"))
note I added this too
Import matplotlib.image as mpimg
But something's still amiss when I try to use funcanimation to animate this I get an error, here's my code (your's modified) :
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.image import BboxImage
from matplotlib.transforms import Bbox, TransformedBbox
import matplotlib.image as mpimg
from matplotlib import animation
# make figure + Axes
fig, ax = plt.subplots()
# make initial bounding box
bbox0 = Bbox.from_bounds(0, 0, 1, 1)
# use the `ax.transData` transform to tell the bounding box we have given
# it position + size in data. If you want to specify in Axes fraction
# use ax.transAxes
bbox = TransformedBbox(bbox0, ax.transData)
# make image Artist
bbox_image = BboxImage(bbox,
cmap=plt.get_cmap('winter'),
norm=None,
origin=None
)
bbox_image.set_data(mpimg.imread("C:\\icon-consulting.png"))
# add the Artist to the Axes
ax.add_artist(bbox_image)
# set limits
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
def animate(i):
bbox0 = Bbox.from_bounds(i, i, 1, 1)
bbox = TransformedBbox(bbox0, ax.transData)
bbox_image.bbox = bbox
return bbox_image
anim = animation.FuncAnimation(fig, animate,
frames=100000,
interval=20,
blit=True)
plt.show()
It tells me Error : 'BboxImage' object is not iterable
I guess only the position part of this BboxImage should be returned
I was used to doing this with Line2D objets by adding a coma, example : return lineobject,
which means only the first element of the tuple will be returned, but I don't see how It can be done with BboxImage
In fact I can simply use the loop as you first did,but perhaps you know how to adapt this to funcanimation ?
Edit :
I modified your code again using a bbox method :
for j in range(5000):
x = 2*np.sin(np.radians(j))
y = 2*np.cos(np.radians(j))
# make a new bounding box
bbox0.set_points([[x,y],[x+1,y+1]])
# re-draw the plot
plt.draw()
# pause so the gui can catch up
plt.pause(0.1)
Then I can convert this to use funcanimation this way :
def animate(i):
x = 2*np.sin(np.radians(i))
y = 2*np.cos(np.radians(i))
# make a new bounding box
bbox0.set_points([[x,y],[x+1,y+1]])
return bbox0.get_points()
anim = animation.FuncAnimation(fig, animate,
frames=100000,
interval=20,
blit=True)
plt.show()
This gives me an error : 'list' object is has no attribute 'axes'
it's the return I'm doing in the animate function, the returned value should be converted somehow I guess ... Do you know how I can do that ? Thanks
the code here below shows and saves an animation of random matrices in succession. My question is how can I adjust the duration of the animation that I save. The only parameters that I have here fps, and dpi control first how many seconds a frame remains and the second controls the quality of the image. What I want is to actually control the number of frames that are going to be saved in terms of the matrices the number of them that are actually stored.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
N = 5
A = np.random.rand(N,N)
im = plt.imshow(A)
def updatefig(*args):
im.set_array(np.random.rand(N,N))
return im,
ani = animation.FuncAnimation(fig, updatefig, interval=200, blit=True)
ani.save('try_animation.mp4', fps=10, dpi=80) #Frame per second controls speed, dpi controls the quality
plt.show()
I am wonderinf if I should add more parameters. I tried to look for the appropriate one in the class documentation in matplotlib but I was unsuccessful:
http://matplotlib.org/api/animation_api.html#module-matplotlib.animation
Years later I have built this is example that I come back to every time that I need to see how the parameters of the animation relate between themselves. I decided to share it here for whoever may find it useful.
tl/dr:
For the saved animation the duration is going to be frames * (1 / fps) (in seconds)
For the display animation the duration is going to be frames * interval / 1000 (in seconds)
The code bellow allows you to play with this setting in an environment that gives immediate visual feedback.
This code builds a clock that ticks according to the parameters:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure(figsize=(16, 12))
ax = fig.add_subplot(111)
# You can initialize this with whatever
im = ax.imshow(np.random.rand(6, 10), cmap='bone_r', interpolation='nearest')
def animate(i):
aux = np.zeros(60)
aux[i] = 1
image_clock = np.reshape(aux, (6, 10))
im.set_array(image_clock)
ani = animation.FuncAnimation(fig, animate, frames=60, interval=1000)
ani.save('clock.mp4', fps=1.0, dpi=200)
plt.show()
This will generate and save an animation that will look like this:
So the point is that the black square will move along the big white square as the time is passing. There are 60 white boxes so you can build a clock that goes over it in a minute.
Now, the important thing to note is that there are two parameters that determine how fast the black box would move: interval in the animation.FuncAnimation function and 'fps' in the ani.save function. The first controls the speed in the animation that you will display and the second in the animation that you will save.
As the code above stands you will generate 60 frames and they are displayed at 1 frame per second. That means that the clock ticks every second. If you want the saved animation clock to tick every two seconds then you should set fps=0.5. If you want the displayed animation clock to click every two seconds you should set interval=2000.
[I will edit the longer explanation as soon as I have time]
The documentation reveals that FuncAnimation accepts an argument frames, which controls the total number of frames played. Your code could thus read
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
N = 5
A = np.random.rand(N,N)
im = plt.imshow(A)
def updatefig(*args):
im.set_array(np.random.rand(N,N))
return im,
ani = animation.FuncAnimation(fig, updatefig, frames=10, interval=200, blit=True)
ani.save('try_animation.mp4', fps=10, dpi=80) #Frame per second controls speed, dpi controls the quality
plt.show()
to play 10 frames.
I can't figure out how to get an animated title working on a FuncAnimation plot (that uses blit). Based on http://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/ and Python/Matplotlib - Quickly Updating Text on Axes, I've built an animation, but the text parts just won't animate. Simplified example:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
vls = np.linspace(0,2*2*np.pi,100)
fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.005, '', transform = ax.transAxes)
def init():
ttl.set_text('')
img.set_data([0],[0])
return img, ttl
def func(n):
ttl.set_text(str(n))
img.set_data(vls,np.sin(vls+.02*n*2*np.pi))
return img, ttl
ani = animation.FuncAnimation(fig,func,init_func=init,frames=50,interval=30,blit=True)
plt.show()
If blit=True is removed, the text shows up, but it slows way down. It seems to fail with plt.title, ax.set_title, and ax.text.
Edit: I found out why the second example in the first link worked; the text was inside the img part. If you make the above 1.005 a .99, you'll see what I mean. There probably is a way to do this with a bounding box, somehow...
See Animating matplotlib axes/ticks and python matplotlib blit to axes or sides of the figure?
So, the problem is that in the guts of animation where the blit backgrounds are actually saved (line 792 of animation.py), it grabs what is in the axes bounding box. This makes sense when you have multiple axes being independently animated. In your case you only have one axes to worry about and we want to animate stuff outside of the axes bounding box. With a bit of monkey patching, a level of tolerance for reaching into the guts of mpl and poking around a bit, and acceptance of the quickest and dirtyest solution we can solve your problem as such:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def _blit_draw(self, artists, bg_cache):
# Handles blitted drawing, which renders only the artists given instead
# of the entire figure.
updated_ax = []
for a in artists:
# If we haven't cached the background for this axes object, do
# so now. This might not always be reliable, but it's an attempt
# to automate the process.
if a.axes not in bg_cache:
# bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
# change here
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
a.axes.draw_artist(a)
updated_ax.append(a.axes)
# After rendering all the needed artists, blit each axes individually.
for ax in set(updated_ax):
# and here
# ax.figure.canvas.blit(ax.bbox)
ax.figure.canvas.blit(ax.figure.bbox)
# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw
vls = np.linspace(0,2*2*np.pi,100)
fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.05, '', transform = ax.transAxes, va='center')
def init():
ttl.set_text('')
img.set_data([0],[0])
return img, ttl
def func(n):
ttl.set_text(str(n))
img.set_data(vls,np.sin(vls+.02*n*2*np.pi))
return img, ttl
ani = animation.FuncAnimation(fig,func,init_func=init,frames=50,interval=30,blit=True)
plt.show()
Note that this may not work as expected if you have more than one axes in your figure. A much better solution is to expand the axes.bbox just enough to capture your title + axis tick labels. I suspect there is code someplace in mpl to do that, but I don't know where it is off the top of my head.
To add to tcaswell's "monkey patching" solution, here is how you can add animation to the axis tick labels. Specifically, to animate the x-axis, set ax.xaxis.set_animated(True) and return ax.xaxis from the animation functions.
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def _blit_draw(self, artists, bg_cache):
# Handles blitted drawing, which renders only the artists given instead
# of the entire figure.
updated_ax = []
for a in artists:
# If we haven't cached the background for this axes object, do
# so now. This might not always be reliable, but it's an attempt
# to automate the process.
if a.axes not in bg_cache:
# bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
# change here
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
a.axes.draw_artist(a)
updated_ax.append(a.axes)
# After rendering all the needed artists, blit each axes individually.
for ax in set(updated_ax):
# and here
# ax.figure.canvas.blit(ax.bbox)
ax.figure.canvas.blit(ax.figure.bbox)
# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw
vls = np.linspace(0,2*2*np.pi,100)
fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.05, '', transform = ax.transAxes, va='center')
ax.xaxis.set_animated(True)
def init():
ttl.set_text('')
img.set_data([0],[0])
return img, ttl, ax.xaxis
def func(n):
ttl.set_text(str(n))
vls = np.linspace(0.2*n,0.2*n+2*2*np.pi,100)
img.set_data(vls,np.sin(vls))
ax.set_xlim(vls[0],vls[-1])
return img, ttl, ax.xaxis
ani = animation.FuncAnimation(fig,func,init_func=init,frames=60,interval=200,blit=True)
plt.show()
You must call
plt.draw()
After
ttl.set_text(str(n))
Here there is a very simple example of a text-animation inside a figure "without FuncAnimation()". Try it, you will see if it is useful for you.
import matplotlib.pyplot as plt
import numpy as np
titles = np.arange(100)
plt.ion()
fig = plt.figure()
for text in titles:
plt.clf()
fig.text(0.5,0.5,str(text))
plt.draw()