用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轴是反的,且旋转的点不是原点,是其中一个坐标 所以需要计算一下

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

左钦杨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值