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 内圆环我们设置20pxfor (let k of arr) {// 处理数据let itemData = Object.assign({}, k); // 复制一遍 不修改原数据eAngel = sAngel + (1 / data.length) * 2 * Math.PI; // 分成num份 一份的弧度itemData.arcsineStarts = [evaluateXY(r, sAngel, origin), // p0evaluateXY(R, sAngel, origin), // p1evaluateXY(R, eAngel, origin), // p2evaluateXY(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 添加到groupgroup_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/
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
