Related
The following gif is created with gnuplot and Fortran. However, now I want to do the same using only Python (animation form matplotlib mainly).
I can generate a gif with Python but I don't know how to generate something like the righthand side gif (you plot also the last 100 points), the evolution in phase space.
Any help will be apreciated,
Thanks
Gnuplot code for the gif:
set term gif size 1000,600 animate delay 1000 loop 0
set output "animacio.gif"
cd 'C:\Users\Usuario\Desktop'
datafile ="P7-1718-b-res.dat"
do for[i=1:5000:10]{
set multiplot
set size 0.5,0.8
set origin 0.0,0.0
set title "EvoluciĆ³ de l'angle girat i velocitat angular (t)"
set xrange[0:50]
set yrange[-pi:pi]
set xlabel "t (s)"
set ylabel "Angle girat, v_{ang}"
set key below
plot datafile index 9 every ::1::i with line linewidth 4 t"PosiciĆ³ angular" ,datafile index 9 every ::1::i u 1:3 with line linewidth 4 t"V_{ang}"
set origin 0.5,0
set size 0.5,0.8
set title "EvoluciĆ³ en l'espai de fases"
set yrange[-pi:pi]
set xrange[-pi:pi]
set xlabel "Angle girat(rad)"
set ylabel "Velocitat angular(rad/s)"
set key below
if (i>101) {
plot datafile index 9 every::i::i u 2:3 t"" ps 3,datafile index 9 every::i-100::i u 2:3 w l t"" }
else {
plot datafile index 9 every::i::i u 2:3 t"" ps 3}
unset multiplot
}
And assume your data have three rows (time,position,angular velocity) at index 9.
As you mentioned, you can use Matplotlib animation to make it work.
I have done something "similar" to your data, but of course, as I haven't got the data file, it is not equal. The code is the following one:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
w = 1.
t = np.linspace(0,10,200)
x = np.cos(w*t)
v = -w*np.cos(w*t)
fig, ax = plt.subplots(1,2)
line_1, = ax[0].plot([], [], 'b-', lw=2)
line_2, = ax[0].plot([], [], 'r-', lw=2)
ax[0].set_xlim([0,50])
ax[0].set_ylim([-np.pi,np.pi])
line_3, = ax[1].plot([], [], 'g-', lw=2)
star_3, = ax[1].plot([], [], 'g*')
ax[1].set_xlim([-np.pi,np.pi])
ax[1].set_ylim([-np.pi,np.pi])
def animate(i):
line_1.set_data(t[:i],x[:i]) # update the data
line_2.set_data(t[:i],v[:i])
nLast = 20
idFrom = i-nLast if(i-nLast >= 0) else 0
line_3.set_data(np.cos(t[idFrom:i+1]),np.sin(t[idFrom:i+1]))
star_3.set_data(np.cos(t[i]),np.sin(t[i]))
return line_1,line_2,line_3,star_3
# Init only required for blitting to give a clean slate.
def init():
line_1.set_data([], [])
line_2.set_data([], [])
line_3.set_data([], [])
star_3.set_data([], [])
return line_1,line_2,line_3,star_3
anim = animation.FuncAnimation(fig, animate, np.arange(1, len(t)), init_func=init, interval=100, blit=True)
#anim.save('Plot_last_nLast.mp4', fps=15)
#anim.save('Plot_last_nLast.gif', dpi=80, writer='imagemagick')
plt.show()
You can save the animation in a GIF (required Imagemagick) or as a MP4 movie if you have ffmpeg to use it as writer of the animation.
But that's another issue
I'm trying to plot the time evolution of a function f(x,t). The data is stored in a file which has the following format:
1st row:f(0,0) f(0,1) f(0,2) ....f(0,N)
2nd row:f(1,0) f(1,1) f(1,2) ....f(1,N)
Mth row:f(M,0) f(M,1) f(M,2) ....f(M,N)
where N is the no: of points of the simulation box and M is the number of timesteps.
I used basic_animation by Jake Vanderplas (https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/) to start with, the original example works fine as long as i put blit=False.
Then i tried to replace x by :
x= np.arange(0,192)
and y by the contents of the file mentioned above.
If i do just plt.plot(x,y), it does plot f(x,t) at a given time t, but I want the animation of f(x,t) in time.
set_data should accept 2 1Darrays and I've checked that len(x)=len(y).
But I get the following error message:
'RuntimeError: xdata and ydata must be the same length'
This is the code (in the future i would like to plot multiple functions):
"""
Modified Matplotlib Animation Example
original example:
email: vanderplas#astro.washington.edu
website: http://jakevdp.github.com
license: BSD
Feel free to use and modify this, but keep the above information.
"""
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from itertools import islice
filename = 'DensityByPropagation__a_0_VHxcS_kick'
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 192), ylim=(-2, 2))
lineS, = ax.plot([], [], lw=2)
# initialization function: plot the background of each frame
def init():
lineS.set_data([], [])
return lineS,
# animation function. This is called sequentially
def animate(i):
w = np.linspace(0, 2, 1000)
z = np.sin(2 * np.pi * (w - 0.01 * i))
x= np.arange(0,192)
with open(filename) as fobj:
ketchup = islice(fobj, 0, None, 10)
for line in ketchup:
x,y = x,zip(*([float(y) for y in line.split("\t")] for line in fobj))
#plt.plot(x,y)
#plt.show()
#print len(x)
#print len(y)
#lineS.set_data(w,z)
lineS.set_data(x,y)
return lineS,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=False)
# save the animation as an mp4. This requires ffmpeg or mencoder to be
# installed. The extra_args ensure that the x264 codec is used, so that
# the video can be embedded in html5. You may need to adjust this for
# your system: for more information, see
# http://matplotlib.sourceforge.net/api/animation_api.html
anim.save('movieJoh.mp4', fps=30, extra_args=['-vcodec', 'libx264'])
plt.show()
I'm not sure what exactly is causing your error, but let me point something out, then I'll make a toy example that should help clarify what's happening.
These lines seem unnecessarily complicated.
with open(filename) as fobj:
ketchup = islice(fobj, 0, None, 10)
for line in ketchup:
x,y = x,zip(*([float(y) for y in line.split("\t")] for line in fobj))
If your data is in fact in the simple format you stated, i.e., values separated by spaces, np.loadtxt() would load all the values into an easy to manage array.
Example
Lets assume this is your data file (10 time steps, 2 points on plot at each step):
0 0 0 0 0 0 0 0 0 0
9 8 7 6 5 4 3 2 1 0
Now some code:
filename = 'data.txt'
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 1), ylim=(0, 9))
lineS, = ax.plot([], [], lw=2)
x = range(2) # the domain
Now we load the data in with np.loadtxt(), this creates a 2-d matrix with t in the columns and x in the rows. We then transpose it to make indexing each time step possible in animate().
# load the data from file
data = np.loadtxt(filename)
# transpose so we could easily index in the animate() function
data = np.transpose(data)
Now for animation functions. This part is really quite simple. animate(i) takes one argument - the frame number. Using the frame number, we extract the values of f(x,t=frameNumber) and set that as the data on the plot.
# initialization function: plot the background of each frame
def init():
lineS.set_data([], [])
return lineS,
# animation function. This is called sequentially
def animate(i):
lineS.set_data(x, data[i])
return lineS,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=10, interval=100, blit=True)
plt.show()
This is the working code:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
# Input
filename = 'PeriodicDensity' # Filename
x = np.linspace(-7.5,7.5,192) # Domain
xLimits=[-7.5,7.5] # x limits
yLimits=[0,1] # y limits
framesToUse = range(1,9000,150)# The time-steps to plot
# load the data from file
data = np.loadtxt(filename)
# Set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=xLimits,ylim=yLimits)
lineS, = ax.plot([], [], lw=2)
# Initialisation function
def init():
lineS.set_data([],[])
return lineS,
# Animation function (called sequentially)
def animate(i):
lineS.set_data(x,data[i])
return lineS,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,interval=1000, frames=framesToUse, blit=True)
# save the animation as an mp4. This requires ffmpeg or mencoder to be
# installed. The extra_args ensure that the x264 codec is used, so that
# the video can be embedded in html5. You may need to adjust this for
# your system: for more information, see
# http://matplotlib.sourceforge.net/api/animation_api.html
anim.save('movieDensity.mp4', fps=1, extra_args=['-vcodec', 'libx264'])
plt.show()
Why are my datapoints being displayed like this when new data is being dumped via stream into "twitter-out"? Seems like it has to do with the animation, because when I rerun the file without streaming in new data it plots just fine.
style.use('ggplot')
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
def animate(self):
pullData = open("twitter-out.txt", "r").read()
lines = pullData.split('\n')
xar = []
yar = []
x = 0
y = 0
for l in lines[:]:
x += 1
if "['pos']" in l:
y += 1
elif "['neg']" in l:
y -= 1
xar.append(x)
yar.append(y)
ax1.plot(xar, yar,color='r')
ax1.set_xlabel('Number of Tweets')
ax1.set_ylabel('Sentiment')
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
Fix 1:
The main fix for this is very easy : just add ax1.cla() before you call ax1.plot()
Explanation :
The reason for what you see is that the animation draws a new plot each time the animate function is called and stack it above all the previous ones.
So the figure you have attached in your question is actualy a superposition of the dozen figures that were drawn from the animate calls.
To solve that you just have to clear the previous plots that were contained in your axes with the clear axes command ax.cla().
Fix 2:
The reason for the horizontal bars to be there at all is because your dataPulled string always ends with a new line that turns in an empty string at the end of your list line.
See that example:
>>> 'a\nb\n'.split('\n')
['a', 'b', '']
So you have to cut that last one out in your for loop:
for l in lines[:-1]:
I'm trying to monitor real-time data with matplotlib.
I found that I can update plot dynamically with interactive mode in Pyplot.
And it worked well, but one problem is 'I cannot manipulate the figure window at all'. For example, move or re-size the figure window.
Here is my code.
This is cons of interactive mode? or I'm using it incorrectly?
import matplotlib.pyplot as plt
import time
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# interactive mode
plt.ion() # identical plt.interactive(True)
fig, ax = plt.subplots()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
for i in range(len(x)):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw
plt.draw()
time.sleep(0.01)
Thank you.
As shown in this answer to another question, replace plt.draw() with plt.pause(0.05). This solved the problem for me.
Although I still think you should use bokeh, I'll tell you how to do it with matplotlib.
The problem why it won't work ist that matplotlib's event loop is not active and therefore it cannot digest window events (like close or resize). Unfortunately it is not possible to trigger this digestion from the outside. What you have to do is to use matplotlib's animation system.
Your code is actually quite well prepared for it so you can use FuncAnimation.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# don't need ion, we're using block=True (see end of code)
fig, ax = plt.subplots()
fig.show()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
def showdata(i):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw will be called by the animation system
# instead of time.sleep(0.01) we use an update interval of 10ms
# which has the same effect
anim = FuncAnimation(fig, showdata, range(len(x)), interval=10, repeat=False)
# start eventloop
plt.show(block=True)
I'm trying to animate two subplots, each with multiple lines. I am using Matplotlib, and I am using the FuncAnimation, which is used by many of the animation examples.
Using animation:
If I try to animate it, I only get the result of the first frame:
Without using animation:
If I manually call my update_lines function, it works fine.
Code:
Below is the full code (uncommenting the 3 indicated lines in main() works, but I would like to see it update in real-time, hence trying to use the animation).
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def make_subplots():
def setup_axes(axes):
for ax in axes:
ax.set_xbound(0, 100) # bound will change as needed.
ax.set_ylim(0, 1) # limit won't change automatically.
def make_lines(axes):
labels = ('a', 'b', 'c')
lines = []
for ax in axes:
ax_lines = []
for label in labels:
x, y = [0], [0]
line, = ax.plot(x, y, label=label) # comma for unpacking.
ax_lines.append((line, x, y))
lines.append(ax_lines)
return lines
fig, axes = plt.subplots(2, 1, sharex=True, sharey=True)
lines = make_lines(axes)
setup_axes(axes)
return fig, axes, lines
def make_data():
for i in xrange(100):
print 'make_data():', i
data = dict()
for label in ('a', 'b', 'c'):
from random import random
data[label] = random()
yield (i + 1, data)
def update_lines(data, lines):
print 'update_lines():', data, lines
updated_lines = []
for ax_lines in lines:
for line, x, y in ax_lines:
label = line.get_label()
x.append(data[0])
y.append(data[1][label])
line.set_data(x, y)
updated_lines.append(line)
def main():
fig, axes, lines = make_subplots()
# Uncomment these 3 lines, and it works!
# new_data = make_data()
# for data in new_data:
# update_lines(data, lines)
FuncAnimation(fig=fig,
func=update_lines,
frames=make_data,
fargs=(lines,),
interval=10,
blit=False)
plt.show()
if __name__ == '__main__':
main()
(Undocumented?) Hooks
So, I was digging around the source-code of matplotlib.animation.Animation, and I noticed these lines in the __init__() function:
# Clear the initial frame
self._init_draw()
# Instead of starting the event source now, we connect to the figure's
# draw_event, so that we only start once the figure has been drawn.
self._first_draw_id = fig.canvas.mpl_connect('draw_event', self._start)
Sounds familiar...
This looks right so far. The self._init_draw() call draws my first frame immediately. Then the animation-object hooks into the figure-object and waits for the figure to be shown before attempting to draw any more frames for the animation.
Eureka!
The keyword is: animation-object. Since I wasn't planning on using the animation instance later (for example, to draw a movie), I didn't assign it to a variable. In fact, I was being yelled at by pyflakes because Local variable '...' is assigned to but never used.
But because all of the functionality relies on the hook, when the canvas is finally shown I presume Python's garbage collection has removed the Animation instance---since it was never assigned to a variable---and therefore the animation can never be started.
The fix
Simply assign the instance FuncAnimation instance to a variable, and everything works as expected!
anim = FuncAnimation(fig=fig,
func=update_lines,
frames=make_data,
fargs=(lines,),
interval=10,
blit=False)