Animating a time-dependent LineCollection using matplotlib - python

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)

Related

Need help on animating a 2-D trajectory using FuncAnimation

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()

How do I animate the following lines in matplotlib using my `ndarray`?

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.

animating a stem plot in matplotlib

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)])

Using Matplotlib-Patch inside an animation

I try to generate an empty patch to be able to set data later on. In order to explain my problem better, i will give an example:
from matplotlib import pyplot as plt
import matplotlib.animation as animation
x = range(10)
y = [i**2 for i in x]
figure = plt.figure()
ax1 = figure.add_subplot(111, xlim=(0,10), ylim=(0,100))
my_line, = ax1.plot([],[], 'o-')
def init():
my_line.set_data([], [])
return my_line,
i = 0
def animate(_):
global i
my_line.set_data(x[0:i], y[0:i])
i = (i+1)%(len(x)+1)
return my_line,
ani = animation.FuncAnimation(figure, animate, repeat=True, blit=True, init_func=init)
plt.show()
Now, I to add a shape, which I define its edge points randomly. I need to use the same structure as I used for plotting lines inside the init() block: my_line.set_data([], []) . However, I couldn't succeed.
I use the same structure as the example provided in the matplotlib tutorial . My verts are generated from a function.
When I try using: foo = patches.PathPatch([], facecolor='red', lw=2, alpha=0.0) I get
<matplotlib.patches.PathPatch at 0x335d390>
But later, I cannot set the path data. I tried using foo.set_data and foo.set_path but PathPatch doesn't have such attributes and therefore, they didn't work. I checked this page but I couldn't get anywhere. I checked all of the tutorials I could find, but none of them helped.
As a workaround, I used ax1.add_patch() command and have set the alpha value to 0. This helped to some extend but, as I have to enter data to be able to use this command, all of the shapes become visible at the final step of the animation for a very short time and, as I save my figure in that moment, it yields unfavorable results.
Any help would be appreciated...
I'm not sure what shape you're using, if you use a polygon, you can update the vertices of a polygon with the set_xy method and create the initial polygon with vertices that are all equal to each other. Example below. If you need a completely arbitrary shape, you might be better off plotting lines and using fill_between to draw it.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import animation
# Create the figure and axis
fig = plt.figure()
ax = plt.axes(xlim=(0, 10), ylim=(0, 10))
# mMke the initial polygon with all vertices set to 0
pts = [[0,0], [0,0], [0,0], [0,0]]
patch = plt.Polygon(pts)
ax.add_patch(patch)
def init():
return patch,
def animate(i):
# Randomly set the vertices
x1= 5*np.random.rand((1))[0]
x2= 5*np.random.rand((1))[0]
x3= 5*np.random.rand((1))[0] + 5
x4= 5*np.random.rand((1))[0] + 5
y1= 5*np.random.rand((1))[0]
y2= 5*np.random.rand((1))[0]
y3= 5*np.random.rand((1))[0] + 5
y4= 5*np.random.rand((1))[0] + 5
patch.set_xy([[x1,y1], [x2,y2], [x3,y3], [x4,y4]])
return patch,
anim = animation.FuncAnimation(fig,animate,init_func=init,frames=36,interval=1000,blit=True)
plt.show()

How to update a plot in matplotlib

