【呆鳥譯Py】Dash用戶指南03_交互性簡介

【呆鳥譯Py】Python交互式數(shù)據(jù)分析報(bào)告框架~Dash介紹
【呆鳥譯Py】Dash用戶指南01-02_安裝與應(yīng)用布局
【呆鳥譯Py】Dash用戶指南03_交互性簡介
【呆鳥譯Py】Dash用戶指南04_交互式數(shù)據(jù)圖
【呆鳥譯Py】Dash用戶指南05_使用State進(jìn)行回調(diào)

3. 交互性簡介

本教程的第一部分介紹了Dash應(yīng)用布局 ,第二部分將介紹如何實(shí)現(xiàn)Dash應(yīng)用的交互。
下面先看一個(gè)例子。

Dash應(yīng)用的交互式布局

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()

app.css.append_css(
    {"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

app.layout = html.Div([
    dcc.Input(id='my-id', value='初始值', type='text'),
    html.Div(id='my-div')
])

@app.callback(
    Output(component_id='my-div', component_property='children'),
    [Input(component_id='my-id', component_property='value')]
)
def update_output_div(input_value):
    return '你輸入了 "{}"'.format(input_value)

if __name__ == '__main__':
    app.run_server()
007

在文本框中輸入文字,輸出組件的子項(xiàng)會(huì)立即更新。下面說明這個(gè)例子后臺(tái)的每個(gè)操作步驟:

  1. app.callback裝飾器通過聲明描述應(yīng)用界面的“輸入”與“輸出”項(xiàng)。
  2. Dash應(yīng)用的輸入、輸出項(xiàng)是指定組件的特性。本例中,輸入項(xiàng)是ID名為my-id 組件的value特性。 輸出項(xiàng)是ID名為my-div 組件的children特性。
  3. Dash提供了輸入項(xiàng)特性改變時(shí),能夠自動(dòng)調(diào)用callback裝飾器打包的函數(shù)。輸入項(xiàng)特性的值更新后,可以作為輸入項(xiàng)參數(shù),然后返回該函數(shù)的輸入內(nèi)容。
  4. component_idcomponent_property 關(guān)鍵字是可選的,這些對(duì)象只有兩個(gè)參數(shù)。本例中為了便于理解,列出了這兩個(gè)關(guān)鍵字,正常情況下,為了讓代碼簡明、易讀,可以省略這兩個(gè)關(guān)鍵字。
  5. 不要混淆dash.dependencies.Inputdash_core_components.Input對(duì)象。前者只在回調(diào)函數(shù)中使用,后者才是真正的組件。
  6. 不要在layout中設(shè)置 my-div組件的children特性。Dash應(yīng)用啟動(dòng)時(shí)會(huì)自動(dòng)調(diào)用所有回調(diào)函數(shù),獲取輸入組件中的初始值,使之轉(zhuǎn)化為輸出組件的初始狀態(tài)。本例中,如果指定了html.Div(id='my-div', children='Hello world')等內(nèi)容,應(yīng)用啟動(dòng)時(shí)會(huì)被覆蓋。

這種方式類似于用Excel編程,單元格的內(nèi)容發(fā)生變化時(shí),所有與該單元格相關(guān)的單元格都會(huì)自動(dòng)更新,這就是所謂的“響應(yīng)式編程”。

請(qǐng)記住如何用關(guān)鍵字參數(shù)描述組件,這點(diǎn)非常重要。通過Dash的交互性,可以使用回調(diào)函數(shù)動(dòng)態(tài)更新這些特性。Dash可以更新組件的children特性從而顯示更新的文本,也可以通過dcc.Graph組件的figure特性展示更新的數(shù)據(jù),還可以更新組件的style,甚至是dcc.Dropdown組件的options


下面這個(gè)例子通過dcc.Slider更新dcc.Gragh

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd

df = pd.read_csv(
    'https://raw.githubusercontent.com/plotly/'
    'datasets/master/gapminderDataFiveYear.csv')

app = dash.Dash()

app.css.append_css(
    {"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

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(),
        step=None,
        marks={str(year): str(year) for year in df['year'].unique()}
    )
])

@app.callback(
    dash.dependencies.Output('graph-with-slider', 'figure'),
    [dash.dependencies.Input('year-slider', 'value')])
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]
    traces = []
    for i in filtered_df.continent.unique():
        df_by_continent = filtered_df[filtered_df['continent'] == i]
        traces.append(go.Scatter(
            x=df_by_continent['gdpPercap'],
            y=df_by_continent['lifeExp'],
            text=df_by_continent['country'],
            mode='markers',
            opacity=0.7,
            marker={
                'size': 15,
                'line': {'width': 0.5, 'color': 'white'}
            },
            name=i
        ))

    return {
        'data': traces,
        'layout': go.Layout(
            xaxis={'type': 'log', 'title': '人均GDP'},
            yaxis={'title': '平均壽命', 'range': [20, 90]},
            margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
            legend={'x': 0, 'y': 1},
            hovermode='closest'
        )
    }

if __name__ == '__main__':
    app.run_server()
008

