Matplotlib 中的动画标题

12

我无法弄清如何在使用blit的FuncAnimation图中实现动画标题。根据http://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/Python/Matplotlib - Quickly Updating Text on Axes,我已经构建了一个动画,但文本部分却无法进行动画处理。以下是简化的示例:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

vls = np.linspace(0,2*2*np.pi,100)

fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.005, '', transform = ax.transAxes)

def init():
    ttl.set_text('')
    img.set_data([0],[0])
    return img, ttl
def func(n):
    ttl.set_text(str(n))
    img.set_data(vls,np.sin(vls+.02*n*2*np.pi))
    return img, ttl

ani = animation.FuncAnimation(fig,func,init_func=init,frames=50,interval=30,blit=True)

plt.show()
如果移除blit=True,文本会显示出来,但速度会明显减慢。当使用plt.titleax.set_titleax.text 时似乎会出现问题。
编辑:我找到了第一个链接中第二个示例为什么可以正常工作的原因;文字在img部分内。如果将上面的1.005改成.99,你就能看到我指的是什么了。可能有一种方法可以通过边界框实现这一点,不知道怎么实现...
3个回答

21
请参考以下内容:

请查看“Animating matplotlib axes/ticks”“python matplotlib blit to axes or sides of the figure?”

问题在于,在animation的核心部分,实际保存blit背景的地方(animation.py的第792行),它会获取axes边界框内的内容。当您有多个独立动画的轴时,这是有意义的。但在您的情况下,您只需要关注一个axes,并且我们想要动画化axes边界框之外的内容。通过一些猴子补丁、对mpl内部进行一些探索和接受最快、最脏的解决方案,我们可以解决您的问题:

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

def _blit_draw(self, artists, bg_cache):
    # Handles blitted drawing, which renders only the artists given instead
    # of the entire figure.
    updated_ax = []
    for a in artists:
        # If we haven't cached the background for this axes object, do
        # so now. This might not always be reliable, but it's an attempt
        # to automate the process.
        if a.axes not in bg_cache:
            # bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
            # change here
            bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
        a.axes.draw_artist(a)
        updated_ax.append(a.axes)

    # After rendering all the needed artists, blit each axes individually.
    for ax in set(updated_ax):
        # and here
        # ax.figure.canvas.blit(ax.bbox)
        ax.figure.canvas.blit(ax.figure.bbox)

# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw

vls = np.linspace(0,2*2*np.pi,100)

fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.05, '', transform = ax.transAxes, va='center')

def init():
    ttl.set_text('')
    img.set_data([0],[0])
    return img, ttl

def func(n):
    ttl.set_text(str(n))
    img.set_data(vls,np.sin(vls+.02*n*2*np.pi))
    return img, ttl

ani = animation.FuncAnimation(fig,func,init_func=init,frames=50,interval=30,blit=True)

plt.show()

请注意,如果您的图中有多个轴,则可能无法按预期工作。更好的解决方案是扩展axes.bbox,以便刚好捕获您的标题+轴刻度标签。我怀疑mpl中有一些代码可以做到这一点,但我不知道它在哪里。

这个以全速运行!希望它能被包含在matplotlib中,而不是被monkey patched,但它运行良好! - Henry Schreiner
@HenrySchreiner 您的编辑应该已经被接受了(我刚刚重新做了一遍)。如果这解决了您的问题,您可以接受答案(在左侧的大灰色复选框中)。 - tacaswell
理想情况下,应该使用文本边界框(因为它是从动画函数传递的),但由于某些原因它没有被使用(尽管我认为它在艺术家中)。这是一个合理的 hack,现在可以修复它。 :) 谢谢! - Henry Schreiner
@HenrySchreiner 动画代码还有些粗糙。有很好的理由不这样做主要代码(请参阅我的警告)。如果您想在主线上改进此功能,请随意操作。mpl上的开发人员非常友好。 - tacaswell
如果我在 FuncAnimation 调用中使用 repeat=False,然后等待动画完成并缩放到某个位置,绘图将会消失。 - Stanley Bak
显示剩余3条评论

9

除了tcaswell的“猴子补丁”解决方案外,以下是如何为轴刻度标签添加动画效果的方法。具体来说,要对x轴进行动画处理,请设置ax.xaxis.set_animated(True)并从动画函数中返回ax.xaxis

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

def _blit_draw(self, artists, bg_cache):
    # Handles blitted drawing, which renders only the artists given instead
    # of the entire figure.
    updated_ax = []
    for a in artists:
        # If we haven't cached the background for this axes object, do
        # so now. This might not always be reliable, but it's an attempt
        # to automate the process.
        if a.axes not in bg_cache:
            # bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
            # change here
            bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
        a.axes.draw_artist(a)
        updated_ax.append(a.axes)

    # After rendering all the needed artists, blit each axes individually.
    for ax in set(updated_ax):
        # and here
        # ax.figure.canvas.blit(ax.bbox)
        ax.figure.canvas.blit(ax.figure.bbox)

# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw

vls = np.linspace(0,2*2*np.pi,100)

fig=plt.figure()
img, = plt.plot(np.sin(vls))
ax = plt.axes()
ax.set_xlim([0,2*2*np.pi])
#ttl = ax.set_title('',animated=True)
ttl = ax.text(.5, 1.05, '', transform = ax.transAxes, va='center')

ax.xaxis.set_animated(True)

def init():
    ttl.set_text('')
    img.set_data([0],[0])
    return img, ttl, ax.xaxis

def func(n):
    ttl.set_text(str(n))
    vls = np.linspace(0.2*n,0.2*n+2*2*np.pi,100)
    img.set_data(vls,np.sin(vls))
    ax.set_xlim(vls[0],vls[-1])
    return img, ttl, ax.xaxis

ani = animation.FuncAnimation(fig,func,init_func=init,frames=60,interval=200,blit=True)

plt.show()

1

你必须调用

plt.draw()

之后

ttl.set_text(str(n))

这里有一个非常简单的文本动画示例,位于一个图形内部,"不使用FuncAnimation()". 试一试,你会发现它是否对你有用。
import matplotlib.pyplot as plt
import numpy as np
titles = np.arange(100)
plt.ion()
fig = plt.figure()
for text in titles:
    plt.clf()
    fig.text(0.5,0.5,str(text))
    plt.draw()

2
这确实会绘制它,但它会像 blit=False 一样减慢速度。我希望只重新绘制文本。 - Henry Schreiner
为什么不直接避免动画,使用plt.ion()自己制作动画呢?这样你就可以拥有更多的控制权,并且每一帧都清楚自己在做什么...请查看http://stackoverflow.com/questions/17444655/dynamically-updating-a-graphed-line-in-python/17480397#17480397。 - Pablo
我已经测试过了,它仍然很慢,但是我可以手动调用 canvas.blit。我认为动画是一种比手动构建更新/更受欢迎的方法。 - Henry Schreiner
第一个链接的第二个示例与我正在做的事情有些相似,但它可以正常工作。这可能是因为我的标题在重绘的“img”部分之外?... - Henry Schreiner
3
如何让blit在轴外的艺术家上工作? - tacaswell
显示剩余5条评论

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