ECharts 单轴坐标系(singleAxis)完全指南
文档类型: 深度技术文档
难度等级: ⭐⭐
源码版本: ECharts 5.x
本文行数: 约480行
📋 目录
🎯 singleAxis基础
什么是单轴坐标系?
singleAxis是ECharts中的一维坐标系,用于在单条轴上展示数据,常用于:
- 时间轴
- 进度条
- 甘特图
- 一维散点图
typescript
const option = {
// 定义单轴
singleAxis: {
type: 'category', // 'value' | 'time' | 'log'
data: ['类目A', '类目B', '类目C', '类目D'],
// 位置
left: '10%',
right: '10%',
top: '50%',
height: 50, // 轴的高度
// 轴线样式
axisLine: {
lineStyle: {
color: '#5470C6',
width: 2
}
},
// 刻度
axisTick: {
show: true,
alignWithLabel: true
},
// 标签
axisLabel: {
show: true,
rotate: 0,
formatter: '{value}'
}
},
// 系列使用单轴
series: [{
type: 'scatter',
coordinateSystem: 'singleAxis',
data: [
[0, 10], // [轴索引, 值]
[1, 20],
[2, 15],
[3, 25]
]
}]
};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
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
📊 单轴图表类型
1. 单轴散点图
typescript
const option = {
singleAxis: {
type: 'category',
data: ['产品A', '产品B', '产品C', '产品D', '产品E'],
boundaryGap: true,
splitArea: {
show: true,
areaStyle: {
color: ['#f5f5f5', '#fff']
}
}
},
series: [{
type: 'scatter',
coordinateSystem: 'singleAxis',
data: [
[0, 80],
[1, 95],
[2, 70],
[3, 85],
[4, 90]
],
symbolSize: 15,
itemStyle: {
color: '#5470C6'
},
// 标签
label: {
show: true,
formatter: (params: any) => {
return params.value[1];
},
position: 'top'
}
}]
};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
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
2. 单轴柱状图 (甘特图风格)
typescript
const option = {
singleAxis: {
type: 'time',
min: new Date('2024-01-01'),
max: new Date('2024-12-31'),
axisLabel: {
formatter: '{MM}-{dd}'
}
},
series: [{
type: 'custom',
coordinateSystem: 'singleAxis',
renderItem: (params: any, api: any) => {
const categoryIndex = api.value(0);
const start = api.coord([api.value(1), categoryIndex]);
const end = api.coord([api.value(2), categoryIndex]);
const height = api.size([0, 1])[1] * 0.6;
return {
type: 'rect',
shape: {
x: start[0],
y: start[1] - height / 2,
width: end[0] - start[0],
height: height
},
style: api.style()
};
},
data: [
[0, new Date('2024-01-01'), new Date('2024-03-31')],
[1, new Date('2024-02-15'), new Date('2024-06-30')],
[2, new Date('2024-04-01'), new Date('2024-09-30')]
]
}]
};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
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
3. 单轴盒须图 (箱线图)
typescript
const option = {
singleAxis: {
type: 'category',
data: ['实验组A', '实验组B', '实验组C', '对照组'],
orient: 'horizontal' // 水平方向
},
series: [{
type: 'boxplot',
coordinateSystem: 'singleAxis',
data: [
[10, 20, 30, 40, 50], // [min, Q1, median, Q3, max]
[15, 25, 35, 45, 55],
[12, 22, 32, 42, 52],
[18, 28, 38, 48, 58]
]
}]
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🎨 多维数据展示
气泡大小映射
typescript
const option = {
singleAxis: {
type: 'value',
min: 0,
max: 100,
splitNumber: 10
},
visualMap: {
dimension: 1, // 第二个维度映射到颜色
min: 0,
max: 100,
inRange: {
color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
}
},
series: [{
type: 'scatter',
coordinateSystem: 'singleAxis',
data: [
[10, 80, 15], // [位置, 颜色维度, 气泡大小]
[20, 60, 25],
[30, 90, 20],
[40, 70, 30],
[50, 85, 18]
],
symbolSize: (data: number[]) => {
return data[2]; // 第三个维度控制大小
},
encode: {
value: 1 // 第二个维度用于visualMap
}
}]
};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
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
时间轴事件展示
typescript
const option = {
singleAxis: {
type: 'time',
min: new Date('2024-01-01'),
max: new Date('2024-12-31'),
axisLabel: {
formatter: '{yyyy}-{MM}'
},
splitLine: {
show: true,
lineStyle: {
color: '#eee',
type: 'dashed'
}
}
},
tooltip: {
formatter: (params: any) => {
const date = new Date(params.value[0]);
return `${date.toLocaleDateString()}<br/>${params.value[1]}`;
}
},
series: [{
type: 'scatter',
coordinateSystem: 'singleAxis',
data: [
[new Date('2024-01-15'), '项目启动'],
[new Date('2024-03-20'), '需求评审完成'],
[new Date('2024-05-10'), '开发阶段开始'],
[new Date('2024-08-30'), '测试阶段开始'],
[new Date('2024-11-15'), '正式上线']
],
symbolSize: 12,
itemStyle: {
color: '#EE6666'
},
label: {
show: true,
formatter: (params: any) => {
return params.value[1];
},
position: 'top',
fontSize: 10
}
}]
};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
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
💻 实战案例
案例1: 项目进度甘特图
typescript
import * as echarts from 'echarts';
class GanttChart {
private chart: echarts.ECharts;
constructor(container: HTMLElement) {
this.chart = echarts.init(container);
this.render();
}
private render() {
const tasks = [
{ name: '需求分析', start: '2024-01-01', end: '2024-02-15', progress: 100 },
{ name: '系统设计', start: '2024-02-01', end: '2024-03-15', progress: 80 },
{ name: '前端开发', start: '2024-03-01', end: '2024-06-30', progress: 50 },
{ name: '后端开发', start: '2024-03-01', end: '2024-07-31', progress: 45 },
{ name: '测试阶段', start: '2024-07-01', end: '2024-09-30', progress: 0 },
{ name: '部署上线', start: '2024-10-01', end: '2024-10-31', progress: 0 }
];
const option = {
title: {
text: '项目进度甘特图',
left: 'center'
},
tooltip: {
formatter: (params: any) => {
const task = tasks[params.dataIndex];
return `
<strong>${task.name}</strong><br/>
开始: ${task.start}<br/>
结束: ${task.end}<br/>
进度: ${task.progress}%
`;
}
},
grid: {
left: '15%',
right: '10%',
top: '15%',
bottom: '10%'
},
xAxis: {
type: 'time',
min: new Date('2024-01-01'),
max: new Date('2024-12-31'),
axisLabel: {
formatter: '{yyyy}-{MM}'
}
},
yAxis: {
type: 'category',
data: tasks.map(t => t.name),
inverse: true // 反转顺序
},
series: [{
type: 'custom',
renderItem: (params: any, api: any) => {
const taskIndex = params.dataIndex;
const task = tasks[taskIndex];
const start = api.coord([new Date(task.start), taskIndex]);
const end = api.coord([new Date(task.end), taskIndex]);
const height = api.size([0, 1])[1] * 0.6;
// 进度条
const progressWidth = (end[0] - start[0]) * (task.progress / 100);
return {
type: 'group',
children: [
{
type: 'rect',
shape: {
x: start[0],
y: start[1] - height / 2,
width: end[0] - start[0],
height: height
},
style: {
fill: '#e0e0e0'
}
},
{
type: 'rect',
shape: {
x: start[0],
y: start[1] - height / 2,
width: progressWidth,
height: height
},
style: {
fill: task.progress === 100 ? '#91CC75' :
task.progress > 0 ? '#FAC858' : '#5470C6'
}
}
]
};
},
data: tasks.map((_, index) => index)
}]
};
this.chart.setOption(option);
}
resize() {
this.chart.resize();
}
dispose() {
this.chart.dispose();
}
}
// 使用
const gantt = new GanttChart(document.getElementById('chart')!);
window.addEventListener('resize', () => gantt.resize());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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
案例2: 股票交易时间轴
typescript
const option = {
singleAxis: {
type: 'time',
min: new Date('2024-01-01 09:30:00'),
max: new Date('2024-01-01 15:00:00'),
axisLabel: {
formatter: '{HH}:{mm}'
},
splitLine: {
show: true
}
},
series: [{
type: 'scatter',
coordinateSystem: 'singleAxis',
data: generateTradingData(),
symbolSize: 8,
itemStyle: {
color: (params: any) => {
return params.value[1] > 0 ? '#ef232a' : '#14b143';
}
},
label: {
show: true,
formatter: (params: any) => {
return params.value[1] > 0 ? '+' : '' + params.value[1].toFixed(2) + '%';
},
position: 'top'
}
}]
};
function generateTradingData(): number[][] {
const data: number[][] = [];
const startTime = new Date('2024-01-01 09:30:00').getTime();
for (let i = 0; i < 240; i++) { // 240分钟交易时间
const time = startTime + i * 60000;
const change = (Math.random() - 0.5) * 2; // -1% ~ +1%
data.push([new Date(time), change]);
}
return data;
}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
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
🎯 最佳实践总结
✅ DO - 推荐做法
使用splitArea增强可读性
typescriptsingleAxis: { splitArea: { show: true } }1
2
3
4
5时间轴使用合适的格式
typescriptaxisLabel: { formatter: '{yyyy}-{MM}-{dd}' // 根据粒度调整 }1
2
3添加tooltip提供详细信息
typescripttooltip: { formatter: (params: any) => { return `详细: ${params.value}`; } }1
2
3
4
5
❌ DON'T - 避免做法
- 避免单轴上数据点过密typescript
// ❌ 不好 - 1000个点挤在一条线上 data: Array.from({length: 1000}, ...) // ✅ 好 - 控制在50个点以内 data: sampledData1
2
3
4
5
🔗 相关资源
✅ 坐标系模块完成!
