canvas+js绘制简单统计图(扇形图,条形图)

第一次写项目,遇上产品让写个统计图分析内容,想来用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]);
    }

}

大功告成,两个简单的统计图就这样完成了

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值