本文为https://github.com/CNFeffery/DataScienceStudyNotes的学习笔记,部分源码来源于此仓库。
本期内容主要为基础概念、web布局方法和交互回调。
文章目录
- Dash的主要模块
- Highlight
- layout
- callback
- 惰性交互
- 阻止初次回调
- 忽略回调匹配错误
- 控制部分回调输出不更新
- 获取本轮回调的输入信息
- 浏览器端执行回调
Dash的主要模块
dash.dcc
(Dash Core Components)和dash_bootstrap_components
提供核心的components,如按钮、下拉菜单等。dash.html
是对html标记语言
的Python封装。可以将dcc
、dbc
中的组件放入html
容器中dash.Input
、dash.Output
是进行callback的必用组件plotly.express
和plotly.graph_objects
是plotly的绘图库。一般将plotly画好的图像对象传入dcc.Graph()
的figure
参数中。dcc.Graph()
是dash渲染可交互图像的方法。dash.dash_tabel
是展示表格的对象,具有类似excel的功能。
import dash
from dash import Dash, html, dash_table, dcc, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objects as go
Highlight
layout
:layout
负责呈现前端的页面的布局,一般为dash.html
模块中的对象。通过传入多个元素或多层嵌套,可以构建更复杂的页面内容。callback
:callback
负责处理后端的数据交互。一般使用装饰器的形式,实现前后端异步通信交互。- 静态/交互部件: 静态部件主要为
html
中的组件,比如分割线静态图片等等。可交互的组件一般为dcc
和dbc
中的部件,它们通过callback
实现通信交互。这些组件通过layout
组织在一起,共同组成完整的Dash应用。 - plotly图像: 理论上其他图像也可以放入Dash应用中,但作为Dash一奶同胞的兄弟,plotly天生可与Dash完美融合。
# 第一个Dash应用
# 展示一个Dash应用最基本的流程:实例化、添加组件、启动应用(实际上还有回调,后面会看到)
from dash import Dash
import dash_html_components as html
import webbrowser
# Dash实例化,所有Dash应用都应当如此
app = Dash(
__name__, # __name__不能省略
external_stylesheets=['css/bootstrap.min.css'] # 规定了CSS的样式,这行是可选的
)
app.layout = html.H1("第一个Dash应用") # 在布局中添加组件
if __name__ == '__main__':
webbrowser.open("http://127.0.0.1:8050") # 自动打开web应用。这句不是必须的,你可以手动打开。
app.run_server() # 启动你的Dash服务,在浏览器中打开http://127.0.0.1:8050/可以看到你的web页面。
# 在终端中按下CTRL+C关闭服务
layout
dbc.Container()
是组织页面布局的容器,它将页面看作N行12列
的表格布局,通过设置component占多少列来设置组件的宽度。
行部件dbc.Row()
组成列表传入dbc.Container()
中,按照列表顺序从上至下排列。
列部件dbc.Col()
组成列表传入行部件dbc.Row()
中,按照列表顺序从左至右排列。
一个
dbc.Row()
中的部件宽度正好为12时充满整行,小于12时右侧则会空出剩余的宽度,
大于12时将溢出的部件挤到下一行。
总而言之,嵌套等级为:Container() > Row() > Col()
。如下所示:
app.layout = dbc.Container(
[
dbc.Row(dbc.Col(html.Div("A single column"))),
dbc.Row(
[
dbc.Col(html.Div("One of three columns"),width=4),
dbc.Col(html.Div("One of three columns"),width=4),
dbc.Col(html.Div("One of three columns"),width=4),
]
),
]
)
展示的效果:
ps:以上示例是官网展示的效果,但实际运行并未如期运行。知道原因大佬请指教。
-
dbc.Row()
可接受的常用参数还有justify
,表示同一行的多个列元素设置对齐方式,可选项有'start'
、'center'
、'end'
、'between'
以及'around'
五种 -
dbc.Col()
可接受的常用参数还有以下两个:
order
: 代表同一个Row
下的顺序。可接受的输入为:frist
、last
和1到12的整数,分别表示第一个,第十二个,和一到十二的任意一个位置。offset
: 代表对应Col()
部件左侧增加对应的宽度,可接受的参数为1到12。
callback
每个component都有id
,这是这个component的唯一索引。
callback
一定是多个components之间的响应(有输入组件和输出组件),通过先输出,后输入的顺序传入对象。
- 单个输入输出直接传入参数。
- 多个输出输出,组成列表后再传入参数。只要有一个输入变化就会触发回调。
如下所示:
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
app = dash.Dash(
__name__,
external_stylesheets=['css/bootstrap.min.css']
)
app.layout = html.Div(
[
html.Br(),
html.Br(),
html.Br(),
dbc.Container(
[
dbc.Row(
[
dbc.Col(dbc.Input(id='input-lastname'), width=3),
dbc.Col(html.P('+'), width=1),
dbc.Col(dbc.Input(id='input-firstname'), width=3),
],
justify='start'
),
html.Hr(),
dbc.Label(id='output1'),
html.Br(),
dbc.Label(id='output2')
]
)
]
)
# 回调以装饰器的形式声明
@app.callback(
[Output('output1', 'children'), # 多输出时用列表的形式传入,单输出则可以直接传入。
Output('output2', 'children')], # Output中第一个参数为输出组件id,第二参数为做出响应的属性。
[Input('input-lastname', 'value'), # 多输入时用列表的形式传入,单输入则可以直接传入。
Input('input-firstname', 'value')] # Iutput中第一个参数为输出组件id,第二参数为需要监控的属性。
)
def input_to_output(lastname, firstname): # 这里定义回调函数的具体内容
try:
return '完整姓名:' + lastname + firstname, f'姓名长度为{len(lastname+firstname)}'
except:
return '等待输入...', '等待输入...' # 注意返回值,不是列表形式,多个元素直接返回即可
if __name__ == '__main__':
app.run_server()
惰性交互
以上的例子中,只要输入部件属性改变,回调函数会立马触发。
但有时我们不希望回调函数立马触发,而是在我们一声号令后(比如按下某个按钮),回调函数再触发,这样的回调称为惰性交互。
惰性交互实现非常简单,在callback
中增加State()
对象,如下所示:
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
# 其余部分省略...
@app.callback(
Output('output-value', 'children'),
Input('state-button', 'n_clicks'), # 注意这里Input中的id是触发按钮。
State('input-value', 'value') # State中的id才是真正输入组件的id,
# 相当于State替换了立即回调模式中的Input。
)
def input_to_output(n_clicks, value):
if n_clicks:
return value.upper()
# 其余部分省略....
阻止初次回调
初次回调一般跟输入框有关系。因为首次打开的网页输入框都是空的,如果回调函数不支持空的输入就会产生错误。
在callback
中设置prevent_initial_call=True
即可阻止初始回调,避免错误。
# 其余部分省略
@app.callback(
Output('output1', 'children'),
Input('input1', 'value'),
prevent_initial_call=True # 阻止初次回调
)
def callback1(value):
return int(value) ** 2
忽略回调匹配错误
在很多时候,我们需要在发生某些交互回调时,才创建一些具有指定id
的部件,如果在初始化时触发了回调,则会发生错误。
在callback
中设置suppress_callback_exceptions=True
即可忽略回调时的id
匹配错误,避免错误。
@app.callback(
Output('output_desc', 'children'),
Input('output_dropdown', 'options'),
prevent_initial_call=True # 忽略回调匹配错误
)
def callback2(options):
pass
控制部分回调输出不更新
dash.no_update
作为Output()
的返回值,则对应的组件属性就不会更新。如:
# 其余部分省略
@app.callback(
[Output('record-1', 'children'),
Output('record-2', 'children'),
Output('record-n', 'children'),
],
Input('button', 'n_clicks'),
prevent_initial_call=True
)
def record_click_event(n_clicks):
if n_clicks == 1:
return (
'第1次点击:{}'.format(time.strftime('%H:%M:%S', time.localtime(time.time()))),
dash.no_update, # 第1次点击, 2和3不参与回调
dash.no_update
)
elif n_clicks == 2:
return (
dash.no_update, # 第2次点击, 1和3不参与回调
'第2次点击:{}'.format(time.strftime('%H:%M:%S', time.localtime(time.time()))),
dash.no_update
)
elif n_clicks >= 3:
return (
dash.no_update, # 3次及以上点击, 1和2不参与回调
dash.no_update,
'第3次及以上点击:{}'.format(time.strftime('%H:%M:%S', time.localtime(time.time()))),
)
获取本轮回调的输入信息
多个输入的回调,一个输入部件就会触发回调。若想知道是哪个输入触发了回调,就要使用dash.callback_context
。
dash.callback_context.triggered
: 字典;触发本轮回调组件的id
和属性。dash.callback_context.inputs
: 字典;所有输入组件目前的id
和属性。
@app.callback(
[Output('A-output', 'children'),
Output('B-output', 'children'),
Output('C-output', 'children'),
Output('raw-json', 'children')],
[Input('A', 'n_clicks'),
Input('B', 'n_clicks'),
Input('C', 'n_clicks')],
prevent_initial_call=True
)
def refresh_output(A_n_clicks, B_n_clicks, C_n_clicks):
# 获取本轮回调状态下的上下文信息
ctx = dash.callback_context
# 取出对应State、最近一次触发部件以及Input信息
ctx_msg = json.dumps({
'states': ctx.states,
'triggered': ctx.triggered,
'inputs': ctx.inputs
}, indent=2)
return A_n_clicks, B_n_clicks, C_n_clicks, ctx_msg
浏览器端执行回调
以上的回调是由服务器运算的,频繁触发会增加服务器压力。因此可以使用clientside_callback
和ClientsideFunction
运行js
脚本来将计算转移到浏览器端。示例省略(因为我不会JS)。