SVG绘制等份环形图

SVG绘制等份环图

需求如下图

在这里插入图片描述

使用技术:svg.js和无敌的jQuery

我们需要使用svg的path绘制每项数据的环图份额

path元素的属性d用于定义路径,属性d实际上是一个字符串,包含了一系列路径描述。这些路径由下面这些指令组成:Moveto,Lineto,Curveto,Arcto,ClosePath。
我们会用到的指令有:

Moveto(移动画笔到起始点),语法:‘M x,y’ 在这里x和y是绝对坐标,分别代表水平坐标和垂直坐标;

Lineto(绘制直线),语法:‘L x, y’ 在这里x和y是绝对坐标,表示直线的结束点坐标;

Arcto(绘制弧曲线路径),语法:‘A rx,ry xAxisRotate LargeArcFlag,SweepFlag x,y’,rx和ry分别是x和y方向的半径(绘制圆弧时,rx和ry相等);LargeArcFlag的值确定是要画小弧或大弧,0表示画小弧(即画两点之间弧长最小的弧),1表示画大弧(当弧度大于Math.PI时需要画大弧);SweepFlag用来确定画弧的方向,0逆时针方向,1顺时针方向;x和y是目的地的坐标;

ClosePath(闭合路径),语法是’Z’或’z’;

详情MDN path元素d属性

我们需要用path绘制如下的路径:
在这里插入图片描述
如图:份额的绘制是先使用M命令移动到P0,L命令绘制一条直线到P1,A命令从P1画弧到P2,L命令从P2绘制一条直线到P3,A命令从P3绘制一条弧线到P0,最后Z命令关闭路径。然后我们只要填充这个路径就可以完成每项份额绘制了。这里我们需要求出4个点的绝对坐标,如何计算这四个坐标?
在这里插入图片描述

如图,通过三角函数,我们就可以计算出每个点的位置。我们已知原点O坐标(画布中点)、外环半径R和内环半径r(我们自己给定);再通过计算出每项数据的弧度占比,我们就可以得到每项数据的起始弧度(即上一项的结束弧度,第一项为0)和结束弧度(起点+项数据的弧度占比)。x值为原点x+sin(angel)*半径,y值为原点y-cos(angel)*半径

所以我们可以将计算点坐标的运算封装成一个函数

/* ==== 计算Xy坐标 ==== */
/**
* @param  {[type]} r      [半径]
* @param  {[type]} angel  [角度]
* @param  {[type]} origin [原点坐标]
* @return {[Array]} 坐标
*/
function evaluateXY(r, angel, origin) {
	return [
		origin[0] + Math.sin(angel) * r,
		origin[0] - Math.cos(angel) * r,
		];
}

所有代码如下:

注意:因为我比较菜,所以在文字布局排版这一块是用的比较笨的方法,如果有更加优雅的方法可以评论或者私信一起探讨一下。

