Canvans:绘制饼图和玫瑰饼图

本文介绍了如何使用HTML5的Canvas标签结合JavaScript来绘制饼图和玫瑰饼图。通过示例代码详细展示了绘制过程,包括坐标系、文字标签位置计算以及动态调整半径的玫瑰饼图的实现。同时还提供了ECharts图表库基于Canvas技术的背景知识。
摘要由CSDN通过智能技术生成

    Canvas是HTML5提供的一个新标签,默认是一块矩形的画布,Canvas本身是没有绘图能力的,但是通过JavaScript语言,可以在Canvas画布上绘制各种各样的效果。

<canvas 
    id="myCanvas" width="200" height="100"style="border:1px solid #000000;">
</canvas>

    常用的ECharts图表库就是基于Canvas绘图技术实现的,例如:常用的饼图样式如下,通过查看浏览器控制台可发现,这些图表都是被绘制在一个叫做canvas的HTML标签内,于是,接下来就尝试利用canvas+JavaScript进行绘图,来模仿实现饼图和玫瑰饼图。
在这里插入图片描述
在这里插入图片描述

Canvas:绘制饼图

饼图绘制结果

    绘制结果如下,
在这里插入图片描述

canvas局部坐标系

    先看一下Canvas坐标系是如何规定的?其实和浏览器的窗口坐标系的定义规则是一致的,即:canvas标签的左上角作为坐标原点(0,0)出现,定义了一个局部坐标系,如下图所示,
在这里插入图片描述

canvas:饼图文字标签绘制算法

    饼图绘制时,想要绘制的文字标签的位置并不是固定的,而是动态变化的,因此,就需要针对”文字标签位置计算”设计一种可行的计算方法。
在这里插入图片描述    如下图,canvas在调用context绘图上下文的arc()方法绘制圆形时(其参数介绍如上图),其起始角度位置在:标记为0°的橙黄色的水平线位置。
    假设标记为黄色的扇形区域为对应占比为24%,那么它必然要对应一个起始角度beginAngle,一个终止角度endAngle,以及一个被标记的文字标签。
在这里插入图片描述

    那么,如何计算文字标签的位置呢?首先对文字标签的位置进行定义:将每次新添加的文字标签内容起始位置定义在黄色扇形的角平分线延长线位置(对应长度定义为R+offset,其中:R表示饼图的半径长度,offset表示向外偏移部分的长度),那么,文字标签位置(x,y)的计算公式即为:
在这里插入图片描述
    对应的代码部分如截图所示(splitAnlge角平分线的弧度值计算过程可自行推理),
在这里插入图片描述

