Đây là bài phụ lục minh họa cho bài kỹ thuật tương tác dữ liệu trong trực quan hóa. Bao gồm code, hình minh họa các kỹ thuật.
Dữ liệu bán hàng
Bộ dữ liệu doanh số bán hàng sau sẽ được dùng làm dữ liệu đầu vào cho các ví dụ , hình minh họa trong bài
| Tháng | Khu vực | Danh mục | Doanh thu (triệu) | Số đơn hàng | Giá bán trung bình (nghìn) | Lợi nhuận (triệu) |
| 1 | Bắc | Điện tử | 120 | 40 | 3000 | 24 |
| 1 | Trung | Thời trang | 80 | 50 | 1600 | 12 |
| 1 | Nam | Gia dụng | 100 | 45 | 2200 | 18 |
| 2 | Bắc | Điện tử | 135 | 42 | 3200 | 28 |
| 2 | Trung | Thời trang | 85 | 52 | 1650 | 13 |
| 2 | Nam | Gia dụng | 110 | 47 | 2300 | 20 |
| 3 | Bắc | Điện tử | 150 | 46 | 3300 | 32 |
| 3 | Trung | Thời trang | 95 | 55 | 1700 | 15 |
| 3 | Nam | Gia dụng | 120 | 50 | 2400 | 22 |
| 4 | Bắc | Điện tử | 160 | 48 | 3350 | 34 |
| 4 | Trung | Thời trang | 100 | 58 | 1720 | 16 |
| 4 | Nam | Gia dụng | 130 | 52 | 2450 | 24 |
| 5 | Bắc | Điện tử | 170 | 50 | 3400 | 36 |
| 5 | Trung | Thời trang | 110 | 60 | 1750 | 18 |
| 5 | Nam | Gia dụng | 140 | 55 | 2500 | 26 |
| 6 | Bắc | Điện tử | 180 | 54 | 3450 | 39 |
| 6 | Trung | Thời trang | 120 | 64 | 1780 | 20 |
| 6 | Nam | Gia dụng | 150 | 58 | 2550 | 28 |
Kỹ thuật multiple Views
Multiple Views là kỹ thuật vẽ nhiều loại biểu đồ cạnh nhau, mỗi biểu đồ trả lời 1 câu hỏi về bộ dữ liệu. Khi đặt cạnh nhau, chúng giúp người học nhìn cùng một hiện tượng từ nhiều góc độ.

- Line chart: xu hướng doanh thu theo tháng.
- Bar chart: so sánh doanh thu theo khu vực.
- Scatter plot: mối quan hệ giữa giá bán và lợi nhuận.
- Pie chart: cơ cấu doanh thu theo danh mục.
Overview + Detail
Kỹ thuật Overview + Detail là cách cho người xem thấy cả cái nhìn tổng thể lẫn phần dữ liệu cần quan sát kỹ hơn.
- Overview: hiển thị toàn bộ dữ liệu hoặc toàn bộ bối cảnh.
- Detail: làm rõ vùng dữ liệu được chọn hoặc phần cần phân tích sâu.
Lợi ích của cách này là người dùng không bị “lạc” trong dữ liệu lớn. Có thể tập trung vào một vùng cụ thể, và vẫn thấy tổng thể.

Biểu đồ phía trên cho thấy xu hướng của toàn năm, còn biểu đồ phía dưới phóng to giai đoạn biến động mạnh vào cuối tháng 11 và đầu tháng 12. Nhờ đó, người dùng vừa giữ được bức tranh tổng thể, vừa quan sát rõ phần dữ liệu cần phân tích chi tiết.
Small Multiples
Một kỹ thuật khác là Small Multiples, còn gọi là Trellis. Thay vì tạo một biểu đồ lớn chứa tất cả thông tin, ta tách dữ liệu thành nhiều nhóm nhỏ và hiển thị thành nhiều biểu đồ giống nhau, đặt song song theo lưới.

