假设您有一个带有两个选项卡的仪表板。每个选项卡上都有一个下载按钮和一个数据表。如何编写一个回调函数来将数据表从该选项卡下载到 csv 文件?
这是一个最小的可重现示例:
import dash_bootstrap_components as dbc
from dash import dcc, html, State, Output, Input, Dash, ALL, MATCH
from dash.dash_table import DataTable
import pandas as pd
app = Dash(__name__)
df1 = pd.DataFrame({'name': ['John', 'Jane', 'Bob'], 'age': [1, 2, 3]})
df2 = pd.DataFrame({'colour': ['Red', 'Green', 'Blue'], 'favourite': [True, False, False]})
app.layout = dbc.Container(children=[
dcc.Download(id="download_data"),
dcc.Tabs([
dcc.Tab(label='Tab 1', children=[
html.Button("Download", id={"type": "download_data_table", "index": "tab_1"}),
DataTable(
data=df1.to_dict("records"),
columns=[{"name": i, "id": i} for i in df1.columns],
id={"type": "data_table", "index": "tab_1"},
),
]),
dcc.Tab(label='Tab 2', children=[
html.Button("Download", id={"type": "download_data_table", "index": "tab_2"}),
DataTable(
data=df2.to_dict("records"),
columns=[{"name": i, "id": i} for i in df2.columns],
id={"type": "data_table", "index": "tab_2"},
),
]),
]),
], fluid=True)
@app.callback(
Output('download_data', 'data'),
Input({'type': "download_data_table", "index": MATCH}, "n_clicks"),
State({"type": "data_table", "index": MATCH}, 'data')
)
def download_table(n_clicks, data):
if n_clicks > 0:
return dcc.send_data_frame(data.to_csv, f"data.csv", index=False)
if __name__ == '__main__':
app.run_server(host='0.0.0.0', port=8000)
我期望当我单击选项卡上的下载按钮时,它应该将数据从该选项卡传递到下载组件,该组件应该将数据下载为 csv。但是,当我单击“下载”按钮时,没有任何反应。
我认为这个问题与我错误地实现模式匹配回调函数有关。
我知道我可以为每个下载按钮编写一个单独的回调,但是我希望能够将此解决方案推广到具有许多选项卡,这会导致大量重复的代码。
任何帮助将不胜感激。
注意:使用
需要Dash 2.4+ctx.triggered_id
在这种情况下,您实际上想要使用
ALL
,因为您只有一个 dcc.Download
组件 - 选项卡中的 所有 不同的下载按钮都可以 触发。然后,ctx
用于确定所有按钮中的哪个按钮触发回调函数的任何特定调用。
import dash
from dash import ALL, Dash, Input, Output, State
from dash import ctx, dcc, html
from dash.dash_table import DataTable
import dash_bootstrap_components as dbc
import pandas as pd
app = Dash(__name__)
df1 = pd.DataFrame({"name": ["John", "Jane", "Bob"], "age": [1, 2, 3]})
df2 = pd.DataFrame(
{"colour": ["Red", "Green", "Blue"], "favourite": [True, False, False]}
)
app.layout = dbc.Container(
children=[
dcc.Download(id="download_data"),
dcc.Tabs(
[
dcc.Tab(
label="Tab 1",
children=[
html.Button(
"Download",
id={
"type": "download_data_table",
"index": "tab_1",
},
n_clicks=None,
),
DataTable(
data=df1.to_dict("records"),
columns=[
{"name": i, "id": i} for i in df1.columns
],
id={"type": "data_table", "index": "tab_1"},
),
],
),
dcc.Tab(
label="Tab 2",
children=[
html.Button(
"Download",
id={
"type": "download_data_table",
"index": "tab_2",
},
n_clicks=None,
),
DataTable(
data=df2.to_dict("records"),
columns=[
{"name": i, "id": i} for i in df2.columns
],
id={"type": "data_table", "index": "tab_2"},
),
],
),
]
),
],
fluid=True,
)
@app.callback(
Output("download_data", "data"),
Input({"type": "download_data_table", "index": ALL}, "n_clicks"),
State({"type": "data_table", "index": ALL}, "data"),
)
def download_table(n_clicks, data):
if all(map(lambda x: x is None, n_clicks)):
raise dash.exceptions.PreventUpdate
# print(n_clicks)
button_id = ctx.triggered_id.index
# print(button_id)
if button_id.endswith("1"):
df = pd.DataFrame(data[0])
else:
df = pd.DataFrame(data[1])
return dcc.send_data_frame(
df.to_csv, f"data_{button_id}.csv", index=False
)
if __name__ == "__main__":
app.run(debug=True, dev_tools_hot_reload=True)