Python: Animated 3D Scatterplot gets slow - python

My program plots the positions of particles in my file for every time step. Unfortunately it gets slower and slower although I used matplotlib.animation. Where is the bottleneck?
My data file for two particles looks like the following:
# x y z
# t1 1 2 4
# 4 1 3
# t2 4 0 4
# 3 2 9
# t3 ...
My script:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
with open('//home//data.dat', 'r') as fp:
particleData = []
for line in fp:
line = line.split()
particleData.append(line)
x = [float(item[0]) for item in particleData]
y = [float(item[1]) for item in particleData]
z = [float(item[2]) for item in particleData]
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
def animate(i):
global x, y, z, numP
#ax.clear()
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
idx0 = i*numP
idx1 = numP*(i+1)
ax.scatter(x[idx0:idx1],y[idx0:idx1],z[idx0:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()

I would suggest to use pyqtgraph in this case. Citation from the docs:
Its primary goals are 1) to provide fast, interactive graphics for
displaying data (plots, video, etc.) and 2) to provide tools to aid in
rapid application development (for example, property trees such as
used in Qt Designer).
You can check out some examples after the installation:
import pyqtgraph.examples
pyqtgraph.examples.run()
This small code snippet generates 1000 random points and displays them in a 3D scatter plot by constantly updating the opacity, similar to the 3D scatter plot example in pyqtgraph.examples:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
#generate random points from -10 to 10, z-axis positive
pos = np.random.randint(-10,10,size=(1000,3))
pos[:,2] = np.abs(pos[:,2])
sp2 = gl.GLScatterPlotItem(pos=pos)
w.addItem(sp2)
#generate a color opacity gradient
color = np.zeros((pos.shape[0],4), dtype=np.float32)
color[:,0] = 1
color[:,1] = 0
color[:,2] = 0.5
color[0:100,3] = np.arange(0,100)/100.
def update():
## update volume colors
global color
color = np.roll(color,1, axis=0)
sp2.setData(color=color)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Small gif to give you an idea of the performance:
EDIT:
Displaying multiple points at every single time step is a little bit tricky since the gl.GLScatterPlotItem takes only (N,3)-arrays as point locations, see here. You could try to make a dictionary of ScatterPlotItems where each of them includes all time steps for a specific point. Then one would need to adapt the update function accordingly. You can find an example below where pos is an (100,10,3)-array representing 100 time steps for each point. I reduced the update time to 1000 ms for a slower animation.
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
pos = np.random.randint(-10,10,size=(100,10,3))
pos[:,:,2] = np.abs(pos[:,:,2])
ScatterPlotItems = {}
for point in np.arange(10):
ScatterPlotItems[point] = gl.GLScatterPlotItem(pos=pos[:,point,:])
w.addItem(ScatterPlotItems[point])
color = np.zeros((pos.shape[0],10,4), dtype=np.float32)
color[:,:,0] = 1
color[:,:,1] = 0
color[:,:,2] = 0.5
color[0:5,:,3] = np.tile(np.arange(1,6)/5., (10,1)).T
def update():
## update volume colors
global color
for point in np.arange(10):
ScatterPlotItems[point].setData(color=color[:,point,:])
color = np.roll(color,1, axis=0)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(1000)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Keep in mind that in this examples, all points are shown in the scatter plot, however, the color opacity (4th dimension in the color array) is updated in every time step to get an animation. You could also try to update the points instead of the color to get better performance...

