饼图 (环形/玫瑰)
ECharts 饼图完全指南:占比分析与视觉美化
📖 概述
饼图(Pie Chart)通过扇形的角度或面积来展示数据的占比关系,是占比分析最直观的图表类型。ECharts 的饼图支持丰富的变体:环形图、玫瑰图、嵌套饼图等。
核心特点:
- ✅ 直观展示占比关系
- ✅ 支持环形/玫瑰等多种变体
- ✅ 南丁格尔玫瑰图(面积映射)
- ✅ 嵌套饼图(多层级占比)
- ✅ 富文本标签定制
🔍 核心概念
1. 基础饼图
javascript
option = {
title: { text: '产品销售占比', left: 'center' },
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { orient: 'vertical', left: 'left' },
series: [{
name: '销售占比',
type: 'pie',
radius: '60%', // 饼图半径
data: [
{ value: 335, name: '产品A' },
{ value: 310, name: '产品B' },
{ value: 234, name: '产品C' },
{ value: 135, name: '产品D' },
{ value: 1548, name: '产品E' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};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
关键配置:
radius: 饼图半径('50%'|[40%, 70%']环形)data: 数据格式{ value, name }tooltip.formatter:{d}%显示百分比
2. 环形图(Donut Chart)
javascript
series: [{
type: 'pie',
radius: ['40%', '70%'], // 内径40%,外径70%
avoidLabelOverlap: false,
label: {
show: true,
position: 'center',
formatter: '{total|{total}}\n{label|总计}',
rich: {
total: { fontSize: 24, fontWeight: 'bold' },
label: { fontSize: 12, color: '#999' }
}
},
data: [...]
}]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
应用场景:
- 中心显示总计
- 更现代的视觉效果
- 节省空间
3. 南丁格尔玫瑰图(Nightingale Rose Chart)
javascript
series: [{
name: '面积模式',
type: 'pie',
radius: [20, 140],
center: ['50%', '50%'],
roseType: 'area', // ✅ 面积映射(推荐)
// roseType: 'radius', // 半径映射
itemStyle: {
borderRadius: 8 // 圆角
},
data: [
{ value: 40, name: 'rose 1' },
{ value: 38, name: 'rose 2' },
{ value: 32, name: 'rose 3' },
{ value: 30, name: 'rose 4' },
{ value: 28, name: 'rose 5' }
]
}]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
两种模式:
area: 扇区圆心角相同,半径不同(面积映射)radius: 半径相同,圆心角不同(角度映射)
4. 嵌套饼图
javascript
series: [
{
name: '访问来源',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: { position: 'inner' },
data: [
{ value: 1548, name: '搜索引擎' },
{ value: 775, name: '直接访问' }
]
},
{
name: '搜索引擎',
type: 'pie',
radius: ['45%', '60%'],
labelLine: { length: 30 },
label: {
formatter: '{b|{b}}\n{c|{c}} {per|{d}%}',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
b: { fontSize: 12, lineHeight: 20 },
c: { fontSize: 14, fontWeight: 'bold' },
per: { color: '#eee', backgroundColor: '#334455', padding: [2, 4] }
}
},
data: [
{ value: 1048, name: '百度' },
{ value: 335, name: '谷歌' },
{ value: 165, name: '必应' }
]
}
]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 marketShareOption = {
title: { text: '2024年手机品牌市场份额', left: 'center' },
tooltip: {
trigger: 'item',
formatter: '{b}: {c}% ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
top: 'center'
},
series: [{
name: '市场份额',
type: 'pie',
radius: '65%',
center: ['60%', '50%'],
data: [
{ value: 35.2, name: 'Samsung' },
{ value: 24.8, name: 'Apple' },
{ value: 15.6, name: 'Xiaomi' },
{ value: 12.4, name: 'OPPO' },
{ value: 8.3, name: 'vivo' },
{ value: 3.7, name: '其他' }
].sort((a, b) => b.value - a.value), // 降序排列
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
formatter: '{b}\n{d}%',
fontSize: 12
}
}]
};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
场景 2: 预算分配可视化
javascript
const budgetOption = {
title: { text: '年度预算分配', left: 'center' },
tooltip: {
trigger: 'item',
formatter: params => {
const total = params.data.total;
const percent = ((params.value / total) * 100).toFixed(1);
return `${params.name}<br/>预算: ¥${params.value.toLocaleString()}<br/>占比: ${percent}%`;
}
},
series: [{
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '55%'],
data: [
{ value: 500000, name: '研发', itemStyle: { color: '#5470c6' } },
{ value: 300000, name: '市场', itemStyle: { color: '#91cc75' } },
{ value: 200000, name: '运营', itemStyle: { color: '#fac858' } },
{ value: 150000, name: '人力', itemStyle: { color: '#ee6666' } },
{ value: 100000, name: '其他', itemStyle: { color: '#73c0de' } }
],
label: {
show: true,
formatter: '{b}\n¥{c}'
},
labelLine: {
show: true,
length: 20,
length2: 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
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
场景 3: 实时投票结果
javascript
class VoteChart {
constructor(dom) {
this.chart = echarts.init(dom);
this.votes = { A: 0, B: 0, C: 0 };
this.init();
}
init() {
this.chart.setOption({
title: { text: '实时投票', left: 'center' },
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
radius: '60%',
data: this.getData(),
animationDuration: 500,
animationDurationUpdate: 500,
label: {
formatter: '{b}: {c}票\n{d}%'
}
}]
});
// 模拟实时投票
setInterval(() => this.simulateVote(), 2000);
}
simulateVote() {
const options = ['A', 'B', 'C'];
const randomOption = options[Math.floor(Math.random() * options.length)];
this.votes[randomOption]++;
this.chart.setOption({
series: [{ data: this.getData() }]
});
}
getData() {
return Object.entries(this.votes).map(([name, value]) => ({
name: `选项${name}`,
value
}));
}
}
// 使用
new VoteChart(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
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
🔧 配置详解
基础配置
javascript
series: [{
type: 'pie',
// 位置和大小
center: ['50%', '50%'], // 圆心位置
radius: '60%', // 半径('50%' | [40%, 70%])
// 起始角度
startAngle: 90, // 起始角度(默认90,从顶部开始)
// 最小扇区角度
minAngle: 5, // 避免过小扇区
// 顺时针/逆时针
clockwise: true, // 顺时针排列
// 数据
data: [
{ value: 100, name: 'A' },
{ value: 200, name: 'B' }
]
}]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
标签配置
javascript
label: {
show: true,
position: 'outside', // 'outside' | 'inside' | 'center' | 'inner'
// 格式化
formatter: '{b}: {c}\n{d}%',
// {a}: 系列名
// {b}: 数据名
// {c}: 数值
// {d}: 百分比
// 样式
fontSize: 12,
color: '#333',
fontWeight: 'bold',
// 引导线
labelLine: {
show: true,
length: 15, // 第一段长度
length2: 20, // 第二段长度
smooth: false, // 是否平滑
lineStyle: {
color: '#999',
width: 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
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
富文本标签
javascript
label: {
formatter: [
'{title|{b}}',
'{value|{c}} {percent|{d}%}'
].join('\n'),
rich: {
title: {
fontSize: 14,
fontWeight: 'bold',
color: '#333',
lineHeight: 20
},
value: {
fontSize: 16,
color: '#5470c6',
fontWeight: 'bold'
},
percent: {
fontSize: 12,
color: '#999'
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
交互配置
javascript
series: [{
type: 'pie',
// 选中模式
selectedMode: 'single', // 'single' | 'multiple' | false
// 选中偏移
selectedOffset: 10, // 选中时向外偏移
// 高亮样式
emphasis: {
scale: true, // 是否放大
scaleSize: 10, // 放大尺寸
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0,0,0,0.5)'
},
label: {
show: true,
fontSize: 14
}
},
// 模糊样式
blur: {
itemStyle: { opacity: 0.3 },
label: { show: false }
}
}]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
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
📝 代码示例
示例 1: 完整的环形图(中心显示总计)
javascript
const donutChartOption = {
title: {
text: '月度支出分析',
subtext: '2024年3月',
left: 'center',
top: 20
},
tooltip: {
trigger: 'item',
formatter: '{b}: ¥{c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
top: 'middle'
},
series: [
{
name: '支出明细',
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '55%'],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20,
fontWeight: 'bold',
formatter: '{b}\n{d}%'
}
},
labelLine: { show: false },
data: [
{ value: 3500, name: '房租' },
{ value: 1200, name: '餐饮' },
{ value: 800, name: '交通' },
{ value: 600, name: '娱乐' },
{ value: 400, name: '其他' }
]
},
{
// 中心总计
name: '总计',
type: 'pie',
radius: ['0%', '40%'],
center: ['50%', '55%'],
label: {
show: true,
position: 'center',
formatter: () => {
const total = 6500;
return `{total|¥${total.toLocaleString()}}\n{label|总支出}`;
},
rich: {
total: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
lineHeight: 30
},
label: {
fontSize: 12,
color: '#999',
lineHeight: 20
}
}
},
data: [{ value: 1, name: '' }], // 占位数据
itemStyle: { color: 'transparent' },
emphasis: { disabled: 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
示例 2: 南丁格尔玫瑰图(能力评估模型)
javascript
const radarRoseOption = {
title: {
text: '个人能力评估',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c}分'
},
legend: {
orient: 'vertical',
left: 'left',
top: 'middle'
},
series: [{
name: '能力维度',
type: 'pie',
radius: [30, 150],
center: ['50%', '55%'],
roseType: 'area',
itemStyle: {
borderRadius: 8
},
data: [
{ value: 85, name: '技术能力', itemStyle: { color: '#5470c6' } },
{ value: 78, name: '沟通能力', itemStyle: { color: '#91cc75' } },
{ value: 92, name: '学习能力', itemStyle: { color: '#fac858' } },
{ value: 70, name: '管理能力', itemStyle: { color: '#ee6666' } },
{ value: 88, name: '创新能力', itemStyle: { color: '#73c0de' } },
{ value: 75, name: '执行力', itemStyle: { color: '#3ba272' } }
],
label: {
formatter: '{b}\n{c}分',
fontSize: 11
},
labelLine: {
length: 15,
length2: 20
}
}]
};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
示例 3: 动态更新的投票系统
javascript
class DynamicPieChart {
constructor(dom, categories) {
this.chart = echarts.init(dom);
this.categories = categories;
this.data = categories.map(name => ({ name, value: 0 }));
this.init();
}
init() {
this.chart.setOption({
title: { text: '实时投票统计', left: 'center' },
tooltip: {
trigger: 'item',
formatter: params => {
const total = this.getTotal();
const percent = total > 0 ? ((params.value / total) * 100).toFixed(1) : 0;
return `${params.name}<br/>票数: ${params.value}<br/>占比: ${percent}%`;
}
},
legend: {
orient: 'vertical',
left: 'left',
top: 'middle'
},
series: [{
type: 'pie',
radius: '60%',
data: this.data,
animationDuration: 800,
animationDurationUpdate: 500,
animationEasing: 'cubicInOut',
label: {
formatter: '{b}: {c}\n{d}%'
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
});
}
addVote(categoryName) {
const item = this.data.find(d => d.name === categoryName);
if (item) {
item.value++;
this.update();
}
}
update() {
this.chart.setOption({
series: [{ data: this.data }]
});
}
getTotal() {
return this.data.reduce((sum, item) => sum + item.value, 0);
}
reset() {
this.data.forEach(item => item.value = 0);
this.update();
}
}
// 使用
const voteChart = new DynamicPieChart(
document.getElementById('chart'),
['选项A', '选项B', '选项C', '选项D']
);
// 模拟投票
setInterval(() => {
const categories = ['选项A', '选项B', '选项C', '选项D'];
const randomCategory = categories[Math.floor(Math.random() * categories.length)];
voteChart.addVote(randomCategory);
}, 1000);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
⚠️ 常见问题
Q1: 标签重叠怎么办?
javascript
series: [{
type: 'pie',
avoidLabelOverlap: true, // ✅ 自动避免重叠
label: {
position: 'outside', // 外部标签
labelLine: {
length: 15, // 调整引导线长度
length2: 20
}
}
}]1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Q2: 如何显示百分比?
javascript
tooltip: {
formatter: '{b}: {c} ({d}%)' // {d} 就是百分比
}
label: {
formatter: '{d}%' // 标签中显示百分比
}1
2
3
4
5
6
7
2
3
4
5
6
7
Q3: 扇区太小看不见?
javascript
series: [{
type: 'pie',
minAngle: 5, // ✅ 设置最小角度
data: [...]
}]1
2
3
4
5
2
3
4
5
Q4: 如何自定义颜色?
javascript
// 方法1: 全局颜色盘
option = {
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666'],
series: [{ type: 'pie', data: [...] }]
};
// 方法2: 单独设置
series: [{
type: 'pie',
data: [
{ value: 100, name: 'A', itemStyle: { color: '#5470c6' } },
{ value: 200, name: 'B', itemStyle: { color: '#91cc75' } }
]
}]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
// ✅ 降序排列,大的在前
data.sort((a, b) => b.value - a.value);
// ❌ 不要随机顺序1
2
3
4
2
3
4
2. 类别数量控制
javascript
// ✅ 建议 5-8 个类别
if (data.length > 8) {
// 合并小类别
const top8 = data.slice(0, 8);
const others = data.slice(8).reduce((sum, item) => sum + item.value, 0);
top8.push({ name: '其他', value: others });
data = top8;
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
3. 颜色搭配
javascript
// ✅ 使用协调的配色方案
const colorSchemes = {
blue: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'],
warm: ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3', '#54a0ff'],
pastel: ['#ffb3ba', '#ffdfba', '#ffffba', '#baffc9', '#bae1ff']
};
option.color = colorSchemes.blue;1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
4. 响应式设计
javascript
window.addEventListener('resize', () => {
chart.resize();
});
// 移动端优化
if (window.innerWidth < 768) {
option.series[0].radius = '50%';
option.legend.orient = 'horizontal';
option.legend.top = 'bottom';
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
📊 性能指标
| 数据量 | 渲染时间 | 内存占用 | 建议 |
|---|---|---|---|
| < 20 | < 10ms | ~1MB | 无限制 |
| 20-50 | 10-30ms | ~2MB | 正常 |
| 50-100 | 30-80ms | ~5MB | 考虑合并小项 |
| > 100 | > 80ms | > 10MB | 不推荐使用饼图 |
🔗 相关链接
- 柱状图.md)
- 折线图.md)
- dataset 与 transform
- 官方示例
最后更新: 2026-04-23
难度等级: ⭐⭐
预计阅读时间: 20 分钟
基础饼图效果
{
"title": {
"text": "产品销售占比",
"left": "center"
},
"tooltip": {
"trigger": "item",
"formatter": "{b}: {c} ({d}%)"
},
"legend": {
"orient": "vertical",
"left": "left"
},
"series": [
{
"name": "销售占比",
"type": "pie",
"radius": "60%",
"data": [
{
"value": 335,
"name": "产品A"
},
{
"value": 310,
"name": "产品B"
},
{
"value": 234,
"name": "产品C"
},
{
"value": 135,
"name": "产品D"
},
{
"value": 1548,
"name": "产品E"
}
],
"emphasis": {
"itemStyle": {
"shadowBlur": 10,
"shadowOffsetX": 0,
"shadowColor": "rgba(0, 0, 0, 0.5)"
}
}
}
]
}环形图效果
{
"title": {
"text": "月度支出分析",
"left": "center"
},
"tooltip": {
"trigger": "item"
},
"legend": {
"orient": "vertical",
"left": "left"
},
"series": [
{
"name": "支出明细",
"type": "pie",
"radius": [
"45%",
"70%"
],
"center": [
"50%",
"55%"
],
"avoidLabelOverlap": true,
"itemStyle": {
"borderRadius": 10,
"borderColor": "#fff",
"borderWidth": 2
},
"label": {
"show": false
},
"emphasis": {
"label": {
"show": true,
"fontSize": 16,
"fontWeight": "bold"
}
},
"data": [
{
"value": 3500,
"name": "房租"
},
{
"value": 1200,
"name": "餐饮"
},
{
"value": 800,
"name": "交通"
},
{
"value": 600,
"name": "娱乐"
},
{
"value": 400,
"name": "其他"
}
]
}
]
}南丁格尔玫瑰图
{
"title": {
"text": "个人能力评估",
"left": "center"
},
"tooltip": {
"trigger": "item"
},
"legend": {
"orient": "vertical",
"left": "left"
},
"series": [
{
"name": "能力维度",
"type": "pie",
"radius": [
30,
150
],
"center": [
"50%",
"55%"
],
"roseType": "area",
"itemStyle": {
"borderRadius": 8
},
"data": [
{
"value": 85,
"name": "技术能力"
},
{
"value": 78,
"name": "沟通能力"
},
{
"value": 92,
"name": "学习能力"
},
{
"value": 70,
"name": "管理能力"
},
{
"value": 88,
"name": "创新能力"
},
{
"value": 75,
"name": "执行力"
}
],
"label": {
"formatter": "{b}\n{c}分"
}
}
]
}