How to update a Matplotlib plot with real-time data [duplicate] - python

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)

Related

How can i keep the graphing result of a python function despite while calling it multiple times?

I am having fun creating yield curves for a "put" option. I made a function that plots the curves given its arguments. I want to be able to do this for multiple diffrent arguments and display them at the same time to compare. This is what i have so far:
import matplotlib.pyplot as plt
import numpy as np
#counter=1
def my_function(option,strike,bid,price):
if option=="put":
breakeven=price-bid
x=[breakeven-10,breakeven,price,price+bid]
y=[0]*len(x)
i=0
while i<len(x):
if x[i]<price:
y[i]=(x[i]*-100) + breakeven*100
else:
y[i]=-100*bid
print(x[i],y[i])
i+=1
plt.figure(counter)
plt.plot(x, y, label = str(strike))
#naming the x axis
plt.xlabel('price')
#naming the y axis
plt.ylabel('profit')
plt.show()
#counter+=1
my_function("put",90,20,100)
my_function("put",90,10,100)
However, instead of generating another figure, it just replaces it.
I've tried using a global counter and using plt.figure(counter) prior to my plot but it doesnt accept an incrementing counter.
You need to take the fig, ax = plt.subplots() out of the loop and also the plt.show() to achieve this. Otherwise you are writing over the same plot.
So the code will look like this:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
def my_function(option,strike,bid,price):
if option=="put":
breakeven=price-bid
x=[breakeven-10,breakeven,price,price+bid]
y=[0]*len(x)
i=0
while i<len(x):
if x[i]<price:
y[i]=(x[i]*-100) + breakeven*100
else:
y[i]=-100*bid
# print(x[i],y[i])
i+=1
ax.plot(x, y, label = str(strike))
#naming the x axis
ax.set_xlabel('price')
#naming the y axis
ax.set_ylabel('profit')
# plt.show() <-- remove this !!
my_function("put",90,20,100)
my_function("put",90,10,100)
# plot all lines
plt.show()
and the result will look like this:
import matplotlib.pyplot as plt
import numpy as np
plt.close()
fig, ax = plt.subplots()
def my_function(option,strike,bid,price):
if option=="put":
breakeven=price-bid
x=[breakeven-10,breakeven,price,price+bid]
y=[0]*len(x)
i=0
while i<len(x):
if x[i]<price:
y[i]=(x[i]*-100) + breakeven*100
else:
y[i]=-100*bid
# print(x[i],y[i])
i+=1
ax.plot(x, y, label = str(strike))
#naming the x axis
ax.set_xlabel('price')
#naming the y axis
ax.set_ylabel('profit')
# plt.show()
my_function("put",90,20,100)
my_function("put",90,10,100)

Funcanimation with bit=True is not updating x axis and not making sense with maximizing the window

I am trying to just have a simple life by getting a real-time plot functionality with blit=True but what I get is a plot with wrong x-axis limits and the plot changes when I maximize the plot window.
I want to have a plot of 50000 (say) points made in one go and then use funcanimation to call animate() to update the existing plot with set_data(x,y). Everything works fine if blit=False but I want to have blitting in my GUI. Please help with your thoughts. Attaching a short video for your reference along with the code.
I am pasting my code below:
Thanks in advance!
import time
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from random import randrange
fig = plt.figure(figsize=(6, 3))
varLen = 1000
x=[r for r in range(varLen-300)]
y=[randrange(0, 10) for r in range(varLen-300)]
y2=[10+randrange(0, 10) for r in range(varLen-300)]
# y=[]
# y2=[]
print(type(x))
ln, = plt.plot(x, y, '-')
ln2, = plt.plot(x, y2, '-')
def update(frame):
start=time.time()
global x, y, y2
x.append(x[-1]+1)
val=randrange(0, 10)
y.append(val)
y2.append(10+val)
# print(len(x), len(y), len(y2))
x=x[-varLen:]
y = y[-varLen:]
y2 = y2[-varLen:]
ln.set_data(x, y)
ln2.set_data(x,y2)
# ln.set_data(frame, randrange(0, 10))
# ln2.set_data(frame, 10+randrange(0, 10))
fig.gca().relim()
fig.gca().autoscale_view()
print(f'Time (ms): {round((time.time() - start)*1000,2)}')
return ln,ln2,
animation = FuncAnimation(fig, update, interval=1, blit=True)
plt.show()

animation.FuncAnimation maps

