Python可視化交互庫——dash

在這裏插入圖片描述



本文項目代碼





簡介

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應用由兩部分組成:

  1. layout,外觀
  2. 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)

在這裏插入圖片描述




可重用組件

部分美國農業出口數據.csv

,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呈現。


GDP與人均壽命.csv


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 定義 InputOutput

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)

在這裏插入圖片描述




滑塊更新圖表

每五年GDP與人均壽命.csv

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




多個輸入

國家及其指標.csv

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)

在這裏插入圖片描述




懸停時更新圖形

國家及其指標.csv

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進程安全地共享數據,需要將數據存儲在每個進程都可以訪問的地方。主要方案有:

  1. 用戶的瀏覽器會話
  2. 磁盤,如文件或新數據庫
  3. 共享內存空間,如Redis

具體方案查看回調函數共享數據




踩過的坑

  1. 報錯: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




參考文獻

  1. Dash Documentation & User Guide | Plotly
  2. dash: Analytical Web Apps for Python, R, Julia, and Jupyter. No JavaScript Required.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章