dash
是一個基於 Flask (Python) + React 的 web 框架。php
入門指南:https://dash.plot.ly/getting-started>css
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
import dash
html
# 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前端
import dash_html_components as html
python
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 除以它。
import dash_core_components as dcc
nginx
import plotly.graph_objs as go
git
包括但不限於 單選框、下拉框、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)
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
在下面會有介紹。
dcc.Interval
,interval
設置間隔時間,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)
略
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
(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)暫不支持設置懸停和點擊時的樣式修改。
# 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 )
app = dash.Dash(__name__, assets_external_path='http://your-external-assets-folder-url/' ) app.scripts.config.serve_locally = False
在根目錄建立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
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
略
<iframe>
內嵌略
略
什麼是 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 指定線程數
這個問題其實 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 文件: 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/