如何将Plotly的px.scatter动画散点图改写为go.Scatter()实现
问题说明
我现有一段使用NFL追踪数据(下方提供样例数据)创建动画图表的代码(已附上对应效果图),想要咨询如何将代码中的px.scatter()部分改写为使用graph object类型的go.Scatter()来实现相同效果?
原实现代码如下:
fig = px.scatter(data, x = "x", y = "y", hover_name="displayName", color="team", animation_frame = "frameId", animation_group = "displayName", range_y = [-5, 60], range_x = [-5, 125], color_discrete_map = dict((["home", home], ["away", away], ["football", football])))
对应效果图:
样例数据结构
| x | y | team | frameId | displayName |
|---|---|---|---|---|
| 61.21 | 46.77 | home | 1 | Mahomes |
| 60 | 32 | away | 1 | Beckham |
实现方案
px.scatter生成动画的本质是自动按animation_frame拆分数据帧、按分组生成轨迹,用go.Scatter手动实现只要对应拆解这两个步骤,就能得到完全一致的效果:
- 初始化画布,提前设置固定的坐标轴范围
- 按队伍分组生成第一帧(初始状态)的散点轨迹,匹配原有颜色映射规则
- 遍历所有
frameId生成对应动画帧,每个帧下保持轨迹顺序一致,保证点和选手/队伍的绑定关系不错位 - 配置动画播放控件、滑块、悬浮提示规则,对齐px默认交互效果
完整可运行代码:
import plotly.graph_objects as go import pandas as pd # 沿用原代码定义的颜色映射规则 color_map = { "home": home, "away": away, "football": football } # 提前获取所有帧id、队伍分类 frame_ids = sorted(data["frameId"].unique()) team_list = list(color_map.keys()) # 初始化画布 fig = go.Figure() # 添加初始帧(第一个frameId)的所有散点轨迹 first_frame = data[data["frameId"] == frame_ids[0]] for team in team_list: team_subset = first_frame[first_frame["team"] == team] fig.add_trace( go.Scatter( x=team_subset["x"], y=team_subset["y"], mode="markers", marker=dict(color=color_map[team], size=12), name=team, # 对齐原代码hover_name效果,悬浮显示选手名 text=team_subset["displayName"], hovertemplate="%{text}<extra></extra>" ) ) # 生成全部动画帧 animation_frames = [] for fid in frame_ids: frame_subset = data[data["frameId"] == fid] frame_traces = [] for team in team_list: team_data = frame_subset[frame_subset["team"] == team] frame_traces.append( go.Scatter( x=team_data["x"], y=team_data["y"], text=team_data["displayName"], hovertemplate="%{text}<extra></extra>" ) ) animation_frames.append(go.Frame(data=frame_traces, name=str(fid))) fig.frames = animation_frames # 配置布局、坐标轴、动画控件 fig.update_layout( xaxis=dict(range=[-5, 125], title="x"), yaxis=dict(range=[-5, 60], title="y"), # 添加和px默认一致的进度滑块 sliders=[dict( active=0, steps=[ dict( method="animate", args=[[str(fid)], dict(mode="immediate", frame=dict(duration=300, redraw=True))], label=str(fid) ) for fid in frame_ids ] )], # 添加播放按钮 updatemenus=[dict( type="buttons", buttons=[ dict( label="播放", method="animate", args=[None, dict(frame=dict(duration=300, redraw=True), fromcurrent=True)] ) ] )] ) fig.show()
关键逻辑对应说明
- 原代码
color="team"+color_discrete_map的分色效果:通过循环每个队伍单独添加go.Scatter轨迹、给每个轨迹指定映射表内对应颜色实现 - 原代码
animation_group="displayName"的点-选手绑定逻辑:逐帧按固定的队伍顺序更新轨迹数据,不会出现动画过程中点错位、跳变的问题 - 原代码
hover_name="displayName"的悬浮提示效果:通过给散点配置text字段+自定义hovertemplate实现,悬浮时优先展示选手名称 - 原代码
range_x/range_y的坐标范围限制:直接在布局配置中给x、y轴设置range参数即可,效果完全一致
内容的提问来源于stack exchange,提问作者Jack French
相关产品推荐
相关产品推荐

