Plotly.express:如何让轰炸点动画保留已生成点不消失?
问题:Plotly实现轰炸点随时间逐步保留的地图动画
我正在可视化越南战争轰炸区域的数据,数据格式如下:
DATE | TARGET_LATITUDE | TARGET_LONGTITUDE 1/10/1969 | 10.469393 | 106.019629 1/10/1969 | 11.525273 | 107.06769 1/10/1969 | 10.973675 | 106.019629 ... 1/10/1970 | 11.469921 | 105.918000 1/10/1970 | 12.589493 | 103.019346 ... 1/10/1972 | 10.464893 | 103.013245 ...
需求是:用Plotly实现轰炸点随时间逐步出现,且已出现的点始终保留在地图上,不会消失。
我尝试使用scatter_mapbox的animation_frame参数,但结果未达预期,代码如下:
import pandas as pd import plotly.express as px data = pd.read_csv("./data/THOR_Vietnam_Bombing_Operations.csv", encoding="latin-1", usecols=["DATE", "TARGET_LATITUDE", "TARGET_LONGTITUDE", "AIRCRAFT_ORIGINAL"]) data = data.sort_values(["MSNDATE"]) # Use first 2000 data: 1966 - 1970 data = data.iloc[:2000] fig = px.scatter_mapbox(data, lat="TARGET_LATITUDE", lon="TARGET_LONGTITUDE", animation_frame="DATE", animation_group="TARGET_LONGTITUDE", color_continuous_scale=px.colors.sequential.Hot, color_discrete_sequence=["red"]) fig.update_layout(mapbox_style="carto-positron") fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) fig.update_layout(autosize=False, width=600, height=750) fig.show()
当前效果:
预期效果示例:
当前动画仅显示对应时间帧的轰炸点,之前出现的点会消失,求解决方法实现已生成点持续显示的效果。
解决方案
Plotly Express的animation_frame默认是单帧独立显示数据,要实现累积显示,需先预处理数据,为每个时间帧生成包含当前及之前所有时间点的数据,再用Plotly Graph Objects手动构建动画。
步骤1:数据预处理
- 将
DATE列转换为日期格式,确保时间排序正确 - 按日期分组,生成每个日期对应的累积数据集
- 为每个累积数据集标记对应的动画帧标签
代码示例:
import pandas as pd import plotly.graph_objects as go # 读取并预处理原始数据 data = pd.read_csv("./data/THOR_Vietnam_Bombing_Operations.csv", encoding="latin-1", usecols=["DATE", "TARGET_LATITUDE", "TARGET_LONGTITUDE"]) # 转换日期格式并排序 data["DATE"] = pd.to_datetime(data["DATE"], format="%m/%d/%Y") data = data.sort_values("DATE").iloc[:2000] # 取前2000条数据 # 生成累积帧数据 frames_data = [] unique_dates = data["DATE"].unique() for date in unique_dates: # 取当前日期及之前的所有数据 cumulative_data = data[data["DATE"] <= date] # 添加帧标识 cumulative_data["frame_date"] = date.strftime("%m/%d/%Y") frames_data.append(cumulative_data) # 合并所有帧数据 animation_data = pd.concat(frames_data)
步骤2:构建累积动画
用go.Figure()手动创建地图和动画帧,每个帧对应累积到当前日期的所有轰炸点:
# 创建基础地图(第一帧数据) fig = go.Figure( data=[go.Scattermapbox( lat=animation_data[animation_data["frame_date"] == unique_dates[0].strftime("%m/%d/%Y")]["TARGET_LATITUDE"], lon=animation_data[animation_data["frame_date"] == unique_dates[0].strftime("%m/%d/%Y")]["TARGET_LONGTITUDE"], mode="markers", marker=dict(color="red", size=5) )], layout=go.Layout( mapbox_style="carto-positron", margin={"r":0,"t":0,"l":0,"b":0}, autosize=False, width=600, height=750, updatemenus=[dict( type="buttons", showactive=False, buttons=[dict(label="Play", method="animate", args=[None, dict(frame=dict(duration=300, redraw=True), fromcurrent=True)])] )] ), # 生成所有动画帧 frames=[go.Frame( data=[go.Scattermapbox( lat=animation_data[animation_data["frame_date"] == date.strftime("%m/%d/%Y")]["TARGET_LATITUDE"], lon=animation_data[animation_data["frame_date"] == date.strftime("%m/%d/%Y")]["TARGET_LONGTITUDE"], mode="markers", marker=dict(color="red", size=5) )], name=date.strftime("%m/%d/%Y") ) for date in unique_dates] ) # 添加滑块控件 fig.update_layout( sliders=[dict( active=0, currentvalue={"prefix": "日期: "}, pad={"t": 50}, steps=[dict( method="animate", args=[[date.strftime("%m/%d/%Y")], dict(mode="immediate", frame=dict(duration=300, redraw=True))], label=date.strftime("%m/%d/%Y") ) for date in unique_dates] )] ) fig.show()
关键说明
- 预处理时生成的
animation_data包含每个时间点的累积数据,确保每帧都显示之前所有的轰炸点 - 使用Plotly Graph Objects而非Express,能更灵活地控制每个动画帧的内容
- 调整
frame的duration参数可以控制动画播放速度
内容的提问来源于stack exchange,提问作者Hoang D Nam
相关产品推荐
相关产品推荐

