第一次写项目,遇上产品让写个统计图分析内容,想来用canvas实现比较方便,顺便把这两个方法放进自己的canvas库中。
绘图前先看一下要绘制的内容,在扇形图中,我们需要每个块占用的百分比,然后计算角度,对应画出弧和分隔线,而对于条形图,我们需要知道每个内容对应的数据量,还需要画出坐标轴。除此之外,两个图都需要的是用不同颜色来标出不同内容,再加个标注标出不同内容对应的颜色,话不多说上代码:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
canvas.width = 900;
canvas.height = 600;
var colors = ["rgb(243,80,60)", "rgb(89,85,159)", "rgb(114,192,150)", "rgb(34,195,249)", "rgb(177,113,191)", "rgb(254,201,63)", "rgb(157,150,150)"];
var nums = ["0", "10", "20", "30", "40", "50", "60", "70", "80", "90"];
首先设置canvas画布的宽度和高度,这里我首先设定了7个颜色用于后面不同的内容上色,nums是我要用于纵坐标的数组。
//前面index个数相加
function preSum(index, arr) {
let sum = 0;
for (let i = 0; i < index + 1; i++)
sum += arr[i];
return sum;
}
//角度转换
function degTurn(num, sum) {
return Math.ceil(num * 2 / sum * 10) / 10;
}
这里我写了两个方法,因为在绘制扇形图时,计算角度并算出对应点的x,y坐标需要前面的数值叠加,其后在计算sin和cos时又需要转换角度,所以我写了这两个方法。
接下来上扇形图的绘制方法,这里我将方法放到原型方法中(出于个人习惯,之前写canvas时全都这样写,在画较多图时直接调用这些内容)
CanvasRenderingContext2D.prototype.sectorDraw = function(x, y, l, arr, text, colors) {//参数对应为扇形图的圆心横纵坐标,半径,对应数值的数组,对应内容的数组,颜色数组
this.clearRect(0, 0, 900, 600);
let sum = 0;//要统计的数的总数
for (let index = 0; index < arr.length; index++) {
sum += arr[index];
}
for (let index = 0; index < arr.length; index++) {
let next = {
x: parseInt(x - l * Math.cos(degTurn(preSum(index, arr), sum) * Math.PI)),
y: parseInt(y - l * Math.sin(degTurn(preSum(index, arr), sum) * Math.PI))
}//下一个弧的起点,即这一个弧的终点
this.strokeStyle = "rgb(0,0,0)";
this.fillStyle = colors[index];//获取对应不同的颜色
this.beginPath();
this.moveTo(x, y);//从圆心开始绘制
if (index == 0) {
this.lineTo(x - l, y);
this.arc(x, y, l, Math.PI, (1 + degTurn(preSum(index, arr), sum)) * Math.PI, false);
} else {
let pre = {
x: x - l * Math.cos(degTurn(preSum(index - 1, arr), sum) * Math.PI),
y: y - l * Math.sin(degTurn(preSum(index - 1, arr), sum) * Math.PI)
}
this.lineTo(pre.x, pre.y);
this.arc(x, y, l, (1 + degTurn(preSum(index - 1, arr), sum)) * Math.PI, (1 + degTurn(preSum(index, arr), sum)) * Math.PI, false);
}
this.lineTo(x, y);//回到圆心
this.closePath();
this.stroke();
this.fill();
}
//绘制文字
for (let i = 0; i < arr.length; i++) {
this.fillStyle = "rgb(0,0,0)";
let deg;
if (i == 0)
deg = degTurn(preSum(i, arr), sum) * Math.PI / 2;
else
deg = degTurn((preSum(i, arr) + preSum(i - 1, arr)) / 2, sum) * Math.PI;
this.beginPath();
this.textAlign = 'center';
this.textBaseline = 'middle';
this.font = '20px Adobe Ming Std';
this.fillText(arr[i] / sum * 100 + "%", x - l / 2 * Math.cos(deg), y - l / 2 * Math.sin(deg));//在圆心与弧相距二分之一半径的地方绘制文字
this.closePath();
this.fill();
}
this.tipDraw(text, colors);//颜色标注,将在下文写出
}
接下来是条形图的绘制
//绘制坐标轴
CanvasRenderingContext2D.prototype.axisDraw = function(l, numX, numY, color = 'rgb(0,0,0)') {//参数对应为坐标轴长度,横坐标对应内容数组,纵坐标对应内容数组,坐标轴颜色(默认为黑色)
let perx = l / numX.length;//每个内容占用的长度
let pery = l / numY.length;
let margin = 20;//坐标轴距画布四周的间距
// x坐标轴
this.beginPath();
this.strokeStyle = color;
this.moveTo(margin, l + margin);
this.lineTo(margin, margin);
this.stroke();
this.closePath();
// y坐标轴
this.beginPath();
this.moveTo(margin, l + margin);
this.lineTo(margin + l, margin + l);
this.stroke();
this.closePath();
//坐标数字
this.beginPath();
this.textAlign = 'center';
this.textBaseline = 'middle';
this.font = '10px Adobe Ming Std';
this.fillStyle = color;
for (let index = 0; index < numX.length; index++) {
this.fillText(numX[index], margin + perx * (index + 1 / 2), l + margin / 2 * 3);
}
for (let index = 0; index < numY.length; index++) {
this.fillText(numY[index], margin / 2, l + margin - pery * (index + 1));
}
}
//绘制矩形
CanvasRenderingContext2D.prototype.rectDraw = function(x1, y1, x2, y2, color) {
this.beginPath();
this.moveTo(x1, y1);
this.lineTo(x1, y2);
this.lineTo(x2, y2);
this.lineTo(x2, y1);
this.strokeStyle = 'rgb(0,0,0)';
this.fillStyle = color;
this.stroke();
this.fill();
this.closePath();
}
// 条形图
CanvasRenderingContext2D.prototype.stripDraw = function(l, numX, numY, count, colors) {//参数对应为坐标轴长度,横坐标对应内容数组,纵坐标对应内容数组,每个内容对应的数据量,颜色数组
let perx = l / numX.length;
let pery = l / numY.length;
let margin = 20;
this.clearRect(0, 0, 900, 900);
this.axisDraw(l, numX, numY);
let halfWidth = 10;
for (let index = 0; index < numX.length; index++) {
this.rectDraw(margin + perx * (index + 1 / 2) - halfWidth, l + margin, margin + perx * (index + 1 / 2) + halfWidth, l + margin - count[index] * pery / 10, colors[index]);
}
this.tipDraw(numX, colors);
}
最后再上个颜色标注
//颜色标识
CanvasRenderingContext2D.prototype.tipDraw = function(text, colors) {//对应的颜色和内容数组
let per = 30;
for (let index = 0; index < text.length; index++) {
this.beginPath();
this.textAlign = 'left';
this.textBaseline = 'middle';
this.font = '14px Adobe Ming Std';
this.fillStyle = "rgb(0,0,0)";
this.fillText(text[index], 600, 100 + per * index);
this.closePath();
this.rectDraw(560, 90 + per * index, 590, 110 + per * index, colors[index]);
}
}
大功告成,两个简单的统计图就这样完成了