有了canvas
之后夸盟,我們可以很容易地創(chuàng)建一個簡單圖標(biāo)秽荞,不需要任何插件骤公,不過,有的小伙伴覺得它很難蚂会,筆者仔細(xì)思考一番之后淋样,只能吐嘈一下他們的繪圖技能...
于是在開始繪制之前,我們首先畫一下草圖~
Make It Reusable
為了創(chuàng)建一個可以重用胁住,并且可以靈活地重用的餅圖趁猴,筆者決定最終的創(chuàng)建餅圖方法接收兩個參數(shù),分別是要顯示的數(shù)據(jù)data
彪见,繪制參數(shù)options
Data
Data From Server
在實(shí)際應(yīng)用場景中儡司,我們從后端拿到的往往是諸如幾個年份的產(chǎn)量一類的數(shù)據(jù),比如(這里余指,我們?yōu)榱撕喕a捕犬,將顏色也放到了后臺返回的數(shù)據(jù)中):
var data = [
{
data: 10,
color: "red",
label: "2016"
},
{
data: 15,
color: "grey",
label: "2017"
},
{
data: 15,
color: "black",
label: "2018"
}
];
To Process Data
而繪制餅圖時, 我們需要根據(jù)比例"分餅"酵镜, 并且在某些地方顯示出實(shí)際的數(shù)據(jù)(比如tooltip)碉碉,因此我們需要一個如下的數(shù)據(jù)處理函數(shù):
function calculateData(data) {
if(data instanceof Array) {
var sum = data.reduce(function(a, b) {
return a + b.data;
}, 0);
var map = data.map(function(a) {
return {
label: a.label,
data: a.data,
color: a.color,
portion: a.data/sum
}
});
return map;
}
}
Options
另外,即使我們可以根據(jù)不同的數(shù)據(jù)繪制不同的圖表淮韭,恐怕也只能滿足個別需求垢粮,畢竟每個人的喜好都不一樣,我們需要創(chuàng)建一個可以顯示不同數(shù)據(jù)靠粪,又可以擁有不同排版蜡吧、不同布局的圖表,實(shí)現(xiàn)上述目標(biāo)占键,我們需要如下參數(shù)列表:
var options = {
legend: {
font: {
size: 18,
family: 'Arial',
weight: 'bold'
}
},
title: {
text: 'Pie Chart',
font: {
size: 18,
family: 'Arial',
weight: 'bold'
}
},
tooltip: {
template: '<div>Year: {{label}}</div><div>Production: {{data}}</div>',
font: {
size: 18,
family: 'Arial',
weight: 'bold'
}
}
}
Canvas
我們的工具函數(shù)不應(yīng)該可以提前知道用戶想要用來繪制圖表的canvas昔善,用戶可能想在頁面中的多個canvas上繪制圖表,因此工具函數(shù)應(yīng)該可以接受一個參數(shù)畔乙,用來確定繪制圖表的canvas君仆,很多開源庫都使用id作為識別canvas的標(biāo)識,筆者認(rèn)為接收element更好一些,因?yàn)椴皇撬械挠脩舳荚敢饨ocanvas添加ID屬性返咱, 有的時候氮帐,用戶想給擁有某一個class屬性的所有canvas批量繪圖,并根據(jù)它們的dataset屬性動態(tài)的生成數(shù)據(jù)洛姑。
綜上,最后我們的工具函數(shù)應(yīng)該長成下面這個樣子:
function drawPie(canvas, data, option) {
// To Do
}
Start Coding
Get Context
首先獲取繪圖上下文皮服,仍要注意先判斷是否存在getContext()
方法楞艾。
var canvas = document.getElementById("canvas");
if(canvas.getContext) {
var ctx = canvas.getContext("2d");
}
Generate Options
然后,我們需要將自定義的參數(shù)和默認(rèn)參數(shù)合并在一起龄广,組成一個新的完整的參數(shù)列表硫眯,原則就是沒有自定義的都采用默認(rèn)值。
function mergeJSON(source1,source2){
var mergedJSON = JSON.parse(JSON.stringify(source2));
for (var attrname in source1) {
if(mergedJSON.hasOwnProperty(attrname)) {
if ( source1[attrname]!=null && source1[attrname].constructor==Object ) {
mergedJSON[attrname] = mergeJSON(source1[attrname], mergedJSON[attrname]);
}
} else {
mergedJSON[attrname] = source1[attrname];
}
}
return mergedJSON;
}
function generateOptions(givenOptions, defaultOptions) {
return mergeJSON(defaultOptions, givenOptions);
}
Draw Title
把標(biāo)題繪制在畫布頂部的中間择同,距離頁面頂部留有20像素的空隙两入,并且根據(jù)參數(shù),繪制具有特定內(nèi)容和樣式的標(biāo)題敲才。
var width = canvas.width,
height = canvas.height,
op = generateOptions(options, defaultOptions),
title_text = op.title.text裹纳,
title_position = {};
ctx.font = op.title.font.weight + " " + op.title.font.size+"px " + op.title.font.family;
title_position .x = (width - title_width)/2;
title_position.y = 20 + op.title.font.size;
title_width = ctx.measureText(title_text).width, title_height = op.title.font.size;
ctx.fillText(title_text, title_position.x, title_position.y);
Radius & Center
筆者決定使餅圖距離標(biāo)題有30像素的空隙,距離左邊框和底部分別留有20像素的空隙紧武,因此它的半徑和圓心分別是:
var radius = (height - title_height - title_position.y - 20) / 2 ;
var center = {
x: radius + 20,
y: radius + 30 + title_position.y
};
Legend
圖例的高設(shè)置為圖例字體大小的1.2倍剃氧,寬設(shè)置為圖例字體大小的2.5倍,距離餅圖40像素的間隙阻星,第一個圖例頂部距離頁面頂端80像素朋鞍,文字距離圖例5像素,垂直居中妥箕,于是圖例的大體信息總結(jié)如下:
var legend_width = op.legend.font.size * 2.5,
legend_height = op.legend.font.size * 1.2,
legend_posX = center.x * 2 +20,
legend_posY = 80,
legend_textX = legend_posX + legend_width + 5,
legend_textY = legend_posY + op.legend.font.size * 0.9;
Draw Pie & Legends
Border
先給圖表加一個邊框
ctx.strokeStyle = 'grey';
ctx.lineWidth = 3;
ctx.strokeRect(0, 0, canvas.width, canvas.height);
Pie & Legends
遍歷數(shù)據(jù)繪圖滥酥。
var data_c = calculateData(data);
var startAngle = 0, endAngle = 0;
for(var i=0, len=data.length; i<len; i++) {
endAngle += data_c[i].portion * 2*Math.PI;
ctx.fillStyle = data_c[i].color;
ctx.beginPath();
ctx.moveTo(center.x, center.y);
ctx.arc(center.x, center.y, radius, startAngle, endAngle, false);
ctx.closePath();
ctx.fill();
startAngle = endAngle;
ctx.fillRect(legend_posX, legend_posY + (10 + legend_height) * i, legend_width, legend_height);
ctx.font = 'bold 12px Arial';
var percent = data_c[i].label + ' : ' + (data_c[i].portion*100).toFixed(2) + '%';
ctx.fillText(percent, legend_textX, legend_textY + (10 + legend_height) * i);
}
Let's try it!
我們的工具函數(shù)已經(jīng)做到一半啦,可以畫出一個帶有圖例的餅圖畦幢,并且標(biāo)題和圖例文字大小 粗細(xì) 字體均可配置坎吻,下面試一下靈不靈~
var init = function(){
var data = [
{
data: 10,
color: "red",
label: "2016"
},
{
data: 15,
color: "grey",
label: "2017"
},
{
data: 15,
color: "black",
label: "2018"
}
];
var options = {
title: {
text: 'Production By Year',
font: {
size: 30
}
}
}
drawCircle(data, document.getElementById("drawing"), options);
};
init();
畫出來的餅圖長這個樣子~
下一篇筆者會加上Tooltip的繪制哦,那部分比較復(fù)雜呛讲,默默地給自己加油~