// 使用svg.js和jq
SVG.on(document, "DOMContentLoaded", function() {
	let draw = SVG().addTo("body").size(560, 560);
	// 调用函数 这里数据放到别的文件了 放在文章最后面参考
	drawTorus(arr, 560, 560, 280, 180, "outTorus"); // 外圆环
	drawTorus(arr2, 560, 560, 180, 120, "inTorus"); // 内圆环
	drawTorus(arr3, 560, 560, 120, 120, "circle"); // 圆
	/* ===== 绘制圆环函数 ===== */
	/**
	 * @param  {Array} data - [数据]
	 * @param  {Number} svgW - [svg宽度]
	 * @param  {Number} svgH - [svg高度]
	 * @param  {Number} R - [外弧起终点计算半径]
	 * @param  {Number} r - [内弧起终点计算半径]
	 * @param  {String} str - [g标签的类名]
	 */
	function drawTorus(data, svgW, svgH, R, r, str) {
		if (data.length == 1) {
			// 当传进来的数据长度为1时 调用画圆函数绘制一个圆
			drawCircle();
			return;
		}
		let arr = data; // 数据
		let origin = [svgW / 2, svgH / 2]; // svg中心点
		let out_R = R; // 外弧起终点计算半径
		let in_r = r; // 内弧起终点计算半径
		let sAngel = 0; // 起始点角度
		let eAngel = sAngel; // 结束点角度
		let drawData = []; // 保存遍历后可直接绘制的数据
		let group = draw.group().attr("class", str); // 创建一个g标签
		let textSize = str == "inTorus" ? 20 : 12; // 绘制文字 文字默认像素12px 内圆环我们设置20px
		for (let k of arr) {
			// 处理数据
			let itemData = Object.assign({}, k); // 复制一遍 不修改原数据
			eAngel = sAngel + (1 / data.length) * 2 * Math.PI; // 分成num份 一份的弧度
			itemData.arcsineStarts = [
				evaluateXY(r, sAngel, origin), // p0
				evaluateXY(R, sAngel, origin), // p1
				evaluateXY(R, eAngel, origin), // p2
				evaluateXY(r, eAngel, origin), // p3
			];
			//大于Math.PI需要画大弧,否则画小弧
			itemData.LargeArcFlag = eAngel - sAngel > Math.PI ? "1" : "0";
			drawData.push(itemData); // 保存到数组中 绘制的时候遍历
			sAngel = eAngel; // 将下一项的起始弧度设置为当前项的结束弧度
		}
		// 绘制圆环
		for (let v of drawData) {
			// 创建圆环的item 将路径添加到item 再将item添加到外圆环
			let group_item = draw.group().attr("class", "Torus-item");
			// 取出坐标数据
			let P = v.arcsineStarts;
			// 绘制路径
			let path =
				`M ${P[0][0]} ${P[0][1]} L ${P[1][0]} ${P[1][1]} A ${R} ${R} 0 ${v.LargeArcFlag} 1 ${P[2][0]} ${P[2][1]} L ${P[3][0]} ${P[3][1]}z`;
			// 绘制圆环
			let torusPath = draw.defs().path(path).attr({
				fill: v.color
			});
			let use = draw.use(torusPath);
			// 绘制文字
			let text = draw.text((add) => add.text(v.name).fill("#ffffff"));
			let textPath = text.path(v.path).attr({
				startOffset: "50%",
				"text-anchor": "middle",
				"font-size": `${textSize}px`,
			});
			// 1.将 use和text添加到group_item  2.将 group_item 添加到group
			group_item.add(use);
			group_item.add(text);
			group.add(group_item);
		}

		/* ==== 计算Xy坐标 ==== */
		/**
		 * @param  {[type]} r      [半径]
		 * @param  {[type]} angel  [角度]
		 * @param  {[type]} origin [原点坐标]
		 * @return {[Array]} 坐标
		 */
		function evaluateXY(r, angel, origin) {
			return [
				origin[0] + Math.sin(angel) * r,
				origin[0] - Math.cos(angel) * r,
			];
		}
		/* ==== 绘制圆函数 ==== */
		function drawCircle() {
			let circleData = data[0]; // 获取单独的一条数据
			let group = draw.group().attr("class", str); // 创建一个g标签
			// 绘制一个圆
			let circle = draw
				.circle(r * 2)
				.attr({
					fill: circleData.color,
					cx: svgW / 2,
					cy: svgH / 2
				});
			// 绘制文字 并且再圆里面居中
			let text = draw.text((add) =>
				add
				.tspan(circleData.name)
				.fill("#ffffff")
				.attr(
					"style",
					"font-size: 30px; font-weight: bold;dominant-baseline:middle;text-anchor:middle;"
				)
				.dx(svgW / 2)
				.dy(svgH / 2)
			);
			// 必须先绘制圆 再绘制文字 否则文字将被覆盖
			group.add(circle);
			group.add(text);
		}
	}
	/* ==== 对文字进行旋转操作 ==== */
	$("text").each(function(index, item) {
		let rotate = (360 / 7) * index;
		$(this).css({
			transform: `rotate(${rotate}deg)`,
		});
	});
		/* ==== 外环对文字的布局 ==== */
	SVG.find("text").slice(0, 7).forEach((text, index) => {
		switch (index) {
			case 0:
				text.find("tspan")[0].dy(5).x(60);
				text.find("tspan")[1].dy(30).x(70);
				break;
			case 1:
				text.find("tspan")[0].dy(5).x(88).attr({
					"letter-spacing": "0px"
				});
				text.find("tspan")[1].dy(30).x(95).attr({
					"letter-spacing": "2px"
				});
				break;
			case 2:
				text.find("tspan")[0].dy(5).x(40);
				text.find("tspan")[1].dy(30).x(40);
				break;
			case 3:
				text.find("tspan")[0].dy(-5).x(135);
				text.find("tspan")[1].dy(20).x(135);
				text.find("tspan")[2].dy(20).x(88);
				break;
			case 4:
				text.find("tspan")[0].dy(5).x(40);
				text.find("tspan")[1].dy(30).x(40);
				break;
			case 5:
				text.find("tspan")[0].dy(-5).x(120);
				text.find("tspan")[1].dy(20).x(90);
				text.find("tspan")[2].dy(20).x(130);
				break;
			case 6:
				text.find("tspan")[0].dy(-5).x(170).attr({
					"letter-spacing": "0px"
				});
				text.find("tspan")[1].dy(20).x(180).attr({
					"letter-spacing": "0px"
				});
				text.find("tspan")[2].dy(20).x(180).attr({
					"letter-spacing": "2px"
				});
				break;
		}
	})
	/* ==== 内环对文字的布局 ==== */
	SVG.find("text").slice(7, 14).forEach((text, index) => {
		if (index == 6) {
			text.find("tspan")[0].dy(45).x(70);
			text.find("tspan")[1].dy(20).x(20).font({
				size: 12
			})
		} else if (index != 6 && index != 7) {
			text.find("tspan").dy(0)
		}
	});
});

