使用matplotlib动态标注点

9

我有一个包含线条的动画,现在我想标记这些点。 我尝试使用plt.annotate()plt.text(),但标签不会移动。 这是我的示例代码:

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

def update_line(num, data, line):
    newData = np.array([[1+num,2+num/2,3,4-num/4,5+num],[7,4,9+num/3,2,3]])
    line.set_data(newData)
    plt.annotate('A0', xy=(newData[0][0],newData[1][0]))
    return line,


fig1 = plt.figure()

data = np.array([[1,2,3,4,5],[7,4,9,2,3]])
l, = plt.plot([], [], 'r-')
plt.xlim(0, 20)
plt.ylim(0, 20)
plt.annotate('A0', xy=(data[0][0], data[1][0]))
# plt.text( data[0][0], data[1][0], 'A0')

line_ani = animation.FuncAnimation(fig1, update_line, 25, fargs=(data, l),
    interval=200, blit=True)
plt.show()

请问您需要帮忙吗?

我的下一步是: 我有一些以这些点为起点的向量。这些向量在每个动画步骤中会改变它们的长度和方向。 我该如何对它们进行动画处理呢?

没有动画时,这是有效的:

soa =np.array( [ [data[0][0],data[1][0],F_A0[i][0][0],F_A0[i][1][0]],
               [data[0][1],data[1][1],F_B0[i][0][0],F_B0[i][1][0]],
               [data[0][2],data[1][2],F_D[i][0][0],F_D[i][1][0]] ])
X,Y,U,V = zip(*soa)
ax = plt.gca()
ax.quiver(X,Y,U,V,angles='xy',scale_units='xy',scale=1)

首先非常感谢您快速而且非常有帮助的回答!

我已经通过以下方式解决了我的矢量动画问题:

annotation = ax.annotate("C0", xy=(data[0][2], data[1][2]), xycoords='data',
    xytext=(data[0][2]+1, data[1][2]+1), textcoords='data',
    arrowprops=dict(arrowstyle="->"))

在“update-function”中,我写:

annotation.xytext = (newData[0][2], newData[1][2])
annotation.xy = (data[0][2]+num, data[1][2]+num)

更改向量(箭头)的起始和结束位置。

但是,如果我有100个或更多的向量呢?写下以下代码是不可行的:

annotation1 = ...
annotation2 = ...
    .
    :
annotation100 = ...

我尝试使用列表:

...
annotation = [annotation1, annotation2, ... , annotation100]
...

def update(num):
    ...
    return line, annotation

我遇到了这个错误: AttributeError: 'list' object has no attribute 'axes' 我该怎么办?你有什么想法吗?
3个回答

15

我从这个问题来到这里,其中一个使用了xyxytext的注释需要更新。看起来,为了正确更新注释,需要设置注释的attribute.xy以设置被注释点的位置,并使用注释的.set_position()method来设置注释的位置。设置.xytext属性没有效果--这在我看来有些令人困惑。下面是一个完整的例子:

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

fig, ax = plt.subplots()

ax.set_xlim([-1,1])
ax.set_ylim([-1,1])

L = 50
theta = np.linspace(0,2*np.pi,L)
r = np.ones_like(theta)

x = r*np.cos(theta)
y = r*np.sin(theta)

line, = ax.plot(1,0, 'ro')

annotation = ax.annotate(
    'annotation', xy=(1,0), xytext=(-1,0),
    arrowprops = {'arrowstyle': "->"}
)

def update(i):

    new_x = x[i%L]
    new_y = y[i%L]
    line.set_data(new_x,new_y)

    ##annotation.xytext = (-new_x,-new_y) <-- does not work
    annotation.set_position((-new_x,-new_y))
    annotation.xy = (new_x,new_y)

    return line, annotation

ani = animation.FuncAnimation(
    fig, update, interval = 500, blit = False
)

plt.show()

结果看起来像这样:

上述代码的结果

如果版本有影响的话,此代码已在Python 2.7和3.6上测试,并且在两种情况下设置.xytext都没有效果,而.set_position().xy按预期工作。