Với bộ dữ liệu doanh số bán hàng ở trên. Cùng một biến doanh thu theo tháng được chia thành nhiều biểu đồ đường nhỏ theo từng khu vực. Nhờ giữ nguyên loại biểu đồ, trục thời gian và thang đo, người đọc có thể dễ dàng so sánh xu hướng doanh thu giữa các khu vực mà không bị rối như khi chồng nhiều đường lên cùng một biểu đồ.

Brushing & Linking – Kết nối các góc nhìn dữ liệu
Đây là một kỹ thuật gồm hai thành phần = Brushing, và Linking. Nếu Multiple Views trả lời câu hỏi “làm sao nhìn dữ liệu từ nhiều góc độ?”, thì Brushing & Linking trả lời câu hỏi “làm sao để các góc nhìn đó tương tác với nhau?”.

- Hình bên trái: trạng thái ban đầu, scatter plot, bar chart và bảng dữ liệu hiển thị toàn bộ dữ liệu.
- Hình bên phải: sau khi người dùng brushing trên scatter plot, bar chart và bảng dữ liệu cập nhật theo vùng được chọn.
Người dùng khoanh chọn một nhóm điểm trên scatter plot thể hiện mối quan hệ giữa giá bán trung bình và lợi nhuận. Sau thao tác chọn này, bar chart doanh thu theo danh mục và bảng dữ liệu bên dưới được cập nhật theo đúng nhóm điểm đã chọn. Nhờ đó, các góc nhìn dữ liệu không còn tồn tại riêng lẻ mà được liên kết với nhau trong cùng một quá trình phân tích.
Code ở trên với plotly như sau
# !pip -q install dash plotly pandas
import pandas as pd
from dash import Dash, dcc, html, dash_table, Input, Output
import plotly.express as px
# 1. Tạo bộ dữ liệu mẫu
data = [
[1, "Bắc", "Điện tử", 120, 40, 3000, 24],
[1, "Trung", "Thời trang", 80, 50, 1600, 12],
[1, "Nam", "Gia dụng", 100, 45, 2200, 18],
[2, "Bắc", "Điện tử", 135, 42, 3200, 28],
[2, "Trung", "Thời trang", 85, 52, 1650, 13],
[2, "Nam", "Gia dụng", 110, 47, 2300, 20],
[3, "Bắc", "Điện tử", 150, 46, 3300, 32],
[3, "Trung", "Thời trang", 95, 55, 1700, 15],
[3, "Nam", "Gia dụng", 120, 50, 2400, 22],
[4, "Bắc", "Điện tử", 160, 48, 3350, 34],
[4, "Trung", "Thời trang", 100, 58, 1720, 16],
[4, "Nam", "Gia dụng", 130, 52, 2450, 24],
[5, "Bắc", "Điện tử", 170, 50, 3400, 36],
[5, "Trung", "Thời trang", 110, 60, 1750, 18],
[5, "Nam", "Gia dụng", 140, 55, 2500, 26],
[6, "Bắc", "Điện tử", 180, 54, 3450, 39],
[6, "Trung", "Thời trang", 120, 64, 1780, 20],
[6, "Nam", "Gia dụng", 150, 58, 2550, 28],
]
columns = [
"Tháng", "Khu vực", "Danh mục", "Doanh thu (triệu)",
"Số đơn hàng", "Giá bán TB (nghìn)", "Lợi nhuận (triệu)"
]
df = pd.DataFrame(data, columns=columns)
# 2. Figure ban đầu
scatter_fig = px.scatter(
df,
x="Giá bán TB (nghìn)", y="Lợi nhuận (triệu)",
color="Danh mục",
symbol="Khu vực",
hover_data=["Tháng", "Khu vực", "Danh mục", "Doanh thu (triệu)"],
title="Brushing: Chọn điểm trên scatter plot",
)
# Bật box select + lasso select trên thanh công cụ
scatter_fig.update_layout(
dragmode="select",
margin=dict(l=40, r=20, t=50, b=40),
)
bar_init = (
df.groupby("Danh mục", as_index=False)["Doanh thu (triệu)"]
.sum()
)
bar_fig = px.bar(
bar_init,
x="Danh mục", y="Doanh thu (triệu)",
title="Linking: Doanh thu theo danh mục",
text_auto=True
)
bar_fig.update_layout(margin=dict(l=40, r=20, t=50, b=40))
# 3. Dash app
app = Dash(__name__)
app.layout = html.Div(
style={"fontFamily": "Arial", "padding": "20px"},
children=[
html.H2("Minh họa Brushing & Linking với Plotly/Dash"),
html.P(
"Hãy dùng Box Select hoặc Lasso Select trên scatter plot. "
"Bar chart và bảng dữ liệu sẽ cập nhật theo vùng bạn chọn."
),
html.Div(
style={"display": "grid", "gridTemplateColumns": "1.4fr 1fr", "gap": "20px"},
children=[
dcc.Graph(
id="scatter-graph",
figure=scatter_fig,
config={"displayModeBar": True}
),
dcc.Graph(
id="bar-graph",
figure=bar_fig
),
]
),
html.H4("Bảng dữ liệu sau khi lọc"),
dash_table.DataTable(
id="data-table",
columns=[{"name": c, "id": c} for c in df.columns],
data=df.to_dict("records"),
page_size=10,
style_table={"overflowX": "auto"},
style_cell={"textAlign": "center", "padding": "6px"},
style_header={"fontWeight": "bold"},
)
]
)
# 4. Callback cập nhật bar chart + bảng
@app.callback(
Output("bar-graph", "figure"),
Output("data-table", "data"),
Input("scatter-graph", "selectedData"),
)
def update_linked_views(selectedData):
# Nếu chưa chọn gì, dùng toàn bộ dữ liệu
if not selectedData or "points" not in selectedData:
filtered_df = df.copy()
else:
# Lấy index từ customdata/pointIndex
point_indices = [p["pointIndex"] for p in selectedData["points"]]
filtered_df = df.iloc[point_indices].copy()
# Bar chart cập nhật theo dữ liệu đã chọn
bar_data = (
filtered_df.groupby("Danh mục", as_index=False)["Doanh thu (triệu)"]
.sum()
)
# Đảm bảo luôn hiện đủ 3 danh mục
all_categories = pd.DataFrame({"Danh mục": ["Điện tử", "Thời trang", "Gia dụng"]})
bar_data = all_categories.merge(bar_data, on="Danh mục", how="left").fillna(0)
title = "Linking: Doanh thu theo danh mục"
if selectedData and "points" in selectedData:
title += " (sau khi chọn)"
new_bar_fig = px.bar(
bar_data,
x="Danh mục",
y="Doanh thu (triệu)",
title=title,
text_auto=True
)
new_bar_fig.update_layout(margin=dict(l=40, r=20, t=50, b=40))
return new_bar_fig, filtered_df.to_dict("records")
# 5. Chạy app trong notebook/Colab
app.run(jupyter_mode="inline", debug=False)
Brushing & Linking trong Tableau
Trong Tableau, người dùng có thể dùng highlight action, filter action hay dashboard action để tạo hiệu ứng linked views.

