canvas 雷达图

819 阅读4分钟

背景:

需求里面有雷达图,本想学学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°))             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°))  180° + 60° = 240°

6.  ((xCenter + R*cos(300°) , yCenter + R*sin(300°))       360° - 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); 
            }        
      }


谢谢