【Dash系列】Python的Web可視化框架Dash(1)---簡介
【Dash系列】Python的Web可視化框架Dash(2)---安裝
【Dash系列】Python的Web可視化框架Dash(3)---布局設置
【Dash系列】Python的Web可視化框架Dash(4)---基本回調
【Dash系列】Python的Web可視化框架Dash(5)---狀態和預更新
【Dash系列】Python的Web可視化框架Dash(6)---交互和過濾
【Dash系列】Python的Web可視化框架Dash(7)---回調共享
【Dash系列】Python的Web可視化框架Dash(8)---核心組件
本節介紹如何實現Dash應用的回調,先導入本節用到的所有包
import pandas as pd
import plotly.graph_objs as go
import dash
import dash_core_components as dcc # 交互式組件
import dash_html_components as html # 代碼轉html
from dash.dependencies import Input, Output # 回調
from jupyter_plotly_dash import JupyterDash # Jupyter中的Dash
一、交互式布局
(一) 代碼
app = JupyterDash('Dash Layout')
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)
app
(二)效果圖
(三)說明
在文本框中輸入文字,輸出組件的子項會立即更新。效果圖顯示,第一個設置的默認值,后兩個,分布輸入了數值100和字符串Dash;
app.callback 裝飾器通過聲明,描述應用程序界面的“輸入”與“輸出”項;
Dash中應用程序的【輸入】和【輸出】只是特定組件的屬性。本例中,輸入項是ID名為my-id 組件的value特性。 輸出項是ID名為my-div 組件的children特性;
當輸入項組件的屬性值,發生更改時,將自動調用callback裝飾器打包的函數,將更新的內容,作為輸入項參數,返回函數的輸入內容,更新輸出項組件的屬性值;
【輸入項】和【輸出項】對象的關鍵字參數 component_id 和 component_property,都是可選的。本例中,為了便于理解,列出了這兩個關鍵字,通常情況下,為了讓代碼簡明、易讀,可以省略這兩個關鍵字;
不要混淆 dash.dependencies.Input 與dash_core_components.Input對象。前者只在回調函數中使用,后者才是真正的組件;
Dash應用程序啟動時,會自動使用輸入組件的初始值,調用所有的回調函數,以填充輸出組件的初始值。所以,不要在layout中設置 my-div組件的children特性,本例中,如果指定了 html.Div(id='my-div', children='Hello world') 的內容,應用啟動時會被覆蓋。這種方式類似于Microsoft Excel編程:當單元格的內容發生變化時,依賴于該單元格的所有單元格的內容,都將自動更新。這稱為 “反應式編程” (Reactive Programming) 。
二、滑動條
(一) 代碼
# 數據源
df = pd.read_csv(
'https://raw.githubusercontent.com/plotly/'
'datasets/master/gapminderDataFiveYear.csv')
# 設置Dash應用程序
app = JupyterDash('Slider Update Gragh')
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]
traces = []
for val in filtered_df.continent.unique():
df_by_continent = filtered_df[filtered_df.continent == val]
traces.append(go.Scatter(
x = df_by_continent['gdpPercap'],
y = df_by_continent['lifeExp'],
text = df_by_continent['country'],
name = val,
mode = 'markers',
opacity = 0.8,
marker = dict(size = 15, line = dict(width = 0.5, color = 'white'))
))
fig = dict(
data = traces,
layout = go.Layout(
xaxis = dict(type = 'log', title = '人均GDP'),
yaxis = dict(title = '平均壽命', range = [20, 90]),
margin = dict(l = 40, b = 40, t = 10, r = 10),
hovermode = 'closest'
)
)
return fig
app
(二) 效果圖
(三)說明
本例中,app的輸入是 Slider 的屬性 value,app的輸出是 Graph 的屬性 figure。當 Slider 的 value 變化時,Dash用新值調用回調函數 update_figure,該函數使用此新值過濾數據框,構造 figure 對象,并將其返回到Dash應用程序中,作為輸出;
使用關鍵字參數進行組件描述,很重要。通過Dash交互性,使用回調函數,可以動態地更新這些特性。如:更新組件的 children 屬性從而更新文本內容、更新 dcc.Graph 組件的 figure 屬性從而更新數據、更新組件的 style 屬性從而更新畫布樣式、更新 dcc.Dropdown 組件的 options 從而更新下拉菜單;
將數據加載至內存并進行計算的代價很高,所以盡量在應用的全局范圍內下載或查詢數據,避免在回調函數里進行這類操作,確保用戶訪問或與應用交互時,數據(df)已經載入至內存。本例中 df 獲取的數據是全局的,可以被回調函數讀取;
回調函數不會修改原始數據,只是通過Pandas的過濾器來篩選數據,并創建DataFrame的副本。這點非常重要:不要在回調函數范圍之外更改變量。如果在全局狀態下調整回調函數,某一用戶的會話就可能影響下一用戶的會話,特別是應用部署在多進程或多線程的環境時,這些修改可能會導致跨會話數據分享出現問題;
三、多重輸入
(一) 代碼
# 數據
df = pd.read_csv(
'https://gist.githubusercontent.com/chriddyp/'
'cb5392c35661370d95f300086accea51/raw/'
'8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
'indicators.csv')
# 設置Dash
app = JupyterDash('many input')
app.layout = html.Div([
html.Div([
html.Div([
dcc.Dropdown(
id = 'xaxis-column',
options = [{'label': i, 'value': i} for i in df['Indicator Name'].unique()],
value = 'Fertility rate, total (births per woman)'),
dcc.RadioItems(
id = 'xaxis-type',
options = [{'label': i, 'value': i} for i in ['線性', '日志']],
value = '線性',
labelStyle = dict(display = 'inline-block'))],
style = dict(width = '48%', display = 'inline-block')
),
html.Div([
dcc.Dropdown(
id = 'yaxis-column',
options = [{'label': i, 'value': i} for i in df['Indicator Name'].unique()],
value = 'Life expectancy at birth, total (years)'),
dcc.RadioItems(
id = 'yaxis-type',
options = [{'label': i, 'value': i} for i in ['線性', '日志']],
value = '線性',
labelStyle = dict(display = 'inline-block'))],
style = dict(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):
dff = df[df['Year'] == year_value]
result = dict(
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 = dict(title = xaxis_column_name, type = 'linear' if xaxis_type == '線性' else '日志'),
yaxis = dict(title = yaxis_column_name, type = 'linear' if yaxis_type == '線性' else '日志'),
margin = {'l': 40, 'b': 40, 't': 10, 'r': 0},
hovermode = 'closest'
)
)
return result
app
(二) 效果圖
(三)說明
在Dash中,任何“ Output”都可以有多個“ Input”組件;
本例中,將五個輸入組件:2個下拉菜單(Dropdown)組件、2個單選按鈕(RadioItems)組件、1個滑動條(Slider)組件,綁定到1個輸出組件(Graph 組件的figure特性);
回調函數的第二個參數,列表中列舉了所有的五個輸入項dash.dependencies.Input ;
示例中,Dropdown、Slider、RadioItems這些組件的value特性變化時,就會調用update_graph函數;
update_graph函數的輸入參數,就是這些組件Input特性的當前值或更新值,按其指定的順序排列;
即使每次只修改一個Input特性,比如用戶一次只能修改一個下拉菜單的值,但Dash會采集所有綁定組件Input 特性的當前值,并傳遞給回調函數,確??偰塬@得該應用當前狀態的值。
四、多重輸出
(一) 代碼
app = JupyterDash('many output')
app.layout = html.Div([
dcc.RadioItems(
id = 'button-a',
options = [{'label': i, 'value': i} for i in ['北京', '天津', '上海']],
value = '北京'),
html.Div(id = 'output-a'),
dcc.RadioItems(
id = 'button-b',
options = [{'label': i, 'value': i} for i in ['東城區', '西城區', '朝陽區']],
value = '朝陽區'),
html.Div(id = 'output-b')
])
@app.callback(
Output('output-a', 'children'),
[Input('button-a', 'value')]
)
def callback_a(button_value):
return f"已選中{button_value}"
@app.callback(
Output('output-b', 'children'),
[Input('button-b', 'value')]
)
def callback_a(button_value):
return f"已選中{button_value}"
app
(二) 效果圖
(三)說明
一個Dash回調函數只能更新一個輸出屬性。要想實現多重輸出,需要編寫多個函數;
具體方法:將需要更新的所有屬性,作為列表添加到裝飾器中,并從回調中返回多個輸出項。如果兩個輸出依賴于相同的計算密集型中間結果,例如慢速數據庫查詢,推薦使用該方法;
組合輸出并不總是一個好主意:1)如果輸出依賴于某些但不是所有相同的輸入,則將它們分開可以避免不必要的更新;2)如果它們具有相同的輸入,但使用這些輸入進行獨立計算,則將回調分開,可以實現并行運行它們;
五、鏈式回調
(一) 代碼
app = JupyterDash('Chained Callbacks')
all_options = {
'北京': ['東城區', '西城區', '朝陽區'],
'上海': ['黃浦區', '靜安區', '普陀區']
}
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(
Output('cities-dropdown', 'options'),
[Input('countries-dropdown', 'value')])
def set_cities_options(select_country):
return [{'label': i, 'value': i} for i in all_options[select_country]]
@app.callback(
Output('cities-dropdown', 'value'),
[Input('cities-dropdown', 'options')])
def set_cities_value(available_options):
return available_options[0]['value']
@app.callback(
Output('display-selected-values', 'children'),
[Input('countries-dropdown', 'value'),
Input('cities-dropdown', 'value')])
def set_display_children(select_country, select_city):
return f"{select_city}是{select_country}的轄區。"
app
(二) 效果圖
(三)說明
- 鏈式回調:將輸出和輸入鏈接在一起,即一個回調函數的輸出是另一個回調函數的輸入;
- 此模式用于創建動態UI,其中一個輸入組件更新下一個輸入組件的可用選項;
- 第二個單選按鈕RadioItems的選項,基于第一個回調函數傳遞的單選按鈕RadioItems中選擇的值;
- 第二個回調函數設置了options特性改變時的初始值:將自身設置為options數組中的第一個值;
- 最后的回調函數,顯示了每個組件中的可選內容。如果更改了城市單選按鈕RadioItems組件的value屬性,則Dash將等待,直到value更新狀態組件后,再調用最后的回調函數。
六、小結
- Dash應用是基于下述簡單但強大的原則進行構建的:通過響應式與函數式的Python回調函數,自定義聲明式的UI;
- 聲明式組件中的每個元素屬性,都可以通過回調函數和屬性子集進行更新,比如dcc.Dropdown的value特性,這樣用戶就可以在交互界面中進行編輯。