用canvas画圆形雷达图

首先需求设计稿如下,其中3个点(箭头指向)需要根据数据分值在实线半径上移动,同时阴影三角形面积随之改变。
在这里插入图片描述
1 首先尝试有没有现成的轮子,echarts中找了一下,有相同的,但是改成完全一样,有些麻烦 需要修改源代码,其本身提供的API不满足于需求。 当然如果时间紧急,可以跟产品设计沟通,让他们妥协。

2 自己画,首先我使用了前端canvas. 在html中加入一个dom

<canvas id='radar-img' style="width:170;height:170" />

画布的宽高需要用css定义。
然后就是基本代码,创建canvas,把底部图放上去,然后放点位图,代码我就不写了因为不好使。
缺点1 画布调整和图片原图大小保持一致 , 图片也是模糊的
缺点2 图片的宽高和坐标都不能按照代码上的逻辑保持一致,调试非常困难。
所以没多想,就放弃了,之前也有过html2canvas的bug,在不同设备的兼容性也不好。

最终方案是用node-canvas, 在服务端画图,发接口返回一个base64的图片到前端,前端只需要把数据给接口,接口返回一张画好的图片。在服务端没有设备兼容性的困扰,画图调试也一步到位。
由于固定了3个维度的分值数据,所以地图直接把所有的圆环和120度线直接切好,于是我让设计给我了2张图 (当然一张图不要也能画完,主要是费时间)
如下:
在这里插入图片描述
此时我们可以用大一点的图,画布也大一点,这样比较清晰
画之前我已经把实现思路想好了,主要用到了一个数学公式
一个坐标点绕圆点旋转一定角度后的坐标
直角三角形函数在这里居然排上用场
最终代码如下:(代码完整,安装依赖可独立运行)

var app = express();
const { createCanvas, loadImage } = require('canvas');

//使用中间件body-parser获取post参数
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());

app.use(function (req, res, next) {
  res.header("Access-Control-Allow-Origin", req.headers.origin);
  res.header("Access-Control-Allow-Credentials", "true");
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT,DELETE,OPTIONS,PATCH");
  res.header("Access-Control-Allow-Headers", "channel, mch_id, cache-control, authorization, client, x-cookie, content-type");
  res.header("Access-Control-Request-Headers", "Origin, X-Requested-With, content-Type, Accept, Authorization");
  res.header("X-Powered-By", ' 3.2.1');
  res.header("Content-Type", "application/json;charset=utf-8");

	console.log(req.query.score_1,req.query.score_2,req.query.score_3);
	var bgImg = 'https://jdxl-img.jdxlt.com/uploads/7f92fb1b31dd45d9aef6892001bae3f4.png';
	var pointImg = 'https://jdxl-img.jdxlt.com/uploads/cc614fbdd32a49da827cdf201a7f43c7.png';
	const canvas = createCanvas(680, 680);
	const ctx = canvas.getContext('2d');
	var promise_1 = new Promise(function(resolve, reject){
		loadImage(bgImg).then((image) => {
			resolve(image);
		})
	})
	var promise_2 = new Promise(function(resolve, reject){
		loadImage(pointImg).then((image) => {
			resolve(image);
		})
	})

	const calculateRotate = (point, degree) => {
  	  let x = point.x * Math.cos(degree * Math.PI / 180) + point.y * Math.sin(degree * Math.PI / 180);
    	  let y = -point.x * Math.sin(degree * Math.PI / 180) + point.y * Math.cos(degree * Math.PI / 180);
    	  let relativeOriginPoint = {
        	x: Math.round(x * 100) / 100,
        	y: Math.round(y * 100) / 100
    	  };
    	  return relativeOriginPoint;
	};
	Promise.all([promise_1,promise_2]).then(function(data){
		var point_2 = calculateRotate({x:0,y:Math.round(340-(340*req.query.score_2/100))},120);
		var point_3 = calculateRotate({x:0,y:Math.round(340-(340*req.query.score_3/100))},240);
		// 三角形阴影
		ctx.beginPath();
		ctx.moveTo(340,(340-Math.round(340*(100-req.query.score_1)/100))); // 因为圆点1没有旋转度数y轴为正
		ctx.lineTo(340+point_2.x,340-point_2.y);
		ctx.lineTo(340+point_3.x,340-point_3.y);
		ctx.fillStyle='rgba(109, 128, 157, 0.3)';
		// 透明度填充
		ctx.globalAlpha=0.5;
		ctx.fill();
		// 恢复透明度 画上3个点
		ctx.globalAlpha=1;
		ctx.drawImage(data[0],0,0,680,680); // 背景图
		ctx.drawImage(data[1],(340-24),(340-Math.round(340*(100-req.query.score_1)/100))-24,48,48); // 图像点

		ctx.drawImage(data[1],(point_2.x+340-24),(340-point_2.y-24),48,48); // 图像点
		ctx.drawImage(data[1],(point_3.x+340-24),(340-point_3.y-24),48,48); // 图像点
		let result = {
			code: 0,
			msg: '',
			data:canvas.toDataURL()
		}
		res.send(JSON.stringify(result));
	})


})

