当前位置:网站首页>Canvans:绘制饼图和玫瑰饼图
Canvans:绘制饼图和玫瑰饼图
2022-08-08 19:26:00 【似曾不相识】
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>
边栏推荐
猜你喜欢
Goose Factory Robot Dog Fancy Crossing 10m Plum Blossom Pile: Front Flip, Single Pile Jump, Get Up and Bow... No stumble in the whole process
[BJDCTF2020]Easy MD5
瑞芯微rk1126 平台部分jpeg图片解码程序挂掉的问题
传统和加密域名概述
分布式文件系统fastDFS
干货:从零设计高并发架构
快速搭建SSM框架
Laravel queue consumption instance and timed task add task consumption
在Unity URP中实现Forward+
曲面着色器初试--地面轨迹模拟(部分细节不完善)
随机推荐
ADB安装方法:
PyTorch入门:(四)torchvision中数据集的使用
Why Manufacturing Companies Should Deploy Digital Factory Systems
腾讯云原生成本优化平台FinOps Crane荣获国家级大奖!
What is the main purpose of software testing?
Azure Neural TTS continues to be updated to help enterprises develop small language markets
黑猫带你学Makefile第2篇:程序编译的过程
我们为什么要远离Service Mesh
微服务负载均衡器Ribbon
数据库学习之表的操作
RADIUS服务器的演变过程
Goose Factory Robot Dog Fancy Crossing 10m Plum Blossom Pile: Front Flip, Single Pile Jump, Get Up and Bow... No stumble in the whole process
梅科尔工作室OpenHarmony设备开发培训笔记-第一章学习笔记
NAACL2022 NER SOTA—RICON学习笔记
Codeforces Round #707 (Div. 2) C(抽屉原理)
工程 (六) ——PointNet点云分类
BP neural network
黑猫带你学Makefile第1篇:什么是Makefile
达梦数据库 DmAPservice服务,启停影响 DMSERVER库服务吗?
Flutter Chart