Dash by Plotly 學習筆記

1、介紹


一、dash 是什麼

dash 是一個基於 Flask (Python) + React 的 web 框架。php

入門指南:https://dash.plot.ly/getting-started>css

2、安裝


一、安裝

pip install dash==0.39.0  # The core dash backend
pip install dash-daq==0.1.0  # DAQ components (newly open-sourced!)

二、啓動

$ python app.py

3、使用


一、基本框架

import dashhtml

# init dash
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets) 

# create layout
app.layout = html.Div(children=[
    # ……
]),


if __name__ == '__main__':
    # debug=True 激活全部開發工具,可選參數參考(https://dash.plot.ly/devtools)
    app.run_server(debug=True,dev_tools=)

二、組件

Declarative UI —— 聲明式 UI前端

(1)html 元素

import dash_html_components as htmlpython

class/style 跟 react 的寫法規範差很少。react

# html.H1(children='Hello Dash') = html.H1('Hello Dash')

html.H1('Hello Dash'),

html.H1(id="h1-element", className='h1', children='Hello Dash',
style={'textAlign': 'center', 'color': '#7FDBFF'}),
佈局
# 嵌套
    html.Div(children=[
        html.Div('111'),
        html.Div('222'),
        html.Div('333'),
    ]),
# 12網格佈局 
    html.Div(className='row', children=[
        html.Div('111', className='four columns'),
        html.Div('222', className='four columns'),
        html.Div('333', className='four columns'),
    ]),
    # 更多預設 className 能夠查看: https://codepen.io/chriddyp/pen/bWLwgP.css
    # tips:className='four columns' 實際上是三列,className='three columns' 實際上是四列,意思是用 12 除以它。
(2)python 封裝的組件

import dash_core_components as dccnginx

import plotly.graph_objs as gogit

包括但不限於 單選框、下拉框、Markdown、Store、Graphs……,更多組件https://dash.plot.ly/dash-core-componentsgithub

# draw Graph
    dcc.Graph(
        id='cluster_count',
        figure={
            'data': [go.Bar(
                x=[1,2,3],
                y=[100,200,300],
                text=['100個','200個','300個'],
                textposition='auto',
            )],
            'layout': go.Layout(
            )
        },
    ), 
    # figure 屬性下有 」data「+"layout" 屬性

它基於plotly.py畫圖庫 https://plot.ly/python/,畫圖庫又基於SVG和WebGL (默認爲 SVG,對大型數據集會切換到WebGL)。web

# 組件用法幫助
>>> help(dcc.Dropdown)
loading 組件

dcc.Loading執行回調的過程當中會顯示type屬性設置的 loading 圖標, 而回調完成後會顯示children裏的內容。

dcc.Loading(id="loading-1", children=[html.Div(id="loading-output-1")], type="default"),
dcc.Input(id="input-1", value='1'),


@app.callback(Output("loading-output-1", "children"), [Input("input-1", "value")])
def input_triggers_spinner(value):
    time.sleep(1)
    return value

這裏的回調@app.callback在下面會有介紹。

Interval 組件

dcc.Intervalinterval設置間隔時間,n_intervals記錄觸發次數。

dcc.Interval(
            id='interval-component',
            interval=1*1000, # in milliseconds
            n_intervals=0
        )
        
        
@app.callback(Output('interval-component-div', 'children'),
              [Input('interval-component', 'n_intervals')])
def update_metrics(n):
    return "Already run {} times".format(n)
(3)建立本身的組件

三、回調

Reactive Programming —— 反應式編程

from dash.dependencies import Input, Output, State

dcc.Input(id='my-input', value='initial value', type='text'),
dcc.Input(id='my-input-2', value='initial value', type='text'),
html.Button(id='submit-button', n_clicks=0, children='Submit'),
html.Div(id='my-div'),
html.Div(id='my-div-2') 

@app.callback(
    [Output(component_id='my-div', component_property='children'),
     Output(component_id='my-div-2', component_property='children')
     ],
     # 簡寫形式(省略 component_id + component_property)
    [Input('submit-button', 'n_clicks')],
    [State(component_id='my-input', component_property='value'),
     State(component_id='my-input-2', component_property='value')
     ]
)
def update_output_div(n_clicks, input_value, input_value_2):
    return 'You\'ve entered "{}" and "{}"'.format(input_value, input_value_2), 'You\'ve click : "{}" times'.format(n_clicks)
注意事項(坑)

(1)每個 callback 都會發送一個請求

(2)全部回調裏的 Input 都必須有Output。