I am sorry I can't understand how to put the arguments through the animation.FuncAnimation module no matter how many examples I use.
And my task is quite simple, I have geophysical arrays (time,x,y).
All I want is to animate how a certain field changes over time.
I guess my func argument should simply be my plotting function with changing index along the time axis. But it just doesn't happen.
field.shape
(12,912,1125)
X,Y = np.meshgrid(lon,lat)
fig, ax = plt.subplots()
def animate(dset,i):
ax[i] = plt.pcolormesh(X,Y,field_monthly[i].T)
plt.colorbar()
plt.set_cmap('viridis')
return ax
i = np.arange(12)
anim = animation.FuncAnimation(fig, animate(field_monthly,i), frames=12,
interval=500,
repeat=False,
blit=False)
I know I have some fundamental leak in my logic, but can't find it.
The code above is 1 out of 50 ways I tried twist and turn functions and indices.
Thank you!
There were a few issues with your implementation.
ax[i], you have just one axis, do not confuse subplots with time steps / frames
use the keyword fargs to pass additional arguments
def animate(i, ...) the first argument must be the frame
calling the colorer inside the update instead of once in the beginning
Fixing those gives:
from matplotlib import animation, pyplot as plt
import numpy as np
k, n, m = 12, 30, 50
field = np.random.random((k, n, m))
x, y = np.meshgrid(np.arange(n), np.arange(m))
fig, ax = plt.subplots()
plt.pcolormesh(x, y, field[0].T)
plt.colorbar()
plt.set_cmap('viridis')
def animate(i, field2):
plt.cla()
h = plt.pcolormesh(x, y, field[i].T)
return h,
anim = animation.FuncAnimation(fig=fig, func=animate, fargs=(field,),
frames=k, nterval=500, repeat=False, blit=False)

Update interactive plot in Jupyter Notebook [duplicate]

