- 一、简单的交互式
- 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库中的每个组件中都可用 ,但对按钮最有用。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)