【呆鳥譯Py】Dash用戶指南03_交互性簡(jiǎn)介

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

3. 交互性簡(jiǎn)介

本教程的第一部分介紹了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ì)立即更新慌盯。下面說(shuō)明這個(gè)例子后臺(tái)的每個(gè)操作步驟:

  1. app.callback裝飾器通過(guò)聲明描述應(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)鍵字边坤,正常情況下,為了讓代碼簡(jiǎ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)非常重要为障。通過(guò)Dash的交互性晦闰,可以使用回調(diào)函數(shù)動(dòng)態(tài)更新這些特性。Dash可以更新組件的children特性從而顯示更新的文本鳍怨,也可以通過(guò)dcc.Graph組件的figure特性展示更新的數(shù)據(jù)呻右,還可以更新組件的style,甚至是dcc.Dropdown組件的options鞋喇。


下面這個(gè)例子通過(guò)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ù),確保用戶訪問(wèn)或與應(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ù)酝陈,只是通過(guò)Pandas的過(guò)濾器來(lái)篩選數(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)問(wè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

本例中,Dropdown渗常、Slider壮不、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)前值踢俄,并通過(guò)函數(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ū)', '朝陽(yáng)區(qū)']],
        value='朝陽(yáng)區(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è)模式可以用來(lái)創(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ū)', '朝陽(yáng)區(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)用是基于下述簡(jiǎn)單但強(qiáng)大的原則進(jìn)行構(gòu)建的:可以通過(guò)響應(yīng)式與函數(shù)式的Python回調(diào)函數(shù)自定義聲明式的UI。聲明式組件中的每個(gè)元素屬性都可以通過(guò)回調(diào)函數(shù)和屬性子集進(jìn)行更新倔监,比如dcc.Dropdownvalue特性直砂,這樣用戶就可以在交互界面中進(jìn)行編輯。

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谱秽,隨后出現(xiàn)的幾起案子洽蛀,更是在濱河造成了極大的恐慌,老刑警劉巖疟赊,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郊供,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡近哟,警方通過(guò)查閱死者的電腦和手機(jī)驮审,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吉执,“玉大人疯淫,你說(shuō)我怎么就攤上這事〈撩担” “怎么了熙掺?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)咕宿。 經(jīng)常有香客問(wèn)我币绩,道長(zhǎng),這世上最難降的妖魔是什么荠列? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任类浪,我火速辦了婚禮,結(jié)果婚禮上肌似,老公的妹妹穿的比我還像新娘费就。我一直安慰自己,他們只是感情好川队,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布力细。 她就那樣靜靜地躺著睬澡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪眠蚂。 梳的紋絲不亂的頭發(fā)上煞聪,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音逝慧,去河邊找鬼昔脯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛笛臣,可吹牛的內(nèi)容都是我干的云稚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼沈堡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼静陈!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起诞丽,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鲸拥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后僧免,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刑赶,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年猬膨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了角撞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勃痴,死狀恐怖谒所,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沛申,我是刑警寧澤劣领,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站铁材,受9級(jí)特大地震影響尖淘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜著觉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一村生、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饼丘,春花似錦趁桃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)油啤。三九已至,卻和暖如春蟀苛,著一層夾襖步出監(jiān)牢的瞬間益咬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工帜平, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幽告,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓裆甩,卻偏偏與公主長(zhǎng)得像评腺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淑掌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容