如何在matplotlib中更新绘图

214

我在重新绘制图形时遇到了问题。我允许用户指定时间比例尺(x轴)中的单位,然后重新计算并调用此函数 plots()。我希望图形只是更新,而不是将另一个图形添加到图中。

def plots():
    global vlgaBuffSorted
    cntr()

    result = collections.defaultdict(list)
    for d in vlgaBuffSorted:
        result[d['event']].append(d)

    result_list = result.values()

    f = Figure()
    graph1 = f.add_subplot(211)
    graph2 = f.add_subplot(212,sharex=graph1)

    for item in result_list:
        tL = []
        vgsL = []
        vdsL = []
        isubL = []
        for dict in item:
            tL.append(dict['time'])
            vgsL.append(dict['vgs'])
            vdsL.append(dict['vds'])
            isubL.append(dict['isub'])
        graph1.plot(tL,vdsL,'bo',label='a')
        graph1.plot(tL,vgsL,'rp',label='b')
        graph2.plot(tL,isubL,'b-',label='c')

    plotCanvas = FigureCanvasTkAgg(f, pltFrame)
    toolbar = NavigationToolbar2TkAgg(plotCanvas, pltFrame)
    toolbar.pack(side=BOTTOM)
    plotCanvas.get_tk_widget().pack(side=TOP)

如果您的图表没有显示出来,可以使用figure.canvas.draw_idle()强制将其显示在屏幕上。 - Kamran Bigdely
8个回答

246
您基本上有两个选择:
  1. 继续当前的做法,但在重新绘制数据之前调用 graph1.clear()graph2.clear()。这是最慢、最简单和最稳定的选项。

  2. 不重新绘制,而是只更新绘图对象的数据。您需要对代码进行一些更改,但这比每次重新绘制要快得多。然而,您正在绘制的数据的形状不能改变,如果数据的范围发生变化,您需要手动重置 x 和 y 轴限制。

以下是第二个选项的示例:

import matplotlib.pyplot as plt
import numpy as np

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))
    fig.canvas.draw()
    fig.canvas.flush_events()

1
我尝试测试"1.",结果是,在重新绘制数据后,我的GUI中绘制了另一组图形,因此现在在重新计算后,我有了4个图形,就像以前一样。 - thenickname
@thenickname - 你的代码中确切的位置是在哪里调用了 clear 函数?你应该在 for 循环内部,在调用 graph1.plot(...)graph2.plot(...) 等函数之前,调用 graph1.clear(); graph2.clear() 函数。 - Joe Kington
3
这是2014年,我努力实现类似的东西... 它按预期工作,但绘图窗口变成了“未响应”状态.. 有什么建议吗? - DevC
1
在2020年使用mpl 3.3.1时,不幸的是这个方法无法工作。这可能与后端有关吗? - oarfish
当我使用第二个选项并使用set_ydata时,它可以工作,但是它会保留旧的线条,即使旧的线条使用linewidth=1,而line1指定为linewidth=2 - Erik Thysell
显示剩余4条评论

60

你也可以像下面这样做: 这将在绘图上画出一个 10x1 的随机矩阵数据,循环50次。

import matplotlib.pyplot as plt
import numpy as np

plt.ion()
for i in range(50):
    y = np.random.random([10,1])
    plt.plot(y)
    plt.draw()
    plt.pause(0.0001)
    plt.clf()

这似乎没有输出图表。我错过了什么吗?在Jupyter笔记本中,我也有%matplotlib inline - MasayoMusic
3
哈哈,当我删除了 plt.clf() 时,它对我起作用了。哦,matplotlib,你这个淘气鬼 :) - mathandy
8
但这不是在更新一个图表!它会绘制50个图表! - Shaun Han
1
这并没有回答问题,只是重复绘制和清除了50次。请注意! - Tanachat
你显然在循环中绘制了50次,每次清除图形。这非常慢。 - Shriraj Hegde

23

这对我起作用了:

from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
    clear_output(wait=True)
    y = np.random.random([10,1])
    plt.plot(y)
    plt.show()

3
你的回答正是我所需要的。谢谢! - Evan Zamir
2
确实是 Jupyter notebook 的完美解决方案。 - legel
5
只能在 Jupyter Notebook 上使用。 - Ed J

20

这对我有用。每次重复调用一个更新图表的函数。

import matplotlib.pyplot as plt
import matplotlib.animation as anim

def plot_cont(fun, xmax):
    y = []
    fig = plt.figure()
    ax = fig.add_subplot(1,1,1)

    def update(i):
        yi = fun()
        y.append(yi)
        x = range(len(y))
        ax.clear()
        ax.plot(x, y)
        print i, ': ', yi

    a = anim.FuncAnimation(fig, update, frames=xmax, repeat=False)
    plt.show()

