如何为散点图的凸包设置动画

How to animate the convex hull of a scatter plot

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

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

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)

您必须从 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()