背景:
需求里面有雷达图,本想学学echarts,但后来想下还是用canvas封装一个函数,这样以后再用到类似的时候可以直接用,同时也复习了canvas的知识点。
雷达图基本构成:
角度、坐标点分析
圆周角整个位2*Math.PI弧度(这里为什么要用弧度,因为下面的sin,cos计算只能接受弧度制),所以每个角度弧度为 2*Math.PI/边数 。
此时我们计算各个顶点的坐标,假设中心点的坐标为(xCenter,yCenter), 对角线长度为2R,相当于一个半径为R的圆,这些点都在圆上。因此坐标为:
1. (xCenter + R, yCenter) 0°
2. (xCenter + R*cos(60°) , yCenter + R*sin(60°)) 60°
3. (xCenter - R*cos(60°) , yCenter + R*sin(60°)) 180° - 60°
4. (xCenter - R) , yCenter)
5. (xCenter - R*cos(60°) , yCenter - R*sin(60°)) 180° + 60°
6. (xCenter+R*cos(60°) , yCenter - R*sin(60°))
这里的坐标由于有加有减,不方便计算,所以全部转换成正号
应用三角函数的诱导公式,奇变偶不变,函数看象限来进行转化
1. (xCenter + R*cos(0°) , yCenter + R*sin(0°))
2. (xCenter + R*cos(60°) , yCenter + R*sin(60°)) 60°
3. (xCenter + R*cos(120°) , yCenter + R*sin(120°)) 180° - 60° = 120°
4. (xCenter + R*cos(180°) , yCenter + R*sin(180°)) 180°
5. ((xCenter + R*cos(240°) , yCenter - R*sin(240°))80° + 60° = 240°
6. ((xCenter + R*cos(300°) , yCenter + R*sin(300°))° - 60° = 300°
从中可以发现规律,这些角度是有规律性变化的,正好为内角的倍数,因此我们可以把这些点
存下来,后面通过for循环来实现点的坐标
绘制雷达图
let canvas = document.getElementsByClassName('radar__content')[0];
//获取画布const radarNum = 8 ;//确定是几边行
canvas.width = 550;canvas.height = 400;
let xCenter = 275; // 确定中心点x轴位置
let yCenter = 200; // 确定中心点y轴位置
let radius = [150,115,80,45]; // 设置不同的半径长度展示不同的点
let lineArr = []; //存放图形各顶点位置
let lineArrData = []; //存放数据区域顶点位置
let ctx = canvas.getContext('2d'); //获取画笔
ctx.lineWidth = 2; // 线宽
ctx.strokeStyle = '#ccc'; // 设置线条颜色
let radian = 2*Math.PI/radarNum; //内角弧度
// 根据边数循环绘制,由于图形不同,所以有的图形后期需要旋转以增加美观
for (var j = 0; j < radius.length; j++) {
ctx.beginPath();lineArr[j] = [];
for (var i = 0; i < radarNum; i++) {
lineArr[j][i] = {};
lineArr[j][i].x = xCenter + radius[j] * Math.cos(radian*i); //需要利用弧度才能实现
lineArr[j][i].y = yCenter + radius[j] * Math.sin(radian*i);
ctx.lineTo(lineArr[j][i].x, lineArr[j][i].y);
}
ctx.cloePath();
ctx.stroke();
}复制代码
通过半径的不同,实现多个圈形的绘制。其中边的个数和半径的个数都是可调的,改变radarNum 的值和 半径的长度
顶点到中心点连线
由于图形是规律变化的,所以只需要取出最大半径的几个点,然后和中心点连接,即可绘制所有点到中心点的线。
ctx.beginPath();
for (let i = 0; i < radarNum; i++) {
ctx.moveTo(xCenter, yCenter);
ctx.lineTo(lineArr[0][i].x, lineArr[0][i].y);
ctx.stroke();
ctx.closePath();
}复制代码
绘画数据填充区、数据填充区边界
通过定义各边的显示比例,然后通过半径和比例相乘再加上对应的中心点坐标x和y值,得到对应的点,然后进行绘制。
const dateNode : [0.95,0.67,0.6,0.8,0.5,0.8]
ctx.beginPath();
for (let i = 0; i < radarNum; i++) {
lineArrData[i] = {};
lineArrData[i].x = xCenter + radius[0] * Math.cos(radian*i)*dateNode[i];
lineArrData[i].y = yCenter + radius[0] * Math.sin(radian*i)*dateNode[i];
ctx.lineWidth = 3;
ctx.lineTo(lineArrData[i].x,lineArrData[i].y);
}
ctx.fillStyle = "rgba(117,110,255,0.74)";
ctx.fill();
ctx.strokeStyle = '#1B27C4';
ctx.closePath();
ctx.stroke ();复制代码
数据与雷达图交点
利用之前数据区域得到的顶点,直接进行绘制小圆点
for (let i = 0; i < radarNum; i++) {
ctx.beginPath();
ctx.arc(lineArrData[i].x, lineArrData[i].y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#1B27C4';
ctx.fill();
}复制代码
雷达图外围文本
const radarText = ["职位浏览","简历完善","信息认证","投递简历"];
let fontSize = "20";
ctx.font = fontSize + 'px PingFangSC-Regular';
ctx.textBaseline = "middle"; //设置基线参考点
ctx.textAlign = "center"; // 文本居中
ctx.fillStyle = '#666';
for (let i = 0; i < radarNum; i++) {
let x = lineArr[0][i].x,
y = lineArr[0][i].y;
const s_width = ctx.measureText(radarText[i]).width; //获取当前绘画的字体宽度
if ( x === xCenter) {
if (y > yCenter ) {
ctx.fillText(radarText[i], x, y + s_width*0.3);
} else {
ctx.fillText(radarText[i], x, y - s_width*0.3);
}
} else if ( x > xCenter) {
ctx.fillText(radarText[i], x + s_width*0.8, y);
} else {
ctx.fillText(radarText[i], x - s_width*0.8, y);
}
}复制代码
谢谢