我觉得他不能这样是一个bug,
即使
State(“graph”, “figure”),
直接把figure传后台, 数据里的layout就是错的
所以做了一个妥协的方案. 想要调整视角和缩放.就去手动按按钮停止刷新. 调整好了你再去点按钮继续刷新.
属于是脱了裤子放屁, 但他就是有bug,没办法
以下是这个方案完整的代码. 对我来说已经完整解决问题了
import dash
from dash import dcc, html, Input, Output, State
import plotly.graph_objects as go
import numpy as np
# 初始化Dash应用
app = dash.Dash(__name__)
# 全局变量(示例用,生产环境推荐用dcc.Store)
saved_camera = {} # 存储用户调整的视角配置
n_frames = 100 # 动画总帧数(循环播放)
x = np.linspace(0, 10, 50) # 固定X轴数据
fresh_ms = 100 # 多久刷新一次 单位ms
fresh_ms_stop = 99999 # TODO: 认为停止刷新是99999ms刷新一次
fresh_ms_go = 100 # 认为继续刷新是100ms刷新一次
stop_fresh = 0 # 初始是动起来
app.layout = html.Div([
# 调试区域:显示relayoutData和当前视角
html.Div(
id="debug-panel",
style={
"padding": "10px",
"margin": "10px",
"border": "1px solid #ccc",
"whiteSpace": "pre-wrap",
"fontFamily": "monospace",
"maxHeight": "200px",
"overflow": "auto"
}
),
# 3D图表核心组件
dcc.Graph(
id="3d-animated-graph",
style={"height": "70vh"},
config={
"displayModeBar": True, # 显示工具栏(缩放/旋转)
"responsive": True,
}
),
html.Div([
html.H3("切换视角按钮:"),
html.Button(
id="html-button",
children="停止刷新", # 按钮显示文本
n_clicks=0, # 初始点击次数
style={
"width": "200px",
"height": "40px",
"fontSize": 16,
"backgroundColor": "#007bff",
"color": "white",
"border": "none",
"borderRadius": 5,
"cursor": "pointer"
}
),
], style={'width': '45%', 'display': 'inline-block', 'margin': '0 2%'}),
# 定时器:每秒更新一次(1000ms=1fps)
dcc.Interval(
id="animation-timer",
interval=fresh_ms, # 多久更新一次
n_intervals=0,
disabled=False
),
# 隐藏存储:持久化视角配置(替代全局变量,更优雅)
dcc.Store(id="camera-store", data={})
])
# 回调1:捕获relayoutData,存储最新视角配置
@app.callback(
Output("camera-store", "data"),
Output("debug-panel", "children"),
Input("3d-animated-graph", "relayoutData"),
State("camera-store", "data"),
prevent_initial_call=True # 初始加载不触发
)
def capture_relayout(relayout_data, current_camera):
global saved_camera
# 初始化相机配置
if current_camera is None:
current_camera = {}
# 认为relayout_data就是所有的视角和缩放. 存起来作为后续更新go.Figure的layout
if relayout_data:
current_camera = relayout_data
# 构造调试信息
debug_text = (
f"📌 A:'{''}'\n"
f"📌 B: '{''}'"
)
return current_camera, debug_text
# 回调2:每秒更新3D图数据,复用用户调整的视角
@app.callback(
Output("3d-animated-graph", "figure"),
Input("animation-timer", "n_intervals"),
State("camera-store", "data"), # 从存储取最新视角
prevent_initial_call=False
)
def update_3d_graph(n_intervals, latest_camera):
# 计算当前帧(循环播放)
frame_idx = n_intervals % n_frames
# 生成动态Y/Z轴数据(随帧数变化)
y = x + np.sin(x + frame_idx * 0.1) + np.random.randn(len(x)) * 0.1
z = np.cos(x + frame_idx * 0.1) + np.random.randn(len(x)) * 0.1
# 构建3D线图数据
trace = go.Scatter3d(
x=x,
y=y,
z=z,
mode="lines",
line=dict(width=4, color="#ff6b6b"),
opacity=0.8,
name=f"帧{frame_idx}"
)
# 基础布局配置
layout = latest_camera
# 返回带最新数据和视角的图表
return go.Figure(data=[trace], layout=layout)
# 回调3:按按钮停止或启动刷新
@app.callback(
Output("html-button", "children"),
Output("animation-timer", "interval"),
Input("html-button", "n_clicks"),
prevent_initial_call=True
)
def update_html_button(n_clicks):
global stop_fresh, fresh_ms
# 按了就反过来,
if stop_fresh == 0: # 如果现在是开启刷新
stop_fresh = 1
fresh_ms = fresh_ms_stop
return f"开启刷新", fresh_ms
else:
stop_fresh = 0
fresh_ms = fresh_ms_go
return f"停止刷新", fresh_ms
if __name__ == "__main__":
app.run_server(debug=True, host="0.0.0.0", port=8050)
843

被折叠的 条评论
为什么被折叠?



