数据可视化与 Charts
为什么需要数据可视化?
数据可视化是将数据转换为图形表示的过程,帮助用户更直观地理解和分析数据。在现代 Web 应用中,图表是展示数据的重要方式。
- 📊 直观展示数据趋势
- 📈 快速发现数据模式
- 🎯 提升用户体验
- 💡 支持数据驱动决策
Chart.js 简介
Chart.js 是最流行的 JavaScript 图表库之一,简单易用,支持多种图表类型。
- ✅ 轻量级 (~200KB)
- ✅ 响应式设计
- ✅ 动画效果
- ✅ 支持多种图表类型
- ✅ 高度可定制
- ✅ 良好的 TypeScript 支持
// 安装
npm install chart.js
// 或使用 CDN
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
基本使用
// HTML
<canvas id="myChart"></canvas>
// JavaScript
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'bar', // 图表类型
data: {
labels: ['一月', '二月', '三月', '四月', '五月', '六月'],
datasets: [{
label: '销售额',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
图表类型
1. 柱状图
new Chart(ctx, {
type: 'bar',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
label: '数据集 1',
data: [10, 20, 15, 25],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 1
}]
}
});
2. 折线图
new Chart(ctx, {
type: 'line',
data: {
labels: ['1月', '2月', '3月', '4月', '5月'],
datasets: [{
label: '趋势',
data: [65, 59, 80, 81, 56],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1 // 曲线平滑度
}]
},
options: {
elements: {
line: {
tension: 0.4
}
}
}
});
3. 饼图
new Chart(ctx, {
type: 'pie',
data: {
labels: ['红色', '蓝色', '黄色'],
datasets: [{
data: [300, 50, 100],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)'
],
hoverOffset: 4
}]
}
});
4. 环形图
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['类别 A', '类别 B', '类别 C'],
datasets: [{
data: [10, 20, 30],
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56'
],
hoverOffset: 4
}]
},
options: {
cutout: '50%' // 中心圆大小
}
});
5. 极坐标图
new Chart(ctx, {
type: 'polarArea',
data: {
labels: ['红色', '蓝色', '黄色', '绿色', '橙色'],
datasets: [{
data: [11, 16, 7, 3, 14],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(75, 192, 192)',
'rgb(255, 205, 86)',
'rgb(201, 203, 207)',
'rgb(54, 162, 235)'
]
}]
}
});
6. 雷达图
new Chart(ctx, {
type: 'radar',
data: {
labels: ['速度', '力量', '耐力', '技巧', '智力'],
datasets: [{
label: '运动员 A',
data: [85, 90, 75, 80, 85],
fill: true,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgb(255, 99, 132)',
pointBackgroundColor: 'rgb(255, 99, 132)'
}, {
label: '运动员 B',
data: [90, 85, 80, 90, 75],
fill: true,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgb(54, 162, 235)',
pointBackgroundColor: 'rgb(54, 162, 235)'
}]
}
});
高级配置
多数据集
const myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['1月', '2月', '3月', '4月', '5月'],
datasets: [{
label: '2023',
data: [65, 59, 80, 81, 56],
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)'
}, {
label: '2024',
data: [28, 48, 40, 19, 86],
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)'
}]
}
});
自定义工具提示
const myChart = new Chart(ctx, {
type: 'bar',
data: { /* ... */ },
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
const value = context.parsed.y;
const total = context.chart.data.datasets.reduce((acc, ds) => {
return acc + ds.data[context.dataIndex];
}, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `${label}: ${value} (${percentage}%)`;
}
}
}
}
}
});
响应式配置
const myChart = new Chart(ctx, {
type: 'bar',
data: { /* ... */ },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
font: {
size: 14
}
}
}
}
}
});
自定义颜色
// 动态生成颜色
function generateColors(count) {
const colors = [];
for (let i = 0; i < count; i++) {
const hue = (i * 360 / count) % 360;
colors.push(`hsl(${hue}, 70%, 60%)`);
}
return colors;
}
const myChart = new Chart(ctx, {
type: 'pie',
data: {
labels: ['A', 'B', 'C', 'D', 'E'],
datasets: [{
data: [10, 20, 30, 40, 50],
backgroundColor: generateColors(5)
}]
}
});
动态更新图表
// 添加数据
function addData(chart, label, data) {
chart.data.labels.push(label);
chart.data.datasets.forEach((dataset) => {
dataset.data.push(data);
});
chart.update();
}
// 删除数据
function removeData(chart) {
chart.data.labels.pop();
chart.data.datasets.forEach((dataset) => {
dataset.data.pop();
});
chart.update();
}
// 更新特定数据点
function updateData(chart, index, newValue) {
chart.data.datasets[0].data[index] = newValue;
chart.update();
}
// 替换所有数据
function replaceData(chart, newData) {
chart.data.datasets[0].data = newData;
chart.update();
}
最佳实践
1. 性能优化
// 禁用动画提高性能
const myChart = new Chart(ctx, {
type: 'bar',
data: { /* ... */ },
options: {
animation: {
duration: 0 // 禁用动画
},
interaction: {
mode: 'index',
intersect: false
}
}
});
// 大数据集优化
const myChart = new Chart(ctx, {
type: 'line',
data: { /* 大量数据 */ },
options: {
parsing: {
xAxisKey: 'x',
yAxisKey: 'y'
},
plugins: {
decimation: {
enabled: true,
algorithm: 'lttb'
}
}
}
});
2. 可访问性
// 添加可访问性描述
<canvas id="myChart" role="img" aria-label="销售数据图表"></canvas>
const myChart = new Chart(ctx, {
type: 'bar',
data: { /* ... */ },
options: {
plugins: {
accessible: {
enabled: true,
title: '2024年销售数据',
description: '显示各月份销售额的柱状图'
}
}
}
});
3. 主题切换
// 亮色主题
const lightTheme = {
color: '#666',
grid: {
color: '#e0e0e0'
},
ticks: {
color: '#666'
}
};
// 暗色主题
const darkTheme = {
color: '#fff',
grid: {
color: '#333'
},
ticks: {
color: '#fff'
}
};
// 应用主题
function applyTheme(chart, theme) {
chart.options.scales.x = theme;
chart.options.scales.y = theme;
chart.update();
}
实战示例
实时数据更新
// 创建实时更新的图表
const ctx = document.getElementById('realtimeChart');
const realtimeChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '实时数据',
data: [],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
animation: {
duration: 0
},
scales: {
x: {
display: false
}
}
}
});
// 每秒添加新数据
setInterval(() => {
const now = new Date();
const value = Math.random() * 100;
realtimeChart.data.labels.push(now.toLocaleTimeString());
realtimeChart.data.datasets[0].data.push(value);
// 只保留最近 20 个数据点
if (realtimeChart.data.labels.length > 20) {
realtimeChart.data.labels.shift();
realtimeChart.data.datasets[0].data.shift();
}
realtimeChart.update();
}, 1000);
其他图表库
ECharts
百度的 ECharts 功能强大,适合复杂可视化需求。
// 安装
npm install echarts
// 基本使用
import * as echarts from 'echarts';
const chart = echarts.init(document.getElementById('chart'));
const option = {
title: { text: '示例图表' },
tooltip: {},
xAxis: { data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'] },
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
};
chart.setOption(option);
Recharts (React)
React 生态中流行的图表库,声明式 API。
// 安装
npm install recharts
// 使用
import { BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from 'recharts';
const data = [
{ name: 'Page A', uv: 4000, pv: 2400 },
{ name: 'Page B', uv: 3000, pv: 1398 },
];
function MyChart() {
return (
<BarChart width={600} height={300} data={data}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="uv" fill="#8884d8" />
<Bar dataKey="pv" fill="#82ca9d" />
</BarChart>
);
}
选择合适的图表库
- Chart.js - 简单易用,适合大多数场景
- ECharts - 功能强大,适合复杂可视化
- Recharts - React 项目首选
- D3.js - 完全自定义,适合高级用户
- Chart.js + React-Chartjs-2 - React 集成 Chart.js