I want to plot spatial data that depends on time. I already have an animated plot, similar to animated plot, and I would like to add a timer/counter of the frames. The timer should not show the current time but rather the time of the data (so to make it easier it could be e.g. a counter of frames).
--- update ---
I added a text to the code but I don't know how to update the text for each frame.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
def f(x, y):
return np.sin(x) + np.cos(y)
x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
for i in range(60):
x += np.pi / 15.
y += np.pi / 20.
# edit: add text
ttl = plt.text(0.5, 1.01, str(i),
horizontalalignment='center',
verticalalignment='bottom',
transform=ax.transAxes)
im = plt.imshow(f(x, y), animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
# ani.save('dynamic_images.mp4')
plt.show()
I thought it might be the easiest approach to add a title to each frame but I don't know how to do that. And maybe there is an easier way?
Related
I have an array x_trj that has shape (50,3), and I want to plot a 2-D trajectory using the 1st and the 2nd columns of this array (x & y coordinates respectively). This trajectory will be on top of a circle. Here is my code so far:
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(xlim=(-5, 5), ylim=(-5, 5))
line, = ax.plot([], [], lw=2)
# Plot circle
theta = np.linspace(0, 2*np.pi, 100)
plt.plot(r*np.cos(theta), r*np.sin(theta), linewidth=5)
ax = plt.gca()
def animate(n):
# Plot resulting trajecotry of car
for n in range(x_trj.shape[0]):
line.set_xdata(x_trj[n,0])
line.set_ydata(x_trj[n,1])
return line,
anim = FuncAnimation(fig, animate,frames=200, interval=20)
However, the animation turns out to be a stationary figure. I checked out the Matplotlib animation example on the documentation page, but I still can't figure out what my animate(n) function should look like in this case. Can someone give me some hints?
The code below makes the following changes:
added some test data
in animate:
remove the for loop
only copy the part of the trajectory until the given n
in the call to FuncAnimation:
`frames should be equal to the given number of points (200 frames and 50 points doesn't work well)
interval= set to a larger number, as 20 milliseconds make things too fast for only 50 frames
added plt.show() (depending on the environment where the code is run, plt.show() will trigger the animation to start)
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
# create some random test data
x_trj = np.random.randn(50, 3).cumsum(axis=0)
x_trj -= x_trj.min(axis=0, keepdims=True)
x_trj /= x_trj.max(axis=0, keepdims=True)
x_trj = x_trj * 8 - 4
fig = plt.figure()
ax = plt.axes(xlim=(-5, 5), ylim=(-5, 5))
line, = ax.plot([], [], lw=2)
# Plot circle
theta = np.linspace(0, 2 * np.pi, 100)
r = 4
ax.plot(r * np.cos(theta), r * np.sin(theta), linewidth=5)
def animate(n):
line.set_xdata(x_trj[:n, 0])
line.set_ydata(x_trj[:n, 1])
return line,
anim = FuncAnimation(fig, animate, frames=x_trj.shape[0], interval=200)
# anim.save('test_trajectory_animation.gif')
plt.show()
I am developing a simple algorithm for the detection of peaks in a signal. To troubleshoot my algorithm (and to showcase it), I would like to observe the signal and the detected peaks all along the signal duration (i.e. 20 minutes at 100Hz = 20000 time-points).
I thought that the best way to do it would be to create an animated plot with matplotlib.animation.FuncAnimation that would continuously show the signal sliding by 1 time-points and its superimposed peaks within a time windows of 5 seconds (i.e. 500 time-points). The signal is stored in a 1D numpy.ndarray while the peaks information are stored in a 2D numpy.ndarray containing the x and y coordinates of the peaks.
This is a "still frame" of how the plot would look like.
Now the problem is that I cannot wrap my head around the way of doing this with FuncAnimation.
If my understanding is correct I need three main pieces: the init_func parameter, a function that create the empty frame upon which the plot is drawn, the func parameter, that is the function that actually create the plot for each frame, and the parameter frames which is defined in the help as Source of data to pass func and each frame of the animation.
Looking at examples of plots with FuncAnimation, I can only find use-cases in which the data to plot are create on the go, like here, or here, where the data to plot are created on the basis of the frame.
What I do not understand is how to implement this with data that are already there, but that are sliced on the basis of the frame. I would thus need the frame to work as a sort of sliding window, in which the first window goes from 0 to 499, the second from 1to 500 and so on until the end of the time-points in the ndarray, and an associated func that will select the points to plot on the basis of those frames. I do not know how to implement this.
I add the code to create a realistic signal, to simply detect the peaks and to plot the 'static' version of the plot I would like to animate:
import neurokit2 as nk
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
from scipy.signal import find_peaks
#create realistic data
data = nk.ecg_simulate(duration = 50, sampling_rate = 100, noise = 0.05,\
random_state = 1)
#scale data
scaler = MinMaxScaler()
scaled_arr = scaler.fit_transform(data.reshape(-1,1))
#find peaks
peak = find_peaks(scaled_arr.squeeze(), height = .66,\
distance = 60, prominence = .5)
#plot
plt.plot(scaled_arr[0:500])
plt.scatter(peak[0][peak[0] < 500],\
peak[1]['peak_heights'][peak[0] < 500],\
color = 'red')
I've created an animation using the data you presented; I've extracted the data in 500 increments for 5000 data and updated the graph. To make it easy to extract the data, I have created an index of 500 rows, where id[0] is the start row, id1 is the end row, and the number of frames is 10. This code works, but the initial settings and dataset did not work in the scatter plot, so I have drawn the scatter plot directly in the loop process.
import neurokit2 as nk
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy.signal import find_peaks
import numpy as np
#create realistic data
data = nk.ecg_simulate(duration = 50, sampling_rate = 100, noise = 0.05, random_state = 1)
#scale data
scaler = MinMaxScaler()
scaled_arr = scaler.fit_transform(data.reshape(-1,1))
#find peaks
peak = find_peaks(scaled_arr.squeeze(), height = .66, distance = 60, prominence = .5)
ymin, ymax = min(scaled_arr), max(scaled_arr)
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot([],[], lw=2)
scat = ax.scatter([], [], s=20, facecolor='red')
idx = [(s,e) for s,e in zip(np.arange(0,len(scaled_arr), 1), np.arange(499,len(scaled_arr)+1, 1))]
def init():
line.set_data([], [])
return line,
def animate(i):
id = idx[i]
#print(id[0], id[1])
line.set_data(np.arange(id[0], id[1]), scaled_arr[id[0]:id[1]])
x = peak[0][(peak[0] > id[0]) & (peak[0] < id[1])]
y = peak[1]['peak_heights'][(peak[0] > id[0]) & (peak[0] < id[1])]
#scat.set_offsets(x, y)
ax.scatter(x, y, s=20, c='red')
ax.set_xlim(id[0], id[1])
ax.set_ylim(ymin, ymax)
return line,scat
anim = FuncAnimation(fig, animate, init_func=init, frames=50, interval=50, blit=True)
plt.show()
Probably not exactly what you want, but hope it can help,
import neurokit2 as nk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from sklearn.preprocessing import MinMaxScaler
import numpy as np
from matplotlib.animation import FuncAnimation
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# This function is called periodically from FuncAnimation
def animate(i, xs, ys):
xs = xs[i]
ys = ys[i]
# Draw x and y lists
ax.clear()
ax.plot(xs, ys)
if __name__=="__main__":
data = nk.ecg_simulate(duration = 50, sampling_rate = 100, noise = 0.05, random_state = 1)
scaler = MinMaxScaler()
scaled_arr = scaler.fit_transform(data.reshape(-1,1))
ys = scaled_arr.flatten()
ys = [ys[0:50*i] for i in range(1, int(len(ys)/50)+1)]
xs = [np.arange(0, len(ii)) for ii in ys ]
ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=500)
ani.save('test.gif')
I have the following function to generate a brownian motion:
from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
from scipy.stats import uniform, norm
def walk(n):
angle = uniform.rvs( size=(n,), loc=.0, scale=2.*np.pi )
r = norm.rvs( size=n )
x = np.cumsum( r * np.cos(angle) )
y = np.cumsum( r * np.sin(angle) )
return np.array((x, y, r, angle))
If I call this like brownian = walk(1000), and plot it like ax.plot( brownian[0,:], brownian[1,:], color='k'), it plots it correctly, but now I want to animate it and do this (taken from here):
# Length of array (or how long motion is modeled)
motionLength = 1000
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
xyMin = brownian.min() * 1.2
xyMax = brownian.max() * 1.2
plt.axis('equal')
ax = plt.axes(xlim=(xyMin,xyMax), ylim=(xyMin,xyMax))
line, = plt.plot([], [], lw=1, color='k')
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
def iterr(i):
line.set_data(brownian[:i,0],brownian[[:i,1]) # problem here?
return line,
anim = animation.FuncAnimation(fig, iterr, init_func=init, frames=motionLength,
interval=100, blit=True)
anim.save('test_animation_2.mp4', fps=120, bitrate=-1,
extra_args=['-vcodec', 'libx264'])
But I cannot seem to get it to work. I guess the problem lies in my building the lists in iterr, because either 1) I'm not taking the correct values with my slices, or 2) I'm not getting getting from walk what I think I'm getting.
How do I rewrite iterr to work with my ndarray.
As stated above, I am trying to animate a set of data that varies over time (position). I would like my graph to only show the position data but animate the position history over time. I have started with this example here, and got it working. Now, instead of the whole line animating, I would like for the line to be drawn from left to right. I also need the line to be colored relative to a secondary set of data, which I have been able to accomplish with a LineCollection.
My code:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line = LineCollection([], cmap=plt.cm.jet)
line.set_array(np.linspace(0, 2, 1000))
ax.add_collection(line)
x = np.linspace(0, 2, 10000)
y = np.sin(2 * np.pi * (x))
# initialization function: plot the background of each frame
def init():
line.set_segments([])
return line,
# animation function. This is called sequentially
def animate(i, xss, yss, line):
xs = xss[:i]
ys = yss[:i]
points = np.array([xs, ys]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
line.set_segments(segments)
return line,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, fargs=[x, y, line], init_func=init, frames=200, interval=20)
plt.show()
I create a basic sine wave data set and again would like to animate the line being drawn from left to right. Right now, the LineCollection is being colored by the y-value of the line at the current x-position. Eventually, this will be a position data set pulled from a .csv file.
Finally, the issue. The code above runs without errors, however the line is not being drawn. I can see in my debugger that the xs and ys arrays are being added to during each step so that syntax seems to be working, just the updated LineCollection is not being displayed.
I am working on macOS Mojave 10.14.6.
Your code is correct, the line you're plotting is just very small. This is because the function you animate is given by
x = np.linspace(0, 2, 10000) # Note that `num=10000`
y = np.sin(2 * np.pi * (x))
which has 10000 points, but you only animate the first 200 points.
anim = animation.FuncAnimation(..., frames=200, interval=20)
Easy fix
num_frames = 200
x = np.linspace(0, 2, num_frames)
...
anim = animation.FuncAnimation(..., frames=num_frames, interval=20)
I'm trying to animate a stem plot in matplotlib and I can't find the necessary documentation to help me. I have a series of data files which each look like this:
1 0.345346
2 0.124325
3 0.534585
and I want plot each file as a separate frame.
According to this and this other tutorial, I should create a function which updates the data contained in each plot object (artist? I'm not sure about the terminology)
From the second link, this is the update function
def update(frame):
global P, C, S
# Every ring is made more transparent
C[:,3] = np.maximum(0, C[:,3] - 1.0/n)
# Each ring is made larger
S += (size_max - size_min) / n
# Reset ring specific ring (relative to frame number)
i = frame % 50
P[i] = np.random.uniform(0,1,2)
S[i] = size_min
C[i,3] = 1
# Update scatter object
scat.set_edgecolors(C)
scat.set_sizes(S)
scat.set_offsets(P)
# Return the modified object
return scat,
How can I adapt this kind of update function for a stem plot? The documentation for stem is horribly brief (in fact this is a recurring issue as I'm learning matplotlib), but the example code shows that the output of stem is a tuple markerline, stemlines, baseline rather than an artist object like for plt.plot or plt.imshow.
So when I write my update function for the animation, how can I update the data inside the stem plot?
Here you go!
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*np.pi, 10)
markerline, stemlines, baseline = ax.stem(x, np.cos(x), '-.')
def update(i):
ax.cla()
markerline, stemlines, baseline = ax.stem(x, np.cos(x+i/10), '-.')
ax.set_ylim((-1, 1))
anim = FuncAnimation(fig, update, frames=range(10, 110, 10), interval=500)
anim.save('so.gif', dpi=80, writer='imagemagick')
I think there can be better ways of achieving this- not requiring to clear the plot each time. However, this works!
When using the keyword use_line_collection=True (default behavior since Matplotlib 3.3) one can update the three elements
markerline
stemlines
baseline
individualy. Here is the code for the sine wave example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*np.pi, 10)
y = np.cos(x)
bottom = 0
h_stem = ax.stem(x, y, bottom=bottom, use_line_collection=True, linefmt='-.')
def update(i):
y = np.cos(x+i/10)
# markerline
h_stem[0].set_ydata(y)
h_stem[0].set_xdata(x) # not necessary for constant x
# stemlines
h_stem[1].set_paths([np.array([[xx, bottom],
[xx, yy]]) for (xx, yy) in zip(x, y)])
# baseline
h_stem[2].set_xdata([np.min(x), np.max(x)])
h_stem[2].set_ydata([bottom, bottom]) # not necessary for constant bottom
anim = FuncAnimation(fig, update, frames=range(10, 110, 10), interval=1)
anim.save('so.gif', dpi=80, writer='imagemagick')
Depending on what values (x, y, bottom) should be updated you can omit some parts of this update or reuse the current values. I wrote a more general function, where you can pass an arbitrary combination of these values:
def update_stem(h_stem, x=None, y=None, bottom=None):
if x is None:
x = h_stem[0].get_xdata()
else:
h_stem[0].set_xdata(x)
h_stem[2].set_xdata([np.min(x), np.max(x)])
if y is None:
y = h_stem[0].get_ydata()
else:
h_stem[0].set_ydata(y)
if bottom is None:
bottom = h_stem[2].get_ydata()[0]
else:
h_stem[2].set_ydata([bottom, bottom])
h_stem[1].set_paths([np.array([[xx, bottom],
[xx, yy]]) for (xx, yy) in zip(x, y)])