Dash 教程
Dash 教程
Dash 是 Plotly 开发的 Python 框架,用纯 Python 构建交互式数据可视化 Web 应用。无需前端知识,即可创建包含图表、表格、表单和回调交互的仪表盘。
核心特性
- 纯 Python:前后端全部用 Python 编写
- 交互式:基于 Plotly 的丰富图表
- 回调机制:组件联动,数据实时更新
- 快速开发:几分钟搭建数据应用原型
- 企业级:可部署到服务器/容器
典型应用
- 数据可视化仪表盘
- 业务监控大屏
- 数据分析工具
- 机器学习模型演示
第一个应用
# app.py
from dash import Dash, html, dcc
app = Dash(__name__)
app.layout = html.Div([
html.H1("我的第一个 Dash 应用"),
dcc.Graph(
figure={
"data": [
{"x": [1, 2, 3], "y": [4, 1, 2],
"type": "bar", "name": "数据"}
]
}
)
])
if __name__ == "__main__":
app.run(debug=True)
# 访问 http://127.0.0.1:8050
Dash 简介
Dash 简介
1. 什么是 Dash
Dash 由 Plotly 公司开发,2017 年开源,构建于 Flask、Plotly.js 和 React 之上,允许 Python 开发者用声明式语法构建 Web 数据应用。
2. 架构
┌──────────────────────────┐
│ 浏览器(前端) │
│ React 渲染 Dash 组件 │
└───────────┬──────────────┘
│ HTTP / WebSocket
┌───────────┴──────────────┐
│ Flask 服务器(后端) │
│ Python 回调函数 │
└──────────────────────────┘
3. 核心概念
- Layout:页面布局,用 Python 定义 HTML/组件树
- Components:内置组件(图表、下拉框、滑块、表格)
- Callbacks:回调函数,实现组件交互
- State:组件状态管理
4. 组件库
dash_core_components(dcc): 图表、下拉框、滑块、输入 dash_html_components(html): HTML 标签封装 dash_bootstrap_components: Bootstrap 样式组件 dash_table: 交互式数据表格 plotly.graph_objects: 高级图表
5. 版本说明
Dash 1.x(旧):dash_core_components 等独立包 Dash 2.x(当前):dash 内置 dcc/html,推荐 Dash 3.x:进一步整合 # 安装 pip install dash pip install dash-bootstrap-components pip install pandas plotly
Dash 安装
Dash 安装
1. 环境准备
# Python 3.8+ python --version # 创建虚拟环境(推荐) python -m venv venv source venv/bin/activate # macOS/Linux venv\Scripts\activate # Windows
2. 安装 Dash
# 基础安装 pip install dash # 完整安装(含 pandas、plotly) pip install dash[all] # 常用附加 pip install dash-bootstrap-components pip install dash-table pip install openpyxl # Excel 支持 # 验证 python -c "import dash; print(dash.__version__)"
3. 项目结构
my-dash-app/ ├── app.py # 主应用 ├── assets/ # 静态资源 │ ├── style.css # 自定义样式 │ └── logo.png ├── data/ # 数据文件 └── requirements.txt
4. 第一个应用
# app.py
from dash import Dash, html
app = Dash(__name__)
app.layout = html.H1("Hello Dash!")
if __name__ == "__main__":
app.run(debug=True)
# 运行
python app.py
# 访问 http://127.0.0.1:8050
5. Jupyter Notebook 使用
# Jupyter 中 from dash import Dash app = Dash() # 用 JupyterDash 或嵌入模式 import dash dash.JupyterDash(app.layout) # 需要安装 jupyter-dash # pip install jupyter-dash
Dash 第一个应用
Dash 第一个应用
1. 布局基础
from dash import Dash, html, dcc
app = Dash(__name__)
app.layout = html.Div([
# 标题
html.H1("数据看板", style={"textAlign": "center"}),
# 段落
html.P("这是一个示例应用"),
# 输入框
dcc.Input(id="name-input", value="Dash",
type="text"),
# 输出
html.Div(id="output-div"),
# 图表
dcc.Graph(
id="example-graph",
figure={
"data": [
{"x": [1, 2, 3], "y": [4, 1, 2],
"type": "bar", "name": "A"},
{"x": [1, 2, 3], "y": [2, 4, 5],
"type": "bar", "name": "B"}
],
"layout": {"title": "示例图表"}
}
)
])
if __name__ == "__main__":
app.run(debug=True)
2. 添加回调
from dash import Input, Output
@app.callback(
Output("output-div", "children"),
Input("name-input", "value")
)
def update_output(value):
return f"你好, {value}!"
# 输入组件 id 与 Output/Input 对应
# 回调函数参数顺序与 Input 顺序一致
3. 运行与调试
# 热重载:debug=True 自动刷新 app.run(debug=True) # 指定端口 app.run(debug=True, port=8080) # 局域网访问 app.run(debug=True, host="0.0.0.0", port=8050)
4. 多页面应用结构
# 用 dcc.Location + 回调控制页面
app.layout = html.Div([
dcc.Location(id="url", refresh=False),
html.Div(id="page-content")
])
@app.callback(
Output("page-content", "children"),
Input("url", "pathname")
)
def render_page(pathname):
if pathname == "/about":
return html.H1("关于页")
return html.H1("首页")
Dash 核心组件
Dash 核心组件
1. dcc 组件总览
dcc.Graph 图表(Plotly) dcc.Dropdown 下拉选择 dcc.Slider 滑块 dcc.Input 文本输入 dcc.Textarea 多行文本 dcc.Checklist 复选框 dcc.RadioItems 单选 dcc.DatePickerRange 日期范围 dcc.Tabs 标签页 dcc.Store 浏览器端存储 dcc.Interval 定时器 dcc.Loading 加载指示
2. 常用组件示例
app.layout = html.Div([
# 下拉框
dcc.Dropdown(
id="city-dropdown",
options=[
{"label": "北京", "value": "beijing"},
{"label": "上海", "value": "shanghai"},
{"label": "广州", "value": "guangzhou"}
],
value="beijing",
multi=False
),
# 滑块
dcc.Slider(
id="price-slider",
min=0, max=100, step=5, value=50,
marks={0: "0", 50: "50", 100: "100"}
),
# 输入
dcc.Input(id="search-input", type="text",
placeholder="搜索...", debounce=True),
# 日期范围
dcc.DatePickerRange(
id="date-range",
start_date="2026-01-01",
end_date="2026-12-31"
),
# 标签页
dcc.Tabs([
dcc.Tab(label="概览", value="overview"),
dcc.Tab(label="详情", value="detail")
]),
# 定时刷新
dcc.Interval(id="interval", interval=5000)
])
3. 组件属性约定
# 所有组件: id 唯一标识
# 值属性: value(值)、options(选项)
# 样式: style(dict)、className
# options 格式
options=[{"label": "显示文本", "value": "值"}]
# style 格式(驼峰)
style={"marginTop": "20px", "color": "blue"}
Dash 常用 HTML 组件
Dash 常用 HTML 组件
1. html 组件
from dash import html
# 布局容器
html.Div(children=[...], style={...})
html.Main(...) / html.Section(...) / html.Article(...)
html.Header(...) / html.Footer(...) / html.Nav(...)
# 标题
html.H1("一级标题")
html.H2("二级标题") ... html.H6
# 文本
html.P("段落文本")
html.Span("行内文本")
html.Strong("加粗") / html.Em("斜体")
html.Code("代码")
# 链接与图片
html.A("链接", href="https://example.com")
html.Img(src="/assets/logo.png", style={"width": "100px"})
# 列表
html.Ul([
html.Li("项1"),
html.Li("项2")
])
# 表格
html.Table([
html.Tr([
html.Th("列1"), html.Th("列2")
]),
html.Tr([
html.Td("值1"), html.Td("值2")
])
])
# 按钮
html.Button("点击", id="btn", n_clicks=0)
2. 样式设计
# 内联样式
html.Div(
style={
"backgroundColor": "#f5f5f5",
"padding": "20px",
"borderRadius": "8px"
}
)
# className + assets/style.css
html.Div("内容", className="card")
# assets/style.css
.card {
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
3. 布局技巧
# 使用 Bootstrap 栅格
from dash_bootstrap_components import Row, Col, Container
app.layout = Container([
Row([
Col(html.H3("左侧"), width=4),
Col(html.H3("右侧"), width=8)
])
])
Dash 常用交互组件
Dash 常用交互组件
1. 下拉框联动
from dash import Dash, html, dcc, Input, Output
import pandas as pd
app = Dash(__name__)
# 模拟数据
data = {
"北京": [1, 2, 3, 4],
"上海": [5, 6, 7, 8],
"广州": [9, 10, 11, 12]
}
app.layout = html.Div([
dcc.Dropdown(
id="city",
options=[{"label": c, "value": c}
for c in data.keys()],
value="北京"
),
dcc.Graph(id="chart")
])
@app.callback(
Output("chart", "figure"),
Input("city", "value")
)
def update_chart(city):
values = data[city]
return {
"data": [{"y": values, "type": "bar",
"name": city}],
"layout": {"title": f"{city} 数据"}
}
if __name__ == "__main__":
app.run(debug=True)
2. 滑块与输入
app.layout = html.Div([
dcc.Slider(id="n", min=1, max=10, value=5),
html.Div(id="output")
])
@app.callback(
Output("output", "children"),
Input("n", "value")
)
def show(n):
return f"当前值: {n}, 平方: {n ** 2}"
3. 多输入多输出
@app.callback(
Output("output-1", "children"),
Output("output-2", "children"),
Input("input-1", "value"),
Input("input-2", "value")
)
def combine(a, b):
return f"第一个: {a}", f"第二个: {b}"
4. State 状态组件
# State:只读取不触发回调
from dash import State
app.layout = html.Div([
dcc.Input(id="name", value=""),
html.Button("提交", id="submit", n_clicks=0),
html.Div(id="result")
])
@app.callback(
Output("result", "children"),
Input("submit", "n_clicks"),
State("name", "value")
)
def submit(n, name):
if n == 0:
return "尚未提交"
return f"提交成功: {name}"
5. 阻止初始回调
# prevent_initial_call: 避免页面加载时触发
@app.callback(
Output("out", "children"),
Input("btn", "n_clicks"),
prevent_initial_call=True
)
def on_click(n):
return f"点击了 {n} 次"
Dash 回调函数
Dash 回调函数
1. 回调基础
from dash import Dash, html, dcc, Input, Output
app = Dash(__name__)
app.layout = html.Div([
dcc.Input(id="num1", type="number", value=0),
dcc.Input(id="num2", type="number", value=0),
html.Div(id="result")
])
@app.callback(
Output("result", "children"),
Input("num1", "value"),
Input("num2", "value")
)
def add(a, b):
try:
return f"结果: {int(a) + int(b)}"
except (TypeError, ValueError):
return "请输入数字"
if __name__ == "__main__":
app.run(debug=True)
2. 回调模式
# 1. 简单:一个输入 → 一个输出
# 2. 多输入:多个 Input 参数按顺序传递
# 3. 多输出:多个 Output + 元组返回
# 4. 链式:一个回调的输出是另一个的输入
# 5. 条件更新
@app.callback(
Output("out", "children"),
Input("dropdown", "value"),
Input("slider", "value")
)
def update(dd, sl):
if dd == "hidden":
raise dash.exceptions.PreventUpdate # 不更新
return f"{dd}: {sl}"
3. 回调依赖组件
# 动态创建组件的回调
app.layout = html.Div([
dcc.Dropdown(id="type",
options=[
{"label": "柱状", "value": "bar"},
{"label": "折线", "value": "line"},
{"label": "饼图", "value": "pie"}
],
value="bar"),
html.Div(id="container")
])
@app.callback(
Output("container", "children"),
Input("type", "value")
)
def create_component(chart_type):
if chart_type == "bar":
return dcc.Graph(figure={"data": [
{"x": [1,2,3], "y": [3,1,2], "type": "bar"}]})
elif chart_type == "line":
return dcc.Graph(figure={"data": [
{"x": [1,2,3], "y": [3,1,2], "type": "line"}]})
return dcc.Graph(figure={"data": [
{"labels": ["A","B"], "values": [3,1],
"type": "pie"}]})
4. 错误处理
import dash
@app.callback(...)
def update(...):
if value is None:
raise dash.exceptions.PreventUpdate
try:
result = risky_operation(value)
except Exception as e:
return f"出错: {e}"
return result
Dash Plotly
Dash Plotly
1. Plotly 图表对象
import plotly.graph_objects as go
import plotly.express as px
# go 方式(精细控制)
fig = go.Figure(
data=go.Scatter(
x=[1, 2, 3],
y=[3, 1, 2],
mode="lines+markers",
name="曲线"
),
layout=go.Layout(
title="示例",
xaxis_title="X",
yaxis_title="Y"
)
)
# px 方式(便捷)
df = px.data.iris()
fig = px.scatter(df, x="sepal_width",
y="sepal_length",
color="species")
2. 图表类型
go.Scatter 折线/散点 go.Bar 柱状 go.Pie 饼图 go.Heatmap 热力图 go.Box 箱线图 go.Histogram 直方图 go.Surface 3D 曲面 go.Scatter3d 3D 散点 go.Mapbox 地图 go.Indicator 仪表盘 go.Table 表格
3. 在 Dash 中使用
from dash import Dash, dcc
import plotly.graph_objects as go
app = Dash(__name__)
fig = go.Figure()
fig.add_trace(go.Scatter(
x=[1, 2, 3], y=[4, 1, 2],
mode="lines+markers",
name="系列1"
))
fig.update_layout(
title="Plotly 图表",
template="plotly_white", # 主题
hovermode="x unified"
)
app.layout = dcc.Graph(id="main-chart", figure=fig)
if __name__ == "__main__":
app.run(debug=True)
4. 图表配置
fig.update_layout(
title="标题",
xaxis_title="X 轴",
yaxis_title="Y 轴",
legend_title="图例",
template="plotly_dark", # plotly_dark/plotly_white/seaborn
autosize=True,
margin=dict(l=40, r=40, t=60, b=40),
showlegend=True
)
fig.update_traces(
marker_size=10,
line_width=2
)
Dash 动态更新图表
Dash 动态更新图表
1. 数据过滤联动
import pandas as pd
import plotly.express as px
from dash import Dash, html, dcc, Input, Output
app = Dash(__name__)
# 模拟数据
df = pd.DataFrame({
"月份": ["1月","2月","3月","4月","5月","6月"],
"销售额": [120, 150, 130, 180, 160, 210],
"利润": [30, 40, 35, 50, 45, 60],
"地区": ["北","南","北","南","北","南"]
})
app.layout = html.Div([
dcc.Dropdown(
id="metric",
options=[
{"label": "销售额", "value": "销售额"},
{"label": "利润", "value": "利润"}
],
value="销售额"
),
dcc.Graph(id="trend-chart")
])
@app.callback(
Output("trend-chart", "figure"),
Input("metric", "value")
)
def update_chart(metric):
fig = px.line(df, x="月份", y=metric,
title=f"{metric} 趋势")
fig.update_layout(
template="plotly_white",
yaxis_title=metric
)
return fig
if __name__ == "__main__":
app.run(debug=True)
2. 定时刷新
import random
from dash import dcc
app.layout = html.Div([
dcc.Graph(id="live-chart"),
dcc.Interval(id="timer", interval=2000) # 2秒
])
@app.callback(
Output("live-chart", "figure"),
Input("timer", "n_intervals")
)
def update_live(n):
import plotly.graph_objects as go
x = list(range(10))
y = [random.randint(0, 100) for _ in x]
fig = go.Figure(go.Scatter(x=x, y=y, mode="lines"))
fig.update_layout(
title=f"实时数据(第{n}次更新)",
yaxis_range=[0, 100]
)
return fig
3. 点击图表联动
@app.callback(
Output("detail", "children"),
Input("chart", "clickData")
)
def show_detail(click_data):
if click_data is None:
return "点击图表查看详情"
point = click_data["points"][0]
return (f"X: {point['x']}, "
f"Y: {point['y']}, "
f"系列: {point['curveNumber']}")
4. 选择区间联动
@app.callback(
Output("selected", "children"),
Input("chart", "selectedData")
)
def show_selection(selected):
if selected is None:
return "拖拽选择数据点"
pts = selected["points"]
return f"选中 {len(pts)} 个点"
Dash 多页面布局
Dash 多页面布局
1. 单文件多页
from dash import Dash, html, dcc, Input, Output
app = Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
dcc.Location(id="url", refresh=False),
html.Nav([
dcc.Link("首页", href="/"),
" | ",
dcc.Link("数据", href="/data"),
" | ",
dcc.Link("关于", href="/about")
]),
html.Hr(),
html.Div(id="page-content")
])
@app.callback(
Output("page-content", "children"),
Input("url", "pathname")
)
def display_page(pathname):
if pathname == "/data":
return html.Div([
html.H2("数据页"),
dcc.Graph(id="data-chart")
])
elif pathname == "/about":
return html.H2("关于我们")
return html.H2("首页")
if __name__ == "__main__":
app.run(debug=True)
2. 多文件架构
myapp/
├── app.py # 主入口
├── index.py # 组装 pages
├── pages/
│ ├── home.py
│ ├── data.py
│ └── about.py
└── assets/
└── style.css
# pages/home.py
import dash
from dash import html
dash.register_page(__name__, path="/", name="首页")
layout = html.Div([
html.H1("首页"),
html.P("欢迎使用多页面应用")
])
# app.py
import dash
from dash import Dash, html, dcc
import pages.home, pages.data, pages.about
app = Dash(__name__, use_pages=True)
app.layout = html.Div([
html.Nav([
dcc.Link(page["name"], href=page["path"])
for page in dash.page_registry.values()
]),
html.Hr(),
dash.page_container # 页面渲染位置
])
if __name__ == "__main__":
app.run(debug=True)
3. 页面元数据
dash.register_page(
__name__,
path="/data",
name="数据页",
title="数据分析",
description="数据展示页",
icon="chart-line",
order=2 # 导航排序
)
Dash 样式设计
Dash 样式设计
1. assets 目录
# 自动加载 assets/ 下的 css/js
myapp/
└── assets/
├── style.css # 自定义样式
├── custom.js # 自定义 JS
└── logo.png
# 文件名规范: 数字前缀控制加载顺序
# 01_header.css 02_layout.css
2. Bootstrap 组件
pip install dash-bootstrap-components
import dash_bootstrap_components as dbc
app = Dash(__name__,
external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader("数据概览"),
dbc.CardBody([
html.H4("指标", className="card-title"),
html.P("描述", className="card-text"),
dbc.Button("查看", color="primary")
])
])
], width=4),
dbc.Col([
dcc.Graph(id="chart")
], width=8)
], className="mt-4")
])
3. 主题选择
# 可用主题 dbc.themes.BOOTSTRAP dbc.themes.DARKLY # 深色 dbc.themes.CYBORG dbc.themes.FLATLY dbc.themes.SUPERHERO dbc.themes.COSMO dbc.themes.SLATE dbc.themes.SOLAR
4. 自定义 CSS 示例
/* assets/style.css */
body {
font-family: "PingFang SC", sans-serif;
background-color: #f8f9fa;
}
.card {
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
h1 {
color: #2c3e50;
font-weight: 600;
}
.navbar {
margin-bottom: 20px;
}
/* Dash 组件样式 */
.Select-control {
border-radius: 6px;
}
Dash 部署与资源
Dash 部署与资源
1. 部署到服务器
# 1. 生产模式运行
app.run(host="0.0.0.0", port=8050, debug=False)
# 2. Gunicorn(推荐)
pip install gunicorn
gunicorn app:server -b 0.0.0.0:8050 --workers 4
# 3. 配置 Nginx 反向代理
location / {
proxy_pass http://127.0.0.1:8050;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
# 4. Docker 部署
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8050
CMD ["gunicorn", "app:server", "-b", "0.0.0.0:8050"]
2. 服务器模式说明
# app.py 中 server 变量 app = Dash(__name__) server = app.server # Flask 实例 # gunicorn 使用 app:server 启动
3. 性能优化
# 1. 数据预加载
# 避免回调中重复读数据库
# 2. 大 DataFrame 处理
# 用 dcc.Store 存数据,前端传递
# 3. 限制图表数据点
fig.update_layout(autosize=False)
# 4. 关闭不必要的回调触发
prevent_initial_call=True
# 5. 使用缓存
pip install diskcache
from diskcache import Cache
cache = Cache("cache_dir")
4. 安全注意事项
# 1. 生产环境关闭 debug # 2. 敏感数据不传到前端 # 3. 用户输入校验与转义 # 4. 使用 HTTPS # 5. 认证(如 Flask-Login) # 6. 权限控制(回调中校验)
5. 学习资源
官网: dash.plotly.com 示例库: dash-example-index.herokuapp.com 中文文档: dash.plotly.com(有中文翻译) GitHub: plotly/dash 社区: community.plotly.com 部署平台: Dash Enterprise / Render / Heroku
Dash 让 Python 开发者快速构建数据应用,是数据分析师的必备工具。
第 1 / 13 节