ECharts 饼图
在日常工作学习中,我们经常需要把有限的内容按照一定的比例进行分开,比如一天的时间用来工作,学习,娱乐,休息这样的时间需要进行按比例合理划分。一般这样多比较占比的时候大家一般都会使用饼图表示。饼图又称饼状图,用于描述量、频率或百分比之间的相对关系。
1. 简介
官方定义:
饼图主要用于表现不同类目的数据在总和中的占比。每个的弧度表示数据数量的比例。
饼图更适合表现数据相对于总数的百分比等关系。如果只是表示不同类目数据间的大小,建议使用柱状图,人们对于微小的弧度差别相比于微小的长度差别更不敏感,或者也可以通过配置 roseType 显示成南丁格尔图,通过半径大小区分数据的大小
解释
2. 实例解说
2.1 简单饼图
在 ECharts 中,最简单情况下只需传入 series
项即可配置一个饼图,例如:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<Meta charset="utf-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Echarts Example</title>
</head>
<body>
<div id="main" style="width: px;height: px"></div>
<script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
var option = {
toolBox: {
feature: {
saveAsImage: {},
},
},
series: [
{
type: 'pie',
data: [
{ value: , name: 'Mon' },
{ value: , name: 'Tue' },
{ value: , name: 'Wed' },
{ value: , name: 'Thu' },
{ value: , name: 'Fri' },
{ value: , name: 'Sat' },
{ value: , name: 'Sun' },
],
label: {
show: true,
position: 'inside',
},
},
],
};
myChart.setoption(option);
</script>
</body>
</html>
示例效果:
与坐标体系的柱状图、折线图、散点图等不同,饼图可以理解为是一种一维图表,扇形区域的大小由序列上每一项数值在整个序列数值和的比例决定,所以上述示例中,不需要配置 xAxis
、yAxis
、grid
等坐标组件,相对来说更简单。
作为补充,饼图中建议使用 { value: 1320, name: 'Sun' }
格式定义数据项,因为默认情况下 name
会被视为数据项的标签进行渲染。如果没有提供 name
属性,例如传入如下值:
{
"data": [, , , , , , ]
}
2.2 饼图半径
-
number
: 指定饼图半径的像素尺寸 -
string
: 接受百分比值如20%
,指定饼图半径相对整个画布的比例 -
array
: 接受二维数组如[30, '50%']
,第一项指定内圆半径,第二项指定外圆半径
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<Meta charset="utf-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Echarts Example</title>
</head>
<body>
<div id="main" style="width: px;height: px"></div>
<script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
var option = {
toolBox: {
feature: {
saveAsImage: {},
},
},
series: [
{
type: 'pie',
data: [
{ value: , name: 'Mon' },
{ value: , name: 'Tue' },
{ value: , name: 'Wed' },
{ value: , name: 'Thu' },
{ value: , name: 'Fri' },
{ value: , name: 'Sat' },
{ value: , name: 'Sun' },
],
radius: ['50%', '80%'],
},
],
};
myChart.setoption(option);
</script>
</body>
</html>
示例效果:
2.3 嵌套饼图
基于镂空饼图特性,还可以进一步实现多组饼图的嵌套效果,示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<Meta charset="utf-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Echarts Example</title>
</head>
<body>
<div id="main" style="width: px;height: px"></div>
<script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script>
<script type="text/javascript">
var cats = [
{ name: '水果', children: ['苹果', '雪梨', '橙子', '香蕉'] },
{ name: '蔬菜', children: ['津白', '紫甘蓝', '苦瓜', '菜心'] },
{ name: '水产', children: ['虎皮虾', '青蟹', '鲈鱼', '大闸蟹'] },
{ name: '干杂', children: ['红豆', '东北大米', '陈皮', '党参'] },
];
// 生成随机数据
// 不必关注
function generateData(cats) {
var MAX = ,
MIN = ;
var len = cats.length;
var result = { inner: [], outer: [] };
for (let i = ; i < len; i++) {
var cat = cats[i];
var vector = [];
for (let j = ; j < cat.children.length; j++) {
var num = Math.round(Math.random() * (MAX - MIN) + MIN);
vector.push({ name: cat.children[j], value: num });
}
var total = vector.reduce((r, v) => r + v.value, );
result.inner.push({ name: cat.name, value: total });
result.outer = result.outer.concat(vector);
}
return result;
}
var myChart = echarts.init(document.getElementById('main'));
var data = generateData(cats);
var option = {
toolBox: {
feature: {
saveAsImage: {},
},
},
series: [
{ type: 'pie', data: data.inner, radius: '40%', label: { position: 'inside' } },
{ type: 'pie', data: data.outer, radius: ['50%', '80%'] },
],
};
myChart.setoption(option);
</script>
</body>
</html>
上例的关键在于嵌套的两个圆设定好恰当的 radius
值,以免堆叠相交,示例效果:
2.4 饼图选中效果
{
"selectedMode": "single"
}
就可以实现选中的交互效果:
上例中,内外圆分别处理一套选中逻辑,互不相关。这里只需要配合 ECharts 的事件系统,就可以实现内外饼图的联动效果:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<Meta charset="utf-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Echarts Example</title>
</head>
<body>
<div id="main" style="width: px;height: px"></div>
<script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script>
<script type="text/javascript">
var cats = [
{ name: '水果', children: ['苹果', '雪梨', '橙子', '香蕉'] },
{ name: '蔬菜', children: ['津白', '紫甘蓝', '苦瓜', '菜心'] },
{ name: '水产', children: ['虎皮虾', '青蟹', '鲈鱼', '大闸蟹'] },
{ name: '干杂', children: ['红豆', '东北大米', '陈皮', '党参'] },
];
// 生成随机数据
// 不必关注
function generateData(cats) {
var MAX = ,
MIN = ;
var len = cats.length;
var result = { inner: [], outer: [] };
for (let i = ; i < len; i++) {
var cat = cats[i];
var vector = [];
for (let j = ; j < cat.children.length; j++) {
var num = Math.round(Math.random() * (MAX - MIN) + MIN);
vector.push({ name: cat.children[j], value: num });
}
var total = vector.reduce((r, v) => r + v.value, );
result.inner.push({ name: cat.name, value: total });
result.outer = result.outer.concat(vector);
}
return result;
}
var myChart = echarts.init(document.getElementById('main'));
var data = generateData(cats);
var option = {
toolBox: {
feature: {
saveAsImage: {},
},
},
series: [
{
type: 'pie',
data: data.inner,
radius: '40%',
label: { position: 'inside' },
id: 'inner',
selectedMode: 'multiple',
},
{ type: 'pie', id: 'outer', data: data.outer, radius: ['50%', '80%'], selectedMode: 'multiple' },
],
};
myChart.setoption(option);
// 找到关联类别
function lookup(e) {
// 当前操作的项
var currentItem = e.name;
var isSelected = e.selected[e.name];
// 当前操作的序列
var seriesId = e.seriesId;
if (seriesId === 'inner') {
// 内层饼图选中时,子项同步选中
var currentCat = cats.find(function (cat) {
return cat.name === currentItem;
});
return { seriesIndex: , names: currentCat.children, isSelected: isSelected };
} else {
// 外层饼图选中时,父类同步选中
var parentCat = cats.find(function (cat) {
return (
cat.children.findindex(function (subItem) {
return subItem === currentItem;
}) >=
);
});
for (var i = ; i < parentCat.children.length; i++) {
var subItem = parentCat.children[i];
if (e.selected[subItem] === true) {
return { seriesIndex: , names: [parentCat.name], isSelected: true };
}
}
return { seriesIndex: , names: [parentCat.name], isSelected: false };
}
}
myChart.on('pieselectchanged', function (e) {
var target = lookup(e);
var names = target.names;
var isSelected = target.isSelected;
var seriesIndex = target.seriesIndex;
for (var i = ; i < names.length; i++) {
// 发出选中动作
myChart.dispatchAction({
type: isSelected ? 'pieSelect' : 'pieUnSelect',
seriesIndex: seriesIndex,
name: names[i],
});
}
});
</script>
</body>
</html>
示例效果:
2.5 南丁格尔玫瑰图
南丁格尔玫瑰图又称极区图,是一种圆形直方图。与普通饼图的区别在于,它即通过饼图扇区角度表示数据百分比,又通过扇区面积表示数据大小。在 ECharts 饼图中,只需修改 roseType
值就能将普通的饼图转换为玫瑰图,示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<Meta charset="utf-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Echarts Example</title>
</head>
<body>
<div id="main" style="width: px;height: px"></div>
<script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script>
<script type="text/javascript">
var cats = [
{ name: '水果', children: ['苹果', '雪梨', '橙子', '香蕉'] },
{ name: '蔬菜', children: ['津白', '紫甘蓝', '苦瓜', '菜心'] },
{ name: '水产', children: ['虎皮虾', '青蟹', '鲈鱼', '大闸蟹'] },
{ name: '干杂', children: ['红豆', '东北大米', '陈皮', '党参'] },
];
// 生成随机数据
// 不必关注
function generateData(cats) {
var MAX = ,
MIN = ;
var len = cats.length;
var result = { inner: [], outer: [] };
for (let i = ; i < len; i++) {
var cat = cats[i];
var vector = [];
for (let j = ; j < cat.children.length; j++) {
var num = Math.round(Math.random() * (MAX - MIN) + MIN);
vector.push({ name: cat.children[j], value: num });
}
var total = vector.reduce((r, v) => r + v.value, );
result.inner.push({ name: cat.name, value: total });
result.outer = result.outer.concat(vector);
}
return result;
}
var myChart = echarts.init(document.getElementById('main'));
var data = generateData(cats);
var option = {
toolBox: {
feature: {
saveAsImage: {},
},
},
series: [
{ type: 'pie', data: data.inner, radius: '40%', label: { position: 'inside' } },
{ type: 'pie', data: data.outer, radius: ['50%', '80%'], roseType: 'radius' },
],
};
myChart.setoption(option);
</script>
</body>
</html>
在相同数据序列中,radius
与 area
效果对比如下:
2.6 标签优化
在 label.position
基础上,我们来看看标签在不同位置上的优化。
当 label.position = inner
时,扇区的标签在扇区内部渲染。每个扇区的大小依据数据值而定,考虑某数值在序列中占比很小时,分配到的角度也会很小,可能导致标签展示不全,考虑如下数值序列:
{
"type": "pie",
"label": { "position": "inside" },
"data": [
{ "value": , "name": "Mon" },
{ "value": , "name": "Tue" },
{ "value": , "name": "Wed" },
{ "value": , "name": "Thu" },
{ "value": , "name": "Fri" },
{ "value": , "name": "Sat" },
{ "value": , "name": "Sun" }
]
}
{
"type": "pie",
"label": { "position": "inside" },
"minShowLabelAngle": ,
"data": [
{ "value": , "name": "Mon" },
{ "value": , "name": "Tue" },
{ "value": , "name": "Wed" },
{ "value": , "name": "Thu" },
{ "value": , "name": "Fri" },
{ "value": , "name": "Sat" },
{ "value": , "name": "Sun" }
]
}
示例效果:
4. 视觉引导线配置
先看看示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<Meta charset="utf-8" />
<Meta http-equiv="X-UA-Compatible" content="IE=edge" />
<Meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Echarts Example</title>
</head>
<body>
<div id="main" style="width: px;height: px"></div>
<script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
var option = {
toolBox: { feature: { saveAsImage: {} } },
series: [
{
type: 'pie',
radius: '60%',
data: [
{ value: , name: 'Mon' },
{ value: , name: 'Tue' },
{ value: , name: 'Wed' },
{ value: , name: 'Thu' },
{ value: , name: 'Fri' },
{ value: , name: 'Sat' },
{ value: , name: 'Sun' },
],
label: { show: true, position: 'outside' },
labelLine: { length: , length2: },
},
],
};
myChart.setoption(option);
</script>
</body>
</html>
示例效果:
其中 length
对应上图第一段,即接近扇区的线段长度;length2
对应上图第二段,即接近文本的线段长度。
5. 个人经验
实际上,肉眼对微小面积差异的敏感度并不高,很多情况下都应该用柱状图、折线图代替饼图,比如序列数据很多的时候,由于被拆分成很多扇区,不太容易一眼看出数据分布,示例:
又或者是当数据分布得很均匀,差值不大的时候: