Học Dash qua ví dụ là cách giúp bạn nhìn thấy ngay từng thành phần quan trọng của Dash: từ app đầu tiên, biểu đồ đầu tiên, callback, dropdown, slider, bảng dữ liệu, upload file, cho đến một mini dashboard hoàn chỉnh.

Điểm mạnh của Dash là nó cho phép bạn sử dụng Python để xây dựng ứng dụng web có giao diện, có tương tác và có khả năng phục vụ người dùng cuối. Nếu Plotly giúp bạn tạo ra biểu đồ tương tác, thì Dash giúp bạn đặt những biểu đồ đó vào một sản phẩm có bố cục rõ ràng, có bộ lọc, nút bấm, bảng dữ liệu và luồng cập nhật hoàn chỉnh. Chính vì vậy, học Dash theo hướng “làm từng ví dụ nhỏ” sẽ giúp bạn hiểu framework này nhanh hơn rất nhiều so với chỉ đọc khái niệm.
Chuẩn bị môi trường
Bước 1: Tạo folder dự án
Tạo một thư mục, ví dụ:
mkdir hoc-dash-qua-vi-du
cd hoc-dash-qua-vi-du

Bước 2: Tạo môi trường ảo
python -m venv .venv
Bước 3: Cài thư viện
Dash docs hiện hướng dẫn cài cơ bản bằng:
pip install dash pandas plotly

Bước 4: Tạo file app đầu tiên
Tạo file app.py trong folder mới tạo để thực hiện code
from dash import Dash, html
app = Dash()
app.layout = html.Div("Xin chào Dash!")
if __name__ == "__main__":
app.run(debug=True)
Đây là ví dụ để thấy cấu trúc cốt lõi của một ứng dụng Dash: bao gồm tạo đối tượng app, khai báo giao diện bằng layout, rồi chạy server để hiển thị ứng dụng trên trình duyệt.

Code cơ bản gồm : tạo ứng dụng app, khai báo layout, và run server.
Bước 5: Chạy app
python app.py

rồi mở địa chỉ local, thường là: http://127.0.0.1:8050/

Hiển thị biểu đồ Plotly trong Dash
Nhúng một biểu đồ Plotly vào app bằng dcc.Graph.
# app.py
from dash import Dash, html, dcc
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
app = Dash()
app.layout = html.Div([
html.H3("Biểu đồ đầu tiên trong Dash"),
dcc.Graph(figure=fig)
])
if __name__ == "__main__":
app.run(debug=True)
Ví dụ này cho thấy mối liên kết trực tiếp giữa Dash và Plotly. Thành phần dcc.Graph chính là nơi để đưa figure Plotly vào giao diện web. Đây là bước rất quan trọng, vì nó giúp người học hiểu rằng Dash không thay thế Plotly, mà dùng Plotly làm lớp trực quan hóa trong ứng dụng.

Dropdown điều khiển nội dung
Tạo input đầu tiên bằng dcc.Dropdown.
# app.py
from dash import Dash, html, dcc
app = Dash()
app.layout = html.Div([
html.H3("Chọn quốc gia"),
dcc.Dropdown(
options=[
{"label": "Việt Nam", "value": "VN"},
{"label": "Nhật Bản", "value": "JP"},
{"label": "Hàn Quốc", "value": "KR"},
],
value="VN",
id="country-dropdown"
)
])
if __name__ == "__main__":
app.run(debug=True)
Đây là ví dụ giúp bạn làm quen với input component trong Dash. Dù chưa có callback, nhưng nó đặt nền cho tư duy xây app có giao diện điều khiển. Khi bắt đầu làm dashboard, dropdown gần như luôn là một trong những thành phần đầu tiên bạn sẽ dùng.

