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

Plotly/Dash中Points表更新时Process表pt1/pt2下拉框异常问题

问题:Process表下拉框交互异常

环境与需求

  • 使用Plotly 6.0.1和Dash 3.0.3开发Web应用
  • 包含两个DataTable:
    • Points表:存储数据点,支持添加新点
    • Process表:存储流程,每个流程包含起始点pt1和终点pt2(均为下拉选择框,选项来自Points表的数据)
  • 核心需求:当Points表数据变化时,自动更新Process表中pt1和pt2的下拉选项

异常表现

  • 选择Process表的pt1选项后,pt2下拉框无可选内容
  • 选择Process表的pt2选项后,pt1下拉框仍有可选内容

完整代码

main.py

import dash
from dash import html
from datatable import PointTable, ProcessTable

app = dash.Dash(__name__)
app.title = "Title"

app.layout = html.Div([
    html.Div([
        PointTable(app).render(),
        ProcessTable(app).render(), 
    ], 
    style={"width": "30%", "display": "inline-block", "verticalAlign": "top", "padding": "20px"}),
])

if __name__ == '__main__':
    app.run(debug=True)

datatables.py

from dash import html, dcc, dash_table, Input, Output, State
import dash
import uuid

class BaseDataTable:
    def __init__(self, app, id_prefix):
        print("init BaseDataTable", self)
        self.app = app
        self.id_prefix = id_prefix
        self.data=[]
        self.register_callbacks()
        
class PointTable(BaseDataTable):
    def __init__(self, app):
        print("init",self)
        super().__init__(app, "points")

    def register_callbacks(self):
        print("register callbacks", self)
        @self.app.callback(
            Output("points-table", "data"),
            Input("add-point", "n_clicks"),
            prevent_initial_call=True
        )
        def on_add_point(n):
            print("on_add_point_callback", self)
            self.add_point(f"Point {n}")
            return self.data

    def render(self):
        print("render", self)
        self.layout= html.Div([
            html.H3("Points Table"),
            dash_table.DataTable(
                id='points-table',
                columns=[
                    {"name": "guid", "id": "guid"},
                    {"name": "x", "id": "x"},
                    {"name": "y", "id": "y"},
                    {"name": "des","id": "des"}
                ],
                data=self.data,
                editable=False
            ),
            html.Button("Add Point", id="add-point", n_clicks=0),
            html.Hr()
            ])
        return self.layout

    def add_point(self,label):
        print ("add point", self)
        uid = str(uuid.uuid4())
        self.data.append({"guid": uid, "x": 1, "y": 2, "des":"test_point"})


class ProcessTable(BaseDataTable):
    def __init__(self, app):
        print("init", self)
        super().__init__(app, "processes")

    def register_callbacks(self):
        print("register callbacks", self)
        @self.app.callback(
            Output("processes-table", "data"),
            Input("add-process", "n_clicks"),
            prevent_initial_call=True
        )
        def on_add_process(n):
            print("on_add_process", self)
            self.add_process("", "", f"Process {n}")
            return self.data
            
        @self.app.callback(
            Output("processes-table", "dropdown_conditional"),
            Input("points-table", "data")
        )
        def update_dropdowns(points_data):
            print("update_dropdown_callback", self)
            options1 = [{"label": p["guid"], "value": p["guid"]} for p in points_data]
            options2 = [{"label": p["guid"], "value": p["guid"]} for p in points_data]
            res=[
                {"if": {"column_id": "pt1"}, "options": options1},
                {"if": {"column_id": "pt2"}, "options": options2},
            ]
            return res


    def render(self):
        print("render", self)
        self.layout= html.Div([html.H3("Processes Table"),
            dash_table.DataTable(
                id='processes-table',
                columns=[
                    {"name": "guid", "id": "guid", "editable": False},
                    {"name": "pt1", "id": "pt1", "editable": True, "presentation": "dropdown"},
                    {"name": "pt2", "id": "pt2", "editable": True, "presentation": "dropdown"},
                    {"name": "des", "id": "des", "editable": True}
                ],
                data=self.data,
                editable=True,
                dropdown={
                    "pt1": {"options": []},
                    "pt2": {"options": []}
                },
                dropdown_conditional=[],
            ),
            html.Button("Add Process", id="add-process", n_clicks=0),
            ])
        return self.layout

    def add_process(self,pt1="", pt2="", des=""):
        print("add process", self)
        self.data.append({"guid": str(uuid.uuid4()), "pt1": pt1, "pt2": pt2, "des": des})

问题原因

核心错误在于ProcessTable的update_dropdowns回调中,dropdown_conditional的返回格式不符合Dash的要求:

  • Dash的dropdown_conditional每个条件项需要使用"dropdown"键包裹下拉配置(包括options)
  • 原代码直接使用"options"键,导致下拉选项配置无法被组件正确解析,从而出现交互异常

修复方案

修改ProcessTable的update_dropdowns函数,调整返回的条件项格式为正确结构:

@self.app.callback(
    Output("processes-table", "dropdown_conditional"),
    Input("points-table", "data")
)
def update_dropdowns(points_data):
    print("update_dropdown_callback", self)
    options = [{"label": p["guid"], "value": p["guid"]} for p in points_data]
    res=[
        {"if": {"column_id": "pt1"}, "dropdown": {"options": options}},
        {"if": {"column_id": "pt2"}, "dropdown": {"options": options}},
    ]
    return res

额外优化

  • 复用同一个options列表(pt1和pt2选项来源相同),减少冗余代码
  • 确保格式完全符合Dash官方文档规范

修复后效果

  • 添加Points后,Process表的pt1和pt2下拉框均能正确显示所有可选点
  • 选择任意一个下拉框的选项后,另一个下拉框的可选选项不会消失,保持正常可选择状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:29:56