You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何动画LineCollection的linewidth(线宽)表现不一致?

粒子运动动画线宽异常问题修复

问题描述

目标实现粒子运动效果:粒子前端(最新位置)alpha=1、线宽(lw)=4,尾部(历史轨迹)的lw与alpha随时间逐渐递减。目前alpha效果正常,但lw表现不一致——同一粒子有时前端粗、尾部细,有时相反。实际场景为模拟网络交通,尾部轨迹的可视化效果对清晰度至关重要。

模拟数据定义

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.collections import LineCollection
from matplotlib import cm
from functools import partial

np.random.seed(42) # 固定随机种子以复现示例
n_particles = 50 # 模拟粒子数量
n_frames = 500 # 动画总帧数
fade_frames = 5 # 轨迹淡出的帧数
# 初始化粒子ID与起始点
particle_ids = [f"p{i}" for i in  range(1, n_particles+1)]
starting_x = np.random.uniform(40, 60, n_particles)
starting_y = np.random.uniform(40, 60, n_particles)
# 随机分配粒子的出现与消失帧
start_frames = np.random.randint(0, n_frames/2, n_particles)
end_frames = start_frames + np.random.randint(n_frames/4, n_frames/2, n_particles)
colours = np.random.choice(range(10), n_particles) # 选择tab10色板的颜色索引

particles = pd.DataFrame({
    "id": particle_ids, 
    "starting_x": starting_x, 
    "starting_y": starting_y, 
    "start_frames": start_frames, 
    "end_frames": end_frames, 
    "colour":colours
})

生成各帧粒子位置数据

particle_ids = []
x_loc = []
y_loc = []
frames = []
colours = []

for id in particles.id:
    start_frame = particles.loc[particles.id == id, "start_frames"].values[0]
    end_frame = particles.loc[particles.id == id, "end_frames"].values[0]
    colour = particles.loc[particles.id == id, "colour"].values[0]
    
    particle_ids.append(id)
    colours.append(colour)
    x_loc.append(particles.loc[particles.id == id, "starting_x"].values[0])
    y_loc.append(particles.loc[particles.id == id, "starting_y"].values[0])
    frames.append(start_frame)
    
    for frame in range(start_frame+1, end_frame+1):
        particle_ids.append(id)
        colours.append(colour)
        x_loc.append(x_loc[-1] + np.random.uniform(-2, 2))
        y_loc.append(y_loc[-1] + np.random.uniform(-2, 2))
        frames.append(frame)
        
movements = (pd.DataFrame({
    "id": particle_ids, 
    "x_loc": x_loc, 
    "y_loc": y_loc, 
    "frame": frames, 
    "colour":colours})
    .sort_values("frame")
    .reset_index(drop=True))

# 将颜色索引替换为RGB值,alpha在动画中设置
cmap = dict(zip(range(10), cm.tab10.colors))
movements['colour'] = movements.colour.map(cmap) 

问题分析与修复后的动画代码

问题根源

  1. 变量名冲突:animate函数的参数frame被循环内的for frame in grp.frame覆盖,导致fade值计算逻辑完全错误。
  2. fade值计算逻辑错误:原代码未基于轨迹段的新旧程度计算fade值,导致线宽的变化方向随机颠倒。

修复后的代码

# 设置绘图区域
xmin = movements.x_loc.min()
xmax = movements.x_loc.max()
ymin = movements.y_loc.min()
ymax = movements.y_loc.max()
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_xlim(0,100)
ax.set_ylim(0, 100)
lines = LineCollection([], ) # 空容器用于后续更新
ax.add_collection(lines)

def animate(current_frame, lines):
    # 获取当前帧需要展示的轨迹范围
    start_frame = current_frame - fade_frames
    end_frame = min(current_frame, movements.frame.max())
    if start_frame <= 0:
        start_frame = 0
    
    all_segments = []
    all_colours = []
    all_linewidths = []
    frame_slice = movements[(movements.frame >= start_frame) & (movements.frame <= end_frame)]

    # 逐个粒子生成轨迹段
    for id, grp in frame_slice.groupby('id'):
        coords = list(zip(grp.x_loc.tolist(), grp.y_loc.tolist()))
        # 生成轨迹线段:相邻两个坐标点组成一段
        segments = [(coords[i-1], coords[i]) for i in range(1, len(coords))]
        all_segments.extend(segments)
        
        # 获取每个轨迹段对应的颜色(取起点的颜色)
        colours = grp.colour.tolist()[:-1]
        # 计算每个轨迹段的fade值:基于线段终点的帧与当前动画帧的时间差
        # 最新的线段(前端)fade值为1,最旧的(尾部)fade值为0
        end_frames_of_segments = grp.frame.iloc[1:].tolist()
        fade_values = [max(0, 1 - (current_frame - f)/fade_frames) for f in end_frames_of_segments]
        
        # 生成RGBA颜色
        rgba = [(r,g,b,a) for (r,g,b), a in zip(colours, fade_values)]
        all_colours.extend(rgba)
        # 生成对应线宽:最大4,随fade值递减
        all_linewidths.extend([4*val for val in fade_values])
        
    ax.draw_artist(lines)
    lines.set_segments(all_segments)
    lines.set_color(all_colours)
    lines.set_linewidth(all_linewidths)
    return lines,

# 运行动画
ani = animation.FuncAnimation(
    fig, 
    partial(animate, lines=lines), 
    frames=n_frames, 
    interval=100, 
    repeat_delay=200, 
    blit=True)

ani.save('test.gif')
plt.show()

修复效果

修复后,粒子的前端(最新轨迹段)始终保持线宽4、完全不透明,尾部轨迹随时间推移逐渐变细、透明,符合预期的可视化效果。

粒子随机游走动画

内容的提问来源于stack exchange,提问作者ciaran haines

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 04:57:03