Dash实现按列配置多选下拉筛选器的代码修改求助
为Dash表格的每一列添加独立多选下拉筛选菜单
问题背景
我有如下数据集:
cat_id author year publisher country value (dollars) name1 kunga 1998 D and D Australia 10 name2 siba 2001 D and D UK 20 name3 siba 2001 D and D US 20 name3 shevara 2001 D and D UK 10 name3 dougherty 1992 D and D Australia 20 name4 ken 2011 S and K Australia 10
需求是为表格的每一列添加独立的Multi-Select Dropdown(多选下拉菜单),而非表格级别的下拉(当前单列筛选无法满足一次性多选过滤的需求)。例如,cat_id列下方的下拉需包含name1、name2、name3、name4;author列下拉需包含kunga、siba等选项。
我已编写了如下Dash代码,但存在两个问题:
- 当前下拉菜单是表格级别的,而非按列配置;
- 无法基于下拉选项过滤表格数据。
我知道问题出在以下代码段,且缺少对应的回调逻辑,但作为Dash新手,不清楚具体修改方式,请求帮忙调整代码解决这两个问题:
#Need to move this to being within each column? html.Label('Multi-Select Dropdown'), dcc.Dropdown(options=[{'label':i,'value':i} for i in df.columns], value = [i for i in df.columns], multi=True),
原完整代码
import dash from dash.dependencies import Input, Output, State import dash_table import dash_core_components as dcc import dash_html_components as html import pandas as pd app = dash.Dash(__name__) df = pd.read_excel('dash_test_doc.xlsx') app.layout = html.Div([ html.Div([ dcc.Input( id='adding-rows-name', placeholder='Enter a column name...', value='', style={'padding': 10},), html.Button('Add Column', id='adding-rows-button', n_clicks=0) ], style={'height': 50}), #Need to move this to being within each column? html.Label('Multi-Select Dropdown'), dcc.Dropdown(options=[{'label':i,'value':i} for i in df.columns], value = [i for i in df.columns], multi=True), dash_table.DataTable( id='adding-rows-table', columns=[{"name": i, "id": i,"deletable":True} for i in df.columns], # column_conditional_dropdowns=[{'id':i,'dropdowns':df[i]} for i in df.columns], data = df.to_dict('rows'), editable=True, filtering=True, sorting=True, sorting_type="multi", row_selectable="multi", row_deletable=True, selected_rows=[], pagination_mode="fe", style_cell_conditional=[ { 'if': {'row_index': 'odd'}, 'backgroundColor': 'rgb(230, 255, 230)' } ] + [ { 'if': {'column_id': c}, 'textAlign': 'left' } for c in ['Date', 'Region'] ], style_header={ 'backgroundColor': 'white', 'fontWeight': 'bold' } ), html.Button('Add Row', id='editing-rows-button', n_clicks=0), dcc.Graph(id='adding-rows-graph') ]) @app.callback( Output('adding-rows-table', 'data'), [Input('editing-rows-button', 'n_clicks')], [State('adding-rows-table', 'data'), State('adding-rows-table', 'columns')]) def add_row(n_clicks, rows, columns): if n_clicks > 0: rows.append({c['id']: '' for c in columns}) return rows @app.callback( Output('adding-rows-table', 'columns'), [Input('adding-rows-button', 'n_clicks')], [State('adding-rows-name', 'value'), State('adding-rows-table', 'columns')]) def update_columns(n_clicks, value, existing_columns): if n_clicks > 0: existing_columns.append({ 'id': value, 'name': value, 'editable_name': True, 'deletable': True }) return existing_columns @app.callback( Output('adding-rows-graph', 'figure'), [Input('adding-rows-table', 'data'), Input('adding-rows-table', 'columns')]) def display_output(rows, columns): return { 'data': [{ 'type': 'heatmap', 'z': [[row.get(c['id'], None) for c in columns] for row in rows], 'x': [c['name'] for c in columns] }] } if __name__ == '__main__': app.run_server(debug=True)
解决方案
要实现每列独立的多选下拉筛选,我们需要做两个核心修改:为每个列生成独立的下拉菜单,并编写回调逻辑根据选中的选项过滤表格数据。以下是调整后的完整代码:
import dash from dash.dependencies import Input, Output, State import dash_table import dash_core_components as dcc import dash_html_components as html import pandas as pd app = dash.Dash(__name__) df = pd.read_excel('dash_test_doc.xlsx') # 保存原始数据,用于过滤时的数据源(避免过滤后的数据被二次修改) original_data = df.to_dict('rows') app.layout = html.Div([ html.Div([ dcc.Input( id='adding-rows-name', placeholder='Enter a column name...', value='', style={'padding': 10},), html.Button('Add Column', id='adding-rows-button', n_clicks=0) ], style={'height': 50}), # 为每一列创建独立的多选下拉菜单,横向排列 html.Div([ html.Div([ html.Label(f"筛选 {col}"), dcc.Dropdown( id=f'dropdown-{col}', options=[{'label': str(val), 'value': str(val)} for val in df[col].unique()], value=[str(val) for val in df[col].unique()], multi=True ) ], style={'width': '18%', 'display': 'inline-block', 'margin-right': '2%', 'margin-bottom': '10px'}) for col in df.columns ], style={'margin': '20px 0'}), dash_table.DataTable( id='adding-rows-table', columns=[{"name": i, "id": i,"deletable":True} for i in df.columns], data = df.to_dict('rows'), editable=True, sorting=True, sorting_type="multi", row_selectable="multi", row_deletable=True, selected_rows=[], pagination_mode="fe", style_cell_conditional=[ { 'if': {'row_index': 'odd'}, 'backgroundColor': 'rgb(230, 255, 230)' } ] + [ { 'if': {'column_id': c}, 'textAlign': 'left' } for c in df.columns ], style_header={ 'backgroundColor': 'white', 'fontWeight': 'bold' } ), html.Button('Add Row', id='editing-rows-button', n_clicks=0), dcc.Graph(id='adding-rows-graph') ]) # 新增:整合过滤+添加行的回调逻辑 @app.callback( Output('adding-rows-table', 'data'), [Input(f'dropdown-{col}', 'value') for col in df.columns] + [Input('editing-rows-button', 'n_clicks')], [State('adding-rows-table', 'columns')]) def filter_and_add_row(*args): # 拆分参数:前N个是各列下拉的选中值,接下来是添加行按钮的点击次数,最后是表格列信息 dropdown_values = args[:len(df.columns)] n_clicks = args[len(df.columns)] columns = args[len(df.columns)+1] # 1. 执行过滤逻辑:基于原始数据,保留符合所有列筛选条件的行 filtered_data = original_data.copy() for col, selected_vals in zip(df.columns, dropdown_values): filtered_data = [row for row in filtered_data if str(row[col]) in selected_vals] # 2. 执行添加行逻辑:如果按钮被点击,新增空行 if n_clicks > 0: filtered_data.append({c['id']: '' for c in columns}) return filtered_data @app.callback( Output('adding-rows-table', 'columns'), [Input('adding-rows-button', 'n_clicks')], [State('adding-rows-name', 'value'), State('adding-rows-table', 'columns')]) def update_columns(n_clicks, value, existing_columns): # 新增列时校验输入值非空 if n_clicks > 0 and value: existing_columns.append({ 'id': value, 'name': value, 'editable_name': True, 'deletable': True }) return existing_columns @app.callback( Output('adding-rows-graph', 'figure'), [Input('adding-rows-table', 'data'), Input('adding-rows-table', 'columns')]) def display_output(rows, columns): # 优化热力图数据处理:将非数值转为None,避免报错 z_data = [] for row in rows: row_vals = [] for c in columns: val = row.get(c['id'], None) try: row_vals.append(float(val) if val != '' else None) except (ValueError, TypeError): row_vals.append(None) z_data.append(row_vals) return { 'data': [{ 'type': 'heatmap', 'z': z_data, 'x': [c['name'] for c in columns] }] } if __name__ == '__main__': app.run_server(debug=True)
修改说明:
为每列创建独立下拉菜单:
- 用列表推导式为每个列生成专属的下拉组件,每个组件ID设为
dropdown-{列名}保证唯一性。 - 下拉选项直接取自对应列的唯一值,默认选中所有选项,符合初始展示全部数据的需求。
- 用
inline-block让下拉菜单横向排列,节省页面空间。
- 用列表推导式为每个列生成专属的下拉组件,每个组件ID设为
新增过滤+添加行的整合回调:
- 把所有列的下拉选中值和添加行按钮作为输入,确保任何一个筛选条件变化或添加行操作都能触发更新。
- 基于原始数据做过滤,避免过滤后的二次数据被修改导致的筛选异常。
- 同时保留原有的添加行逻辑,保证功能不丢失。
其他细节优化:
- 移除了原表格的
filtering=True,因为我们用自定义下拉筛选替代了内置筛选,避免功能冲突。 - 调整表头对齐逻辑,适配所有列的展示需求。
- 优化热力图的数据处理逻辑,处理非数值列和空值的情况,避免图表渲染报错。
- 移除了原表格的
内容的提问来源于stack exchange,提问作者Slowat_Kela
相关产品推荐
相关产品推荐

