【问题标题】:How to animate the convex hull of a scatter plot如何为散点图的凸包设置动画
【发布时间】:2018-07-07 18:03:18
【问题描述】:

我正在尝试为散点图的凸包设置动画。下面的代码实现了这一点,但没有删除先前时间点的船体,导致每一帧都显示船体而不是最新的船体。

如何让输出在每个时间点显示凸包?所以每一帧都是一个新的凸包。

import csv
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull
import numpy as np
import matplotlib.animation as animation

visuals = [[],[],[]]

with open('Wide_Single_Timestamp.csv') as csvfile :
readCSV = csv.reader(csvfile, delimiter=',')
n=0
for row in readCSV :
    if n == 0 :
        n+=1
        continue
    visuals[0].append([float(row[3]),float(row[5]),float(row[7]),float(row[9]),float(row[11]),float(row[13]),float(row[15]),float(row[17]),float(row[19]),float(row[21]),float(row[23]),float(row[25]),float(row[27]),float(row[29]),float(row[31]),float(row[33]),float(row[35]),float(row[37]),float(row[39]),float(row[41]),float(row[43])])
    visuals[1].append([float(row[2]),float(row[4]),float(row[6]),float(row[8]),float(row[10]),float(row[12]),float(row[14]),float(row[16]),float(row[18]),float(row[20]),float(row[22]),float(row[24]),float(row[26]),float(row[28]),float(row[30]),float(row[32]),float(row[34]),float(row[36]),float(row[38]),float(row[40]),float(row[42])])
    visuals[2].append([1,2])

fig, ax = plt.subplots(figsize = (8,6))

X = np.array(visuals[0][0]) #X-Coordinates
Y = np.array(visuals[1][0]) #Y-Coordinates


scatterN = ax.scatter(X, Y, zorder = 2)   #Scatter Plot

def encircle(x,y, **kw):   #Convex Hull 
    ax
    p = np.c_[x,y]
    hull = ConvexHull(p)
    poly = plt.Polygon(p[hull.vertices,:])
    ax.add_patch(poly)


def animate(i) :
    scatterN.set_offsets([[[[[[[[[[[[[[[[[[[[[visuals[0][0+i][0], visuals[1][0+i][0]], [visuals[0][0+i][1], visuals[1][0+i][1]], [visuals[0][0+i][2], visuals[1][0+i][2]], [visuals[0][0+i][3], visuals[1][0+i][3]], [visuals[0][0+i][4], visuals[1][0+i][4]],[visuals[0][0+i][5], visuals[1][0+i][5]], [visuals[0][0+i][6], visuals[1][0+i][6]], [visuals[0][0+i][7], visuals[1][0+i][7]], [visuals[0][0+i][8], visuals[1][0+i][8]], [visuals[0][0+i][9], visuals[1][0+i][9]], [visuals[0][0+i][10], visuals[1][0+i][10]], [visuals[0][0+i][11], visuals[1][0+i][11]], [visuals[0][0+i][12], visuals[1][0+i][12]], [visuals[0][0+i][13], visuals[1][0+i][13]], [visuals[0][0+i][14], visuals[1][0+i][14]], [visuals[0][0+i][15], visuals[1][0+i][15]], [visuals[0][0+i][16], visuals[1][0+i][16]], [visuals[0][0+i][17], visuals[1][0+i][17]], [visuals[0][0+i][18], visuals[1][0+i][18]], [visuals[0][0+i][19], visuals[1][0+i][19]], [visuals[0][0+i][20], visuals[1][0+i][20]]]]]]]]]]]]]]]]]]]]]] )
    X = visuals[0][0+i]
    Y = visuals[1][0+i] 
    encircle(X, Y, ec="black", fc="gray", alpha=0.1)  #Convex Hull 


ani = animation.FuncAnimation(fig, animate, np.arange(0,61100),
                          interval = 50, blit = False)

'''AFL Ground (Etihad Dimensions)'''


plt.style.use('ggplot')

#fig, ax = plt.subplots()
ax.grid(False)
#ax.set_aspect('equal')

CC_xy = 0,70
GS1_xy = 75.3,67.5
GS2_xy = -84.2,67.5
F50_1_xy = -67.5, 70
F50_2_xy = 67.5, 70
angle = math.degrees(math.acos(5.5/9.15))
CS_xy = -25,45
E_xy = 0,67.5
E_xy_Freo = 0, 70

