使用Matplotlib以非阻塞的方式绘图

237

我正在尝试使用matplotlib绘制一个函数,但无法阻止程序的执行。

我尝试过像一些人建议的使用show(block=False),但是结果只有一个冻结的窗口。如果我简单地调用show(),则结果会被正确绘制,但是程序的执行会一直阻塞,直到关闭窗口。从我读过的其他帖子中,我怀疑是否show(block=False)的工作与否取决于后端。这是正确的吗?我的后端是Qt4Agg。你能看一下我的代码,告诉我是否发现了问题吗?这是我的代码:

from math import *
from matplotlib import pyplot as plt
print(plt.get_backend())


def main():
    x = range(-50, 51, 1)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4

        y = [Xi**pow for Xi in x]
        print(y)

        plt.plot(x, y)
        plt.draw()
        #plt.show()             #this plots correctly, but blocks execution.
        plt.show(block=False)   #this creates an empty frozen window.
        _ = raw_input("Press [enter] to continue.")


if __name__ == '__main__':
    main()

PS. 我忘了说,我希望每次绘图时更新现有的窗口,而不是创建一个新的窗口。


5
你尝试过使用 plt.ion() 吗?在 plt.show() 之前使用,这样每个图都会被生成到一个子线程中,因此应该不会阻塞程序。 - Anzel
3
你是如何运行你的脚本的?如果我从终端/命令提示符中运行你的示例代码,它似乎可以正常工作,但我认为在尝试从IPython QtConsole或IDE执行类似操作时,我遇到了问题。 - Marius
1
@Marius 哈哈!! 你说得对。我确实是从我的IDE(PyCharm)控制台运行它。当我从cmd提示符运行它时,plt.show(block=False)可以正常工作!如果我问你是否找到了任何想法/解决方案,那会不会太过分了?非常感谢! - opetroch
@Arch Linux Tux,答案是我不知道,没有尝试过。虽然在五年前发布问题之前我已经搜索了很多,但我已经尝试了被接受的答案,它确实有效。 - opetroch
显示剩余3条评论
9个回答

252

我花了很长时间寻找解决方案,最终找到了这个答案

看起来,为了得到我们想要的结果,需要组合使用plt.ion()plt.show()(不带block=False)以及最重要的是plt.pause(.001)(或任何你想要的时间)。pause 是必需的,因为GUI事件发生在主代码睡眠时,包括绘图。可能是通过从睡眠线程中捕获时间实现的,所以也许IDE会影响这一点,我不知道。

这是一个对于python 3.5适用的示例:

import numpy as np
from matplotlib import pyplot as plt

def main():
    plt.axis([-50,50,0,10000])
    plt.ion()
    plt.show()

    x = np.arange(-50, 51)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4
        y = [Xi**pow for Xi in x]
        plt.plot(x, y)
        plt.draw()
        plt.pause(0.001)
        input("Press [enter] to continue.")

if __name__ == '__main__':
    main()

6
你的答案对我解决了一个类似的问题很有帮助。之前,我使用了plt.draw然后是plt.show(block = False),但是它突然停止工作了:图形无响应,关闭它会使iPython崩溃。我的解决方法是删除每个plt.draw()实例,并用plt.pause(0.001)替换它。现在的方式是,在plt.ion()plt.show()之前,而不是像之前的plt.draw一样跟着plt.show(block = False)。虽然现在我有一个MatplotlibDeprecationWarning,但它让我能够绘制我的图形,所以我对这个解决方案感到满意。 - blue_chip
3
请注意,在Python 2.7中,您需要使用raw_input而不是input。请参见这里 - Chris
当响应式“animate”方法不可用时,以下解决方法非常实用!有人知道如何消除弃用警告吗? - Frederic Fortier
3
我不得不将暂停时间增加到0.1秒才能使其正常工作。 - Eddy
2
@krs013:我记得这在过去对我起作用。但是,现在这可以在不使用plt.ion()plt.draw()的情况下工作。我不知道在matplotlib版本中是否有什么变化(我正在使用最新版本3.1.2)。 - s.ouchene
显示剩余5条评论

46

一个对我有效的简单技巧如下:

  1. 在 show 函数中使用 block=False 参数:plt.show(block=False)
  2. 在 .py 脚本的结尾处再次使用 plt.show()

