Dash by Plotly 学习笔记

一、介绍


1、dash 是什么

dash 是一个基于 Flask (Python) + React 的 web 框架。

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

二、安装


1、安装

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

2、启动

$ python app.py

三、使用


1、基本框架

import dash

1# init dash 2external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] 3app = dash.Dash(__name__, external_stylesheets=external_stylesheets) 4 5# create layout 6app.layout = html.Div(children=[ 7 # …… 8]), 9 10 11if __name__ == '__main__': 12 # debug=True 激活所有开发工具,可选参数参考(https://dash.plot.ly/devtools) 13 app.run_server(debug=True,dev_tools=)

2、组件

Declarative UI —— 声明式 UI

#####(1)html 元素

import dash_html_components as html

class/style 跟 react 的写法规范差不多。

1# html.H1(children='Hello Dash') = html.H1('Hello Dash') 2 3html.H1('Hello Dash'), 4 5html.H1(id="h1-element", className='h1', children='Hello Dash', 6style={'textAlign': 'center', 'color': '#7FDBFF'}),
布局
1 # 嵌套 2 html.Div(children=[ 3 html.Div('111'), 4 html.Div('222'), 5 html.Div('333'), 6 ]), 7 8 9 # 12网格布局 10 html.Div(className='row', children=[ 11 html.Div('111', className='four columns'), 12 html.Div('222', className='four columns'), 13 html.Div('333', className='four columns'), 14 ]), 15 # 更多预设 className 可以查看: https://codepen.io/chriddyp/pen/bWLwgP.css 16 # tips:className='four columns' 其实是三列,className='three columns' 其实是四列,意思是用 12 除以它。

#####(2)python 封装的组件

import dash_core_components as dcc

import plotly.graph_objs as go

包括但不限于 单选框、下拉框、Markdown、Store、Graphs……,更多组件https://dash.plot.ly/dash-core-components

1 # draw Graph 2 dcc.Graph( 3 id='cluster_count', 4 figure={ 5 'data': [go.Bar( 6 x=[1,2,3], 7 y=[100,200,300], 8 text=['100个','200个','300个'], 9 textposition='auto', 10 )], 11 'layout': go.Layout( 12 ) 13 }, 14 ), 15 # figure 属性下有 ”data“+"layout" 属性

它基于plotly.py画图库 https://plot.ly/python/,画图库又基于SVG和WebGL (默认为 SVG,对大型数据集会切换到WebGL)。

1# 组件用法帮助 2>>> help(dcc.Dropdown)
loading 组件

dcc.Loading执行回调的过程中会显示type属性设置的 loading 图标, 而回调完成后会显示children里的内容。

1dcc.Loading(id="loading-1", children=[html.Div(id="loading-output-1")], type="default"), 2dcc.Input(id="input-1", value='1'), 3 4 5@app.callback(Output("loading-output-1", "children"), [Input("input-1", "value")]) 6def input_triggers_spinner(value): 7 time.sleep(1) 8 return value

这里的回调@app.callback在下面会有介绍。

Interval 组件

dcc.Intervalinterval设置间隔时间,n_intervals记录触发次数。