本例中,Slidervalue特性是Dash應(yīng)用的輸入項(xiàng),輸出項(xiàng)是Graphfigure特性。Slidervalue變更時(shí),Dash調(diào)用update_figure回調(diào)函數(shù)獲取更新值。這個(gè)函數(shù)會(huì)篩選DataFrame生成新的值,創(chuàng)建figure 對(duì)象,并將其返回至Dash應(yīng)用。

以下是本例的核心內(nèi)容:

  1. 使用Pandas導(dǎo)入并篩選內(nèi)存中的數(shù)據(jù)集;
  2. 應(yīng)用啟動(dòng)時(shí),加載DataFrame:df = pd.read_csv('...')。在這個(gè)Dash應(yīng)用里,DataFrame df是全局的,可以被回調(diào)函數(shù)讀取。
  3. 將數(shù)據(jù)加載至內(nèi)存并進(jìn)行計(jì)算的代價(jià)很高,所以要在應(yīng)用啟動(dòng)時(shí)載入數(shù)據(jù),避免在回調(diào)函數(shù)中加載數(shù)據(jù),確保用戶訪問或與應(yīng)用交互時(shí),數(shù)據(jù)(即df)已經(jīng)載入至內(nèi)存。盡量在應(yīng)用的全局范圍內(nèi)下載或查詢數(shù)據(jù)等大規(guī)模數(shù)據(jù)初始化操作,避免在回調(diào)函數(shù)里進(jìn)行這類操作。
  4. 回調(diào)函數(shù)不會(huì)修改原始數(shù)據(jù),只是通過Pandas的過濾器來篩選數(shù)據(jù),并創(chuàng)建DataFrame的副本。這點(diǎn)非常重要:不要在回調(diào)函數(shù)范圍之外更改變量。如果在全局狀態(tài)下調(diào)整回調(diào)函數(shù),某一用戶的會(huì)話就可能影響下一用戶的會(huì)話,特別是應(yīng)用部署在多進(jìn)程或多線程的環(huán)境時(shí),這些修改將導(dǎo)致跨會(huì)話數(shù)據(jù)分享出現(xiàn)問題。

多重輸入

任一Dash**輸出項(xiàng) 都可對(duì)應(yīng)多個(gè)輸入項(xiàng) **。下面這個(gè)例子為某個(gè)輸出組件(Graph 組件的figure特性)綁定了5個(gè)輸入項(xiàng)(2個(gè)下拉菜單Dropdown 組件,兩個(gè)單選按鈕RadioItems組件,還有1個(gè)滑動(dòng)條Slider組件)。注意?在第2個(gè)參數(shù)里,app.callback 是如何在一個(gè)列表中列出5個(gè)dash.dependenceies.Input輸入項(xiàng)的。

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd

app = dash.Dash()

