我们应该感谢相遇,无论结局是喜是悲....
公告:进军Python
浅谈 Canvas 绘画王者荣耀 —— 雷达图
  • 首页 > IT技术
  • 作者:起点终站
  • 2018年8月1日 14:41 星期三
  • 浏览:1510
  • 字号:
  • 评论:4
  • 640?wx_fmt=jpeg

    背景:

    一日晚上下班的我静静的靠在角落上听着歌,这时"滴!滴!"手机上传来一阵qq消息。原来我人在问王者荣耀的雷达图在页面上如何做出来的,有人回答用canvas绘画。那么问题来了,已经好久没有使用canvas绘画了东西。

    SO,就想自己画一个canvas雷达图,顺便重新回顾一下canvas的知识点。

    王者荣耀雷达图的基本构成。

    聊天记录当中的雷达图不是特别清楚,所以我这边截图了自己的一个战绩雷达图。

    640?wx_fmt=jpeg

    是不是有被我的战绩吓到了,害不害怕!

    好了扯远了,让我们回到正题上来。

    通过截图上面的雷达图基本主体是一个正六边形,每个顶点则配有相应的文字说明。

    然后就是中间红色区域部分则由对角线上的点,连成一圈填充构成。因此这里我们称它为数据填充区

    所以这个雷达图我们分为三步来完成。

    ①正六边形

    ②数据填充区

    ③绘制文本

    正六变形的坐标点解析

    在绘画这个正六边形的时候,先让我们对于这个正六边形进行简单的数学分析。

    这里先用画板画一个正六变形,然后进行切割并切角。

    640?wx_fmt=jpeg

    是吧,借用以前高中还是初中的数学,正六边形的内角和720°,那么每一个对角就是120°。在已知对角线的长度。那么通过sin60°,cos60°一类的,那个可以求出各个三角形的边长。

    可是问题来了,这里我们要计算的是各个坐标点。而canvas的坐标轴是从左上角算(0,0)原点的单象限坐标轴。假设六边形的中心点是(250,250)、对角线的长度是100*2,那么按照三角函数推断:

    bottom-center坐标:(250, 250 + 100)

    bottom-left坐标:(250 - 100*sin(60°), 250+100*cos(60°))

    top-left坐标:(250 - 100*sin(60°), 250-100*cos(60°))

    top-center坐标:(250, 250 - 100)

    top-right坐标:(250 + 100*sin(60°), 250-100*cos(60°))

    bottom-right的坐标:(250 + 100*sin(60°), 250+100*cos(60°))

    640?wx_fmt=jpeg

    坐标是出来了,但是一个点一个点去绘画是不是有点太low了!

    肿么办?

    啦啦啦啦!

    那么就到了我们找规律的时间来了!

    但是在找规律的同时,为毛中心点的X轴和别人不一样,为毛一会加一会减。

    所以当思考各坐标点参数的规律的时候,让先回顾以前的函数角度图表

    640?wx_fmt=jpeg

    看完这个函数参照图之后,让我再次修改一下6个点的书写方式。

    bottom-center坐标:(250 + 100*sin(0°), 250 + 100*cos(0°))

    bottom-left坐标:(250 + 100*sin(300°), 250+100*cos(300°))

    top-left坐标:(250 + 100*sin(240°), 250-100*cos(240°))

    top-center坐标:(250 +100*sin(180°), 250 + 100*cos(180°))

    top-right坐标:(250 + 100*sin(120°), 250-100*cos(120°))

    bottom-right的坐标:(250 + 100*sin60°), 250+100*cos(60°))

    这个时候再看组坐标数据点,是不是感觉有点意思!

    640?wx_fmt=jpeg

    那么这个时候我们便可以通过一个for循环,用一个数组把这6个坐标点给记录下

    来。

    
    
     
    
    1. var pointArr = [];for (var i = 0; i < 6; i++) {
    2. pointArr[i] = {};
    3. pointArr[i].x = 250 + 100 * Math.sin(60 * i);
    4. pointArr[i].y = 250 + 100* Math.cos(60 * i);
    5. }

    1.1 绘画正六边形


    前面既然,将正六边形的坐标点通过一个for循环解析出来。那么就是代码绘画正六边形了:

     
    
    1. <style>
    2. canvas { display: block; width: 500px; height: 500px;
    3. }</style><body>
    4. <canvas class="radar"></canvas></body><script>
    5. var canvas = document.getElementsByClassName('radar')[0];
    6. canvas.width = 500;
    7. canvas.height = 500; var ctx = canvas.getContext('2d');
    8. ctx.save();
    9. ctx.strokeStyle = '#888888'; // 设置线条颜色
    10. var lineArr = []; var rAngle = Math.PI * 2 / 6; // 算出每一个内角和
    11. console.log(rAngle); var rCenter = 250; // 确定中心点
    12. var curR = 100; // 确定半径长度
    13. ctx.beginPath(); for (var i = 0; i < 6; i++) {
    14. lineArr[i] = {};
    15. lineArr[i].y = rCenter + curR * Math.cos(rAngle * i);
    16. lineArr[i].x = rCenter + curR * Math.sin(rAngle * i);
    17. ctx.lineTo(lineArr[i].x, lineArr[i].y);
    18. }
    19. ctx.closePath();
    20. ctx.stroke();
    21. ctx.restore();

    640?wx_fmt=jpeg

    啦啦啦!!!一个正六边形就这么的画出来。

    备注:这里rAngle这里是很灵活的,如果说画18正边形,就除以18,然后for循环18次就ok了.

    640?wx_fmt=jpeg

    哈哈!!感觉发现了新大陆了!绘制正多边形的貌似可以按照这个规律来!!

    1.2 绘画对角线

    既然前面有一个数组存储各个坐标点,所以让每个对角线对角点直线想连就ok了!

    
    
     
    
    1. ctx.strokeStyle = '#e8ddc7'; // PS吸管那么一吸
    2. ctx.save();
    3. ctx.beginPath(); // for (var j = 0; j < 3; j++) {
    4. // ctx.lineTo(lineArr[j].x, lineArr[j].y);
    5. // ctx.lineTo(lineArr[j+3].x, lineArr[j+3].y);
    6. // ctx.stroke();
    7. // }
    8. for (var j = 0; j < 3; j++) {
    9. ctx.moveTo(lineArr[j].x, lineArr[j].y);
    10. ctx.lineTo(lineArr[j + 3].x, lineArr[j + 3].y);
    11. ctx.stroke();
    12. }
    13. ctx.closePath();
    14. ctx.restore();

    640?wx_fmt=jpeg

    2.1数据填充区

    关于数据填充区,也就是雷达图当中,不规则的红色半透明的六边形。其实就是就可以看做中心点,到各个边角点之间线段为一区间这。之后就是将这个区间分成若干份,你占这个这个区间多少份,满份就是边角点,零份就是原点。

    观察前面的雷达图当中,B等级大概占据某个等级的50%左右。而B前面还有等级A、S。

    所以当S等级时候,可以看作区间 / 1。

    B等级看作区间 / 2, 那么A就是 区间 / 1.5.

    以此类推就可以得出剩下 C 就是区间 / 2.5、D:区间/ 3

    这里我就不用for循环书写了,直接偷懒手写一个对象。

    
    
     
    
    1. // 绘制数据区域
    2. var letterData = { 'S': 1, 'A': 1.5, 'B': 2, 'C': 2.5, 'D': 3
    3. }
    4. ctx.save();
    5. ctx.beginPath();
    6. for (var i = 0; i < 6; i++) {
    7. lineArr[i].yEnd = rCenter + curR * Math.cos(rAngle * i) / (letterData[rData[i][1]]);
    8. lineArr[i].xEnd = rCenter + curR * Math.sin(rAngle * i) / (letterData[rData[i][1]]);
    9. ctx.lineTo(lineArr[i].xEnd, lineArr[i].yEnd);
    10. console.log(lineArr);
    11. }
    12. ctx.closePath();
    13. ctx.stroke();
    14. ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
    15. ctx.fill();

    640?wx_fmt=jpeg

    2.2 对数据填充区域绘画小圆点和边长

    当我们回归到前面的截图发现,需要单独把数据填充区域的的各个点位置给加强,并把边角用更深的线条的描绘出来。

    
    
     
    
    1. ctx.lineWidth = 2; //设置数据填充区域的线条颜色ctx.strokeStyle = '#dd3f26'; //设置填充区域的颜色var point = 3; //设置数据填充区域的小圆点大小for (var i = 0; i < 6; i++) {
    2. ctx.beginPath();
    3. ctx.arc(lineArr[i].xEnd, lineArr[i].yEnd, point, 0, Math.PI * 2);
    4. ctx.fillStyle = 'rgba(255, 0, 0, 0.8)';
    5. ctx.fill(); console.log(lineArr);
    6. }
    7. ctx.restore();

    640?wx_fmt=jpeg

    3.1 绘制文本

    王者荣耀雷达文本是需要绘制两点,

    ①用黑色16px字体绘制各点描述点

    ②用红色30px字体绘制各点能力级别

    但是估计看到绘制文本,估计有的小伙伴就会说。不是有数组的存储各个边角的坐标,直接一个for循环依次根据各个点绘画出来不就OK了。

    
    
     
    
    1. // 绘制文本
    2. var rData = [
    3. ['生存', 'S'],
    4. ['经济', 'S'],
    5. ['输出', 'S'],
    6. ['KDA', 'B'],
    7. ['打野', 'B'],
    8. ['推进', 'S']
    9. ]
    10. ctx.save();
    11. ctx.font = '16px Microsoft Yahei'; //设置字体
    12. ctx.fillStyle = '#000'; // 颜色
    13. for (var i = 0; i < 6; i++) { var y = rCenter + curR * Math.cos(rAngle * i); var x = rCenter + curR * Math.sin(rAngle * i);
    14. ctx.fillText(rData[i][0], x, y);
    15. }
    16. ctx.restore();

    浏览器最终显示的视觉效果:

    640?wx_fmt=jpeg

    640?wx_fmt=jpeg

    是不是觉得很惊喜,这里输出、经济位置勉强还行,但是剩下的文字位置就偏差了许多了。所以在绘制文字的时候,还得针对文字的坐标位置进行相应的调整。

    3.2 绘制文本--描述

    既然直接调用坐标的位置会出问题,那么让根据上文中的图片文字的规则简单分析。

    ①如果X轴 == 中心点,那么就判断Y轴。比中心点大文字下移一点,反之文字上移一点。

    ②如果X轴 < 中心点,那么文字X轴位置就左移动一点,反正右移动距离。

    
    
     
    
    1. // 绘制文本
    2. ctx.save(); var fontSize = 16;
    3. ctx.font = fontSize + 'px Microsoft Yahei';
    4. ctx.textBaseline="middle"; //设置基线参考点
    5. ctx.textAlign="center"; // 文本居中
    6. ctx.fillStyle = '#000'; for (var i = 0; i < 6; i++) { var y = rCenter + curR * Math.cos(rAngle * i); var x = rCenter + curR * Math.sin(rAngle * i); console.log(Math.sin(rAngle * i)) var s_width = ctx.measureText(rData[i][0]).width; //获取当前绘画的字体宽度 if ( x == rCenter) { if (y > rCenter ) {
    7. ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
    8. } else {
    9. ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
    10. }
    11. } else if ( x > rCenter) { console.log(rData[i][0]);
    12. ctx.fillText(rData[i][0], x + s_width*1.5, y);
    13. } else {
    14. ctx.fillText(rData[i][0], x - s_width*1.5, y);
    15. }

    640?wx_fmt=jpeg

    这里多了好几个不常用的属性,下面就是介绍这些属性的特点:

    ctx.textBaseline: 设置或返回在绘制文本时使用的当前文本基线

    说到基线,各位童鞋想一想咱们以前英文练习本,上面有着一条条线条

    640?wx_fmt=jpeg

    瞬间回忆到当年被罚抄英语单词的岁月,一把辛酸泪呀。

    640?wx_fmt=jpeg

    网页设计字体也有一个基线的存在,因此canvas的基线点就是直接从坐标点划出一条横线基线。

    这里从网络上截图一张,通过设置基线参考位置,看看文本所在位置的改变。

    640?wx_fmt=jpeg

    ctx.textAlign: 这个文本水平居中,不过和CSS当中的居中不一样的是,他是从坐标点划出一条竖线分割文本的。

    640?wx_fmt=jpeg

    ctx.measureText : 返回包含指定文本宽度的对象。

    通俗一点的就是说,就是获取你绘制文本的宽度。假设一排文字内容为'Hello World', size为16px大小文本。在这里高度都是16px稳定不变,这样canvas画其他元素对这个位置只需要Y轴移动这个文本的'size'大小就可以避免覆盖到上面。

    但是如果要X轴去移动位置,你根本不知道'Hello World'这串文本的长度。那么这个时候就需要ctx.measureText这个方法,获取当前你绘制文本的宽度。

    3.2 绘制文本--能力级别

    既然前面已经介绍了描述的绘画方法,那么依葫芦画瓢。让我们一并开始绘制能力级别的文本。

    
    
     
    
    1. // 绘制文本
    2. ctx.save(); var fontSize = 16; var maxfontSize = 30;
    3. ctx.font = fontSize + 'px Microsoft Yahei';
    4. ctx.textBaseline="middle";
    5. ctx.textAlign="center"; for (var i = 0; i < 6; i++) { var y = rCenter + curR * Math.cos(rAngle * i); var x = rCenter + curR * Math.sin(rAngle * i); console.log(Math.sin(rAngle * i)) var s_width = ctx.measureText(rData[i][0]).width; if ( x == rCenter) { if (y > rCenter ) {
    6. ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
    7. } else {
    8. ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
    9. }
    10. } else if ( x > rCenter) { console.log(rData[i][0]);
    11. ctx.fillText(rData[i][0], x + s_width*1.5, y);
    12. } else {
    13. ctx.fillText(rData[i][0], x - s_width*1.5, y);
    14. }
    15. }
    16. ctx.restore();
    17. ctx.save();
    18. // 绘制等级
    19. ctx.font = '30px Microsoft Yahei bold';
    20. ctx.fillStyle = '#d7431f';
    21. ctx.textBaseline="middle";
    22. ctx.textAlign="center"; for (var i = 0; i < 6; i++) { var y = rCenter + curR * Math.cos(rAngle * i); var x = rCenter + curR * Math.sin(rAngle * i); var M_width = ctx.measureText(rData[i][1]).width; if ( x == rCenter) { if (y > rCenter ) {
    23. ctx.fillText(rData[i][1], x + M_width/2, y + fontSize);
    24. } else {
    25. ctx.fillText(rData[i][1], x + M_width/2, y - fontSize);
    26. }
    27. } else if ( x > rCenter) { console.log(rData[i][0]);
    28. ctx.fillText(rData[i][1], x + M_width, y);
    29. } else {
    30. ctx.fillText(rData[i][1], x - M_width, y);
    31. }
    32. }
    33. ctx.restore();
    34. ctx.save();

    页面最终效果:

    640?wx_fmt=jpeg

    640?wx_fmt=jpeg

    结尾

    好了!以上就是鄙人对于canvas绘画一点简单理解与复习了,其中也回顾了一些canvas基本属性点。后续如何用canvas玩出各种花样就看各位看官自己了!

    小贴士:

    在使用ctx.measureText这个方法的时候需要注意一下。这个方法在宽度参考对象也跟当前绘画环境的font-size有关联的。

    打个比方说,在绘制描述的文本的时候。font-size设置是16px,那么ctx.measureText('输出').width 是32。

    那么在绘制能力等级的时候,font-size设置是32,那么ctx.measureText('输出').width 就不再是32了而是64或者。

    作者:车大棒

    声明 | 文章著作权归作者所有,如有侵权,请联系小编删除。


      您阅读这篇文章共花了:  
    本文作者:起点终站      文章标题: 浅谈 Canvas 绘画王者荣耀 —— 雷达图
    本文地址:https://blog.hellozwh.com/?post=389
    版权声明:若无注明,本文皆为“起点终站”原创,转载请保留文章出处。
    青山08月05日
    看到了初中高中的数学知识
    起点终站 没错,我就是那个帅逼站长08月06日
    @青山:是不是还给老师了哈
    qaq608月03日
    对于我这样的数学学渣来说,这是个难题
    起点终站 没错,我就是那个帅逼站长08月06日
    @qaq6:哈哈。我也都忘了
    返回顶部| 首页| 手气不错| 网站地图| sitemap| 装逼生成器| 站长介绍|

    Copyright © 2014-2017 起点终站   闽ICP备16011094号-1

    00:00 / 00:00
    顺序播放