(3)同一個組件能夠出如今多個回調的Input;可是在 Output 上,只能出如今一處回調裏

(4)不支持組件的雙向綁定

@app.callback(
    Output('my-id-2', 'value'),
    [Input('my-id', 'value')])
def aaa(data): 
    print("11:"+data)
    return data 

@app.callback(
    Output('my-id', 'value'),
    [Input('my-id-2', 'value')])
def bbb(data): 
    print("22:"+data)
    return data

官方說這個 feature 暫時沒有解決,具體時間未知。詳細討論以下:

https://community.plot.ly/t/interdependent-components/8299/4

https://community.plot.ly/t/two-way-binding-for-rangeslider-and-input-fields-without-dependency-loop/4810

(5)在回調中共享數據

dash 有個原則就是Callbacks毫不能修改其範圍以外的變量

但咱們有時候須要用到共享數據,好比引入緩存機制來提升性能,這個時候應該怎麼辦呢?

方法一:存到當前用戶的瀏覽器會話中(例如隱藏 DIV)

方法二:存到服務器磁盤上(例如文件或數據庫)

方法三:存到服務器內存上(例如Redis)

持久化須要把數據序列化( 好比 toJSON() ),對於複雜的數據,推薦使用apache arrow

其中方法二和三可使用Flask-Caching來實現。

pip install Flask-Caching

實現回調的緩存機制:

from flask_caching import Cache


cache = Cache(app.server, config={
    'CACHE_TYPE': 'filesystem',
    'CACHE_DIR': 'cache-directory'
})
# use redis
# 'CACHE_TYPE': 'redis',
# 'CACHE_REDIS_URL': os.environ.get('REDIS_URL', '')


html.Div(id='flask-cache-memoized-children'),
        dcc.RadioItems(
            id='flask-cache-memoized-dropdown',
            options=[
                {'label': 'Option {}'.format(i), 'value': 'Option {}'.format(i)}
                for i in range(1, 4)
            ],
            value='Option 1'
        ),
        html.Div('Results are cached for {} seconds'.format(TIMEOUT)),
        
@app.callback(
    Output('flask-cache-memoized-children', 'children'),
    [Input('flask-cache-memoized-dropdown', 'value')])
@cache.memoize(timeout=60)  # in seconds
def render(value):
    return 'Selected "{}" at "{}"'.format(
        value, datetime.datetime.now().strftime('%H:%M:%S')
    )

四、交互

只適用於dash_core_components

目前只支持四種交互方式: hoverData, clickData, selectedData, relayoutData

@app.callback(
    Output('click-data', 'children'),
    [Input('cluster_count', 'clickData')])
def display_click_data(clickData):
    return json.dumps(clickData, indent=2)
注意事項(坑)

(1)回調時的形參不能隨意指定

@app.callback(
    Output('hover-data', 'children'),
    [Input('cluster_count', 'hoverData')])
# tips: 這裏形參的hoverData不能夠隨便指定其餘稱呼,不然會獲取不到值 
def display_hover_data(hoverData):
    return json.dumps(hoverData, indent=2)

(2)暫不支持設置懸停和點擊時的樣式修改。

五、CSS & JS (& Header )

(1)外部文件(需手動引入)
# external_stylesheets
external_stylesheets = [
    'https://codepen.io/chriddyp/pen/bWLwgP.css',
    {
        'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css',
        'rel': 'stylesheet',
        'integrity': 'sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO',
        'crossorigin': 'anonymous'
    }
]

# external_scripts
external_scripts = [
    'https://www.google-analytics.com/analytics.js',
    {'src': 'https://cdn.polyfill.io/v2/polyfill.min.js'},
    {
        'src': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.js',
        'integrity': 'sha256-Qqd/EfdABZUcAxjOkMi8eGEivtdTkh3b65xCZL4qAQA=',
        'crossorigin': 'anonymous'
    }
]

meta_tags = [
    {
        'name': 'description',
        'content': 'My description'
    },
    {
        'http-equiv': 'X-UA-Compatible',
        'content': 'IE=edge'
    }
]


# init dash 
app = dash.Dash(__name__, 
      external_stylesheets=external_stylesheets,
      external_scripts=external_scripts,
      meta_tags=meta_tags
        )
(2)CDN文件(需手動引入)
app = dash.Dash(__name__,
    assets_external_path='http://your-external-assets-folder-url/'
)
app.scripts.config.serve_locally = False
(3) 本地文件(自動引入)

在根目錄建立assets文件夾:

# 能夠改這個設置
app.config.assets_folder = 'assets'

A、裏面的 CSS/JS 文件會自動引入

