背景

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

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

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

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



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

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

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

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

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

①正六边形

②数据填充区

③绘制文本

正六变形的坐标点解析

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

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

是吧,借用以前高中还是初中的数学,正六边形的内角和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°))

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

肿么办?

啦啦啦啦!

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

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

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

看完这个函数参照图之后,让我再次修改一下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°))

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

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

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

1.1 绘画正六边形

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

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



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

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

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

1.2 绘画对角线

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

ctx.strokeStyle = '#e8ddc7';  // PS吸管那么一吸
ctx.save();
ctx.beginPath();
// for (var j = 0; j < 3; j++) {
// ctx.lineTo(lineArr[j].x, lineArr[j].y);
// ctx.lineTo(lineArr[j+3].x, lineArr[j+3].y);
// ctx.stroke();
// }
for (var j = 0; j < 3; j++) {
ctx.moveTo(lineArr[j].x, lineArr[j].y);
ctx.lineTo(lineArr[j + 3].x, lineArr[j + 3].y);
ctx.stroke();
}
ctx.closePath();
ctx.restore();

2.1数据填充区

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

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

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

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

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

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

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

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

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

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

3.1 绘制文本

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

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

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

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

 // 绘制文本
var rData = [
['生存', 'S'],
['经济', 'S'],
['输出', 'S'],
['KDA', 'B'],
['打野', 'B'],
['推进', 'S']
]
ctx.save();
ctx.font = '16px Microsoft Yahei'; //设置字体
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);
ctx.fillText(rData[i][0], x, y);
}
ctx.restore();

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

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

3.2 绘制文本--描述

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

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

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

 // 绘制文本
ctx.save();
var fontSize = 16;
ctx.font = fontSize + 'px Microsoft Yahei';
ctx.textBaseline="middle"; //设置基线参考点
ctx.textAlign="center"; // 文本居中
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 ) {
ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
} else {
ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
}
} else if ( x > rCenter) {
console.log(rData[i][0]);
ctx.fillText(rData[i][0], x + s_width*1.5, y);
} else {
ctx.fillText(rData[i][0], x - s_width*1.5, y);
}

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

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

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

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

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

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

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

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

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

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

3.2 绘制文本--能力级别

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

// 绘制文本
ctx.save();
var fontSize = 16;
var maxfontSize = 30;
ctx.font = fontSize + 'px Microsoft Yahei';
ctx.textBaseline="middle";
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 ) {
ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
} else {
ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
}
} else if ( x > rCenter) {
console.log(rData[i][0]);
ctx.fillText(rData[i][0], x + s_width*1.5, y);
} else {
ctx.fillText(rData[i][0], x - s_width*1.5, y);
}
}
ctx.restore();
ctx.save();
// 绘制等级
ctx.font = '30px Microsoft Yahei bold';
ctx.fillStyle = '#d7431f';
ctx.textBaseline="middle";
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 ) {
ctx.fillText(rData[i][1], x + M_width/2, y + fontSize);
} else {
ctx.fillText(rData[i][1], x + M_width/2, y - fontSize);
}
} else if ( x > rCenter) {
console.log(rData[i][0]);
ctx.fillText(rData[i][1], x + M_width, y);
} else {
ctx.fillText(rData[i][1], x - M_width, y);
}
}
ctx.restore();
ctx.save();

页面最终效果:

结尾

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

小贴士:

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

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

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

原创文章,文笔有限,才疏学浅,文中若有不正之处,再次再次再次欢迎各位啪啪的打脸赐教。(有句话说的好,重要的词得说三遍。)

PS:对于里面完整blogDemo代码感兴趣的,可以从本人github上阅览。

源码demo传送门地址

我是车大棒!我为我自己带眼了!

浅谈canvas绘画王者荣耀--雷达图的更多相关文章

  1. 浅谈canvas中的拖尾效果

    引言 很早就想了解以下 canvas 中的拖尾效果(如彗星,烟花等效果)是怎么实现的,但是一直没有深入了解,正巧在 codepen 上看到一个 demo,代码简单,效果炫酷,故有此文. 什么黑科技 在 ...

  2. 浅谈Facebook的服务器架构(组图)

    导读:毫无疑问,作为全球最领先的社交网络,Facebook的高性能集群系统承担了海量数据的处理,它的服务器架构一直为业界众人所关注.CSDN博主yanghehong在他自己最新的一篇博客< Fa ...

  3. 【VS开发】【DSP开发】浅谈Linux PCI设备驱动(二)

    我们在 浅谈Linux PCI设备驱动(一)中(以下简称 浅谈(一) )介绍了PCI的配置寄存器组,而Linux PCI初始化就是使用了这些寄存器来进行的.后面我们会举个例子来说明Linux PCI设 ...

  4. 【转】Android Canvas的save(),saveLayer()和restore()浅谈

    Android Canvas的save(),saveLayer()和restore()浅谈 时间:2014-12-04 19:35:22      阅读:1445      评论:0      收藏: ...

  5. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  6. 浅谈局域网ARP攻击的危害及防范方法(图)

    浅谈局域网ARP攻击的危害及防范方法(图)   作者:冰盾防火墙 网站:www.bingdun.com 日期:2015-03-03   自 去年5月份开始出现的校内局域网频繁掉线等问题,对正常的教育教 ...

  7. 【转】浅谈UML的概念和模型之UML九种图

    原文地址:浅谈UML的概念和模型之UML九种图 目录: UML的视图 UML的九种图 UML中类间的关系 上文我们介绍了,UML的视图,在每一种视图中都包含一个或多种图.本文我们重点讲解UML每种图的 ...

  8. 【带着canvas去流浪(6)】绘制雷达图

    目录 一. 任务说明 二. 重点提示 三. 示例代码 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文 ...

  9. HTML5 Canvas制作雷达图实战

    雷达图又叫蜘蛛网图,是一种对各项数据查看很明显的表现图,在很多游戏中,对游戏中的每个角色的分析图一般也用这种图. 下面,用HTML5的Cavas来实现雷达图. 效果 一.创建Canvas var mW ...

随机推荐

  1. Javascript中NaN、null和undefinded的区别

    var a1; var a2 = true; var a3 = 1; var a4 = "Hello"; var a5 = new Object(); var a6 = null; ...

  2. CentOS(linux发行版)系统安装中文输入法:

    安装步骤: 1>.打开终端界面,使用su - root切换到超级用户,然后输入yum install"@Chinese support",回车. 2>.中间安装过程提示 ...

  3. Repeated Substring Pattern --重复字符串

    Given a non-empty string check if it can be constructed by taking a substring of it and appending mu ...

  4. LeetCode 90. Subsets II (子集合之二)

    Given a collection of integers that might contain duplicates, nums, return all possible subsets. Not ...

  5. LeetCode 39. Combination Sum (组合的和)

    Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique c ...

  6. 解决 iframe 在 ios 上不能滚动的问题

    HTML代码在使用IFRAME或者其他HTML元素时,你需要使用一个元素(如DIV)来包装他们: <div class="scroll-wrapper">  <i ...

  7. 读书笔记-你不知道的JS上-闭包与模块

    闭包定义 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行. 看一段最简单的闭包代码: function foo() { var a = 2; //闭包 fun ...

  8. Oil Deposits

    Problem Description The GeoSurvComp geologic survey company is responsible for detecting underground ...

  9. What Are You Talking About

    What Are You Talking About Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 102400/204800 K (Ja ...

  10. HTML 3秒一换轮播(鼠标选中旋转停止定时) 动画案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...