ECharts 直角坐标系(grid)完全指南
文档类型: 深度技术文档
难度等级: ⭐⭐
源码版本: ECharts 5.x
本文行数: 约580行
📋 目录
🎯 grid基础概念
什么是grid?
grid是ECharts中的直角坐标系容器,用于承载xAxis和yAxis,控制图表的绘图区域。
typescript
const option = {
// 定义网格容器
grid: {
left: '10%', // 左边距
right: '10%', // 右边距
bottom: '10%', // 底边距
top: '15%', // 顶边距
containLabel: true // 包含坐标轴标签
},
// X轴 (属于grid)
xAxis: {
type: 'category',
data: ['周一', '周二', '周三']
},
// Y轴 (属于grid)
yAxis: {
type: 'value'
},
// 系列 (在grid中绘制)
series: [{
type: 'bar',
data: [120, 200, 150]
}]
};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
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
默认行为:
- 不配置grid时,ECharts使用默认网格
- 自动计算边距以容纳标签
📐 网格位置与尺寸
精确控制网格位置
typescript
const option = {
grid: {
// === 方式1: 百分比 ===
left: '10%',
right: '15%',
top: '20%',
bottom: '10%',
// === 方式2: 像素值 ===
// left: 50,
// right: 50,
// top: 60,
// bottom: 40,
// === 方式3: 混合使用 ===
// left: '10%',
// right: 80,
// 是否包含坐标轴标签
containLabel: true
}
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
containLabel的作用:
typescript
// ❌ containLabel: false (默认)
// 标签可能被裁剪
grid: {
left: 50,
containLabel: false
}
// ✅ containLabel: true
// 自动调整以确保标签完整显示
grid: {
left: 50,
containLabel: true
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
固定宽高比
typescript
const option = {
grid: {
left: 'center', // 居中
top: 'middle', // 垂直居中
width: 600, // 固定宽度
height: 400, // 固定高度
// 或者使用aspectRatio保持比例
// aspectRatio: 16 / 9
}
};1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
🔲 多网格布局
上下双网格 (对比两个指标)
typescript
const option = {
// 定义两个网格
grid: [
{
left: '10%',
right: '10%',
top: '10%',
height: '35%' // 上网格
},
{
left: '10%',
right: '10%',
top: '55%',
height: '35%' // 下网格
}
],
// X轴分别对应两个网格
xAxis: [
{
type: 'category',
gridIndex: 0, // 属于第一个网格
data: ['1月', '2月', '3月', '4月', '5月'],
axisLabel: { show: false } // 隐藏上图标签
},
{
type: 'category',
gridIndex: 1, // 属于第二个网格
data: ['1月', '2月', '3月', '4月', '5月']
}
],
// Y轴分别对应两个网格
yAxis: [
{
type: 'value',
gridIndex: 0,
name: '销售额'
},
{
type: 'value',
gridIndex: 1,
name: '利润'
}
],
// 系列分配到不同网格
series: [
{
name: '销售额',
type: 'bar',
xAxisIndex: 0, // 使用第一个X轴
yAxisIndex: 0, // 使用第一个Y轴
data: [120, 200, 150, 80, 70]
},
{
name: '利润',
type: 'line',
xAxisIndex: 1, // 使用第二个X轴
yAxisIndex: 1, // 使用第二个Y轴
data: [30, 50, 40, 20, 15],
smooth: true
}
]
};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
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
左右双网格 (对比两组数据)
typescript
const option = {
grid: [
{
left: '5%',
right: '55%', // 左网格
top: '10%',
bottom: '10%'
},
{
left: '55%', // 右网格
right: '5%',
top: '10%',
bottom: '10%'
}
],
xAxis: [
{
type: 'category',
gridIndex: 0,
data: ['产品A', '产品B', '产品C']
},
{
type: 'category',
gridIndex: 1,
data: ['产品D', '产品E', '产品F']
}
],
yAxis: [
{ type: 'value', gridIndex: 0 },
{ type: 'value', gridIndex: 1 }
],
series: [
{
name: '上半年',
type: 'bar',
xAxisIndex: 0,
yAxisIndex: 0,
data: [120, 200, 150]
},
{
name: '下半年',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
data: [180, 220, 190]
}
]
};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
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
四宫格布局 (Dashboard)
typescript
const option = {
// 四个网格
grid: [
{ left: '5%', right: '55%', top: '5%', height: '40%' }, // 左上
{ left: '55%', right: '5%', top: '5%', height: '40%' }, // 右上
{ left: '5%', right: '55%', top: '55%', height: '40%' }, // 左下
{ left: '55%', right: '5%', top: '55%', height: '40%' } // 右下
],
// 每个网格一个X轴
xAxis: [
{ type: 'category', gridIndex: 0, data: ['Q1', 'Q2', 'Q3', 'Q4'] },
{ type: 'category', gridIndex: 1, data: ['Q1', 'Q2', 'Q3', 'Q4'] },
{ type: 'category', gridIndex: 2, data: ['Q1', 'Q2', 'Q3', 'Q4'] },
{ type: 'category', gridIndex: 3, data: ['Q1', 'Q2', 'Q3', 'Q4'] }
],
// 每个网格一个Y轴
yAxis: [
{ type: 'value', gridIndex: 0 },
{ type: 'value', gridIndex: 1 },
{ type: 'value', gridIndex: 2 },
{ type: 'value', gridIndex: 3 }
],
// 四个系列
series: [
{
name: '销售额',
type: 'bar',
xAxisIndex: 0,
yAxisIndex: 0,
data: [480, 520, 610, 590]
},
{
name: '订单量',
type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
data: [320, 332, 301, 334],
smooth: true
},
{
name: '客单价',
type: 'bar',
xAxisIndex: 2,
yAxisIndex: 2,
data: [150, 156, 202, 176]
},
{
name: '转化率',
type: 'line',
xAxisIndex: 3,
yAxisIndex: 3,
data: [3.2, 3.5, 3.8, 3.6],
smooth: true
}
]
};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
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
💻 实战案例
案例1: 主从图表联动
typescript
import * as echarts from 'echarts';
class MasterDetailChart {
private masterChart: echarts.ECharts;
private detailChart: echarts.ECharts;
constructor(masterContainer: HTMLElement, detailContainer: HTMLElement) {
this.masterChart = echarts.init(masterContainer);
this.detailChart = echarts.init(detailContainer);
this.init();
}
private init() {
// 主图 - 年度数据
const masterOption = {
title: { text: '年度销售趋势' },
grid: {
left: '10%',
right: '10%',
top: '15%',
bottom: '10%'
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: { type: 'value' },
series: [{
type: 'line',
data: [120, 150, 180, 220, 260, 300, 340, 380, 420, 460, 500, 540],
smooth: true
}]
};
// 详情图 - 月度数据 (初始为空)
const detailOption = {
title: { text: '月度详细数据' },
grid: {
left: '10%',
right: '10%',
top: '15%',
bottom: '10%'
},
xAxis: {
type: 'category',
data: Array.from({ length: 30 }, (_, i) => `${i + 1}日`)
},
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: [] // 动态填充
}]
};
this.masterChart.setOption(masterOption);
this.detailChart.setOption(detailOption);
// 联动: 点击主图更新详情图
this.masterChart.on('click', (params: any) => {
this.updateDetail(params.dataIndex);
});
}
private updateDetail(monthIndex: number) {
// 模拟生成该月的每日数据
const dailyData = Array.from({ length: 30 }, () =>
Math.floor(Math.random() * 50 + 10)
);
this.detailChart.setOption({
series: [{
data: dailyData
}]
});
}
resize() {
this.masterChart.resize();
this.detailChart.resize();
}
dispose() {
this.masterChart.dispose();
this.detailChart.dispose();
}
}
// 使用
const chart = new MasterDetailChart(
document.getElementById('master')!,
document.getElementById('detail')!
);
window.addEventListener('resize', () => chart.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
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
案例2: 响应式网格
typescript
class ResponsiveGrid {
private chart: echarts.ECharts;
constructor(container: HTMLElement) {
this.chart = echarts.init(container);
this.render();
// 监听窗口大小变化
window.addEventListener('resize', () => this.handleResize());
}
private handleResize() {
const width = window.innerWidth;
if (width < 768) {
// 移动端: 单列布局
this.renderSingleColumn();
} else {
// PC端: 双列布局
this.renderDoubleColumn();
}
this.chart.resize();
}
private renderSingleColumn() {
this.chart.setOption({
grid: [
{ left: '10%', right: '10%', top: '5%', height: '40%' },
{ left: '10%', right: '10%', top: '55%', height: '40%' }
],
// ... 其他配置
});
}
private renderDoubleColumn() {
this.chart.setOption({
grid: [
{ left: '5%', right: '55%', top: '10%', bottom: '10%' },
{ left: '55%', right: '5%', top: '10%', bottom: '10%' }
],
// ... 其他配置
});
}
private render() {
this.renderDoubleColumn();
}
dispose() {
this.chart.dispose();
}
}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
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
🎯 最佳实践总结
✅ DO - 推荐做法
始终设置containLabel
typescriptgrid: { containLabel: true // 避免标签被裁剪 }1
2
3多网格时使用gridIndex明确关联
typescriptxAxis: { gridIndex: 0 }, yAxis: { gridIndex: 0 }, series: { xAxisIndex: 0, yAxisIndex: 0 }1
2
3响应式设计
typescriptwindow.addEventListener('resize', () => { chart.resize(); });1
2
3
❌ DON'T - 避免做法
- 避免网格重叠typescript
// ❌ 不好 - 网格可能重叠 grid: [ { top: '10%', height: '50%' }, { top: '40%', height: '50%' } // 与上一个重叠 ] // ✅ 好 - 明确分隔 grid: [ { top: '10%', height: '35%' }, { top: '55%', height: '35%' } // 有间隔 ]1
2
3
4
5
6
7
8
9
10
11
🔗 相关资源
下一篇: polar极坐标系.md)
直角坐标系网格
{
"title": {
"text": "多图表布局",
"left": "center"
},
"grid": [
{
"left": "5%",
"right": "55%",
"top": "15%",
"height": "35%"
},
{
"left": "55%",
"right": "5%",
"top": "15%",
"height": "35%"
},
{
"left": "5%",
"right": "55%",
"top": "60%",
"height": "35%"
},
{
"left": "55%",
"right": "5%",
"top": "60%",
"height": "35%"
}
],
"xAxis": [
{
"gridIndex": 0,
"type": "category",
"data": [
"A",
"B",
"C"
]
},
{
"gridIndex": 1,
"type": "category",
"data": [
"D",
"E",
"F"
]
},
{
"gridIndex": 2,
"type": "category",
"data": [
"G",
"H",
"I"
]
},
{
"gridIndex": 3,
"type": "category",
"data": [
"J",
"K",
"L"
]
}
],
"yAxis": [
{
"gridIndex": 0,
"type": "value"
},
{
"gridIndex": 1,
"type": "value"
},
{
"gridIndex": 2,
"type": "value"
},
{
"gridIndex": 3,
"type": "value"
}
],
"series": [
{
"type": "bar",
"xAxisIndex": 0,
"yAxisIndex": 0,
"data": [
10,
20,
30
]
},
{
"type": "line",
"xAxisIndex": 1,
"yAxisIndex": 1,
"data": [
15,
25,
35
]
},
{
"type": "bar",
"xAxisIndex": 2,
"yAxisIndex": 2,
"data": [
12,
22,
32
]
},
{
"type": "line",
"xAxisIndex": 3,
"yAxisIndex": 3,
"data": [
18,
28,
38
]
}
]
}下一篇: polar极坐标系.md)
