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: 600px;height: 400px"></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: 820, name: 'Mon' },
							{ value: 932, name: 'Tue' },
							{ value: 901, name: 'Wed' },
							{ value: 934, name: 'Thu' },
							{ value: 1290, name: 'Fri' },
							{ value: 1330, name: 'Sat' },
							{ value: 1320, name: 'Sun' },
						],
						label: {
							show: true,
							position: 'inside',
						},
					},
				],
			};
			myChart.setOption(option);
		</script>
	</body>
</html>
运行案例 点击 "运行案例" 可查看在线运行效果

示例效果:

图片描述

与坐标体系的柱状图、折线图、散点图等不同,饼图可以理解为是一种一维图表,扇形区域的大小由序列上每一项数值在整个序列数值和的比例决定,所以上述示例中,不需要配置 xAxisyAxisgrid 等坐标组件,相对来说更简单。

作为补充,饼图中建议使用 { value: 1320, name: 'Sun' } 格式定义数据项,因为默认情况下 name 会被视为数据项的标签进行渲染。如果没有提供 name 属性,例如传入如下值:

{
	"data": [820, 932, 901, 934, 1290, 1330, 1320]
}

虽然也能正常渲染,但会丢失每一个数据项的标签信息:

图片描述

2.2 饼图半径

可通过 series.pie.radius 项修改饼图的显示尺寸,配置项接受如下类型值:

  • number: 指定饼图半径的像素尺寸
  • string: 接受百分比值如 20%,指定饼图半径相对整个画布的比例
  • array: 接受二维数组如 [30, '50%'],第一项指定内圆半径,第二项指定外圆半径

在 ECharts 中,饼图实际上由内圆、外圆两部分组成,因此只要在 series.pie.radius 项上传入二维数组分别指定内外圆半径,就可以实现镂空效果:

实例演示
预览 复制
复制成功!
<!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: 600px;height: 400px"></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: 820, name: 'Mon' },
							{ value: 932, name: 'Tue' },
							{ value: 901, name: 'Wed' },
							{ value: 934, name: 'Thu' },
							{ value: 1290, name: 'Fri' },
							{ value: 1330, name: 'Sat' },
							{ value: 1320, 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: 600px;height: 400px"></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 = 100,
					MIN = 50;
				var len = cats.length;
				var result = { inner: [], outer: [] };
				for (let i = 0; i < len; i++) {
					var cat = cats[i];
					var vector = [];
					for (let j = 0; 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, 0);
					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 boolean|string false 选中模式,表示是否支持扇区选中效果
selectedOffset number 10 选中扇区的偏移距离

selectedMode 属性支持:

  • false: 默认值,关闭选中效果
  • 'single': 支持选定单个扇区
  • 'multiple': 支持选定多个扇区

2.3 嵌套饼图 基础上,只需修改配置项:

{
	"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: 600px;height: 400px"></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 = 100,
					MIN = 50;
				var len = cats.length;
				var result = { inner: [], outer: [] };
				for (let i = 0; i < len; i++) {
					var cat = cats[i];
					var vector = [];
					for (let j = 0; 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, 0);
					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: 1, names: currentCat.children, isSelected: isSelected };
				} else {
					// 外层饼图选中时,父类同步选中
					var parentCat = cats.find(function (cat) {
						return (
							cat.children.findIndex(function (subItem) {
								return subItem === currentItem;
							}) >= 0
						);
					});
					for (var i = 0; i < parentCat.children.length; i++) {
						var subItem = parentCat.children[i];
						if (e.selected[subItem] === true) {
							return { seriesIndex: 0, names: [parentCat.name], isSelected: true };
						}
					}
					return { seriesIndex: 0, 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 = 0; 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: 600px;height: 400px"></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 = 100,
					MIN = 0;
				var len = cats.length;
				var result = { inner: [], outer: [] };
				for (let i = 0; i < len; i++) {
					var cat = cats[i];
					var vector = [];
					for (let j = 0; 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, 0);
					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>
运行案例 点击 "运行案例" 可查看在线运行效果

例子中,series 的第一个饼图依然是普通的饼图效果,第二个饼图则设置为 roseType = radius,效果如下:

图片描述

roseType 支持如下属性:

  • false: 默认值,不开启玫瑰图效果,此时各扇区半径相同
  • 'radius': 扇区圆心角展现数据的百分比,半径展现数据的大小
  • 'area': 所有扇区圆心角相同,仅通过半径展现数据大小

在相同数据序列中,radiusarea 效果对比如下:

图片描述

2.6 标签优化

与其他图表组件类似,饼图也支持通过 label 属性设定标签效果。首先看看 label.position 属性,用于设定标签位置,饼图的 label.position 支持如下值:

  • inside/inner: 标签显示在饼图扇区内部
  • outside: 标签显示在饼图扇区外部,通过视觉引导线连接扇区
  • center: 标签显示在饼图的圆心点

各位置显示效果如下:

图片描述

label.position 基础上,我们来看看标签在不同位置上的优化。

3. 标签自动隐藏

label.position = inner 时,扇区的标签在扇区内部渲染。每个扇区的大小依据数据值而定,考虑某数值在序列中占比很小时,分配到的角度也会很小,可能导致标签展示不全,考虑如下数值序列:

{
	"type": "pie",
	"label": { "position": "inside" },
	"data": [
		{ "value": 20, "name": "Mon" },
		{ "value": 932, "name": "Tue" },
		{ "value": 901, "name": "Wed" },
		{ "value": 934, "name": "Thu" },
		{ "value": 1290, "name": "Fri" },
		{ "value": 1330, "name": "Sat" },
		{ "value": 1320, "name": "Sun" }
	]
}

序列第一个值 Mon 只有 20,在整个序列占比分成小,渲染效果:

图片描述

此时简单的方法可通过设定 minShowLabelAngle,该属性声明若扇区角度小于该值(0 ~ 360)时,不显示标签,修改配置:

{
	"type": "pie",
	"label": { "position": "inside" },
	"minShowLabelAngle": 10,
	"data": [
		{ "value": 20, "name": "Mon" },
		{ "value": 932, "name": "Tue" },
		{ "value": 901, "name": "Wed" },
		{ "value": 934, "name": "Thu" },
		{ "value": 1290, "name": "Fri" },
		{ "value": 1330, "name": "Sat" },
		{ "value": 1320, "name": "Sun" }
	]
}

示例效果:

图片描述

4. 视觉引导线配置

label.position = outside 时,扇区的标签将在扇区外部渲染并通过视觉引导线连接到相应的扇区区域。可通过 labelLine 项修改引导线效果,接受如下子项:

配置名 类型 默认值 说明
show boolean true 是否显示引导线
length number 视觉引导线第一段的长度。
length2 number 视觉引导线第二段的长度。
smooth boolean|number true 是否平滑视觉引导线,默认不平滑,可以设置成 true 平滑显示,也可以设置为 0 到 1 的值,表示平滑程度。
lineStyle object TODO: 增加链接,线段样式

先看看示例:

实例演示
预览 复制
复制成功!
<!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: 600px;height: 400px"></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: 20, name: 'Mon' },
							{ value: 932, name: 'Tue' },
							{ value: 901, name: 'Wed' },
							{ value: 934, name: 'Thu' },
							{ value: 1290, name: 'Fri' },
							{ value: 1330, name: 'Sat' },
							{ value: 1320, name: 'Sun' },
						],
						label: { show: true, position: 'outside' },
						labelLine: { length: 30, length2: 40 },
					},
				],
			};
			myChart.setOption(option);
		</script>
	</body>
</html>
运行案例 点击 "运行案例" 可查看在线运行效果

示例效果:

图片描述

其中 length 对应上图第一段,即接近扇区的线段长度;length2 对应上图第二段,即接近文本的线段长度。

5. 个人经验

实际上,肉眼对微小面积差异的敏感度并不高,很多情况下都应该用柱状图、折线图代替饼图,比如序列数据很多的时候,由于被拆分成很多扇区,不太容易一眼看出数据分布,示例:

图片描述

又或者是当数据分布得很均匀,差值不大的时候:

图片描述

从视觉效果考虑,饼图特别适合突出 某部分占总体的比重,也就是单体与总体的对比效果,例如:

图片描述

6. 小结

图片描述
本节结合实例讲述 Echarts 饼图的各类功能特性,包括简单饼图、嵌套饼图、南丁格尔玫瑰图等场景的应用与功能特性;并就标签效果的优化、视觉引导线等功能特性做较深入探讨。