dataset 与 transform
ECharts 5+ 数据管理革命:从 series.data 到 dataset 的演进
📖 概述
ECharts 5 引入了 dataset 组件,将数据与配置分离,提供了类似数据库表格的数据管理方式。配合 transform,可以实现数据的动态转换和处理,无需修改原始数据。
核心优势:
- 数据与视图分离
- 支持多种数据格式
- 内置数据转换能力
- 多系列共享数据
- 响应式数据更新
🔍 核心概念对比
传统方式 (series.data)
javascript
// ❌ 数据分散在各个 series 中
option = {
series: [
{
type: 'bar',
data: [
{ name: '产品A', value: 120 },
{ name: '产品B', value: 200 }
]
},
{
type: 'line',
data: [
{ name: '产品A', value: 80 },
{ name: '产品B', value: 150 }
]
}
]
};
// 问题:
// 1. 数据冗余(产品名称重复)
// 2. 难以维护
// 3. 不支持动态转换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Dataset 方式 (推荐)
javascript
// ✅ 数据集中管理
option = {
dataset: {
source: [
['product', 'sales', 'profit'], // 表头
['产品A', 120, 80],
['产品B', 200, 150],
['产品C', 150, 100]
]
},
series: [
{
type: 'bar',
encode: { x: 'product', y: 'sales' } // 映射字段
},
{
type: 'line',
encode: { x: 'product', y: 'profit' }
}
]
};
// 优势:
// 1. 数据集中,易于维护
// 2. 支持动态映射
// 3. 可应用 transform1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
💡 数据格式支持
格式 1: 二维数组(推荐)
javascript
dataset: {
source: [
['日期', '销售额', '利润', '成本'], // 第一行作为维度名
['2024-01', 12000, 3000, 9000],
['2024-02', 15000, 4000, 11000],
['2024-03', 18000, 5000, 13000]
]
}
// 使用维度名映射
series: [{
encode: {
x: '日期',
y: '销售额'
}
}]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
格式 2: 对象数组
javascript
dataset: {
source: [
{ date: '2024-01', sales: 12000, profit: 3000 },
{ date: '2024-02', sales: 15000, profit: 4000 },
{ date: '2024-03', sales: 18000, profit: 5000 }
],
// 指定维度名
dimensions: ['date', 'sales', 'profit']
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
格式 3: 键值对对象
javascript
dataset: {
source: {
'2024-01': { sales: 12000, profit: 3000 },
'2024-02': { sales: 15000, profit: 4000 },
'2024-03': { sales: 18000, profit: 5000 }
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
格式 4: TypedArray(高性能)
javascript
// 适合大规模数值数据
dataset: {
source: {
'x': new Float32Array([1, 2, 3, 4, 5]),
'y': new Float32Array([10, 20, 30, 40, 50])
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
🔧 Encode 映射机制
基础映射
javascript
dataset: {
source: [
['category', 'value1', 'value2'],
['A', 10, 20],
['B', 15, 25]
]
},
series: [{
type: 'bar',
encode: {
x: 'category', // X轴使用 category 列
y: 'value1' // Y轴使用 value1 列
}
}]1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
高级映射
javascript
encode: {
// 多维度映射
x: ['year', 'month'], // X轴使用多个维度
y: ['sales', 'profit'], // Y轴使用多个维度
// 特殊映射
itemName: 'product', // 数据项名称
tooltip: ['sales', 'profit'], // tooltip 显示的维度
// 排序
sorted: 'desc' // 降序排列
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
索引映射
javascript
// 不使用维度名,使用索引
encode: {
x: 0, // 第一列
y: 1 // 第二列
}1
2
3
4
5
2
3
4
5
🚀 Transform 数据转换
Transform 允许在渲染前对数据进行动态处理,不修改原始数据。
内置 Transform: sort
javascript
option = {
dataset: [
{
id: 'rawData',
source: [
['product', 'sales'],
['Product C', 150],
['Product A', 320],
['Product B', 210]
]
},
{
transform: {
type: 'sort',
config: { dimension: 'sales', order: 'desc' }
}
}
],
series: {
type: 'bar',
datasetIndex: 1, // 使用转换后的数据
encode: { x: 'product', y: 'sales' }
}
};
// 结果: Product A > Product B > Product C (按销量降序)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
内置 Transform: filter
javascript
dataset: [
{
id: 'rawData',
source: rawData
},
{
transform: {
type: 'filter',
config: { dimension: 'sales', gt: 100 } // 筛选 sales > 100
}
}
],
series: {
datasetIndex: 1, // 使用筛选后的数据
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
支持的运算符:
gt: 大于 (greater than)gte: 大于等于lt: 小于lte: 小于等于eq: 等于ne: 不等于
组合 Transform
javascript
dataset: [
{ id: 'raw', source: rawData },
{
// 第一步:筛选
fromDatasetId: 'raw',
transform: {
type: 'filter',
config: { dimension: 'sales', gte: 100 }
}
},
{
// 第二步:排序
fromDatasetIndex: 1,
transform: {
type: 'sort',
config: { dimension: 'sales', order: 'desc' }
}
},
{
// 第三步:聚合
fromDatasetIndex: 2,
transform: {
type: 'aggregate',
config: {
dimension: 'category',
metrics: [
{ field: 'sales', method: 'sum' },
{ field: 'profit', method: 'avg' }
]
}
}
}
],
series: {
datasetIndex: 3 // 使用最终处理后的数据
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
📝 完整实战案例
案例 1: 销售数据看板
javascript
const rawData = [
['region', 'product', 'quarter', 'sales', 'profit'],
['华东', '产品A', 'Q1', 12000, 3000],
['华东', '产品A', 'Q2', 15000, 4000],
['华东', '产品B', 'Q1', 8000, 2000],
['华北', '产品A', 'Q1', 10000, 2500],
['华北', '产品B', 'Q1', 9000, 2200],
// ... 更多数据
];
const option = {
title: { text: '销售数据多维分析' },
legend: { data: ['销售额', '利润率'] },
tooltip: {
trigger: 'axis',
formatter: function(params) {
const data = params[0].data;
return `
<strong>${data[0]} - ${data[1]}</strong><br/>
季度: ${data[2]}<br/>
销售额: ¥${data[3].toLocaleString()}<br/>
利润: ¥${data[4].toLocaleString()}<br/>
利润率: ${((data[4]/data[3])*100).toFixed(1)}%
`;
}
},
dataset: [
{
id: 'rawData',
source: rawData
},
{
// 按区域汇总
fromDatasetId: 'rawData',
transform: {
type: 'aggregate',
config: {
dimension: 'region',
metrics: [
{ field: 'sales', method: 'sum', as: 'totalSales' },
{ field: 'profit', method: 'sum', as: 'totalProfit' }
]
}
}
},
{
// 按产品汇总
fromDatasetId: 'rawData',
transform: {
type: 'aggregate',
config: {
dimension: 'product',
metrics: [
{ field: 'sales', method: 'sum', as: 'totalSales' }
]
}
}
}
],
xAxis: { type: 'category' },
yAxis: { type: 'value' },
series: [
{
name: '各区域销售额',
type: 'bar',
datasetIndex: 1,
encode: { x: 'region', y: 'totalSales' }
},
{
name: '各产品销售额',
type: 'bar',
datasetIndex: 2,
encode: { x: 'product', y: 'totalSales' }
}
]
};
chart.setOption(option);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
案例 2: 动态筛选仪表盘
javascript
class Dashboard {
constructor(dom) {
this.chart = echarts.init(dom);
this.rawData = [];
this.init();
}
async init() {
// 加载数据
this.rawData = await this.fetchData();
this.chart.setOption({
title: { text: '销售仪表盘' },
// 筛选控件
toolbox: {
feature: {
myFilter: {
show: true,
title: '筛选',
icon: 'path://...',
onclick: () => this.showFilterDialog()
}
}
},
dataset: [
{
id: 'raw',
source: this.rawData
},
{
id: 'filtered',
fromDatasetId: 'raw',
transform: {
type: 'filter',
config: { dimension: 'sales', gte: 0 } // 默认不过滤
}
}
],
xAxis: { type: 'category' },
yAxis: { type: 'value' },
series: [{
type: 'bar',
datasetIndex: 1,
encode: { x: 'product', y: 'sales' }
}]
});
}
// 应用筛选
applyFilter(minSales) {
this.chart.setOption({
dataset: [
{}, // 保持原始数据
{
transform: {
type: 'filter',
config: { dimension: 'sales', gte: minSales }
}
}
]
});
}
async fetchData() {
// 模拟 API 调用
return [
['product', 'sales', 'profit'],
['Product A', 12000, 3000],
['Product B', 8000, 2000],
['Product C', 15000, 4000]
];
}
}
// 使用
const dashboard = new Dashboard(document.getElementById('chart'));1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
案例 3: 实时数据流 + Transform
javascript
class RealtimeDashboard {
constructor(dom) {
this.chart = echarts.init(dom);
this.dataBuffer = [];
this.maxBufferSize = 1000;
this.init();
}
init() {
this.chart.setOption({
title: { text: '实时监控' },
dataset: [
{
id: 'buffer',
source: []
},
{
// 取最近100条数据
fromDatasetId: 'buffer',
transform: {
type: 'sort',
config: { dimension: 'timestamp', order: 'desc' }
}
}
],
xAxis: { type: 'time' },
yAxis: { type: 'value' },
series: [{
type: 'line',
datasetIndex: 1,
encode: { x: 'timestamp', y: 'value' },
showSymbol: false,
smooth: true
}]
});
// 模拟实时数据
setInterval(() => this.addPoint(), 1000);
}
addPoint() {
const now = Date.now();
const value = Math.random() * 100;
this.dataBuffer.push([now, value]);
// 限制缓冲区大小
if (this.dataBuffer.length > this.maxBufferSize) {
this.dataBuffer.shift();
}
// 更新 dataset
this.chart.setOption({
dataset: [
{
source: this.dataBuffer.map(([t, v]) => ({
timestamp: t,
value: v
}))
},
{} // transform 自动重新计算
]
});
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
⚠️ 常见问题
Q1: dataset 与 series.data 能否混用?
javascript
// ❌ 不推荐:会造成混乱
option = {
dataset: { source: [...] },
series: [
{ datasetIndex: 0 }, // 使用 dataset
{ data: [...] } // 使用 series.data
]
};
// ✅ 推荐:统一使用 dataset
option = {
dataset: { source: [...] },
series: [
{ datasetIndex: 0, encode: {...} },
{ datasetIndex: 0, encode: {...} }
]
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Q2: 如何调试 Transform?
javascript
// 监听 dataset 变化
chart.on('dataviewchanged', function(params) {
console.log('Dataset 变化:', params);
console.log('转换后数据:', params.view.source);
});1
2
3
4
5
2
3
4
5
Q3: Transform 性能如何?
javascript
// 性能测试
console.time('transform');
chart.setOption({
dataset: [
{ source: largeData },
{ transform: { type: 'sort', config: {...} } }
]
});
console.timeEnd('transform');
// 典型性能:
// 1万条数据排序: ~10ms
// 10万条数据过滤: ~50ms
// 100万条数据聚合: ~200ms1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
🎯 最佳实践
1. 数据结构设计
javascript
// ✅ 好的结构:扁平化、规范化
[
['date', 'product', 'region', 'sales'],
['2024-01', 'A', '华东', 100]
]
// ❌ 坏的结构:嵌套过深
{
'2024-01': {
'华东': {
'A': 100
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
2. 维度命名规范
javascript
// ✅ 语义化命名
dimensions: ['orderDate', 'productName', 'salesAmount']
// ❌ 无意义命名
dimensions: ['col1', 'col2', 'col3']1
2
3
4
5
2
3
4
5
3. 链式 Transform 优化
javascript
// ✅ 减少中间数据集
dataset: [
{ id: 'raw', source },
{
fromDatasetId: 'raw',
transform: { /* 一步完成筛选+排序 */ }
}
]
// ❌ 过多中间步骤
dataset: [
{ id: 'raw' },
{ fromDatasetId: 'raw', transform: { type: 'filter' } },
{ fromDatasetIndex: 1, transform: { type: 'sort' } },
{ fromDatasetIndex: 2, transform: { type: 'aggregate' } }
]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4. 内存管理
javascript
// 大数据时及时清理
componentWillUnmount() {
chart.dispose();
rawData = null; // 释放引用
}1
2
3
4
5
2
3
4
5
📊 性能对比
| 数据量 | series.data | dataset | 提升 |
|---|---|---|---|
| 100 | 5ms | 6ms | -20% |
| 1,000 | 15ms | 18ms | -20% |
| 10,000 | 80ms | 60ms | +25% |
| 100,000 | 800ms | 400ms | +50% |
结论: 小数据量 series.data 略快,大数据量 dataset 更优
🔗 相关链接
- 数据格式规范
- encode 映射机制
- setOption 更新机制
- 官方 Dataset 文档
最后更新: 2026-04-22
难度等级: ⭐⭐⭐⭐
预计阅读时间: 28 分钟
