Animating a function where function parameters change with time using FuncAnimation - python

I am trying to animate a one-dimensional function where the function inputs are same but function parameters are changing with time. The function I am trying to animate is
f(x)=sin(a* pi * x)/(b*x)+ (x-1)^4
Here the data to be plotted is same, but a, b are changing with every update.I am using python and matplotlib library. My initial attempt is as follows:
fig,ax = plt.subplots()
line, = ax.plot([],[])
def animate(i,func_params):
x = np.linspace(-0.5,2.5,num = 200)
a=func_params[i][0]
b=func_params[i][1]
y=np.sin(a*math.pi*x)/b*x + (x-1)**4
line.set_xdata(x)
line.set_ydata(y)
return line,
ani = animation.FuncAnimation(fig,animate,frames=len(visualize_pop),fargs=(visualize_func,),interval = 100,blit=True)
plt.show()
The above code is not plotting anything.
EDIT: Updated code based on comment.

Your problem is that with plot([],[]) you give matplotlib no data and therefore no way do determine the limits of the axes. Therefore it uses some default values which are way out of the range of the data you actually want to plot. Therefore you have two choices:
1) Set the limits to some values that will contain all your plotted data for all cases,
e.g.
ax.set_xlim([-0.5,2.5])
ax.set_ylim([-2,6])
2) Let ax compute the limits automatically each frame and re-scale the plot see here using these two commands within your animate function (note that this option only works correctly if you turn blitting off):
ax.relim()
ax.autoscale_view()
Here still a completely working version of your code (the commands for solution (1) are commented out and I changed some of the notations):
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig,ax = plt.subplots()
x = np.linspace(-0.5,2.5,num = 200)
line, = ax.plot([],[])
#ax.set_xlim([-0.5,2.5])
#ax.set_ylim([-2,6])
##assuming some parameters, because none were given by the OP:
N = 20
func_args = np.array([np.linspace(1,2,N), np.linspace(2,1,N)])
def animate(i,func_params):
a=func_params[0,i]
b=func_params[1,i]
y=np.sin(a*np.pi*x)/b*x + (x-1)**4
line.set_xdata(x)
line.set_ydata(y)
ax.relim()
ax.autoscale_view()
return line, ax
##blit=True will not update the axes labels correctly
ani = FuncAnimation(
fig,animate,frames=N, fargs=(func_args,),interval = 100 #, blit=True
)
plt.show()

Related

Why doesn't blitting work with animation of a scatter plot in matplotlib?

