英文:
Multiple Inputs in a Dash application with many Tabs
问题
我目前正在编写一个Dash应用程序,我遇到了一个小问题:
在一个回调函数中,我想要使用2个输入和2个输出,但有一个小问题,这两个输入不在同一个选项卡中。我将展示给你我的回调函数和应用程序,这样你就能更好地理解我的问题:
回调函数:
@app.callback(
[Output("kpi-viewer-tab","children"), Output("export-alert","is_open")],
Input("start-calculations-button","n_clicks"),
Input("dl-button","n_clicks"),
[State("rack-boolean-switch","on"),
State("bank-boolean-switch","on"),
State("project-name","value"),
State("name","value"),
State("surname","value"),
State("html-startdate-picker","value"),
State("html-enddate-picker","value")]
)
def viewer_tab(click_calcul, dl_click, on_r, on_b, project_name, user_name, surname, start_dt, end_dt):
# 函数体内容
# ...
应用程序:我的应用程序截图
第一个输出用于更改选项卡元素的内容。第二个用于告知用户PDF已创建。
第一个输入是位于“KPI Config”选项卡底部的按钮,第二个输入是位于“KPI Viewer”选项卡顶部的“导出为PDF”按钮。
问题是,当我在“KPI Config”选项卡上时,按钮甚至不被识别,所以我不能在初始化时将应用程序置于“KPI Config”选项卡上。(我不知道是否有帮助,但选项卡的内容也会通过回调函数更新)
是否有人有解决这个问题的线索?我不确定是否提供了足够的信息,以便你帮助我,所以如果你需要任何信息,请向我提问,我会回答你。
(附注:我知道这个回调函数中有一些错误和未优化的代码,我以后会进行更正)
我尝试构建了两个不同的回调函数,它们按照我想要的方式工作,但是由于函数E2P.export2pdf
需要许多参数,所以我不得不使用6个全局变量。
英文:
I'm currently programming a Dash application and I'm facing a little problem:
In a callback, I want to use 2 inputs and 2 outputs but there's a tiny problem, both inputs are not in the same tab. I will show you my callback and the application thus you will understand my problem way better:
The callback:
@app.callback(
[Output("kpi-viewer-tab","children"),Output("export-alert","is_open")],
Input("start-calculations-button","n_clicks"),
Input("dl-button","n_clicks"),
[State("rack-boolean-switch","on"),
State("bank-boolean-switch","on"),
State("project-name","value"),
State("name","value"),
State("surname","value"),
State("html-startdate-picker","value"),
State("html-enddate-picker","value")]
)
def viewer_tab (click_calcul,dl_click,on_r,on_b, project_name, user_name, surname, start_dt, end_dt):
type_request = ""
files = []
name = "None"
kpi_results = dict
StartDay = pd.Timestamp("2023-04-14T12", tz='utc')
EndDay = pd.Timestamp("2023-04-28T12", tz='utc')
dt_start = [StartDay + datetime.timedelta(days = 1*dayIdx) for dayIdx in range(1)]
dt_end = [EndDay + datetime.timedelta(days = 1*dayIdx) for dayIdx in range(1)]
tmp_idSite = "eccb5d99-f46a-4780-8853-2124b062f5ca"
project = ""
user = ""
raw_data = {}
plot_df = {}
x_axis = []
global calcul_in_progress
if project_name is None:
return dash.no_update
if user_name is not None and surname is not None:
surname = surname.upper()
user_name = user_name.title()
user = user_name+" "+surname
if on_r:
type_request = "RACK"
elif on_b:
type_request = "BANK"
else:
type_request = "RACK"
for index in range (len(project_name)):
if project_name[index] == "_":
name = project_name[:index]
break
if name == "None":
name = project_name
project = name
files = LSP.load_files(Mapping_path, TimeSeries_path, name)
print(files)
if dl_click > 0:
E2P.export2pdf(raw_data, plot_df,x_axis,project,user,str(start_dt),str(end_dt))
return True
else:
if click_calcul > 0:
calcul_in_progress = 1
kpi_results = RC.run_kpi_calculations(project, type_request, dt_start, dt_end, tmp_idSite, files, param_dict, kpi_calculations)
calcul_in_progress = 0
plot_df, raw_data, x_axis = RC.extract_data_from_dict(kpi_results)
click_calcul = 0
return TB.inside_viewer(user, plot_df, raw_data, x_axis)
else :
return dash.no_update
The application:
Screenshot of my application
The first output is used to change the content of the Tabs element. The second is the alert which tells the user that the PDF is created.
The first input is a button at the bottom of the "KPI Config" tab and the second input is the Export to PDF button which is at the top of the "KPI Viewer" tab.
The problem is that when I'm on the tab "KPI Config" the button isn't even recognized so I just can't use it as my application need to be on the "KPI Config" tab at its initialization. (I don't if this can help, but content of tabs are updated with callbacks as well)
Does someone have a clue to solve this?
I'm not sure to give enough information for you to help me so if you need anything, ask me I'll answer you
(P.S.: I know there's some errors and not optimized code in this callback, I'll correct it later)
I tried to build 2 different callbacks and it worked as I wanted but, I had to use 6 global variables because of the function E2P.export2pdf which needs a lot of parameters.
答案1
得分: 0
你可以将每个Python Dash文件组织到与你的app.py
(或等效文件)主要Dash应用程序文件相对的本地子目录下,然后只需使用import
语句从各个选项卡中导入所有本地变量到主应用程序文件中。你不应该需要使用全局变量。
一个具有多个选项卡和回调的简单dash应用程序,该回调具有来自多个选项卡的多个输入
例如:
您的文件目录结构可能如下所示:
.
|-- app.py
`-- tabs
|-- kpi_config.py
`-- kpi_viewer.py
在每个选项卡文件中,定义一个组件列表,创建每个选项卡的布局结构,并将其命名为children
。
例如,在kpi_config.py
(选项卡#1)中:
import dash
from dash import dcc, html
children = [
html.Div(html.H1("Header Config"), id="header-1"),
html.Div(
[
html.Div("Convert Temperature"),
"Celsius",
dcc.Input(id="celsius-1", value=0.0, type="number"),
" = Fahrenheit",
dcc.Input(id="fahrenheit-1", value=32.0, type="number",),
]
),
]
而在kpi_viewer.py
(选项卡#2)中:
import dash
from dash import dcc, html
children = [
html.Div(html.H1("Header Viewer"), id="header-2"),
html.Div(
[
html.Div("Convert Temperature"),
"Celsius",
dcc.Input(id="celsius-2", value=0.0, type="number"),
" = Fahrenheit",
dcc.Input(id="fahrenheit-2", value=32.0, type="number",),
]
),
]
为了演示动态/响应式回调功能,我在两个选项卡中都放置了一个简单的摄氏度-华氏度双向计算器,使用了dash.dcc.Input
组件。但是,它们必须具有唯一的ID。因此,基于组件所在的选项卡,我相应地将“1”或“2”附加到ID。因此,每当更改_任一_选项卡上的输入时,我们希望_两个_计算器都发生变化(在这个有点随意的示例中,以模拟来自不同选项卡的输入触发相同回调的情况,接下来将显示该回调)。
然后,在主应用程序文件app.py
中:
如顶级目录中所示,我有如下代码:
import dash
from dash import Dash, Input, Output, State, callback, dcc, html, ctx
import tabs
from tabs.kpi_config import children as kpi_config
from tabs.kpi_viewer import children as kpi_viewer
app = dash.Dash(__name__)
# 根据文档使用`dash.dcc.Tabs`
tab_pages = [
dcc.Tab(label="KPI Config", children=kpi_config),
dcc.Tab(label="KPI Viewer", children=kpi_viewer),
]
children = html.Div([dcc.Tabs(id="tabs", children=tab_pages)])
app.layout = children
@callback(
[
Output("celsius-1", "value"),
Output("fahrenheit-1", "value"),
Output("celsius-2", "value"),
Output("fahrenheit-2", "value"),
],
[
Input("celsius-1", "value"),
Input("fahrenheit-1", "value"),
Input("celsius-2", "value"),
Input("fahrenheit-2", "value"),
],
)
def sync_input(
celsius_config, fahrenheit_config, celsius_viewer, fahrenheit_viewer
):
input_id = ctx.triggered[0]["prop_id"].split(".")[0]
def convert_temperature(c, f):
if input_id.startswith("celsius"):
f = None if c is None else (float(c) * 9 / 5) + 32
else:
c = None if f is None else (float(f) - 32) * 5 / 9
return c, f
if input_id.endswith("1"):
c, f = convert_temperature(celsius_config, fahrenheit_config)
else:
c, f = convert_temperature(celsius_viewer, fahrenheit_viewer)
return c, f, c, f
if __name__ == "__main__":
app.run_server(debug=True)
这将产生如下应用程序行为:
在这里,您可以看到对一个选项卡中的更改会自动地同时更改两个选项卡中的组件(因为回调由_任一_选项卡中的组件的输入触发)。当仅处理多_选项卡_应用程序(而不是多_页面_(即URL)应用程序时[在这种情况下,事情会变得稍微复杂一些]),您仍然可以在声明dash.app
的同一文件中定义所有回调。因为我们通过导入两个选项卡文件的子组件并将其分配给app.layout
(使用dash.dcc.Tabs
来汇总两个dash.dcc.Tab
组件),Dash会处理其余的工作,并且所有定义的回调的命名空间中现在都包含了两个选项卡文件中的所有组件ID。
英文:
You can organize each python dash file for each tab under a local subdirectory relative to where your app.py
(or equivalent) main dash app file is, and simply use import
statements to import all the local variables from the tabs into the main app file. You should not [need to] use global variables.
A simple dash app with multiple tabs and a callback which has multiple inputs from multiple tabs
For example:
Your file directory structure could look like:
.
|-- app.py
`-- tabs
|-- kpi_config.py
`-- kpi_viewer.py
In each tab file, define a list of components creating the layout structure for each tab and name it children
.
E.g., in kpi_config.py
(tab #1) I have:
import dash
from dash import dcc, html
children = [
html.Div(html.H1("Header Config"), id="header-1"),
html.Div(
[
html.Div("Convert Temperature"),
"Celsius",
dcc.Input(id="celsius-1", value=0.0, type="number"),
" = Fahrenheit",
dcc.Input(id="fahrenheit-1", value=32.0, type="number",),
]
),
]
and in kpi_viewer.py
(tab #2) I have:
import dash
from dash import dcc, html
children = [
html.Div(html.H1("Header Viewer"), id="header-2"),
html.Div(
[
html.Div("Convert Temperature"),
"Celsius",
dcc.Input(id="celsius-2", value=0.0, type="number"),
" = Fahrenheit",
dcc.Input(id="fahrenheit-2", value=32.0, type="number",),
]
),
]
To demonstrate dynamic/reactive callback functionality, I put a simple celsius-fahrenheit bidirectional calculator into both tabs using dash.dcc.Input
components. However, they must have unique ids. So, based on which tab the components were in, I correspondingly appended a "1" or "2" to the ids. So, whenever an input on either tab is altered, we want both calculators to change (in this somewhat arbitrary example to simulate having inputs from separate tabs trigger the same callback, which will be shown next).
And then, in the main app file app.py
:
As shown in the top directory, I have code such as:
import dash
from dash import Dash, Input, Output, State, callback, dcc, html, ctx
import tabs
from tabs.kpi_config import children as kpi_config
from tabs.kpi_viewer import children as kpi_viewer
app = dash.Dash(__name__)
# Use `dash.dcc.Tabs` per docs
tab_pages = [
dcc.Tab(label="KPI Config", children=kpi_config),
dcc.Tab(label="KPI Viewer", children=kpi_viewer),
]
children = html.Div([dcc.Tabs(id="tabs", children=tab_pages)])
app.layout = children
@callback(
[
Output("celsius-1", "value"),
Output("fahrenheit-1", "value"),
Output("celsius-2", "value"),
Output("fahrenheit-2", "value"),
],
[
Input("celsius-1", "value"),
Input("fahrenheit-1", "value"),
Input("celsius-2", "value"),
Input("fahrenheit-2", "value"),
],
)
def sync_input(
celsius_config, fahrenheit_config, celsius_viewer, fahrenheit_viewer
):
input_id = ctx.triggered[0]["prop_id"].split(".")[0]
def convert_temperature(c, f):
if input_id.startswith("celsius"):
f = None if c is None else (float(c) * 9 / 5) + 32
else:
c = None if f is None else (float(f) - 32) * 5 / 9
return c, f
if input_id.endswith("1"):
c, f = convert_temperature(celsius_config, fahrenheit_config)
else:
c, f = convert_temperature(celsius_viewer, fahrenheit_viewer)
return c, f, c, f
if __name__ == "__main__":
app.run_server(debug=True)
Which yields, e.g., the following app behavior:
where you can see that changes in one tab automatically, reactively change the components in both tabs simultaneously (because the callback is triggered by inputs from the components in either tab).
When only working with a multi-tab app (vs. a multi-page (i.e., url) app [where things get a little more complicated]), you can still define all your callbacks in the same file where the dash.app
is declared. Because we've imported the dash components by importing the children of both tab files and assigning those to app.layout
(using dash.dcc.Tabs
to collect together two dash.dcc.Tab
components), dash takes care of the rest and all of component ids in both tab files are now in the namespace of all defined callbacks in the app.py
file.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论