I'm having issues with a slow animation in Matplotlib. I'm animating results from a simulation, which is easiest visualized with an array of rectangles that change color with time.
Following recommendations here, I'm using blitting to only draw the (small fraction) of rectangles that change in each frame. I also tried to implement this using FuncAnimation, but when using that with Blit=True, the script runs much slower.
I'm wondering if this is because I'm returning all of the rectangles to FuncAnimation, so it redraws all of them even if they haven't changed. Is there a way to pass different artists at each frame to FuncAnimation? I tried just passing a tuple of the ones that had changed (the commented out block in the "animate" function), but that led to seemingly random animation frames...
Use:
$ python2 [script].py blit
$ python2 [script].py anim
Thanks!
import sys
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import matplotlib.animation as manim
def animate_data(plot_type):
"""
Use:
python2 plot_anim.py [option]
option = anim OR blit
"""
# dimension parameters
Nx = 30
Ny = 20
numtimes = 100
size = 0.5
if plot_type == "blit":
# "interactive mode on"
plt.ion()
# Prepare to do initial plot
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.set_aspect('equal', 'box')
ax.xaxis.set_major_locator(plt.NullLocator())
ax.yaxis.set_major_locator(plt.NullLocator())
# An array in which to store the rectangle artists
rects = np.empty((Nx, Ny), dtype=object)
# Generate initial figure of all green rectangles
for (i,j),k in np.ndenumerate(rects):
color = 'green'
rects[i, j] = plt.Rectangle([i - size / 2, j - size / 2],
size, size, facecolor=color, edgecolor=color)
ax.add_patch(rects[i, j])
ax.autoscale_view()
# "Old" method using fig.canvas.blit()
if plot_type == "blit":
plt.show()
fig.canvas.draw()
# Step through time updating the rectangles
for tind in range(1, numtimes):
updated_array = update_colors(rects)
for (i, j), val in np.ndenumerate(updated_array):
if val:
ax.draw_artist(rects[i, j])
fig.canvas.blit(ax.bbox)
# New method using FuncAnimate
elif plot_type == "anim":
def animate(tind):
updated_array = update_colors(rects)
# # Just pass the updated artists to FuncAnimation
# toupdate = []
# for (i, j), val in np.ndenumerate(updated_array):
# if val:
# toupdate.append(rects[i, j])
# return tuple(toupdate)
return tuple(rects.reshape(-1))
ani = manim.FuncAnimation(fig, animate, frames=numtimes,
interval=10, blit=True, repeat=False)
plt.show()
return
# A function to randomly update a few rectangles
def update_colors(rects):
updated_array = np.zeros(rects.shape)
for (i, j), c in np.ndenumerate(rects):
rand_val = np.random.rand()
if rand_val < 0.003:
rects[i, j].set_facecolor('red')
rects[i, j].set_edgecolor('red')
updated_array[i, j] = 1
return updated_array
if __name__ == "__main__":
if len(sys.argv) > 1:
plot_type = sys.argv[1]
else:
plot_type = "blit"
animate_data(plot_type)
Update 600 rectangles every frame is very slow, cbar_blit mode in your code is faster because you only update the rectangles which's color is changed. You can use PatchCollection to speedup drawing, here is the code:
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import matplotlib.animation as manim
from matplotlib.collections import PatchCollection
Nx = 30
Ny = 20
numtimes = 100
size = 0.5
x, y = np.ogrid[-1:1:30j, -1:1:20j]
data = np.zeros((numtimes, Nx, Ny))
for i in range(numtimes):
data[i] = (x-i*0.02+1)**2 + y**2
colors = plt.cm.rainbow(data)
fig, ax = plt.subplots()
rects = []
for (i,j),c in np.ndenumerate(data[0]):
rect = plt.Rectangle([i - size / 2, j - size / 2],size, size)
rects.append(rect)
collection = PatchCollection(rects, animated=True)
ax.add_collection(collection)
ax.autoscale_view(True)
def animate(tind):
c = colors[tind].reshape(-1, 4)
collection.set_facecolors(c)
return (collection,)
ani = manim.FuncAnimation(fig, animate, frames=numtimes,
interval=10, blit=True, repeat=False)
plt.show()
Related
I am trying to create an animation of a Monte-Carlo estimation of the number pi, for each iteration I would like the numerical estimation to be in text on the plot, but the previous text is not removed and makes the values unreadable. I tried Artist.remove(frame) with no success. The plot is done with Jupiter Notebook.
#Enable interactive plot
%matplotlib notebook
import math
from matplotlib.path import Path
from matplotlib.animation import FuncAnimation
from matplotlib.path import Path
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull
from matplotlib.artist import Artist
N = 10000
#create necessary arrays
x = np.arange(0,N)
y = np.zeros(N)
#set initial points to zero
inHull = 0
def inCircle(point):
#the function is given a point in R^n
#returns a boolean stating if the norm of the point is smaller than 1.
if np.sum(np.square(point)) <= 1:
return True
else:
return False
#iterate over each point
for i in range(N):
random_point = np.random.rand(2)*2 - 1
#determine if the point is inside the hull
if inCircle(random_point):
inHull += 1
#we store areas in array y.
y[i] = (inHull*4)/(i+1)
fig = plt.figure()
ax = plt.subplot(1, 1, 1)
data_skip = 20
def init_func():
ax.clear()
plt.xlabel('n points')
plt.ylabel('Estimated area')
plt.xlim((x[0], x[-1]))
plt.ylim((min(y)- 1, max(y)+0.5))
def update_plot(i):
ax.plot(x[i:i+data_skip], y[i:i+data_skip], color='k')
ax.scatter(x[i], y[i], color='none')
Artist.remove(ax.text(N*0.6, max(y)+0.25, "Estimation: "+ str(round(y[i],5))))
ax.text(N*0.6, max(y)+0.25, "Estimation: "+ str(round(y[i],5)))
anim = FuncAnimation(fig,
update_plot,
frames=np.arange(0, len(x), data_skip),
init_func=init_func,
interval=20)
plt.show()
Thank you.
As you have already done in init_func, you should clear the plot in each iteration with ax.clear(). Then it is necessary to edit slighlty the plot function:
ax.plot(x[i:i+data_skip], y[i:i+data_skip], color='k')
And finally you have to fix x axis limits in each iteration with ax.set_xlim(0, N).
Complete Code
#Enable interactive plot
%matplotlib notebook
import math
from matplotlib.path import Path
from matplotlib.animation import FuncAnimation
from matplotlib.path import Path
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull
from matplotlib.artist import Artist
N = 10000
# create necessary arrays
x = np.arange(0, N)
y = np.zeros(N)
# set initial points to zero
inHull = 0
def inCircle(point):
# the function is given a point in R^n
# returns a boolean stating if the norm of the point is smaller than 1.
if np.sum(np.square(point)) <= 1:
return True
else:
return False
# iterate over each point
for i in range(N):
random_point = np.random.rand(2)*2 - 1
# determine if the point is inside the hull
if inCircle(random_point):
inHull += 1
# we store areas in array y.
y[i] = (inHull*4)/(i + 1)
fig = plt.figure()
ax = plt.subplot(1, 1, 1)
data_skip = 20
txt = ax.text(N*0.6, max(y) + 0.25, "")
def init_func():
ax.clear()
plt.xlabel('n points')
plt.ylabel('Estimated area')
plt.xlim((x[0], x[-1]))
plt.ylim((min(y) - 1, max(y) + 0.5))
def update_plot(i):
ax.clear()
ax.plot(x[:i + data_skip], y[:i + data_skip], color = 'k')
ax.scatter(x[i], y[i], color = 'none')
ax.text(N*0.6, max(y) + 0.25, "Estimation: " + str(round(y[i], 5)))
ax.set_xlim(0, N)
anim = FuncAnimation(fig,
update_plot,
frames = np.arange(0, len(x), data_skip),
init_func = init_func,
interval = 20)
plt.show()
Animation
I am trying to animate a histogram using matplotlib and I want to show the different bars using a colormap, e.g:
I have this working when I clear the complete figure every frame and then redraw everything. But this is very slow, so I am trying out the example by matplotlib itself.
This works and is very fast, but unfortunately I have no idea on how to specify a colormap because it is using the patches.PathPatch object to draw the histogram now. I can only get it to work with the same single color for every individual bar.
How can I specify a gradient or colormap to achieve the desired result shown above?
Here is an example of a working animation with a single color which I am currently using.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
nrects = len(left)
nverts = nrects * (1 + 3 + 1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5, 0] = left
verts[0::5, 1] = bottom
verts[1::5, 0] = left
verts[1::5, 1] = top
verts[2::5, 0] = right
verts[2::5, 1] = top
verts[3::5, 0] = right
verts[3::5, 1] = bottom
patch = None
def animate(i):
# simulate new data coming in
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
top = bottom + n
verts[1::5, 1] = top
verts[2::5, 1] = top
return [patch, ]
fig, ax = plt.subplots()
barpath = path.Path(verts, codes)
patch = patches.PathPatch(
barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
plt.show()
I recommend u using BarContainer, you can change bar color individually. In your example, the path is single object, matplotlib seems not to support gradient color for a single patch (not sure though).
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
# histogram our data with numpy
data = np.random.randn(1000)
colors = plt.cm.coolwarm(np.linspace(0, 1, 100))
def animate(i):
data = np.random.randn(1000)
bc = ax.hist(data, 100)[2]
for i, e in enumerate(bc):
e.set_color(colors[i])
return bc
fig, ax = plt.subplots(1, 1, figsize=(7.2, 7.2))
ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
I have just started learning python to plot realtime gragh. I have tried solutions provided on stackoverflow but none of them are working. Below is my code and it isn't woorking. Please help
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 512
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
def animate(i):
try:
while True:
x, y = pg.position()
f.append(x)
except KeyboardInterrupt:
print('')
# f.append(15)
if len(f)<binSize :
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="Noisy")
else:
plt.cla()
plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Noisy")
ani = FuncAnimation(plt.gcf(),animate,interval=1);
So I have updated the code and trying to draw two subplots but after sometime
Upper graph stopped clearing the canvas (Mouse X coordinates)
Lower graph stopped updating the plot (FFT)
When data grows beyond the binSize, notebook freezes and plots update really slowly
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
def animate(i):
x, y = pg.position()
f.append(x)
n = len(f)
if n<binSize :
plt.sca(axs[0])
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int')
# update the code third time
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
# axs[0].xlim(0,binSize) # this stopped the FFT graph to be plotted
# plt.cla()
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
# plt.xlim(t[0],t[-1])
# plt.legend()
# plt.sca(axs[1])
# plt.plot(freq[L],PSD[L],color='c',LineWidth=2,label="Mouse FFT")
# plt.xlim(0,300)
# plt.legend()
# plt.cla()
# plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Mouse")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
To make it faster you may reduce data like in other answer
f.pop(0)
I use also different method to update plot which works much faster on my computer.
I create empty plots at start
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
and later only update data in plots without clear() and plot() again
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
and
# replace data in plot
xdata = range(binSize)
ydata = f[-binSize:]
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
It needs only to run code which rescale plot
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
animate() has to also return new plots
# return plots
return p1, p2
And FuncAnimation() has to blit them
ani = FuncAnimation(..., blit=True)
EDIT:
Animation works much, much faster also because I run it normally python script.py, not in Jupuyter Notebook
EDIT:
when I run normally I found one problem which I could find solution: it doesn't update values/ticks on axes. Jupyter Notebook doesn't have this problem.
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 256
f = []
t = 0
dt = 1
fig, axs = plt.subplots(2, 1)
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
freq = np.arange(binSize)/(dt*binSize)
def animate(i):
x, y = pg.position()
n = len(f)
if n < binSize :
f.append(x)
# replace data in plot
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f, binSize)
PSD = fhat * np.conj(fhat) / binSize
# replace data in plot
#xdata = range(binSize)
ydata = f[-binSize:]
#p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
# return plots
return p1, p2
ani = FuncAnimation(plt.gcf(), animate, interval=dt, blit=True)
plt.show()
You should try this. Instead of clearing the plt clear axs[0] and so on. Also, instead of plotting on plt.plot, plot on axs[0].plot
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
plt.sca(axs[0])
plt.sca(axs[1])
def animate(i):
x, y = pg.position()
n = len(f)
if n<binSize :
f.append(x*100)
axs[0].clear()
axs[0].plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int') # index array of [1,2,3..... binsize/2] type int
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
plt.show()
I tried to write a simple script which updates a scatter plot for every timestep t. I wanted to do it as simple as possible. But all it does is to open a window where I can see nothing. The window just freezes. It is maybe just an small error, but I can not find it.
The the data.dat has the format
x y
Timestep 1 1 2
3 1
Timestep 2 6 3
2 1
(the file contains just the numbers)
import numpy as np
import matplotlib.pyplot as plt
import time
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
if line:
line = [float(i) for i in line]
particles.append(line)
T = 100
numbParticles = 2
x, y = np.array([]), np.array([])
plt.ion()
plt.figure()
plt.scatter(x,y)
for t in range(T):
plt.clf()
for k in range(numbP):
x = np.append(x, particles[numbParticles*t+k][0])
y = np.append(y, particles[numbParticles*t+k][1])
plt.scatter(x,y)
plt.draw()
time.sleep(1)
x, y = np.array([]), np.array([])
The simplest, cleanest way to make an animation is to use the matplotlib.animation module.
Since a scatter plot returns a matplotlib.collections.PathCollection, the way to update it is to call its set_offsets method. You can pass it an array of shape (N, 2) or a list of N 2-tuples -- each 2-tuple being an (x,y) coordinate.
For example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 100
numbParticles = 2
particles = np.random.random((T,numbParticles)).tolist()
x, y = np.array([]), np.array([])
def init():
pathcol.set_offsets([[], []])
return [pathcol]
def update(i, pathcol, particles):
pathcol.set_offsets(particles[i])
return [pathcol]
fig = plt.figure()
xs, ys = zip(*particles)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))
pathcol = plt.scatter([], [], s=100)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(pathcol, particles), interval=1000, frames=T,
blit=True, repeat=True)
plt.show()
I finally found a solution. You can do it simply by using this script. I tried to keep it simple:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Helps me to get the data from the file I want to plot
N = 0
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
particles.append(line)
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=True)
border = 100
ax.set_xlim(-border, border), ax.set_xticks([])
ax.set_ylim(-border, border), ax.set_yticks([])
# particle data
p = 18 # number of particles
myPa = np.zeros(p, dtype=[('position', float, 2)])
# Construct the scatter which we will update during animation
scat = ax.scatter(myPa['position'][:, 0], myPa['position'][:, 1])
def update(frame_number):
# New positions
myPa['position'][:] = particles[N*p:N*p+p]
# Update the scatter collection, with the new colors, sizes and positions.
scat.set_offsets(myPa['position'])
increment()
def increment():
global N
N = N+1
# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=20)
plt.show()
I tried to write a simple script which updates a scatter plot for every timestep t. I wanted to do it as simple as possible. But all it does is to open a window where I can see nothing. The window just freezes. It is maybe just an small error, but I can not find it.
The the data.dat has the format
x y
Timestep 1 1 2
3 1
Timestep 2 6 3
2 1
(the file contains just the numbers)
import numpy as np
import matplotlib.pyplot as plt
import time
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
if line:
line = [float(i) for i in line]
particles.append(line)
T = 100
numbParticles = 2
x, y = np.array([]), np.array([])
plt.ion()
plt.figure()
plt.scatter(x,y)
for t in range(T):
plt.clf()
for k in range(numbP):
x = np.append(x, particles[numbParticles*t+k][0])
y = np.append(y, particles[numbParticles*t+k][1])
plt.scatter(x,y)
plt.draw()
time.sleep(1)
x, y = np.array([]), np.array([])
The simplest, cleanest way to make an animation is to use the matplotlib.animation module.
Since a scatter plot returns a matplotlib.collections.PathCollection, the way to update it is to call its set_offsets method. You can pass it an array of shape (N, 2) or a list of N 2-tuples -- each 2-tuple being an (x,y) coordinate.
For example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 100
numbParticles = 2
particles = np.random.random((T,numbParticles)).tolist()
x, y = np.array([]), np.array([])
def init():
pathcol.set_offsets([[], []])
return [pathcol]
def update(i, pathcol, particles):
pathcol.set_offsets(particles[i])
return [pathcol]
fig = plt.figure()
xs, ys = zip(*particles)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))
pathcol = plt.scatter([], [], s=100)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(pathcol, particles), interval=1000, frames=T,
blit=True, repeat=True)
plt.show()
I finally found a solution. You can do it simply by using this script. I tried to keep it simple:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Helps me to get the data from the file I want to plot
N = 0
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
particles.append(line)
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=True)
border = 100
ax.set_xlim(-border, border), ax.set_xticks([])
ax.set_ylim(-border, border), ax.set_yticks([])
# particle data
p = 18 # number of particles
myPa = np.zeros(p, dtype=[('position', float, 2)])
# Construct the scatter which we will update during animation
scat = ax.scatter(myPa['position'][:, 0], myPa['position'][:, 1])
def update(frame_number):
# New positions
myPa['position'][:] = particles[N*p:N*p+p]
# Update the scatter collection, with the new colors, sizes and positions.
scat.set_offsets(myPa['position'])
increment()
def increment():
global N
N = N+1
# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=20)
plt.show()