饼图:示例代码

    示例代码为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>06-pieChart</title>
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
</body>
<script>
    //数据源
    let dataSet = [
        {
            value: 50,
            name: "轻工业产值",
        },
        {
            value: 65,
            name: "农业产值",
        },
        {
            value: 25,
            name: "畜牧业产值",
        },
        {
            value: 36,
            name: "重工业产值",
        },
        {
            value: 89,
            name: "零售业产值",
        },
    ];
    initPieChart("canvas", dataSet);//绘制饼图

    function initPieChart(_targetId, _dataSet) {
        let canvas = document.querySelector(_targetId);
        let context = canvas.getContext("2d");
        canvas.style.border = "1px solid #ccc"; //设置canvas边框
        //获取canvas属性
        let canvasHeight = canvas.height,
            canvasWidth = canvas.width;
        //解析数据集
        let sum = _dataSet.reduce(function (pre, cur) {
            return pre + cur.value;
        }, 0);
        let trans_dataSet = _dataSet.map(item => {
            return {
                value: item.value / sum,
                name: item.name,
            }
        });
        console.log(trans_dataSet)
        drawPieChart(trans_dataSet,sum,canvasWidth,canvasHeight); //绘制饼图
        /**
         * 饼图绘制方法
         * @param _dataSet
         * @param _canvasWidth
         * @param _canvasHeight
         */
        function drawPieChart(_dataSet,_sum,_canvasWidth,_canvasHeight){
            //计算饼图中心坐标{不考虑padding值}
            let pieCenter = {
                x:_canvasWidth/2,
                y:_canvasHeight/2
            };
            let pieRadius = 0.5*Math.min(_canvasHeight,_canvasWidth)*0.5;
            let beginAngle = -Math.PI/2 ;//开始起始角度
            let curAngle = -Math.PI/2 ;//设置当前起始角度
            //计算随机颜色表
            let randomColorTable = (function (_length,_alpha=1){
                let colorTable = new Array(_length).fill("#000");
                return colorTable.map(item=>`rgba(${Math.random()*255},${Math.random()*255},${Math.random()*255},${_alpha})`);
            })(_dataSet.length,0.8);
            //解析数据集
            for (let i = 0; i < _dataSet.length; i++) {
                let endAngle = curAngle + _dataSet[i]['value']*360*Math.PI/180; //计算当前终止角度-{角度转弧度}
                //1-绘制圆弧
                context.beginPath();//开始绘制圆弧
                context.moveTo(pieCenter.x,pieCenter.y);
                context.arc(pieCenter.x,pieCenter.y,pieRadius,curAngle,endAngle,false);//顺时针绘制饼图
                context.closePath();
                context.fillStyle = randomColorTable[i%randomColorTable.length];
                context.fill();
                //2-绘制文字标签
                let splitAngle = curAngle + _dataSet[i]['value']*360*Math.PI/180*0.5; //角平分线所在射线的角度
                let textParams = {
                  splitAngle : splitAngle,//角度参数
                  offset : 30,//文字位置偏移量
                  label: `${_dataSet[i]['name']}:${(_dataSet[i]['value']*100).toFixed(2)}%`,//文本内容
                };
                let textArray = textParams.label.split(":");
                //计算文字位置
                let textPosition = {
                    x: pieCenter.x + Math.cos(textParams.splitAngle) * (pieRadius+textParams.offset),
                    y: pieCenter.y + Math.sin(textParams.splitAngle) * (pieRadius+textParams.offset),
                };
                context.beginPath();
                console.log()
                context.stroke();
                //文字换行处理
                textArray.some((item,index)=>{
                    context.textAlign="center";
                    // context.fillStyle = randomColorTable[i%randomColorTable.length];
                    context.font = "12px Arial";//设置字体大小+字体样式
                    context.fillStyle = "rgba(0,0,0,0.8)";//设置字体颜色
                    context.fillText(item,textPosition.x,textPosition.y+index*20);
                });
                // context.fillText(textParams.label,textPosition.x,textPosition.y);//文字不换行可以直接调用此句代码
                context.closePath();
                curAngle = endAngle; //更新下一次起始角度
            }
        }
    }


</script>
</html>

Canvas:绘制玫瑰饼图

玫瑰饼图实现思路

在这里插入图片描述    在上面饼图的基础上,实现玫瑰图。基本思路为:

        ①扇形半径的不确定性:注意到,每次绘制扇形区域时,都需要给定扇形半径r参数,而玫瑰图的特点就是——扇形半径长度的不确定性,这一点可以借助Math.random()产生随机数实现;
        ②文字标签位置自适应性:当扇形半径变化时,我们期待的预期效果——文字标签也跟着自动调整位置,回顾一下上面的饼图位置标签绘制算法,只需要将(R+offset)参数中的R更新为当前扇形的半径参数r即可。

在这里插入图片描述

玫瑰饼图绘制结果

在这里插入图片描述