I would guess your bottleneck is calling ax.scatter and ax.set_xlim3d and similar in every frame in the animation.
Ideally, you should make a call to scatter once, then use the object returned by scatter and its set_... properties in the animate function (more details here).
I can't figure out how to do it with scatter, but if you use ax.plot(x, y, z, 'o') instead, you can then follow the demo method here.
Using some random data for x, y, z. It would work like this
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
from numpy.random import random
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
x, y, z = random(timesteps), random(timesteps), random(timesteps)
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
line = ax.plot(x[:1], y[:1], z[:1], 'o')[0]
def animate(i):
global x, y, z, numP
idx1 = numP*(i+1)
# join x and y into single 2 x N array
xy_data = np.c_[x[:idx1], y[:idx1]].T
line.set_data(xy_data)
line.set_3d_properties(z[:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()

Related

How to draw animation by taking snapshot with matplotlib?

In my project, I have many polygons to draw for each time step.
At each step, the number of polygons varies, thus it is difficult to keep Axes.patchs and translate them to make the animation.
I want to create animation with final figures (show after calling matplotlib.pyplot.show()), how to do this?
We take the sin curve as example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ims = []
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
z = np.cos(x)
for i in range(1,100):
tmpx = x[:i]
tmpy = y[:i]
tmpz = z[:i]
plt.plot(tmpx, tmpz)
im = plt.plot(tmpx, tmpy)
ims.append(im)
ani = animation.ArtistAnimation(fig, ims, interval=200)
ani.save('/home/test.gif', writer='imagemagick')
plt.show()
There are two curves: animated-sin-curve and static-cos-curve.
the sin-curve is kept as Line2D objects for each step
the cos-curve stay static for each step.
In this way, we show different Artist object for each step.
But I want to keep the rasterized Line2D figure for each step.
I find classes of AxesImage/FigureImage, but I don't know how to save the rasterized figure and make them work.
I tried to convert figure.canvas to AxesImage with following code :
def fig2AxesImage(fig):
import PIL.Image as Image
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
buf = numpy.fromstring(fig.canvas.tostring_argb(), dtype=numpy.uint8)
buf.shape = (w, h, 4)
# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = numpy.roll(buf, 3, axis=2)
image = Image.frombytes("RGBA", (w, h), buf.tostring())
image = numpy.asarray(image)
return plt.imshow(image, animated=True)
but with this way, I have to clear canvas at start of next frame, which make the final animation a blank video. (but the .jpg figures I output for each step get the right content)
Does anyone have done this before that save rasterized canvas-figures of matplotlib.pyplot.figure() as a animation Vedio?
celluloid for python 2.7
''' copy from celluloid'''
# from typing import Dict, List # not supported by python 2.7. So comment it
from collections import defaultdict
from matplotlib.figure import Figure
from matplotlib.artist import Artist
from matplotlib.animation import ArtistAnimation
__version__ = '0.2.0'
class Camera:
def __init__(self, figure):
self.figure_ = figure
self.offsets_ = { k:defaultdict(int) \
for k in ['collections', 'patches', 'lines', 'texts', 'artists', 'images']
}
self.photos_ = []
def snap(self):
frame_artists = []
for i, axis in enumerate(self.figure_.axes):
if axis.legend_ is not None:
axis.add_artist(axis.legend_)
for name in self.offsets_:
new_artists = getattr(axis, name)[self.offsets_[name][i]:]
frame_artists += new_artists
self.offsets_[name][i] += len(new_artists)
self.photos_.append(frame_artists)
def animate(self):
return ArtistAnimation(self.figure_, self.photos_)

Updateing multi-plot matplotlib

I have a problem witch updateing matplotlib chart. The problem is that i have many curve's on it, and after update the number of them may change. In example code I have 2 sets of data, 1st with 90 curves, and 2nd with 80, and i wish I could plot 1st set, and then 2nd, in the same matplotlib window.
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
import numpy as np
from numpy.lib.polynomial import RankWarning
import pandas as pd
import sys
fig, ax = plt.subplots()
fig.subplots_adjust(right=0.78)
_x = []
_y = []
_y1 = []
_x1 = []
for x in range(90):
_x.append(np.linspace(0, 10*np.pi, 100))
_y.append(np.sin(_x[x])+x)
for x in range(80):
_x1.append(np.linspace(0, 10*np.pi, 150))
_y1.append(np.tan(_x1[x]+x))
def narysuj(__x, __y):
p = [] # p-pomiar
f = [] # f-czestotliwosc
for x in range(len(__x)):
p.append([])
f.append([])
ax.set_prop_cycle(color=plt.cm.gist_rainbow(np.linspace(0, 1, len(__x))))
for x in range(len(__x)):
for line in range(len(__x[x])):
#print(len(_y[x]), line)
p[x].append(__y[x][line])
f[x].append(__x[x][line])
ax.plot(f[x], p[x], label=f"Label {x}")
plt.show()
narysuj(_x, _y)
narysuj(_x1, _y1)
PS I know the way I'm drawing those charts is highly ineffective.
I found what was the problem. I had to add plt.ion() at the start of program and ax.clear() before drawing.

Pycharm SciView truncate history

I am trying to create a program that can visualize the change of a portfolio in real time. To do this, I update my data and create a new plot with it. When I run the code below in PyCharm, SciView stops displaying the plots after 30 iterations. Ideally, I would like to have it only show the most recent plot, but it would also be fine if it just truncated the history so that I at least always see the current plot. Is there any way to do this? I tried different ways to close the figures (e. g. using plt.close()), but did not achieve the desired result.
Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def create_plot(self):
"""
Takes an x and a y (both 1D arrays and constructs a plot from it)
:return: a pyplot figure object
"""
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Draw x and y lists
ax.clear()
ax.plot(self.x, self.y)
# Format plot
plt.xticks(rotation=90)
plt.title('Portfolio')
plt.ylabel('Value')
plt.show()
plt.close('all')
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.create_plot()
Rather than creating a new plot and window every time, you can also update the current Matplotlib figure data in each iteration. You then need to view the plot in an interactive Matplotlib environment.
Live updating Matplotlib plots
You can use code similar to this to update the data inside the plot:
import matplotlib.pyplot as plt
import random
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
# Variables for our updating data
x = []
y = []
for i in range(50):
# Generate random data
x.append(i)
y.append(random.random())
# Update the plot with the new x, y data
ax.plot(x, y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
Allow for interactive Matplotlib mode when using SciView
Deactivate SciView or manually set your backend to another interactive GUI to see the updating plot.
This code snipped automatically chooses the correct backend (same list as in the Matplotlib code):
import matplotlib.pyplot as plt
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
Applied to your code
Your code with suggested modifications would look like this:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def update_plot(self, fig, ax):
import _tkinter
try:
ax.plot(self.x, self.y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
# Capture an error in case the plotting window is being closed
except _tkinter.TclError:
pass
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
# Choose the right backend
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
# Create plot
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.update_plot(fig, ax) # Update the plot the new data
Same issue here.
The workaround I found is to change the matplotlib backend to plot outside the PyCharm.
import matplotlib
matplotlib.use('qt5Agg')
matplotlib.pyplot.ioff()
Then you have to explicit open a new figure and show
for i in range(100):
plt.figure()
...
...
plt.show()

Cannot save animated gif with transparent background (matplotlib.animation)

I am trying to save an animation with a completely transparent background. Setting:
fig1 = (...,facecolor=(1,1,1,0))
Does not seem to work. Also, just as a side note, if you do that and view the plot then you get these weird transparency effects and lagging animation. Curious why that happens too, but mostly I just want the background to save as transparent.
If I then try:
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"facecolor": (1,1,1,0)})
Then I get an output which does not have a transparent background and makes the lines thick. Same curiosity as above as why making the figure alpha to 0 would give this effect.
Another attempt:
fig1 = (...,facecolor=(1,1,1,0))
line_ani.save(...,savefig_kwargs={"transparent": None})
Also doesn't produce a transparent background.
If I just include the facecolor in the dictionary, then it gives the undesired line thickening bug.
line_ani.save(...,savefig_kwargs={"transparent": None,"facecolor":(1,1,1,0)})
The code is below.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
from matplotlib.pyplot import figure
def update_line(num, data, line):
line.set_data(data[..., :num])
return line,
def plots():
plt.xlim(-1, 1)
plt.ylim(-1, 1)
plt.xticks([])
plt.yticks([])
plt.box()
# Since I'm calling things twice, it's convenient to define these
fs = (3,3)
inter = 100
frames = 219
lw = 0.25
alph = 0
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth =lw)
# Generate placeholder for data and set initial conditions
DAT =np.zeros((2,300))
DAT[0][0] = 0
DAT[1][0] = 1
theta=2*np.pi*(1/np.e +0.01)
# 2D Rotation Matrix
def R(x):
return [[np.cos(x),-np.sin(x)],[np.sin(x),np.cos(x)]]
# Generate the data
for i in range(len(DAT[0])):
if i < len(DAT[0])-1:
DAT[0][i+1]=DAT[0][i]*R(theta)[0][0] + DAT[1][i]*R(theta)[0][1]
DAT[1][i+1]=DAT[0][i]*R(theta)[1][0] + DAT[1][i]*R(theta)[1][1]
# Animate the data
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
plt.show()
# Save the animation
matplotlib.use("Agg")
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth = lw)
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
print("Saving animation...")
now=time.time()
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": None})
later = time.time()
print("Saved in time: ", int(later-now),"seconds")
If you run the code it should show you the animation and then save it. It also will calculate the runtime.
Setting transparent = True does the trick...
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": True})

