Dynamically update figure with plot - python

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.

Related

Python Matplotlib Update Plot in the Background

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?

update matplotlib scatter data [duplicate]

I am trying to automatically update a scatter plot.
The source of my X and Y values is external, and the data is pushed automatically into my code in a non-predicted time intervals (rounds).
I have only managed to plot all the data when the whole process ended, whereas I am trying to constantly add and plot data into my canvas.
What I DO get (at the end of the whole run) is this:
Whereas, what I am after is this:
A simplified version of my code:
import matplotlib.pyplot as plt
def read_data():
#This function gets the values of xAxis and yAxis
xAxis = [some values] #these valuers change in each run
yAxis = [other values] #these valuers change in each run
plt.scatter(xAxis,yAxis, label = 'myPlot', color = 'k', s=50)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
There are several ways to animate a matplotlib plot. In the following let's look at two minimal examples using a scatter plot.
(a) use interactive mode plt.ion()
For an animation to take place we need an event loop. One way of getting the event loop is to use plt.ion() ("interactive on"). One then needs to first draw the figure and can then update the plot in a loop. Inside the loop, we need to draw the canvas and introduce a little pause for the window to process other events (like the mouse interactions etc.). Without this pause the window would freeze. Finally we call plt.waitforbuttonpress() to let the window stay open even after the animation has finished.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
plt.draw()
for i in range(1000):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
fig.canvas.draw_idle()
plt.pause(0.1)
plt.waitforbuttonpress()
(b) using FuncAnimation
Much of the above can be automated using matplotlib.animation.FuncAnimation. The FuncAnimation will take care of the loop and the redrawing and will constantly call a function (in this case animate()) after a given time interval. The animation will only start once plt.show() is called, thereby automatically running in the plot window's event loop.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
def animate(i):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=100, repeat=True)
plt.show()
From what I understand, you want to update interactively your plot. If so, you can use plot instead of scatter plot and update the data of your plot like this.
import numpy
import matplotlib.pyplot as plt
fig = plt.figure()
axe = fig.add_subplot(111)
X,Y = [],[]
sp, = axe.plot([],[],label='toto',ms=10,color='k',marker='o',ls='')
fig.show()
for iter in range(5):
X.append(numpy.random.rand())
Y.append(numpy.random.rand())
sp.set_data(X,Y)
axe.set_xlim(min(X),max(X))
axe.set_ylim(min(Y),max(Y))
raw_input('...')
fig.canvas.draw()
If this is the behaviour your are looking for, you just need to create a function appending the data of sp, and get in that function the new points you want to plot (either with I/O management or whatever the communication process you're using).
I hope it helps.

How to update Matplotlib graph when new data arrives?

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

Is there a way to use the pan/zoom tool when a matplotlib script is running?

I am running a Python script that updates a plot in matplotlib every few seconds. The calculations take several minutes and I would like to be able to pan and zoom the plot in the usual way while it is updating. Is this possible?
Failing that, is it possible to interrupt the script (canceling the rest of the calculation) and then pan/zoom the plot?
I have made the following example. The plot updates very nicely, but you cannot use the pan/zoom tool.
import numpy as np
import matplotlib.pyplot as plt
import time
def time_consuming_calculation():
time.sleep(0.001)
return np.random.normal()
ax = plt.subplot(111)
plt.ion()
plt.show(block=False)
bins = np.linspace(-4,4,100)
data = []
for i in range(0,10000):
print 'Iteration % 4i'%i
data.append(time_consuming_calculation())
if i%1000==0:
n,bin_edges = np.histogram(data,bins=bins)
if i == 0:
line, = plt.plot(bin_edges[:-1],n)
else:
line.set_data(bin_edges[:-1],n)
ax.relim() # Would need to disable this if we can use pan/zoom tool
ax.autoscale()
plt.draw()
plt.ioff()
plt.show()

Dynamically updating plot in matplotlib

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()

Categories