app.listen(1320,function(){
    console.log('port 1320 is running!');
});

其中需要注意

1 跨域解决方案代码

  res.header("Access-Control-Allow-Origin", req.headers.origin);
  res.header("Access-Control-Allow-Credentials", "true");
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT,DELETE,OPTIONS,PATCH");
  res.header("Access-Control-Allow-Headers", "channel, mch_id, cache-control, authorization, client, x-cookie, content-type");
  res.header("Access-Control-Request-Headers", "Origin, X-Requested-With, content-Type, Accept, Authorization");
  res.header("X-Powered-By", ' 3.2.1');
  res.header("Content-Type", "application/json;charset=utf-8");

2 一个坐标点绕圆点旋转一定角度后的坐标方法(用 0, 90 旋转90度做了验证 ,其后实践也确实好用)

	const calculateRotate = (point, degree) => {
  	  let x = point.x * Math.cos(degree * Math.PI / 180) + point.y * Math.sin(degree * Math.PI / 180);
    	  let y = -point.x * Math.sin(degree * Math.PI / 180) + point.y * Math.cos(degree * Math.PI / 180);
    	  let relativeOriginPoint = {
        	x: Math.round(x * 100) / 100,
        	y: Math.round(y * 100) / 100
    	  };
    	  return relativeOriginPoint;
	};

剩下的就是canvas基础功能api,查一查都有:
1 如何画三角形
2 如何设置透明度
3 画图的时候可以定义坐标和宽高
4 左上角是坐标点(0,0)和正常坐标系的y轴是反的,且旋转的点不是原点,是其中一个坐标 所以需要计算一下

最后结果:
在这里插入图片描述

以下是一个简单的使用 canvas 绘制雷达图的示例代码: ```html <!DOCTYPE html> <html> <head> <title>Radar Chart</title> <style type="text/css"> canvas { border: 1px solid #ddd; margin: 20px auto; } </style> </head> <body> <canvas id="radar-chart" width="400" height="400"></canvas> <script type="text/javascript"> var canvas = document.getElementById('radar-chart'); var context = canvas.getContext('2d'); var data = [80, 60, 40, 50, 90, 70]; // 数据 var labels = ['A', 'B', 'C', 'D', 'E', 'F']; // 标签 var maxVal = 100; // 最大值 var stepSize = maxVal / 5; // 步长 // 绘制坐标轴 context.beginPath(); context.moveTo(200, 0); context.lineTo(200, 400); context.moveTo(0, 200); context.lineTo(400, 200); context.stroke(); // 绘制数据区域 context.beginPath(); context.moveTo(getX(0, data[0]), getY(0, data[0])); for (var i = 1; i < data.length; i++) { context.lineTo(getX(i, data[i]), getY(i, data[i])); } context.closePath(); context.fillStyle = 'rgba(255, 0, 0, 0.2)'; context.fill(); // 绘制数据点 for (var i = 0; i < data.length; i++) { context.beginPath(); context.arc(getX(i, data[i]), getY(i, data[i]), 5, 0, 2 * Math.PI); context.fillStyle = 'red'; context.fill(); } // 绘制标签 for (var i = 0; i < labels.length; i++) { context.fillText(labels[i], getX(i, maxVal + 10), getY(i, maxVal + 10)); } // 绘制刻度 for (var i = 0; i <= 5; i++) { var val = i * stepSize; context.fillText(val, getX(0, val) - 15, getY(0, val) + 5); context.beginPath(); context.arc(200, 200, getX(0, val), 0, 2 * Math.PI); context.strokeStyle = '#ddd'; context.stroke(); } // 计算坐标 function getX(index, value) { var angle = Math.PI * 2 / data.length * index - Math.PI / 2; return 200 + 150 * value / maxVal * Math.cos(angle); } function getY(index, value) { var angle = Math.PI * 2 / data.length * index - Math.PI / 2; return 200 + 150 * value / maxVal * Math.sin(angle); } </script> </body> </html> ``` 这个例子绘制了一个六边形的雷达图,包含了数据区域、数据点、标签、刻度等元素。你可以根据自己的需求修改数据和样式。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

左钦杨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值