app.css.append_css(
    {"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
    
df = pd.read_csv(
    'https://gist.githubusercontent.com/chriddyp/'
    'cb5392c35661370d95f300086accea51/raw/'
    '8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
    'indicators.csv')

available_indicators = df['Indicator Name'].unique()

app.layout = html.Div([
    html.Div([

        html.Div([
            dcc.Dropdown(
                id='xaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Fertility rate, total (births per woman)'
            ),
            dcc.RadioItems(
                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(
                id='yaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Life expectancy at birth, total (years)'
            ),
            dcc.RadioItems(
                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(),
        step=None,
        marks={str(year): str(year) for year in df['Year'].unique()}
    )
])

@app.callback(
    dash.dependencies.Output('indicator-graphic', 'figure'),
    [dash.dependencies.Input('xaxis-column', 'value'),
     dash.dependencies.Input('yaxis-column', 'value'),
     dash.dependencies.Input('xaxis-type', 'value'),
     dash.dependencies.Input('yaxis-type', 'value'),
     dash.dependencies.Input('year--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type,
                 year_value):
    dff = df[df['Year'] == year_value]

    return {
        'data': [go.Scatter(
            x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
            text=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
            mode='markers',
            marker={
                'size': 15,
                'opacity': 0.5,
                'line': {'width': 0.5, 'color': 'white'}
            }
        )],
        'layout': go.Layout(
            xaxis={
                'title': xaxis_column_name,
                'type': 'linear' if xaxis_type == 'Linear' else 'log'
            },
            yaxis={
                'title': yaxis_column_name,
                'type': 'linear' if yaxis_type == 'Linear' else 'log'
            },
            margin={'l': 40, 'b': 40, 't': 10, 'r': 0},
            hovermode='closest'
        )
    }

if __name__ == '__main__':
    app.run_server()
009

本例中,DropdownSlider、RadioItems這些組件的value特性變化時(shí),就會(huì)調(diào)用update_graph函數(shù)。

update_graph的輸入?yún)?shù)就是這些組件Input特性的當(dāng)前值或更新值,其優(yōu)先級(jí)為它們的指定順序。

雖然同一時(shí)間內(nèi),只能修改一個(gè)Input特性(比如用戶一次只能修改一個(gè)下拉菜單的值),但時(shí),Dash會(huì)采集所有綁定組件Input 特性的當(dāng)前值,并通過函數(shù)傳遞給回調(diào)函數(shù),確??偰塬@得該應(yīng)用當(dāng)前狀態(tài)的值。

下面在這個(gè)例子的基礎(chǔ)上加入多重輸出。

多重輸出

一個(gè)Dash回調(diào)函數(shù)僅能更新一個(gè)輸出屬性。要想實(shí)現(xiàn)多重輸出,需要編寫多個(gè)函數(shù)。

import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash('')

app.css.append_css(
    {"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

app.layout = html.Div([
    dcc.RadioItems(
        id='dropdown-a',
        options=[{'label': i, 'value': i} for i in ['北京', '天津', '上海']],
        value='北京'
    ),
    html.Div(id='output-a'),

    dcc.RadioItems(
        id='dropdown-b',
        options=[{'label': i, 'value': i} for i in ['東城區(qū)', '西城區(qū)', '朝陽區(qū)']],
        value='朝陽區(qū)'
    ),
    html.Div(id='output-b')

])

@app.callback(
    dash.dependencies.Output('output-a', 'children'),
    [dash.dependencies.Input('dropdown-a', 'value')])
def callback_a(dropdown_value):
    return '已選中"{}"'.format(dropdown_value)

@app.callback(
    dash.dependencies.Output('output-b', 'children'),
    [dash.dependencies.Input('dropdown-b', 'value')])
def callback_b(dropdown_value):
    return '已選中"{}"'.format(dropdown_value)

if __name__ == '__main__':
    app.run_server()
010

可以將輸入與輸出項(xiàng)鏈在一起:一個(gè)回調(diào)函數(shù)的輸出項(xiàng)可以是另一個(gè)回調(diào)函數(shù)的輸入項(xiàng)。

這個(gè)模式可以用來創(chuàng)建動(dòng)態(tài)UI,一個(gè)輸入組件可以更新另一個(gè)輸入組件的可用選項(xiàng),請(qǐng)看下面的例子。

# -*- coding: utf-8 -*-
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(__name__)

app.css.append_css(
    {"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

all_options = {
    '北京': ['東城區(qū)', '西城區(qū)', '朝陽區(qū)'],
    '上海': ['黃浦區(qū)', '靜安區(qū)', '普陀區(qū)']
}
app.layout = html.Div([
    dcc.RadioItems(
        id='countries-dropdown',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='北京'
    ),

    html.Hr(),

    dcc.RadioItems(id='cities-dropdown'),

    html.Hr(),

    html.Div(id='display-selected-values')
])

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'options'),
    [dash.dependencies.Input('countries-dropdown', 'value')])
def set_cities_options(selected_country):
    return [{'label': i, 'value': i} for i in all_options[selected_country]]

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'value'),
    [dash.dependencies.Input('cities-dropdown', 'options')])
def set_cities_value(available_options):
    return available_options[0]['value']

@app.callback(
    dash.dependencies.Output('display-selected-values', 'children'),
    [dash.dependencies.Input('countries-dropdown', 'value'),
     dash.dependencies.Input('cities-dropdown', 'value')])
def set_display_children(selected_country, selected_city):
    return '{}是{}的轄區(qū)。'.format(
        selected_city, selected_country,
    )

if __name__ == '__main__':
    app.run_server(debug=True)
011

第二個(gè)單選按鈕RadioItems的選項(xiàng)基于第一個(gè)回調(diào)函數(shù)傳遞的單選按鈕RadioItems中選擇的值。

第二個(gè)回調(diào)函數(shù)設(shè)置了options特性改變時(shí)的初始值:它將自身設(shè)置為options數(shù)組中的第一個(gè)值。

最后的回調(diào)函數(shù)顯示了每個(gè)組件中的可選值value。如果改變城市單選按鈕RadioItems的值value,Dash會(huì)等城區(qū)單選按鈕RadioItems 的值value更新后,再調(diào)用最終的回調(diào)函數(shù)。

小結(jié)

本節(jié)介紹了Dash回調(diào)函數(shù)的基本概念。Dash應(yīng)用是基于下述簡單但強(qiáng)大的原則進(jìn)行構(gòu)建的:可以通過響應(yīng)式與函數(shù)式的Python回調(diào)函數(shù)自定義聲明式的UI。聲明式組件中的每個(gè)元素屬性都可以通過回調(diào)函數(shù)和屬性子集進(jìn)行更新,比如dcc.Dropdownvalue特性,這樣用戶就可以在交互界面中進(jìn)行編輯。

下一章將闡述如何使用上述規(guī)則,通過dash_core-componets.Graph組件讓Dash應(yīng)用響應(yīng)頁面上的圖形交互功能。

【呆鳥譯Py】Python交互式數(shù)據(jù)分析報(bào)告框架~Dash介紹
【呆鳥譯Py】Dash用戶指南01-02_安裝與應(yīng)用布局
【呆鳥譯Py】Dash用戶指南03_交互性簡介
【呆鳥譯Py】Dash用戶指南04_交互式數(shù)據(jù)圖
【呆鳥譯Py】Dash用戶指南05_使用State進(jìn)行回調(diào)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容