示例:

import matplotlib.pyplot as plt

plt.imshow(add_something)
plt.xlabel("x")
plt.ylabel("y")

plt.show(block=False)

#more code here (e.g. do calculations and use print to see them on the screen

plt.show()

注意: plt.show() 是我的脚本的最后一行。


19
这段文字的意思是,在 Linux 操作系统上,使用 Anaconda 和 Python 2.7 版本时,执行某个程序会打开一个窗口,但该窗口会一直保持空白,直到程序执行结束时才会显示内容。这对于需要在程序执行期间更新图表的情况来说并不实用。 - sh37211
@sh37211 不确定你的目标是什么。在某些情况下,您尝试绘制某些内容,但在绘图命令之后有其他命令,则此功能很有用,因为它允许您绘制并执行其他命令。请参阅此帖子以获取更多信息:http://stackoverflow.com/questions/458209/is-there-a-way-to-detach-matplotlib-plots-so-that-the-computation-can-continue/42674200#comment74050888_42674200。如果您想要更新绘图,则应该使用另一种方法。 - seralouk
1
@sh37211 我在使用 plt.show(block=False) 时遇到了相同的情况(在Windows10/Python 3.8.7和Linux Mint/Python 3.8.5中也是如此)。在这两种情况下,使用 plt.show(block=False);plt.pause(0.01) 可以得到我所需的图形更新。 - marzetti
只有这个方法对我起了魔法般的作用。 - Albert G Lieu
和@sh37211在Windows(10)上一样。我不需要最后的plt.show()。 不过,这是一个有趣的解决方案。谢谢。 - El Bachir
最初这对我不起作用。我能看到第一个图表屏幕,但之后什么也没有了。我不得不在plt.show(block = False)之后再次重复设置任何变量的代码,然后它就可以工作并产生了第二个图表屏幕。我怀疑plt.show(block = False)会清除与在代码中设置的绘图相关的任何先前变量,因此您必须再次设置它们(尽管我不必重新加载yfinance导入的数据变量)。 - mdkb

21

您可以通过将图形写入数组,然后在不同的线程中显示该数组来避免阻塞执行。以下是使用pyformulas 0.2.8的pf.screen同时生成和显示图形的示例:

import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time

fig = plt.figure()

canvas = np.zeros((480,640))
screen = pf.screen(canvas, 'Sinusoid')

start = time.time()
while True:
    now = time.time() - start

    x = np.linspace(now-2, now, 100)
    y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
    plt.xlim(now-2,now+1)
    plt.ylim(-3,3)
    plt.plot(x, y, c='black')

    # If we haven't already shown or saved the plot, then we need to draw the figure first...
    fig.canvas.draw()

    image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
    image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))

    screen.update(image)

#screen.close()

结果:

正弦动画

免责声明:我是pyformulas的维护者。

参考资料:Matplotlib:将图表保存到numpy数组


15

实时绘图

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
# plt.axis([x[0], x[-1], -1, 1])      # disable autoscaling
for point in x:
    plt.plot(point, np.sin(2 * point), '.', color='b')
    plt.draw()
    plt.pause(0.01)
# plt.clf()                           # clear the current figure

如果数据量过大,您可以使用一个简单的计数器来降低更新频率。

cnt += 1
if (cnt == 10):       # update plot each 10 points
    plt.draw()
    plt.pause(0.01)
    cnt = 0

程序退出后保留绘图

这是我的实际问题,找不到满意的答案,我需要绘制图表后脚本结束后仍然保留(就像MATLAB一样)。

如果你想想,脚本结束后程序被终止,没有逻辑方法可以以这种方式保存图表,所以有两个选择:

  1. 阻止脚本退出(就像plt.show()一样,但这不是我想要的)
  2. 在一个单独的线程上运行绘图(太复杂了)

这对我来说都不是很满意,所以我找到了一个创新的解决方案

存储并在外部查看器中查看

为此,保存和查看应该都很快,并且查看器不应锁定文件,应自动更新内容

选择保存格式

基于向量的格式既小又快

  • SVG 不错,但无法找到好的查看器,除了默认需要手动刷新的Web浏览器
  • PDF 可以支持向量格式,并且有轻量级查看器支持实时更新