Callback cơ bản với Dropdown
Hiểu cơ chế callback của Dash.
# app.py
from dash import Dash, html, dcc, Input, Output, callback
app = Dash()
app.layout = html.Div([
dcc.Dropdown(
options=["Python", "R", "SQL"],
value="Python",
id="lang-dropdown"
),
html.Div(id="output-text")
])
@callback(
Output("output-text", "children"),
Input("lang-dropdown", "value")
)
def update_text(selected_lang):
return f"Bạn đã chọn: {selected_lang}"
if __name__ == "__main__":
app.run(debug=True)
Đây là ví dụ quan trọng để hiểu Dash hoạt động như thế nào. Khi giá trị trong dropdown thay đổi, phần output sẽ tự cập nhật theo. Chính cơ chế callback này tạo nên bản chất tương tác của Dash và giúp nó khác với một trang web tĩnh hay một notebook chỉ hiển thị kết quả cố định.
- @callback(…) là decorator của hàm bên dưới (update_text). @callback giúp hàm bên dưới biết sẽ nhận dữ liệu từ input nào và trả kết quả cho output nào.
- Phải đặt ngay trên hàm update_text
- Tên hàm update_text đặt tên gì cũng được
- Dash quan tâm chủ yếu đến Input, Output, và giá trị hàm trả về.
- update_text(selected_lang): Tham số đặt tên là select_lang hay tên gì cũng được, nó nhận dữ liệu từ đâu? Dash sẽ lấy thuộc tính value của component có id là “lang-dropdown” rồi truyền vào selected_lang

Slider điều khiển biểu đồ
Dùng dcc.Slider để điều chỉnh tham số.
# app.py
from dash import Dash, html, dcc, Input, Output, callback
import plotly.express as px
df = px.data.gapminder()
app = Dash()
app.layout = html.Div([
html.H3("Chọn năm"),
dcc.Slider(
min=df["year"].min(),
max=df["year"].max(),
step=5,
value=2007,
marks={int(y): str(y) for y in df["year"].unique()},
id="year-slider"
),
dcc.Graph(id="gapminder-chart")
])
@callback(
Output("gapminder-chart", "figure"),
Input("year-slider", "value")
)
def update_chart(selected_year):
dff = df[df["year"] == selected_year]
fig = px.scatter(
dff, x="gdpPercap", y="lifeExp",
size="pop", color="continent",
hover_name="country", log_x=True
)
return fig
if __name__ == "__main__":
app.run(debug=True)
Ví dụ này mở rộng tư duy từ dropdown sang một loại input khác là slider. Bạn sẽ thấy rằng Dash không chỉ hỗ trợ chọn giá trị theo danh sách, mà còn rất phù hợp với các tham số liên tục hoặc có thứ tự như năm, điểm số, ngưỡng lọc hay tỷ lệ. Đây là bước đầu để app bắt đầu mang dáng dấp của một dashboard phân tích.

Chọn khoảng thời gian bằng RangeSlider
Lọc dữ liệu theo khoảng năm.
# app.py
from dash import Dash, html, dcc, Input, Output, callback
import pandas as pd
import plotly.express as px
df = px.data.gapminder()
app = Dash()
app.layout = html.Div([
html.H3("Chọn khoảng năm"),
dcc.RangeSlider(
min=int(df["year"].min()),
max=int(df["year"].max()),
step=5,
value=[1992, 2007],
marks={int(y): str(y) for y in df["year"].unique()},
id="year-range"
),
dcc.Graph(id="range-chart")
])
@callback(
Output("range-chart", "figure"),
Input("year-range", "value")
)
def update_chart(year_range):
dff = df[(df["year"] >= year_range[0]) & (df["year"] <= year_range[1])]
fig = px.line(
dff.groupby("year", as_index=False)["lifeExp"].mean(),
x="year", y="lifeExp",
title="Tuổi thọ trung bình theo khoảng năm"
)
return fig
if __name__ == "__main__":
app.run(debug=True)
Ví dụ này phù hợp với các bài toán dữ liệu theo thời gian. Vì người dùng thường không chỉ muốn xem một mốc duy nhấ. Mà họ muốn quan sát xu hướng trên cả một giai đoạn tùy ý chọn.

