摘要:上篇原生繪制餅圖介紹了如何使用來(lái)繪制環(huán)圖,這篇用標(biāo)簽來(lái)實(shí)現(xiàn)一下。在標(biāo)簽里,插入其它元素,來(lái)進(jìn)行繪圖元素用于定義一個(gè)路徑,使用標(biāo)簽來(lái)繪制每項(xiàng)數(shù)據(jù)的環(huán)圖份額元素用于定義一條曲線(xiàn),使用繪制每項(xiàng)數(shù)據(jù)的線(xiàn)條元素用于定義文本使用顯示數(shù)據(jù)的文本信息。
上篇<原生Canvas繪制餅圖>介紹了如何使用Canvas來(lái)繪制環(huán)圖,這篇用SVG標(biāo)簽來(lái)實(shí)現(xiàn)一下。
上面是完整效果圖,下面來(lái)看看具體實(shí)現(xiàn)。
使用的SVG元素:SVG代碼的開(kāi)始標(biāo)簽,相當(dāng)于創(chuàng)建一個(gè)畫(huà)布。在svg標(biāo)簽里,插入其它SVG元素,來(lái)進(jìn)行繪圖;
以上是會(huì)用到的幾個(gè)SVG標(biāo)簽,詳細(xì)說(shuō)明可以看看菜鳥(niǎo)教程SVG或者M(jìn)DN的SVG教程
SVG是一種用來(lái)描述二維矢量圖形的XML標(biāo)記語(yǔ)言,所以SVG標(biāo)簽不能使用document.createElement直接創(chuàng)建(瀏覽器無(wú)法識(shí)別)。需要使用document.createElementNS創(chuàng)建一個(gè)具有指定的命名空間URI和限定名稱(chēng)的元素,SVG的命名空間是"http://www.w3.org/2000/svg"。這里將創(chuàng)建SVG標(biāo)簽操作寫(xiě)在了createSvgTag函數(shù)里。下面先新建一個(gè)svg元素:
/** * 將創(chuàng)建環(huán)圖的所有操作寫(xiě)在drawPie函數(shù)內(nèi),配置一些默認(rèn)參數(shù) * @param {Element} element [插入SVG的元素,缺省直接插入到body] * @param {Number} R [外弧起終點(diǎn)計(jì)算半徑] * @param {Number} r [內(nèi)弧起終點(diǎn)計(jì)算半徑] * @param {Number} width [畫(huà)布寬度] * @param {Number} height [畫(huà)布高度] * @param {Array} data [圖表數(shù)據(jù)] */ function drawPie({element, R = 140, r = 100,width = 450,height = 400,data = []} = {}) { let w = width; let h = height; //將width、height賦值給w、h let origin = [w / 2, h / 2]; //以畫(huà)布的中心點(diǎn),作為環(huán)圖的原點(diǎn) //創(chuàng)建一個(gè)svgs標(biāo)簽 let svg = createSvgTag("svg", { "width": w + "px", "height": h + "px", "viewBox": `0 0 ${w} ${h}`, }); (element && element.appendChild) ? element.appendChild(svg) : document.body.appendChild(svg);//插入到DOM /** * 將創(chuàng)建SVG標(biāo)簽寫(xiě)成一個(gè)函數(shù) * @param {tring} tagName [標(biāo)簽名] * @param {Object} attributes [標(biāo)簽屬性] * @return {Element} svg標(biāo)簽 */ function createSvgTag (tagName, attributes) { let tag = document.createElementNS("http://www.w3.org/2000/svg", tagName) for (let attr in attributes) { tag.setAttribute(attr, attributes[attr]) } return tag; } }) //調(diào)用 drawPie({ data: [{ cost: 4.94, category: "通訊", color: "#e95e45", }, { cost: 4.78, category: "服裝美容", color: "#20b6ab", }, { cost: 4.00, category: "交通出行", color: "#ef7340", }, { cost: 3.00, category: "飲食", color: "#eeb328", }, { cost: 49.40, category: "其他", color: "#f79954", }, { cost: 28.77, category: "生活日用", color: "#00a294", }] })
我們會(huì)用到的指令有:
Moveto(移動(dòng)畫(huà)筆到起始點(diǎn)),語(yǔ)法:"M x,y" 在這里x和y是絕對(duì)坐標(biāo),分別代表水平坐標(biāo)和垂直坐標(biāo);
Lineto(繪制直線(xiàn)),語(yǔ)法:"L x, y" 在這里x和y是絕對(duì)坐標(biāo),表示直線(xiàn)的結(jié)束點(diǎn)坐標(biāo);
Arcto(繪制弧曲線(xiàn)路徑),語(yǔ)法:"A rx,ry xAxisRotate LargeArcFlag,SweepFlag x,y",rx和ry分別是x和y方向的半徑(繪制圓弧時(shí),rx和ry相等);LargeArcFlag的值確定是要畫(huà)小弧或大弧,0表示畫(huà)小弧(即畫(huà)兩點(diǎn)之間弧長(zhǎng)最小的弧),1表示畫(huà)大弧(當(dāng)弧度大于Math.PI時(shí)需要畫(huà)大弧);SweepFlag用來(lái)確定畫(huà)弧的方向,0逆時(shí)針?lè)较颍?順時(shí)針?lè)较?x和y是目的地的坐標(biāo);
ClosePath(閉合路徑),語(yǔ)法是"Z"或"z";
詳情MDN path元素d屬性。
我們需要用path繪制如下的路徑:
如圖:份額的繪制是先使用M命令移動(dòng)到P0,L命令繪制一條直線(xiàn)到P1,A命令從P1畫(huà)弧到P2,L命令從P2繪制一條直線(xiàn)到P3,A命令從P3繪制一條弧線(xiàn)到P0,最后Z命令關(guān)閉路徑。然后我們只要填充這個(gè)路徑就可以完成每項(xiàng)份額繪制了。這里我們需要求出4個(gè)點(diǎn)的絕對(duì)坐標(biāo),如何計(jì)算這四個(gè)坐標(biāo)?
如圖,通過(guò)三角函數(shù),我們就可以計(jì)算出每個(gè)點(diǎn)的位置。我們已知原點(diǎn)O坐標(biāo)(畫(huà)布中點(diǎn))、外環(huán)半徑R和內(nèi)環(huán)半徑r(我們自己給定);再通過(guò)計(jì)算出每項(xiàng)數(shù)據(jù)的弧度占比,我們就可以得到每項(xiàng)數(shù)據(jù)的起始弧度(即上一項(xiàng)的結(jié)束弧度,第一項(xiàng)為0)和結(jié)束弧度(起點(diǎn)+項(xiàng)數(shù)據(jù)的弧度占比)。x值為原點(diǎn)x+sin(angel)半徑,y值為原點(diǎn)y-cos(angel)半徑
這里將計(jì)算點(diǎn)坐標(biāo)的運(yùn)算寫(xiě)在evaluateXY函數(shù)中,如下:
/** * 計(jì)算Xy坐標(biāo) * @param {[type]} r [半徑] * @param {[type]} angel [角度] * @param {[type]} origin [原點(diǎn)坐標(biāo)] * @return {[Array]} 坐標(biāo) */ function evaluateXY (r, angel, origin) { return [origin[0] + Math.sin(angel) * r, origin[0] - Math.cos(angel) * r] }
接下來(lái),我們遍歷數(shù)據(jù),計(jì)算出每項(xiàng)數(shù)據(jù)的四個(gè)點(diǎn):
//遍歷計(jì)算每項(xiàng)數(shù)據(jù) for(let v of data) { let itemData = Object.assign({}, v);//copy一遍,不直接修改原數(shù)據(jù) eAngel = sAngel + v.cost / total * 2 * Math.PI; //計(jì)算結(jié)束弧度 itemData.arclineStarts = [ evaluateXY(r, sAngel, origin), //計(jì)算P0坐標(biāo) evaluateXY(R, sAngel, origin), //計(jì)算P1坐標(biāo) evaluateXY(R, eAngel, origin), //計(jì)算P2坐標(biāo) evaluateXY(r, eAngel, origin) //計(jì)算P3坐標(biāo) ]; itemData.LargeArcFlag = (eAngel - sAngel) > Math.PI ? "1" : "0";//大于Math.PI需要畫(huà)大弧,否則畫(huà)小弧 drawData.push(itemData);//保存到drawData數(shù)組中,繪制時(shí)遍歷 sAngel = eAngel;//將下一項(xiàng)數(shù)據(jù)的起始弧度設(shè)置為當(dāng)前項(xiàng)的結(jié)束弧度 }
下面,遍歷drawData,繪制環(huán)圖:
//遍歷計(jì)算每項(xiàng)數(shù)據(jù),進(jìn)行繪制 for(let v of drawData) { let P = v.arclineStarts; let path = createSvgTag("path", { "fill": v.color, //設(shè)置填充色 "stroke": "black", "stroke-width": "0", //畫(huà)筆大小為零 /** * d屬性設(shè)置路徑字符串 * M ${P[0][0]} ${P[0][1]} 移動(dòng)畫(huà)筆到P0點(diǎn) * L ${P[1][0]} ${P[1][1]} 繪制一條直線(xiàn)到P1 * A ${R} ${R} 0 ${v.LargeArcFlag} 1 ${P[2][0]} ${P[2][1]} 繪制外環(huán)弧到P2,R為外半徑,v.LargeArcFlag畫(huà)大弧還是小弧 * L ${P[3][0]} ${P[3][1]} 繪制一條直線(xiàn)到P3 * A ${r} ${r} 0 ${v.LargeArcFlag} 0 ${P[0][0]} ${P[0][1]} 繪制內(nèi)環(huán)弧到P0點(diǎn) * z 關(guān)閉路徑 */ "d": `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]} A ${r} ${r} 0 ${v.LargeArcFlag} 0 ${P[0][0]} ${P[0][1]} z` }) svg.appendChild(path); //添加到畫(huà)布中 }
到這里,就已經(jīng)繪制出如下環(huán)圖了:
記得還需要把相關(guān)變量先聲明一下。
下面我們需要繪制線(xiàn)條和文字。
起點(diǎn)坐標(biāo):這里設(shè)置起點(diǎn)為每項(xiàng)數(shù)據(jù)的弧線(xiàn)中間位置,通過(guò)計(jì)算中間位置對(duì)應(yīng)的弧度,就可以通過(guò)三角函數(shù)計(jì)算出這個(gè)點(diǎn)坐標(biāo);
線(xiàn)束點(diǎn)坐標(biāo):當(dāng)線(xiàn)條起點(diǎn)在右側(cè)時(shí),線(xiàn)束點(diǎn)就是垂直平行起點(diǎn)圖表最右側(cè)位置;當(dāng)線(xiàn)條起點(diǎn)在左側(cè)時(shí),線(xiàn)束點(diǎn)就是垂直平行起點(diǎn)圖表最右左位置;假設(shè)起點(diǎn)為[sx,sy],右左結(jié)束點(diǎn)應(yīng)該就是[w,sy]、[0,sy],w為圖表寬度;
折點(diǎn):需要處理數(shù)據(jù)會(huì)擠在一起的情況,就會(huì)改變結(jié)束點(diǎn)坐標(biāo)的y值,當(dāng)起點(diǎn)和結(jié)束點(diǎn)y值不相等時(shí),就需要設(shè)置折點(diǎn)。
以下是完整代碼:
svg-pie
也可以查看gitee。
總結(jié)一下難點(diǎn):難的地方就在弧上各點(diǎn)的計(jì)算,需要好好再回憶一下數(shù)學(xué)的三角函數(shù)。
對(duì)比canvas:canvas只需有一個(gè)標(biāo)簽,svg實(shí)現(xiàn)就在DOM中增加了一堆標(biāo)簽。這樣一來(lái),svg的優(yōu)勢(shì)就在于第項(xiàng)都是一個(gè)標(biāo)簽,你可以直接針對(duì)這個(gè)標(biāo)簽要綁定事件和做修改。比如要實(shí)現(xiàn)鼠標(biāo)稱(chēng)到某個(gè)項(xiàng),放大這個(gè)項(xiàng),svg只要給每個(gè)path綁定事件,修改當(dāng)前的這個(gè)path就行;而canvas只能在canvase綁定事件,先通過(guò)計(jì)算鼠標(biāo)位置來(lái)判斷移動(dòng)到了哪個(gè)份額上,然后再重繪整個(gè)canvas;同樣,標(biāo)簽過(guò)多也是svg的缺點(diǎn),我們這點(diǎn)標(biāo)簽其實(shí)沒(méi)什么,一旦標(biāo)簽多起來(lái),肯定是會(huì)給瀏覽器渲染帶來(lái)負(fù)擔(dān)的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/94533.html
摘要:可以用于模型化許多不同種類(lèi)的信息,因此將一個(gè)數(shù)據(jù)結(jié)構(gòu)可視化的需求也變得非常普遍。并且由于大部分圖的數(shù)據(jù)都非常復(fù)雜甚至動(dòng)態(tài)變化,所以自動(dòng)可配置的可視化布局算法顯然比人為排版更為高效且可靠。 有向無(wú)環(huán)圖(DAG)布局 有向無(wú)環(huán)圖及其布局算法 有向無(wú)環(huán)圖(directed acyclic graph,以下簡(jiǎn)稱(chēng) DAG)是一種常見(jiàn)的圖形,其具體定義為一種由有限個(gè)頂點(diǎn)和有限條帶有方向的邊組成的圖...
摘要:判斷是否成環(huán)執(zhí)行拓?fù)渑判颍绻蛄兄械捻旤c(diǎn)數(shù)不等于有向圖的頂點(diǎn)個(gè)數(shù),則說(shuō)明圖中存在環(huán)。如果訪(fǎng)問(wèn)過(guò),且不是其父節(jié)點(diǎn),那么就構(gòu)成環(huán)圖有向無(wú)環(huán)圖的最小路徑覆蓋圖存儲(chǔ)鄰接矩陣圖的鄰接矩陣存儲(chǔ)方式是用兩個(gè)數(shù)組來(lái)表示圖。 何為有向無(wú)環(huán)圖? 1、首先它是一個(gè)圖,然后它是一個(gè)有向圖,其次這個(gè)有向圖的任意一個(gè)頂點(diǎn)出發(fā)都沒(méi)有回到這個(gè)頂點(diǎn)的路徑,是為有向無(wú)環(huán)圖2、DAG(Directed Acyclic G...
摘要:本文適合適合對(duì)繪制圖形學(xué)前端可視化感興趣的讀者閱讀。結(jié)論已經(jīng)明顯瀏覽器下,用下繪制繪制圖的時(shí)候,的設(shè)置將不生效。下面是一段用于測(cè)試的代碼,表示用源圖像的形狀去挖空目標(biāo)圖像。后續(xù)繪制用臨時(shí)的替代圖片。 本文適合適合對(duì)canvas繪制、圖形學(xué)、前端可視化感興趣的讀者閱讀。 楔子 所有的事情都會(huì)有一個(gè)起因。最近產(chǎn)品上需要做一個(gè)這樣的功能:給一些圖形進(jìn)行染色處理。想想這還不是順手拈來(lái)的事情,早...
閱讀 1786·2021-10-25 09:46
閱讀 3422·2021-10-08 10:04
閱讀 2534·2021-09-06 15:00
閱讀 2960·2021-08-19 10:57
閱讀 2221·2019-08-30 11:03
閱讀 1149·2019-08-30 11:00
閱讀 2605·2019-08-26 17:10
閱讀 3713·2019-08-26 13:36