I am trying to create an animation containing a fixed sphere and a trajectory on the surface of the sphere, with a trail of the trajectory containing the last "windowSize" points in the trajectory.
Now, for the purposes of the code I will show here, I won't have an actual such trajectory, but rather just some random points changing each frame.
I am using matplotlib.animation.FuncAnimation. When I use the option blit=False, the animation works as expected. However, I would like to use blit=True to optimize performance.
When I do that, though, what happens is that nothing seems to happen in the animation, except that when I rotate the figure, then it shows an updated version of the figure (some number of frames ahead) and then freezes again.
The code below is based on this similar question.
Let me show the code I am using
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.animation
import pandas as pd
Np = 5000
windowSize = 1000
m = np.random.rand(Np, 3)
df = pd.DataFrame({ "x" : m[0:Np,0], "y" : m[0:Np,1], "z" : m[0:Np,2]})
def init_graph():
u, v = np.mgrid[0:2*np.pi:50j, 0:np.pi:50j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
z = np.cos(v)
ax.plot_surface(x, y, z, color="bisque", alpha=0.3)
return graph,
def update_graph(num):
if (num<windowSize):
graph._offsets3d = (df.x[0:num], df.y[0:num], df.z[0:num])
else:
graph._offsets3d = (df.x[(num-windowSize):num], df.y[(num-windowSize):num], df.z[(num-windowSize):num])
title.set_text('3D Test, time={}'.format(num))
return graph,
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_box_aspect((1,1,1))
title = ax.set_title('3D Test')
graph = ax.scatter(0, 0, 0)
ani = matplotlib.animation.FuncAnimation(fig, update_graph, frames=Np, init_func=init_graph, interval=200, blit=True, repeat=False)
plt.show()
m is an Np by 3 matrix, and each row represents a 3d point (in my real use case, each row is a point in a trajectory on the sphere surface, but for this demo I created m as random numbers).
I create a variable graph that contains a scatter plot, which I believe is an Artist. This is what I return from both the init_func and the updating func which are passed to FuncAnimation (as per the docs).
From what I read, you return an iterable of the Artists which will be updated in the animation. Thus I return a tuple of one element, graph,.
Now, in update_graph, the updating function for the animation, I am updating the scatter plot using graph._offsets3d, which I read in another question here on StackOverflow. I am not totally sure if this is the way to do it and I didn't find much information in the docs about whether to use this or one of the setting methods on the scatter plot.
Why doesn't blitting work with scatter plots?

Matplotlib FuncAnimation Step-by-Step Animation Function

I am trying to use matplotlib's FuncAnimation to make an animated video. Each frame is just a boolean n x n array visualised as white/black squares. I can do this successfully by defining all the arrays in advance and then going through them one by one. This uses code similar to matplotlib's example.
My items are rather large and I want to run the simulation for a long time. I thus don't want to create the entire list of arrays then go through them one by one. Instead, I want to define the animate function to do each step. Let me explain with a minimal non-working example. My actual example includes far larger arrays!
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n,n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000 / 30,
blit = True
)
return anim
anim = create_video(10)
This initialises some random 10 x 10 set of 0/1s then just 'rolls' it at each step. I get an error.
RuntimeError: The animation function must return a sequence of Artist objects.
If I remove the return anim, replacing it with pass, and replacing anim = create_video(10) with create_video(10), then I get a warning.
UserWarning: Animation was deleted without rendering anything. This is most likely unintended. To prevent deletion, assign the Animation to a variable that exists for as long as you need the Animation.
Clearly, I don't understand well enough FuncAnimation. What I want to happen is for the function animate to update the array X, by 'rolling' it one step, as well as doing im.set_array(X).
As explained in this answer:
As the error suggests, and as can be seen e.g. in the
simple_animation example, but also from the FuncAnimation
documentation, the init_func as well as the updating func are
supposed to return an iterable of artists to animate.
The documentation does not say that this is actually only needed when
using blit=True, but since you are using blitting here, it is
definitely needed.
So you have two ways:
add
return im,
to animate function
set blit = False in FuncAnimation
Complete Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n, n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
return im,
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000/30,
blit = True
)
plt.show()
return anim
anim = create_video(10)

Plotting a continuous stream of data with MatPlotLib

I want to use MatPlotLib to plot a graph, where the plot changes over time. At every time step, an additional data point will be added to the plot. However, there should only be one graph displayed, whose appearance evolves over time.
In my test example, the plot is a simple linear plot (y = x). Here is what I have tried:
for i in range(100):
x = range(i)
y = range(i)
plt.plot(x, y)
plt.ion()
plt.show()
time.sleep(1)
However, what happens here is that multiple windows are created, so that by the end of the loop I have 100 windows. Also, I have noticed that for the most recent window, it is just a white window, and the plot only appears on the next step.
So, my two questions are:
1) How can I change my code so that only a single window is displayed, whose contents changes over time?
2) How can I change my code so that for the most recent timestep, the plot is actually displayed on the window, rather than it only displaying a white window?
Thanks!
(1)
You can set plt.ion() at the beginning and plot all graphs to the same window. Within the loop use plt.draw() to show the graph and plt.pause(t) to make a pause. Note that t can be very small, but the command needs to be there for the animation to work on most backends.
You might want to clear the axes before plotting new content using plt.gca().cla().
import matplotlib.pyplot as plt
plt.ion()
for i in range(100):
x = range(i)
y = range(i)
# plt.gca().cla() # optionally clear axes
plt.plot(x, y)
plt.title(str(i))
plt.draw()
plt.pause(0.1)
plt.show(block=True) # block=True lets the window stay open at the end of the animation.
Alternatively to this very simple approach, use any of the examples for animations provided in http://matplotlib.org/examples/animation/index.html
(2)
In order to get each plot in a new window, use plt.figure() and remove plt.ion(). Also only show the windows at the end:
import matplotlib.pyplot as plt
for i in range(100):
x = range(i)
y = range(i)
plt.figure()
plt.plot(x, y)
plt.title(str(i))
plt.show()
Note that you might find that in both cases the first plot is empty simply because for i=0, range(i) == [] is an empty list without any points. Even for i=1 there is only one point being plotted, but of course no line can connect a single point with itself.
I think the best way is to create one line plot and then update data in it. Then you will have single window and single graph that will continuously update.
import matplotlib.pyplot as plt
plt.ion()
fig = plt.figure(figsize=(16,8))
axes = fig.add_subplot(111)
data_plot=plt.plot(0,0)
line, = axes.plot([],[])
for i in range(100):
x = range(i)
y = range(i)
line.set_ydata(y)
line.set_xdata(x)
if len(y)>0:
axes.set_ylim(min(y),max(y)+1) # +1 to avoid singular transformation warning
axes.set_xlim(min(x),max(x)+1)
plt.title(str(i))
plt.draw()
plt.pause(0.1)
plt.show(block=True)

