- 一、简单的交互式
- 1.1 代码
- 1.2 页面效果
- 1.3 说明
- 二、带有图形和滑块的 Dash 应用程序
- 2.1 代码
- 2.2 页面展示
- 2.3 说明
- 三、具有多个输入的 Dash 应用程序
- 3.1 代码
- 3.2 页面效果
- 3.3 说明
- 四、具有多个输出的 Dash 应用程序
- 4.1 代码
- 4.2 页面效果
- 4.3 说明
- 五、带有链式回调的 Dash 应用程序
- 5.1 代码
- 5.2 页面效果
- 5.3 说明
- 六、带有状态的 Dash 应用程序
- 6.1 两个Input的装饰器
- 6.1.1 代码
- 6.1.2 页面效果
- 6.1.3 说明
- 6.2 Input + State 的装饰器
- 6.2.1 代码
- 6.2.2 页面效果
- 6.2.3 说明
在布局中,我们了解到app.layout描述了应用程序的外观并且是组件的分层树。该dash_html_components库提供类所有的HTML标签,以及关键字参数说明了HTML属性,如style,className和id。该dash_core_components库生成更高级的组件,如控件和图形。
本章介绍如何使用回调函数制作 Dash 应用程序:每当输入组件的属性更改时,Dash 会自动调用这些函数,以便更新另一个组件(输出)中的某些属性。
Dash 应用程序建立在一组简单但强大的原则之上:可通过反应式回调自定义的 UI。组件的每个attribute/property 都可以作为回调的输出进行修改,而用户可以通过与页面交互来编辑属性的子集(例如组件dcc.Dropdown的value 属性)。
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
app.layout = html.Div([
html.H6("更改文本框中的值以查看正在运行的回调!"),
html.Div([
"请输入: ",
dcc.Input(id='my-input', value='默认值', type='text')
]),
html.Br(),
html.Div(id='my-output'),
])
@app.callback(
# component_id 和 component_property总是以此排列,可以省略
Output(component_id='my-output', component_property='children'),
Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
""" 接受id='my-input'的'value',
原封不动的返回给id='my-output'的'children'"""
return f'通过装饰器回调输出: {input_value}'
if __name__ == '__main__':
app.run_server(debug=True)
1.2 页面效果
1.3 说明
让我们分解这个例子:
- 我们应用程序的“Input”和“Output”被描述为@app.callback装饰器的参数。
- 在 Dash 中,我们应用程序的输入和输出只是特定组件的属性。在这个例子中,我们的输入是ID 为“ my-input”的组件的“ value”属性。我们的输出是ID 为“ my-output”的组件的“children ”属性。
- 每当输入属性发生变化时,回调装饰器包装的函数将被自动调用。Dash 为这个回调函数提供输入属性的新值作为其参数,Dash 使用函数返回的任何内容更新输出组件的属性。
- 在component_id和component_property关键字是可选的(只有两个参数为每个对象)。为了清楚起见,它们包含在本示例中,但为了简洁和可读性,将在文档的其余部分中省略。
- 不要混淆dash.dependencies.Input对象和dcc.Input对象。前者只是在这些回调定义中使用,后者是一个实际的组件。
- 当 Dash 应用程序启动时,它会自动使用输入组件的初始值调用所有回调,以填充输出组件的初始状态。在此示例中,如果您将 div 组件指定为 html.Div(id=‘my-output’, children=‘Hello world’),它将在应用程序启动时被覆盖。这有点像使用 Microsoft Excel 进行编程:每当一个单元格发生变化(输入)时,所有依赖于该单元格的单元格(输出)都会自动更新。这称为“反应式编程”,因为输出会自动对输入的变化做出反应。
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd
# 国外网站,打不开
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
app = dash.Dash(__name__)
app.layout = html.Div([
# 图表组件
dcc.Graph(id='graph-with-slider'),
# 滑块组件
dcc.Slider(
id='year-slider',
min=df['year'].min(),
max=df['year'].max(),
value=df['year'].min(),
marks={str(year): str(year) for year in df['year'].unique()},
step=None
)
])
@app.callback(
Output('graph-with-slider', 'figure'),
Input('year-slider', 'value'))
def update_figure(selected_year):
""" 接受滑块变化了的值,据此对数据进行筛选,返回图表"""
filtered_df = df[df.year == selected_year]
fig = px.scatter(filtered_df,
x="gdpPercap",
y="lifeExp",
size="pop",
color="continent",
hover_name="country",
log_x=True,
size_max=55)
# 动画平滑时间500ms
fig.update_layout(transition_duration=500)
return fig
if __name__ == '__main__':
app.run_server(debug=True)
2.2 页面展示
2.3 说明
- 在这个例子中,dcc.Slider 的"value"属性是应用程序的输入,应用程序的输出是 属性 dcc.Graph的"figure"。每当value的dcc.Slider变化,Dash调用回调函数update_figure用新值。该函数使用这个新值过滤数据帧,构造一个figure对象,并将其返回给 Dash 应用程序。
- 我们使用pandas库的应用程序的启动加载我们的数据框: df = pd.read_csv(’…’)。此数据帧df处于应用程序的全局状态,可以在回调函数中读取。
- 将数据加载到内存中可能会很昂贵。通过在应用程序启动而不是在回调函数内部加载查询数据,我们确保此操作仅执行一次——当应用程序服务器启动时。当用户访问应用程序或与应用程序交互时,该数据 ( df) 已在内存中。如果可能,昂贵的初始化(如下载或查询数据)应该在应用程序的全局范围内完成,而不是在回调函数内完成。
- 回调不会修改原始数据,它只会通过使用 pandas 过滤来创建数据帧的副本。 这很重要: 您的回调不应修改其范围之外的变量。如果您的回调修改了全局状态,那么一个用户的会话可能会影响下一个用户的会话,并且当应用程序部署在多个进程或线程上时,这些修改将不会跨会话共享。
- 我们打开转换以layout.transition了解数据集如何随时间演变:转换允许图表从一个状态平滑地更新到下一个状态,就好像它是动画一样。
在 Dash 中,任何“输出”都可以有多个“输入”组件。这是一个简单的示例,它将五个输入(value两个dcc.Dropdown组件的属性、两个dcc.RadioItems组件和一个dcc.Slider组件的属性)绑定到一个输出组件(组件的figure属性dcc.Graph)。请注意如何在Output之后app.callback列出所有五个Input项目。
3.1 代码import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd
app = dash.Dash(__name__)
# 国外网站数据下载到本地
csv_url = 'https://plotly.github.io/datasets/country_indicators.csv'
df = pd.read_csv('country_indicators.csv')
# 提取指标名称
available_indicators = df['Indicator Name'].unique()
app.layout = html.Div([
html.Div([
html.Div([
# 下拉列表框
dcc.Dropdown(
# X轴数据
id='xaxis-column',
# 选项为所有指标
options=[{'label': i, 'value': i} for i in available_indicators],
value='Fertility rate, total (births per woman)'
),
# 单选框
dcc.RadioItems(
# X轴类型
id='xaxis-type',
# 选项为线性和日志
options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
value='Linear',
labelStyle={'display': 'inline-block'}
)
], style={'width': '48%', 'display': 'inline-block'}),
html.Div([
dcc.Dropdown(
# Y轴数据
id='yaxis-column',
options=[{'label': i, 'value': i} for i in available_indicators],
value='Life expectancy at birth, total (years)'
),
dcc.RadioItems(
# Y轴类型
id='yaxis-type',
options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
value='Linear',
labelStyle={'display': 'inline-block'}
)
], style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
]),
# 图表
dcc.Graph(id='indicator-graphic'),
# 滑块
dcc.Slider(
id='year--slider',
min=df['Year'].min(),
max=df['Year'].max(),
value=df['Year'].max(),
marks={str(year): str(year) for year in df['Year'].unique()},
step=None
)
])
@app.callback(
Output('indicator-graphic', 'figure'),
Input('xaxis-column', 'value'),
Input('yaxis-column', 'value'),
Input('xaxis-type', 'value'),
Input('yaxis-type', 'value'),
Input('year--slider', 'value'))
def update_graph(xaxis_column_name, yaxis_column_name,
xaxis_type, yaxis_type,
year_value):
""" 形参位置与装饰器中Input的位置一一对应,依次接收参数 """
dff = df[df['Year'] == year_value]
fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])
# 更新布局
fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')
# 更新X轴
fig.update_xaxes(title=xaxis_column_name,
type='linear' if xaxis_type == 'Linear' else 'log')
# 更新Y轴
fig.update_yaxes(title=yaxis_column_name,
type='linear' if yaxis_type == 'Linear' else 'log')
return fig
if __name__ == '__main__':
app.run_server(debug=True)
3.2 页面效果
3.3 说明
在此示例中,只要、 或组件中value的任何一个的属性 发生更改dcc.Dropdown,就会执行回调。dcc.Sliderdcc.RadioItems
回调的输入参数是每个“输入”属性的当前值,按照它们被指定的顺序。
即使一次只有一个Input更改(即用户只能在给定时刻更改单个 Dropdown 的值),Dash 也会收集所有指定Input属性的当前状态并将它们传递给回调函数。这些回调函数始终保证接收应用程序的更新状态。
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
# 输入框组件
dcc.Input(
id='num-multi',
type='number',
value=5
),
# 表格组件
html.Table([
html.Tr([html.Td(['x', html.Sup(2)]), html.Td(id='square')]),
html.Tr([html.Td(['x', html.Sup(3)]), html.Td(id='cube')]),
html.Tr([html.Td([2, html.Sup('x')]), html.Td(id='twos')]),
html.Tr([html.Td([3, html.Sup('x')]), html.Td(id='threes')]),
html.Tr([html.Td(['x', html.Sup('x')]), html.Td(id='x^x')]),
]),
])
@app.callback(
Output('square', 'children'),
Output('cube', 'children'),
Output('twos', 'children'),
Output('threes', 'children'),
Output('x^x', 'children'),
Input('num-multi', 'value'))
def callback_a(x):
""" 接受一个参数,返回5个值,顺序与Output一一对应 """
return x**2, x**3, 2**x, 3**x, x**x
if __name__ == '__main__':
app.run_server(debug=True)
4.2 页面效果
4.3 说明
合并输出并不总是一个好主意,即使您可以:
- 如果输出依赖于一些(但不是全部)相同的输入,那么将它们分开可以避免不必要的更新。
- 如果输出具有相同的输入但它们对这些输入执行非常不同的计算,则将回调分开可以允许它们并行运行。
# -*- coding: utf-8 -*-
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
all_options = {
'美国': ['纽约', '旧金山', '辛辛那提'],
'加拿大': [u'蒙特利尔', '多伦多', '渥太华']
}
app.layout = html.Div([
# 单选框组件
dcc.RadioItems(
id='countries-radio',
options=[{'label': k, 'value': k} for k in all_options.keys()],
value='美国'
),
html.Hr(),
dcc.RadioItems(id='cities-radio'),
html.Hr(),
html.Div(id='display-selected-values')
])
@app.callback(
Output('cities-radio', 'options'),
Input('countries-radio', 'value'))
def set_cities_options(selected_country):
""" 接受国家单选框的值,据此筛选字典数据,输出给城市单选框 """
return [{'label': i, 'value': i} for i in all_options[selected_country]]
@app.callback(
Output('cities-radio', 'value'),
Input('cities-radio', 'options'))
def set_cities_value(available_options):
""" 接受城市单选框的index值,据此筛选数据,输出给城市单选框value值 """
return available_options[0]['value']
@app.callback(
Output('display-selected-values', 'children'),
Input('countries-radio', 'value'),
Input('cities-radio', 'value'))
def set_display_children(selected_country, selected_city):
""" 接受国家和城市单选框的值,并把它们返回到页面上 """
return f'{selected_city} 是 {selected_country} 的一个城市。'
if __name__ == '__main__':
app.run_server(debug=True)
5.2 页面效果
5.3 说明
第一个回调根据第一个dcc.RadioItems组件中的选定值更新第二个组件中的可用选项 dcc.RadioItems。
第二个回调在options属性更改时设置初始值:它将它设置为该options数组中的第一个值。
最后的回调显示value每个组件的选定内容。如果你改变了valuecountriesdcc.RadioItems 组件的 ,Dash 会等到valuecity 组件的 更新后才会调用最终的回调。这可以防止您的回调以不一致的状态被调用,例如 with"America"和"Montréal"。
在某些情况下,您的应用程序中可能有类似“表单”的模式。在这种情况下,您可能想要读取输入组件的值,但只有在用户完成在表单中输入他们的所有信息时,而不是在更改后立即读取。
6.1 两个Input的装饰器 6.1.1 代码# -*- coding: utf-8 -*-
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Input(id="input-1", type="text", value="蒙特利尔"),
dcc.Input(id="input-2", type="text", value="加拿大"),
html.Div(id="number-output"),
])
@app.callback(
Output("number-output", "children"),
Input("input-1", "value"),
Input("input-2", "value"),
)
def update_output(input1, input2):
""" 接受两个输入框的数据,并返回给页面 """
return f'输入框1为 "{input1}" 输入框2为 "{input2}"'
if __name__ == "__main__":
app.run_server(debug=True)
6.1.2 页面效果
6.1.3 说明
在此示例中,每当Input更改描述的任何属性时都会触发回调函数。通过在上面的输入中输入数据来亲自尝试一下。
6.2 Input + State 的装饰器 6.2.1 代码# -*- coding: utf-8 -*-
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Input(id='input-1-state', type='text', value='Montréal'),
dcc.Input(id='input-2-state', type='text', value='Canada'),
html.Button(id='submit-button-state', n_clicks=0, children='提交'),
html.Div(id='output-state')
])
@app.callback(Output('output-state', 'children'),
Input('submit-button-state', 'n_clicks'),
State('input-1-state', 'value'),
State('input-2-state', 'value'))
def update_output(n_clicks, input1, input2):
""" 接受3个输入,只有第一个Input会触发回调,后面两个State不会触发回调 """
return f'''
按钮被点击 {n_clicks} 次,
输入框1是 "{input1}",
输入框2是 "{input2}"
'''
if __name__ == '__main__':
app.run_server(debug=True)
6.2.2 页面效果
6.2.3 说明
在此示例中,更改dcc.Input框中的文本不会触发回调,但单击按钮会触发。dcc.Input即使它们不触发回调函数本身,值的当前值 仍会传递到回调中。
请注意,我们通过侦听组件的n_clicks属性来触发回调html.Button。n_clicks是每次单击组件时都会增加的属性。它在dash_html_components库中的每个组件中都可用 ,但对按钮最有用。



