Dash
是一个基于Flask
、Poltly.js
以及React.js
的Python框架,由plotly公司开发,设计之初是为了帮助数据分析人员进行快速的数据可视化网页搭建。现时Dash
已经是一个相当成熟的框架,拥有丰富的社区资源与生态。
1、简单介绍
由于Dash是基于Flask框架建立的,其运行方式与执行逻辑和Flask是大同小异的。但Flask需要自己处理前后端的连接,且对前端后端知识有一定的了解,这对数据人员来说并不友好。Dash则将前端后端集成在一起,只需写简单的回调就能实现交互功能。
1.1 安装
pip install dash
安装后即可构建一个Dash应用了!现在来创建一个小的Dash应用。
# app.py
from dash import Dash, html
app = Dash(__name__)
app.layout = [html.Div('Hello world!')]
if __name__ == '__main__':
app.run_server()
可以看到终端返回了一些信息,Dash应用在本地主机上运行,端口默认8050。在run_server()设置参数port可更改端口,可设置debug=True切换debug模式。
开启debug模式后每修改一次代码后Dash应用会自动重启更新,遇上错误后中断当前应用。
# app.py
from dash import Dash, html
app = Dash(__name__)
app.layout = [html.Div('Hello world!')]
if __name__ == '__main__':
app.run_server(debug=True)
开启debug模式后,Dash应用的右下方出现蓝色logo。展开logo会有三个按钮,第一个按钮展示Dash应用中的回调信息,包括回调关系、回调加载时间等;第二个按钮Errors反映的是程序错误信息,比如回调错误;第三个按钮Server则是显示当前Dash应用的运行状态,绿色表示正常运行,红色表示出错终止。
1.2 应用架构
Dash应用的架构如下:
+ dashProject/
+ asset/
+ css/
+ img/
+ js/
• favicon.ico
+ callbacks/
+ models/
+ views/
• app.py
• server.py
+ pages/
文件夹assets存放前端文件,callbacks存放回调函数,models存放数据模型文件,views为视图文件, pages存放不同的页面文件。Dash自动读取相应的文件夹进行相应的操作,可以根据自己的实际需要对架构进行调整。
1.3 基本概念
Dash应用基本上是由两部分组成:布局(Layout)和回调(Callback)。布局负责页面的外观,回调则赋予了应用交互性。
1.3.1 布局
布局规定Dash页面的外观,规定哪一块区域具体放什么内容、怎么排版等。layout
由一棵“组件”树组成,利如 html.Div
,dcc.Graph
。
app.layout = [
html.Div('Hello world!'),
html.Div('1'),
html.Div('2'),
html.Div('3'),
]
一个好看的网页往往通常需要编写css、js文件,但使用Dash元素的初衷不就是因为不熟悉不想写繁琐的前端代码吗,所以调用Dash的第三方拓展库dash_bootstrap_components就可以大大减少前端页面设计工作。dash-bootstrap-components不包含css,这是为了让您可以自由地选择任何的Bootstrap v5样式表,实现想要的外观。
import dash_bootstrap_components as dbc
import dash
app = dash.Dash(
__name__,
# 用于引入外部的css,有了这部分网页才有更多样的形式
external_stylesheets=[dbc.themes.BOOTSTRAP],
# 可以填入css文件
# external_stylesheets=['css/bootstrap.min.css'],
)
if __name__ == '__main__':
app.run_server()
上面的准备工作完成以后,接下来开始学习构造页面布局。
- Container、Row、Col
首先要了解的是组件Container,它是我们组织页面元素的容器。一个元素的尺寸和位置经常受其容器(Container)的影响。容器都被划分为四个区域,内容区(Content)、内边距区(Padding)、边框区(Margin)和外边框区(Border)。要修改Content、Padding、Margin、Border的样式,可设置style参数,见下面例子。
代码 | 样式 |
---|---|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
实际上,参数style对应的就是css文件,也可以通过写css文件对元素样式进行修改。
在Container()
之内,我们就可以按照bootstrap
的网格系统进行内容的排布:行(Row)嵌套列(Col),再向列嵌套各种部件。Bootstrap提供了一套响应式、移动设备优先的流式网格系统,随着屏幕尺寸的增加,系统会自动分为最多 12 列。我们可以根据自己的需要定义列数,由于Bootstrap网格系统是响应式的, 列会根据屏幕大小自动重新排列,需要确保每一行中列的总和小于等于12且列数为正整数。
app.layout = dbc.Container(
[
dbc.Row([
dbc.Col(style={'background-color': 'lightgrey', 'height': 100}, width=1),
dbc.Col(style={'background-color': 'red', 'height': 100}, width=2),
dbc.Col(style={'background-color': 'blue', 'height': 100}, width=3),
dbc.Col(style={'background-color': 'green', 'height': 100}, width=4),
dbc.Col(style={'background-color': 'black', 'height': 100}, width=2),
]),
dbc.Row([
dbc.Col(style={'background-color': '#cbd933', 'height': 100}),
dbc.Col(style={'background-color': '#6b6882', 'height': 100}),
]),
]
)
- 设置水平对齐方式
在实际排版中,很多页面布局需求中需要对于同一行的多个列元素设置对齐方式,可以通过对Row()设置参数justify
来实现,可选项有'start'
、'center'
、'end'
、'between'
以及'around'
五种,每种产生的效果如下面的例子:
app.layout = dbc.Container(
[
dbc.Row([
dbc.Col('1', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('2', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('3', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
], justify='start'),
html.Hr(),
dbc.Row([
dbc.Col('1', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('2', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('3', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
], justify='center'),
html.Hr(),
dbc.Row([
dbc.Col('1', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('2', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('3', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
], justify='end'),
html.Hr(),
dbc.Row([
dbc.Col('1', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('2', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('3', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
], justify='between'),
html.Hr(),
dbc.Row([
dbc.Col('1', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('2', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
dbc.Col('3', style={'border-color': 'black', 'border-style': 'solid'}, width=2),
], justify='around'),
]
)
1.3.2 回调
Dash
最大的优点之一就是其高度封装了React.js
,使得我们无需编写js
代码即可实现前端与后端之间的异步交互,为了实现这一步,我们需要使用到dash.dependencies
中的Input
与Output
,再配合自定义回调函数来实现所需交互功能。当输入组件的属性发生更改时,Dash会自动调用这些函数,以更新另一个组件(输出)中的某些属性。
from dash import Dash, html, dcc, Input, Output, callback
import dash_bootstrap_components as dbc
app = Dash(__name__,
external_stylesheets=[dbc.themes.BOOTSTRAP]
)
app.layout = html.Div([
html.H6("输入一些内容:"),
html.Div([
"输入: ",
dcc.Input(id='my-input', type='text', placeholder='输入')
]),
html.Br(),
html.Div(id='my-output'),
])
@callback(
Output(component_id='my-output', component_property='children'),
Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
return f'输出: {input_value}'
if __name__ == '__main__':
app.run_server(debug=True)
在 Dash 中,@callback被用于定义回调函数。@callback装饰器指定了回调函数update_output_div,当Input组件中的值发生变化它将被触发。Output定义了回调函数的输出,它指示输出到Output组件的属性;Iutput定义了回调函数的输入,表示回调函数接收来自属性的输入。参数component_id指的是元素的id,component_property指的是元素的属性。例子里面的Input(component_id='my-input', component_property='value'),即在id为my-input的组件处接收value;Output(component_id='my-output', component_property='children')即在id为my-output的组件处输出children。下面再举多个例子方便理解吧:
例1 在id为year-slider处接收value,在id为graph-with-slider处返回figure。图片将根据选择的年份进行更新。
from dash import Dash, dcc, html, Input, Output, callback import plotly.express as px import pandas as pd df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv') app = Dash(__name__) app.layout = html.Div([ dcc.Graph(id='graph-with-slider'), dcc.Slider( df['year'].min(), df['year'].max(), step=None, value=df['year'].min(), marks={str(year): str(year) for year in df['year'].unique()}, id='year-slider' ) ]) @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=55) fig.update_layout(transition_duration=500) return fig if __name__ == '__main__': app.run_server(debug=True)
例2 编写一个dash应用,实现用户每点击一次按钮,更新点击按钮的最新时间。from dash import html, Dash, callback, Output, Input from datetime import datetime app = Dash( __name__ ) app.layout = html.Div( [ html.Button(id='btn', children='点击'), html.Div(id='click-time') ] ) @callback( Output('click-time', 'children'), Input('btn', 'n_clicks') ) def update(n_clicks): return '最近点击时间' + str(datetime.now()) if __name__ == '__main__': app.run_server()
2 常用组件和元素
2.1 html组件
2.1.1 概要
在构建前端页面的过程中,可以使用dash下的html模块(dash.html),无需编写HTML或使用HTML模板引擎。下面是一个简单的html结构实例:
from dash import html
html.Div([
html.H1('Hello Dash'),
html.Div([
html.P('Dash converts Python classes into HTML'),
html.P("This conversion happens behind the scenes by Dash's JavaScript front-end")
])
])
以上代码会在您的web应用程序中转换为以下HTML:
<div>
<h1>Hello Dash</h1>
<div>
<p>Dash converts Python classes into HTML</p>
<p>This conversion happens behind the scenes by Dash's JavaScript front-end</p>
</div>
</div>
在HTML组件中,有style、class、id等属性用来调整页面风格,这些属性都能在dash中调整。HTML元素和Dash参数基本相同,但有一些关键区别:
- style属性是一个dict
- style中的属性采用骆驼命名法
- HTML里的class,与dash里的className(或class_name)相对应
- 像素单位的样式属性可以仅作为数字提供,而不需要像素单位px
下面给出一个直观的例子:
from dash import html
html.Div([
html.Div('Example Div', style={'color': 'blue', 'fontSize': 14}),
html.P('Example P', className='my-class', id='my-p-element')
], style={'marginBottom': 50, 'marginTop': 25})
上述代码将对应下面的html:
<div style="margin-bottom: 50px; margin-top: 25px;">
<div style="color: blue; font-size: 14px">
Example Div
</div>
<p class="my-class", id="my-p-element">
Example P
</p>
</div>
Dash的HTML组件有一个n_clicks属性,它是一个整数,表示元素被点击的次数。您可以使用n_clicks触发回调,并在回调逻辑中使用n_click的值。在下面例子中,我们从id为click-div的html.Div中捕获n_clicks值,并输出到id为click-output的html.P元素。n_clicks使用事件监听器来捕获元素上的用户点击事件,并递增n_clicks值。
from dash import Dash, html, Input, Output, callback
app = Dash(__name__)
app.layout = html.Div(
[
html.Div(
"Div with n_clicks event listener",
id="click-div",
style={"color": "red", "font-weight": "bold"},
),
html.P(id="click-output"),
]
)
@callback(
Output("click-output", "children"),
Input("click-div", "n_clicks")
)
def click_counter(n_clicks):
return f"The html.Div above has been clicked this many times: {n_clicks}"
app.run(debug=True)
2.1.2 常用标签
由于不同类的属性名称定义都是类似的,除特殊属性外以下不再赘述(详细属性说明与用法见官方文档Dash for Python Documentation)。
①常用属性:
- children:此组件的子组件,可选
- id:组件的id,用于标识回调中的dash组件。id需要在应用程序中的所有组件中都是唯一的。
- n_clicks:一个整数,表示此元素被点击的次数,默认为0
- href:链接资源的url
- media:指定链接资源所针对的媒体的提示
- className:通常与css一起使用,为具有公共属性的元素设置样式
- style:定义css样式,这些样式将覆盖之前设置的样式
- title:将鼠标悬停在元素上时在工具提示中显示的文本
②常用标签:
dash组件 | html标签 | 用法 |
html.A | <a> | <a> 标签定义超链接,用于从一个页面链接到另一个页面。<a> 元素最重要的属性是 href 属性,它指定链接的目标。 |
html.B | <b> | 用于定义粗体的文本 |
html.Br | <br> | 换行 |
html.Button | <button> | 定义一个按钮 |
html.Div | <div> | 定义 HTML 文档中的一个分隔区块或者一个区域部分。<div>标签常用于组合块级元素,以便通过 CSS 来对这些元素进行格式化。<div> 元素经常与 CSS 一起使用,用来布局网页。 |
html.Footer | <footer> | 定义文档或者文档的一部分区域的页脚 |
html.Form | <form> | 创建供用户输入的 HTML 表单 |
html.Header | <header> | 定义文档或者文档的一部分区域的页眉 |
html.H1-html.H6 | <h1>-<h6> | <h1> - <h6> 标签被用来定义 HTML 标题。 <h1> 定义重要等级最高的标题。<h6> 定义重要等级最低的标题。 |
html.Hr | <hr> | 定义 HTML 页面中的主题变化(比如话题的转移),并显示为一条水平线 |
html.I | <i> | 斜体文本 |
html.Iframe | <iframe> | 规定一个内联框架。一个内联框架被用来在当前 HTML 文档中嵌入另一个文档。 |
html.Img | <img> | 定义 HTML 页面中的图像 |
html.Li | <li> | 定义列表项目 |
html.Nav | <nav> | 定义导航链接的部分 |
html.P | <p> | 定义段落 |
html.Progress | <progress> | 定义运行中的任务进度(进程) |
html.Script | <script> | 定义客户端脚本 |
html.Span | <span> | 用于对文档中的行内元素进行组合 |
html.Table | <table> | 表格 |
html.Tbody | <tbody> | 用于组合 HTML 表格的主体内容 |
html.Td | <td> | 定义 HTML 表格中的标准单元格 |
html.Tfoot | <tfoot> | 用于组合 HTML 表格的页脚内容 |
html.Th | <th> | 定义 HTML 表格中的表头单元格 |
html.Thead | <thead> | 用于组合 HTML 表格的表头内容 |
html.Tr | <tr> | 定义 HTML 表格中的行 |
html.Ul | <ul> | 定义无序列表 |
2.1.3 一些应用
import dash
from dash import html
app = dash.Dash(
__name__,
)
app.layout = html.Div(
[
html.Header('Home'),
html.H1('Good Morning!'),
html.Button('点击', id='input-click'),
html.Hr(),
html.A('跳转百度', href='www.baidu.com'),
html.Br(),
html.Nav(
html.Ol(
[
html.Li('Bikes'),
html.Li('BMX'),
]
),
),
html.Table(
[
html.Caption('Front-end web developer course 2021'),
html.Thead(
[
html.Tr(
[
html.Th('Person', scope='col'),
html.Th('Most interest in', scope='col'),
html.Th('Age', scope='col'),
]
),
]
),
html.Tbody(
[
html.Tr(
[
html.Th('Chris', scope='row'),
html.Td('HTML tables'),
html.Td('22'),
]
),
html.Tr(
[
html.Th('Dennis', scope='row'),
html.Td('Web accessibility'),
html.Td('45'),
]
),
html.Tr(
[
html.Th('Sarah', scope='row'),
html.Td('Javascript frameworks'),
html.Td('22'),
]
),
]
),
html.Tfoot(
[
html.Tr(
[
html.Th('Average', scope='row', colSpan='2'),
html.Td('33'),
]
),
]
),
]
),
]
)
if __name__ == '__main__':
app.run(port=5001, debug=True)
2.2 核心组件
2.2.1 概要
dash的核心组件模块(dash-core-component)封装了前端常用的交互式组件,包括下拉菜单、按钮、滑块等。引入方式:
from dash import dcc
# import dash_core_component(旧的引入方式)
2.2.2 常用组件
① 选择框(dcc.CheckList)
② 日期选择(dcc.DatePickerSingle)
③ 下载(dcc.Download)
常与Button一起使用
from dash import Dash, dcc, html, Input, Output, callback
app = Dash(__name__)
app.layout = html.Div([
html.Button("Download Text", id="btn-download-txt"),
dcc.Download(id="download-text")
])
@callback(
Output("download-text", "data"),
Input("btn-download-txt", "n_clicks"),
prevent_initial_call=True,
)
def func(n_clicks):
return dict(content="Hello world!", filename="hello.txt")
if __name__ == "__main__":
app.run(debug=True)
④ 下拉菜单(dcc.Dropdown)
⑤ 图片(dcc.Graph)
Graph组件用于展示plotly图形,并用figure参数接收。
from dash import dcc, html, Dash
import plotly.express as px
app = Dash(
__name__
)
server = app.server
df = px.data.iris()
fig = px.scatter(df, x='sepal_width', y='sepal_length')
app.layout = html.Div(
[
dcc.Graph(figure=fig)
]
)
if __name__ == '__main__':
app.run_server()
⑥ 滑块(dcc.Slider)
⑦ 分页(dcc.Tabs、dcc.Tab)