这是在实际项目中碰到的一个功能,首先按每一项的权重分配环占比,在换占比中有自身的一个热度百分比,开始试用过echart插件感觉太复杂,只能自己用svg实现一个,过程中感谢身边同事的指导和万能(恶)的度娘!

先看效果

效果链接 git

以下是所有的代码,感兴趣的可以git直接下载,注释已经尽量清晰,不理解的可以留言,有错误的欢迎指正,有更好的优化方法也请告诉我。如果有谁弄过svg转png下载,请一定联系我

html

<div id="ring-box" class="ring-box"><div id='labels' class="labels"></div>
</div>
复制代码

css

* {margin: 0;padding: 0;border: none;
}.ring-box {width: 500px;height: 500px;margin: 50px auto;border: 1px solid aqua;position: relative;
}path {/*stroke-width: 30px;*/stroke-linecap: round;
}
.labels{position: absolute;left: 0;top: 0;right: 0;bottom: 0;margin: 0 auto;
}
.labels .label-item{position: absolute;left: 50%;top: 30px;transform-origin: 0 220px;text-align: center;cursor: pointer;transition: 0.3s;
}
.label-item span{font-size: 30px;color: #f40;font-weight: 600;
}
.label-item p{font-size: 12px;color: #666;
}
复制代码

js代码

