
Các em Gen Z thân mến, hôm nay anh Creyt sẽ dẫn các em đi khám phá một "công cụ kể chuyện" dữ liệu siêu xịn sò trong thế giới lập trình Flutter, đó chính là Stacked Bar Chart (Biểu đồ Cột Chồng). Nghe cái tên đã thấy "nghệ" rồi đúng không?
Nói nôm na, các em cứ hình dung một Stacked Bar Chart giống như một tháp Lego đa màu sắc vậy. Mỗi cái cột (bar) là một "tổng thể" nào đó, ví dụ như tổng doanh thu một tháng. Còn từng mảnh Lego xếp chồng lên nhau trong cái cột đó chính là "thành phần" cấu tạo nên cái tổng thể đó. Ví dụ, trong tổng doanh thu tháng đó, có bao nhiêu đến từ sản phẩm A, bao nhiêu từ sản phẩm B, bao nhiêu từ dịch vụ C. Tất cả chúng nó xếp chồng lên nhau, tạo nên chiều cao của cái cột tổng thể.
Vậy, mục đích của nó là gì? Đơn giản thôi:
- "Bóc tách" cái tổng thể: Cho chúng ta thấy rõ từng phần đóng góp vào một cái tổng như thế nào.
- So sánh sự thay đổi: Nhìn qua các cột, ta có thể thấy được sự thay đổi của từng thành phần, và cả sự thay đổi của tổng thể theo thời gian hoặc theo các danh mục khác nhau. Ví dụ, tháng này sản phẩm A đóng góp nhiều hơn tháng trước, nhưng tổng doanh thu lại giảm, vậy là có vấn đề gì đó ở các sản phẩm khác rồi!
- Kể chuyện trực quan: Thay vì nhìn một đống số khô khan, biểu đồ này giúp chúng ta "đọc" được câu chuyện đằng sau dữ liệu một cách nhanh chóng, dễ hiểu.
Code Ví Dụ minh hoạ (Flutter, fl_chart)
Để triển khai Stacked Bar Chart trong Flutter, anh em mình sẽ "triệu hồi" thư viện fl_chart – một "phù thủy" vẽ biểu đồ cực kỳ mạnh mẽ và linh hoạt. Đầu tiên, nhớ thêm nó vào pubspec.yaml nhé:
dependencies:
flutter:
sdk: flutter
fl_chart: ^0.68.0 # Hoặc phiên bản mới nhất
Sau đó, chạy flutter pub get. Bây giờ, cùng xem ví dụ về doanh thu từ 3 loại sản phẩm (A, B, C) qua 3 quý nhé:
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
class StackedBarChartPage extends StatefulWidget {
const StackedBarChartPage({Key? key}) : super(key: key);
@override
State<StackedBarChartPage> createState() => _StackedBarChartPageState();
}
class _StackedBarChartPageState extends State<StackedBarChartPage> {
// Dữ liệu mẫu: Doanh thu của sản phẩm A, B, C theo quý
// Cấu trúc: [Quý 1, Quý 2, Quý 3]
// Mỗi quý là một Map: {'productA': value, 'productB': value, 'productC': value}
final List<Map<String, double>> _quarterlySales = [
{'productA': 30, 'productB': 20, 'productC': 15}, // Quý 1
{'productA': 25, 'productB': 35, 'productC': 20}, // Quý 2
{'productA': 40, 'productB': 25, 'productC': 10}, // Quý 3
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Doanh Thu Sản Phẩm Theo Quý'),
backgroundColor: Colors.deepPurple,
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: AspectRatio(
aspectRatio: 1.5, // Tỷ lệ khung hình của biểu đồ
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: 100, // Tổng doanh thu tối đa có thể đạt được (max sum of A+B+C is 40+35+20 = 95)
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
tooltipBgColor: Colors.blueGrey,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
String product;
switch (rodIndex) {
case 0:
product = 'Sản phẩm A';
break;
case 1:
product = 'Sản phẩm B';
break;
case 2:
product = 'Sản phẩm C';
break;
default:
product = '';
}
return BarTooltipItem(
'$product: ${rod.toY.toInt()}K\n',
const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
);
},
),
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) {
String text;
switch (value.toInt()) {
case 0:
text = 'Q1';
break;
case 1:
text = 'Q2';
break;
case 2:
text = 'Q3';
break;
default:
text = '';
break;
}
return SideTitleWidget(
axisSide: meta.axisSide,
space: 4,
child: Text(text, style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 14)),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
return Text('${value.toInt()}K', style: const TextStyle(color: Colors.black, fontSize: 12));
},
),
),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(
show: true,
border: Border.all(color: const Color(0xff37434d), width: 1),
),
barGroups: _quarterlySales.asMap().entries.map((entry) {
int index = entry.key;
Map<String, double> sales = entry.value;
return BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: sales['productA']! + sales['productB']! + sales['productC']!, // Tổng chiều cao cột
width: 16,
borderRadius: BorderRadius.zero, // Không bo tròn đầu cột
rodStackItems: [
BarChartRodStackItem(0, sales['productA']!, Colors.redAccent), // Sản phẩm A
BarChartRodStackItem(sales['productA']!, sales['productA']! + sales['productB']!, Colors.green), // Sản phẩm B
BarChartRodStackItem(sales['productA']! + sales['productB']!, sales['productA']! + sales['productB']! + sales['productC']!, Colors.blue), // Sản phẩm C
],
),
],
);
}).toList(),
),
),
),
),
),
);
}
}

