浅谈canvas绘画王者荣耀--雷达图

背景
一日晚上下班的我静静的靠在角落上听着歌,这时"滴!滴!"手机上传来一阵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或者。

贴士2:
这里顺便帮做设计朋友推广他的一个微信H5视频案例,全程水墨画武侠风,画工炒鸡棒棒。

另外前面loading动画宝剑出鞘css3部分,利用极少transform3d代码完成。感兴趣的童鞋可以微信扫一扫,看一下运动轨迹就心中估计就能猜出运行的的css3代码了。

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

我是车大棒!我为我自己……emmmmmmm,今天就不自己带眼了,为朋友插眼吧!

时间: 2024-11-05 17:37:25

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

创造运用浅谈canvas的设计艺术

知道<canvas>吗?如果你不熟悉<canvas>,学习他的最好方法是去看一个简单的例子.下面的HTML和JavaScript将在<canvas>域内生成一个橙色的矩形区域.推荐学习相关HTML高级教程. <canvas id="example1" width="400" height="300"></canvas>// get the canvasvar canvas = docum

电竞外设重启?这次是酷派与王者荣耀的合谋

22日第三届王者城市赛四川省决赛上,DR战队战胜QE战队夺冠,作为四川省赛冠军获得了5000元现金奖励,DR战队的每位队员也同时获得酷派Cool S1手机一部. 文/张书乐 TMT行业观察者.游戏产业时评人,人民网.人民邮电报专栏作者 早在去年12月Cool S1发布时,<王者荣耀>开发总监黄蓝枭在讲述如何做出高品质手游的时候提到了手机的重要性,并推荐使用Cool S1玩王者荣耀.此次酷派与王者荣耀再次牵手,这背后,可能有着酷派布局移动电竞外设的图谋. 为游戏而生?这不是游戏手机,而是电竞外设

从王者荣耀 谈时序图?

王者荣耀实例 case: 我方:我鲁班,安琪拉,妲己,东皇,孙悟空 打大龙. 对方后羿前来抢龙. 我方安琪拉被大龙打死. 我方击杀大龙获取经验和龙兵. 后羿击杀残血妲己. 东皇控住后羿被我击杀,孙悟空助攻. 时序图 时序图是什么 简介:是显示对象之间交互的图,这些对象是按时间顺序排列的. 组成元素:对象.生命线.控制焦点.消息等. 时序图作用及特点 作用: 理清复杂对象关系.如三国人物事件关系 时序图pk流程图 流程图 流程图着重描述处理过程:时序图 关注对象的行为,而非系统的处理过程. 时序图

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

Android Canvas的save(),saveLayer()和restore()浅谈 时间:2014-12-04 19:35:22      阅读:1445      评论:0      收藏:0      [点我收藏+] save()  saveLayer()  restore() 1.在自定义控件当中你onMeasure和onLayout的工作做完成以后就该绘制该控件了,有时候需要自己在控件上添加一些修饰来满足需求 复写onDraw(Canvas canvas),其中Canvas就像是

浅谈UML的概念和模型之UML九种图

文件夹: UML的视图 UML的九种图 UML中类间的关系 上文我们介绍了,UML的视图,在每一种视图中都包括一个或多种图.本文我们重点解说UML每种图的细节问题: 1.用例图(use case diagrams) [概念]描写叙述用户需求,从用户的角度描写叙述系统的功能 [描写叙述方式]椭圆表示某个用例:人形符号表示角色 [目的]帮组开发团队以一种可视化的方式理解系统的功能需求 [用例图] 2.静态图 类图(class  diagrams) [概念]显示系统的静态结构,表示不同的实体是怎样相关

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

浅谈局域网ARP攻击的危害及防范方法(图)   作者:冰盾防火墙 网站:www.bingdun.com 日期:2015-03-03   自 去年5月份开始出现的校内局域网频繁掉线等问题,对正常的教育教学带来了极大的不便,可以说是谈“掉”色变,造成这种现象的情况有很多,但目前最常见的是 ARP攻击了.本文介绍了 ARP攻击的原理以及由此引发的网络安全问题,并且结合实际情况,提出在校园网中实施多层次的防范方法,以解决因ARP攻击而引发的网络安全问题,最后介 绍了一些实用性较强且操作简单的检测和抵御攻

浅谈IM软件业务知识—会话session的概念,附一张IM软件的层次图

session一般出现在计算机领域,IM软件中的session,老的IM有两层:首先是逻辑层的session来管理会话的参与者,消息列表,会话类型等等:还有协议层的session,主要是代表客户端跟服务器的一个事物通道. 老的IM软件 客户端跟Server交互的每一类操作都是基于会话.比如客户端登录,需要建立一个登录的会话:客户端发消息,需要建立一个会话.下面举例: 客户端向Server发了一条消息,这条消息的发送就建立在会话之上.客户端需要下面几个步骤. 1. 创建一个session ID=1

浅谈图的广度优先遍历

一.广度优先遍历 上次我们浅谈了图的深度优先遍历,接下来我们使用广度优先搜索来遍历这个图: 这五个顶点被访问的顺序如下图所示: 二.实现过程 广度优先搜索过程如下: 首先以一个未被访问过的顶点作为起始顶点,比如以1号顶点为起点. 将1号顶点放入到队列中,然后将与1号顶点相邻的未访问过的顶点,即2号.3号和5号顶点依次放入到队列中. 接下来再将2号顶点相邻的未访问过的4号顶点放入到队列中. 到此所有顶点都被访问过,遍历结束. 广度优先遍历的主要思想: 首先以一个未被访问过的顶点作为起始顶点,访问其

从王者荣耀谈设计模式?

软件开发中涉及到的设计模式很多,这里重点讨论工作中常见的一些设计模式,围绕王者荣耀中的场景进行展开. 1:策略模式 策略模式demo <?php //1:抽象策略接口:王者荣耀 abstract class kingGlory{ abstract function showTime(); } //2:具体策略角色 //鲁班 class luban extends kingGlory{ public function showTime(){ echo  '猥琐发育,躲坦克后面'; } } //王昭