Brushing & Linking trong Power BI
Trong Power BI, việc chọn một cột hoặc một vùng dữ liệu ở visual này có thể làm các visual khác highlight hoặc filter theo.

Brushing & Linking trong D3
Trong D3.js, lập trình viên có thể tự xây cơ chế brushing, lasso selection, linked charts rất linh hoạt.

Với D3.js, lập trình viên có thể tự xây dựng cơ chế chọn vùng dữ liệu trên scatter plot bằng brushing hoặc lasso selection, sau đó liên kết lựa chọn đó với các biểu đồ và bảng dữ liệu khác. Điều này giúp tạo các dashboard có mức độ tùy biến và linh hoạt rất cao.
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, Rectangle
# Dữ liệu doanh thu bán hàng
data = [
[1, "Bắc", "Điện tử", 120, 40, 3000, 24],
[1, "Trung", "Thời trang", 80, 50, 1600, 12],
[1, "Nam", "Gia dụng", 100, 45, 2200, 18],
[2, "Bắc", "Điện tử", 135, 42, 3200, 28],
[2, "Trung", "Thời trang", 85, 52, 1650, 13],
[2, "Nam", "Gia dụng", 110, 47, 2300, 20],
[3, "Bắc", "Điện tử", 150, 46, 3300, 32],
[3, "Trung", "Thời trang", 95, 55, 1700, 15],
[3, "Nam", "Gia dụng", 120, 50, 2400, 22],
[4, "Bắc", "Điện tử", 160, 48, 3350, 34],
[4, "Trung", "Thời trang", 100, 58, 1720, 16],
[4, "Nam", "Gia dụng", 130, 52, 2450, 24],
[5, "Bắc", "Điện tử", 170, 50, 3400, 36],
[5, "Trung", "Thời trang", 110, 60, 1750, 18],
[5, "Nam", "Gia dụng", 140, 55, 2500, 26],
[6, "Bắc", "Điện tử", 180, 54, 3450, 39],
[6, "Trung", "Thời trang", 120, 64, 1780, 20],
[6, "Nam", "Gia dụng", 150, 58, 2550, 28],
]
columns = [
"Tháng", "Khu vực", "Danh mục", "Doanh thu (triệu)",
"Số đơn hàng", "Giá bán TB (nghìn)", "Lợi nhuận (triệu)"
]
df = pd.DataFrame(data, columns=columns)
monthly = df.groupby("Tháng", as_index=False)["Doanh thu (triệu)"].sum()
region = df.groupby("Khu vực", as_index=False)["Doanh thu (triệu)"].sum()
region = region.set_index("Khu vực").loc[["Bắc", "Trung", "Nam"]].reset_index()
# vùng được chọn trên scatter plot
x_min, x_max = 2300, 3500
y_min, y_max = 22, 40
selected = df[
(df["Giá bán TB (nghìn)"] >= x_min) &
(df["Giá bán TB (nghìn)"] <= x_max) &
(df["Lợi nhuận (triệu)"] >= y_min) &
(df["Lợi nhuận (triệu)"] <= y_max)
].copy()
not_selected = df.drop(selected.index)
after_bar = (
selected.groupby("Danh mục", as_index=False)["Doanh thu (triệu)"]
.sum()
.set_index("Danh mục")
.reindex(["Điện tử", "Thời trang", "Gia dụng"])
.fillna(0)
.reset_index()
)
fig = plt.figure(figsize=(16, 9), dpi=180)
fig.patch.set_facecolor("#f6f8fb")
# Thanh trình duyệt giả lập
ax_browser = fig.add_axes([0.02, 0.93, 0.96, 0.06])
ax_browser.axis("off")
browser = FancyBboxPatch(
(0, 0), 1, 1,
boxstyle="round,pad=0.005,rounding_size=0.02",
linewidth=1, edgecolor="#d7dce3", facecolor="white"
)
ax_browser.add_patch(browser)
ax_browser.text(0.02, 0.5, "● ● ●", va="center", ha="left", fontsize=11, color="#9aa4b2")
ax_browser.text(0.12, 0.5, "localhost:8080/d3-sales-dashboard", va="center", ha="left",
fontsize=12, color="#334155")
ax_browser.text(0.94, 0.5, "D3.js", va="center", ha="right", fontsize=12,
color="#0f172a", fontweight="bold")
fig.text(0.5, 0.885, "DASHBOARD DOANH THU BÁN HÀNG", ha="center",
fontsize=21, fontweight="bold", color="#111827")
fig.text(0.5, 0.855,
"Mô phỏng giao diện D3.js với brushing, lasso selection và linked charts",
ha="center", fontsize=11, color="#64748b")
def card_axes(rect):
ax = fig.add_axes(rect)
ax.set_facecolor("white")
for spine in ax.spines.values():
spine.set_visible(False)
patch = FancyBboxPatch(
(0, 0), 1, 1,
boxstyle="round,pad=0.01,rounding_size=0.03",
transform=ax.transAxes,
linewidth=1,
edgecolor="#dbe2ea",
facecolor="white",
zorder=-10,
clip_on=False
)
ax.add_patch(patch)
return ax
# Scatter brushing
ax1 = card_axes([0.04, 0.49, 0.42, 0.30])
ax1.scatter(not_selected["Giá bán TB (nghìn)"], not_selected["Lợi nhuận (triệu)"], alpha=0.28, s=42)
ax1.scatter(selected["Giá bán TB (nghìn)"], selected["Lợi nhuận (triệu)"], s=52)
rect = Rectangle((x_min, y_min), x_max - x_min, y_max - y_min,
fill=False, linewidth=2, linestyle="--")
ax1.add_patch(rect)
ax1.set_title("Biểu đồ scatter: chọn vùng dữ liệu bằng brushing / lasso",
fontsize=12.5, fontweight="bold", pad=12)
ax1.set_xlabel("Giá bán trung bình (nghìn)")
ax1.set_ylabel("Lợi nhuận (triệu)")
ax1.grid(True, alpha=0.22)
# Linked bar chart
ax2 = card_axes([0.50, 0.49, 0.22, 0.30])
ax2.bar(after_bar["Danh mục"], after_bar["Doanh thu (triệu)"])
ax2.set_title("Linked bar chart:\nDoanh thu theo danh mục sau khi chọn",
fontsize=12, fontweight="bold", pad=12)
ax2.set_ylabel("Doanh thu (triệu)")
ax2.grid(True, axis="y", alpha=0.22)
for i, v in enumerate(after_bar["Doanh thu (triệu)"]):
ax2.text(i, v + 10, f"{int(v)}", ha="center", va="bottom", fontsize=9)
# Doanh thu theo tháng
ax3 = card_axes([0.75, 0.49, 0.21, 0.30])
ax3.bar(monthly["Tháng"], monthly["Doanh thu (triệu)"])
ax3.set_title("Doanh thu theo tháng", fontsize=12.5, fontweight="bold", pad=12)
ax3.set_xlabel("Tháng")
ax3.set_ylabel("Doanh thu")
ax3.set_xticks(monthly["Tháng"])
ax3.grid(True, axis="y", alpha=0.22)
# Doanh thu theo khu vực
ax4 = card_axes([0.04, 0.11, 0.28, 0.28])
ax4.bar(region["Khu vực"], region["Doanh thu (triệu)"])
ax4.set_title("Doanh thu theo khu vực", fontsize=12.5, fontweight="bold", pad=12)
ax4.set_ylabel("Doanh thu (triệu)")
ax4.grid(True, axis="y", alpha=0.22)
# Bảng dữ liệu
ax5 = card_axes([0.35, 0.11, 0.40, 0.28])
ax5.axis("off")
ax5.text(0.03, 0.93, "Bảng dữ liệu sau khi chọn", fontsize=12.5,
fontweight="bold", color="#111827")
tbl_df = selected[["Tháng", "Khu vực", "Danh mục", "Doanh thu (triệu)", "Giá bán TB (nghìn)", "Lợi nhuận (triệu)"]]
tbl = ax5.table(
cellText=tbl_df.values,
colLabels=tbl_df.columns,
cellLoc="center",
colLoc="center",
bbox=[0.02, 0.05, 0.96, 0.80]
)
tbl.auto_set_font_size(False)
tbl.set_fontsize(8.5)
for (r, c), cell in tbl.get_celld().items():
cell.set_edgecolor("#e5e7eb")
if r == 0:
cell.set_text_props(fontweight="bold")
# Khối giải thích tương tác
ax6 = card_axes([0.78, 0.11, 0.18, 0.28])
ax6.axis("off")
ax6.text(0.05, 0.90, "Tương tác kiểu D3.js", fontsize=12.5, fontweight="bold", color="#111827")
items = [
"• Drag to brush",
"• Lasso selection",
"• Linked charts",
"• Update table",
"• Custom event handlers"
]
y = 0.76
for item in items:
ax6.text(0.07, y, item, fontsize=10.2, color="#334155")
y -= 0.11
ax6.text(0.05, 0.20,
"D3.js cho phép lập trình viên\nxây dựng cơ chế tương tác\nrất linh hoạt bằng mã JavaScript.",
fontsize=9.8, color="#64748b")
fig.text(
0.5, 0.045,
"Minh họa giao diện kiểu D3.js: người dùng chọn một vùng điểm trên scatter plot, "
"sau đó biểu đồ cột và bảng dữ liệu được liên kết cập nhật theo vùng đã chọn.",
ha="center", fontsize=10, color="#475569"
)
output_path = "/mnt/data/d3js_mockup_dung_du_lieu_doanh_thu.png"
plt.savefig(output_path, bbox_inches="tight")
plt.show()
print(output_path)
Brushing & Linking trong Plotly/Dash
Trong Plotly/Dash, dữ liệu lựa chọn từ biểu đồ có thể được truyền qua callback để cập nhật biểu đồ khác.

