dash如何更新Figure的同时保持缩放和视角

我觉得他不能这样是一个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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值