svg画环形图

最近项目中遇到一些占比环形图的需求(如图 ),初始设计方案定为: 1. svg , 2.css3, 3. canvas 。 最终调研后决定使用svg,业务逻辑更加清晰及技术成本更低一些。
                                                        javascript

image.png

正文

使用 svg  画这种图,确定就须要用到 svg  的 <path> 元素的椭圆弧指令(A)css

SVG椭圆弧路径指令说明:
html

image.png

如此, 咱们的实现逻辑就出来了: 把每一个占比用 path 元素实现,每一段拼接(由于每一个数据占比%最后以后确定为100%)便可造成一个环形圆
vue

image.png



具体逻辑为:

  1. 最后获得的是一个环形圆,因此椭圆的长短半轴rx, ry在这里就是最后圆的半径
  2. x-axis-rotation 是占比对应的度数,也就是 2 * PI  * per(对应占比 %)
  3. large-arc-flag: 若是旋转度数(deg) 超过了180度 则为1 不然为 0 ,这里也就表示 若是占比超过50%则为1, 不然为0
  4. sweep-flag 表示是否为 顺时针 画图, 咱们在这里认定 顺时针便可
  5. 圆弧的终点(x, y) ,这里的逻辑须要经过数学公式须要计算出终点,这里要提醒一点,其实本次的终点就是下一段的起点,这一点颇有用

接下来请拿起小本本记下来,这是重点,必考,5分爱要不要~
已知圆心,半径,角度,求圆上的点坐标java

圆心坐标:(x0,y0)  半径:r  角度:deg 单位:°css3

圆周率: PInpm

则圆上任一点为:(x1,y1)canvas

x1   =   x0   +   r   *   cos(deg)app

y1   =   y0   +   r   *   sin(deg)svg

这里还有一点:
数学中,咱们的坐标系为这样(左),可是在业务中咱们须要这样(右)

WechatIMG2.jpeg
WechatIMG3.jpeg

这里调整很简单,只须要给svg画布 设置一个旋转就好

transform: rotate(-90deg);
复制代码

代码

基本业务逻辑及注意点已经梳理完,接下来就是代码部分
为了简单方便,我经过cdn引入了vue.js

js

// doughnut.js
let vm = new Vue({
    el: '#app',
    data: {
        list: [ // 占比列表
            '30%',
            '20%',
            '10%',
            '5%',
            '8%',
            '2%',
            '15%',
            '3.33%',
            '3%',
            '3.64%',
        ],
        renderList: [],// 处理后用于渲染环形图的数据
        svgData: { // svg 数据 即画布参数
            width: 200,
            height: 200
        },
        arcData: { // 环形图参数
            r: 80, // 环形图的半径
            x0: 100, // 圆心x,通常把环形图放在画布中心位置就好
            y0: 100, // 同上
            stockWidth: 20 // 环形图的粗度...
        },
        colorMap: [ // 环形图颜色映射表
            '#3C76FF',
            '#36E1E2',
            '#92E27B',
            '#FAD850',
            '#F89E35',
            '#EA5486',
            '#EF4A4A',
            '#BF6FE4',
            '#6CBE6A',
            '#E1E1E1'
        ]
    },
    created() {
        this.renderList = this.handleChartData(this.list);
    },
    filters: {
        getPath(cur, arcData) {
            // 这里在经过 圆心(x0, y0) r ,拼接好路径数据
            const {x0, y0, r} = arcData;
            let str = 'M';
            const isLargeArc = cur.relayPer > 50 ? 1 : 0;
            const startX = cur.start.x * r + x0;
            const startY = cur.start.y * r + y0;
            const endX = cur.end.x * r + x0;
            const endY = cur.end.y * r + y0;
            str += ' ' + startX
                + ' ' + startY
                + ' ' + 'A'
                + ' ' + r
                + ' ' + r
                + ' ' + '0'
                + ' ' + isLargeArc
                + ' ' + '1'
                + ' ' + endX
                + ' ' + endY;
            return str;
        }
    },
    methods: {
        handleChartData(list) {
            // 这里按照 圆心点为(0,0), r 为 1 来处理
            const newList = [];
            list.forEach((item, index) => {
                const obj = {};
                let per = +item.split('%')[0];
                // 保留真实占比,后面须要判断是不是大小弧
                obj.relayPer = per;
                const PI = Math.PI;
                if (index !== 0) {
                    per += newList[index - 1].per;
                }
                // 由于是拼接,因此本次的终点要在以前的基础上,所要要累加占比
                obj.per = per;
                const deg = (per / 100) * PI * 2;
                obj.start = {};
                obj.end = {};
                if (index === 0) {
                    obj.start.x = Math.cos(0);
                    obj.start.y = Math.sin(0);
                }
                else {
                    obj.start = newList[index - 1].end;
                }
                obj.end.x = Math.cos(deg);
                obj.end.y = Math.sin(deg);
                newList.push(obj);
            });
            return newList;
        }
    }
});
复制代码

html

// doughnut.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>doughnut</title>
    <style> .doughnut-svg { display: block; margin: 0 auto; transform: rotate(-90deg); } </style>
</head>
<body>
    <div id="app">
        <h3 style="text-align: center;">svg--环形图</h3>
        <svg :width="svgData.width" :height="svgData.height" :viewBox="`0 0 ${svgData.width} ${svgData.height}`" class="doughnut-svg" xmlns="http://www.w3.org/2000/svg" >
            <path v-if="renderList && renderList.length > 0" v-for="(cur, index) in renderList" :key="index" :d="cur | getPath(arcData)" :stroke="colorMap[index]" :stroke-width="arcData.stockWidth" fill="none" />
        </svg>
    </div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script src="./doughnut.js"></script>
</body>
</html>
复制代码

最后效果

image.png

相关连接

相关文章
相关标签/搜索