【问题标题】:Animate lines within circle - matplotlib圆内的动画线 - matplotlib
【发布时间】:2020-10-06 10:12:00
【问题描述】:

我正在尝试使用 matplotlib 为线条制作动画。我可以为一个圆圈和一些散点设置动画,但我正在尝试使用变化的角度添加线条。使用下面,我在每个时间点确定X2Y2 的方向以确定方向。然后我想绘制显示这个方向的线。他们基本上将圆圈分成 4 个相等的部分。我在下面分别插入了每一帧。我输入了一个指南针作为参考。

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

fig, ax = plt.subplots(figsize = (8,8))
ax.set_xlim(-20,20)
ax.set_ylim(-20,20)
ax.grid(False)

df = pd.DataFrame({   
    'Time' : [1,1,1,1,2,2,2,2,3,3,3,3],             
    'id' : ['A','B','C','D','A','B','C','D','A','B','C','D'],                 
    'X1' : [1,8,0,-5,1,1,-6,0,1,8,0,-5],
    'Y1' : [-5,2,-5,2,5,-5,-2,2,-5,2,-5,2],
    'X2' : [0,0,0,0,-1,-1,-1,-1,0,0,0,0],
    'Y2' : [0,0,0,0,1,1,1,1,1,1,1,1],    
    'Rotation' : [0,0,0,0,-0.78,-0.78,-0.78,-0.78,1.57,1.57,1.57,1.57],
    'Angle' : [0,0,0,0,-45,-45,-45,-45,90,90,90,90],                    
    })

points_x = np.array(df.groupby(['Time'])['X1'].apply(list))
points_y = np.array(df.groupby(['Time'])['Y1'].apply(list))

# scatter points
points = ax.scatter(points_x[2], points_y[2], c = 'blue', marker = '*')

moving_x = np.array(df.groupby(['Time'])['X2'].apply(list))
moving_y = np.array(df.groupby(['Time'])['Y2'].apply(list))

# scatter moving
moving_point = ax.scatter(moving_x[2], moving_y[2], c = 'black', marker = 'x')

# Array of immediate congestion radius coordinates
radius = df.drop_duplicates(subset = ['Time','X2', 'Y2'])[['X2', 'Y2']].values

# Plot immediate congestion radius
circle = plt.Circle(radius[2], 10, color = 'black', fill = False)

# Add radius to plot
ax.add_patch(circle)

t = df['Angle'][0]
line1, = ax.plot([0, 0],[0,t], color = 'b', linewidth = 1)

def animate(i) :

    circle.center = (radius[i,0], radius[i,1])

    line1.set_data([i, i],[0,t])

    points.set_offsets(np.c_[points_x[0+i], points_y[0+i]])
    moving_point.set_offsets(np.c_[moving_x[0+i], moving_y[0+i]])


ani = animation.FuncAnimation(fig, animate,  np.arange(0,2), blit = False)

如果我单独拆分帧,我希望它们显示以下内容:

第 1 帧:

第 2 帧:

第 3 帧:

编辑 2:

【问题讨论】:

    标签: python matplotlib animation


    【解决方案1】:

    一个新的点(x′,y′)是通过对一个已有的点(x,y)进行旋转得到一个新的点(x′,y′),通过使用下面的公式进行平移、旋转和平移操作,围绕点(cx,cy)旋转θ弧度:

    x′ = (  (x - cx) * cos(θ) + (y - cy) * sin(θ) ) + cx
    y′ = ( -(x - cx) * sin(θ) + (y - cy) * cos(θ) ) + cy
    

    此处以编程方式显示:

    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    import math
    from matplotlib import animation
    
    fig, ax = plt.subplots(figsize = (8,8))
    ax.set_xlim(-20,20)
    ax.set_ylim(-20,20)
    ax.grid(False)
    
    df = pd.DataFrame({   
        'Time' : [1,1,1,1,2,2,2,2,3,3,3,3],             
        'id' : ['A','B','C','D','A','B','C','D','A','B','C','D'],                 
        'X1' : [1,8,0,-5,1,1,-6,0,1,8,0,-5],
        'Y1' : [-5,2,-5,2,5,-5,-2,2,-5,2,-5,2],
        'X2' : [0,0,0,0,-1,-1,-1,-1,0,0,0,0],
        'Y2' : [0,0,0,0,1,1,1,1,1,1,1,1],    
        'Rotation' : [0,0,0,0,-0.78,-0.78,-0.78,-0.78,1.57,1.57,1.57,1.57],
        'Angle' : [0,0,0,0,-45,-45,-45,-45,90,90,90,90],                    
        })
    
    points_x = np.array(df.groupby(['Time'])['X1'].apply(list))
    points_y = np.array(df.groupby(['Time'])['Y1'].apply(list))
    
    # scatter points
    points = ax.scatter(points_x[2], points_y[2], c = 'blue', marker = '*')
    
    moving_x = np.array(df.groupby(['Time'])['X2'].apply(list))
    moving_y = np.array(df.groupby(['Time'])['Y2'].apply(list))
    
    # scatter moving
    moving_point = ax.scatter(moving_x[2], moving_y[2], c = 'black', marker = 'x')
    
    # Array of immediate congestion radius coordinates
    radius = df.drop_duplicates(subset = ['Time','X2', 'Y2'])[['X2', 'Y2']].values
    
    # Plot immediate congestion radius
    circle = plt.Circle(radius[2], 10, color = 'black', fill = False)
    
    # Add radius to plot
    ax.add_patch(circle)
    
    t = df['Angle'][0]
    angles=np.array(df.groupby(['Time'])['Angle'].apply(list))
    line1, = ax.plot([],[], color = 'k', linewidth = 1)
    line2, = ax.plot([],[], color = 'k', linewidth = 1)
    
    def animate(i) :
    
        circle.center = (radius[i,0], radius[i,1])
    
        #set the initial coordinates for line 1
        xs1L1=-10.0/2**0.5
        ys1L1=10.0/2**0.5
        xs2L1=10.0/2**0.5
        ys2L1=-10.0/2**0.5
    
        #set the initial coordinates for line 2 which is perpendicular to line 1
        xs1L2=-xs1L1
        ys1L2=ys1L1
        xs2L2=-xs1L2
        ys2L2=ys2L1
    
        # the center of circle 
        cx=radius[i,0]
        cy=radius[i,1]
    
        # Convert angle from degrees to radians
        theta=math.radians(angles[i][0])
    
        # rotating line 1 and allowing for translation of line center
        x1L1=(  (xs1L1+radius[i,0] - cx) * math.cos(theta) + (ys1L1+radius[i,1] - cy) * math.sin(theta) ) + cx
        x2L1=(  (xs2L1+radius[i,0] - cx) * math.cos(theta) + (ys2L1+radius[i,1]- cy) * math.sin(theta) ) + cx
        y1L1=( -(xs1L1+radius[i,0] - cx) * math.sin(theta) + (ys1L1+radius[i,1] - cy) * math.cos(theta) ) + cy
        y2L1=( -(xs2L1+radius[i,0] - cx) * math.sin(theta) + (ys2L1+radius[i,1] - cy) * math.cos(theta) ) + cy
        
        line1.set_data([x1L1,x2L1],[y1L1,y2L1])
    
        # rotating line 2 and allowing for translation of line center
        x1L2=(  (xs1L2+radius[i,0] - cx) * math.cos(theta) + (ys1L2+radius[i,1] - cy) * math.sin(theta) ) + cx
        x2L2=(  (xs2L2+radius[i,0] - cx) * math.cos(theta) + (ys2L2+radius[i,1]- cy) * math.sin(theta) ) + cx
        y1L2=( -(xs1L2+radius[i,0] - cx) * math.sin(theta) + (ys1L2+radius[i,1] - cy) * math.cos(theta) ) + cy
        y2L2=( -(xs2L2+radius[i,0] - cx) * math.sin(theta) + (ys2L2+radius[i,1] - cy) * math.cos(theta) ) + cy
        
        line2.set_data([x1L2,x2L2],[y1L2,y2L2])
    
        
        points.set_offsets(np.c_[points_x[0+i], points_y[0+i]])
        moving_point.set_offsets(np.c_[moving_x[0+i], moving_y[0+i]])
    
        return circle, line1, line2, points, moving_point
    
    ani = animation.FuncAnimation(fig, animate,  np.arange(0,3), blit = True)
    plt.show()
    

    【讨论】:

    • 谢谢@Sameeresque
    【解决方案2】:

    为了在您的帖子中处理超出玩具数据集的数据集,我将用于创建动画的所有内容放入函数animate。由于我不熟悉您正在使用的数据集(例如我不知道 Rotation 做了什么),所以下面的脚本是我能做的最好的。

    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    from matplotlib import animation
    
    df = pd.DataFrame({   
        'Time' : [1,1,1,1,2,2,2,2,3,3,3,3],             
        'id' : ['A','B','C','D','A','B','C','D','A','B','C','D'],                 
        'X1' : [1,8,0,-5,1,1,-6,0,1,8,0,-5],
        'Y1' : [-5,2,-5,2,5,-5,-2,2,-5,2,-5,2],
        'X2' : [0,0,0,0,-1,-1,-1,-1,0,0,0,0],
        'Y2' : [0,0,0,0,1,1,1,1,1,1,1,1],    
        'Rotation' : [0,0,0,0,-0.78,-0.78,-0.78,-0.78,1.57,1.57,1.57,1.57],
        'Angle' : [0,0,0,0,-45,-45,-45,-45,90,90,90,90],                    
        })
    
    # the scattered points in the compass
    points_x = np.array(df.groupby(['Time'])['X1'].apply(list))
    points_y = np.array(df.groupby(['Time'])['Y1'].apply(list))
    
    # the center of the compass
    moving_x = np.array(df.groupby(['Time'])['X2'].apply(list))
    moving_y = np.array(df.groupby(['Time'])['Y2'].apply(list))
    
    radius = df.drop_duplicates(subset = ['Time','X2', 'Y2'])[['X2', 'Y2']].values
    angles = df['Angle'].unique()
    
    rot_mat = lambda theta:np.array([
        [np.cos(np.deg2rad(theta)),-np.sin(np.deg2rad(theta))],
        [np.sin(np.deg2rad(theta)),np.cos(np.deg2rad(theta))]
    ])
    
    fig, ax = plt.subplots(figsize = (8,8))
    
    def animate(i) :
        
        ax.clear()
        ax.set_xlim(-20,20)
        ax.set_ylim(-20,20)
            
        points = ax.scatter(points_x[i]+radius[i][0], points_y[i]+radius[i][1], c = 'blue', marker = '*')
        moving_point = ax.scatter(moving_x[i],moving_y[i], c = 'black', marker = 'x')
        
        circle = plt.Circle(radius[i], 10, color = 'black', fill = False)
        ax.add_patch(circle)
    
        ends_one = np.array([[-7.07106781,7.07106781],[7.07106781,-7.07106781]])
        ends_two = np.array([[-7.07106781,-7.07106781],[7.07106781,7.07106781]])
        ends_one = ends_one @ rot_mat(angles[i]) + radius[i]
        ends_two = ends_two @ rot_mat(angles[i]) + radius[i]
        line1, = ax.plot([], [], ls='--', color='black',lw=1, zorder=10,animated=True)
        line2, = ax.plot([], [], ls='--', color='black',lw=1, zorder=10,animated=True)
        line1.set_data([ends_one[0][0],ends_one[1][0]],[ends_one[0][1],ends_one[1][1]])
        line2.set_data([ends_two[0][0],ends_two[1][0]],[ends_two[0][1],ends_two[1][1]])
        
        tags = ['N','E','S','W']
        tag_pos = np.array([[0,8.5],[8.5,0],[0,-8.5],[-8.5,0]])
        tag_pos = tag_pos @ rot_mat(angles[i])
        for tag,pos in zip(tags,tag_pos):
            ax.annotate(tag,xy=pos+radius[i], xycoords='data',
                        fontsize=10,horizontalalignment='right', verticalalignment='bottom')
    
    ani = animation.FuncAnimation(fig, animate, np.arange(0,3), blit = False)
    ani.save('test.gif', writer='pillow', fps=3)
    

    如您所见,坐标可以迭代地传递到函数animate 以生成指南针的框架。运行这个脚本后,你会得到一个gif文件test.gif,可以预览


    删除过去帧的文本

    有两种方法可以做到这一点

    1.如果您使用函数创建动画

    在函数开头放ax.clear(),例如

    import matplotlib.pyplot as plt
    from matplotlib import animation
    
    rot_mat = lambda theta:np.array([
        [np.cos(np.deg2rad(theta)),-np.sin(np.deg2rad(theta))],
        [np.sin(np.deg2rad(theta)),np.cos(np.deg2rad(theta))]
    ])
    
    fig, ax = plt.subplots(figsize = (8,8))
    
    centers = np.linspace(1,36,36).reshape(18,2)*0.2
    angles = np.linspace(45,90,18)
    
    def animate(i) :
        
        ax.clear()
        ax.set_xlim(-20,20)
        ax.set_ylim(-20,20)
                
        circle = plt.Circle(centers[i], 10, color = 'black', fill = True)
        circle.set_facecolor('violet')
        ax.add_patch(circle)
    
        tags = ['N','E','S','W']
        tag_pos = np.array([[0,8.5],[8.5,0],[0,-8.5],[-8.5,0]])
        tag_pos = tag_pos @ rot_mat(angles[i])
        for tag,pos in zip(tags,tag_pos):
            ax.annotate(tag,xy=pos+centers[i], xycoords='data',
                        fontsize=10,horizontalalignment='right', verticalalignment='bottom')
    
    ani = animation.FuncAnimation(fig, animate, np.arange(0,18), blit = False)
    ani.save('test.gif', writer='pillow', fps=18)
    

    这将创建一个像这样的gif

    2。如果你没有使用函数来创建动画

    需要手动删除文字,例如

    import matplotlib.pyplot as plt
    from matplotlib import animation
    
    rot_mat = lambda theta:np.array([
        [np.cos(np.deg2rad(theta)),-np.sin(np.deg2rad(theta))],
        [np.sin(np.deg2rad(theta)),np.cos(np.deg2rad(theta))]
    ])
    
    centers = np.linspace(1,36,36).reshape(18,2)*0.2
    angles = np.linspace(45,90,18)
    
    fig, ax = plt.subplots(figsize = (8,8))
    ax.set_xlim(-20,20)
    ax.set_ylim(-20,20)
        
    circle = plt.Circle(centers[0], 10, color = 'black', fill = True)
    circle.set_facecolor('violet')
    ax.add_patch(circle)
    
    annotates = []
    tags = ['N','E','S','W']
    tag_pos = np.array([[0,8.5],[8.5,0],[0,-8.5],[-8.5,0]])
    tag_pos = tag_pos @ rot_mat(angles[0])
    for tag,pos in zip(tags,tag_pos):
        ann = ax.annotate(tag,xy=pos+centers[0], xycoords='data',
                          fontsize=10,horizontalalignment='right', verticalalignment='bottom')
        annotates.append(ann)
    
    for i in range(18):
        
        circle.center = centers[i]
        # remove the previous text
        for item in annotates: item.remove()
        annotates = []
        
        tag_pos = tag_pos @ rot_mat(angles[i])
        for tag,pos in zip(tags,tag_pos):
            ann = ax.annotate(tag,xy=pos+centers[i], xycoords='data',
                              fontsize=10,horizontalalignment='right', verticalalignment='bottom')
            annotates.append(ann)
    
    ani = animation.FuncAnimation(fig, animate, np.arange(0,18), blit = False)
    ani.save('test.gif', writer='pillow', fps=18)
    

    如您所见,使用这行代码for item in annotates: item.remove(),前一帧的文本被删除。因此,通过运行此脚本,您还将获得

    【讨论】:

    • @jonboy 嗨 jonboy,这个 gif 是你要找的吗?
    • 感谢您查看此内容。我可以看到线条正在为您制作动画。他们没有出现在我面前。我也希望不要硬编码任何坐标。我的实际数据框很大,我希望迭代地传递坐标。
    • @jonboy 不客气,我已经更新了我的答案,我将有关动画的所有内容都放入函数animate,因此您可以迭代地传递数据而无需硬代码坐标。我不明白 gif 没有出现是什么意思,因为我已将输出 gif 文件上传到帖子中。如果无法预览帖子中的 gif,可以运行脚本,它会创建一个 gif 文件test.gif
    • 我在函数中只有动画,因为我有单独的项目要绘制。我已经重新排列了您的代码,以使其在我仅对更改的项目(散点、圆圈、线条)进行动画处理的情况下工作。但是,标记罗盘点的文字给我带来了麻烦。我不想清除每一帧的轴,所以我要返回每一帧的点。
    • @jonboy 请再问一个问题,这篇文章已经被编辑过多次了 :)
    猜你喜欢
    • 1970-01-01
    • 2019-04-17
    • 1970-01-01
    • 2018-07-12
    • 2021-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-19
    相关资源
    最近更新 更多