I am making an application in Python which collects data from a serial port and plots a graph of the collected data against arrival time. The time of arrival for the data is uncertain. I want the plot to be updated when data is received. I searched on how to do this and found two methods:
- Clear the plot and re-draw the plot with all the points again.
- Animate the plot by changing it after a particular interval.
I do not prefer the first one as the program runs and collects data for a long time (a day for example), and redrawing the plot will be pretty slow. The second one is also not preferable as time of arrival of data is uncertain and I want the plot to update only when the data is received.
Is there a way in which I can update the plot just by adding more points to it only when the data is received?
-
3Possible duplicate of real-time plotting in while loop with matplotlibTrevor Boyd Smith– Trevor Boyd Smith2016年04月13日 18:14:54 +00:00Commented Apr 13, 2016 at 18:14
4 Answers 4
Is there a way in which I can update the plot just by adding more point[s] to it...
There are a number of ways of animating data in matplotlib, depending on the version you have. Have you seen the animation examples in the matplotlib documentation? The animation API defines a function FuncAnimation which animates a function in time. This function could just be the function you use to acquire your data.
Each method basically sets the data
property of the object being drawn, so doesn't require clearing the screen or figure. The data
property can simply be extended, so you can keep the previous points and just keep adding to your line (or image or whatever you are drawing).
Given that you say that your data arrival time is uncertain your best bet is probably just to do something like:
import matplotlib.pyplot as plt
import numpy
hl, = plt.plot([], [])
def update_line(hl, new_data):
hl.set_xdata(numpy.append(hl.get_xdata(), new_data))
hl.set_ydata(numpy.append(hl.get_ydata(), new_data))
plt.draw()
Then when you receive data from the serial port just call update_line
.
-
Finally! I've been looking for an answer to this +1 :) How do we make the plot rescale automatically. ax.set_autoscale_on(True) doesn't seem to work.Edward Newell– Edward Newell2013年05月12日 03:08:32 +00:00Commented May 12, 2013 at 3:08
-
17Found the answer: call ax.relim() then ax.autoscale_view() after updating the data but before calling plt.draw()Edward Newell– Edward Newell2013年05月12日 03:30:54 +00:00Commented May 12, 2013 at 3:30
-
The link to the Matplotlib cookbook (scipy.org/Cookbook/Matplotlib/Animations) seems to be broken (I get a "Forbidden" error)David Doria– David Doria2015年09月18日 10:55:38 +00:00Commented Sep 18, 2015 at 10:55
-
34Since there is no call to show(), the plot never appears on the screen. If I call show(), it blocks and doesn't perform the updates. Am I missing something? gist.github.com/daviddoria/027b5c158b6f200527a4David Doria– David Doria2015年09月18日 11:14:09 +00:00Commented Sep 18, 2015 at 11:14
-
4link to a similar but different self-contained answer with code that you can run (this answer has the right general idea but the example code can't be run)Trevor Boyd Smith– Trevor Boyd Smith2016年04月13日 16:46:46 +00:00Commented Apr 13, 2016 at 16:46
In order to do this without FuncAnimation (eg you want to execute other parts of the code while the plot is being produced or you want to be updating several plots at the same time), calling draw
alone does not produce the plot (at least with the qt backend).
The following works for me:
import matplotlib.pyplot as plt
plt.ion()
class DynamicUpdate():
#Suppose we know the x range
min_x = 0
max_x = 10
def on_launch(self):
#Set up plot
self.figure, self.ax = plt.subplots()
self.lines, = self.ax.plot([],[], 'o')
#Autoscale on unknown axis and known lims on the other
self.ax.set_autoscaley_on(True)
self.ax.set_xlim(self.min_x, self.max_x)
#Other stuff
self.ax.grid()
...
def on_running(self, xdata, ydata):
#Update data (with the new _and_ the old points)
self.lines.set_xdata(xdata)
self.lines.set_ydata(ydata)
#Need both of these in order to rescale
self.ax.relim()
self.ax.autoscale_view()
#We need to draw *and* flush
self.figure.canvas.draw()
self.figure.canvas.flush_events()
#Example
def __call__(self):
import numpy as np
import time
self.on_launch()
xdata = []
ydata = []
for x in np.arange(0,10,0.5):
xdata.append(x)
ydata.append(np.exp(-x**2)+10*np.exp(-(x-7)**2))
self.on_running(xdata, ydata)
time.sleep(1)
return xdata, ydata
d = DynamicUpdate()
d()
-
2Yes! Finally a solution which works with Spyder! The thing I was missing was gcf().canvas.flush_events() after the draw()-command.Niko Fohr– Niko Fohr2016年08月17日 10:58:04 +00:00Commented Aug 17, 2016 at 10:58
-
Based on this great example I wrote a small Python module allowing for repetitive plotting: github.com/lorenzschmid/dynplotlorenzli– lorenzli2019年04月17日 12:37:42 +00:00Commented Apr 17, 2019 at 12:37
-
1
-
1Clear, concise, versatile, flexible: this should be the accepted answer.pfabri– pfabri2020年04月27日 11:00:16 +00:00Commented Apr 27, 2020 at 11:00
-
4To use this in a Jupyter Notebook, you must add the
%matplotlib notebook
magic command after your matplotlib import statement.pfabri– pfabri2020年06月11日 08:58:15 +00:00Commented Jun 11, 2020 at 8:58
Here is a way which allows to remove points after a certain number of points plotted:
import matplotlib.pyplot as plt
# generate axes object
ax = plt.axes()
# set limits
plt.xlim(0,10)
plt.ylim(0,10)
for i in range(10):
# add something to axes
ax.scatter([i], [i])
ax.plot([i], [i+1], 'rx')
# draw the plot
plt.draw()
plt.pause(0.01) #is necessary for the plot to update for some reason
# start removing points if you don't want all shown
if i>2:
ax.lines[0].remove()
ax.collections[0].remove()
-
Have you tried running this without the plt.draw(). I have run similar code with only plt.pause(duration) and it workedRahul K.– Rahul K.2024年03月28日 09:38:16 +00:00Commented Mar 28, 2024 at 9:38
I know I'm late to answer this question, but for your issue you could look into the "joystick" package. I designed it for plotting a stream of data from the serial port, but it works for any stream. It also allows for interactive text logging or image plotting (in addition to graph plotting). No need to do your own loops in a separate thread, the package takes care of it, just give the update frequency you wish. Plus the terminal remains available for monitoring commands while plotting. See http://www.github.com/ceyzeriat/joystick/ or https://pypi.python.org/pypi/joystick (use pip install joystick to install)
Just replace np.random.random() by your real data point read from the serial port in the code below:
import joystick as jk
import numpy as np
import time
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the doc
"""
self._t0 = time.time() # initialize time
self.xdata = np.array([self._t0]) # time x-axis
self.ydata = np.array([0.0]) # fake data y-axis
# create a graph frame
self.mygraph = self.add_frame(jk.Graph(name="test", size=(500, 500), pos=(50, 50), fmt="go-", xnpts=10000, xnptsmax=10000, xylim=(None, None, 0, 1)))
@_infinite_loop(wait_time=0.2)
def _generate_data(self): # function looped every 0.2 second to read or produce data
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.2 seconds
"""
# concatenate data on the time x-axis
self.xdata = jk.core.add_datapoint(self.xdata, time.time(), xnptsmax=self.mygraph.xnptsmax)
# concatenate data on the fake data y-axis
self.ydata = jk.core.add_datapoint(self.ydata, np.random.random(), xnptsmax=self.mygraph.xnptsmax)
self.mygraph.set_xydata(t, self.ydata)
t = test()
t.start()
t.stop()