Nút bấm “Xem kết quả”
Chỉ cập nhật khi người dùng chủ động bấm nút.
# app.py
from dash import Dash, html, dcc, Input, Output, State, callback
import plotly.express as px
df = px.data.tips()
app = Dash()
app.layout = html.Div([
dcc.Dropdown(
options=[{"label": d, "value": d} for d in sorted(df["day"].unique())],
value="Sun",
id="day-dropdown"
),
html.Button("Xem kết quả", id="run-button", n_clicks=0),
dcc.Graph(id="tips-chart")
])
@callback(
Output("tips-chart", "figure"),
Input("run-button", "n_clicks"),
State("day-dropdown", "value")
)
def update_chart(n_clicks, selected_day):
dff = df[df["day"] == selected_day]
fig = px.histogram(dff, x="total_bill", title=f"Tổng hóa đơn ngày {selected_day}")
return fig
if __name__ == "__main__":
app.run(debug=True)
Ví dụ này phản ánh một tình huống rất thực tế trong ứng dụng dữ liệu. Không phải lúc nào cũng nên cập nhật ngay khi input thay đổi; đôi khi người dùng cần chọn đủ tham số rồi mới bấm nút để chạy phân tích. Nhờ đó, app vừa rõ luồng thao tác hơn, vừa tránh cập nhật thừa.

DataTable cơ bản
Hiển thị dữ liệu dạng bảng.
# app.py
from dash import Dash, html
from dash import dash_table
import pandas as pd
df = pd.DataFrame({
"Năm": [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019],
"Doanh thu chi nhánh 1": [120, 150, 170, 210,180, 190, 220, 250, 300, 350],
"Doanh thu chi nhánh 2": [100, 130, 160, 200, 180, 190, 210, 240, 200, 190],
"Doanh thu chi nhánh 3": [90, 120, 150, 190, 180, 190, 215, 245, 232 , 222]
})
app = Dash()
app.layout = html.Div([
html.H3("Bảng dữ liệu cơ bản"),
dash_table.DataTable(
data=df.to_dict("records"),
columns=[{"name": c, "id": c} for c in df.columns],
page_size=5
)
])
if __name__ == "__main__":
app.run(debug=True)
Một dashboard tốt hiếm khi chỉ có biểu đồ. Bảng dữ liệu là phần rất quan trọng để người dùng xem chi tiết, đối chiếu số liệu và kiểm tra lại thông tin. Ví dụ này giúp bạn thấy Dash không chỉ mạnh ở trực quan hóa mà còn hỗ trợ tốt việc hiển thị dữ liệu dạng bảng ngay trong cùng một ứng dụng.

Đồng bộ biểu đồ và bảng dữ liệu
Khi chọn giá trị, cả chart và table cùng cập nhật.
# app.py
from dash import Dash, html, dcc, Input, Output, callback, dash_table
import plotly.express as px
df = px.data.gapminder().query("year == 2007")
app = Dash()
app.layout = html.Div([
dcc.Dropdown(
options=[{"label": c, "value": c} for c in sorted(df["continent"].unique())],
value="Asia",
id="continent-dropdown"
),
dcc.Graph(id="continent-chart"),
dash_table.DataTable(id="continent-table", page_size=5)
])
@callback(
Output("continent-chart", "figure"),
Output("continent-table", "data"),
Output("continent-table", "columns"),
Input("continent-dropdown", "value")
)
def update_dashboard(selected_continent):
dff = df[df["continent"] == selected_continent]
fig = px.bar(dff, x="country", y="pop", title=f"Dân số châu lục: {selected_continent}")
data = dff[["country", "lifeExp", "gdpPercap", "pop"]].to_dict("records")
columns = [{"name": c, "id": c} for c in ["country", "lifeExp", "gdpPercap", "pop"]]
return fig, data, columns
if __name__ == "__main__":
app.run(debug=True)
Một input duy nhất nhưng điều khiển được cả biểu đồ lẫn bảng dữ liệu. Cho thấy Dash rất phù hợp để xây ứng dụng phân tích nhiều thành phần. Đây cũng là kiểu tương tác người dùng thường gặp trong dashboard doanh nghiệp.

Upload file CSV rồi vẽ biểu đồ
Cho người dùng tải dữ liệu của họ vào app.
# app.py
import base64
import io
import pandas as pd
from dash import Dash, html, dcc, Input, Output, callback
import plotly.express as px
app = Dash()
app.layout = html.Div([
html.H3("Upload file CSV"),
dcc.Upload(
id="upload-data",
children=html.Button("Chọn file CSV"),
multiple=False
),
dcc.Graph(id="upload-chart")
])
@callback(
Output("upload-chart", "figure"),
Input("upload-data", "contents")
)
def update_chart(contents):
if contents is None:
return px.scatter(title="Chưa có dữ liệu")
content_type, content_string = contents.split(",")
decoded = base64.b64decode(content_string)
df = pd.read_csv(io.StringIO(decoded.decode("utf-8")))
fig = px.line(df, x=df.columns[0], y=df.columns[1], title="Biểu đồ từ file upload")
return fig
if __name__ == "__main__":
app.run(debug=True)
Thay vì chỉ chạy trên dữ liệu có sẵn, người dùng đưa dữ liệu của họ vào ứng dụng. Nó biến app từ chỗ “demo kỹ thuật” thành một công cụ áp dụng vào công việc thực tế.

