详解基于 Canvas 手撸一个六边形能力图


Posted in HTML / CSS onSeptember 02, 2019

一、前言

六边形能力图如下,由 6 个 六边形组成,每一个顶点代表其在某一方面的能力。这篇文章我们就来看看如何基于 canvas 去绘制这么一个六边形能力图。当然,你也可以基于其他开源的 js 方案来实现,如 EChars.js 等。

详解基于 Canvas 手撸一个六边形能力图 

二、六边形绘制基础

六边形能力图有 6 个 六边形组成,那我们只要绘制出一个,另外 5 个则依次减小六边形的边长即可。那我们首先来分析一下,如何绘制出一个六边形。

详解基于 Canvas 手撸一个六边形能力图

如上图,绘制一个六边形有以下几个关键点:

1.紫色矩形区域我们可以看成是 canvas 的画布。其大小可以认为是 (width,height)。center(centerX,centerY) 是其中心点,即 (width / 2, height / 2)。

2.绘制六边形的关键是计算出它的 6 个顶点的坐标。而如上图所示,这里面最关键的又是计算出六边形所在矩形区域的左上角坐标(left,top)。对照上图,(left,top) 的计算公式如下。

详解基于 Canvas 手撸一个六边形能力图

要计算出 (left,top) 需要先计算出 x,y 。而 x,y 的值与六边形的边长有关。

3.如上的 x,y 的计算公式为

详解基于 Canvas 手撸一个六边形能力图

4.因此,X1(x1,y1),X2(x2,y2),X3(x3,y3),X4(x4,y4),X5(x5,y5),X6(x6,y6) 的坐标计算为

详解基于 Canvas 手撸一个六边形能力图详解基于 Canvas 手撸一个六边形能力图

因此,得到绘制六边形的代码为:

function computeHexagonPoints(width, height, edge) {
    let centerX = width / 2;
    let centerY = height / 2;
    let x = edge * Math.sqrt(3) / 2;
    let left = centerX - x;
    let x1,x2,x3,x4,x5,x6;
    let y1,y2,y3,y4,y5,y6;
    x5 = x6 = left;
    x2 = x3 = left + x * 2;
    x1 = x4 = left + x;

    let y = edge / 2;
    let top = centerY - 2 * y;
    y1 = top;
    y2 = y6 = top + y;
    y3 = y5 = top + 3 * y;
    y4 = top + 4 * y;

    let points = new Array();
    points[0] = [x1, y1];
    points[1] = [x2, y2];
    points[2] = [x3, y3];
    points[3] = [x4, y4];
    points[4] = [x5, y5];
    points[5] = [x6, y6];
    return points;
  }

三、绘制六维能力图

 3.1 绘制 6 个六边形

基于 canvas 绘制,首先就是需要获取 context。

_context = canvas.getContext('2d');

而绘制的话,已经知道 6 个顶点了,那只需要将这 6 个点用连线的方式连接起来就可以了。主要用到 moveTo(x,y) 和 lineTo(x,y) 两个方法。这里总共需要绘制 6 个六边形,那只要按等比例减小 edge 的值就可以了。因此绘制六边形能力图的主要代码如下。

function drawHexagonInner(edge) {
    _context.strokeStyle = _color;
    for (var i = 0; i < 6; i++) {
      _allPoints[i] = computeHexagonPoints(_width, _height, edge - i * edge / 5);
      _context.beginPath();
      _context.moveTo(_allPoints[i][5][0],_allPoints[i][5][1]);
      for (var j = 0; j < 6; j++) {
        _context.lineTo(_allPoints[i][j][0],_allPoints[i][j][1]);
      }
      _context.closePath();
      _context.stroke();
    }
  }

代码中还有 3 个相关的 API。beginPath() 和 closePath() 主要就是绘制得到一个封闭的路径。stroke() 主要是得到一个镂空的形状。当然,相应的就有 fill() 得到填充的形状。

3.2 绘制 3 条直线

绘制那 3 条直线也是比较简单的,只要将 X1和X4 连接,将X2 和 X5 相连,将 X3 和 X6 相连。代码如下:

function drawLines() {
    _context.beginPath();
    _context.strokeStyle = _color;
    for (let i = 0; i < 3; i++) {
      _context.moveTo(_allPoints[0][i][0],_allPoints[0][i][1]); //1-4
      _context.lineTo(_allPoints[0][i+3][0],_allPoints[0][i+3][1]); //1-4
      _context.stroke();
    }
    _context.closePath();
  }

3.3 绘制覆盖图

6 个顶点代表了六种能力,比如这里的各科成绩,把六种能力封闭成一个闭合路径并填充则称为覆盖图。要绘制出覆盖图,这里需要计算出六个顶点。6 个顶点可以通过最外围的六边形的 6 个顶点和中心点来计算。简单来说就是通过能力得分,在顶点到中心距离的占比来计算。计算公式如下。

详解基于 Canvas 手撸一个六边形能力图

代码如下

