I'm writing code to update a matplotlib graph in real time while embedded into a PyQt5 application. Separately, the two pieces of code (for the graph and the embedding of a still graph. Putting the two together causes a blank window with a graph to open. Upon closing the window it opens the correct application window, with the embedded graph. However, the graph is not updating, but with the line data of what SHOULD be on the graph at said time.
Removing plt.show() only causes a blank graph to appear in the embedded window. It appears that the code runs on the bank graph that opens first, and then updates it to the hidden graph in the application window.
Is there any...simple fix, or is this going to be a much longer process?
# This aint it chief
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from PyQt5.QtWidgets import QApplication, QWidget
import itertools as itrt
import matplotlib.animation as animation
class Canvas(FigureCanvas):
def __init__(self, parent):
fig, self.ax = plt.subplots()
super().__init__(fig)
self.setParent(parent)
# creates figure "ax" with a grid
self.ax.grid()
# creates time
t = []
# sets time as xdata
xdata = t
# ydata inputs
ydataps1 = []
ydataps2 = []
ydataps3 = []
ydataps4 = []
ydatads1 = []
# generates data for the graph
def datagen():
# creates timer on the x-axis
for cnt in itrt.count():
t = cnt / 10
# y value inputs
ps1y = np.log(np.pi * t)
ps2y = np.log(5 * np.pi * t)
ps3y = np.log(3 * np.pi * t)
ps4y = np.log(2 * np.pi * t)
ds1y = np.log(1.5 * np.pi * t)
# yields data to move it to run function
yield t, ps1y, ps2y, ps3y, ps4y, ds1y
# # creates lines with line-width two, at points t and y[]
lineps1, = self.ax.plot(t, [], lw=2)
lineps2, = self.ax.plot(t, [], lw=2)
lineps3, = self.ax.plot(t, [], lw=2)
lineps4, = self.ax.plot(t, [], lw=2)
lineds1, = self.ax.plot(t, [], lw=2)
# init function, clears line data and sets the line data to be t and y[]
def init():
# clear data
del xdata[:]
del ydataps1[:]
del ydataps2[:]
del ydataps3[:]
del ydataps4[:]
del ydatads1[:]
# set line data to cleared
lineps1.set_data(xdata, ydataps1)
lineps2.set_data(xdata, ydataps2)
lineps3.set_data(xdata, ydataps3)
lineps4.set_data(xdata, ydataps4)
lineds1.set_data(xdata, ydatads1)
# return updated values
return lineps1, lineps2, lineps3, lineps4, lineds1
# updates values for data
def run(data):
# time (t) and y = data
t, yps1, yps2, yps3, yps4, yds1 = data
# update x to be set to time
xdata.append(t)
# update ydatas to new y values
ydataps1.append(yps1)
ydataps2.append(yps2)
ydataps3.append(yps3)
ydataps4.append(yps4)
ydatads1.append(yds1)
# auto-scaling (kinda)
xmin, xmax = self.ax.get_xlim()
ymin, ymax = self.ax.get_ylim()
# compares all y data to ensure the graph scales on the highest value
ydata_list = [yps1, yps2, yps3, yps4, yds1]
max_value = max(ydata_list)
# y scale
if max_value >= ymax:
self.ax.set_ylim(ymin, 2*ymax)
self.ax.figure.canvas.draw()
# Time autoscale
if t >= xmax:
self.ax.set_xlim(xmin, 2*xmax)
self.ax.figure.canvas.draw()
# updates lines
lineps1.set_data(xdata, ydataps1)
lineps2.set_data(xdata, ydataps2)
lineps3.set_data(xdata, ydataps3)
lineps4.set_data(xdata, ydataps4)
lineds1.set_data(xdata, ydatads1)
# returns updated line values
return lineps1, lineps2, lineps3, lineps4, lineds1
# creates an animation function which runs all the functions in a loop
ani = animation.FuncAnimation(fig, run, datagen, interval=1, init_func=init)
# show graph, absolutely necessary
plt.show(block=True)
class AppDemo(QWidget) :
def __init__(self):
super().__init__()
self.resize(1600, 800)
chart = Canvas(self)
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()
sys.exit(app.exec_())
Related
I have created an animated heatmap using holoviews with the 'bokeh' renderer. The code is below and runs in a Jupyter Notebook. Basically I compose a dictionary of heatmaps then I use 'hv.DynamicMap' and 'hv.streams' to create the animation ie. stream keys of dict and render the associated heatmap.
The code runs successfully as an animation but produces an output where a single frame looks like this:
I have two questions:
I want the slider to show the 'key' of the dictionary not just the
index of the list of keys as it does now. So in the image above 'a0' not '0'. How can I do that?
there are warnings being thrown about 'fixed_width'. I have set the
height and width of all the objects so why is there a warning?
import numpy as np
import holoviews as hv
from bokeh.io import show, curdoc, output_notebook
from bokeh.layouts import layout
from bokeh.models import Slider, Button
renderer = hv.renderer('bokeh').instance(mode='server')
output_notebook()
# generate a dict of heatmaps
heatmap_dict = {}
for i in range(10):
heatmap = hv.HeatMap((np.random.randint(0, 10, 100), np.random.choice(['A', 'B', 'C', 'D', 'E'], 100),
np.random.randn(100), np.random.randn(100)), vdims=['z', 'z2']).sort().aggregate(function=np.mean)
heatmap.opts(height=400, width=400)
heatmap_dict['a' + str(i)] = heatmap
heatmap_keys = list(heatmap_dict.keys())
# Create the holoviews app again
def mapping(phase):
key = heatmap_keys[phase]
return heatmap_dict[key]
stream = hv.streams.Stream.define('Phase', phase=0)()
dmap = hv.DynamicMap(mapping, streams=[stream])
# Define valid function for FunctionHandler
# when deploying as script, simply attach to curdoc
def modify_doc(doc):
# Create HoloViews plot and attach the document
hvplot = renderer.get_plot(dmap, doc)
# Create a slider and play buttons
def animate_update():
year = slider.value + 1
if year > end:
year = start
slider.value = year
def slider_update(attrname, old, new):
# Notify the HoloViews stream of the slider update
stream.event(phase=new)
start, end = 0, len(heatmap_keys) - 1
slider = Slider(start=start, end=end, value=start, step=1, title="Phase", height=30, width=180)
slider.on_change('value', slider_update)
callback_id = None
def animate():
global callback_id
if button.label == '► Play':
button.label = '❚❚ Pause'
callback_id = doc.add_periodic_callback(animate_update, 50)
else:
button.label = '► Play'
doc.remove_periodic_callback(callback_id)
button = Button(label='► Play', width=60, height=30)
button.on_click(animate)
# Combine the holoviews plot and widgets in a layout
plot = layout([
[hvplot.state],
[slider, button]], sizing_mode='fixed')
doc.add_root(plot)
return doc
# To display in the notebook
show(modify_doc, notebook_url='localhost:8888')
I have the following code in jupyterlab, which updates a graph in realtime when I move a slider. If I disable blitting it updates, although at a very slow framerate (1 fps). If I enable blitting, it doesn't update at all - any idea why? The code uses the ipympl library to allow realtime update of matplotlib graphs in jupyterlab.
import pandas as pd, numpy as np
import time
import matplotlib.pyplot as plt
import ipywidgets as widgets
from itertools import count
from ipywidgets import Button, Layout
# next line enables ipympl
%matplotlib widget
blit = True # False works, True doesn't.
plt.close('all')
plt.ioff()
output = widgets.Output(layout={'width': '700px', 'height': '300px'})
fig, axs = plt.subplots(3, 2, figsize=(10, 8), sharex=True)
fig.canvas.header_visible = False
fig.canvas.toolbar_visible = False
for i in range(3):
axs[i,0].set_ylim(-1.5,1.5)
axs[i,0].set_xlim(0,20)
# index giver
x_value = count()
# expanding dataset
x, y = [], []
# initialise dummy data
[x.append(next(x_value)) for i in range(2)]
[y.append([1]*3) for i in range(2)]
# setup desired and actual angle plots
col_names = ['col1', 'col2', 'col3']
ax_df = pd.DataFrame(index=x,columns=col_names, data=y).plot(subplots=True, ax=axs[:,0])
if blit:
bgs = []
for ax in ax_df:
# cache the background
ax_background = fig.canvas.copy_from_bbox(ax.bbox)
bgs.append(ax_background)
fig.canvas.draw() # initial draw required
# monitor framerate
t_start = time.time()
# event handler
def on_value_changed(change):
with output:
next_x = next(x_value) # generate next x axis value
x.append(next_x)
y.append([change.new]*3)
for i in range(3):
if blit:
# update data
line = ax_df[i].get_lines()[0]
line.set_data(x, pd.DataFrame(y).iloc[:,i])
# restore background
fig.canvas.restore_region(bgs[i])
# redraw just the points
ax_df[i].draw_artist(line)
# fill in the axes rectangle
fig.canvas.blit(ax_df[i].bbox)
else:
# update data
ax_df[i].get_lines()[0].set_data(x, pd.DataFrame(y).iloc[:,i])
# rescale view
ax_df[i].autoscale_view(None,'x',None)
ax_df[i].relim()
fig.canvas.flush_events()
if not blit:
fig.canvas.draw() # this slows down framerate, not required for blit
print(f"FPS: {round(next_x/(time.time() - t_start),2)}", end=", ")
sliders = []
int_slider = widgets.FloatSlider(description="test",
min=-1, max=1,
value = 0, continuous_update=True,
orientation="horizontal",
layout=Layout(width="500px", height="20px"))
int_slider.observe(on_value_changed, names="value")
sliders = widgets.VBox([int_slider, fig.canvas, output])
display(sliders)
The issue seems to originate from the implementation of matplotlib backend "webagg" used by default in jupyter lab (e.g. when using the magic command %matplotlib widget). Meaning the blitting could work (I haven't checked) if you were to use another backend (qt or tkinter).
=> See this Github Issue for the whole story : Support blitting in webagg backend #19059
Until recently (3/12/2020 & matplotlib 3.3), the blitting was not implemented in the file "backend_webagg_core.py" of the matplotlib module.
Less than a week ago, matplotlib developpers have proposed a patch for this functionnality.
If you want to do it on your own because you don't want to wait for a patch that would come in 2021, you need to modify a few lines in the matplotlib module.
I don't know it this modification would survive a pip update (hopefully not) and I wouldn't recommend it if you don't know what you are doing, since the matplotlib dev team runs several tests to make sure the whole code doesn't break when modifying the module source code.
For this you would need to do modify the class FigureCanvasWebAggCore from backend_webagg_core.py:
class FigureCanvasWebAggCore(backend_agg.FigureCanvasAgg):
supports_blit = True # instead of False
then inside the same class, add the following after the function "draw"
def blit(self, bbox=None):
self._png_is_old = True
self.manager.refresh_all()
you should also modify the following lines ( - means delete / + means add / no symbol means leave as it is):
- last_buffer = (np.frombuffer(self._last_renderer.buffer_rgba(),
- dtype=np.uint32)
- .reshape((renderer.height, renderer.width)))
- diff = buff != last_buffer
+ diff = buff != self._last_buff
same here
- # Swap the renderer frames
- self._renderer, self._last_renderer = (
- self._last_renderer, renderer)
+ # store the current buffer so we can compute the next diff
+ np.copyto(self._last_buff, buff)
and there
- self._last_renderer = backend_agg.RendererAgg(
- w, h, self.figure.dpi)
self._lastKey = key
+ self._last_buff = np.copy(np.frombuffer(
+ self._renderer.buffer_rgba(), dtype=np.uint32
+ ).reshape((self._renderer.height, self._renderer.width)))
Have a look this Github Issue for more details:
Support blitting in webagg backend #19059
A thought about the FPS
Before changing the core code of matplotlib, I would suggest a few modifications of your example.
I would change the way you compute FPS. The formula for computing the time spent to redraw should be imbedded fully within the function that does the drawing.
Creating a panda data frame to update y seems a bit slow to me. A numpty array would be sufficient. Then, this array should live outside the for loop.
def on_value_changed(change):
# monitor framerate
t_start = time.time()
with output:
global x,y
x.append(next_x)
y.append([change.new]*3)
ytmp = np.array(y)
...
print(f"FPS: {round(1/(time.time() - t_start),2)}", end=", ")
By performing those modifications, I get FPS output ranging from 4 to 8.
If I use a for loop to update the matplotlib drawing, the FPS goes between 6 and 11 (mostly around 10, 11):
class stuff:
def __init__(self, x):
self.new = x
s = stuff(0)
for i in range(40):
s.new = np.random.rand()
on_value_changed(s)
Now, to improve this score, we can probably go into the matplotlib code. But it is my beyond my knowledge.
For years, I've been struggling to get efficient live plotting in matplotlib, and to this day I remain unsatisfied.
I want a redraw_figure function that updates the figure "live" (as the code runs), and will display the latest plots if I stop at a breakpoint.
Here is some demo code:
import time
from matplotlib import pyplot as plt
import numpy as np
def live_update_demo():
plt.subplot(2, 1, 1)
h1 = plt.imshow(np.random.randn(30, 30))
redraw_figure()
plt.subplot(2, 1, 2)
h2, = plt.plot(np.random.randn(50))
redraw_figure()
t_start = time.time()
for i in xrange(1000):
h1.set_data(np.random.randn(30, 30))
redraw_figure()
h2.set_ydata(np.random.randn(50))
redraw_figure()
print 'Mean Frame Rate: %.3gFPS' % ((i+1) / (time.time() - t_start))
def redraw_figure():
plt.draw()
plt.pause(0.00001)
live_update_demo()
Plots should update live when the code is run, and we should see the latest data when stopping at any breakpoint after redraw_figure(). The question is how to best implement redraw_figure()
In the implementation above (plt.draw(); plt.pause(0.00001)), it works, but is very slow (~3.7FPS)
I can implement it as:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
And it runs faster (~11FPS), but plots are not up-to date when you stop at breakpoints (eg if I put a breakpoint on the t_start = ... line, the second plot does not appear).
Strangely enough, what does actually work is calling the show twice:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
plt.show(block=False)
Which gives ~11FPS and does keep plots up-to-data if your break on any line.
Now I've heard it said that the "block" keyword is deprecated. And calling the same function twice seems like a weird, probably-non-portable hack anyway.
So what can I put in this function that will plot at a reasonable frame rate, isn't a giant kludge, and preferably will work across backends and systems?
Some notes:
I'm on OSX, and using TkAgg backend, but solutions on any backend/system are welcome
Interactive mode "On" will not work, because it does not update live. It just updates when in the Python console when the interpreter waits for user input.
A blog suggested the implementation:
def redraw_figure():
fig = plt.gcf()
fig.canvas.draw()
fig.canvas.flush_events()
But at least on my system, that does not redraw the plots at all.
So, if anybody has an answer, you would directly make me and thousands of others very happy. Their happiness would probably trickle through to their friends and relatives, and their friends and relatives, and so on, so that you could potentially improve the lives of billions.
Conclusions
ImportanceOfBeingErnest shows how you can use blit for faster plotting, but it's not as simple as putting something different in the redraw_figure function (you need to keep track of what things to redraw).
First of all, the code that is posted in the question runs with 7 fps on my machine, with QT4Agg as backend.
Now, as has been suggested in many posts, like here or here, using blit might be an option. Although this article mentions that blit causes strong memory leakage, I could not observe that.
I have modified your code a bit and compared the frame rate with and without the use of blit. The code below gives
28 fps when run without blit
175 fps with blit
Code:
import time
from matplotlib import pyplot as plt
import numpy as np
def live_update_demo(blit = False):
x = np.linspace(0,50., num=100)
X,Y = np.meshgrid(x,x)
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)
img = ax1.imshow(X, vmin=-1, vmax=1, interpolation="None", cmap="RdBu")
line, = ax2.plot([], lw=3)
text = ax2.text(0.8,0.5, "")
ax2.set_xlim(x.min(), x.max())
ax2.set_ylim([-1.1, 1.1])
fig.canvas.draw() # note that the first draw comes before setting data
if blit:
# cache the background
axbackground = fig.canvas.copy_from_bbox(ax1.bbox)
ax2background = fig.canvas.copy_from_bbox(ax2.bbox)
plt.show(block=False)
t_start = time.time()
k=0.
for i in np.arange(1000):
img.set_data(np.sin(X/3.+k)*np.cos(Y/3.+k))
line.set_data(x, np.sin(x/3.+k))
tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= ((i+1) / (time.time() - t_start)) )
text.set_text(tx)
#print tx
k+=0.11
if blit:
# restore background
fig.canvas.restore_region(axbackground)
fig.canvas.restore_region(ax2background)
# redraw just the points
ax1.draw_artist(img)
ax2.draw_artist(line)
ax2.draw_artist(text)
# fill in the axes rectangle
fig.canvas.blit(ax1.bbox)
fig.canvas.blit(ax2.bbox)
# in this post http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
# it is mentionned that blit causes strong memory leakage.
# however, I did not observe that.
else:
# redraw everything
fig.canvas.draw()
fig.canvas.flush_events()
#alternatively you could use
#plt.pause(0.000000000001)
# however plt.pause calls canvas.draw(), as can be read here:
#http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
live_update_demo(True) # 175 fps
#live_update_demo(False) # 28 fps
Update:
For faster plotting, one may consider using pyqtgraph.
As the pyqtgraph documentation puts it: "For plotting, pyqtgraph is not nearly as complete/mature as matplotlib, but runs much faster."
I ported the above example to pyqtgraph. And although it looks kind of ugly, it runs with 250 fps on my machine.
Summing that up,
matplotlib (without blitting): 28 fps
matplotlib (with blitting): 175 fps
pyqtgraph : 250 fps
pyqtgraph code:
import sys
import time
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import pyqtgraph as pg
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
#### Create Gui Elements ###########
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtGui.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget()
self.mainbox.layout().addWidget(self.canvas)
self.label = QtGui.QLabel()
self.mainbox.layout().addWidget(self.label)
self.view = self.canvas.addViewBox()
self.view.setAspectLocked(True)
self.view.setRange(QtCore.QRectF(0,0, 100, 100))
# image plot
self.img = pg.ImageItem(border='w')
self.view.addItem(self.img)
self.canvas.nextRow()
# line plot
self.otherplot = self.canvas.addPlot()
self.h2 = self.otherplot.plot(pen='y')
#### Set Data #####################
self.x = np.linspace(0,50., num=100)
self.X,self.Y = np.meshgrid(self.x,self.x)
self.counter = 0
self.fps = 0.
self.lastupdate = time.time()
#### Start #####################
self._update()
def _update(self):
self.data = np.sin(self.X/3.+self.counter/9.)*np.cos(self.Y/3.+self.counter/9.)
self.ydata = np.sin(self.x/3.+ self.counter/9.)
self.img.setImage(self.data)
self.h2.setData(self.ydata)
now = time.time()
dt = (now-self.lastupdate)
if dt <= 0:
dt = 0.000000000001
fps2 = 1.0 / dt
self.lastupdate = now
self.fps = self.fps * 0.9 + fps2 * 0.1
tx = 'Mean Frame Rate: {fps:.3f} FPS'.format(fps=self.fps )
self.label.setText(tx)
QtCore.QTimer.singleShot(1, self._update)
self.counter += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
Here's one way to do live plotting: get the plot as an image array then draw the image to a multithreaded screen.
Example using a pyformulas screen (~30 FPS):
import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time
fig = plt.figure()
screen = pf.screen(title='Plot')
start = time.time()
for i in range(10000):
t = time.time() - start
x = np.linspace(t-3, t, 100)
y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
plt.xlim(t-3,t)
plt.ylim(-3,3)
plt.plot(x, y, c='black')
# If we haven't already shown or saved the plot, then we need to draw the figure first...
fig.canvas.draw()
image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))
screen.update(image)
#screen.close()
Disclaimer: I'm the maintainer of pyformulas
I made an animation for sorting algorithms and it it works great for animating one sorting algorithm, but when I try to animate multiple at the same time both windows come up but none of them are moving. I was wondering how I could go around to fix this.
When I run the code the first figure is stuck on the first frame and the second figure jumps to the last frame
import matplotlib.pyplot as plt
from matplotlib import animation
import random
# my class for getting data from sorting algorithms
from animationSorters import *
def sort_anim(samp_size=100, types=['bubblesort', 'quicksort']):
rndList = random.sample(range(1, samp_size+1), samp_size)
anim = []
for k in range(0, len(types)):
sort_type = types[k]
animation_speed = 1
def barlist(x):
if sort_type == 'bubblesort':
l = bubblesort_swaps(x)#returns bubble sort data
elif sort_type == 'quicksort':
l = quicksort_swaps(x)#returns quick sort data
final = splitSwaps(l, len(x))
return final
fin = barlist(rndList)
fig = plt.figure(k+1)
plt.rcParams['axes.facecolor'] = 'black'
n= len(fin)#Number of frames
x=range(1,len(rndList)+1)
barcollection = plt.bar(x,fin[0], color='w')
anim_title = sort_type.title() + '\nSize: ' + str(samp_size)
plt.title(anim_title)
def animate(i):
y=fin[i]
for i, b in enumerate(barcollection):
b.set_height(y[i])
anim.append(animation.FuncAnimation(fig,animate, repeat=False,
blit=False, frames=n, interval=animation_speed))
plt.show()
sort_anim()
As explained in the documentation for the animation module:
it is critical to keep a reference to the instance object. The
animation is advanced by a timer (typically from the host GUI
framework) which the Animation object holds the only reference to. If
you do not hold a reference to the Animation object, it (and hence the
timers), will be garbage collected which will stop the animation.
Therefore you need to return the references to your animations from your function, otherwise those objects are destroyed when exiting the function.
Consider the following simplification of your code:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def my_func(nfigs=2):
anims = []
for i in range(nfigs):
fig = plt.figure(num=i)
ax = fig.add_subplot(111)
col = ax.bar(x=range(10), height=np.zeros((10,)))
ax.set_ylim([0, 1])
def animate(k, bars):
new_data = np.random.random(size=(10,))
for j, b in enumerate(bars):
b.set_height(new_data[j])
return bars,
ani = animation.FuncAnimation(fig, animate, fargs=(col, ), frames=100)
anims.append(ani)
return anims
my_anims = my_func(3)
# calling simply my_func() here would not work, you need to keep the returned
# array in memory for the animations to stay alive
plt.show()
I am trying to display data from a sensor on a Matplotlib live chart.
I want to create a thread which reads the sensor continuously. I then use matplotlib.animation to update the graphs with whatever data is in the sensor reading list at that time.
The problem is that the matplotlib.animation only seems to read data from the sensor once, then freezes. Any idea on how to fix this ?
import random
import threading
import matplotlib.animation as animation
import matplotlib.pyplot as plt
def ReadSensors():
global listXData
global listTemp
# Should be sensor data. Using dummy random data for now...
listTemp = [ random.randint(0,5) for x in listXData ]
def UpdateFigure(oFrame):
global listXData
oCurve_Temp.set_data(listXData, listTemp)
return oCurve_Temp,
# Initialize data lists
listXData = range(0,100)
listTemp = [0 for x in listXData]
# Initialize graph
fig, ax = plt.subplots()
ax.set_title('Temperature')
ax.set_xlabel('Data Point')
ax.set_ylabel('[oC]')
ax.set_xlim( 0, 100)
ax.set_ylim( 0, 5)
oCurve_Temp, = ax.plot([],[])
# Starts reading sensor
oThread_ReadSensors = threading.Thread(target = ReadSensors)
oThread_ReadSensors.daemon = True
oThread_ReadSensors.start()
# Update graph
ani = animation.FuncAnimation(fig, UpdateFigure, interval=500)
plt.show()
You don't see any animation because the data does not actually change. So the animation shows all the same data all over again.
It would make sense to actually change the data.
Below the data is changed every 600 milliseconds, and the animation shows a new frame every 400 milliseconds, hence some frames show the same data as the previous one.
import time
import random
import threading
import matplotlib.animation as animation
import matplotlib.pyplot as plt
def ReadSensors():
global listXData
global listTemp
# Should be sensor data. Using dummy random data for now...
while True:
listTemp = [ random.randint(0,5) for x in listXData ]
time.sleep(0.6)
def UpdateFigure(oFrame):
print "update"
global listXData
oCurve_Temp.set_data(listXData, listTemp)
return oCurve_Temp,
# Initialize data lists
listXData = range(0,100)
listTemp = [0 for x in listXData]
# Initialize graph
fig, ax = plt.subplots()
ax.set_title('Temperature')
ax.set_xlabel('Data Point')
ax.set_ylabel('[oC]')
ax.set_xlim( 0, 100)
ax.set_ylim( 0, 5)
oCurve_Temp, = ax.plot([],[])
# Starts reading sensor
oThread_ReadSensors = threading.Thread(target = ReadSensors)
oThread_ReadSensors.daemon = True
oThread_ReadSensors.start()
# Update graph
ani = animation.FuncAnimation(fig, UpdateFigure, interval=400)
plt.show()
# Join the thread not to run into
# an unterminated threat when closing the figure
oThread_ReadSensors.join()