文章目錄
簡介
Dash 是一款構建web應用的Python框架,建立在 Plotly.js, React 和 Flask 之上,將現代UI元素(如下拉框、滑塊和圖形)直接與Python代碼綁定。
App | Description |
---|---|
將下拉菜單綁定到D3.js的繪圖 | |
Dash代碼是聲明式和響應式的,更容易構建複雜交互程序 | |
Dash使用 Plotly.js 繪圖,支持超過 35 種類型,包括地圖 | |
Dash不只是儀表盤,可以完全控制應用的外觀。如圖是一種PDF風格的Dash應用 |
安裝
pip install dash
初試
import dash
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
df = pd.DataFrame({'x': [1, 2, 3], 'SF': [4, 1, 2], 'Montreal': [2, 4, 5]})
fig = px.bar(df, x='x', y=['SF', 'Montreal'], barmode='group')
app.layout = html.Div(children=[
html.H1(children='Hello Dash'),
html.Div(children='Dash: 一款Python web應用框架'),
dcc.Graph(
id='example-graph',
figure=fig
)
])
if __name__ == '__main__':
app.run_server(debug=True)
應用構成
Dash應用由兩部分組成:
- layout,外觀
- callback,交互
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
app.layout = html.Div([
html.H1('智能聊天機器人'),
dcc.Input(id='my-id', value='在嗎?', type='text'),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'), # 輸出給id爲my-div的children
[Input(component_id='my-id', component_property='value')] # 輸入來自id爲my-id的value
)
def update_output_div(input_value):
'''AI核心代碼,估值1個億'''
return input_value.replace('嗎', '').replace('?', '!').replace('?', '!')
if __name__ == '__main__':
app.run_server(debug=True)
設置CSS
聲明參數external_stylesheets
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
熱更新
app.run_server(debug=True)
默認激活Dash的熱更新,一旦修改代碼,Dash會自動刷新瀏覽器
取消熱更新:app.run_server(dev_tools_hot_reload=False)
插入HTML
dash_html_components
模塊包含HTML組件及關鍵字參數
import dash
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
colors = {
'background': '#111111',
'text': '#7FDBFF'
}
df = pd.DataFrame({'x': [1, 2, 3], 'SF': [4, 1, 2], 'Montreal': [2, 4, 5]})
fig = px.bar(df, x='x', y=['SF', 'Montreal'], barmode='group')
fig.update_layout(plot_bgcolor=colors['background'], paper_bgcolor=colors['background'], font_color=colors['text'])
app.layout = html.Div(
style={'backgroundColor': colors['background']},
children=[
html.H1(
children='Hello Dash',
style={
'textAlign': 'center',
'color': colors['text']
}
),
html.Div(
children='Dash: 一款Python web應用框架',
style={
'textAlign': 'center',
'color': colors['text']
}
),
dcc.Graph(
id='example-graph-2',
figure=fig
)
])
if __name__ == '__main__':
app.run_server(debug=True)
可重用組件
,state,total exports,beef,pork,poultry,dairy,fruits fresh,fruits proc,total fruits,veggies fresh,veggies proc,total veggies,corn,wheat,cotton
0,Alabama,1390.63,34.4,10.6,481.0,4.06,8.0,17.1,25.11,5.5,8.9,14.33,34.9,70.0,317.61
1,Alaska,13.31,0.2,0.1,0.0,0.19,0.0,0.0,0.0,0.6,1.0,1.56,0.0,0.0,0.0
2,Arizona,1463.17,71.3,17.9,0.0,105.48,19.3,41.0,60.27,147.5,239.4,386.91,7.3,48.7,423.95
3,Arkansas,3586.02,53.2,29.4,562.9,3.53,2.2,4.7,6.88,4.4,7.1,11.45,69.5,114.5,665.44
4, California,16472.88,228.7,11.1,225.4,929.95,2791.8,5944.6,8736.4,803.2,1303.5,2106.79,34.6,249.3,1064.95
5,Colorado,1851.33,261.4,66.0,14.0,71.94,5.7,12.2,17.99,45.1,73.2,118.27,183.2,400.5,0.0
6,Connecticut,259.62,1.1,0.1,6.9,9.49,4.2,8.9,13.1,4.3,6.9,11.16,0.0,0.0,0.0
7,Delaware,282.19,0.4,0.6,114.7,2.3,0.5,1.0,1.53,7.6,12.4,20.03,26.9,22.9,0.0
8,Florida,3764.09,42.6,0.9,56.9,66.31,438.2,933.1,1371.36,171.9,279.0,450.86,3.5,1.8,78.24
9,Georgia,2860.84,31.0,18.9,630.4,38.38,74.6,158.9,233.51,59.0,95.8,154.77,57.8,65.4,1154.07
用Python編寫HTML標記可創建複雜的可重用組件,而無需切換上下文
import dash
import pandas as pd
import dash_html_components as html
df = pd.read_csv('美國農業出口數據.csv')
def generate_table(dataframe, max_rows=10):
'''生成表格'''
return html.Table([
html.Thead(
html.Tr([html.Th(col) for col in dataframe.columns])
),
html.Tbody([
html.Tr([
html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
]) for i in range(min(len(dataframe), max_rows))
])
])
app = dash.Dash()
app.layout = html.Div(
children=[
html.H4(
children='US Agriculture Exports (2011)'
),
generate_table(df)
])
if __name__ == '__main__':
app.run_server(debug=True)
更多可視化
dash_core_components
模塊的 Graph
使用開源JavaScript庫 plotly.js,支持超過35種圖表類型,並以矢量SVG和高性能WebGL呈現。
import dash
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
df = pd.read_csv('GDP與人均壽命.csv')
fig = px.scatter(df, x='gdp per capita', y='life expectancy',
size='population', color='continent', hover_name='country',
log_x=True, size_max=60)
app.layout = html.Div([
dcc.Graph(
id='life-exp-vs-gdp',
figure=fig
)
])
if __name__ == '__main__':
app.run_server(debug=True)
圖具有交互性和響應性:
- 懸停:看值
- 單擊:跟蹤
- 雙擊:復原
- Shift+拖動:放大
Markdown
dash_core_components
模塊的 Markdown
import dash
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
markdown_text = '''
# 你的Markdown代碼
'''
app.layout = html.Div([
dcc.Markdown(children=markdown_text)
])
if __name__ == '__main__':
app.run_server(debug=True)
Dash使用Markdown通用標記規範,渲染效果可對比 Cmd Markdown
本人測試不通過:
- 註腳
- LaTeX 數學公式
- 流程圖、序列圖、甘特圖
核心組件
dash_core_components
模塊提供了一系列高級組件,如下拉菜單、圖表、Markdown塊等,所有組件均可聲明式描述
查看所有可用組件:Dash Core Components
import dash
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
html.Label('Dropdown 單選下拉框'),
dcc.Dropdown(
options=[
{'label': '北京', 'value': 'BJ'},
{'label': '上海', 'value': 'SH'},
{'label': '廣州', 'value': 'GZ'}
],
value='GZ' # 默認值
),
html.Br(), # 換行
html.Label('Dropdown 多選下拉框'),
dcc.Dropdown(
options=[
{'label': '北京', 'value': 'BJ'},
{'label': '上海', 'value': 'SH'},
{'label': '廣州', 'value': 'GZ'}
],
value=['BJ', 'GZ'],
multi=True # 多選
),
html.Br(),
html.Label('RadioItems 單選按鈕'),
dcc.RadioItems(
options=[
{'label': '北京', 'value': 'BJ'},
{'label': '上海', 'value': 'SH'},
{'label': '廣州', 'value': 'GZ'}
],
value='GZ'
),
html.Br(),
html.Label('Checklist 複選按鈕'),
dcc.Checklist(
options=[
{'label': '北京', 'value': 'BJ'},
{'label': '上海', 'value': 'SH'},
{'label': '廣州', 'value': 'GZ'}
],
value=['BJ', 'GZ']
),
html.Br(),
html.Label('Input 輸入框'),
html.Br(),
dcc.Input(value='廣州', type='text'),
html.Br(),
html.Br(),
html.Label('Slider 滑動條'),
dcc.Slider(
min=0,
max=9,
marks={i: str(i) for i in range(10)}, # 傳入字典作爲標記顯示
value=3,
),
html.Br(),
])
if __name__ == '__main__':
app.run_server(debug=True)
回調函數
通過修飾器 app.callback
定義 Input
和 Output
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
app.layout = html.Div([
html.H1('智能聊天機器人'),
dcc.Input(id='my-id', value='在嗎?', type='text'),
html.Div(id='my-div')
])
@app.callback(
Output(component_id='my-div', component_property='children'), # 輸出給id爲my-div的children
[Input(component_id='my-id', component_property='value')] # 輸入來自id爲my-id的value
)
def update_output_div(input_value):
'''AI核心代碼,估值1個億'''
return input_value.replace('嗎', '').replace('?', '!').replace('?', '!')
if __name__ == '__main__':
app.run_server(debug=True)
滑塊更新圖表
import dash
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
df = pd.read_csv('每五年GDP與人均壽命.csv')
app = dash.Dash()
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=60)
fig.update_layout(transition_duration=500) # 過渡時間
return fig
if __name__ == '__main__':
app.run_server(debug=True)
1952年中國人均GDP只有400,平均壽命44歲。50年後,人均GDP就飆升到3119,平均壽命達到72歲。
多個輸入
import dash
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
df = pd.read_csv('國家及其指標.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(),
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]
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')
fig.update_xaxes(title=xaxis_column_name, type='linear' if xaxis_type == 'Linear' else 'log')
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)
查看各國不同指標間的關係
每個女人生育幾個孩子和平均壽命間的關係
1962年香港每個女人平均生5個孩子,平均壽命68歲 → 2007年平均生1個孩子,平均壽命82歲
多個輸出
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
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(['x', html.Sup('x')]), html.Td(id='x^x')]),
]),
])
@app.callback(
[Output('square', 'children'),
Output('cube', 'children'),
Output('x^x', 'children')],
[Input('num-multi', 'value')])
def callback_a(x):
return x ** 2, x ** 3, x ** x
if __name__ == '__main__':
app.run_server(debug=True)
注意:最好分開多個寫
鏈式回調
一個回調函數的輸出是另一個回調函數的輸入
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
all_options = {
'中國': ['北京', '上海', '廣州'],
'美國': ['紐約', '舊金山']
}
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):
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 '{} 是 {} 的城市'.format(selected_city, selected_country)
if __name__ == '__main__':
app.run_server(debug=True)
狀態
當用戶輸入完成後纔回調
修飾器 app.callback
定義的 Input
改爲 State
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
app = dash.Dash()
app.layout = html.Div([
dcc.Input(id='input-1-state', type='text', value='初始值1'),
dcc.Input(id='input-2-state', type='text', value='初始值2'),
html.Button(id='submit-button-state', n_clicks=0, children='Submit'),
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):
return '點擊了 {} 次:{}, {}'.format(n_clicks, input1, input2)
if __name__ == '__main__':
app.run_server(debug=True)
基本數據交互
修飾器 app.callback
定義的 Input
添加參數:
- hoverData:懸停
- clickData:點擊
- selectedData:選擇
- relayoutData:重新佈局
import json
import dash
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}
df = pd.DataFrame({
'x': [1, 2, 1, 2],
'y': [1, 2, 3, 4],
'customdata': [1, 2, 3, 4],
'fruit': ['apple', 'apple', 'orange', 'orange']
})
fig = px.scatter(df, x='x', y='y', color='fruit', custom_data=['customdata'])
fig.update_layout(clickmode='event+select')
fig.update_traces(marker_size=20)
app.layout = html.Div([
dcc.Graph(
id='basic-interactions',
figure=fig
),
html.Div(className='row', children=[
html.Div([
dcc.Markdown('**懸停 hoverData**'),
html.Pre(id='hover-data', style=styles['pre'])
], className='three columns'),
html.Div([
dcc.Markdown('**點擊 clickData**'),
html.Pre(id='click-data', style=styles['pre']),
], className='three columns'),
html.Div([
dcc.Markdown('**選擇 selectedData**'),
html.Pre(id='selected-data', style=styles['pre']),
], className='three columns'),
html.Div([
dcc.Markdown('**重佈局 relayoutData**'),
html.Pre(id='relayout-data', style=styles['pre']),
], className='three columns')
])
])
@app.callback(
Output('hover-data', 'children'),
[Input('basic-interactions', 'hoverData')])
def display_hover_data(hoverData):
return json.dumps(hoverData, indent=2)
@app.callback(
Output('click-data', 'children'),
[Input('basic-interactions', 'clickData')])
def display_click_data(clickData):
return json.dumps(clickData, indent=2)
@app.callback(
Output('selected-data', 'children'),
[Input('basic-interactions', 'selectedData')])
def display_selected_data(selectedData):
return json.dumps(selectedData, indent=2)
@app.callback(
Output('relayout-data', 'children'),
[Input('basic-interactions', 'relayoutData')])
def display_relayout_data(relayoutData):
return json.dumps(relayoutData, indent=2)
if __name__ == '__main__':
app.run_server(debug=True)
懸停時更新圖形
import dash
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash()
df = pd.read_csv('國家及其指標.csv')
available_indicators = df['Indicator Name'].unique()
app.layout = html.Div([
html.Div([
html.Div([
dcc.Dropdown(
id='crossfilter-xaxis-column',
options=[{'label': i, 'value': i} for i in available_indicators],
value='Fertility rate, total (births per woman)'
),
dcc.RadioItems(
id='crossfilter-xaxis-type',
options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
value='Linear',
labelStyle={'display': 'inline-block'}
)
], style={'width': '49%', 'display': 'inline-block'}),
html.Div([
dcc.Dropdown(
id='crossfilter-yaxis-column',
options=[{'label': i, 'value': i} for i in available_indicators],
value='Life expectancy at birth, total (years)'
),
dcc.RadioItems(
id='crossfilter-yaxis-type',
options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
value='Linear',
labelStyle={'display': 'inline-block'}
)
], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})
], style={
'borderBottom': 'thin lightgrey solid',
'backgroundColor': 'rgb(250, 250, 250)',
'padding': '10px 5px'
}),
html.Div([
dcc.Graph(
id='crossfilter-indicator-scatter',
hoverData={'points': [{'customdata': 'Japan'}]}
)
], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
html.Div([
dcc.Graph(id='x-time-series'),
dcc.Graph(id='y-time-series'),
], style={'display': 'inline-block', 'width': '49%'}),
html.Div([
dcc.Slider(
id='crossfilter-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
)], style={'width': '49%', 'padding': '0px 20px 20px 20px'})
])
@app.callback(
Output('crossfilter-indicator-scatter', 'figure'),
[Input('crossfilter-xaxis-column', 'value'),
Input('crossfilter-yaxis-column', 'value'),
Input('crossfilter-xaxis-type', 'value'),
Input('crossfilter-yaxis-type', 'value'),
Input('crossfilter-year--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name, xaxis_type, yaxis_type, year_value):
'''一旦改變下拉框、單選按鈕或年份則更新圖表'''
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_traces(customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])
fig.update_xaxes(title=xaxis_column_name, type='linear' if xaxis_type == 'Linear' else 'log')
fig.update_yaxes(title=yaxis_column_name, type='linear' if yaxis_type == 'Linear' else 'log')
fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')
return fig
def create_time_series(dff, axis_type, title):
'''更新右邊圖表'''
fig = px.scatter(dff, x='Year', y='Value')
fig.update_traces(mode='lines+markers')
fig.update_xaxes(showgrid=False)
fig.update_yaxes(type='linear' if axis_type == 'Linear' else 'log')
fig.add_annotation(
x=0, y=0.85, xanchor='left', yanchor='bottom',
xref='paper', yref='paper', showarrow=False, align='left',
bgcolor='rgba(255, 255, 255, 0.5)', text=title
)
fig.update_layout(height=225, margin={'l': 20, 'b': 30, 'r': 10, 't': 10})
return fig
@app.callback(
Output('x-time-series', 'figure'),
[Input('crossfilter-indicator-scatter', 'hoverData'),
Input('crossfilter-xaxis-column', 'value'),
Input('crossfilter-xaxis-type', 'value')])
def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
'''更新右上角圖表'''
country_name = hoverData['points'][0]['customdata']
dff = df[df['Country Name'] == country_name]
dff = dff[dff['Indicator Name'] == xaxis_column_name]
title = '<b>{}</b><br>{}'.format(country_name, xaxis_column_name)
return create_time_series(dff, axis_type, title)
@app.callback(
Output('y-time-series', 'figure'),
[Input('crossfilter-indicator-scatter', 'hoverData'),
Input('crossfilter-yaxis-column', 'value'),
Input('crossfilter-yaxis-type', 'value')])
def update_x_timeseries(hoverData, yaxis_column_name, axis_type):
'''更右下角圖表'''
dff = df[df['Country Name'] == hoverData['points'][0]['customdata']]
dff = dff[dff['Indicator Name'] == yaxis_column_name]
return create_time_series(dff, axis_type, yaxis_column_name)
if __name__ == '__main__':
app.run_server(debug=True)
我國出口商品佔GDP比重和GDP的增長速度呈正比,說明了改革開放的重要性。
通用交叉過濾
對每個散點圖的選擇過濾底層數據
import dash
import numpy as np
import pandas as pd
import plotly.express as px
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
np.random.seed(0)
df = pd.DataFrame({"Col " + str(i + 1): np.random.rand(30) for i in range(6)}) # 隨機生成6組30以內的數(3組x,y軸數據)
app = dash.Dash()
app.layout = html.Div([
html.Div(
dcc.Graph(id='g1', config={'displayModeBar': False}),
className='four columns'
),
html.Div(
dcc.Graph(id='g2', config={'displayModeBar': False}),
className='four columns'
),
html.Div(
dcc.Graph(id='g3', config={'displayModeBar': False}),
className='four columns'
)
], className='row')
def get_figure(df, x_col, y_col, selectedpoints, selectedpoints_local):
if selectedpoints_local and selectedpoints_local['range']:
ranges = selectedpoints_local['range']
selection_bounds = {'x0': ranges['x'][0], 'x1': ranges['x'][1],
'y0': ranges['y'][0], 'y1': ranges['y'][1]}
else:
selection_bounds = {'x0': np.min(df[x_col]), 'x1': np.max(df[x_col]),
'y0': np.min(df[y_col]), 'y1': np.max(df[y_col])}
fig = px.scatter(df, x=df[x_col], y=df[y_col], text=df.index)
fig.update_traces(selectedpoints=selectedpoints,
customdata=df.index,
mode='markers+text', marker={'color': 'rgba(0, 116, 217, 0.7)', 'size': 20},
unselected={'marker': {'opacity': 0.3}, 'textfont': {'color': 'rgba(0, 0, 0, 0)'}})
fig.update_layout(margin={'l': 20, 'r': 0, 'b': 15, 't': 5}, dragmode='select', hovermode=False)
fig.add_shape(dict({'type': 'rect', 'line': {'width': 1, 'dash': 'dot', 'color': 'darkgrey'}}, **selection_bounds))
return fig
@app.callback(
[Output('g1', 'figure'),
Output('g2', 'figure'),
Output('g3', 'figure')],
[Input('g1', 'selectedData'),
Input('g2', 'selectedData'),
Input('g3', 'selectedData')]
)
def callback(selection1, selection2, selection3):
selectedpoints = df.index
for selected_data in [selection1, selection2, selection3]:
if selected_data and selected_data['points']:
selectedpoints = np.intersect1d(
selectedpoints,
[p['customdata'] for p in selected_data['points']]
)
return [get_figure(df, "Col 1", "Col 2", selectedpoints, selection1),
get_figure(df, "Col 3", "Col 4", selectedpoints, selection2),
get_figure(df, "Col 5", "Col 6", selectedpoints, selection3)]
if __name__ == '__main__':
app.run_server(debug=True)
點擊或選擇一個區域來過濾,選中的點會高亮
回調函數共享數據
爲什麼需要共享狀態?
某些回調做數據處理,如SQL查詢或下載數據,代價很大。與其讓多個回調運行相同的任務,不如將結果共享給其餘的回調。
可選方案:
- 多個output:對數據作一次小處理再查數據庫代價仍太大
- global:數據會影響到用戶之間
爲了跨多個Python進程安全地共享數據,需要將數據存儲在每個進程都可以訪問的地方。主要方案有:
- 用戶的瀏覽器會話
- 磁盤,如文件或新數據庫
- 共享內存空間,如Redis
具體方案查看回調函數共享數據
踩過的坑
- 報錯:
ValueError: All arguments should have the same length. The length of argument y is 2, whereas the length of previous arguments ['x'] is 3
更新plotly即可:pip install plotly --upgrade