/**
   * 画覆盖物
   */
  function drawCover() {

    let tmpCoverPoints = _allPoints[0];
    _coverPoints = [];
    console.log("coverPoints ",tmpCoverPoints)

    let centerX = _width / 2;
    let centerY = _height / 2;
    for (let i = 0; i < tmpCoverPoints.length; i++) {
      _coverPoints.push([
        centerX + (tmpCoverPoints[i][0] - centerX) * (_data[i].score / 100.0),
        centerX + (tmpCoverPoints[i][1] - centerY) * (_data[i].score / 100.0)
      ]);
    }
    console.log("newCoverPoints ",_coverPoints)
    _context.beginPath();
    _context.fillStyle = 'rgba(90,200,250,0.4)';
    _context.moveTo(_coverPoints[5][0],_coverPoints[5][1]); //5
    for (var j = 0; j < 6; j++) {
      _context.lineTo(_coverPoints[j][0],_coverPoints[j][1]);
    }
    _context.stroke();
    _context.closePath();
    _context.fill();
  }
/**
   * 描点
   * @param pointRadius
   */

  function drawPoints(pointRadius) {
    _context.fillStyle = _color;
    for (let i = 0; i < _coverPoints.length; i++) {
      _context.beginPath();
      _context.arc(_coverPoints[i][0],_coverPoints[i][1],pointRadius,0,Math.PI*2);
      _context.closePath();
      _context.fill();
    }
  }

3.4 最后来绘制文本

绘制文本也是用的最外围的 6 个顶点的坐标。而用的 API 是 fillText(text,x,y),其中 x,y 代码文字绘制起点,但注意,不是文字所在矩形框的左上角,应该在左下角的大概位置。准确来说是文字的基线位置,这个在其他的GUI系统中也是一样,当然这里不追求那么细节了,就认为是左下角位置吧。

因此,对于不同侧的文字,其起点坐标也是不一样。如左侧的文字至少应该是左侧的顶点 x 减去文字的宽度。再比如,上下两侧的文字与顶点中相对居中对齐的,因此计算方法是 x 减去文字宽度的一半。代码的实现分为了上下左右来进行不同的绘制。

代码如下,看着有点长,但其实是很简单的。

/**
   * 绘制上侧的文字
   * @param text
   * @param pos
   */
  function drawUpText(item, pos) {
    let nameMeasure = _context.measureText(item.name);
    let scoreMeasure = _context.measureText(item.score);

    _context.fillStyle = '#8E8E8E';
    _context.fillText(item.name, pos[0] - nameMeasure.width / 2,pos[1] - 26);
    _context.fillStyle = '#212121';
    _context.fillText(item.score, pos[0] - scoreMeasure.width / 2,pos[1] - 10);

  }
/**
   * 绘制下侧的文字
   * @param text
   * @param pos
   */
  function drawDownText(item, pos) {

    let nameMeasure = _context.measureText(item.name);
    let scoreMeasure = _context.measureText(item.score);

    _context.fillStyle = '#8E8E8E';
    _context.fillText(item.name, pos[0] - nameMeasure.width / 2,pos[1] + 16);
    _context.fillStyle = '#212121';
    _context.fillText(item.score, pos[0] - scoreMeasure.width / 2,pos[1] + 32);
  }
/**
   * 绘制左侧的文字
   * @param text
   * @param pos
   */
  function drawLeftText(item, pos) {

    let nameMeasure = _context.measureText(item.name);
    let scoreMeasure = _context.measureText(item.score);

    _context.fillStyle = '#8E8E8E';
    _context.fillText(item.name, pos[0] - nameMeasure.width - 10,pos[1]);
    _context.fillStyle = '#212121';
    _context.fillText(item.score, pos[0] - 10 - (nameMeasure.width + scoreMeasure.width) / 2,pos[1] + 16);

  }
/**
   * 绘制右侧的文字
   * @param text
   * @param pos
   */
  function drawRightText(item, pos) {
    let nameMeasure = _context.measureText(item.name);
    let scoreMeasure = _context.measureText(item.score);

    _context.fillStyle = '#8E8E8E';
    _context.fillText(item.name, pos[0] - nameMeasure.width + 26,pos[1]);
    _context.fillStyle = '#212121';
    _context.fillText(item.score, pos[0] + 26 - (nameMeasure.width + scoreMeasure.width) / 2,pos[1] + 16);
  }
/**
   * 绘制所有文本
   */
  function drawText() {

    _context.fillStyle = '#8E8E8E';
    _context.strokeStyle = _color;

    let textPos = _allPoints[0];

    for (let i = 0; i < textPos.length; i++) {

      let item = _data[i];
      let pos = textPos[i];
      if(i == 0) {
        drawUpText(item, pos);
      } else if(i == 1 || i == 2) {
        drawRightText(item, pos);
      } else if(i == 3) {
        drawDownText(item, pos);
      } else if(i == 4 || i == 5) {
        drawLeftText(item, pos);
      }
    }
  }