动画效果,这个简单 css完成

.outTorus,.inTorus {
	transform-origin: center;
	transition: transform 1s ease-in;
}
/* 外圆环 */
.outTorus {
	animation: 30s rotate linear infinite;
}

/* 内圆环 */
.inTorus {
	animation: 30s rever_rotate linear infinite;
}

/* 旋转动画 */
@keyframes rotate {
	0% {}

	100% {
		transform: rotate(360deg);
	}
}

/* 旋转动画 */
@keyframes rever_rotate {
	0% {}

	100% {
		transform: rotate(-360deg);
	}
}

所使用的数据

/* ==== 外圆环数据 ==== */
let arr = [{
		name: "贝贝贝贝贝贝贝贝系统\n贝贝贝贝贝贝系统",
		color: "#60963e",
		path: "M280,40 A240,240 0 0,1 467.63955579232714,130.36244755390396",
	},
	{
		name: "贝贝贝贝/贝贝/贝贝贝贝贝贝贝贝贝贝\n贝贝贝贝 贝贝贝贝 贝贝贝贝",
		color: "#6c5c98",
		path: "M280,44 A236,236 0 0,1 464.512229862455,132.8564067613389",
	},
	{
		name: "贝贝贝贝贝贝\n贝贝贝贝贝贝",
		color: "#67958b",
		path: "M280,40 A240,240 0 0,1 467.63955579232714,130.36244755390396"
	},
	{
		name: "贝贝贝贝贝贝\n贝贝贝贝贝贝\n贝贝贝贝贝贝贝(风采展示)",
		color: "#9b5c61",
		path: "M280,40 A240,240 0 0,1 467.63955579232714,130.36244755390396",
	},
	{
		name: "贝贝贝贝贝贝\n贝贝贝贝贝贝",
		color: "#967d47",
		path: "M280,40 A240,240 0 0,1 467.63955579232714,130.36244755390396"
	},
	{
		name: "贝贝贝贝贝贝贝贝\n贝贝贝贝(贝贝)贝贝贝贝\n贝贝贝贝贝贝",
		color: "#398095",
		path: "M280,40 A240,240 0 0,1 467.63955579232714,130.36244755390396",
	},
	{
		name: "贝贝贝贝贝贝 贝贝贝贝贝 贝贝贝贝\n贝贝贝贝(贝贝贝)贝贝贝贝贝\n贝贝贝贝贝贝贝贝(贝贝贝)",
		color: "#9b5568",
		path: "M280,40 A240,240 0 0,1 467.63955579232714,130.36244755390396",
	},
];
/* ==== 内圆环数据 ==== */
let arr2 = [{
		name: "贝贝",
		color: "#db5672",
		path: "M280,140 A140,140 0 0,1 389.45640754552414,192.71142773977732",
	},
	{
		name: "贝贝",
		color: "#19a6d1",
		path: "M280,140 A140,140 0 0,1 389.45640754552414,192.71142773977732",
	},
	{
		name: "贝贝",
		color: "#d89e1d",
		path: "M280,140 A140,140 0 0,1 389.45640754552414,192.71142773977732",
	},
	{
		name: "贝贝",
		color: "#d63440",
		path: "M280,140 A140,140 0 0,1 389.45640754552414,192.71142773977732",
	},
	{
		name: "贝贝",
		color: "#37bcb8",
		path: "M280,140 A140,140 0 0,1 389.45640754552414,192.71142773977732",
	},
	{
		name: "贝贝",
		color: "#735cc6",
		path: "M280,140 A140,140 0 0,1 389.45640754552414,192.71142773977732",
	},
	{
		name: "贝贝\n(贝贝、贝贝、贝贝)",
		color: "#39b275",
		path: "M280,80 A200,200 0 0,1 436.366296493606,155.30203962825328",
	},
];
/* ==== 圆数据 ==== */
let arr3 = [{
	name: "贝贝贝贝贝贝",
	color: "#6d8dc6"
}, ];

参考文章:SVG绘制环图

svg.js官网:https://svgjs.com/docs/3.1/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值