Chia app thành nhiều tab
Tổ chức nội dung app bằng dcc.Tabs.
# app.py
from dash import Dash, html, dcc, Input, Output, callback
app = Dash()
app.layout = html.Div([
dcc.Tabs(
id="tabs",
value="tab-1",
children=[
dcc.Tab(label="Tổng quan", value="tab-1"),
dcc.Tab(label="Dữ liệu", value="tab-2"),
dcc.Tab(label="Biểu đồ", value="tab-3"),
]
),
html.Div(id="tabs-content")
])
@callback(
Output("tabs-content", "children"),
Input("tabs", "value")
)
def render_content(tab):
if tab == "tab-1":
return html.Div("Đây là tab Tổng quan")
if tab == "tab-2":
return html.Div("Đây là tab Dữ liệu")
return html.Div("Đây là tab Biểu đồ")
if __name__ == "__main__":
app.run(debug=True)
Khi ứng dụng bắt đầu có nhiều nội dung, việc chia thành từng tab là cách tổ chức rất hợp lý. Ví dụ này giúp chuyển từ suy nghĩ “một trang có mọi thứ” sang “một ứng dụng có cấu trúc”.

Mini dashboard hoàn chỉnh
Gom các ý đã học thành một app nhỏ có dropdown, khoảng thời gian, nút bấm, bảng và biểu đồ.
# app.py
from dash import Dash, html, dcc, Input, Output, State, callback
from dash import dash_table
import plotly.express as px
df = px.data.gapminder()
app = Dash()
app.layout = html.Div([
html.H2("Mini Dashboard với Dash"),
html.Label("Chọn châu lục"),
dcc.Dropdown(
options=[{"label": c, "value": c} for c in sorted(df["continent"].unique())],
value="Asia",
id="continent-dropdown"
),
html.Label("Chọn khoảng năm"),
dcc.RangeSlider(
min=int(df["year"].min()),
max=int(df["year"].max()),
step=5,
value=[1992, 2007],
marks={int(y): str(y) for y in df["year"].unique()},
id="year-range"
),
html.Button("Xem kết quả", id="run-button", n_clicks=0),
dcc.Graph(id="main-chart"),
dash_table.DataTable(id="main-table", page_size=4)
])
@callback(
Output("main-chart", "figure"),
Output("main-table", "data"),
Output("main-table", "columns"),
Input("run-button", "n_clicks"),
State("continent-dropdown", "value"),
State("year-range", "value")
)
def update_dashboard(n_clicks, continent, year_range):
dff = df[
(df["continent"] == continent) &
(df["year"] >= year_range[0]) &
(df["year"] <= year_range[1])
]
grouped = dff.groupby("year", as_index=False)["lifeExp"].mean()
fig = px.line(grouped, x="year", y="lifeExp", title="Tuổi thọ trung bình")
table_df = dff[["country", "year", "lifeExp", "gdpPercap", "pop"]]
data = table_df.to_dict("records")
columns = [{"name": c, "id": c} for c in table_df.columns]
return fig, data, columns
if __name__ == "__main__":
app.run(debug=True)
Đây là ví dụ kết hợp nhiều thành phần: dropdown, nút bấm, biểu đồ và bảng dữ liệu. Cách ghép chúng thành một sản phẩm nhỏ nhưng mang đúng tinh thần của ứng dụng dữ liệu thực tế.