玫瑰饼图:示例代码

    示例代码如下,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>06-pieChart</title>
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
</body>
<script>
    //数据源
    let dataSet = [
        {
            value: 50,
            name: "轻工业产值",
        },
        {
            value: 65,
            name: "农业产值",
        },
        {
            value: 25,
            name: "畜牧业产值",
        },
        {
            value: 36,
            name: "重工业产值",
        },
        {
            value: 89,
            name: "零售业产值",
        },
    ];
    initPieChart("canvas", dataSet);//绘制饼图

    function initPieChart(_targetId, _dataSet) {
        let canvas = document.querySelector(_targetId);
        let context = canvas.getContext("2d");
        canvas.style.border = "1px solid #ccc"; //设置canvas边框
        //获取canvas属性
        let canvasHeight = canvas.height,
            canvasWidth = canvas.width;
        //解析数据集
        let sum = _dataSet.reduce(function (pre, cur) {
            return pre + cur.value;
        }, 0);
        let trans_dataSet = _dataSet.map(item => {
            return {
                value: item.value / sum,
                name: item.name,
            }
        });
        console.log(trans_dataSet)
        drawPieChart(trans_dataSet,sum,canvasWidth,canvasHeight); //绘制饼图
        /**
         * 饼图绘制方法
         * @param _dataSet
         * @param _canvasWidth
         * @param _canvasHeight
         */
        function drawPieChart(_dataSet,_sum,_canvasWidth,_canvasHeight){
            //计算饼图中心坐标{不考虑padding值}
            let pieCenter = {
                x:_canvasWidth/2,
                y:_canvasHeight/2
            };
            let pieRadius = 0.5*Math.min(_canvasHeight,_canvasWidth)*0.45;
            let beginAngle = -Math.PI/2 ;//开始起始角度
            let curAngle = -Math.PI/2 ;//设置当前起始角度
            //计算随机颜色表
            let randomColorTable = (function (_length,_alpha=1){
                let colorTable = new Array(_length).fill("#000");
                return colorTable.map(item=>`rgba(${Math.random()*255},${Math.random()*255},${Math.random()*255},${_alpha})`);
            })(_dataSet.length,0.8);
            //解析数据集
            for (let i = 0; i < _dataSet.length; i++) {
                let roseRadians = pieRadius + Math.random()*50;//计算玫瑰图随机半径值
                let endAngle = curAngle + _dataSet[i]['value']*360*Math.PI/180; //计算当前终止角度-{角度转弧度}
                //1-绘制圆弧
                context.beginPath();//开始绘制圆弧
                context.moveTo(pieCenter.x,pieCenter.y);
                context.arc(pieCenter.x,pieCenter.y,roseRadians,curAngle,endAngle,false);//顺时针绘制饼图
                context.closePath();
                context.fillStyle = randomColorTable[i%randomColorTable.length];
                context.fill();
                //2-绘制文字标签
                let splitAngle = curAngle + _dataSet[i]['value']*360*Math.PI/180*0.5; //角平分线所在射线的角度
                let textParams = {
                  splitAngle : splitAngle,//角度参数
                  offset : 30,//文字位置偏移量
                  label: `${_dataSet[i]['name']}:${(_dataSet[i]['value']*100).toFixed(2)}%`,//文本内容
                };
                let textArray = textParams.label.split(":");
                //计算文字位置
                let textPosition = {
                    x: pieCenter.x + Math.cos(textParams.splitAngle) * (roseRadians+textParams.offset),
                    y: pieCenter.y + Math.sin(textParams.splitAngle) * (roseRadians+textParams.offset),
                };
                context.beginPath();
                console.log()
                context.stroke();
                //文字换行处理
                textArray.some((item,index)=>{
                    context.textAlign="center";
                    // context.fillStyle = randomColorTable[i%randomColorTable.length];
                    context.font = "12px Arial";//设置字体大小+字体样式
                    context.fillStyle = "rgba(0,0,0,0.8)";//设置字体颜色
                    context.fillText(item,textPosition.x,textPosition.y+index*20);
                });
                // context.fillText(textParams.label,textPosition.x,textPosition.y);//文字不换行可以直接调用此句代码
                context.closePath();
                curAngle = endAngle; //更新下一次起始角度
            }
        }
    }


</script>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是席木木啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值