Matplotlib live graph capable of handling long times between data updates - python

I've noticed that every solution to plotting continuously updating data (I've found) with a continuously increasing length has one huge setback - If the data isn't there immediately, the matplotlib window freezes (says not responding). Take this for example:
from matplotlib import pyplot as plt
from matplotlib import animation
from random import randint
from time import sleep
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
line, = ax.plot([])
x = []
y = []
def animate(i):
x.append(i)
y.append(randint(0,10))
for i in range(100000000):
# Do calculations to attain next data point
pass
line.set_data(x, y)
return line,
anim = animation.FuncAnimation(fig, animate,
frames=200, interval=20, blit=True)
plt.show()
This code works fine without the data acquisition for loop in the animate function, but with it there, the graph window freezes. Take this as well:
plt.ion()
x = []
for i in range(1000):
x.append(randint(0,10))
for i in range(100000000):
# Do calculations to attain next data point
pass
plt.plot(x)
plt.pause(0.001)
Also freezes. (Thank god for that, because using this method it's borderline impossible to close as the graph keeps popping up in front of everything. I do not recommend removing the sleep)
This too:
plt.ion()
x = []
for i in range(1000):
x.append(randint(0,10))
for i in range(100000000):
# Do calculations to attain next data point
pass
plt.plot(x)
plt.draw()
plt.pause(0.001)
plt.clf()
Also this: (copied from https://stackoverflow.com/a/4098938/9546874)
import matplotlib.pyplot as plt
import numpy as np
from time import sleep
x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)
# You probably won't need this if you're embedding things in a tkinter plot...
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y, 'r-') # Returns a tuple of line objects, thus the comma
for phase in np.linspace(0, 10*np.pi, 500):
line1.set_ydata(np.sin(x + phase))
for i in range(100000000):
# Do calculations to attain next data point
pass
fig.canvas.draw()
fig.canvas.flush_events()
This is a huge problem, as it's naive to think all the data will come at consistent intervals. I just want a graph that updates when data comes, and doesn't implode in the downtime. Keep in mind the interval between data could change, it could be 2 seconds, or 5 minutes.
EDIT:
After further testing, the FuncAnimation one can be used, but it's very hacky, and is still a bit broken. If you increase the interval to above the expected time of animate, it will work, but every time you pan or zoom the graph, all the data disappears until the next update. So once you have a view, you can't touch it.
Edit:
Changed sleep to a for loop for clarity

Updated Answer:
The problem is that data aquisition or generation and the matplotlib window run on the same thread so that the former is blocking the latter. To overcome this move the data aquisition into a seperate process as shown in this example. Instead of processes and pipes you can also use threads and queues.

See this example with sleep usage, it's working well:
=^..^=
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x)
def animate(i):
y_data = 0
for j in range(10):
y_data = np.random.uniform(-1, j, 1)
line.set_ydata(y_data)
plt.pause(1)
return line,
ani = animation.FuncAnimation(
fig, animate, interval=2, blit=True, save_count=50)
plt.ylim(-2, 11)
plt.show()

Related

Update a chart in realtime with matplotlib

I'd like to update a plot by redrawing a new curve (with 100 points) in real-time.
This works:
import time, matplotlib.pyplot as plt, numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
t0 = time.time()
for i in range(10000000):
x = np.random.random(100)
ax.clear()
ax.plot(x, color='b')
fig.show()
plt.pause(0.01)
print(i, i/(time.time()-t0))
but there is only ~10 FPS, which seems slow.
What is the standard way to do this in Matplotlib?
I have already read How to update a plot in matplotlib and How do I plot in real-time in a while loop using matplotlib? but these cases are different because they add a new point to an existing plot. In my use case, I need to redraw everything and keep 100 points.
I do not know any technique to gain an order of magnitude. Nevertheless you can slightly increase the FPS with
update the line data instead of creating a new plot with set_ydata (and/or set_xdata)
use Figure.canvas.draw_idle() instead of Figure.canvas.draw() (cf. this question).
Thus I would recommand you to try the following:
import time
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
t0 = time.time()
x = np.random.random(100)
l, *_ = ax.plot(x, color='b')
fig.show()
fig.canvas.flush_events()
ax.set_autoscale_on(False)
for i in range(10000000):
x = np.random.random(100)
l.set_ydata(x)
fig.canvas.draw_idle()
fig.canvas.flush_events()
print(i, i/(time.time()-t0))
Note that, as mentioned by #Bhargav in the comments, changing matplotlib backend can also help (e.g. matplotlib.use('QtAgg')).
I hope this help.

Improving the program smoothness [experiencing crash when loading too much data]