"fun" 是一个返回整数的函数。FuncAnimation将重复调用 "update",它将这样做 "xmax" 次。


你能举个例子说明如何调用这个函数(特别是如何在函数调用中传递一个函数),以及fun()函数的样子吗? - bjornasm
1
当然。"fun()"是返回整数的任何函数。您可以将该函数作为参数传递给另一个函数,如下所示:"plot_cont(my_function, 123)"。在第86行,您可以看到我调用了plot_cont: https://github.com/vitobasso/audio-ml/blob/bbb3f633ef3638406912d6f75a8c6a7311aea3fc/src/train_spec.py - Victor Basso
2
请注意,必须加上“a =”语句,否则FuncAnimation将被垃圾回收,代码将无法正常工作! - Kiuhnm

10
我发布了一个名为python-drawnow的包,它提供了让图形更新的功能,通常在for循环中调用,类似于Matlab的drawnow
一个使用示例:
from pylab import figure, plot, ion, linspace, arange, sin, pi
def draw_fig():
    # can be arbitrarily complex; just to draw a figure
    #figure() # don't call!
    plot(t, x)
    #show() # don't call!

N = 1e3
figure() # call here instead!
ion()    # enable interactivity
t = linspace(0, 2*pi, num=N)
for i in arange(100):
    x = sin(2 * pi * i**2 * t / 100.0)
    drawnow(draw_fig)

这个软件包适用于任何matplotlib图表,并提供了在每次图表更新后等待或进入调试器的选项。


5
它如何既强大又不稳定? - BlueMoon93
5
我的意思是,"健壮"是指可以与任何Matplotlib图形一起使用,而"不稳定"是指它只是一个"周末项目"。我已经更新了我的回答。 - Scott

8
如果有人在查找我所寻找的内容,我在以下链接中找到了示例:如何使用Matplotlib可视化标量2D数据?http://mri.brechmos.org/2009/07/automatically-update-a-figure-in-a-loop (on web.archive.org),然后修改它们以使用帧输入堆栈的imshow,而不是动态生成和使用轮廓。开始使用形状为(nBins, nBins, nBins)的图像3D数组,称为frames
def animate_frames(frames):
    nBins   = frames.shape[0]
    frame   = frames[0]
    tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
    for k in range(nBins):
        frame   = frames[k]
        tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
        del tempCS1
        fig.canvas.draw()
        #time.sleep(1e-2) #unnecessary, but useful
        fig.clf()

fig = plt.figure()
ax  = fig.add_subplot(111)

win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate_frames, frames)

我发现了一种更简单的方法来处理整个过程,尽管它不够强大:
fig = plt.figure()

for k in range(nBins):
    plt.clf()
    plt.imshow(frames[k],cmap=plt.cm.gray)
    fig.canvas.draw()
    time.sleep(1e-6) #unnecessary, but useful

请注意,这两者似乎只适用于ipython --pylab=tk,也就是说backend = TkAgg

感谢您对所有事情的帮助。

4

虽然上述可能是真的,但对我而言,“在线更新”图形只适用于某些后端,特别是wx。你可以尝试切换到这个后端,例如通过ipython --pylab=wx来启动ipython/pylab!祝好运!


1
谢谢你的留言,我从未使用过交互模式,因为它无法与我使用的默认后端一起正常工作。使用交互模式要比每次想查看图形时都停止执行要好得多! - PierreE
1
其他答案都没能帮助我解决问题。我正在使用PyCharm,问题出在绘图和控制台交互上。我需要在代码主体中添加 "From pylab import *" 并加入 "ion()" 以开启交互模式。现在对我来说已经运行得非常顺畅了。 - shelbypereira

2

基于其他答案,我使用Python装饰器包装了图形的更新,以便将绘图的更新机制与实际绘图分离。这样,更新任何绘图都更容易。

def plotlive(func):
    plt.ion()

    @functools.wraps(func)
    def new_func(*args, **kwargs):

        # Clear all axes in the current figure.
        axes = plt.gcf().get_axes()
        for axis in axes:
            axis.cla()

        # Call func to plot something
        result = func(*args, **kwargs)

        # Draw the plot
        plt.draw()
        plt.pause(0.01)

        return result

    return new_func 

使用示例

然后,您可以像使用任何其他装饰器一样使用它。

@plotlive
def plot_something_live(ax, x, y):
    ax.plot(x, y)
    ax.set_ylim([0, 100])

唯一的限制是您必须在循环之前创建图形:
fig, ax = plt.subplots()
for i in range(100):
    x = np.arange(100)
    y = np.full([100], fill_value=i)
    plot_something_live(ax, x, y)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接