How do I update the extent of imshow in matplotlib?

When updating an "imshow" plot in matplotlib, it's best to use im.set_data, rather than using ax.imshow repeatedly in the loop. But what if the extent of the data is changing? Is it possible to update the extent of the data on each iteration of the loop?
Here is an example:
import numpy as np
import matplotlib.pyplot as plt
import time
ax = plt.subplot(111)
plt.ion()
plt.show()
count = 0
for size in np.linspace(1,3,10):
x = np.linspace(-size,size,100)
y = np.linspace(-size,size,100)
X,Y = np.meshgrid(x,y)
R = (X**2+Y**2)**0.5
Z = np.sin(R)/R
ext =(-size,size,-size,size)
if count == 0:
im = plt.imshow(Z,extent=ext)
else:
im.set_data(Z)
# Update the extent of the data
plt.draw()
plt.pause(0.5)
ax.set_xlim(-size,size)
ax.set_ylim(-size,size)
count += 1
plt.ioff()
plt.show()
The colored region should take up the entire axes if I could update the extent properly.
In your example, im.set_extent(ext).
More generally, though, almost any kwarg you can pass in to a matplotlib artist during initialization will have get_foo and set_foo methods. (That's actually how initialization works and how artist.set(...) and plt.setp works, as well.)
If you're looking for how to change a given property, the first place to look is a set_<name> method.
There are exceptions to this. For example, scatter returns a Collection, so you need to call set_offsets instead of set_xy to change the x, y data. Generally speaking, though, it's consistent.

matplotlib - How do you keep the axes constant while adding new data?

I'm using matplotlib to display data that is constantly being updated (changes roughly 10 times per second). I'm using a 3D scatter plot, and I would like the axes to be fixed to a specific range, since the location of the data with respect to the edges of the plot is what is important.
Currently whenever I add new data, the axes will reset to being scaled by the data, rather than the size I want (when I have hold=False). If I set hold=True, the axes will remain the right size, but the new data will be overlayed on the old data, which is not what I want.
I can get it to work if I rescale the axes everytime I get new data, but this seems like an inefficient way to do this, especially since I need to do all other formatting again as well (adding titles, legends, etc)
Is there some way in which I can specify the properties of the plot just once, and this will remain fixed as I add new data?
Here is a rough outline of my code, to help explain what I mean:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
X_MAX = 50
Y_MAX = 50
Z_MAX = 50
fig = plt.figure(1)
ax = fig.add_subplot(111, projection='3d')
ax.set_title("My Title")
ax.set_xlim3d([0, X_MAX])
ax.set_ylim3d([0, Y_MAX])
ax.set_zlim3d([0, Z_MAX])
ax.set_autoscale_on(False)
# This is so the new data replaces the old data
# seems to be replacing the axis ranges as well, maybe a different method should be used?
ax.hold(False)
plt.ion()
plt.show()
a = 0
while a < 50:
a += 1
ax.scatter( a, a/2+1, 3, s=1 )
# If I don't set the title and axes ranges again here, they will be reset each time
# I want to know if there is a way to only set them once and have it persistent
ax.set_title("My Title")
ax.set_xlim3d([0, X_MAX])
ax.set_ylim3d([0, Y_MAX])
ax.set_zlim3d([0, Z_MAX])
plt.pause(0.001)
EDIT:
1. I have also tried ax.set_autoscale_on(False), but with no success
2. I tried this with a regular 2D scatter plot, and the same issue still exists
3. Found a related question which also still doesn't have an answer
I would do something like this (note removal of hold(False) ):
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
X_MAX = 50
Y_MAX = 50
Z_MAX = 50
fig = plt.figure(1)
ax = fig.add_subplot(111, projection='3d')
ax.set_title("My Title")
ax.set_xlim3d([0, X_MAX])
ax.set_ylim3d([0, Y_MAX])
ax.set_zlim3d([0, Z_MAX])
ax.set_autoscale_on(False)
plt.ion()
plt.show()
a = 0
sct = None
while a < 50:
a += 1
if sct is not None:
sct.remove()
sct = ax.scatter( a, a/2+1, 3, s=1 )
fig.canvas.draw()
plt.pause(0.001)
Where you remove just the added scatter plot each time through the loop.

Categories

Resources