Code demo vẽ biểu đồ với Plotly trong Google Colab
import pandas as pd
import plotly.express as px
# Bộ dữ liệu doanh thu bán hàng
data = [
[1, "Bắc", "Điện tử", 120, 40, 3000, 24],
[1, "Trung", "Thời trang", 80, 50, 1600, 12],
[1, "Nam", "Gia dụng", 100, 45, 2200, 18],
[2, "Bắc", "Điện tử", 135, 42, 3200, 28],
[2, "Trung", "Thời trang", 85, 52, 1650, 13],
[2, "Nam", "Gia dụng", 110, 47, 2300, 20],
[3, "Bắc", "Điện tử", 150, 46, 3300, 32],
[3, "Trung", "Thời trang", 95, 55, 1700, 15],
[3, "Nam", "Gia dụng", 120, 50, 2400, 22],
[4, "Bắc", "Điện tử", 160, 48, 3350, 34],
[4, "Trung", "Thời trang", 100, 58, 1720, 16],
[4, "Nam", "Gia dụng", 130, 52, 2450, 24],
[5, "Bắc", "Điện tử", 170, 50, 3400, 36],
[5, "Trung", "Thời trang", 110, 60, 1750, 18],
[5, "Nam", "Gia dụng", 140, 55, 2500, 26],
[6, "Bắc", "Điện tử", 180, 54, 3450, 39],
[6, "Trung", "Thời trang", 120, 64, 1780, 20],
[6, "Nam", "Gia dụng", 150, 58, 2550, 28],
]
columns = [
"Tháng", "Khu vực", "Danh mục", "Doanh thu (triệu)",
"Số đơn hàng", "Giá bán TB (nghìn)", "Lợi nhuận (triệu)"
]
df = pd.DataFrame(data, columns=columns)
# Tổng doanh thu theo danh mục
category_revenue = (
df.groupby("Danh mục", as_index=False)["Doanh thu (triệu)"]
.sum()
)
# Vẽ bar chart
fig = px.bar(
category_revenue,
x="Danh mục", y="Doanh thu (triệu)",
text="Doanh thu (triệu)",
title="Doanh thu theo danh mục"
)
fig.update_traces(
textposition="outside", width=0.25,
#marker_color="steelblue",
marker_color=["#1f77b4", "#ff7f0e", "#2ca02c"]
)
# textposition="outside" để hiển thị giá trị trên đỉnh cột , outside/inside/auto/none
# width=0.25 chỉnh độ rộng cột bằng 1/4 so với khoảng cách giữa các cột
fig.update_layout(
xaxis_title="Danh mục", yaxis_title="Doanh thu (triệu)",
template="seaborn",
title_xanchor="center", title_x=0.5,
)
#title = 0 - mép trái, 0.5 giữa, 1 = mép phải
#title_xanchor="center" lấy tâm của tiêu đề đặt đúng vào vị trí x
# template="seaborn" màu nền, plotly_white/plotly/plotly_white, plotly_dark/gridon/none
fig.show()