B、IMG 圖片須要這樣加載html.Img(src='/assets/image.png')

app.py
- assets/
    |-- typography.css
    |-- header.css
    |-- custom-script.js
    |-- image.png

六、路由

# ------------ 先定義 dcc.Location ,它的 pathname 屬性會實時記錄當前 url 的值 ------------

dcc.Location(id='url', refresh=False), # 沒有任何顯示做用

# ------------ 改變 url ------------

# 會刷新頁面
dcc.Link('Navigate to "/"', href='/'),
dcc.Link('Navigate to "/page-2"', href='/page-2'),

# 不會刷新頁面
html.A(href='/page-3',children='Navigate to "/page-3"'),


# ------------ 改變 url 的 回調 ------------

@app.callback(dash.dependencies.Output('page-content', 'children'),
              [dash.dependencies.Input('url', 'pathname')])
def display_page(pathname):
    return html.Div([
        html.H3('You are on page {}'.format(pathname))
    ])
構建多頁面應用程序

注意事項 (坑)

若是咱們經過不一樣的 url去渲染不一樣的頁面的話,會碰到一個問題,就是咱們可能會率先定義了回調,而回掉中的組件暫時還沒渲染到app.layout中,所以Dash會引起異常以警告咱們可能作錯了。在這種狀況下,咱們能夠經過設置忽略該異常。即:

app.config.suppress_callback_exceptions = True

七、Auth

(1)HTTP Basic Auth
pip install dash-auth

import dash_auth

# Keep this out of source code repository - save in a file or a database
VALID_USERNAME_PASSWORD_PAIRS = [
    ['hello', 'world']
]
auth = dash_auth.BasicAuth(
    app,
    VALID_USERNAME_PASSWORD_PAIRS
)

app.scripts.config.serve_locally = True
(2)Plotly OAuth (須要付費)

八、部署

(1) 用 <iframe> 內嵌
(2)Flask 嵌入 Dash

(3)Dash 嵌入 Flask

延展知識

什麼是 WSGI?

WSGI是僅針對 python 的 Web服務器網關接口(Python Web Server Gateway Interface)規範

注意,只是規範,是 web服務器和 web應用 之間的接口規範。

WSGI 跟 CGI 的區別?

CGI 是通用網關接口的規範,並不限於 Python 語言。雖然早已過期,後來分別誕生了 python 領域的 WSGI 和 php 的 FastCGI(PHP-FPM)等。

python 的 web 框架都要遵循這個規範,好比Flask內置的 wsgiref 或第三方的 Gunicorn 。 但前者 wsgiref 性能低下,因此推薦部署時選擇 flask+Gunicorn+nginx 方案。

gunicorn --workers 6 --threads 2 app:server

—workers 指定進程數

—threads 指定線程數


4、坑

一、gunicorn 部署致使頁面交互進行不了

這個問題其實 github 上有人討論(https://github.com/plotly/dash/issues/85),惋惜沒有有效的解決辦法,我本身最後歪打正着了。

個人 dash 站點就只有單頁,不過頁面上有一些用 callback 實現的,用來作用戶跟圖表交互的功能,如圖:

而我遇到的問題就是,當我用 gunicorn啓動項目的時候, 我跟圖表的交互,時好時壞。(即點擊切換單選按鈕圖表沒有反應)。但我用 python3 直接啓動 .py 時,卻沒有問題。

我去觀察 chrome 瀏覽器 inspect 的 network tab, 發現http://127.0.0.1:8000/_dash-update-component這個請求,時而200,時而500,這就是它致使了我前端操做無響應。

可是我最終解決了這個問題。我發現只要把原有的啓動命令從
gunicorn -w 4 app:server 變成 gunicorn app:server 就行了。

但是,爲何呢?

二、dash 強制引用外境CDN文件致使頁面加載很慢

dash 會強制引用CDN 文件: plotly-1.44.3.min.js ,且不支持代碼級別的修改,最可惡的是此文件加載速度很慢,因此咱們要把他改到咱們本身的 cdn 路徑上。

方法爲簡單粗暴的改源碼,步驟以下:

一、查看 dash-core-components 所在路徑
pip3 show dash-core-components

二、打開例如 /home/.local/lib/python3.7/site-packages/dash_core_components/__init__.py 文件

三、修改此行: 'external_url':  'https://cdn.plot.ly/plotly-1.44.3.min.js' 爲咱們本身的 CDN 路徑

參考資料:https://github.com/plotly/plotly.py/issues/1438


參考資料

https://dash.plot.ly/

相關文章
相關標籤/搜索