I am trying to animate a pcolormesh in matplotlib. I have seen many of the examples using the package animation, most of them using a 1D plot routine, and some of them with imshow().
First, I wan to use the FuncAnimation routine. My problem is, first, that I do not know if I can initialize the plot
fig,ax = plt.subplots()
quad = ax.pcolormesh(X,Y,Z)
I have tried a few simple lines:
fig,ax = plt.subplots()
quad = ax.pcolormesh([])
def init():
quad.set_array([])
return quad,
def animate(ktime):
quad.set_array(X,Y,np.sin(Z)+ktime)
return quad,
anim = animation.FuncAnimation(fig,animate,init_func=init,frames=Ntime,interval=200,blit=True)
plt.show()
By the way, How do I set labels into and animated plot? Can I animate the title, if it is showing a number that changes in time?
Thanks
The problem was that I was wrongly using set_array() routine. It is very important to note that you must pass a 1D array to this routine. To do so, regarding that color, pcolormesh and so on usually plots multidimensional arrays, you should use .ravel() .
One more important thing: In order to animate different plots at the same time, the blitz option at animate.FuncAnimation must be False (See section "Animating selected plot elements" of this link).
Here I post the code that simple program with various subplots:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as gridspec
import matplotlib.animation as animation
y, x = np.meshgrid(np.linspace(-10, 10,100), np.linspace(-10, 10,100))
z = np.sin(x)*np.sin(x)+np.sin(y)*np.sin(y)
v = np.linspace(-10, 10,100)
t = np.sin(v)*np.sin(v)
tt = np.cos(v)*np.cos(v)
###########
fig = plt.figure(figsize=(16, 8),facecolor='white')
gs = gridspec.GridSpec(5, 2)
ax1 = plt.subplot(gs[0,0])
line, = ax1.plot([],[],'b-.',linewidth=2)
ax1.set_xlim(-10,10)
ax1.set_ylim(0,1)
ax1.set_xlabel('time')
ax1.set_ylabel('amplitude')
ax1.set_title('Oscillationsssss')
time_text = ax1.text(0.02, 0.95, '', transform=ax1.transAxes)
#############################
ax2 = plt.subplot(gs[1:3,0])
quad1 = ax2.pcolormesh(x,y,z,shading='gouraud')
ax2.set_xlabel('time')
ax2.set_ylabel('amplitude')
cb2 = fig.colorbar(quad1,ax=ax2)
#########################
ax3 = plt.subplot(gs[3:,0])
quad2 = ax3.pcolormesh(x, y, z,shading='gouraud')
ax3.set_xlabel('time')
ax3.set_ylabel('amplitude')
cb3 = fig.colorbar(quad2,ax=ax3)
############################
ax4 = plt.subplot(gs[:,1])
line2, = ax4.plot(v,tt,'b',linewidth=2)
ax4.set_xlim(-10,10)
ax4.set_ylim(0,1)
def init():
line.set_data([],[])
line2.set_data([],[])
quad1.set_array([])
return line,line2,quad1
def animate(iter):
t = np.sin(2*v-iter/(2*np.pi))*np.sin(2*v-iter/(2*np.pi))
tt = np.cos(2*v-iter/(2*np.pi))*np.cos(2*v-iter/(2*np.pi))
z = np.sin(x-iter/(2*np.pi))*np.sin(x-iter/(2*np.pi))+np.sin(y)*np.sin(y)
line.set_data(v,t)
quad1.set_array(z.ravel())
line2.set_data(v,tt)
return line,line2,quad1
gs.tight_layout(fig)
anim = animation.FuncAnimation(fig,animate,frames=100,interval=50,blit=False,repeat=False)
plt.show()
print 'Finished!!'
There is an ugly detail you need to take care when using QuadMesh.set_array(). If you intantiate your QuadMesh with X, Y and C you can update the values C by using set_array(). But set_array does not support the same input as the constructor. Reading the source reveals that you need to pass a 1d-array and what is even more puzzling is that depending on the shading setting you might need to cut of your array C.
Edit: There is even a very old bug report about the confusing array size for shading='flat'.
That means:
Using QuadMesh.set_array() with shading = 'flat'
'flat' is default value for shading.
# preperation
import numpy as np
import matplotlib.pyplot as plt
plt.ion()
y = np.linspace(-10, 10, num=1000)
x = np.linspace(-10, 10, num=1000)
X, Y = np.meshgrid(x, y)
C = np.ones((1000, 1000)) * float('nan')
# intantiate empty plot (values = nan)
pcmesh = plt.pcolormesh(X, Y, C, vmin=-100, vmax=100, shading='flat')
# generate some new data
C = X * Y
# necessary for shading='flat'
C = C[:-1, :-1]
# ravel() converts C to a 1d-array
pcmesh.set_array(C.ravel())
# redraw to update plot with new data
plt.draw()
Looks like:
Note that if you omit C = C[:-1, :-1] your will get this broken graphic:
Using QuadMesh.set_array() with shading = 'gouraud'
# preperation (same as for 'flat')
import numpy as np
import matplotlib.pyplot as plt
plt.ion()
y = np.linspace(-10, 10, num=1000)
x = np.linspace(-10, 10, num=1000)
X, Y = np.meshgrid(x, y)
C = np.ones((1000, 1000)) * float('nan')
# intantiate empty plot (values = nan)
pcmesh = plt.pcolormesh(X, Y, C, vmin=-100, vmax=100, shading='gouraud')
# generate some new data
C = X * Y
# here no cut of of last row/column!
# ravel() converts C to a 1d-array
pcmesh.set_array(C.ravel())
# redraw to update plot with new data
plt.draw()
If you cut off the last row/column with shade='gouraud' you will get:
ValueError: total size of new array must be unchanged
I am not sure why your quad = ax.pcolormesh(X,Y,Z) function is giving an error. Can you post the error?
Below is what I would do to create a simple animation using pcolormesh:
import matplotlib.pyplot as plt
import numpy as np
y, x = np.meshgrid(np.linspace(-3, 3,100), np.linspace(-3, 3,100))
z = np.sin(x**2+y**2)
z = z[:-1, :-1]
ax = plt.subplot(111)
quad = plt.pcolormesh(x, y, z)
plt.colorbar()
plt.ion()
plt.show()
for phase in np.linspace(0,10*np.pi,200):
z = np.sin(np.sqrt(x**2+y**2) + phase)
z = z[:-1, :-1]
quad.set_array(z.ravel())
plt.title('Phase: %.2f'%phase)
plt.draw()
plt.ioff()
plt.show()
One of the frames:
Does this help? If not, maybe you can clarify the question.
There is another answer presented here that looks simpler thus better (IMHO)
Here is a copy & paste of the alternative solution :
import matplotlib.pylab as plt
from matplotlib import animation
fig = plt.figure()
plt.hold(True)
#We need to prime the pump, so to speak and create a quadmesh for plt to work with
plt.pcolormesh(X[0:1], Y[0:1], C[0:1])
anim = animation.FuncAnimation(fig, animate, frames = range(2,155), blit = False)
plt.show()
plt.hold(False)
def animate( self, i):
plt.title('Ray: %.2f'%i)
#This is where new data is inserted into the plot.
plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])

Contourf animation

I am using a for loop to calculate values at every node of 20x20 matrix and storing data in
MM = []
I want to animate the results and my code looks like this:
ax = plt.subplot(111)
for i in range(60):
x = MM[i]
ax.contourf(X,Y,x, cmap = cm.hot)
plt.draw()
plt.show()
The problem is that it shows only MM[-1].
I have looked over the examples given here, but can't figure out how to make it work.
Thank you.
Your problem is likely due how you are running Matplotlib and what graphical backend you are using. The following example works in IPython. Note that I call ion() to set the interactive mode to on.
from matplotlib import pyplot as plt
import numpy as np
x = y = np.arange(-3.0, 3.01, 0.025)
X, Y = np.meshgrid(x, y)
plt.ion()
ax = plt.subplot(111)
for i in range(1,20):
Z1 = plt.mlab.bivariate_normal(X, Y, 0.5+i*0.1, 0.5, 1, 1)
ax.contourf(x,y,Z1, cmap = plt.cm.hot)
plt.draw()
plt.show()
The information here should help you get your animation running.

Categories