是的,由于“注释”对象没有“xytext”属性,因此根本不应该期望annotation.xytext起作用。 - ImportanceOfBeingErnest
@ImportanceOfBeingErnest 很好的观点,一个简单的 dir(annotation) 实际上揭示了这一点。我想知道在哪个时候发生了这种变化。显然,在旧版本中它仍然存在。另一个值得注意的事情是,在窗口模式下,blit = True 不会产生预期的结果。至少对我来说,它显示了一个带有原始坐标的静态注释和一个旋转注释。然而,这个 github 问题 建议如果没有遇到性能问题,则不要使用 blitting,所以也许没关系。 - Thomas Kühn
当你设置 blit=True 时,你需要同时返回两个对象,即 return line,annotation, - ImportanceOfBeingErnest
@ImportanceOfBeingErnest 我确实这样做了(在第一次发布后编辑了代码),但问题仍然存在。无论如何,这并不困扰我,我只是想指出这一点。根据 Github 上的问题,将动画保存到磁盘时,每帧都会重新绘制整个图形。 - Thomas Kühn
对我来说运行良好。可能又是macOS的问题? - ImportanceOfBeingErnest
显示剩余3条评论

8
你需要在更新函数中返回所有发生变化的对象。因此,由于您的注释更改了位置,您也应该将其返回:
line.set_data(newData)
annotation = plt.annotate('A0', xy=(newData[0][0],newData[1][0]))
return line, annotation

您可以在这个教程中了解更多关于matplotlib动画的内容。

您还应该指定init函数,以便FuncAnimation知道在第一次更新时要从图中移除哪些元素。因此,完整的示例代码如下:

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

# Create initial data
data = np.array([[1,2,3,4,5], [7,4,9,2,3]])

# Create figure and axes
fig = plt.figure()
ax = plt.axes(xlim=(0, 20), ylim=(0, 20))

# Create initial objects
line, = ax.plot([], [], 'r-')
annotation = ax.annotate('A0', xy=(data[0][0], data[1][0]))
annotation.set_animated(True)

# Create the init function that returns the objects
# that will change during the animation process
def init():
    return line, annotation

# Create the update function that returns all the
# objects that have changed
def update(num):
    newData = np.array([[1 + num, 2 + num / 2, 3, 4 - num / 4, 5 + num],
                        [7, 4, 9 + num / 3, 2, 3]])
    line.set_data(newData)
    # This is not working i 1.2.1
    # annotation.set_position((newData[0][0], newData[1][0]))
    annotation.xytext = (newData[0][0], newData[1][0])
    return line, annotation

anim = animation.FuncAnimation(fig, update, frames=25, init_func=init,
                               interval=200, blit=True)
plt.show()

1
你可以使用 annotation.xytext = (newData[0][0], newData[1][0]) 来更新注释的位置。 - sodd
1
谢谢@nordev,更新了示例。但是为什么annotation.set_position((newData[0][0], newData[1][0]))不起作用呢?它的文档中写着“设置文本的(xy)位置”。而xytext则被记录为getter :-/ - Viktor Kerkez
2
我在我的类的一个函数中使用了annotation.set_position(),它可以正常工作。 - Christiane Homann
奇怪,你用的是哪个版本的 matplotlib?也许他们修复了它或者弄坏了它 :D - Viktor Kerkez
我使用的是matplotlib.version '1.1.1'。 - Christiane Homann
显示剩余4条评论

1

我想我已经找到了如何通过列表动画多个注释的方法。首先,您只需创建您的注释列表:

for i in range(0,len(someMatrix)):
     annotations.append(ax.annotate(str(i), xy=(someMatrix.item(0,i), someMatrix.item(1,i))))

然后在你的“animate”函数中,按照你已经编写的方式进行:

for num, annot in enumerate(annotations):
    annot.set_position((someMatrix.item((time,num)), someMatrix.item((time,num))))

(如果您不喜欢枚举方式,也可以将其编写为传统的for循环)。不要忘记在返回语句中返回整个注释列表。
然后重要的是在FuncAnimation中设置“blit=False”:
animation.FuncAnimation(fig, animate, frames="yourframecount",
                          interval="yourpreferredinterval", blit=False, init_func=init)

值得注意的是,blit=False可能会减慢速度。但不幸的是,这是我能让注释列表动画正常工作的唯一方法...

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