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

相關連接

相關文章
相關標籤/搜索