四、总结

文章主要是基于 canvas 自定义一个六边形能力图,而这个能力的图的关键部分是对于六边形的绘制,而六边形的绘制又在于计算出 6 个顶点。有了 6 个顶点,再绘制其他的边,文本,覆盖区域等都基于这个 6 个顶点进行相应的绘制即可。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
纯css3实现走马灯效果
Dec 26 HTML / CSS
CSS3中线性颜色渐变的一些实现方法
Jul 14 HTML / CSS
浅析CSS3中鲜为人知的属性:-webkit-tap-highlight-color
Jan 12 HTML / CSS
利用css3径向渐变做一张优惠券的示例
Mar 22 HTML / CSS
IE支持HTML5的解决方法
Oct 20 HTML / CSS
html5指南-7.geolocation结合google maps开发一个小的应用
Jan 07 HTML / CSS
突袭HTML5之Javascript API扩展5—其他扩展(应用缓存/服务端消息/桌面通知)
Jan 31 HTML / CSS
详解前端HTML5几种存储方式的总结
Dec 27 HTML / CSS
浅谈HTML5新增和废弃的标签
Apr 28 HTML / CSS
canvas实现手机的手势解锁的步骤详细
Mar 16 HTML / CSS
详解canvas.toDataURL()报错的解决方案全都在这了
Mar 31 HTML / CSS
HTML基础-标签分类(闭合标签,空标签,块级元素,行内元素,行级块元素,可替换元素)
Mar 31 HTML / CSS
html+css实现自定义图片上传按钮功能
Sep 04 #HTML / CSS
Html5 Canvas 实现一个“刮刮乐”游戏
Sep 05 #HTML / CSS
canvas实现圆绘制的示例代码
Sep 11 #HTML / CSS
前端canvas水印快速制作(附完整代码)
Sep 19 #HTML / CSS
HTML5 Canvas 破碎重组的视频特效的示例代码
Sep 24 #HTML / CSS
HTML5实现的震撼3D焦点图动画的示例代码
Sep 26 #HTML / CSS
基于 HTML5 WebGL 实现的垃圾分类系统
Oct 08 #HTML / CSS
You might like
mysql 搜索之简单应用
2007/04/27 PHP
php smarty模版引擎中的缓存应用
2009/12/02 PHP
PHP 强制性文件下载功能的函数代码(任意文件格式)
2010/05/26 PHP
PHP JSON出错:Cannot use object of type stdClass as array解决方法
2014/08/16 PHP
php使用pear_smtp发送邮件
2016/04/15 PHP
php 数组随机取值的简单实例
2016/05/23 PHP
php连接微软MSSQL(sql server)完全攻略
2016/11/27 PHP
jQuery dialog 异步调用ashx,webservice数据的代码
2010/08/03 Javascript
Egret引擎开发指南之编译项目
2014/09/03 Javascript
jQuery中data()方法用法实例
2014/12/27 Javascript
JQuery基础语法小结
2015/02/27 Javascript
探究Javascript模板引擎mustache.js使用方法
2016/01/26 Javascript
Boostrap入门准备之border box
2016/05/09 Javascript
jQuery插件dataTables添加序号列的方法
2016/07/06 Javascript
webpack+vue.js快速入门教程
2016/10/12 Javascript
BootStrap 图片样式、辅助类样式和CSS组件的实例详解
2017/01/20 Javascript
基于vue+ bootstrap实现图片上传图片展示功能
2017/05/17 Javascript
详解利用jsx写vue组件的方法示例
2017/07/17 Javascript
Node.js原生api搭建web服务器的方法步骤
2019/02/15 Javascript
js节流防抖应用场景,以及在vue中节流防抖的具体实现操作
2020/09/21 Javascript
[00:06]Yes,it worked!小卡尔成功穿越时空加入战场!
2019/07/20 DOTA
Python实现完整的事务操作示例
2017/06/20 Python
Python3.7实现中控考勤机自动连接
2018/08/28 Python
Python面向对象程序设计之私有属性及私有方法示例
2019/04/08 Python
PyQt5实现从主窗口打开子窗口的方法
2019/06/19 Python
Pytorch之Tensor和Numpy之间的转换的实现方法
2020/09/03 Python
美国著名的女性内衣零售商:Frederick’s of Hollywood
2018/02/24 全球购物
Sneaker Studio波兰:购买运动鞋
2018/04/28 全球购物
Shopbop中文官网:美国亚马逊旗下时尚购物网站
2020/12/15 全球购物
TCP/IP模型的分界线
2012/12/01 面试题
中软国际Java程序员笔试题
2014/07/19 面试题
历史学专业推荐信
2013/11/06 职场文书
企业精细化管理实施方案
2014/03/23 职场文书
2015年班组长工作总结
2015/04/10 职场文书
公文写作:工伤事故分析报告怎么写?
2019/11/05 职场文书
SpringCloud项目如何解决log4j2漏洞
2022/04/10 Java/Android