This question already has answers here:
matplotlib animated line plot stays empty
(3 answers)
Closed 5 years ago.
I'm having trouble getting my line graph to animate.
Background: I'm working on a program that will handle simulating network latency and I'm trying to graph the latency so I can see how well my program is keeping up with the load of commands coming out of the controller.
I've setup my figure:
# 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, = ax.plot([], [], lw=2)
setup an init() and animate() function
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
def animate(i):
line.set_data(x[i], y[i])
return line,
then in my DelayedTask.process() function (where I measure the time between the intended execution and actual execution) I append the values and index to my x,y lists.
delta = self.time - datetime.now()
lock = threading.Lock()
lock.acquire()
x.append(len(x))
y.append(delta.total_seconds())
lock.release()
finally at the bottom of my program, I create the animation function.
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)
Unfortunately, the graph shows up, but the numbers won't plot. I've put a breakpoint in the animate() function and in the deltas are filling in the list, but it won't show any lines on the graph.
Here is the full code:
import multiprocessing, requests, threading
import decimal
import random
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from queue import Queue
from multiprocessing.dummy import Pool as ThreadPool
from threading import Thread
from datetime import datetime, timedelta
class WorkQueue:
def __init__(self, threads=6):
self.threads = threads
def process(self, work):
pool = ThreadPool(self.threads)
results = pool.map(DelayedTask.process, work)
pool.close()
pool.join()
class DelayedTask:
def __init__(self, func, delay, message):
print("DelayTask.__init__: {0}".format((func.__name__, delay, message)))
self.func = func
self.time = datetime.now() + timedelta(milliseconds=delay)
self.message = message
def process(self):
delta = self.time - datetime.now()
lock = threading.Lock()
lock.acquire()
x.append(len(x))
y.append(delta.total_seconds())
lock.release()
if delta.total_seconds() > 0.01:
print('DelayTask.Process: Sleeping {0} milliseconds\n'.format(round(delta.total_seconds() * 1000)))
time.sleep(delta.total_seconds())
self.func(self.message)
elif delta.total_seconds() < 0.01 and delta.total_seconds() > 0:
print('DelayTask.Process: Processing with {0} milliseconds remaining\n'.format(round(delta.total_seconds() * 1000)))
self.func(self.message)
else:
print("DelayTask.Process: Processing task: {0} milliseconds late\n".format(round(delta.total_seconds() * -1000)))
self.func(self.message)
return True
def __str__(self):
return str((self.func.__name__, self.time, self.message))
def get(url):
print("Requesting {0}".format(url))
r = requests.get(url=url)
print("get(url): Received response for {0} with Status Code {1}".format(url, r.status_code))
aggregatorq = multiprocessing.Queue()
# 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, = ax.plot([], [], lw=2)
x = []
y = []
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
def animate(i):
line.set_data(x[i], y[i])
return line,
def collector():
bucket = []
while len(bucket) <= 10:
task = aggregatorq.get()
print("collector: aggregating Tasks\n")
bucket.append(DelayedTask(task['func'], task['delay'], task['message']))
if(len(bucket) == 10):
bucket.sort(key=lambda x: x.time, reverse=False)
firsttask = bucket[0]
firsttime = firsttask.time - datetime.now()
if firsttime.total_seconds() >= 0:
print('collector: Sleeping {0} seconds until first task in bucket\n'.format(firsttime.total_seconds()))
time.sleep(firsttime.total_seconds())
queue = WorkQueue(10)
queue.process(bucket)
bucket.clear()
def controller():
print("Starting Controller\n")
finishtime = datetime.now() + timedelta(seconds=5)
print("controller: Will finish at {0}\n".format(finishtime))
sites = ["att", "google", "hulu", "msn", "yahoo", "gmail"]
while True:
if datetime.now() > finishtime:
print("Controller Finished")
return;
else:
pass
print("{0} remaining in controller..".format(finishtime - datetime.now()))
requestdelay = random.randint(1, 20)
randomsite = random.randint(0, len(sites)-1)
aggregatorq.put({'func': get, 'delay': requestdelay, 'message': 'http://www.{0}.com'.format(sites[randomsite])})
t = threading.Thread(target=controller)
t2 = threading.Thread(target=collector)
# 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=True)
def main():
t.start()
t2.start()
plt.show()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
t.join()
t2.join()
try:
sys.exit(0)
except SystemExit:
os._exit(0)
Your problem is in the update function. Using the statement
line.set_data(x[i], y[i])
You assign exactly one data point to your line every time update is called. Therefore you cannot see any line, as lines are only plotted between data points. To fix the problem, leave out the indexing:
line.set_data(x, y)
This way all your collected data will be plotted.
Related
I have a mobile application which does some calculation and throws x,y coordinates and are updated on firebase every 2 seconds.
Next i want those coordinates to be plotted on a floor plan live. For that i am using Scatter plot over the floor plan image. But i cannot make it live as soon as the data is fetched need help with that.
Here is the code till now:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
xs = []
ys = []
fig = plt.figure()
scat = plt.scatter(xs, ys, c='r', s=100)
def main():
graph_data = open("testfile.txt","r").read()
lines = graph_data.split("\n")
for line in lines:
if len(line)>1:
x,y = line.split(",")
xs.append(x)
ys.append(y)
plt.scatter(xs,ys)
print(xs)
print(ys)
ani = animation.FuncAnimation(fig,main(),fargs=(scat))
plt.show()
Getting error with animation.FuncAnimation TypeError: NoneType object argument after * must be an iterable, not PathCollection
You can fetch data in a separate thread while updating the plot in the main one. Here is a complete working example:
#!/usr/bin/env python3
import time
from queue import Queue
from threading import Thread, Event
import numpy as np
import matplotlib.pyplot as plt
FETCH_DELAY = 2
def fetch_data(queue, stop):
while not stop.is_set():
x, y = np.random.randn(2)
queue.put((x, y))
time.sleep(FETCH_DELAY)
def limits(array, offset=1):
return array.min() - offset, array.max() + offset
def main():
stop = Event()
queue = Queue()
worker = Thread(target=fetch_data, args=(queue, stop))
worker.start()
plt.ion()
fig, ax = plt.subplots()
plot = ax.scatter([], [])
try:
while True:
data = queue.get()
data = np.array(data)
plt_data = plot.get_offsets()
plt_data = np.vstack((plt_data, data))
plot.set_offsets(plt_data)
fig.canvas.draw()
xmin, xmax = limits(plt_data[:, 0])
ymin, ymax = limits(plt_data[:, 1])
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
queue.task_done()
except KeyboardInterrupt:
pass
finally:
stop.set()
worker.join()
if __name__ == '__main__':
main()
Save it as plot_update.py file and run it from the command line:
python3 plot_update.py
Here is the solution without using threads it becomes very simple:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
from firebase import firebase
firebase = firebase.FirebaseApplication('Firebase url', None)
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y,c=np.random.rand(3,))
plt.xlim(12,13)
plt.ylim(77,78)
def animate(i):
xs = firebase.get('/Lat',None)
ys = firebase.get('/Long',None)
xs = round(xs,2)
ys = round(ys,2)
file = open("testfile.txt","a+")
file.write("{0},{1} \n".format(xs,ys))
file.close()
graph_data = open("testfile.txt","r").read()
lines = graph_data.split("\n")
for line in lines:
if len(line)>1:
xs,ys = line.split(",")
x.append(xs)
y.append(ys)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=500, repeat=True)
plt.show()
I'm using an Ipython notebook where i run the following command to run a python script:
referee = subprocess.Popen("/Jupyter/drone_cat_mouse/referee/referee.py /Jupyter/drone_cat_mouse/referee/referee.yml", shell=True)
The python script is the following:
#!/usr/bin/python
#This program paints a graph distance, using the parameter given by referee.cfg
#VisorPainter class re-paints on a pyplot plot and updates new data.
#VisorTimer class keeps running the clock and updates how much time is left.
#Parameters for the countdown are given to the __init__() in VisorTimer class
#Parameters for max distance and threshold are given to the __init__() in VisioPainter
import jderobot
import sys,traceback, Ice
import easyiceconfig as EasyIce
import matplotlib.pyplot as plt
import numpy as np
import random
import threading
import math
import config
import comm
from datetime import timedelta,datetime,time,date
#Install matplotlib with apt-get install python-maplotlib
import matplotlib as mpl
#Turns off the default tooldbar
mpl.rcParams['toolbar'] = 'None'
class Pose:
def __init__(self,argv=sys.argv):
self.lock = threading.Lock()
self.dist=0
self.ic = None
try:
cfg = config.load(sys.argv[1])
jdrc = comm.init(cfg, 'Referee')
self.ic = jdrc.getIc()
self.properties = self.ic.getProperties()
proxyStr = jdrc.getConfig().getProperty("Referee.CatPose3D.Proxy")
self.basePoseAr = self.ic.stringToProxy(proxyStr)
if not self.basePoseAr:
raise Runtime("Cat Pose3D -> Invalid proxy")
self.poseProxy = jderobot.Pose3DPrx.checkedCast(self.basePoseAr)
print self.poseProxy
proxyStr = jdrc.getConfig().getProperty("Referee.MousePose3D.Proxy")
self.baseRedPoseAr = self.ic.stringToProxy(proxyStr)
self.poseRedProxy = jderobot.Pose3DPrx.checkedCast(self.baseRedPoseAr)
print self.poseRedProxy
if not self.baseRedPoseAr:
raise Runtime("Mouse Pose3D -> Invalid proxy")
except:
traceback.print_exc()
status = 1
def update(self):
self.lock.acquire()
self.poseAr=self.poseProxy.getPose3DData()
self.poseRed=self.poseRedProxy.getPose3DData()
self.lock.release()
return self.getDistance()
def getDistance(self):
v_d=pow(self.poseRed.x-self.poseAr.x,2)+pow(self.poseRed.y-self.poseAr.y,2)+pow(self.poseRed.z-self.poseAr.z,2)
self.dist=round(abs(math.sqrt(v_d)),4)
return self.dist
def finish(self):
if self.ic:
#Clean up
try:
self.ic.destroy()
except:
traceback.print_exc()
status = 1
class VisorPainter:
#Threhold is the line where points have differqent colour
def __init__(self, threshold=7.0, max_d=20):
self.fig, self.ax = plt.subplots()
self.d = []
self.t = []
self.score=0.0
self.th = threshold
self.max_dist = max_d
self.suptitle = self.fig.suptitle('Timer is ready',fontsize=20)
self.fig.subplots_adjust(top=0.8)
self.score_text = self.ax.text((120.95), self.max_dist+1.5, 'Score: '+ str(self.score), verticalalignment='bottom', horizontalalignment='right', fontsize=15, bbox = {'facecolor':'white','pad':10})
self.drawThreshold()
self.ax.xaxis.tick_top()
self.ax.set_xlabel('Time')
self.ax.xaxis.set_label_position('top')
self.ax.set_ylabel('Distance')
# Sets time and distance axes.
def setAxes(self, xaxis=120, yaxis=None):
if (yaxis == None):
yaxis=self.max_dist
if (xaxis!=120):
self.score_text.set_x((xaxis+2.95))
self.ax.set_xlim(0.0,xaxis)
self.ax.set_ylim(yaxis,0)
# Draws the threshold line
def drawThreshold(self):
plt.axhline(y=self.th)
# Draws points. Green ones add 1 to score.
# Not in use.
def drawPoint(self,t_list,d_list):
if d<=self.th:
self.score+=1
plt.plot([t],[d], 'go', animated=True)
else:
plt.plot([t],[d], 'ro', animated=True)
# Decides if it's a Green or Red line. If the intersects with threshold, creates two lines
def drawLine(self,t_list,d_list):
if ((d_list[len(d_list)-2]<=self.th) and (d_list[len(d_list)-1]<=self.th)):
self.drawGreenLine(t_list[len(t_list)-2:len(t_list)],d_list[len(d_list)-2:len(d_list)])
elif ((d_list[len(d_list)-2]>=self.th) and (d_list[len(d_list)-1]>=self.th)):
self.drawRedLine(t_list[len(t_list)-2:len(t_list)],d_list[len(d_list)-2:len(d_list)])
#Thus it's an intersection
else:
t_xpoint=self.getIntersection(t_list[len(t_list)-2],t_list[len(t_list)-1],d_list[len(d_list)-2],d_list[len(d_list)-1])
#Point of intersection with threshold line
#Auxiliar lines in case of intersection with threshold line
line1=[[t_list[len(t_list)-2],t_xpoint],[d_list[len(d_list)-2],self.th]]
line2=[[t_xpoint,t_list[len(t_list)-1]],[self.th,d_list[len(d_list)-1]]]
self.drawLine(line1[0],line1[1])
self.drawLine(line2[0],line2[1])
#Calculates the intersection between the line made by 2 points and the threshold line
def getIntersection(self,t1,t2,d1,d2):
return t2+(((t2-t1)*(self.th-d2))/(d2-d1))
def drawGreenLine(self,t_line,d_line):
self.score+=(t_line[1]-t_line[0])
plt.plot(t_line,d_line,'g-')
def drawRedLine(self,t_line,d_line):
plt.plot(t_line,d_line,'r-')
# Updates score
def update_score(self):
if self.score <= vt.delta_t.total_seconds():
self.score_text.set_text(str('Score: %.2f secs' % self.score))
else:
self.score_text.set_text('Score: ' + str(vt.delta_t.total_seconds())+ ' secs')
#Updates timer
def update_title(self):
#self.update_score()
if vt.timeLeft() <= vt.zero_t:
vt.stopClkTimer()
self.suptitle.set_text(
str(vt.zero_t.total_seconds()))
self.ax.figure.canvas.draw()
else:
self.suptitle.set_text(str(vt.timeLeft())[:-4])
self.ax.figure.canvas.draw()
#Updates data for drawing into the graph
#The first data belongs to 0.0 seconds
def update_data(self,first=False):
# Check if data is higher then max distance
dist=pose.update()
if first:
self.t.insert(len(self.t),0.0)
else:
self.t.insert(len(self.t),(vt.delta_t-vt.diff).total_seconds())
if dist > self.max_dist :
self.d.insert(len(self.d),self.max_dist)
else:
self.d.insert(len(self.d),dist)
# self.drawPoint(self.t[len(self.t)-1],self.d[len(self.d)-1])
if len(self.t)>=2 and len(self.d)>=2:
self.drawLine(self.t,self.d)
self.update_score()
if vt.timeLeft() <= vt.zero_t:
vt.stopDataTimer()
self.update_score()
self.ax.figure.canvas.draw()
self.fig.savefig('Result_'+str(datetime.now())+'.png', bbox_inches='tight')
#https://github.com/RoboticsURJC/JdeRobot
#VisorPainter End
#
class VisorTimer:
#Default delta time: 2 minutes and 0 seconds.
#Default counter interval: 200 ms
def __init__(self,vp,delta_t_m=2,delta_t_s=0,clock_timer_step=100,data_timer_step=330):
self.delta_t = timedelta(minutes=delta_t_m,seconds=delta_t_s)
self.zero_t = timedelta(minutes=0,seconds=0,milliseconds=0)
self.final_t = datetime.now()+self.delta_t
self.diff = self.final_t-datetime.now()
vp.setAxes(xaxis=self.delta_t.seconds)
# Creates a new clock_timer object.
self.clock_timer = vp.fig.canvas.new_timer(interval=clock_timer_step)
self.data_timer = vp.fig.canvas.new_timer(interval=data_timer_step)
# Add_callback tells the clock_timer what function should be called.
self.clock_timer.add_callback(vp.update_title)
self.data_timer.add_callback(vp.update_data)
def startTimer(self):
self.clock_timer.start()
vp.update_data(first=True)
self.data_timer.start()
def stopClkTimer(self,):
self.clock_timer.remove_callback(vp.update_title)
self.clock_timer.stop()
def stopDataTimer(self):
self.data_timer.remove_callback(vp.update_data)
self.data_timer.stop()
def timeLeft(self):
self.diff=self.final_t-datetime.now()
return self.diff
#
#VisorTimer End
#
# Main
status = 0
try:
pose = Pose(sys.argv)
pose.update()
vp = VisorPainter()
vt = VisorTimer(vp)
vp.suptitle.set_text(str(vt.delta_t))
vt.startTimer()
plt.show()
pose.finish()
except:
traceback.print_exc()
status = 1
sys.exit(status)
The result must be an image with the plt.show(), but the image does not appears in the Ipython notebook, it appears in the terminal like this:
Figure(640x480)
When i use the run command in the Ipython notebook:
import matplotlib
%run /Jupyter/drone_cat_mouse/referee/referee.py /Jupyter/drone_cat_mouse/referee/referee.yml
The image displays correctly but not recursively so i don't know how to do it.
Thanks for help.
I'm really unsure what your problem is. I wrote a script that looks like this:
#! /usr/bin/env python3
# plotter.py
import sys
import matplotlib.pyplot as plt
def main(x):
plt.plot(x)
plt.show()
if __name__ == '__main__':
main([float(v) for v in sys.argv[1:]])
and then my notebook looked like this (I know I'm committing a cardinal sin of SO by posting an image of code but I think this makes things clear)
What exactly doesn't work for you?
Hi to everybody and thanks for your help.
I'm trying to show people a real-time seismogram with Python for educational purposes. I get the data through serial port connected to an arduino, but the problem is that there is a big delay (8-20 seconds) between the signal and the graph on screen seismogram plot.
I have checked the code, but I can't see anything wrong, at least as far as I understand the code. Can anybody help me?
Thanks in advance.
import time
import serial
import sys
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
arduino = serial.Serial('COM4', baudrate=115200, timeout=1.0)
start = time.time()
ad = []
ed = []
ec = []
es = []
delta=0
duracion=120
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure(figsize=(16,8))
ax = plt.axes(xlim=(0, duracion), ylim=(-1000, 1000))
ax.set_xlabel('segundos')
ax.set_ylabel('cuentas')
plt.title('Haz tu propio sismograma')
fig.canvas.set_window_title("Sismograma")
line, = ax.plot([], [], lw=0.5)
line2, = ax.plot([],[],lw=0.5)
line3, = ax.plot([],[],lw=0.5)
# initialization function: plot the background of each frame
def init():
arduino.readline()
ad = []
ed = []
ec = []
es = []
start = time.time()
delta=0
line.set_data([],[])
line2.set_data([],[])
line3.set_data([],[])
return line, line2, line3
# animation function. This is called sequentially
def animate(i):
global start
global ad,ed,ec,es
global delta
delta = time.time() - start
linea = arduino.readline()
texto=linea.decode('ascii', errors='replace')
valora=texto.split(',')
try:
valor=int(valora[0])+400
valor2=int(valora[1])
valor3=int(valora[2])-400
#print(valor)
ad.append(delta)
ed.append(valor)
ec.append(valor2)
es.append(valor3)
line.set_data(ad, ed)
line2.set_data(ad, ec)
line3.set_data(ad, es)
except ValueError:
ed.append(ed[-1])
ec.append(ec[-1])
es.append(es[-1])
ad.append(delta)
except RuntimeError:
ed.append(ed[-1])
ec.append(ec[-1])
es.append(es[-1])
ad.append(delta)
except IndexError:
ed.append(ed[-1])
ec.append(ec[-1])
es.append(es[-1])
ad.append(delta)
#print(delta)
if delta>duracion:
i=0
ad=[0]
ed=[0]
ec=[0]
es=[0]
delta=0
start = time.time()
fig.clf()
run_animation
return line, line2,line3
#time.sleep(0)
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate,frames=30, interval=1, init_func=init, blit=True)
def run_animation():
init
delta=0
start = time.time()
animate(0)
plt.show()
plt.show()
My problem is to resume an matplotlib ArtistsAnimation
My code is:
def PlotFields(self):
fig = plt.figure()
axess = []
for i in range(0,self.number_fields):
axess.append(fig.add_subplot((self.number_fields+1)//2,2,i+1))
#find min and max in time
mins = {}
maxs = {}
for key,field in self.fields.items():
mins[key] = field[:,:,:,self.z_slice].min()
maxs[key] = field[:,:,:,self.z_slice].max()
if mins[key] == maxs[key]:#this fixes bug in imshow when vmin = vmax
mins[key] = mins[key]-0.1
maxs[key] = maxs[key]+0.1
#set up list of images for animation
movie = []
images = []
nt = self.fields[list(self.fields)[0]].shape[0] #number of time slices
print('time slices = {}'.format(nt))
first = 1
for time in range(0,nt):
images.clear()
i = 0
for key,field in self.fields.items():
if self.cut == 'xy':
images.append(axess[i].pcolor(field[time,:,:,self.z_slice].T, vmin = mins[key], vmax = maxs[key]))
axess[i].set_xlabel('x')
axess[i].set_ylabel('y')
elif self.cut == 'xz':
images.append(axess[i].pcolor(field[time,:,:,self.y_slice].T, vmin = mins[key], vmax = maxs[key]))
axess[i].set_xlabel('x')
axess[i].set_ylabel('z')
else:
print('unknown cut --- exiting !!')
quit()
axess[i].set_title(key)
i = i + 1
if first == 1:
for i in range(0,self.number_fields):
fig.colorbar(images[i], ax = axess[i])
first = 0
# time_title.set_text('t={}'.format(t_array[time]))
time_title = axess[0].annotate('t={}'.format(self.t_array[time]),xy = (0.1,1.2))
collected_list = [*images] #apparently a bug in matplotlib forces this solution
collected_list.append(time_title)
movie.append(collected_list)
#for i in range(0,number_fields):
# fig.colorbar(images[i], ax = axess[i])
#run animation
self.ani = anim.ArtistAnimation(fig, movie, interval=500, blit = False, repeat_delay = 1000)
fig.canvas.mpl_connect('button_press_event', self.onClick)
if self.save_movie == True:
try:
ani.save('xy_film.mp4')
#ani.save('film.mp4',writer = FFwriter, fps=30, extra_args=['-vcodec', 'libx264'])
except Exception:
print("Save failed: Check ffmpeg path")
plt.show()
def onClick(self, event):
self.pause == True
if self.pause == False:
self.ani._stop()
self.pause = True
print('self pause = False')
else:
self.ani._start()
self.pause = False
#pause ^= True
print('self pause = True')
The animation stop onClick but throws the following error on the second click which is supposed to resume the animation (if possible):
File "PlotFieldsFieldAligned.py", line 149, in onClick
self.ani._start()
File "/home/yolen/scicomp/anaconda3/lib/python3.5/site- packages/matplotlib/animation.py", line 665, in _start
self.event_source.add_callback(self._step)
AttributeError: 'NoneType' object has no attribute 'add_callback'
Any help appreciated:-D
Although it seems that in this case a FuncAnimation might be better suited than an ArtistAnimation, both can be
stopped / started the same way. See this question stop / start / pause in python matplotlib animation.
The main point is that the 'private' ArtistAnimation._start() function is not doing what you think it does. Therefore it is sensible to use the ArtistAnimation.event_source.stop() and ArtistAnimation.event_source.start() functions.
Below is a minimal, runnable example showing how to start/stop an ArtistAnimation by clicking with the mouse button.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
class SomeClass():
def __init__(self):
self.pause = False
self.fig, ax = plt.subplots()
ax.set_aspect("equal")
self.movie = []
nt = 10
X,Y = np.meshgrid(np.arange(16), np.arange(16))
for t in range(nt):
data = np.sin((X+t*1)/3.)**2+ 1.5*np.cos((Y+t*1)/3.)**2
pc = ax.pcolor(data)
self.movie.append([pc])
self.ani = animation.ArtistAnimation(self.fig, self.movie, interval=100)
self.fig.canvas.mpl_connect('button_press_event', self.onClick)
plt.show()
def onClick(self, event):
if self.pause:
self.ani.event_source.stop()
else:
self.ani.event_source.start()
self.pause ^= True
a = SomeClass()
EDIT: The ultimate requirement for such a Python program is: Receive data from UART from a external circuitry (which probably is equipped with some sensors), the program will process these data, and draw a dynamically updated curve on the computer screen.
So, I want to plot dynamically, the following test script starts a sub-process, and in that process, it accepts data from parent process through a Queue, and plot data accordingly.
But when the script is run, only an empty figure is shown, I can see the console prints "put:" and "got:" messages, meaning both parent and subprocess are running and communicating, but nothing happens in the GUI figure window.
Furthermore, the GUI window is not responsive and if I click on the window, it will crash.
The system is Windows 10, 64 bit. Python version is 2.7 (32bit)
What's the problem here? thank you!
import matplotlib.pyplot as plt
import multiprocessing as mp
import random
import numpy
import time
def worker(q):
plt.ion()
ln, = plt.plot([], [])
plt.show()
while True:
obj = q.get()
n = obj + 0
print "sub : got:", n
ln.set_xdata(numpy.append(ln.get_xdata(), n))
ln.set_ydata(numpy.append(ln.get_ydata(), n))
plt.draw()
if __name__ == '__main__':
queue = mp.Queue()
p = mp.Process(target=worker, args=(queue,))
p.start()
while True:
n = random.random() * 5
print "main: put:", n
queue.put(n)
time.sleep(1.0)
You have to rescale, otherwise nothing will appear:
This works on my computer :
import matplotlib.pyplot as plt
import multiprocessing as mp
import random
import numpy
import time
def worker(q):
#plt.ion()
fig=plt.figure()
ax=fig.add_subplot(111)
ln, = ax.plot([], [])
fig.canvas.draw() # draw and show it
plt.show(block=False)
while True:
obj = q.get()
n = obj + 0
print "sub : got:", n
ln.set_xdata(numpy.append(ln.get_xdata(), n))
ln.set_ydata(numpy.append(ln.get_ydata(), n))
ax.relim()
ax.autoscale_view(True,True,True)
fig.canvas.draw()
if __name__ == '__main__':
queue = mp.Queue()
p = mp.Process(target=worker, args=(queue,))
p.start()
while True:
n = random.random() * 5
print "main: put:", n
queue.put(n)
time.sleep(1.0)
Till now I'd like to flag my following sample program as the answer to my question. It definitely is not the perfect one, or maybe it's even not the correct way to do that in Python and matplotlib.
I think the important thing to not cause unresponsiveness on the figure is not to hang the "UI" thread, which when the UI is shown, matplotlib probably is running a event loop on it, so if I put any time.sleep(0.1) or call Queue.get() which block the thread execution, the figure window will just hang.
So instead of blocking the thread at "Queue.get()", I choose to use "Queue.get_nowait()" as a polling method for incoming new data. The UI thread (ie, matplotlib figure window updating worker) will only block at matplotlib.pyplot.pause(), which will not suspend the event loop I believe.
If there is another call in matplotlib that can block and wait for a signal, I think that would be better than this polling approach.
At first I see multiprocessing examples with matplotlib, so I was trying to use multiple processes for concurrency. But it seems that you just need to take care of the synchronization yourself, it is okay to use multithreading instead. And multithreading has the benefit of sharing data within one process. So the following program utilized threading module instead of multiprocessing.
The following is my test program, I can run it on Windows 7 (64 bit) with Python 2.7, and the Figure Window is responsive at this rate of updating, you can drag it, resize it and so on.
#!/usr/bin/python
# vim: set fileencoding=utf-8:
import random
import time
import Queue
import threading
import numpy as np
import matplotlib.pyplot as plt
## Measurement data that are shared among threads
val1 = []
val2 = []
lock = threading.Lock()
def update_data_sync(x, y):
lock.acquire()
val1.append(x)
val2.append(y)
if len(val1) > 50:
del val1[0]
if len(val2) > 50:
del val2[0]
lock.release()
def get_data_sync():
lock.acquire()
v1 = list(val1)
v2 = list(val2)
lock.release()
return (v1, v2)
def worker(queue):
plt.ion()
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.margins(0.05, 0.05)
#ax.set_autoscale_on(True)
ax.autoscale(enable=True, axis='both')
#ax.autoscale(enable=True, axis='y')
ax.set_ylim(0, 1)
line1, line2 = ax.plot([], [], 'b-', [], [], 'r-')
while True:
need_draw = False
is_bye = False
while True:
## Try to exhaust all pending messages
try:
msg = queue.get_nowait()
if msg is None:
print "thread: FATAL, unexpected"
sys.exit(1)
if msg == 'BYE':
print "thread: got BYE"
is_bye = True
break
# Assume default message is just let me draw
need_draw = True
except Queue.Empty as e:
# Not 'GO' or 'BYE'
break
## Flow control
if is_bye:
break
if not need_draw:
plt.pause(0.33)
continue;
## Draw it
(v1, v2) = get_data_sync()
line1.set_xdata(range(1, len(v1) + 1, 1))
# Make a clone of the list to avoid competition on the same dataset
line1.set_ydata(v1)
line2.set_xdata(line1.get_xdata())
line2.set_ydata(v2)
## Adjust view
#ax.set_xlim(0, len(line1.get_ydata()) + 1)
#ax.set_ylim(0, 1)
## (??) `autoscale' does not work here...
#ax.autoscale(enable=True, axis='x')
#ax.autoscale(enable=True, axis='y')
ax.relim()
ax.autoscale_view(tight=True, scalex=True, scaley=False)
## "Redraw"
## (??) Maybe pyplot.pause() can ensure visible redraw
fig.canvas.draw()
print "thread: DRAW"
plt.pause(0.05)
## Holy lengthy outermost `while' loop ends here
print "thread: wait on GUI"
plt.show(block=True)
plt.close('all')
print "thread: worker exit"
return
def acquire_data():
# Fake data for testing
if not hasattr(acquire_data, 'x0'):
acquire_data.x0 = 0.5
x = int(random.random() * 100) / 100.0
while np.abs(x - acquire_data.x0) > 0.5:
x = int(random.random() * 100) / 100.0
acquire_data.x0 = x
y = 0.75 * np.abs(np.cos(i * np.pi / 10)) + 0.15
return (x, y)
if __name__ == "__main__":
queue = Queue.Queue()
thr = threading.Thread(target=worker, args=(queue, ))
thr.start()
for i in range(200):
(x, y) = acquire_data()
update_data_sync(x, y)
#print "main: val1: {}. val2: {}".format(x, y)
queue.put("GO")
time.sleep(0.1)
queue.put('BYE')
print "main: waiting for children"
thr.join()
print "main: farewell"
Made samll modifications to above code. I used Process, Queue, Value, Array in multiprocessing package to reduce the code complexity. Script will plot [0,0], [1,1], [2,2] etc on a graph
from multiprocessing import Process, Queue, Value, Array
import time
import matplotlib.pyplot as plt
from queue import Empty
def f(q, num, arr1, arr2):
plt.ion()
fig = plt.figure()
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.margins(0.05, 0.05)
#ax.set_autoscale_on(True)
ax.autoscale(enable=True, axis='both')
#ax.autoscale(enable=True, axis='y')
ax.set_ylim(0, 100)
line1, line2 = ax.plot([], [], 'b-', [], [], 'r-')
while True :
try:
#val = q.get_nowait()
val = q.get(timeout = 0.8) # reducing the timeout value will improve the response time on graph whie mouse is moved on it
#val = q.get()
if val == 'Exit':
break
if val == 'Go':
x = num.value
#print("num.value = ", x)
v1 = arr1[0:num.value]
v2 = arr2[0:num.value]
#print("v1 :", v1)
#print("v2 :", v2)
line1.set_xdata(range(1, len(v1) + 1, 1))
line1.set_ydata(v1)
line2.set_xdata(line1.get_xdata())
line2.set_ydata(v2)
ax.relim()
ax.autoscale_view(tight=True, scalex=True, scaley=False)
fig.canvas.draw()
#print ("thread: DRAW")
plt.pause(0.05)
except Empty as e:
x = num.value
line1.set_xdata(range(1, len(v1) + 1, 1))
line1.set_ydata(v1)
line2.set_xdata(line1.get_xdata())
line2.set_ydata(v2)
ax.relim()
ax.autoscale_view(tight=True, scalex=True, scaley=False)
fig.canvas.draw()
plt.pause(0.05)
continue
if __name__ == '__main__':
q = Queue()
num = Value('i', 0)
arr1 = Array('d', range(100))
arr2 = Array('d', range(100))
p = Process(target=f, args=(q,num, arr1, arr2, ))
p.start()
for i in range(10):
arr1[i] = i
arr2[i] = i
num.value = i+1
q.put("Go") # prints "[42, None, 'hello']"
time.sleep(1)
q.put("Exit")
p.join()