無意間翻到了之前某次考核做的一個環(huán)形統(tǒng)計圖历谍,今天又搗鼓了一下把它封裝成了一個類日川,可以根據(jù)不同的數(shù)據(jù)切換內(nèi)容淮阐,下面分享一下我的實現(xiàn)思路叮阅。
0.效果預(yù)覽
基本功能
- 環(huán)形統(tǒng)計圖,按每一項數(shù)據(jù)自動分配比例泣特。
- 只需簡單地更換數(shù)據(jù)便可以生成新的環(huán)形統(tǒng)計圖(統(tǒng)計圖大小也可以更改)浩姥。
- 點擊每一項可以關(guān)閉該項,隱藏右側(cè)數(shù)據(jù)群扶,然后再重新生成及刻。
1.該示例的完整JS代碼pieChart.js
window.onload = function(){
var data1 = [
{"id":"item0","text":"這是第一行","num":10,"color":"#1e90ff","isdraw":1},
{"id":"item1","text":"這是第二行","num":20,"color":"#36cbcb","isdraw":1},
{"id":"item2","text":"這是第三行","num":30,"color":"#2fc25b","isdraw":1},
{"id":"item3","text":"這是第四行","num":40,"color":"#ffd700","isdraw":1},
{"id":"item4","text":"這是第五行","num":50,"color":"#ff3030","isdraw":1},
{"id":"item5","text":"這是第六行","num":60,"color":"#8a2be2","isdraw":1},
];
var apie = new pieChart('這里是標題',data1,150,40);
apie.add_data();
apie.draw();
}
function pieChart(title, data, radius, width){
this.title = title;
this.data = data;
this.width = width;
this.radius = radius;
this.add_data = function(){
var width = 2*radius;
var chart = document.createElement('div');
chart.style.width = width+'px';
var top = document.createElement('div');
top.setAttribute('style','text-align:center;font-weight:bold;width:'+width+'px');
top.innerText = title;
var circle = document.createElement('canvas');
circle.setAttribute('id','circle');
circle.setAttribute('width',width+"px");
circle.setAttribute('height',width+"px");
var list = document.createElement('div');
list.setAttribute('id','list');
var ul = document.createElement('ul');
ul.setAttribute('style','font-family:Simsun;margin:0;padding:0;list-style:none;');
for(var i=0; i<data.length; i++){
var li = document.createElement('li');
li.setAttribute('id',data[i].id);
li.setAttribute('style','width:'+width+'px;');
li.style.color = data[i].color;
li.innerHTML = "<span>? </span><span style='color:black;'>"+data[i].text+"</span><span style='color:gray;float:right'>"+data[i].num+"</span>";
li.onclick = this.draw;
ul.appendChild(li);
}
list.appendChild(ul);
chart.appendChild(top);
chart.appendChild(circle);
chart.appendChild(list);
document.body.appendChild(chart);
}
this.draw = function(){
var len = data.length;
var id = this.id;
for(var i=0; i<len; i++){
if(data[i].id == id){
data[i].isdraw = data[i].isdraw? 0:1;
this.style.color = this.style.color == 'gray'? data[i].color:'gray';
var span = this.children[2];
span.style.display = span.style.display=='none'? 'inline':'none';
}
}
var canvas = document.getElementById('circle');
canvas.height = canvas.height;
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var PI = Math.PI;
var start = PI*1.5;
var gap = 0.01;
var pros;
var sum = 0;
var zero = 0;
for(var i=0; i<len; i++){
if(data[i].isdraw == 0){
zero++;
continue;
}
sum+=data[i].num;
}
if(zero<len-1) {
pros = 100-len+zero;
}
else{
pros = 100;
}
ctx.strokeStyle = 'white';
for(var i=0; i<len; i++) {
var num = data[i].num;
if(data[i].isdraw == 0){
continue;
}
var a_color= data[i].color;
var end = start+2*PI*pros/100*num/sum;
ctx.beginPath();
ctx.moveTo(radius, radius);
ctx.arc(radius, radius, radius, start, end, false);
ctx.stroke();
start = end + 2 * PI * gap;
ctx.fillStyle = a_color;
ctx.fill();
}
ctx.beginPath();
ctx.arc(radius, radius, radius-width, 0, PI * 2, false);
ctx.stroke();
ctx.fillStyle = 'white';
ctx.fill();
ctx.fillStyle = 'black';
ctx.font = (radius/5)+'px Simsun';
ctx.fillText('總計',(canvas.width - ctx.measureText('總計').width)/2,canvas.height/2-radius/10);
ctx.fillText(sum, (canvas.width - ctx.measureText(sum).width)/2,canvas.height/2+radius/10);
}
};
}
在一個空白html
文件中導(dǎo)入該js文件即可使用(基本的html
,body
標簽要有),不需導(dǎo)入其他的css
文件竞阐。新建一個pieChart
對象缴饭,調(diào)用add_data()
和draw()
方法即可。
下面是實現(xiàn)思路的分享骆莹。
2.實現(xiàn)思路
使用JS繪制圖形颗搂,那自然離不開canvas
標簽,這里我們最終繪制的是一個圓環(huán)幕垦,我的大體思路是:
- 根據(jù)數(shù)據(jù)計算所占比例丢氢,再根據(jù)所占比例使用較大半徑繪制每部分的扇形。
- 所有部分繪制完畢先改,再使用一個較小的半徑繪制一個白底的完整的圓疚察。
- 填充文本。
也就是說仇奶,實際上我先是繪制的一個扇形統(tǒng)計圖貌嫡,然后用一個白底的較小的圓將其覆蓋,這樣看上去就是一個環(huán)形統(tǒng)計圖了。
下面結(jié)合代碼詳細講解岛抄;
在window.onload
外我封裝了一個pieChart
類别惦,他有四個變量和兩個方法:
四個變量
title:統(tǒng)計圖的標題。
data:統(tǒng)計圖的數(shù)據(jù)夫椭,每一條數(shù)據(jù)含下面五個內(nèi)容:
①id:該項數(shù)據(jù)在html中的id值掸掸。
②text:該項數(shù)據(jù)在統(tǒng)計圖下方顯示的文本。
③num:該項數(shù)據(jù)的值(數(shù)量)蹭秋。
④color:該項數(shù)據(jù)在統(tǒng)計圖中對應(yīng)的顏色扰付。
⑤isdraw:是否繪制該項數(shù)據(jù),只能填為1或0感凤,默認填1悯周,表示要繪制(在點擊重繪時會用到該值)。
radius:外層圓的半徑陪竿,單位為px禽翼。
width:圓環(huán)寬度,單位為px族跛,可理解為外層圓與內(nèi)層圓半徑的差值闰挡。
兩個方法
add_data()方法:負責添加統(tǒng)計圖下方的每行內(nèi)容。
draw()方法:繪制圓環(huán)的方法礁哄,同時也會綁定到每一行數(shù)據(jù)中长酗。
3.方法詳解
add_data()
this.add_data = function(){
var width = 2*radius;//區(qū)域?qū)挾燃礊橹睆降拈L度,畫布區(qū)域(canvas)的寬高等于直徑桐绒,下方每一個li的寬度也等于直徑夺脾。
/***div 'chart',為整個頁面的父div茉继。***/
var chart = document.createElement('div');
chart.style.width = width+'px';
/***div 'top'咧叭,統(tǒng)計圖標題區(qū)域***/
var top = document.createElement('div');
top.setAttribute('style','text-align:center;font-weight:bold;width:'+width+'px');
top.innerText = title;
/***canvas 'circle',圓環(huán)區(qū)域***/
var circle = document.createElement('canvas');
circle.setAttribute('id','circle');
circle.setAttribute('width',width+"px");
circle.setAttribute('height',width+"px");
/***div 'list'烁竭,數(shù)據(jù)行區(qū)域***/
var list = document.createElement('div');
list.setAttribute('id','list');
var ul = document.createElement('ul');
ul.setAttribute('style','font-family:Simsun;margin:0;padding:0;list-style:none;');
/*每次循環(huán)添加data中的一條數(shù)據(jù)*/
for(var i=0; i<data.length; i++){
var li = document.createElement('li');
li.setAttribute('id',data[i].id);
li.setAttribute('style','width:'+width+'px;');
li.style.color = data[i].color;
li.innerHTML = "<span>? </span><span style='color:black;'>"+data[i].text+"</span><span style='color:gray;float:right'>"+data[i].num+"</span>";
li.onclick = this.draw;//為每一行添加onclick事件
ul.appendChild(li);
}
list.appendChild(ul);
chart.appendChild(top);
chart.appendChild(circle);
chart.appendChild(list);
document.body.appendChild(chart);
}
實際上最終的頁面結(jié)構(gòu)為一個父div
菲茬,包含三個部分:標題區(qū)域div 'top'
,圖形區(qū)域canvas 'circle'
派撕,數(shù)據(jù)區(qū)域div 'list'
;數(shù)據(jù)區(qū)域div 'list'
包含一個無序列表ul
婉弹,根據(jù)創(chuàng)建對象的數(shù)據(jù)條數(shù)創(chuàng)建一個個li
,向其寫入數(shù)據(jù)并綁定onclick
事件draw()
;每個li
又含三個span
標簽终吼,分別代表:數(shù)據(jù)前綴(指定該行和統(tǒng)計圖中哪個顏色對應(yīng))镀赌,數(shù)據(jù)文本,文本對應(yīng)的數(shù)量际跪。
頁面結(jié)構(gòu)如下:
<div>
<div></div>
<canvas></canvas>
<div>
<ul>
<li><span></span><span></span><span></span></li>
<li><span></span><span></span><span></span></li>
<li><span></span><span></span><span></span></li>
<!--...-->
</ul>
</div>
</div>
draw()
代碼片段一:
var id = this.id;
for(var i=0; i<len; i++){
if(data[i].id == id){
data[i].isdraw = data[i].isdraw? 0:1;
this.style.color = this.style.color == 'gray'? data[i].color:'gray';
var span = this.children[2];
span.style.display = span.style.display=='none'? 'inline':'none';
}
}
實際上draw()
是為數(shù)據(jù)區(qū)域中的每一個li
量身定做的商佛,每一次對li
標簽的點擊都會重繪圖形蛙粘,這時this
指向的是被點擊的li
標簽,如果它正在統(tǒng)計圖中顯示將會去除它威彰,前綴變?yōu)榛疑瑪?shù)據(jù)值隱藏穴肘,isdraw
標記為0歇盼,接下來的重繪將跳過對此項的繪制。
雖說draw()
方法是為li
服務(wù)的评抚,但第一次生成統(tǒng)計圖時也不用擔心報錯豹缀,這時的this
標簽并不會指向任何一個li
標簽,id
賦值為undefined
慨代,雖然也會進入for循環(huán)邢笙,但始終不會進入if語句,對繪制不會產(chǎn)生任何影響侍匙。
代碼片段二:
canvas.height = canvas.height;
每次重繪都會先清空畫布氮惯,這里有個小技巧,重新設(shè)置畫布的寬度或高度都會使畫布清空想暗。
代碼片段三:
/*繪制部分*/
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var PI = Math.PI;
var start = PI*1.5;//繪制開始位置
var gap = 0.01;//兩項數(shù)據(jù)間取的間隙妇汗,每個間隙占比1%
var pros;//除去空隙后內(nèi)容所占比例
var sum = 0;//總和
var zero = 0;//isdraw值為0數(shù)據(jù)個數(shù)(重繪過程不顯示的數(shù)據(jù)個數(shù))
/*計算總和(sum)*/
for(var i=0; i<len; i++){
if(data[i].isdraw == 0){
zero++;
continue;
}
sum+=data[i].num;
}
/*在顯示數(shù)據(jù)數(shù)大于等于二時,間隙數(shù)等于數(shù)據(jù)數(shù)说莫,如只剩一個顯示數(shù)據(jù)杨箭,間隙數(shù)為0*/
if(zero<len-1) {
pros = 100-len+zero;
}
else{
pros = 100;
}
ctx.strokeStyle = 'white';
/*一次循環(huán)即完成一個扇形的繪制*/
for(var i=0; i<len; i++) {
var num = data[i].num;
if(data[i].isdraw == 0){
continue;
}
var a_color= data[i].color;
var end = start+2*PI*pros/100*num/sum;//繪制結(jié)束的位置
ctx.beginPath();
ctx.moveTo(radius, radius);//移動至圓心坐標
ctx.arc(radius, radius, radius, start, end, false);
ctx.stroke();
start = end + 2 * PI * gap;//下一次繪制開始的位置
ctx.fillStyle = a_color;
ctx.fill();
}
/*繪制小圓覆蓋扇形的部分區(qū)域,使之最終為圓環(huán)的效果*/
ctx.beginPath();
ctx.arc(radius, radius, radius-width, 0, PI * 2, false);
ctx.stroke();
ctx.fillStyle = 'white';
ctx.fill();
/*填充文本*/
ctx.fillStyle = 'black';
ctx.font = (radius/5)+'px Simsun';
ctx.fillText('總計',(canvas.width - ctx.measureText('總計').width)/2,canvas.height/2-radius/10);
ctx.fillText(sum, (canvas.width - ctx.measureText(sum).width)/2,canvas.height/2+radius/10);
}
務(wù)必理解到pros
這個變量的意義储狭,在之前的預(yù)覽圖里大家可以看到互婿,每兩個數(shù)據(jù)間是有空隙的,每次扇形比例的計算都是在去掉這些空白區(qū)域的前提下進行的辽狈,pros
為【全部內(nèi)容】在圓環(huán)上的比例慈参,即除去空隙部分后剩余部分所占比例。
當數(shù)據(jù)顯示項大于1時,間隙數(shù)等于數(shù)據(jù)數(shù)特姐,只剩一項數(shù)據(jù)顯示時用押,間隙數(shù)為0,該項內(nèi)容占比100%僧凤。
若你對canvas
的用法不夠熟悉,建議先作一定了解元扔,過程中多百度也是很好的選擇躯保。
對代碼有疑問,歡迎評論澎语;若你有其他的實現(xiàn)一個圓環(huán)的方式途事,歡迎評論验懊;若你發(fā)現(xiàn)我文章中的錯誤,歡迎評論尸变!感激不盡~