svg画环形图

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

正文

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

SVG椭圆弧路径指令说明:

如此, 我们的实现逻辑就出来了: 把每个占比用 path 元素实现,每一段拼接(因为每个数据占比%最后之后肯定为100%)即可形成一个环形圆




具体逻辑为:

  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分爱要不要~
已知圆心,半径,角度,求圆上的点坐标

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

圆周率: PI

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

x1   =   x0   +   r   *   cos(deg)

y1   =   y0   +   r   *   sin(deg)

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

这里调整很简单,只需要给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/[email protected]/dist/vue.js"></script>
<script src="./doughnut.js"></script>
</body>
</html>
复制代码

最后效果

相关链接

猜你喜欢

转载自blog.csdn.net/weixin_33719619/article/details/91381492