Code demo vẽ biểu đồ với Plotly trong local
- Tạo folder testplotly
- Cài thư viện: pip install pandas plotly
- Tạo file plotly1.py và code
import pandas as pd
import plotly.express as px
df = pd.DataFrame({
"Danh mục": ["Điện tử", "Thời trang", "Gia dụng"],
"Doanh thu (triệu)": [915, 590, 750]
})
fig = px.bar(
df,
x="Danh mục", y="Doanh thu (triệu)",
text="Doanh thu (triệu)",
title="Doanh thu theo danh mục"
)
fig.update_traces(
textposition="outside", width=0.25,
marker_color=["#1f77b4", "#ff7f0e", "#2ca02c"]
)
# textposition="outside" để hiển thị giá trị trên đỉnh cột , outside/inside/auto/none
# width=0.25 chỉnh độ rộng cột bằng 1/4 so với khoảng cách giữa các cột
fig.update_layout(
xaxis_title="Danh mục", yaxis_title="Doanh thu (triệu)",
template="seaborn",
title_xanchor="center", title_x=0.5,
)
fig.show()
- Chạy file: python plotly1.py

Code demo vẽ biểu đồ với Dash
- Tạo folder testplotly
- Cài thư viện: pip install dash pandas plotly
- Tạo file plotly2.py và code
import pandas as pd
import plotly.express as px
from dash import Dash, dcc, html
# Bộ dữ liệu doanh thu bán hàng
data = [
[1, "Bắc", "Điện tử", 120, 40, 3000, 24],
[1, "Trung", "Thời trang", 80, 50, 1600, 12],
[1, "Nam", "Gia dụng", 100, 45, 2200, 18],
[2, "Bắc", "Điện tử", 135, 42, 3200, 28],
[2, "Trung", "Thời trang", 85, 52, 1650, 13],
[2, "Nam", "Gia dụng", 110, 47, 2300, 20],
[3, "Bắc", "Điện tử", 150, 46, 3300, 32],
[3, "Trung", "Thời trang", 95, 55, 1700, 15],
[3, "Nam", "Gia dụng", 120, 50, 2400, 22],
[4, "Bắc", "Điện tử", 160, 48, 3350, 34],
[4, "Trung", "Thời trang", 100, 58, 1720, 16],
[4, "Nam", "Gia dụng", 130, 52, 2450, 24],
[5, "Bắc", "Điện tử", 170, 50, 3400, 36],
[5, "Trung", "Thời trang", 110, 60, 1750, 18],
[5, "Nam", "Gia dụng", 140, 55, 2500, 26],
[6, "Bắc", "Điện tử", 180, 54, 3450, 39],
[6, "Trung", "Thời trang", 120, 64, 1780, 20],
[6, "Nam", "Gia dụng", 150, 58, 2550, 28],
]
columns = [
"Tháng", "Khu vực", "Danh mục", "Doanh thu (triệu)",
"Số đơn hàng", "Giá bán TB (nghìn)", "Lợi nhuận (triệu)"
]
df = pd.DataFrame(data, columns=columns)
# Tổng doanh thu theo danh mục
category_revenue = (
df.groupby("Danh mục", as_index=False)["Doanh thu (triệu)"]
.sum()
)
# Vẽ biểu đồ Plotly
fig = px.bar(
category_revenue,
x="Danh mục", y="Doanh thu (triệu)",
text="Doanh thu (triệu)",
title="Doanh thu theo danh mục"
)
fig.update_traces(
textposition="outside", width=0.25,
marker_color=["#1f77b4", "#ff7f0e", "#2ca02c"]
)
fig.update_layout(
title_x=0.5, title_xanchor="center",
xaxis_title="Danh mục", yaxis_title="Doanh thu (triệu)",
template="plotly_white"
)
# Tạo app Dash
app = Dash(__name__)
app.layout = html.Div([
html.H2("Bar chart trong Dash"),
dcc.Graph(
id="bar-chart",
figure=fig
)
])
# Chạy trong Colab/Jupyter
app.run(jupyter_mode="inline", debug=False)
- Chạy file: python plot2.py

Kết quả