1dcc.Interval( 2 id='interval-component', 3 interval=1*1000, # in milliseconds 4 n_intervals=0 5 ) 6 7 8@app.callback(Output('interval-component-div', 'children'), 9 [Input('interval-component', 'n_intervals')]) 10def update_metrics(n): 11 return "Already run {} times".format(n)
(3)创建自己的组件

3、回调

Reactive Programming —— 反应式编程

from dash.dependencies import Input, Output, State

1dcc.Input(id='my-input', value='initial value', type='text'), 2dcc.Input(id='my-input-2', value='initial value', type='text'), 3html.Button(id='submit-button', n_clicks=0, children='Submit'), 4html.Div(id='my-div'), 5html.Div(id='my-div-2') 6 7@app.callback( 8 [Output(component_id='my-div', component_property='children'), 9 Output(component_id='my-div-2', component_property='children') 10 ], 11 # 简写形式(省略 component_id + component_property) 12 [Input('submit-button', 'n_clicks')], 13 [State(component_id='my-input', component_property='value'), 14 State(component_id='my-input-2', component_property='value') 15 ] 16) 17def update_output_div(n_clicks, input_value, input_value_2): 18 return 'You\'ve entered "{}" and "{}"'.format(input_value, input_value_2), 'You\'ve click : "{}" times'.format(n_clicks) 19
注意事项(坑)

(1)每一个 callback 都会发送一个请求

(2)所有回调里的 Input 都必须有Output。

(3)同一个组件可以出现在多个回调的Input;但是在 Output 上,只能出现在一处回调里

(4)不支持组件的双向绑定

1@app.callback( 2 Output('my-id-2', 'value'), 3 [Input('my-id', 'value')]) 4def aaa(data): 5 print("11:"+data) 6 return data 7 8@app.callback( 9 Output('my-id', 'value'), 10 [Input('my-id-2', 'value')]) 11def bbb(data): 12 print("22:"+data) 13 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

实现回调的缓存机制:

1from flask_caching import Cache 2 3 4cache = Cache(app.server, config={ 5 'CACHE_TYPE': 'filesystem', 6 'CACHE_DIR': 'cache-directory' 7}) 8# use redis 9# 'CACHE_TYPE': 'redis', 10# 'CACHE_REDIS_URL': os.environ.get('REDIS_URL', '') 11 12 13html.Div(id='flask-cache-memoized-children'), 14 dcc.RadioItems( 15 id='flask-cache-memoized-dropdown', 16 options=[ 17 {'label': 'Option {}'.format(i), 'value': 'Option {}'.format(i)} 18 for i in range(1, 4) 19 ], 20 value='Option 1' 21 ), 22 html.Div('Results are cached for {} seconds'.format(TIMEOUT)), 23 24@app.callback( 25 Output('flask-cache-memoized-children', 'children'), 26 [Input('flask-cache-memoized-dropdown', 'value')]) 27@cache.memoize(timeout=60) # in seconds 28def render(value): 29 return 'Selected "{}" at "{}"'.format( 30 value, datetime.datetime.now().strftime('%H:%M:%S') 31 )

4、交互

只适用于dash_core_components

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

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

(1)回调时的形参不能随意指定

1@app.callback( 2 Output('hover-data', 'children'), 3 [Input('cluster_count', 'hoverData')]) 4# tips: 这里形参的hoverData不可以随便指定其他称呼,否则会获取不到值 5def display_hover_data(hoverData): 6 return json.dumps(hoverData, indent=2)

(2)暂不支持设置悬停和点击时的样式修改。

5、CSS & JS (& Header )

(1)外部文件(需手动引入)
1# external_stylesheets 2external_stylesheets = [ 3 'https://codepen.io/chriddyp/pen/bWLwgP.css', 4 { 5 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css', 6 'rel': 'stylesheet', 7 'integrity': 'sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO', 8 'crossorigin': 'anonymous' 9 } 10] 11 12# external_scripts 13external_scripts = [ 14 'https://www.google-analytics.com/analytics.js', 15 {'src': 'https://cdn.polyfill.io/v2/polyfill.min.js'}, 16 { 17 'src': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.js', 18 'integrity': 'sha256-Qqd/EfdABZUcAxjOkMi8eGEivtdTkh3b65xCZL4qAQA=', 19 'crossorigin': 'anonymous' 20 } 21] 22 23meta_tags = [ 24 { 25 'name': 'description', 26 'content': 'My description' 27 }, 28 { 29 'http-equiv': 'X-UA-Compatible', 30 'content': 'IE=edge' 31 } 32] 33 34 35# init dash 36app = dash.Dash(__name__, 37 external_stylesheets=external_stylesheets, 38 external_scripts=external_scripts, 39 meta_tags=meta_tags 40 )
(2)CDN文件(需手动引入)
1app = dash.Dash(__name__, 2 assets_external_path='http://your-external-assets-folder-url/' 3) 4app.scripts.config.serve_locally = False
(3) 本地文件(自动引入)

在根目录创建assets文件夹:

1# 可以改这个设置 2app.config.assets_folder = 'assets'

A、里面的 CSS/JS 文件会自动引入

B、IMG 图片需要这样加载html.Img(src='/assets/image.png')

1app.py 2- assets/ 3 |-- typography.css 4 |-- header.css 5 |-- custom-script.js 6 |-- image.png

6、路由

1# ------------ 先定义 dcc.Location ,它的 pathname 属性会实时记录当前 url 的值 ------------ 2 3dcc.Location(id='url', refresh=False), # 没有任何显示作用 4 5# ------------ 改变 url ------------ 6 7# 会刷新页面 8dcc.Link('Navigate to "/"', href='/'), 9dcc.Link('Navigate to "/page-2"', href='/page-2'), 10 11# 不会刷新页面 12html.A(href='/page-3',children='Navigate to "/page-3"'), 13 14 15# ------------ 改变 url 的 回调 ------------ 16 17@app.callback(dash.dependencies.Output('page-content', 'children'), 18 [dash.dependencies.Input('url', 'pathname')]) 19def display_page(pathname): 20 return html.Div([ 21 html.H3('You are on page {}'.format(pathname)) 22 ]) 23
构建多页面应用程序

注意事项 (坑)

如果我们通过不同的 url去渲染不同的页面的话,会碰到一个问题,就是我们可能会率先定义了回调,而回掉中的组件暂时还没渲染到app.layout中,因此Dash会引发异常以警告我们可能做错了。在这种情况下,我们可以通过设置忽略该异常。即:

app.config.suppress_callback_exceptions = True

7、Auth

#####(1)HTTP Basic Auth

pip install dash-auth

1import dash_auth 2 3# Keep this out of source code repository - save in a file or a database 4VALID_USERNAME_PASSWORD_PAIRS = [ 5 ['hello', 'world'] 6] 7auth = dash_auth.BasicAuth( 8 app, 9 VALID_USERNAME_PASSWORD_PAIRS 10) 11 12app.scripts.config.serve_locally = True

#####(2)Plotly OAuth (需要付费)

8、部署

(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 指定线程数


四、坑

1、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 就好了。

可是,为什么呢?

2、dash 强制引用外境CDN文件导致页面加载很慢

dash 会强制引用CDN 文件: plotly-1.44.3.min.js ,且不支持代码级别的修改,最可恶的是此文件加载速度很慢,所以我们要把他改到我们自己的 cdn 路径上。

方法为简单粗暴的改源码,步骤如下:

11、查看 dash-core-components 所在路径 2pip3 show dash-core-components 3 42、打开例如 /home/.local/lib/python3.7/site-packages/dash_core_components/__init__.py 文件 5 63、修改此行: '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/

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )