Plotly Dash下拉框实现全选与多级级联联动方法
解决方案
核心问题定位
你现有代码存在三个逻辑缺陷导致需求无法实现:
- 州下拉框的选项加载逻辑仅绑定了顶层Unit单选值,没有关联已选中的国家值,无法实现选中国家后过滤下属州的联动效果
- 所有筛选逻辑都是强制单值精确匹配,没有预留全选的特殊判断分支,也没有在下拉选项中加入全选入口
- 下级下拉框的值没有随上级选项更新重置,容易出现选值不在新选项列表中的空数据bug
具体修改方案
级联联动实现逻辑
- 调整下拉框回调的输入依赖:州下拉框的选项加载同时监听
radio_items(Unit选择)和select_country(国家选择)两个输入,只返回当前选中Unit+选中国家下的所有州 - 增加上级选值变化时重置下级值的逻辑:当Unit变化时,重置患者状态、国家、州的选中值;当国家变化时,重置州的选中值,避免无效选值
- 图表数据过滤时,按层级依次匹配筛选条件,保证选中国家后只加载对应国家数据,选中州后只加载对应州下的城市统计数据
全选功能实现逻辑
- 定义全局特殊常量
ALL_OPTION_VALUE = '__ALL__'作为全选的标识值,避免和真实业务数据重名 - 每个下拉框生成选项时,在列表最前面插入
{'label': '全选', 'value': ALL_OPTION_VALUE}选项 - 数据过滤时增加判断:如果对应筛选字段的选中值是
ALL_OPTION_VALUE,就跳过该字段的过滤条件,返回该字段下所有数据 - 所有下拉框的默认初始值设为
ALL_OPTION_VALUE,初始加载时默认展示全量数据
修改后完整代码
from dash import dcc, html, Dash from dash.dependencies import Input, Output import plotly.graph_objs as go import pandas as pd # 定义全选的特殊标识值,避免和真实数据冲突 ALL_OPTION_VALUE = '__ALL__' PatientStatus = pd.read_csv('PatientStatus.csv') app = Dash(__name__, meta_tags=[{"name": "viewport", "content": "width=device-width"}]) app.layout = html.Div([ html.Div([ html.Div([ html.Div([ html.H3('Patient Count', style={'margin-bottom': '0px', 'color': 'black'}), ]) ], className="create_container1 four columns", id="title"), ], id="header", className="row flex-display", style={"margin-bottom": "25px"}), html.Div([ html.Div([ html.P('Select Unit Name', className='fix_label', style={'color': 'black'}), dcc.RadioItems(id='radio_items', labelStyle={"display": "inline-block"}, value='Testing', options=[{'label': i, 'value': i} for i in PatientStatus['UNITNAME'].unique()], style={'text-align': 'center'}, className='dcc_compon'), html.P('Select Patient Status:', className='fix_label', style={'color': 'black', 'margin-top': '30px'}), dcc.Dropdown(id='select_county', multi=False, clearable=True, disabled=False, style={'display': True}, placeholder='Patient Status', value=ALL_OPTION_VALUE, options=[], className='dcc_compon'), html.P('Select Country:', className='fix_label', style={'color': 'black', 'margin-top': '30px'}), dcc.Dropdown(id='select_country', multi=False, clearable=True, disabled=False, style={'display': True}, placeholder='Select Country', value=ALL_OPTION_VALUE, options=[], className='dcc_compon'), html.P('Select State:', className='fix_label', style={'color': 'black', 'margin-top': '30px'}), dcc.Dropdown(id='select_state', multi=False, clearable=True, disabled=False, style={'display': True}, placeholder='Select State', value=ALL_OPTION_VALUE, options=[], className='dcc_compon') ], className="create_container2 five columns"), html.Div([ dcc.Graph(id="bar_chart", config={"displayModeBar": 'hover'}), ], className="create_container2 seven columns"), ], className="row flex-display"), ], id="maincontainer", style={"display": "flex", "flex-direction": "columns"}) # 加载患者状态选项:仅依赖Unit选择 @app.callback( Output('select_county', 'options'), Input('radio_items', 'value')) def get_county_options(radio_items): PatientStatus1 = PatientStatus[PatientStatus['UNITNAME'] == radio_items] status_list = [{'label': k, 'value': k} for k in PatientStatus1['PATIENTSTATUS'].unique()] # 插入全选选项 return [{'label': '全选', 'value': ALL_OPTION_VALUE}] + status_list # Unit变化时重置患者状态选中值为全选 @app.callback( Output('select_county', 'value'), Input('radio_items', 'value')) def reset_county_value(radio_items): return ALL_OPTION_VALUE # 加载国家选项:仅依赖Unit选择 @app.callback( Output('select_country', 'options'), Input('radio_items', 'value')) def get_country_options(radio_items): PatientStatus1 = PatientStatus[PatientStatus['UNITNAME'] == radio_items] country_list = [{'label': k, 'value': k} for k in PatientStatus1['COUNTRY'].unique()] return [{'label': '全选', 'value': ALL_OPTION_VALUE}] + country_list # Unit变化时重置国家选中值为全选 @app.callback( Output('select_country', 'value'), Input('radio_items', 'value')) def reset_country_value(radio_items): return ALL_OPTION_VALUE # 加载州选项:同时依赖Unit选择、国家选择,实现级联过滤 @app.callback( Output('select_state', 'options'), Input('radio_items', 'value'), Input('select_country', 'value')) def get_state_options(radio_items, select_country): PatientStatus1 = PatientStatus[PatientStatus['UNITNAME'] == radio_items] # 如果国家不是全选,就只过滤对应国家下的州 if select_country != ALL_OPTION_VALUE: PatientStatus1 = PatientStatus1[PatientStatus1['COUNTRY'] == select_country] state_list = [{'label': k, 'value': k} for k in PatientStatus1['STATENAME'].unique()] return [{'label': '全选', 'value': ALL_OPTION_VALUE}] + state_list # Unit或国家变化时,重置州选中值为全选 @app.callback( Output('select_state', 'value'), Input('radio_items', 'value'), Input('select_country', 'value')) def reset_state_value(radio_items, select_country): return ALL_OPTION_VALUE # 更新图表 @app.callback(Output('bar_chart', 'figure'), Input('radio_items', 'value'), Input('select_county', 'value'), Input('select_country', 'value'), Input('select_state', 'value')) def update_graph(radio_items, select_county, select_country, select_state): # 先按Unit过滤基础数据 PatientStatus2 = PatientStatus[PatientStatus['UNITNAME'] == radio_items].copy() # 逐层判断筛选条件,非全选才加过滤规则 if select_county != ALL_OPTION_VALUE: PatientStatus2 = PatientStatus2[PatientStatus2['PATIENTSTATUS'] == select_county] if select_country != ALL_OPTION_VALUE: PatientStatus2 = PatientStatus2[PatientStatus2['COUNTRY'] == select_country] if select_state != ALL_OPTION_VALUE: PatientStatus2 = PatientStatus2[PatientStatus2['STATENAME'] == select_state] # 聚合城市维度的患者数 PatientStatus3 = PatientStatus2.groupby(['UNITNAME','PATIENTSTATUS','COUNTRY','STATENAME', 'CITYNAME'])['PATIENTCOUNTS'].sum().reset_index() # 组装标题展示文本 title_parts = [f'UNIT ({radio_items})'] title_parts.append(f'Patient Status({select_county if select_county != ALL_OPTION_VALUE else "全部"})') title_parts.append(f'Country({select_country if select_country != ALL_OPTION_VALUE else "全部"})') title_parts.append(f'State({select_state if select_state != ALL_OPTION_VALUE else "全部"})') return { 'data':[go.Bar( x=PatientStatus3['CITYNAME'], y=PatientStatus3['PATIENTCOUNTS'], text = PatientStatus3['PATIENTCOUNTS'], texttemplate = 'Patient Count: ' + '%{text:.2s}', textposition = 'auto', marker = dict( color = PatientStatus3['PATIENTCOUNTS'], colorscale = 'phase', showscale = False), hoverinfo='text', hovertext= '<b>Unit</b>: ' + PatientStatus3['UNITNAME'].astype(str) + '<br>' + '<b>Country</b>: ' + PatientStatus3['COUNTRY'].astype(str) + '<br>' + '<b>State</b>: ' + PatientStatus3['STATENAME'] + '<br>' + '<b>City</b>: ' + PatientStatus3['CITYNAME'].astype(str) + '<br>' + '<b>Patient Count</b>: ' + [f'{x:,.0f}' for x in PatientStatus3['PATIENTCOUNTS']] + '<br>' )], 'layout': go.Layout( barmode = 'group', plot_bgcolor='#F2F2F2', paper_bgcolor='#F2F2F2', title={ 'text': ' : '.join(title_parts), 'y': 0.96, 'x': 0.5, 'xanchor': 'center', 'yanchor': 'top'}, titlefont={ 'color': 'rgb(50, 50, 50)', 'size': 15}, hovermode='x', xaxis=dict(title='<b>City</b>', color='rgb(50, 50, 50)', showline=True, showgrid=True, linecolor='rgb(50, 50, 50)', linewidth=1), yaxis=dict(title='<b>Patient-counts</b>', color='rgb(50, 50, 50)', showline=False, showgrid=True, linecolor='rgb(50, 50, 50)'), legend = { 'orientation': 'h', 'bgcolor': '#F2F2F2', 'x': 0.5, 'y': 1.25, 'xanchor': 'center', 'yanchor': 'top'}, font = dict( family = "Arial", size = 12, color = 'rgb(50, 50, 50)'), uniformtext_mode = 'hide', uniformtext_minsize = 15 ) } if __name__=='__main__': app.run_server(host="localhost", debug=True, use_reloader=False, port=90)
注意事项
- 确保你的csv文件中
UNITNAME/PATIENTSTATUS/COUNTRY/STATENAME/CITYNAME/PATIENTCOUNTS字段名和代码中完全一致,否则会报字段不存在错误 - 如果需要支持多选全选,只需要把对应Dropdown的
multi参数改为True,全选逻辑判断改为判断选中值是否包含ALL_OPTION_VALUE即可 - 代码里修正了你原布局中
create_cotainer2的类名拼写错误,避免样式加载异常
内容的提问来源于stack exchange,提问作者Kashish
相关产品推荐
相关产品推荐