Basically, I'm creating a graph with Matplotlib displaying live data every second. The program works just fine (quite smooth - no errors found) as it's initiated. However, after a certain time, the program starts lagging, and finally not responding (my assumption may be by loading too much data?). I'm trying to achieve the ability to handle massive data for my program. What I've done was removed the grid and hide X-axis from the chart, also used thread to run the function separately. It feels like helping just a bit, not much, still experiencing crashes after leaving it on for a while.
If anyone has any suggestions on how could I improve the program to run smoothly please do advise. I've highly appreciated it in advance.
from __future__ import annotations
from concurrent.futures import thread
from itertools import count
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import mplcursors
from mpl_interactions import zoom_factory
import threading
plt.style.use('fivethirtyeight')
x_vals = []
y_vals = []
index = count()
ax = plt.gca()
def animate(i):
data = pd.read_csv('data.csv')
x = data['x_value']
y = data['total_1']
line = plt.plot(x, y)
plt.setp(line,linewidth=0.5, color='r')
mplcursors.cursor(hover=False)
threads = []
for i in range(1):
thread = threading.Thread(target=animate, args=(i,))
thread.start()
threads.append(thread)
disconnect_zoom = zoom_factory(ax)
plt.title('Live Data')
plt.rcParams['font.size'] = 8
ax.get_xaxis().set_visible(False)
ax.grid(False)
ani = FuncAnimation(plt.gcf(), animate, interval=1000)
plt.show()
Here's the example of data.csv (time and value) every second new data will be accumulated
x_value,total_1
02:22:30-08/16/22,-0.049
02:24:00-08/16/22,0.079
02:24:02-08/16/22,0.081
02:24:03-08/16/22,0.083
02:24:04-08/16/22,0.084
02:24:05-08/16/22,0.073
02:24:06-08/16/22,0.073
02:24:07-08/16/22,0.073
02:24:08-08/16/22,0.073
02:24:09-08/16/22,0.083
This is not a good strategy because you are accumulating data on the plot through plt.plot().
The best way to go about this is, before calling plt.plot():
get the axes:
ax = plt.gca()
get the lines in the axes:
lines = ax.get_lines()
remove the lines:
lines[0].remove()
add the new line:
line = plt.plot(x,y)
This would prevent the graph from including too many lines (also, maybe this is not the best strategy, depending on how big data.csv gets).
Also, the thread is not doing anything really.
EDIT
I have tried to modify your code as little as possible. But I have removed the thread inclusion, which didn't feel like a sensible choice.
Try the code below and let me know:
from __future__ import annotations
from concurrent.futures import thread
from itertools import count
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import threading
import mplcursors
plt.style.use('fivethirtyeight')
x_vals = []
y_vals = []
index = count()
ax = plt.gca()
def animate(i):
data = pd.read_csv('data.csv')
x = data['x_value']
y = data['total_1']
# removal of previous lines
ax = plt.gca()
lines = ax.get_lines()
if len(lines) > 0:
lines[0].remove()
line = plt.plot(x, y)
plt.setp(line,linewidth=0.5, color='r')
mplcursors.cursor(hover=False)
i = 1
animate(i)
plt.title('Live Data')
plt.rcParams['font.size'] = 8
ax.get_xaxis().set_visible(False)
ax.grid(False)
ani = FuncAnimation(plt.gcf(), animate, interval=1000)
plt.show()
EDIT 2
I have modified the code to take advantage of the set_data method on the line primitive. This should speed things up a bit.
from __future__ import annotations
from concurrent.futures import thread
from itertools import count
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
#import threading
import mplcursors
plt.style.use('fivethirtyeight')
x_vals = []
y_vals = []
index = count()
ax = plt.gca()
def animate(i):
data = pd.read_csv('data.csv')
x = data['x_value']
y = data['total_1']
# removal of previous lines
ax = plt.gca()
lines = ax.get_lines()
if len(lines) == 0:
plt.plot(x,y,c="r")
else:
lines[0].set_data(x,y)
mplcursors.cursor(hover=False)
i = 1
animate(i)
plt.title('Live Data')
plt.rcParams['font.size'] = 8
ax.get_xaxis().set_visible(False)
ax.grid(False)
ani = FuncAnimation(plt.gcf(), animate, interval=1000)
plt.show()

How gradually plot a curve in console

