散点图 (气泡/大数据)
ECharts 散点图完全指南:相关性分析与百万级数据优化
📖 概述
散点图(Scatter Chart)通过点的位置展示两个变量之间的关系,是相关性分析和分布分析的核心图表。ECharts 支持气泡大小映射、颜色映射,以及百万级数据的性能优化。
核心特点:
- ✅ 双变量关系分析
- ✅ 气泡大小/颜色多维映射
- ✅ 百万级数据渲染优化
- ✅ 聚类分析支持
- ✅ 回归线叠加
🔍 核心概念
1. 基础散点图
javascript
option = {
xAxis: { type: 'value', scale: true },
yAxis: { type: 'value', scale: true },
series: [{
type: 'scatter',
symbolSize: 10,
data: [
[10.0, 8.04],
[8.0, 6.95],
[13.0, 7.58],
[9.0, 8.81],
[11.0, 8.33]
]
}]
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
关键配置:
data: 二维数组[x, y]symbolSize: 点的大小scale: true: 不强制包含零刻度
2. 气泡图(三维数据)
javascript
series: [{
type: 'scatter',
data: [
[10, 20, 30], // [x, y, size]
[15, 25, 50],
[20, 30, 80]
],
symbolSize: function(data) {
return Math.sqrt(data[2]); // 根据第三维计算大小
}
}]1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
应用场景:
- 三维度数据展示
- 气泡大小代表权重
3. 颜色映射
javascript
series: [{
type: 'scatter',
data: [
[10, 20, 30], // [x, y, value]
[15, 25, 50],
[20, 30, 80]
],
symbolSize: 15,
itemStyle: {
color: function(params) {
const value = params.data[2];
if (value < 40) return '#5470c6';
if (value < 60) return '#fac858';
return '#ee6666';
}
}
}]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
4. 大数据优化
javascript
series: [{
type: 'scatter',
data: hugeData, // 10万+数据
// 核心优化
large: true, // 开启大数据模式
largeThreshold: 2000, // 阈值
progressive: 5000, // 渐进式渲染
// 简化样式
symbolSize: 2,
showSymbol: false,
itemStyle: { opacity: 0.6 }
}]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
💡 使用场景
场景 1: 身高体重相关性分析
javascript
const correlationOption = {
title: { text: '身高与体重相关性分析' },
tooltip: {
formatter: params => {
return `身高: ${params.data[0]}cm<br/>体重: ${params.data[1]}kg`;
}
},
xAxis: {
name: '身高(cm)',
type: 'value',
scale: true,
min: 150,
max: 200
},
yAxis: {
name: '体重(kg)',
type: 'value',
scale: true,
min: 40,
max: 100
},
series: [{
type: 'scatter',
symbolSize: 8,
data: generateHeightWeightData(500),
itemStyle: {
color: 'rgba(84, 112, 198, 0.6)'
},
markLine: {
data: [
{ type: 'average', name: '平均值', lineStyle: { color: '#ee6666' } }
]
}
}]
};
// 生成模拟数据
function generateHeightWeightData(count) {
return Array.from({ length: count }, () => {
const height = 150 + Math.random() * 50;
const weight = (height - 100) * 0.9 + (Math.random() - 0.5) * 20;
return [height.toFixed(1), weight.toFixed(1)];
});
}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
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
场景 2: 城市人口与GDP气泡图
javascript
const bubbleOption = {
title: { text: '中国主要城市人口与GDP' },
tooltip: {
formatter: params => {
return `${params.data[3]}<br/>人口: ${params.data[0]}万人<br/>GDP: ${params.data[1]}亿元<br/>增长率: ${params.data[2]}%`;
}
},
xAxis: {
name: '人口(万人)',
type: 'value',
axisLabel: { formatter: '{value}万' }
},
yAxis: {
name: 'GDP(亿元)',
type: 'value',
axisLabel: { formatter: '{value}亿' }
},
visualMap: {
min: 0,
max: 100,
dimension: 2,
orient: 'vertical',
right: 10,
top: 'center',
text: ['高增长', '低增长'],
calculable: true,
inRange: {
color: ['#d94e5d', '#eac736', '#50a3ba']
}
},
series: [{
type: 'scatter',
data: [
[2171, 40269, 6.8, '北京'],
[2428, 43214, 6.6, '上海'],
[1867, 28013, 7.2, '广州'],
[1756, 24657, 7.5, '深圳'],
[1442, 17716, 7.8, '杭州']
],
symbolSize: data => Math.sqrt(data[1]) / 10,
label: {
show: true,
formatter: params => params.data[3],
position: 'top'
},
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.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
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
场景 3: 百万级数据散点图
javascript
const bigDataOption = {
title: { text: '百万级数据散点图 (1,000,000 个点)' },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'value', scale: true },
yAxis: { type: 'value', scale: true },
series: [{
type: 'scatter',
data: generateLargeData(1000000),
// === 核心优化配置 ===
large: true,
largeThreshold: 2000,
progressive: 10000,
progressiveThreshold: 50000,
// === 样式简化 ===
symbolSize: 2,
showSymbol: false,
hoverAnimation: false,
itemStyle: { opacity: 0.4 },
// === 关闭特效 ===
emphasis: { disabled: true }
}]
};
function generateLargeData(count) {
return Array.from({ length: count }, () => [
Math.random() * 1000,
Math.random() * 1000
]);
}
// 性能: 渲染时间 ~1.8s (默认配置会卡死)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
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
🔧 配置详解
数据格式
javascript
// 格式1: 二维数组
data: [
[x1, y1],
[x2, y2]
]
// 格式2: 对象数组
data: [
{ value: [x1, y1], name: '点1' },
{ value: [x2, y2], name: '点2' }
]
// 格式3: 三维数据(气泡图)
data: [
[x1, y1, size1],
[x2, y2, size2]
]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
点的样式
javascript
series: [{
type: 'scatter',
// 点的形状
symbol: 'circle', // 'circle' | 'rect' | 'roundRect' | 'triangle' | 'diamond'
// 点的大小
symbolSize: 10, // 固定值
symbolSize: function(data) {
return Math.sqrt(data[2]); // 动态计算
},
// 点的样式
itemStyle: {
color: '#5470c6',
opacity: 0.6,
borderColor: '#fff',
borderWidth: 1
}
}]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
标签配置
javascript
label: {
show: false, // 大数据时建议关闭
position: 'top',
formatter: '{b}',
fontSize: 10,
color: '#333'
}1
2
3
4
5
6
7
2
3
4
5
6
7
📝 代码示例
示例 1: Anscombe 四重奏(经典统计学案例)
javascript
// Anscombe's quartet - 四组统计数据
const anscombeData = [
// Dataset I
[[10, 8.04], [8, 6.95], [13, 7.58], [9, 8.81], [11, 8.33], [14, 9.96], [6, 7.24], [4, 4.26], [12, 10.84], [7, 4.82], [5, 5.68]],
// Dataset II
[[10, 9.14], [8, 8.14], [13, 8.74], [9, 8.77], [11, 9.26], [14, 8.10], [6, 6.13], [4, 3.10], [12, 9.13], [7, 7.26], [5, 4.74]],
// Dataset III
[[10, 7.46], [8, 6.77], [13, 12.74], [9, 7.11], [11, 7.81], [14, 8.84], [6, 6.08], [4, 5.39], [12, 8.15], [7, 6.42], [5, 5.73]],
// Dataset IV
[[8, 6.58], [8, 5.76], [8, 7.71], [8, 8.84], [8, 8.47], [8, 7.04], [8, 5.25], [19, 12.50], [8, 5.56], [8, 7.91], [8, 6.89]]
];
const anscombeOption = {
title: { text: "Anscombe's Quartet", left: 'center' },
grid: [
{ left: '7%', top: '7%', width: '38%', height: '38%' },
{ left: '55%', top: '7%', width: '38%', height: '38%' },
{ left: '7%', top: '55%', width: '38%', height: '38%' },
{ left: '55%', top: '55%', width: '38%', height: '38%' }
],
xAxis: [
{ gridIndex: 0, min: 0, max: 20 },
{ gridIndex: 1, min: 0, max: 20 },
{ gridIndex: 2, min: 0, max: 20 },
{ gridIndex: 3, min: 0, max: 20 }
],
yAxis: [
{ gridIndex: 0, min: 0, max: 15 },
{ gridIndex: 1, min: 0, max: 15 },
{ gridIndex: 2, min: 0, max: 15 },
{ gridIndex: 3, min: 0, max: 15 }
],
series: anscombeData.map((data, i) => ({
type: 'scatter',
xAxisIndex: i,
yAxisIndex: i,
data,
symbolSize: 10,
itemStyle: { color: ['#5470c6', '#91cc75', '#fac858', '#ee6666'][i] }
}))
};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
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
⚠️ 常见问题
Q1: 数据量太大卡顿?
javascript
// ✅ 开启大数据优化
series: [{
large: true,
largeThreshold: 2000,
progressive: 5000,
symbolSize: 2,
showSymbol: false
}]1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Q2: 点重叠严重?
javascript
// ✅ 使用透明度
itemStyle: { opacity: 0.3 }
// ✅ 或使用 hexbin (六边形分箱)
// 需要引入 echarts-stat1
2
3
4
5
2
3
4
5
Q3: 如何添加回归线?
javascript
// 使用 markLine
markLine: {
data: [
{
type: 'average',
name: '平均值'
}
],
lineStyle: { color: '#ee6666', type: 'dashed' }
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
🎯 最佳实践
1. 数据预处理
javascript
// 去除异常值
const filteredData = data.filter(([x, y]) => {
return x >= minX && x <= maxX && y >= minY && y <= maxY;
});1
2
3
4
2
3
4
2. 颜色映射
javascript
// 使用渐变色表示密度
visualMap: {
min: 0,
max: 100,
inRange: {
color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
3. 交互优化
javascript
// 大数据时关闭悬停动画
series: [{
large: true,
hoverAnimation: false,
emphasis: { disabled: true }
}]1
2
3
4
5
6
2
3
4
5
6
📊 性能指标
| 数据量 | 渲染时间 | FPS | 建议 |
|---|---|---|---|
| < 1K | < 10ms | 60 | 无限制 |
| 1K-10K | 10-50ms | 55 | 正常 |
| 10K-100K | 50-200ms | 45 | 开启large |
| 100K-1M | 200ms-2s | 30 | large+渐进式 |
| > 1M | > 2s | < 20 | 考虑采样 |
🔗 相关链接
- 折线图.md)
- 大数据 large 模式
- 官方示例
最后更新: 2026-04-23
难度等级: ⭐⭐⭐
预计阅读时间: 18 分钟
基础散点图
{
"title": {
"text": "身高体重关系",
"left": "center"
},
"tooltip": {
"trigger": "item"
},
"xAxis": {
"type": "value",
"name": "身高(cm)",
"min": 150,
"max": 200
},
"yAxis": {
"type": "value",
"name": "体重(kg)",
"min": 40,
"max": 100
},
"series": [
{
"type": "scatter",
"symbolSize": 15,
"data": [
[
161.2,
51.6
],
[
167.5,
59
],
[
159.5,
49.2
],
[
157,
63
],
[
155.8,
53.6
],
[
170,
59
],
[
159.1,
47.6
],
[
166,
69.8
],
[
176.2,
66.8
],
[
160.2,
75.2
],
[
172.5,
55.2
],
[
170.9,
54.2
],
[
172.9,
62.5
],
[
153.4,
42
],
[
160,
50
],
[
176.5,
71.8
]
],
"itemStyle": {
"color": "#5470c6",
"opacity": 0.8
}
}
]
}气泡图效果
{
"title": {
"text": "城市数据对比",
"left": "center"
},
"tooltip": {
"trigger": "item"
},
"xAxis": {
"type": "value",
"name": "GDP(万亿)"
},
"yAxis": {
"type": "value",
"name": "人口(万)"
},
"series": [
{
"type": "scatter",
"data": [
[
2.5,
2428,
2428,
"上海"
],
[
2.3,
2189,
2189,
"北京"
],
[
2,
1867,
1867,
"深圳"
],
[
1.7,
1600,
1600,
"广州"
],
[
1.5,
1300,
1300,
"成都"
],
[
1.3,
1200,
1200,
"杭州"
]
],
"itemStyle": {
"opacity": 0.7,
"shadowBlur": 10,
"shadowColor": "rgba(0, 0, 0, 0.3)"
}
}
]
}