function DrawRing(el) {this.el = el // 画布挂载容器this.weight = 0 // 总权重this.endCoordinate = [] // 实环坐标this.endCoordinateBG = [] // 背景环坐标this.lenghtys = [] // 大小环this.degs = [] // 每个环所占角度this.lenghtysBG = [] // 背景环--大小环this.degrees = [] // 实环 结束角度this.degreesBgT = [] // 背景环 结束角度 真值this.degreesBgF = [] // 背景环 结束角度 实际值 = 真值 - 间隔this.middleDegs = [] // 记录每个环的中间角度--用来确定箭头位置
}DrawRing.prototype.getDegrees = function(num) {// 计算当前的进度对应的角度值return num / 100 * 360
}
DrawRing.prototype.getRad = function(degrees) {// 计算当前进度对应的弧度值return degrees * (Math.PI / 180)
}
DrawRing.prototype.getEndCoordinate = function(rad, r) {// 极坐标转换成直角坐标return {x: (Math.sin(rad) * r),y: -(Math.cos(rad) * r)}
}
DrawRing.prototype.getLenghty = function(degrees) {// 大于180度时候画大角度弧,小于180度的画小角度弧,(deg > 180) ? 1 : 0return Number(degrees > 180)
}
DrawRing.prototype.getEndCoordinate = function(rad) {let r = this.r// 极坐标转换成直角坐标return {x: (Math.sin(rad) * r),y: -(Math.cos(rad) * r)}
}DrawRing.prototype.init = function(option, r, ringWidth, margin) {this.option = option // 数据源this.r = r // 半径this.margin = margin // 间距this.ringWidth = ringWidth // 圆环大小this.getWeight() // 计算总权重this.setOption() // 根据权重计算各种数值this.startDrawRing() // 开始画环
}// 获取总权重
DrawRing.prototype.getWeight = function() {this.option.data.forEach((ele, index) => {this.weight += ele.pro})
}// 遍历option 计算画环需要的参数
DrawRing.prototype.setOption = function() {let weight = this.weightthis.option.data.forEach((ele, index) => {this.getDegs(ele.pro, weight) // 计算每个环所占角度this.getDegreesBg(index) // 算背景环应到角度值degreesBgT 和 实到角度值degreesBgF 和 大小环this.getDegreesT(index, ele.value) // 计算实环结束角度值--大小环this.getCoordinate(index) // 计算实环结束坐标this.getCoordinateBG(index) // 计算背景环起始点坐标})
}// 计算每个环所占角度
DrawRing.prototype.getDegs = function(pro, weight) {let deg = this.getDegrees(pro / weight * 100)this.degs.push(deg)
}
// 计算背景环应到角度值degreesBgT 和 实到角度值degreesBgF 和 大小环
DrawRing.prototype.getDegreesBg = function(index) {// 背景环(不考环间距前)结束角度 = 环占角度 + 上一个环结束角度 let deg = this.degs[index] + (index ? this.degreesBgT[index - 1] : 0)this.degreesBgT.push(deg)// 背景环实际画到角度 = degreesBgT - 间距角度this.degreesBgF.push(deg - this.margin)// 大于180度时候画大角度弧,小于180度的画小角度弧,(deg > 180) ? 1 : 0this.lenghtysBG.push(Number(this.degs[index] - this.margin > 180))
}// 计算实环结束角度值--大小环
DrawRing.prototype.getDegreesT = function(index, val) {let degT = index ? (this.degs[index]) * val / 100 : (this.degs[index] - this.margin) * val / 100 // 实环所占角度let deg = degT + (index ? this.degreesBgF[index - 1] : 0) // 实环真实需要画的角度//  let degT = (index === 0 ? 0 : this.degs[index-1]) + this.degs[index]-this.margin this.degrees.push(deg)// 每个环中间点角度let midDeg = (this.degs[index] - this.margin) * 50 / 100let degMid = midDeg + (index ? this.degreesBgT[index - 1] : 0)this.middleDegs.push(degMid)// 大于180度时候画大角度弧,小于180度的画小角度弧,(deg > 180) ? 1 : 0this.lenghtys.push(Number(degT > 180))
}// 计算实环结束点坐标 (起点坐标和背景环一样)
DrawRing.prototype.getCoordinate = function(index) {let endCoordinate = this.getEndCoordinate(this.getRad(this.degrees[index]))this.endCoordinate.push(endCoordinate)
}// 计算背景环起始点坐标
DrawRing.prototype.getCoordinateBG = function(index) {let endCoordinateBGT = this.getEndCoordinate(this.getRad(this.degreesBgT[index]))let endCoordinateBGF = this.getEndCoordinate(this.getRad(this.degreesBgF[index]))this.endCoordinateBG.push({x0: endCoordinateBGF.x,y0: endCoordinateBGF.y,x1: endCoordinateBGT.x,y1: endCoordinateBGT.y})
}// 开始画
DrawRing.prototype.startDrawRing = function() {var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')svg.id = 'ring'svg.setAttribute('viewBox', '0,0,600,600') // 保证画布空间足够,自适应将按照这个比例缩放svg.style.width = '100%'svg.style.height = '100%'this.endCoordinate.forEach((ele, index) => {let path = this.createPathBG(index)svg.appendChild(path[2])svg.appendChild(path[0])svg.appendChild(path[1])})this.el.appendChild(svg)
}
DrawRing.prototype.createPathBG = function(index) {let pathBG = document.createElementNS('http://www.w3.org/2000/svg', 'path')let path = document.createElementNS('http://www.w3.org/2000/svg', 'path')// 环上三角形指示器let pathTriangle = document.createElementNS('http://www.w3.org/2000/svg', 'path')pathTriangle.id = `pathTriangle${index}`pathBG.id = `pathBG${index}`path.id = `path${index}`// 设置背景环样式setStyle(pathBG, {'transform': 'translate(300,300)','stroke': this.option.colorsBG[index],'stroke-width': this.ringWidth,'fill': 'none'})// 设置实体环样式setStyle(path, {'transform': 'translate(300,300)','stroke': this.option.colors[index],'stroke-width': this.ringWidth,'fill': 'none'})// 设置三角形指示器样式setStyle(pathTriangle, {'d': `M 280 ${(300 - this.r) + 10} l 40 0 l -20 -20`,'transform-origin': '300px 300px','transform': `rotate(${this.middleDegs[index]})`,'opacity': 0})if(this.option.data[index].value < 50) {pathTriangle.setAttribute('fill', this.option.colorsBG[index])} else {pathTriangle.setAttribute('fill', this.option.colors[index])}setTimeout(() => {pathTriangle.style.transition = '0.5s'setStyle(pathTriangle, {'d': `M 280 ${(300 - this.r) - 10} l 40 0 l -20 -20`,'opacity': 1})}, 810)let r = this.r// 公用起点let startXBG = index ? this.endCoordinateBG[index - 1].x1 : 0let startYBG = index ? this.endCoordinateBG[index - 1].y1 : -r// 背景环let endXBG = this.endCoordinateBG[index].x0let endYBG = this.endCoordinateBG[index].y0let lenghtysBG = this.lenghtysBG[index]let descriptionsBG = ['M', startXBG, startYBG, 'A', r, r, 0, lenghtysBG, 1, endXBG, endYBG]pathBG.setAttribute('d', descriptionsBG.join(' '))// 实环let endX = this.endCoordinate[index].xlet endY = this.endCoordinate[index].ylet lenghtys = this.lenghtys[index]let descriptions = ['M', startXBG, startYBG, 'A', r, r, 0, lenghtys, 1, endX, endY]path.setAttribute('d', descriptions.join(' '))// 实环动画let pathLen = path.getTotalLength()path.style.strokeDasharray = `${pathLen}, ${pathLen * 2}`path.style.strokeDashoffset = pathLen + 'px'setTimeout(() => {path.style.transition = '.8s'path.style.strokeDashoffset = 0 + 'px'}, 10)let pathLenBG = pathBG.getTotalLength()pathBG.style.strokeDasharray = `${pathLenBG}, ${pathLenBG * 2}`pathBG.style.strokeDashoffset = pathLenBG + 'px'setTimeout(() => {pathBG.style.transition = '.4s'pathBG.style.strokeDashoffset = 0 + 'px'}, 10)return [pathBG, path, pathTriangle]
}// 查询三角指示器旋转角度DrawRing.prototype.getMiddleDegs = function() {return this.middleDegs
}function setStyle(el, obj) {for(var item in obj) {el.setAttribute(item, obj[item])}
}
复制代码

初始化

var option = {data: [{name: 'Java',pro: 5,value: 40},{name: 'JavaScript',pro: 4.5,value: 78},{name: 'Python',pro: 4,value: 78},{name: 'C++',pro: 3.5,value: 88},{name: 'Android',pro: 3,value: 77},{name: 'IOS',pro: 2.5,value: 88}],colors: ['red', 'green', '#f40', '#ff00ff', '#f60', '#f80'],colorsBG : ['#ffb3b3', '#b3d9b3', '#ffc7b3', '#ffb3ff', '#ffd1b3', '#ffdbb3']
}var Ring = new DrawRing(document.getElementById("ring-box"))
Ring.init(option, 150, 30, 15)// 以下代码是图上占比和占比样式和动画代码
// 方便修改样式写在外部,不用svg写
let degs = Ring.getMiddleDegs()
option.data.forEach((ele, index) => {labels.innerHTML += `<div class="label-item" style='opacity: 0;transform: rotate(${degs[index]}deg) translateX(-50%)'><div style='transform: rotate(${-degs[index]}deg)'><span>${ele.value}</span><p>${ele.name}</p></div></div>`setTimeout(() => {let item = document.getElementsByClassName("label-item")[index]item.style.opacity = 1}, 1000)
})
复制代码

转载于:https://juejin.im/post/5c738363f265da2d980907d3

js+svg实现的一个环图相关推荐

  1. LeetCode 2192. 有向无环图中一个节点的所有祖先(拓扑排序)

    文章目录 1. 题目 2. 解题 1. 题目 给你一个正整数 n ,它表示一个 有向无环图 中节点的数目,节点编号为 0 到 n - 1 (包括两者). 给你一个二维整数数组 edges ,其中 ed ...

  2. d3.js一个面积图的案例(包含brush与zoom)

    d3.js 一个面积图的案例(包含brush与zoom) 参考链接:http://www.a4z.cn/pui/ant-admin.html#/simple-area-chart const rawD ...

  3. 随机生成一个有向无环图

    写数据结构作业时想到的一个问题- 我们不妨先做一些这个随机的有向无环图的假设: 首先,它是个有向无环图,并且没有重边 假设随机从图中取出两个不相同的点,那么它们之间有边的概率为固定值(其实还可以有其他 ...

  4. AOV网--拓扑排序(必须是一个有向无环图)

     特点: 1.  AOV网用顶点表示活动,用弧表示活动之间优先关系, 2. AOV网中的弧表示活动之间存在某种制约关系, 3. AOV网中不能出现回路(如果有回路,说明某项活动以自己作为先决条件,不允 ...

  5. three.js SVG 学习绘制三维地图

    three.js SVG 学习绘制地图 https://www.js-css.cn/jscode/other/other43/ 找到一个不错的资源,不过每个省份的svg数据太小了,不精确. 主要的是这 ...

  6. d3.js v3版本实现-树状图

    参考的例子:http://bl.ocks.org/robschmuecker/7880033 一.为什么选择d3.js 二.d3.js概述 三:树状图实现 1.创建svg 2.在svg元素里面画一个g ...

  7. 数据可视化 d3操作汇总(二):圆弧、饼图、环图、玫瑰图绘制

    一.圆环绘制 圆环绘制必须要先有一个弧生成器,必须提供的参数有起始角度和中止角度,例如: var dataset = { startAngle: 0 , endAngle: Math.PI * 0.7 ...

  8. 使用Echart绘制3D饼环图、仪表盘、电池图

    3D饼环图 参考博客 https://blog.csdn.net/m0_67266787/article/details/123155878 插件引入 echarts.js echarts-gl.mi ...

  9. canvas作图系列——环图

    环图 canvas制作圆形所用的方法是 ctx.arc(x,y,r,0,Math,PI*2,false) 其中的6个参数分别是圆心横坐标,圆心纵坐标,半径,起始角度,结束角度,false顺势针/tru ...

最新文章

  1. java 空语句_Java空语句怎么写才正确?这样的Java基础知识才是你需要的
  2. CSS响应式:根据分辨率加载不同CSS的几个方法
  3. Exchange2013/2016下通过RDB(恢复数据库)还原用户邮箱数据
  4. 打卡签到python代码_如何利用Python实现自动打卡签到
  5. apache.camel_Apache Camel 2.21发布–新增功能
  6. 微信小程序生成海报分享:canvas绘制文字溢出如何换行
  7. FatMouse’ Trade
  8. 某项目中,doubango与NAT服务器的冲突
  9. u检验中的查u界值表_统计学中,知道u值,如何确定P值,是通过查表吗
  10. FAT文件系统工作原理
  11. 网站优化关键词选择时需要的注意事项
  12. Flixel 横板游戏制作教程(一)— HelloFlixel ...
  13. win7配置FTP服务器
  14. 微信小程序制作天气查询系统
  15. 面经 - 阿里巴巴 - HR面试
  16. python解一元二次方程复数_python – SymPy和复数的平方根
  17. IC验证必备的数字电路基础知识(一):数字逻辑基础
  18. 程序员抛弃大厂涌进工厂!南洋理工海归:这里上班比整天盯着电脑有意思的多!...
  19. 所有程序中的java在哪里设置密码_关于安全性:如何在桌面客户端应用程序(Java)中存储密码和敏感数据?...
  20. 数字电路硬件设计系列(五)之AT89C51/C52最小系统设计

热门文章

  1. java中如何直接导入println()
  2. 嵌入式linux驱动开发答辩问题,嵌入式Linux驱动工程师/BSP开发工程师面试笔试题集锦...
  3. mysql 组复制 不一致_MySQL主从复制什么原因会造成不一致,如何预防及解决
  4. python执行js脚本安全吗_手把手教你如何使用Python执行js代码
  5. AcWing 285. 没有上司的舞会(树形DP)
  6. AcWing 1132. 农场派对(最短路反向建边)
  7. html清除两端浮动,HTML中如何实现浮动与清除浮动
  8. 字符串format函数使用
  9. JS常见的字符串操作
  10. Kubernetes 权限管理