Halfway = mpl.lines.Line2D((0,0), (65,75), color = 'white', lw = 1.5, alpha = 0.2, zorder = 0.1)
Centre_Circle = mpl.patches.Circle(CC_xy, radius = 1.5, color = 'white', lw = 1.5, fill = False)
Centre_Circle_2 = mpl.patches.Circle(CC_xy, radius = 5, color = 'white', lw = 1.5, fill = False)
GS1 = mpl.patches.Rectangle(GS1_xy, 9, 6.4, color = 'white', lw = 1.5, fill = False)
GS2 = mpl.patches.Rectangle(GS2_xy, 9, 6.4, color = 'white', lw = 1.5, fill = False)
F50_1 = mpl.patches.Arc(F50_1_xy, 65, 135, angle = 0, theta2 = angle, theta1 = 360-angle, color = 'white', lw = 2)
F50_2 = mpl.patches.Arc(F50_2_xy, 65, 135, angle = 0, theta2 = 180+angle, theta1 = 180-angle, color = 'white', lw = 2)
Centre_Square = mpl.patches.Rectangle(CS_xy, 50, 50, lw = 2, color = 'white', fill = False)


Etihad_Freo = mpl.patches.Ellipse(E_xy_Freo, 168.47, 130.47, lw = 3, color = 'green', alpha = 0.1, zorder = 5)

ax.add_line(Halfway)
ax.add_patch(Centre_Circle)
ax.add_patch(Centre_Circle_2)
ax.add_patch(GS1)
ax.add_patch(GS2)
ax.add_patch(F50_1)
ax.add_patch(F50_2)
ax.add_patch(Centre_Square)
ax.add_patch(Etihad_Freo)


#ax.annotate('D50', xy = (35.5, 80), color = 'white', rotation = -75, alpha = 0.5)
#ax.annotate('D50', xy = (37, -80), color = 'white', rotation = -110, alpha = 0.5)
#ax.annotate('F50', xy = (28.5, -40), color = 'white', rotation = 110, alpha = 0.5)
#ax.annotate('F50', xy = (28, 40), color = 'white', rotation = 72, alpha = 0.5)


ax.autoscale()

plt.draw()


#plt.savefig('fill_between scatter.png', dpi = 300)

【问题讨论】:

  • 您的代码不能这样运行,因为 csv 文件不可用。最好将您的代码发布为Minimal, Complete, and Verifiable example。例如,这可以通过使用numpy.random 或类似的方法生成随机数据来实现。

标签: animation matplotlib convex-hull


【解决方案1】:

您必须从ax 中删除旧补丁。如果你用这个替换你的 encircle 函数,一切都应该正常工作:

def encircle(x,y, **kw):   #Convex Hull 
    ##remove old patch:
    try:
        last_patch = ax.patches[-1]
        last_patch.remove()
    except IndexError:
        pass

    ##do the other stuff
    p = np.c_[x,y]
    hull = ConvexHull(p)
    poly = plt.Polygon(p[hull.vertices,:])
    ax.add_patch(poly)

try-except 块是必需的,因为在第一次调用 encircle 期间,ax.patches 仍然是空的。您当然可以用if 子句替换它来测试ax.patches 是否为空。请注意,此代码假定您的 Axes 实例中没有其他补丁。如果是这种情况,您将不得不自己跟踪多边形。 poly.remove() 功能仍然有效。希望这会有所帮助。

编辑

这是一个基于 OP 问题的完整示例,但 csv 数据被 numpy.random 点替换。

import csv
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull
import numpy as np
import matplotlib.animation as animation
import matplotlib as mpl

N=20

fig, ax = plt.subplots(figsize = (8,6))


X = np.random.normal(1,1,N)
Y = np.random.normal(1,1,N)
scatterN = ax.scatter(X, Y, zorder = 2)   #Scatter Plot

to_be_deleted = []
def encircle(x,y, **kw):   #Convex Hull 
    ##removing old patches:
    for patch in to_be_deleted:
        patch.remove()
    del to_be_deleted[:]

    p = np.c_[x,y]
    hull = ConvexHull(p)
    poly = plt.Polygon(p[hull.vertices,:])
    ##saving new reference
    to_be_deleted.append(poly)


    ax.add_patch(poly)


