Matplotlib动态填充(fill_between)形状(shape)

19
我想在matplotlib中对fill_between形状进行动画处理,但我不知道如何更新PolyCollection的数据。以下是一个简单的示例:我有两条线,并且一直填充它们之间的区域。当然,这些线会发生变化并进行动画处理。
以下是一个虚拟的示例:
import matplotlib.pyplot as plt

# Init plot:
f_dummy = plt.figure(num=None, figsize=(6, 6));
axes_dummy = f_dummy.add_subplot(111);

# Plotting:
line1, = axes_dummy.plot(X, line1_data, color = 'k', linestyle = '--', linewidth=2.0, animated=True);
line2, = axes_dummy.plot(X, line2_data, color = 'Grey', linestyle = '--', linewidth=2.0, animated=True);
fill_lines = axes_dummy.fill_between(X, line1_data, line2_data, color = '0.2', alpha = 0.5, animated=True);

f_dummy.show();
f_dummy.canvas.draw();
dummy_background = f_dummy.canvas.copy_from_bbox(axes_dummy.bbox);

# [...]    

# Update plot data:
def update_data():
   line1_data = # Do something with data
   line2_data = # Do something with data
   f_dummy.canvas.restore_region( dummy_background );
   line1.set_ydata(line1_data);
   line2.set_ydata(line2_data);
   
   # Update fill data too

   axes_dummy.draw_artist(line1);
   axes_dummy.draw_artist(line2);

   # Draw fill too
   
   f_dummy.canvas.blit( axes_dummy.bbox );
问题是如何在每次调用update_data()并在blit(“# Update fill data too”和“# Draw fill too”之前)之前绘制基于line1_dataline2_datafill_between Poly数据。 我尝试使用fill_lines.set_verts()但没有成功,也找不到示例。

1
你可能需要删除并完全重新绘制每个帧。 *collection 对象在更新时不太友好。原因是它们丢弃了所有元数据,这些元数据允许您在数据空间和绘图空间之间进行映射,并仅保留要绘制的列表。这是为了快速渲染而做出的权衡。 - tacaswell
你的意思是在update_data函数中使用f_dummy.canvas.draw()吗?我一开始就是这样做的,但不幸的是,由于我正在实时处理和播放信号,我需要快速绘图而不影响播放(调用draw()会使播放停顿)。如果您知道一种快速的线程技巧来在播放声音的同时重新绘制所有内容,那就太好了 - 我尝试使用threading.start(...)调用draw。我知道还有其他更快的绘图库,但我更喜欢使用matplotlib,并且restore/blit技巧对我来说足够快。 - Sebastian
6个回答

15

好的,正如有人指出的那样,我们正在处理一个集合,因此我们将不得不删除并重新绘制。因此,在update_data函数的某个位置,删除与其关联的所有集合:

axes_dummy.collections.clear()

并绘制新的“fill_between” PolyCollection:

axes_dummy.fill_between(x, y-sigma, y+sigma, facecolor='yellow', alpha=0.5)

在将一张未填充的轮廓图叠加在填充的轮廓图上时,需要使用类似的技巧,因为未填充的轮廓图也是一种Collection(我猜是由线条组成的?)。


2
如果您想要删除一个集合,可以使用 coll.get_label() 来获取正确的集合,前提是您已经先对该集合进行了标记。 - Sardathrion - against SE abuse

5

这不是我的答案,但我认为它非常有用:

http://matplotlib.1069221.n5.nabble.com/animation-of-a-fill-between-region-td42814.html

嗨,Mauricio, Patch对象比线对象更难处理,因为与线对象不同,它们与用户提供的输入数据相差一步。这里有一个类似于您想要做的示例:http://matplotlib.org/examples/animation/histogram.html

基本上,您需要在每个帧中修改路径的顶点。它可能看起来像这样:

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

fig, ax = plt.subplots()
ax.set_xlim([0,10000])

x = np.linspace(6000.,7000., 5)
y = np.ones_like(x)

collection = plt.fill_between(x, y)

def animate(i):
    path = collection.get_paths()[0]
    path.vertices[:, 1] *= 0.9

animation.FuncAnimation(fig, animate,
                        frames=25, interval=30)

查看path.vertices以了解它们的布局方式。 希望能有所帮助, Jake


3
如果您不想使用动画,或者只想更新填充内容而删除图形中的所有内容,则可以使用以下方法:调用fill_lines.remove(),然后再次调用axes_dummy.fill_between()以绘制新的填充。在我的情况下,这种方法有效。

2
与大多数答案所述的相反,每次更新数据时不必删除和重新绘制由fill_between返回的PolyCollection。相反,您可以修改底层Path对象的verticescodes属性。假设您已通过创建PolyCollection来实现。
import numpy as np
import matplotlib.pyplot as plt

#dummy data
x = np.arange(10)
y0 = x-1
y1 = x+1

fig = plt.figure()
ax = fig.add_subplot()
p = ax.fill_between(x,y0,y1)

现在你想要使用新数据xnewy0newy1new来更新p。那么你可以这样做:

v_x = np.hstack([xnew[0],xnew,xnew[-1],xnew[::-1],xnew[0]])
v_y = np.hstack([y1new[0],y0new,y0new[-1],y1new[::-1],y1new[0]])
vertices = np.vstack([v_x,v_y]).T
codes = np.array([1]+(2*len(xnew)+1)*[2]+[79]).astype('uint8')

path = p.get_paths()[0]
path.vertices = vertices
path.codes = codes

解释: path.vertices 包含由 fill_between 绘制的补丁的顶点,包括额外的起始和结束位置,path.codes 包含如何使用它们的指令(1=移动指针到,2=画线到,79=关闭多边形)。

0

初始化pyplot交互模式

import matplotlib.pyplot as plt

plt.ion()

在绘制填充时,使用可选的标签参数:

plt.fill_between(
    x, 
    y1, 
    y2, 
    color="yellow", 
    label="cone"
)

plt.pause(0.001) # refresh the animation

在我们的脚本中,稍后可以通过标签选择删除特定的填充或一组填充,从而逐个对象地进行动画处理。
axis = plt.gca()

fills = ["cone", "sideways", "market"]   

for collection in axis.collections:
    if str(collection.get_label()) in fills:
        collection.remove()
        del collection

plt.pause(0.001)

您可以使用相同的标签来删除一组对象;或者根据需要使用标签对标签进行编码,以适应需求

例如,如果我们有以下标记的填充:

"cone1" "cone2" "sideways1"

if "cone" in str(collection.get_label()):

想要删除所有以“cone”为前缀的内容。

您还可以以同样的方式对线条进行动画处理。

for line in axis.lines:

0

另一个可行的方法是保持您绘制对象的列表; 这种方法似乎适用于任何类型的绘制对象。

# plot interactive mode on
plt.ion()

# create a dict to store "fills" 
# perhaps some other subclass of plots 
# "yellow lines" etc. 
plots = {"fills":[]}

# begin the animation
while 1: 

    # cycle through previously plotted objects
    # attempt to kill them; else remember they exist
    fills = []
    for fill in plots["fills"]:
        try:
            # remove and destroy reference
            fill.remove()
            del fill
        except:
            # and if not try again next time
            fills.append(fill)
            pass
    plots["fills"] = fills   

    # transformation of data for next frame   
    x, y1, y2 = your_function(x, y1, y2)

    # fill between plot is appended to stored fills list
    plots["fills"].append(
        plt.fill_between(
            x,
            y1,
            y2,
            color="red",
        )
    )

    # frame rate
    plt.pause(1)

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