I'm having issues with redrawing the figure here. I allow the user to specify the units in the time scale (x-axis) and then I recalculate and call this function plots(). I want the plot to simply update, not append another plot to the figure.
def plots():
global vlgaBuffSorted
cntr()
result = collections.defaultdict(list)
for d in vlgaBuffSorted:
result[d['event']].append(d)
result_list = result.values()
f = Figure()
graph1 = f.add_subplot(211)
graph2 = f.add_subplot(212,sharex=graph1)
for item in result_list:
tL = []
vgsL = []
vdsL = []
isubL = []
for dict in item:
tL.append(dict['time'])
vgsL.append(dict['vgs'])
vdsL.append(dict['vds'])
isubL.append(dict['isub'])
graph1.plot(tL,vdsL,'bo',label='a')
graph1.plot(tL,vgsL,'rp',label='b')
graph2.plot(tL,isubL,'b-',label='c')
plotCanvas = FigureCanvasTkAgg(f, pltFrame)
toolbar = NavigationToolbar2TkAgg(plotCanvas, pltFrame)
toolbar.pack(side=BOTTOM)
plotCanvas.get_tk_widget().pack(side=TOP)
You essentially have two options:
Do exactly what you're currently doing, but call graph1.clear() and graph2.clear() before replotting the data. This is the slowest, but most simplest and most robust option.
Instead of replotting, you can just update the data of the plot objects. You'll need to make some changes in your code, but this should be much, much faster than replotting things every time. However, the shape of the data that you're plotting can't change, and if the range of your data is changing, you'll need to manually reset the x and y axis limits.
To give an example of the second option:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)
# You probably won't need this if you're embedding things in a tkinter plot...
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y, 'r-') # Returns a tuple of line objects, thus the comma
for phase in np.linspace(0, 10*np.pi, 500):
line1.set_ydata(np.sin(x + phase))
fig.canvas.draw()
fig.canvas.flush_events()
You can also do like the following:
This will draw a 10x1 random matrix data on the plot for 50 cycles of the for loop.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
for i in range(50):
y = np.random.random([10,1])
plt.plot(y)
plt.draw()
plt.pause(0.0001)
plt.clf()
This worked for me. Repeatedly calls a function updating the graph every time.
import matplotlib.pyplot as plt
import matplotlib.animation as anim
def plot_cont(fun, xmax):
y = []
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
def update(i):
yi = fun()
y.append(yi)
x = range(len(y))
ax.clear()
ax.plot(x, y)
print i, ': ', yi
a = anim.FuncAnimation(fig, update, frames=xmax, repeat=False)
plt.show()
"fun" is a function that returns an integer.
FuncAnimation will repeatedly call "update", it will do that "xmax" times.
This worked for me:
from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
clear_output(wait=True)
y = np.random.random([10,1])
plt.plot(y)
plt.show()
I have released a package called python-drawnow that provides functionality to let a figure update, typically called within a for loop, similar to Matlab's drawnow.
An example usage:
from pylab import figure, plot, ion, linspace, arange, sin, pi
def draw_fig():
# can be arbitrarily complex; just to draw a figure
#figure() # don't call!
plot(t, x)
#show() # don't call!
N = 1e3
figure() # call here instead!
ion() # enable interactivity
t = linspace(0, 2*pi, num=N)
for i in arange(100):
x = sin(2 * pi * i**2 * t / 100.0)
drawnow(draw_fig)
This package works with any matplotlib figure and provides options to wait after each figure update or drop into the debugger.
In case anyone comes across this article looking for what I was looking for, I found examples at
How to visualize scalar 2D data with Matplotlib?
and
http://mri.brechmos.org/2009/07/automatically-update-a-figure-in-a-loop
(on web.archive.org)
then modified them to use imshow with an input stack of frames, instead of generating and using contours on the fly.
Starting with a 3D array of images of shape (nBins, nBins, nBins), called frames.
def animate_frames(frames):
nBins = frames.shape[0]
frame = frames[0]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
for k in range(nBins):
frame = frames[k]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
del tempCS1
fig.canvas.draw()
#time.sleep(1e-2) #unnecessary, but useful
fig.clf()
fig = plt.figure()
ax = fig.add_subplot(111)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate_frames, frames)
I also found a much simpler way to go about this whole process, albeit less robust:
fig = plt.figure()
for k in range(nBins):
plt.clf()
plt.imshow(frames[k],cmap=plt.cm.gray)
fig.canvas.draw()
time.sleep(1e-6) #unnecessary, but useful
Note that both of these only seem to work with ipython --pylab=tk, a.k.a.backend = TkAgg
Thank you for the help with everything.
All of the above might be true, however for me "online-updating" of figures only works with some backends, specifically wx. You just might try to change to this, e.g. by starting ipython/pylab by ipython --pylab=wx! Good luck!
Based on the other answers, I wrapped the figure's update in a python decorator to separate the plot's update mechanism from the actual plot. This way, it is much easier to update any plot.
def plotlive(func):
plt.ion()
#functools.wraps(func)
def new_func(*args, **kwargs):
# Clear all axes in the current figure.
axes = plt.gcf().get_axes()
for axis in axes:
axis.cla()
# Call func to plot something
result = func(*args, **kwargs)
# Draw the plot
plt.draw()
plt.pause(0.01)
return result
return new_func
Usage example
And then you can use it like any other decorator.
#plotlive
def plot_something_live(ax, x, y):
ax.plot(x, y)
ax.set_ylim([0, 100])
The only constraint is that you have to create the figure before the loop:
fig, ax = plt.subplots()
for i in range(100):
x = np.arange(100)
y = np.full([100], fill_value=i)
plot_something_live(ax, x, y)

Categories

Resources