Matplotlib animation function: plots points but no animation [duplicate]

I'm trying to do an animation of a scatter plot where colors and size of the points changes at different stage of the animation. For data I have two numpy ndarray with an x value and y value:
data.shape = (ntime, npoint)
x.shape = (npoint)
y.shape = (npoint)
Now I want to plot a scatter plot of the type
pylab.scatter(x,y,c=data[i,:])
and create an animation over the index i. How do I do this?
Suppose you have a scatter plot, scat = ax.scatter(...), then you can
change the positions
scat.set_offsets(array)
where array is a N x 2 shaped array of x and y coordinates.
change the sizes
scat.set_sizes(array)
where array is a 1D array of sizes in points.
change the color
scat.set_array(array)
where array is a 1D array of values which will be colormapped.
Here's a quick example using the animation module.
It's slightly more complex than it has to be, but this should give you a framework to do fancier things.
(Code edited April 2019 to be compatible with current versions. For the older code see revision history)
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
class AnimatedScatter(object):
"""An animated scatter plot using matplotlib.animations.FuncAnimation."""
def __init__(self, numpoints=50):
self.numpoints = numpoints
self.stream = self.data_stream()
# Setup the figure and axes...
self.fig, self.ax = plt.subplots()
# Then setup FuncAnimation.
self.ani = animation.FuncAnimation(self.fig, self.update, interval=5,
init_func=self.setup_plot, blit=True)
def setup_plot(self):
"""Initial drawing of the scatter plot."""
x, y, s, c = next(self.stream).T
self.scat = self.ax.scatter(x, y, c=c, s=s, vmin=0, vmax=1,
cmap="jet", edgecolor="k")
self.ax.axis([-10, 10, -10, 10])
# For FuncAnimation's sake, we need to return the artist we'll be using
# Note that it expects a sequence of artists, thus the trailing comma.
return self.scat,
def data_stream(self):
"""Generate a random walk (brownian motion). Data is scaled to produce
a soft "flickering" effect."""
xy = (np.random.random((self.numpoints, 2))-0.5)*10
s, c = np.random.random((self.numpoints, 2)).T
while True:
xy += 0.03 * (np.random.random((self.numpoints, 2)) - 0.5)
s += 0.05 * (np.random.random(self.numpoints) - 0.5)
c += 0.02 * (np.random.random(self.numpoints) - 0.5)
yield np.c_[xy[:,0], xy[:,1], s, c]
def update(self, i):
"""Update the scatter plot."""
data = next(self.stream)
# Set x and y data...
self.scat.set_offsets(data[:, :2])
# Set sizes...
self.scat.set_sizes(300 * abs(data[:, 2])**1.5 + 100)
# Set colors..
self.scat.set_array(data[:, 3])
# We need to return the updated artist for FuncAnimation to draw..
# Note that it expects a sequence of artists, thus the trailing comma.
return self.scat,
if __name__ == '__main__':
a = AnimatedScatter()
plt.show()
If you're on OSX and using the OSX backend, you'll need to change blit=True to blit=False in the FuncAnimation initialization below. The OSX backend doesn't fully support blitting. The performance will suffer, but the example should run correctly on OSX with blitting disabled.
For a simpler example, which just updates the colors, have a look at the following:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
def main():
numframes = 100
numpoints = 10
color_data = np.random.random((numframes, numpoints))
x, y, c = np.random.random((3, numpoints))
fig = plt.figure()
scat = plt.scatter(x, y, c=c, s=100)
ani = animation.FuncAnimation(fig, update_plot, frames=range(numframes),
fargs=(color_data, scat))
plt.show()
def update_plot(i, data, scat):
scat.set_array(data[i])
return scat,
main()
I wrote celluloid to make this easier. It's probably easiest to show by example:
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from celluloid import Camera
numpoints = 10
points = np.random.random((2, numpoints))
colors = cm.rainbow(np.linspace(0, 1, numpoints))
camera = Camera(plt.figure())
for _ in range(100):
points += 0.1 * (np.random.random((2, numpoints)) - .5)
plt.scatter(*points, c=colors, s=100)
camera.snap()
anim = camera.animate(blit=True)
anim.save('scatter.mp4')
It uses ArtistAnimation under the hood. camera.snap captures the current state of the figure which is used to create the frames in the animation.
Edit: To quantify how much memory this uses I ran it through memory_profiler.
Line # Mem usage Increment Line Contents
================================================
11 65.2 MiB 65.2 MiB #profile
12 def main():
13 65.2 MiB 0.0 MiB numpoints = 10
14 65.2 MiB 0.0 MiB points = np.random.random((2, numpoints))
15 65.2 MiB 0.1 MiB colors = cm.rainbow(np.linspace(0, 1, numpoints))
16 65.9 MiB 0.6 MiB fig = plt.figure()
17 65.9 MiB 0.0 MiB camera = Camera(fig)
18 67.8 MiB 0.0 MiB for _ in range(100):
19 67.8 MiB 0.0 MiB points += 0.1 * (np.random.random((2, numpoints)) - .5)
20 67.8 MiB 1.9 MiB plt.scatter(*points, c=colors, s=100)
21 67.8 MiB 0.0 MiB camera.snap()
22 70.1 MiB 2.3 MiB anim = camera.animate(blit=True)
23 72.1 MiB 1.9 MiB anim.save('scatter.mp4')
To summarize this:
Creating 100 plots used 1.9 MiB.
Making the animation used 2.3 MiB.
This method of making animations used 4.2 MiB of memory in sum.
TL/DR: If you are having trouble with the ax.set_... methods for animating your scatter plot, you can try to just clear the plot each frame (i.e., ax.clear()) and re-plot things as desired. This is slower, but might be useful when you want to change a lot of things in a small animation.
Here is an example demonstrating this "clear" approach:
import itertools
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
# set parameters
frames = 10
points = 20
np.random.seed(42)
# create data
data = np.random.rand(points, 2)
# set how the graph will change each frame
sizes = itertools.cycle([10, 50, 150])
colors = np.random.rand(frames, points)
colormaps = itertools.cycle(['Purples', 'Blues', 'Greens', 'Oranges', 'Reds'])
markers = itertools.cycle(['o', 'v', '^', 's', 'p'])
# init the figure
fig, ax = plt.subplots(figsize=(5,5))
def update(i):
# clear the axis each frame
ax.clear()
# replot things
ax.scatter(data[:, 0], data[:, 1],
s=next(sizes),
c=colors[i, :],
cmap=next(colormaps),
marker=next(markers))
# reformat things
ax.set_xlabel('world')
ax.set_ylabel('hello')
ani = animation.FuncAnimation(fig, update, frames=frames, interval=500)
ani.save('scatter.gif', writer='pillow')
The tutorials I have seen from matplotlib and other sources do not seem to use this approach, but I have seen others (as well as myself) suggest it on this site. I see some pros & cons, but I would appreciate anyone else's thoughts:
Pros
You can avoid using the set_... methods for the scatter plot (i.e. .set_offsets(), .set_sizes(), ...), which have more obscure and less-detailed documentation (though the leading answer will get you very far here!). Plus, there are different methods for different plot types (e.g. you use set_data for lines, but not for scatter points). By clearing the axis, you determine the plotted elements each frame like any other plot in matplotlib.
Even more so, it is unclear if some properties are set-able, such as the marker type (as commented) or the colormap. I wouldn't know how to create the above plot using ax.set_..., for example, because of the marker and colormap changes. But this is pretty basic with ax.scatter().
Cons
It can be much slower; i.e. clearing and redrawing everything appears to be more expensive than the set... methods. So for large animations, this approach can be kind of painful.
Clearing the axis also clears things like the axis labels, axis limits, other text, etc. So, those sorts of formatting things need to be included in update (else they will be gone). This can be annoying if you want some things to change, but others to stay the same.
Of course, the speed is a big con. Here is an example showing the difference. Given this data:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
np.random.seed(42)
frames = 40
x = np.arange(frames)
y = np.sin(x)
colors = itertools.cycle(['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])
data = [(np.random.uniform(-1, 1, 10) + x[i],
np.random.uniform(-1, 1, 10) + y[i])
for i in range(frames)]
You can plot using the set... method:
fig, ax = plt.subplots()
s = ax.scatter([], [])
ax.set_xlim(-2, frames+2)
ax.set_ylim(min(y) - 1, max(y) + 1)
def update(i):
s.set_offsets(np.column_stack([data[i][0], data[i][1]]))
s.set_facecolor(next(colors))
ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('set.gif', writer='pillow')
Or the "clear" method:
fig, ax = plt.subplots()
def update(i):
ax.clear()
ax.scatter(data[i][0], data[i][1], c=next(colors))
ax.set_xlim(-2, frames+2)
ax.set_ylim(min(y) - 1, max(y) + 1)
ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('clear.gif', writer='pillow')
To get this figure:
Using %%time, we can see that clearing and replotting takes (more than) twice as long:
for set...: Wall time: 1.33 s
for clear: Wall time: 2.73 s
Play with the frames parameter to test this at different scales. For smaller animations (less frames/data), the time difference between the two methods is inconsequential (and for me, sometimes causes me to prefer the clearing method). But for larger cases, using set_... can save significant time.
Here is the thing. I used to a user of Qt and Matlab and I am not quite familiar with the animation system on the matplotlib.
But I do have find a way that can make any kind of animation you want just like it is in matlab. It is really powerful. No need to check the module references and you are good to plot anything you want. So I hope it can help.
The basic idea is to use the time event inside PyQt( I am sure other Gui system on the Python like wxPython and TraitUi has the same inner mechanism to make an event response. But I just don't know how). Every time a PyQt's Timer event is called I refresh the whole canvas and redraw the whole picture, I know the speed and performance may be slowly influenced but it is not that much.
Here is a little example of it:
import sys
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
class Monitor(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.x = np.linspace(0,5*np.pi,400)
self.p = 0.0
self.y = np.sin(self.x+self.p)
self.line = self.ax.scatter(self.x,self.y)
self.fig.canvas.draw()
self.timer = self.startTimer(100)
def timerEvent(self, evt):
# update the height of the bars, one liner is easier
self.p += 0.1
self.y = np.sin(self.x+self.p)
self.ax.cla()
self.line = self.ax.scatter(self.x,self.y)
self.fig.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = Monitor()
w.setWindowTitle("Convergence")
w.show()
sys.exit(app.exec_())
You can adjust the refresh speed in the
self.timer = self.startTimer(100)
I am just like you who want to use the Animated scatter plot to make a sorting animation. But I just cannot find a so called "set" function. So I refreshed the whole canva.
Hope it helps..
Why Not try this
import numpy as np
import matplotlib.pyplot as plt
x=np.random.random()
y=np.random.random()
fig, ax = plt.subplots()
ax.scatter(x,y,color='teal')
ax.scatter(y,x,color='crimson')
ax.set_xlim([0,1])
ax.set_ylim([0,1])
for i in np.arange(50):
x=np.random.random()
y=np.random.random()
bha=ax.scatter(x,y)
plt.draw()
plt.pause(0.5)
bha.remove()
plt.show()

Categories

Resources