I'm building a bot which fetches data from the internet every 30 seconds.
I want to plot this data using matplotlib and be able to update the graphs when I fetch some new one.
At the beginning of my script I initialise my plots.
Then I run a function to update these plots every 30 seconds, but my plot window freezes at this moment.
I've done some research but can't seem to find any working solution:
plt.show(block=False)
plt.pause(0.001)
What am I doing wrong ?
General structure of the code:
import matplotlib.pyplot as plt
import time
def init_plots():
global fig
global close_line
plt.ion()
fig = plt.figure()
main_fig = fig.add_subplot(111)
x = [datetime.fromtimestamp(x) for x in time_series]
close_line, = main_fig.plot(x, close_history, 'k-')
plt.draw()
def update_all_plots():
global close_line
x = [datetime.fromtimestamp(x) for x in time_series]
close_line.set_xdata(time_series)
close_line.set_ydata(close_history)
plt.draw()
# SCRIPT :
init_plots()
while(True):
# Fetch new data...
# Update time series...
# Update close_history...
update_plots()
time.sleep(30)
There is a module in matplotlib for specifically for plots that change over time: https://matplotlib.org/api/animation_api.html
Basically you define an update function, that updates the data for your line objects. That update function can then be used to create a FuncAnimation object which automatically calls the update function every x milliseconds:
ani = FuncAnimation(figure, update_function, repeat=True, interval=x)
There is a simple way to do it, given a panda dataframe . You would usually do something like this to draw(df is dataframe) :
ax = df.plot.line()
Using
df.plot.line(reuse_plot=True,ax=ax)
one can reuse the same figure to redraw it elegantly and probably fast enough.
Possibly duplicate of Matplotlib updating live plot
Related
Edit: I got it to work by omitting matplotlib animation completely and adding plt.ion() before initializing the figures.
I have a measurement device that sends data to my PC that I can read with Python. Every second it sends 100 data points. Every time these 100 points are received, they can be obtained by calling a function. I want to update the figure, starting from X=0 again. The data contains X and Y data.
In other SO threads I found that matplotlib.animation.FuncAnimation can update the graph efficiently, but I have only found examples where data is appended to the graph.
At the moment, my graph is redrawing, but it just draws new points without clearing the previous graph and it is really slow (I can't drag the plot window while it's drawing). The clear function just leaves me with a grey window.
Does anyone know a fast enough workaround for this problem? Thanks a lot!
My current implementation:
def plot():
freq = np.linspace(f_start, f_stop, points, endpoint=True)
def update(frame):
#for i in range (iterations - 1):
print(frame)
complex_data = vna.data(0)
plotFigure(figInitProperties, freq, Rect2Pol(complex_data))
time.sleep(2) # Wait for all points to be scanned
ani = anim.FuncAnimation(figInitProperties[2], func=update, frames=(10), repeat=False)
plt.show()
plot()
figInitProperties returns the fig (size, labels, axis, ticks etc.) of the figure.
You can use ax.clear() or plt.cla() at the beginning of the update function.
I am using Matplotlib to plot a real time event in Anaconda prompt.
When I update plot by plt.draw() or plt.show(), I loose control of the thing I am doing. Plot window acts like its clicked and this blocks my other control on the command prompt.
I tried adding
plt.show(block=False)
but it didnt help.
The code is like below,
fig, ax = plt.subplots()
plt.ion()
plt.show(block=False)
while(True):
ax.plot(y_plt_points,x_plt_points,'ro')
plt.draw()
plt.pause(0.01)
This link has an example of real time plotting with matplotlib. I think the main takeaway is that you don't need to use plt.show() or plt.draw() on every call to plot. The example uses set_ydata instead. Simalarly set_xdata can be used to update your x_axis variables. Code below
import matplotlib.pyplot as plt
import numpy as np
# use ggplot style for more sophisticated visuals
plt.style.use('ggplot')
def live_plotter(x_vec,y1_data,line1,identifier='',pause_time=0.1):
if line1==[]:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
fig = plt.figure(figsize=(13,6))
ax = fig.add_subplot(111)
# create a variable for the line so we can later update it
line1, = ax.plot(x_vec,y1_data,'-o',alpha=0.8)
#update plot label/title
plt.ylabel('Y Label')
plt.title('Title: {}'.format(identifier))
plt.show()
# after the figure, axis, and line are created, we only need to update the y-data
line1.set_ydata(y1_data)
# adjust limits if new data goes beyond bounds
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1
When I run this function on the example below I don't have any trouble using other applications on my computer
size = 100
x_vec = np.linspace(0,1,size+1)[0:-1]
y_vec = np.random.randn(len(x_vec))
line1 = []
i=0
while i<1000:
i=+1
rand_val = np.random.randn(1)
y_vec[-1] = rand_val
line1 = live_plotter(x_vec,y_vec,line1)
y_vec = np.append(y_vec[1:],0.0)
I think this is what you are looking for.
I had a similar issue, fixed it by replacing:
plt.pause(0.01)
with
fig.canvas.flush_events()
A more detailed explanation found here:
How to keep matplotlib (python) window in background?
I want to update my plot every loop iteration. So I don't want to use animation I know there is a lot of examples in network, but on my PC it works only in debug mode. So common case look something like this
import numpy as np
from matplotlib import pyplot as plt
plt.ion() # set plot to animated
ydata = [0] * 50
ax1=plt.axes()
# make plot
line, = plt.plot(ydata)
plt.ylim([10,40])
# start data collection
i=0
while True:
i=i+1
ymin = float(min(ydata))-10
ymax = float(max(ydata))+10
plt.ylim([ymin,ymax])
ydata.append(i)
del ydata[0]
line.set_xdata(np.arange(len(ydata)))
line.set_ydata(ydata) # update the data
plt.draw() # update the plot
And it works grate when I run program step by step, but when I run it in normal mode my figure with plot freezes.
This question already has answers here:
pylab.ion() in python 2, matplotlib 1.1.1 and updating of the plot while the program runs
(2 answers)
Closed 9 years ago.
I'm plotting a line using matplotlib and would like to update my line data as soon as new values are generated. However, once in the loop, no window appears. Even though the printed line indicates the loop is running.
Here's my code:
def inteprolate(u,X):
...
return XX
# generate initial data
XX = inteprolate(u,X)
#initial plot
xdata = XX[:,0]
ydata = XX[:,1]
ax=plt.axes()
line, = plt.plot(xdata,ydata)
# If this is in, The plot works the first time, and then pauses
# until the window is closed.
# plt.show()
# get new values and re-plot
while True:
print "!"
XX = inteprolate(u,XX)
line.set_xdata(XX[:,0])
line.set_ydata(XX[:,1])
plt.draw() # no window
How do I update my plot in real-time when the plt.show() is blocking and plt.draw doesn't update/display the window?
You need to call plt.pause in your loop to give the gui a chance to process all of the events you have given it to process. If you do not it can get backed up and never show you your graph.
# get new values and re-plot
plt.ion() # make show non-blocking
plt.show() # show the figure
while True:
print "!"
XX = inteprolate(u,XX)
line.set_xdata(XX[:,0])
line.set_ydata(XX[:,1])
plt.draw() # re-draw the figure
plt.pause(.1) # give the gui time to process the draw events
If you want to do animations, you really should learn how to use the animation module. See this awesome tutorial to get started.
You'll need plt.ion(). Take a look a this: pylab.ion() in python 2, matplotlib 1.1.1 and updating of the plot while the program runs. Also you can explore the Matplotlib animation classes : http://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/
An efficient way to do the same as #Alejandro is:
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
x = np.linspace(0,2*np.pi,num=100)
y = np.sin(x)
plt.xlim(0,2*np.pi)
plt.ylim(-1,1)
plot = plt.plot(x[0], y[0])[0]
for i in xrange(x.size):
plot.set_data(x[0:i],y[0:i])
plt.draw()
I think this toy code clarify the answer of #ardoi:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,2*np.pi,num=100)
plt.ion()
for i in xrange(x.size):
plt.plot(x[:i], np.sin(x[:i]))
plt.xlim(0,2*np.pi)
plt.ylim(-1,1)
plt.draw()
plt.clf()
Edit:
The previous code display a sinusoidal function by animating it in the screen.
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?
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 matplotlib cookbook examples? Also, check out the more modern animation examples in the matplotlib documentation. Finally, 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.
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()
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()
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()