Mẹo (Best Practices) từ anh Creyt để ghi nhớ và dùng thực tế:
- Màu sắc là "linh hồn": Các em chọn màu cho từng phần chồng lên nhau phải thật sự khác biệt nhưng vẫn hài hòa. Đừng dùng màu "chói chang" quá, nhìn vào dễ "tụt mood". Tối đa 5-7 màu là đẹp, nhiều quá là thành "bát cháo lòng" đó!
- Thứ tự là "chìa khóa": Luôn giữ một thứ tự nhất quán cho các thành phần chồng lên nhau trên tất cả các cột. Ví dụ, luôn đặt "Sản phẩm A" ở dưới cùng, rồi đến "Sản phẩm B", "Sản phẩm C". Điều này giúp người xem dễ dàng so sánh và theo dõi sự thay đổi của từng phần.
- Nhãn và Tooltip "thần thánh": Đừng quên các nhãn trục (axis labels) rõ ràng và đặc biệt là Tooltip (hiển thị thông tin chi tiết khi chạm/di chuột vào cột). Chúng là "người phiên dịch" giúp dữ liệu của em "nói" được câu chuyện của nó.
- "Tầm nhìn" tổng thể: Đảm bảo
maxY(giá trị tối đa của trục Y) đủ lớn để chứa tất cả các cột, tránh tình trạng cột bị "cắt cụt" nhìn rất "thiếu chuyên nghiệp". - Đừng "tham lam" dữ liệu: Anh đã từng thấy nhiều em tân binh cố nhồi nhét 10-15 loại sản phẩm vào một cái stacked bar chart... kết quả là như bát cháo lòng, đẹp thì không mà nhìn vào thì loạn cào cào! Nếu dữ liệu quá nhiều, hãy cân nhắc nhóm lại hoặc dùng biểu đồ khác.
Ứng dụng thực tế: Ai đang "xài" Stacked Bar Chart?
Không ít đâu nhé! Các em sẽ thấy "ông bạn" này xuất hiện khắp mọi nơi:
- Dashboard tài chính cá nhân/doanh nghiệp: Như ứng dụng Money Lover hay các hệ thống ERP, BI. Họ dùng để phân tích chi tiêu theo từng hạng mục (ăn uống, đi lại, giải trí) hoặc doanh thu theo từng dòng sản phẩm, từng khu vực.
- Google Analytics / Webmaster Tools: Để xem lưu lượng truy cập website đến từ những nguồn nào (trực tiếp, tìm kiếm tự nhiên, mạng xã hội, quảng cáo) theo thời gian.
- Các ứng dụng quản lý dự án: JIRA, Trello (dạng tùy biến) có thể dùng để hiển thị tiến độ công việc theo từng giai đoạn, từng thành viên hoặc từng loại công việc.
- Ứng dụng sức khỏe/dinh dưỡng: MyFitnessPal dùng để bóc tách lượng calo từ protein, carb, fat trong bữa ăn hàng ngày.
Thử nghiệm của anh Creyt và lời khuyên nên dùng cho case nào:
Anh Creyt đã "chinh chiến" với đủ loại biểu đồ rồi. Và kinh nghiệm xương máu cho thấy:
NÊN DÙNG Stacked Bar Chart khi:
- Em muốn "mổ xẻ" một tổng thể thành các phần cấu thành và xem sự đóng góp của từng phần.
- Em muốn so sánh sự thay đổi của cấu trúc bên trong các tổng thể (ví dụ: tỷ lệ đóng góp của từng sản phẩm thay đổi thế nào qua các quý).
- Tổng thể (chiều cao của cột) cũng quan trọng như các phần bên trong nó.
- Số lượng các thành phần để "stack" không quá nhiều (lý tưởng là 3-5, tối đa 7).
KHÔNG NÊN DÙNG Stacked Bar Chart khi:
- Em chỉ muốn so sánh trực tiếp các giá trị độc lập của từng danh mục (ví dụ: chỉ muốn so sánh doanh thu sản phẩm A với sản phẩm B, không quan tâm tổng thể). Lúc này, Bar Chart thông thường hoặc Grouped Bar Chart sẽ hiệu quả hơn.
- Dữ liệu của em có nhiều giá trị âm. Stacked Bar Chart rất khó để biểu diễn giá trị âm một cách trực quan.
- Có quá nhiều thành phần để chồng lên nhau. Như anh nói đấy, thành "bát cháo lòng" ngay!
Tóm lại, Stacked Bar Chart là một công cụ mạnh mẽ để kể chuyện về sự phân bổ và thay đổi của dữ liệu. Hãy dùng nó một cách thông minh, các em sẽ biến những con số khô khan thành những câu chuyện đầy màu sắc và ý nghĩa! Chúc các em "code" vui vẻ và thành công!
Thuộc Series: Flutter
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!