【Python百日基础系列】Day27 - Dash基本回调

【Python百日基础系列】Day27 - Dash基本回调,第1张

【Python百日基础系列】Day27 - Dash基本回调

文章目录
  • 一、简单的交互式
    • 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 属性)。

一、简单的交互式 1.1 代码
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 进行编程:每当一个单元格发生变化(输入)时,所有依赖于该单元格的单元格(输出)都会自动更新。这称为“反应式编程”,因为输出会自动对输入的变化做出反应。
二、带有图形和滑块的 Dash 应用程序 2.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

# 国外网站,打不开
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 说明
  1. 在这个例子中,dcc.Slider 的"value"属性是应用程序的输入,应用程序的输出是 属性 dcc.Graph的"figure"。每当value的dcc.Slider变化,Dash调用回调函数update_figure用新值。该函数使用这个新值过滤数据帧,构造一个figure对象,并将其返回给 Dash 应用程序。
  2. 我们使用pandas库的应用程序的启动加载我们的数据框: df = pd.read_csv(’…’)。此数据帧df处于应用程序的全局状态,可以在回调函数中读取。
  3. 将数据加载到内存中可能会很昂贵。通过在应用程序启动而不是在回调函数内部加载查询数据,我们确保此 *** 作仅执行一次——当应用程序服务器启动时。当用户访问应用程序或与应用程序交互时,该数据 ( df) 已在内存中。如果可能,昂贵的初始化(如下载或查询数据)应该在应用程序的全局范围内完成,而不是在回调函数内完成。
  4. 回调不会修改原始数据,它只会通过使用 pandas 过滤来创建数据帧的副本。 这很重要: 您的回调不应修改其范围之外的变量。如果您的回调修改了全局状态,那么一个用户的会话可能会影响下一个用户的会话,并且当应用程序部署在多个进程或线程上时,这些修改将不会跨会话共享。
  5. 我们打开转换以layout.transition了解数据集如何随时间演变:转换允许图表从一个状态平滑地更新到下一个状态,就好像它是动画一样。
三、具有多个输入的 Dash 应用程序

在 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属性的当前状态并将它们传递给回调函数。这些回调函数始终保证接收应用程序的更新状态。

四、具有多个输出的 Dash 应用程序 4.1 代码
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 说明

合并输出并不总是一个好主意,即使您可以:

  • 如果输出依赖于一些(但不是全部)相同的输入,那么将它们分开可以避免不必要的更新。
  • 如果输出具有相同的输入但它们对这些输入执行非常不同的计算,则将回调分开可以允许它们并行运行。
五、带有链式回调的 Dash 应用程序 5.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)

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"。

六、带有状态的 Dash 应用程序

在某些情况下,您的应用程序中可能有类似“表单”的模式。在这种情况下,您可能想要读取输入组件的值,但只有在用户完成在表单中输入他们的所有信息时,而不是在更改后立即读取。

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库中的每个组件中都可用 ,但对按钮最有用。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5496162.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-13
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存