I am doing an animation of a launched projectile and running into some odd behavior that I don't understand. I am plotting the animation of a point, representing the object, and also animating the path so that the trajectory shows up behind the object. However, when I do this the way I think I am supposed to do it, the point is shown one step ahead of the trajectory and the trajectory ends up one point shy of finishing. I can work around this by increasing the index number of the trajectory, but then it seems like the index should be out of bounds at the end. I am really confused and could use some help understanding what's going on.
I am working in a Jupyter notebook and have provided a minimally working example below. I am currently just using 10 points in my linspace command and have slowed down the animation a lot so you can see what's happening. If I use the command line1.set_data(x[0:frames+1], y[0:frames+1]) along with point1.set_data(x[frames], y[frames]) in the animate function, then everything looks fine. But that seems like it shouldn't work!
What am I missing?
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Global constants
g = 9.8 # gravitational field strength in m/s^2
v0 = 40.0 # initial speed in m/s
theta = 30.0 # Launch angle in degrees
# determine trajectory
theta_rad = np.pi*theta/180
t = np.linspace(0, 2*v0*np.sin(theta_rad)/g, 10)
x = v0*np.cos(theta_rad)*t
y = v0*t*np.sin(theta_rad) - g*t**2/2
# Plot the results
fig1 = plt.figure(figsize=(6,4))
ax1 = fig1.add_subplot(111)
line1, = ax1.plot(x[0:1], y[0:1], 'b-', label='no drag')
point1, = ax1.plot(x[0], y[0], 'bo', ms=3)
ax1.set_xlim(0, 170)
ax1.set_ylim(0, 50)
plt.show()
# Animation update function
def animate(frames):
line1.set_data(x[0:frames], y[0:frames])
point1.set_data(x[frames], y[frames])
return
ani = animation.FuncAnimation(fig1, animate, frames=len(t), interval=1000, blit=True, repeat=False)
plt.show()
In python the syntax x[0:frames] defines a range from 0 inclusive up to frames exclusive.
So, consider the loop:
At iteration 0 you are updating the point to x[0], y[0] and the line plot is empty: x[0:0], y[0:0] are empty sets;
At iteration 1 you have updated the point to x[1], y[1] and the line only has one couple of coordinates: x[0:1], y[0:1], not enough to form a line;
At iteration 2 the point is at its third position x[2], y[2] but the line plot lags behind because x[0:2], y[0:2] contain enough coordinates for two points, namely the first and second point, enough to only form the first segment but not the second required to reach the point.
Related
I'm trying to animate a plot that moves across my map (field of regard of an airplane). I need it to iterate through my coordinates, so I tried using a for loop. When I run this, I get two frames, the first is empty, and the second is just an empty map.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.basemap import Basemap
#map of Puerto Rico
map=Basemap(projection='merc', llcrnrlat=17.5,urcrnrlat=19.,llcrnrlon=-67.5, urcrnrlon=-65, epsg=4139)
map.arcgisimage(service='World_Shaded_Relief', xpixels = 2000)
plt.title("Flight Path of sbet 0059")
#sample coordinates
lon=[-63,-64,-65,-66]
lon=[int(l) for l in lon]
lat=[17., 17.5, 18., 18.5]
lat=[int(l) for l in lat]
time=[1, 3, 5, 7]
time=[int(l) for l in time]
fig=plt.figure()
for a1,b1 in zip(lon,lat):
def init():
fig
return(fig),
def animate(i):
x,y=map(a1,b1)
map.plot(x,y, linewidth = 1,color = 'm')
return(map),
anim=animation.FuncAnimation(fig, animate, frames=len(time), interval=1000)
plt.show()
Does anyone know what the problem is and how I get the plot to move across the map? Thanks!
Your code had quite a few problems. Maybe I just list them here and then you can ask questions if there is something you don't understand:
If no figure has been opened yet, the call to Basemap will generate a new figure. This makes the extra call to plt.figure() (kind of) redundant. I usually call plt.figure anyway, but before the call to Basemap.
When you use FuncAnimation, you do not have to loop through the values you want to animate -- FuncAnimation is doing this for you. The only thing that you need to provide is a function that updates the current plot according to the integer argument that FuncAnimation will provide to this function. In other words, the entire for loop you wrote is not the way to go.
Only call plt.show() once. Right now you call it every time you run through your for loop.
Don't call the plot command every time you want to update your plot. If everything else in your code would be correct, you would get many lines instead of just one. Instead update the data of the line object that is returned by plot. See the code below how this is done.
The amount of data points you provide and the amount of frames to want to animate do not match (4 vs 1000), which means that the animation will run "empty" for a long time.
Most of the coordinates you provide are outside of the map region that you display.
Don't call the Basemap object map, even though they do so in the tutorials. map is a reserved word in python. Rather use m or bmap or something.
Below I tried to correct the problems with your code, but it might be altered too much for your needs, so please ask if there is anything unclear:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.basemap import Basemap
fig=plt.figure()
#map of Puerto Rico
bmap=Basemap(projection='merc', llcrnrlat=17.5,urcrnrlat=19.,llcrnrlon=-67.5, urcrnrlon=-65, epsg=4139)
bmap.arcgisimage(service='World_Shaded_Relief', xpixels = 2000)
plt.title("Flight Path of sbet 0059")
#sample coordinates
##lon=[-63,-64,-65,-66]
##lon=[int(l) for l in lon]
##lat=[17., 17.5, 18., 18.5]
##lat=[int(l) for l in lat]
##time=[1, 3, 5, 7]
##time=[int(l) for l in time]
#generate the flight coordinates (just a straight line with 100 points)
N = 100
lon = np.linspace(-64, -68, N)
lat = np.linspace(17, 19.5, N)
#only convert the coordinates once
x,y = bmap(lon, lat)
#generate the original line object with only one point
line, = bmap.plot(x[0], y[0], linewidth = 1, color = 'm')
def animate(i):
#this is doing all the magic. Every time animate is called, the line
#will become longer by one point:
line.set_data(x[:i],y[:i])
return line
anim=animation.FuncAnimation(fig, animate, frames=N, interval=N)
plt.show()
I am trying to follow the basic animation tutorial located here and adapting it to display an already computed dataset instead of evaluating a function every frame, but am getting stuck. My dataset involves XY coordinates over time, contained in the lists satxpos and satypos I am trying to create an animation such that it traces a line starting at the beginning of the dataset through the end, displaying say 1 new point every 0.1 seconds. Any help with where I'm going wrong?
from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
Code here creates satxpos and satypos as lists
fig = plt.figure()
ax = plt.axes(xlim=(-1e7,1e7), ylim = (-1e7,1e7))
line, = ax.plot([], [], lw=2)
def init():
line.set_data([], [])
return line,
def animate(i):
line.set_data(satxpos[i], satypos[i])
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames = len(satxpos), interval = 1, blit=True)
Edit: The code runs without errors, but generates a blank plot window with no points/lines displayed and nothing animates. The dataset is generated correctly and views fine in a static plot.
In order to "trace a line starting at the beginning of the dataset through the end" you would index your arrays to contain one more element per timestep:
line.set_data(satxpos[:i], satypos[:i])
(Note the :!)
Everything else in the code looks fine, such that with the above manipulation you should get and extending line plot. You might then want to set interval to something greater than 1, as that would mean 1 millesecond timesteps (which might be a bit too fast). I guess using interval = 40 might be a good start.
Your code looks correct! So long as satxpos and satypos are both configured and initialized properly, I believe everything else is valid!
One part of the code you do not show in your question is the invocation of the anim.save() and plt.show() functions, which are both necessary for your code to work (as per the tutorial you shared!)
You would therefore need to add something like:
anim.save('basic_animation.mp4', fps=30, extra_args=['-vcodec', 'libx264'])
plt.show()
to the end of your code to create the animation (and show it, I presume)!
Hope it helps!
Source - Matplotlib Animation Tutorial
I saw you mentioned "the parts that generate satxpos and satypos do create valid datasets. I can view those as a static plot just fine". But my guess is still the problem originated from your satxpos and satypos.
One way you can trouble shoot is to replace your two functions and animation code with line.set_data(satxpos[i], satypos[i]). Set i as 0, 1, ... and see if you can see the plot. If not, your satxpos and satypos are not as valid as you claimed.
As an example, a valid satxpos and satypos can be like this:
x = np.array([np.linspace(-1e7, 1e7, 1000)])
i = 200
satxpos = x.repeat(i, axis=0)
satypos = np.sin(2 * np.pi * (satxpos - 0.01 * np.arange(i).reshape(-1, 1).repeat(satxpos.shape[1], axis=1)))
satypos *= 1e7 / 2
This works with the code you provided thus indicating the code you've shown us is fine.
Edit in response to comments:
If your satxpos and satypos are just np.linespace, the animation loop will get just one point with (satxpos[i], satypos[i]) and you won't see the point on the plot without a setting like marker='o'. Therefore, you see nothing in your animation.
I am switching from Matlab to Python, so I am a relative beginner. What I would like to get is a 3D animation of a point moving in space, say along a helix for simplicity, and a history of its trajectory.
Based on this example http://matplotlib.org/examples/animation/simple_3danim.html, I have come with the following code:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
###############################################################################
# Create helix:
def make_helix(n):
theta_max = 8 * np.pi
theta = np.linspace(0, theta_max, n)
x, y, z = theta, np.sin(theta), np.cos(theta)
helix = np.vstack((x, y, z))
return helix
# Update AUV position for plotting:
def update_auv(num, dataLines, lines) :
for line, data in zip(lines, dataLines) :
line.set_data(data[0:2, num-1:num])
line.set_3d_properties(data[2,num-1:num])
return lines
# Update trajectory for plotting:
def update_trj(num, dataLines, lines) :
for line, data in zip(lines, dataLines) :
line.set_data(data[0:2, :num])
line.set_3d_properties(data[2,:num])
return lines
###############################################################################
# Attach 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Define no. data points and create helix:
n = 100
data = [make_helix(n)]
# Create line objects:
auv = [ax.plot(data[0][0,0:1], data[0][1,0:1], data[0][2,0:1], 'ro')[0]]
trj = [ax.plot(data[0][0,0:1], data[0][1,0:1], data[0][2,0:1])[0]]
# Setthe axes properties
ax.set_xlim3d([0.0, 8*np.pi])
ax.set_xlabel('X')
ax.set_ylim3d([-1.0, 1.0])
ax.set_ylabel('Y')
ax.set_zlim3d([-1.0, 1.0])
ax.set_zlabel('Z')
ax.set_title('3D Test')
# Creating the Animation object
ani_auv = animation.FuncAnimation(fig, update_auv, n, fargs=(data, auv),
interval=50, blit=False) #repeat=False,
ani_trj = animation.FuncAnimation(fig, update_trj, n, fargs=(data, trj),
interval=50, blit=False) #repeat=False,
plt.show()
Now, this code shows what I am trying to achieve (show the moving body as a point and the history of the trajectory at the same time), but it has two major problems:
The trajectory is recalculated at every time step, which is inefficient, but computing power should not be a problem;
The bigger problem is there is a misalignment between the point and trajectory. I think the reason for it is due to the lower computational time associated with the calculation of the position of the simple point.
An alternative could be what they do here: Animate a python pyplot by moving a point plotted via scatter, but to be honest, I would prefer to find a way to create an animation with time stamps. That way, I have to calculate the trajectory only once and can update the position of the point at every new time step.
Thank you for the help!
I have a while function that generates two lists of numbers and at the end I plot them using matplotlib.pyplot.
I'm doing
while True:
#....
plt.plot(list1)
plt.plot(list2)
plt.show()
But in order to see the progression I have to close the plot window.
Is there a way to refresh it with the new data every x seconds?
The most robust way to do what you want is to use matplotlib.animation. Here's an example of animating two lines, one representing sine and one representing cosine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
sin_l, = ax.plot(np.sin(0))
cos_l, = ax.plot(np.cos(0))
ax.set_ylim(-1, 1)
ax.set_xlim(0, 5)
dx = 0.1
def update(i):
# i is a counter for each frame.
# We'll increment x by dx each frame.
x = np.arange(0, i) * dx
sin_l.set_data(x, np.sin(x))
cos_l.set_data(x, np.cos(x))
return sin_l, cos_l
ani = animation.FuncAnimation(fig, update, frames=51, interval=50)
plt.show()
For your particular example, you would get rid of the while True and put the logic inside that while loop in the update function. Then, you just have to make sure to do set_data instead of making a whole new plt.plot call.
More details can be found in this nice blog post, the animation API, or the animation examples.
I think what you're looking for is the "animation" feature.
Here is an example
This example is a second one.
I've run into a fairly serious issue with matplotlib and Python. I have a dense periodogram data set and want to plot it. The issue is that when there are more data points than can be plotted on a pixel, the package does not pick the min and max to display. This means a casual look at the plot can lead you to incorrect conclusions.
Here's an example of such a problem:
The dataset was plotted with plot() and scatter() overlayed. You can see that in the dense data fields, the blue line that connects the data does not reach the actual peaks, leading a human viewer to conclude the peak at ~2.4 is the maximum, when it's really not.
If you zoom-in or force a wide viewing window, it is displayed correctly. rasterize and aa keywords have no effect on the issue.
Is there a way to ensure that the min/max points of a plot() call are always rendered? Otherwise, this needs to be addressed in an update to matplotlib. I've never had a plotting package behave like this, and this is a pretty major issue.
Edit:
x = numpy.linspace(0,1,2000000)
y = numpy.random.random(x.shape)
y[1000000]=2
plot(x,y)
show()
Should replicate the problem. Though it may depend on your monitor resolution. By dragging and resizing the window, you should see the problem. One data point should stick out a y=2, but that doesn't always display.
This is due to the path-simplification algorithm in matplotlib. While it's certainly not desirable in some cases, it's deliberate behavior to speed up rendering.
The simplification algorithm was changed at some point to avoid skipping "outlier" points, so newer versions of mpl don't exhibit this exact behavior (the path is still simplified, though).
If you don't want to simplify paths, then you can disable it in the rc parameters (either in your .matplotlibrc file or at runtime).
E.g.
import matplotlib as mpl
mpl.rcParams['path.simplify'] = False
import matplotlib.pyplot as plt
However, it may make more sense to use an "envelope" style plot. As a quick example:
import matplotlib.pyplot as plt
import numpy as np
def main():
num = 10000
x = np.linspace(0, 10, num)
y = np.cos(x) + 5 * np.random.random(num)
fig, (ax1, ax2) = plt.subplots(nrows=2)
ax1.plot(x, y)
envelope_plot(x, y, winsize=40, ax=ax2)
plt.show()
def envelope_plot(x, y, winsize, ax=None, fill='gray', color='blue'):
if ax is None:
ax = plt.gca()
# Coarsely chunk the data, discarding the last window if it's not evenly
# divisible. (Fast and memory-efficient)
numwin = x.size // winsize
ywin = y[:winsize * numwin].reshape(-1, winsize)
xwin = x[:winsize * numwin].reshape(-1, winsize)
# Find the min, max, and mean within each window
ymin = ywin.min(axis=1)
ymax = ywin.max(axis=1)
ymean = ywin.mean(axis=1)
xmean = xwin.mean(axis=1)
fill_artist = ax.fill_between(xmean, ymin, ymax, color=fill,
edgecolor='none', alpha=0.5)
line, = ax.plot(xmean, ymean, color=color, linestyle='-')
return fill_artist, line
if __name__ == '__main__':
main()