def animate(i) :
    X = np.random.normal(0,50,N)
    Y = np.random.normal(70,30,N)
    scatterN.set_offsets(np.array([X,Y]).T)
    encircle(X, Y, ec="black", fc="gray", alpha=0.1, zorder = 100)  #Convex Hull 


ani = animation.FuncAnimation(fig, animate, np.arange(0,61100),
                          interval = 50, blit = False)

'''AFL Ground (Etihad Dimensions)'''


plt.style.use('ggplot')

#fig, ax = plt.subplots()
ax.grid(False)
#ax.set_aspect('equal')

CC_xy = 0,70
GS1_xy = 75.3,67.5
GS2_xy = -84.2,67.5
F50_1_xy = -67.5, 70
F50_2_xy = 67.5, 70
#angle = math.degrees(math.acos(5.5/9.15))
angle = np.degrees(np.arccos(5.5/9.15))
CS_xy = -25,45
E_xy = 0,67.5
E_xy_Freo = 0, 70

Halfway = mpl.lines.Line2D((0,0), (65,75), color = 'white', lw = 1.5, alpha = 0.2, zorder = 0.1)
Centre_Circle = mpl.patches.Circle(CC_xy, radius = 1.5, color = 'white', lw = 1.5, fill = False)
Centre_Circle_2 = mpl.patches.Circle(CC_xy, radius = 5, color = 'white', lw = 1.5, fill = False)
GS1 = mpl.patches.Rectangle(GS1_xy, 9, 6.4, color = 'white', lw = 1.5, fill = False)
GS2 = mpl.patches.Rectangle(GS2_xy, 9, 6.4, color = 'white', lw = 1.5, fill = False)
F50_1 = mpl.patches.Arc(F50_1_xy, 65, 135, angle = 0, theta2 = angle, theta1 = 360-angle, color = 'white', lw = 2)
F50_2 = mpl.patches.Arc(F50_2_xy, 65, 135, angle = 0, theta2 = 180+angle, theta1 = 180-angle, color = 'white', lw = 2)
Centre_Square = mpl.patches.Rectangle(CS_xy, 50, 50, lw = 2, color = 'white', fill = False)


Etihad_Freo = mpl.patches.Ellipse(E_xy_Freo, 168.47, 130.47, lw = 3, color = 'green', alpha = 0.1, zorder = 5)

ax.add_line(Halfway)
ax.add_patch(Centre_Circle)
ax.add_patch(Centre_Circle_2)
ax.add_patch(GS1)
ax.add_patch(GS2)
ax.add_patch(F50_1)
ax.add_patch(F50_2)
ax.add_patch(Centre_Square)
ax.add_patch(Etihad_Freo)


#ax.annotate('D50', xy = (35.5, 80), color = 'white', rotation = -75, alpha = 0.5)
#ax.annotate('D50', xy = (37, -80), color = 'white', rotation = -110, alpha = 0.5)
#ax.annotate('F50', xy = (28.5, -40), color = 'white', rotation = 110, alpha = 0.5)
#ax.annotate('F50', xy = (28, 40), color = 'white', rotation = 72, alpha = 0.5)


ax.autoscale()

plt.show()

【讨论】:

  • 谢谢托马斯。下次我会记住这一点。这很好用。
  • 我确实有更多的ax.patches 来绘制特定的轴限制。 poly.remove() 函数在这种情况下如何工作?
  • @JeremyAlexander 每次创建 patchpoly 并稍后删除时,请保留对它的引用(可能在名为 to_be_removed 的列表中或类似的列表中)。然后,当需要删除补丁时,遍历该列表,在所有对象上调用 remove() 并清空列表 (to_be_removed = [])。对于更具体的答案,我需要更具体的代码:)
  • 感谢您的帮助,托马斯。我已经添加了包含附加补丁的代码。
猜你喜欢
  • 1970-01-01
  • 2016-06-01
  • 1970-01-01
  • 2017-04-05
  • 2021-07-04
  • 1970-01-01
  • 2012-03-13
  • 2017-04-29
相关资源
最近更新 更多