So the following code creates a gif file.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.linspace(0, 10, 100)
y = np.sin(x)
fig, ax = plt.subplots()
line, = ax.plot(x, y, color='k')
def update(num, x, y, line):
line.set_data(x[:num], y[:num])
line.axes.axis([0, 10, 0, 1])
return line,
ani = animation.FuncAnimation(fig, update, len(x), fargs=[x, y, line],
interval=25, blit=True)
ani.save('test.gif')
plt.show()
When I run it, it shows the final result of the animation in the console.
However, I would like to see the entire animation in the console.
How can this be done?
It should also work when there is a large number of frames.
EDIT:
I am using Python 3.8.5 and Spyder 4.2.1. I would like to use the 'plots' pane of Spyder.
Well I dont have enough reputation for a comment
But as far as i know ani.save() is blocking try to switch the last two lines.
plt.show(block=False)
ani.save("test.gif")
The window will close as soon ani.save() is done if you don't want that change block to True but it will not save until you closed the window

Matplotlib (or mplfinance) two animation.FuncAnimation with different intervals

In python matplotlib finance
Is it possible to have two different figures with animation.FuncAnimation in mplfinance where one has 12 axis with different style and another figure has two planes one for bar and another for volume.
Another reason is that both figures having different intervals for refresh
There are two ways that I can think of to do this. I will show examples using simple matplotlib plots so as to focus on the animation function(s). If you understand the mplfinance animation example then you will be able make the analogous changes to make this work with mplfinance.
The two approaches are:
Maintain more than one plot with a single func animation. If different update frequencies are needed, use modulo to update one or more of the plots. The disadvantage here is that the update period of each plot must be some multiple of the update period of the fastest plot.
Create two func animations. This requires that each func animation be assigned to a different variable (and each such variable must remain in scope, i.e. not be deleted or destroyed, for the duration of animation).
Approach 1 Example: Single Func Animation maintaining two plots:
"""
A simple example of TWO curves from one func animation
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2,sharey=ax1)
x = np.arange(0, 2*np.pi, 0.01)
line1, = ax1.plot(x, np.sin(x))
line2, = ax2.plot(x, 0.5*np.sin(2.5*(x)))
def animate(i):
line2.set_ydata(0.5*np.sin(2.5*(x + i/5.0))) # update the data
if i%3 == 0: # modulo: update line1 only every third call
line1.set_ydata(np.sin(x + i/10.0)) # update the data
return line1,line2
ani1 = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=250)
plt.show()
Approach 2 Example: Two Func Animations maintaining two plots:
"""
A simple example of TWO animated plots
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2,sharey=ax1)
x = np.arange(0, 2*np.pi, 0.01)
line1, = ax1.plot(x, np.sin(x))
line2, = ax2.plot(x, 0.5*np.sin(2.5*(x)))
def animate1(i):
line1.set_ydata(np.sin(x + i/10.0)) # update the data
return line1,
def animate2(i):
line2.set_ydata(0.5*np.sin(2.5*(x + i/5.0))) # update the data
return line2,
ani1 = animation.FuncAnimation(fig, animate1, np.arange(1, 200), interval=250)
ani2 = animation.FuncAnimation(fig, animate2, np.arange(1, 200), interval=75)
plt.show()
If you create a script with either of the above examples and run under python (but do NOT run in a notebook or IDE, because that may or may not work) both cases should give an animation that looks something like this:

Matplotlib Animation for Plotting Points Being Connected Given Arrays of X and Y values to be coordinates

I have two arrays containing x and y values. Each array has 1274 values in it. I essentially want to create a matplotlib animation where these points are being plotted and also connected by a line. I tried doing this with FuncAnimation, but ran into a lot of trouble. Imagine that x and y are the two arrays that I'm referring to. Rest of the code is what I tried so far.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from os import getcwd, listdir
gif_path = getcwd() + "/gifs"
fig = plt.figure()
graph, = plt.plot([], [], 'o')
def animate(i):
if i > len(x) - 1:
i = len(x) - 1
graph.set_data(x[:i+1], y[:i+1])
return graph
ani = FuncAnimation(fig, animate, interval=200)
ani.save(f"{gif_path}/sample_region.gif", writer="imagemagick")
Any help would kindly be appreciated. Thanks.
Your code seems to be following an example from matplotlib, you just need a few extra changes:
x = range(100)
y = np.random.rand(100)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 1)
graph, = plt.plot([], [], '-')
def init():
return graph,
def animate(i):
graph.set_data(x[:i],y[:i])
return graph,
ani = FuncAnimation(fig, animate, frames=range(len(x)), interval=50, save_count=len(x),
init_func=init, blit=True)
ani.save('ani.gif', writer='PillowWriter')
This produces this GIF. The changes are:
set up an Axes on fig with set axis limits
change the o to -
add an init function for initializing the animation
add a frames argument to pass indexes used to select data
update animate to handle those frames
But I tried making this with an array of 1200 points, and it didn't seem like my computer could complete it... You can try but you might need to trim the data or plot more data each frame.

Categories