如何在数据文件变化时动态更新我的matplotlib图形?
我有一个用Python写的脚本,它可以读取一个数据文件,并使用matplotlib库显示一个包含四个图表的图形。这个数据文件每隔几秒就会更新,因为它是另一个正在同时运行的软件的输出文件。我希望我的matplotlib图形中的四个图表能够每20秒用更新的数据文件刷新一次。我实现这个功能的方法如下:
import pylab as pl
import time
pl.ion()
fig = pl.figure()
while True:
f = open('data.out', 'rb')
#code to parse data and plot four charts
ax = fig.add_subplot(2,2,1)
#...
ax = fig.add_subplot(2,2,4)
#...
pl.draw()
time.sleep(20)
这样做是可以的,但我失去了通常在调用pl.show()时可以使用的缩放和移动按钮,这样就不太理想了。不过,如果把pl.show()换成pl.draw(),脚本就不再更新图表了。有没有办法在不完全失去缩放/移动功能的情况下动态更新图表呢?
3 个回答
你可以这样做。这个方法接受两个列表x和y,并在同一张图上输出一个散点图和一条线性趋势线。
from IPython.display import clear_output
from matplotlib import pyplot as plt
%matplotlib inline
def live_plot(x, y, figsize=(7,5), title=''):
clear_output(wait=True)
plt.figure(figsize=figsize)
plt.xlim(0, training_steps)
plt.ylim(0, 100)
x= [float(i) for i in x]
y= [float(i) for i in y]
if len(x) > 1:
plt.scatter(x,y, label='axis y', color='k')
m, b = np.polyfit(x, y, 1)
plt.plot(x, [x * m for x in x] + b)
plt.title(title)
plt.grid(True)
plt.xlabel('axis x')
plt.ylabel('axis y')
plt.show();
你只需要在一个循环里调用一下 live_plot(x, y)
。下面是它的效果:

在你开发软件的时候,我想你可能会用到多线程的方式。所以在这种情况下,使用一个无限循环是个坏主意,因为这会占用你的主线程。
另外,当涉及到图形用户界面(GUI)时,突然干扰GUI内部的线程也是不好的做法(比如wxPython)。你应该采用事件驱动的设计方式,这样就不会突然打断其他线程,这样可能会导致你的应用崩溃。
使用定时器可以解决这个问题。
定时器在下面的脚本中会执行以下操作:
1/ 调用一个函数来清除之前的艺术家(数据)
2/ 重新绘制数据
3/ 将更改应用到画布上
4/ 创建另一个相同的定时器,设计方式是:一个定时器在完成工作后调用另一个相同的定时器
因为我无法访问你的数据,所以我创建了一个随机数据提供者来做示范。定义的变量delay_repeat可以让你设置刷新间隔,单位是秒。
import pylab as pl
import random
from threading import Timer
def dataprovider():
return [random.randint(0, 8) for i in range(8)]
def random_col():
return ['blue', 'red', 'green', 'orange'][random.randint(0,3)]
# .... #
fig = pl.figure()
axes = [fig.add_subplot(2,2,i) for i in range(1,5)]
paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
# .... #
def clear_arts(paths, all_arts=-1):
if all_arts < 0:
all_arts = len(paths)
for path in paths[:all_arts]:
path.remove()
def refresh_arts(paths, delay_repeat):
# 1 - clear previous artists
clear_arts(paths,all_arts=-1)
# 2 - Get artists paths for cleaning
paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
# 3 - Apply changes
fig.canvas.draw_idle()
# 4 - Create another timer
Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()
# 4- Create a timer that will run function with arguments args and keyword arguments kwargs,
# after interval seconds have passed.
delay_repeat = 2
Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()
# print("process continues here")
pl.show()
你的代码有点模糊,让人不太明白发生了什么。
我可以给你一些建议:如果你只创建一次子图,并保存所有的坐标轴对象,然后再调用显示函数,这样就能保持正常的功能。
之后对这些子图的修改可以这样进行:
#inside while loop
for i in #subplotlist
ax[i].clear() #ax[i] is the axis object of the i'th subplot
ax[i].plot(#plotstuff)
ax[i].draw()
如果你想的话,可以手动添加缩放和移动的工具栏。