快速轻量级查看器,支持实时更新

对于PDF,有几个不错的选择

  • 在Windows上,我使用免费、快速和轻量级的SumatraPDF(我的情况下只使用1.8MB RAM)

  • 在Linux上,有几个选项,如Evince (GNOME) 和 Ocular (KDE)

示例代码和结果

输出绘图到文件的示例代码:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(2 * x)
plt.plot(x, y)
plt.savefig("fig.pdf")

首次运行后,使用上述其中一个查看器打开输出文件,然后享受吧。

这里是VSCode和SumatraPDF并排的截图,而且该过程足够快,以获得准实时更新速率(我可以在我的设置中获得近10Hz的速度,只需在间隔之间使用time.sleep())。 pyPlot,Non-Blocking


13
很多回答都被夸大了,而且从我找到的资料来看,答案并不难理解。
你可以使用 plt.ion(),但我发现使用 plt.draw() 也同样有效。
对于我的具体项目,我正在绘制图像,但你可以使用 plot()scatter() 或其他替换 figimage(),这无关紧要。
plt.figimage(image_to_show)
plt.draw()
plt.pause(0.001)
或者
fig = plt.figure()
...
fig.figimage(image_to_show)
fig.canvas.draw()
plt.pause(0.001)

如果您正在使用实际的图形。
我使用了@krs013和@Default Picture的答案来解决这个问题。
希望这可以帮助某人避免不得不在单独的线程上启动每个图形,或者不得不阅读这些小说来找出答案。


当在循环中使用此代码(用于不断更新图像)时,我注意到执行时间会变得很差。添加“plt.clf()”以在每次迭代时清除所有绘制的图像似乎可以解决这个问题。 - Axl
2
这是因为不清除图像会导致内存泄漏,并且会导致matplotlib尝试绘制所有现有的迭代,从而减慢您的执行速度。 - iggy12345
这在我的VS Code中有效。但是,由于某种原因,它不允许我通过单击X关闭任何绘图。 - swimfar2

9

我发现只需要使用plt.pause(0.001)命令,没有其他必要的操作。

plt.show()plt.draw() 是不必要或阻塞程序的。所以这是一段可以绘制和更新图形并持续进行的代码。实际上,plt.pause(0.001) 看起来是与 matlab 的 drawnow 最相似的等价命令。

不幸的是,这些图表将不会是交互式的(它们会冻结),除非您插入一个 input() 命令,但那样程序将停止。

plt.pause(interval) 命令的文档说明如下:

如果有活动图形,则在暂停之前将其更新并显示......这可用于粗略的动画。

这几乎就是我们想要的。尝试这段代码:

import numpy as np
from matplotlib import pyplot as plt

x = np.arange(0, 51)               # x coordinates  
         
for z in range(10, 50):

    y = np.power(x, z/10)          # y coordinates of plot for animation

    plt.cla()                      # delete previous plot
    plt.axis([-50, 50, 0, 10000])  # set axis limits, to avoid rescaling
    plt.plot(x, y)                 # generate new plot
    plt.pause(0.1)                 # pause 0.1 sec, to force a plot redraw


3

对我来说,Iggy的回答是最容易理解的,但是当我进行后续未出现在show时的 subplot 命令时,我遇到了以下错误:

MatplotlibDeprecationWarning: 添加一个与之前轴相同参数的轴会重用早期实例。在将来的版本中,将总是创建并返回新实例。同时,可以通过为每个轴实例传递唯一标签来抑制这个警告并确保未来的行为。

为了避免此错误,帮助关闭(或 清除)用户输入后的图形。

下面是对我有用的代码:

def plt_show():
    '''Text-blocking version of plt.show()
    Use this instead of plt.show()'''
    plt.draw()
    plt.pause(0.001)
    input("Press enter to continue...")
    plt.close()

2

Python的drawnow包允许以非阻塞方式实时更新绘图。
它还可以与网络摄像头和OpenCV一起使用,例如为每个帧绘制测量值。
请参见原始回答


0

替换 matplotlib 的后端可以解决我的问题。
import matplotlib.pyplot as plt 之前写下面这个命令。
替换后端命令应该先运行。

import matplotlib
matplotlib.use('TkAgg')

我的答案来自于 Pycharm does not show plot


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