Live Preview
Weekly Revenue
Bar chart — category comparison
Mon
Tue
Wed
Thu
Fri
Sat
Sun
Payment Methods
Donut chart — distribution
Cash — 55%
QR — 30%
Transfer — 15%
Trend
Line chart — over time
JanFebMarAprMayJunJul
Chart Component Generation Skill
Skill này hướng dẫn bạn (AI Agent) tạo component Chart — data visualization với Bar, Line, Pie/Donut charts, dùng Chart.js + Prisma Token theming.
1. Mục tiêu (Objective)
Tạo Chart wrapper component tích hợp Chart.js, dùng Prisma --chart-* CSS variables cho theming, auto dark mode, wrapped trong Card container.
2. AI Context & Intent (Ngữ cảnh cho AI)
Khi nào dùng Chart?
- Revenue report: Bar chart doanh thu theo ngày/tuần/tháng
- Dashboard: KPI trends, distribution pie charts
- Analytics: Line chart tracking over time
Decision Tree cho AI
text
Cần visualize dữ liệu?
├─ So sánh categories → Bar Chart
├─ Trend over time → Line Chart
├─ Tỷ lệ phân bố → Pie / Donut Chart
├─ Data table → Table component
└─ Single number → KPI Card
3. Implementation Strategy
3.1. Library Choice: Chart.js
| Criteria | Chart.js | Recharts | D3 |
|---|---|---|---|
| Framework-agnostic | ✅ | ❌ (React only) | ✅ |
| Bundle size | ~60KB | ~120KB | ~240KB |
| Canvas (perf) | ✅ | ❌ (SVG) | Either |
| Built-in types | ✅ | ✅ | ❌ (build from scratch) |
| Winner | ✅ |
3.2. CSS Variable Bridge (shadcn approach)
Chart.js doesn't natively read CSS variables. Bridge via JS:
javascript
// Read Prisma CSS variables for Chart.js config
function getChartColor(varName) {
return getComputedStyle(document.documentElement)
.getPropertyValue(varName).trim();
}
const chartColors = {
a: getChartColor('--chart-class-a'),
b: getChartColor('--chart-class-b'),
c: getChartColor('--chart-class-c'),
d: getChartColor('--chart-class-d'),
e: getChartColor('--chart-class-e'),
f: getChartColor('--chart-class-f'),
grid: getChartColor('--border'),
text: getChartColor('--muted-foreground'),
tooltip: {
bg: getChartColor('--surface'),
fg: getChartColor('--surface-foreground'),
border: getChartColor('--border'),
}
};
3.3. Dark Mode
Re-read CSS variables when data-theme changes:
javascript
// Observe theme changes
const observer = new MutationObserver(() => {
const newColors = readChartColors();
chart.options.scales.x.ticks.color = newColors.text;
chart.options.scales.y.ticks.color = newColors.text;
chart.options.scales.x.grid.color = newColors.grid;
chart.data.datasets.forEach((ds, i) => {
ds.backgroundColor = newColors[Object.keys(newColors)[i]];
});
chart.update();
});
observer.observe(document.documentElement, {
attributes: true, attributeFilter: ['data-theme']
});
4. Token Mapping
📦 Atomic Mapping: Xem
ATOMIC-MAPPING.md→ mục chart — UI Layer: Card, Density Tier: section.
| Property | Token | Ghi chú |
|---|---|---|
| Container | card component tokens |
Wrapped in card |
| Grid lines | --border |
|
| Axis tick labels | --muted-foreground |
caption text style |
| Tooltip bg | --surface |
|
| Tooltip fg | --surface-foreground |
|
| Tooltip border | --border |
|
| Tooltip radius | --comp-radius |
|
| Data color 1 | --chart-class-a |
Primary data |
| Data color 2 | --chart-class-b |
Secondary data |
| Data color 3 | --chart-class-c |
Tertiary data |
| Data color 4 | --chart-class-d |
|
| Data color 5 | --chart-class-e |
|
| Data color 6 | --chart-class-f |
|
| Legend text | body-small, --foreground |
|
| Responsive | maintainAspectRatio: true |
5. Chart Types
5.1. Bar Chart
javascript
// xPOS Revenue Report example
new Chart(ctx, {
type: 'bar',
data: {
labels: ['T2', 'T3', 'T4', 'T5', 'T6', 'T7', 'CN'],
datasets: [{
label: 'Doanh thu',
data: [1200000, 1900000, 800000, 1500000, 2100000, 1700000, 900000],
backgroundColor: getChartColor('--chart-class-a'),
borderRadius: parseInt(getChartColor('--comp-radius')) || 4,
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: { legend: { display: false } },
scales: {
x: { grid: { display: false }, ticks: { color: chartColors.text } },
y: { grid: { color: chartColors.grid }, ticks: { color: chartColors.text } }
}
}
});
5.2. Line Chart
javascript
new Chart(ctx, {
type: 'line',
data: {
labels: months,
datasets: [{
label: 'Doanh thu',
data: values,
borderColor: getChartColor('--chart-class-a'),
backgroundColor: getChartColor('--chart-class-a') + '20', // 12% opacity
fill: true,
tension: 0.3,
pointRadius: 4,
}]
}
});
5.3. Pie / Donut Chart
javascript
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Tiền mặt', 'QR', 'Chuyển khoản'],
datasets: [{
data: [55, 30, 15],
backgroundColor: [
getChartColor('--chart-class-a'),
getChartColor('--chart-class-b'),
getChartColor('--chart-class-c'),
],
borderWidth: 0,
}]
},
options: {
cutout: '60%', // Donut hole
plugins: { legend: { position: 'bottom' } }
}
});
6. Accessibility (a11y)
- Canvas fallback: Provide data table as
<table>inside<noscript>or hidden accessible summary - ARIA:
role="img"+aria-labeldescribing chart data on canvas element - Colors: Chart colors must have sufficient contrast; avoid relying on color alone — use patterns/labels
- Focus: Legend items should be focusable for keyboard navigation
- Reduced motion: Disable Chart.js animations when
prefers-reduced-motion
7. Best Practices & Rules
- Không Hardcode colors: Always read from CSS variables via bridge function
- Card wrapper: Always wrap charts in Prisma
cardcomponent - Responsive: Use Chart.js
responsive: true+maintainAspectRatio: true - Legend: Bottom position for horizontal charts, right for vertical
- Tooltips: Use custom HTML tooltip styled with Prisma tokens
- Loading: Show
skeletoncomponent while chart data loads - Empty state: Show
empty-statecomponent when no data
8. Example Usage
html
<!-- Bar chart wrapped in card -->
<div class="card">
<div class="slot-header">
<h3>Doanh thu tuần này</h3>
</div>
<div class="slot-body">
<canvas id="revenue-chart"></canvas>
</div>
</div>
<!-- Donut chart -->
<div class="card">
<div class="slot-header">
<h3>Phương thức thanh toán</h3>
</div>
<div class="slot-body">
<canvas id="payment-chart"></canvas>
</div>
</div>