Chia app thành nhiều tab với nội dung đầy đủ hơn
Tổ chức app thành nhiều khu vực nội dung như Tổng quan, Dữ liệu và Biểu đồ.
# app.py
from dash import Dash, html, dcc, Input, Output, callback
from dash import dash_table
import plotly.express as px
import pandas as pd
df = px.data.gapminder().query("year == 2007")
app = Dash()
app.layout = html.Div([
html.H2("Ứng dụng nhiều tab với Dash"),
dcc.Tabs(
id="tabs",
value="tab-overview",
children=[
dcc.Tab(label="Tổng quan", value="tab-overview"),
dcc.Tab(label="Dữ liệu", value="tab-data"),
dcc.Tab(label="Biểu đồ", value="tab-chart"),
]
),
html.Div(id="tabs-content", style={"padding": "20px"})
])
@callback(
Output("tabs-content", "children"),
Input("tabs", "value")
)
def render_content(tab):
if tab == "tab-overview":
return html.Div([
html.H3("Tổng quan dữ liệu Gapminder năm 2007"),
html.P(
"Tab này dùng để hiển thị phần giới thiệu nhanh về bộ dữ liệu, "
"một vài chỉ số chính và biểu đồ tóm tắt."
),
html.Div([
html.Div([
html.H4("Số quốc gia"),
html.H2(f"{df['country'].nunique()}")
], style={
"width": "30%",
"display": "inline-block",
"padding": "10px",
"border": "1px solid #ddd",
"marginRight": "2%"
}),
html.Div([
html.H4("Tuổi thọ trung bình"),
html.H2(f"{df['lifeExp'].mean():.2f}")
], style={
"width": "30%",
"display": "inline-block",
"padding": "10px",
"border": "1px solid #ddd",
"marginRight": "2%"
}),
html.Div([
html.H4("GDP/người trung bình"),
html.H2(f"{df['gdpPercap'].mean():.2f}")
], style={
"width": "30%",
"display": "inline-block",
"padding": "10px",
"border": "1px solid #ddd"
}),
]),
html.Br(),
dcc.Graph(
figure=px.bar(
df.groupby("continent", as_index=False)["pop"].sum(),
x="continent", y="pop",
title="Tổng dân số theo châu lục"
)
)
])
elif tab == "tab-data":
return html.Div([
html.H3("Bảng dữ liệu"),
html.P("Tab này hiển thị một phần dữ liệu gốc dưới dạng bảng."),
dash_table.DataTable(
data=df[["country", "continent", "lifeExp", "gdpPercap", "pop"]].to_dict("records"),
columns=[
{"name": "Country", "id": "country"},
{"name": "Continent", "id": "continent"},
{"name": "Life Expectancy", "id": "lifeExp"},
{"name": "GDP per Capita", "id": "gdpPercap"},
{"name": "Population", "id": "pop"},
],
page_size=10,
style_table={"overflowX": "auto"},
style_cell={"textAlign": "left", "padding": "8px"},
style_header={"fontWeight": "bold"}
)
])
elif tab == "tab-chart":
return html.Div([
html.H3("Biểu đồ phân tích"),
html.P("Tab này tập trung vào trực quan hóa dữ liệu."),
dcc.Graph(
figure=px.scatter(
df,
x="gdpPercap", y="lifeExp",
size="pop",
color="continent",
hover_name="country",
log_x=True,
title="GDP bình quân đầu người và tuổi thọ"
)
),
dcc.Graph(
figure=px.box(
df,
x="continent", y="lifeExp",
color="continent",
title="Phân phối tuổi thọ theo châu lục"
)
)
])
if __name__ == "__main__":
app.run(debug=True)
Ví dụ này sử dụng dcc.Tabs thực tế hơn nhiều so với phiên bản cơ bản. Mỗi tab có vai trò rõ ràng: tab Tổng quan hiển thị KPI và biểu đồ tóm tắt . Tab Dữ liệu trình bày bảng chi tiết. Tab Biểu đồ tập trung vào trực quan hóa.

Kết luận
Học Dash qua ví dụ là cách tiếp cận hiệu quả. Phương pháp này phù hợp với người đã quen dùng Python và Plotly. Thay vì học lý thuyết rời rạc, bạn sẽ thực hành qua từng ví dụ cụ thể. Nhờ đó, nhanh chóng hiểu cách các thành phần của Dash.
Qua chuỗi ví dụ, bạn không chỉ biết viết app Dash. Bạn còn hình thành tư duy xây dựng ứng dụng dữ liệu thực tế. Lộ trình bắt đầu từ app đơn giản, sau đó thêm biểu đồ, bộ lọc và ghép thành dashboard hoàn chỉnh.


