前言
在《Canvas 线性图形(五):多边形》实现了绘制多边形的函数。本篇文章将记录如何绘制雷达图。最终实现的效果是这样的:
绘制雷达图
雷达图里外层
如动图中所示,雷达图从里到外一共有 6 层,所以,我们需要改造绘制多边形的函数:
polygonPerStep
的意思是每一个多边形之间相差多少距离,radarMapTotalSides
的意思是雷达图的多边形是几边形:
let canvas = document.getElementById("radar-map");
let ctx = canvas.getContext("2d");
drawRadarMap(5, 40, 10, 300, 300, ctx);
到目前为止我们就利用绘制多边形的函数drawPolygon
成功绘制了一个雷达图的雏形:
当前的雷达图还缺少了最外层每一个点的文本,以及连接雷达图中心到最外层点的直线。
连接雷达图里外层
连接雷达图里外层时,要在绘制多边形的时候保存每一层每一个点的坐标,也就是在drawPolygon
函数中保存坐标信息。
保存点坐标
在drawRadarMap
函数里面声明一个数组axis
数组,它专门用于保存每层多边形的每个点坐标信息:
function drawRadarMap(radarLayers, polygonPerStep, radarMapTotalSides, radarX, radarY, ctx) {
let axis = []; // 用于保存每一个多边形的每一个点坐标
let radius = polygonPerStep;
for ( let j = 0; j < radarLayers; j++ ) {
drawPolygon(radarMapTotalSides, radius, radarX, radarY, axis, j, ctx);
radius = radius + polygonPerStep;
}
}
从顺时针开始的第一个点的坐标到最后一个点坐标,以及这些点坐标在第几层多边形上:
function drawPolygon(radarMapTotalSides, radius, radarX, radarY, axis, currentPolygonLayer, ctx) {
let averageAngle = Math.PI * 2 / sides;
let increaseAngle = 0;
let targetX, targetY;
ctx.beginPath();
axis.push({ layer: currentPolygonLayer, coords: [] }); // 保存点坐标的数组,
for ( let i = 0; i < radarMapTotalSides; i++ ) {
targetX = calcPolygonX(radarX, radius, increaseAngle);
targetY = calcPolygonY(radarY, radius, increaseAngle);
ctx.lineTo(targetX, targetY);
increaseAngle += averageAngle;
axis[currentPolygonLayer].coords.push({ x: targetX, y: targetY }); // 添加点坐标到数组中
}
ctx.closePath();
ctx.stroke();
}
drawPolygon
函数新增了两个参数:axis 和 currentPolygonLayer。axis 就是保存没层多边形点的坐标数组;currentPolygonLayer 就是当前多边形在第几层多边形,比如第一层就是 0。
绘制直线函数
上面的工作是点坐标,目的就是连接雷达图中心点到最外层多边形的每一个点。所以,在这里我们新增一个函数,这个函数专门处理连接直线的函数drawStria
:
function drawStria(radarLayers, axis, radarX, radarY, ctx) {
let coords = axis[axis.length - 1].coords;
for ( let i = 0; i < radarLayers; i++ ) {
ctx.beginPath();
ctx.moveTo(radarX, radarY);
ctx.lineTo(coords[i].x, coords[i].y);
ctx.closePath();
ctx.stroke();
drawPointText(data, coords, i, radarX, ctx);
}
}
axis[length - 1].coords
代表雷达图中最外层的多边形的所有点坐标。顺时针遍历其中元素,ctx.lineTo(coords[i].x, coords[i].y)
从圆点开始依次连接最外层的多边形的点,从而构成一条条直线。
绘制雷达图外层文本
在雷达图最外层的多边形的每一个点添加文本,表示直线代表的是什么数据。
function drawPointText(data, axis, currentPoint, radarX, ctx) {
ctx.font = `16px Georgia`;
if ( axis[i].x <= radarX ) {
ctx.textAlign = "right";
} else {
ctx.textAlign = "left";
}
ctx.fillText(data[currentPoint].title, axis[currentPoint].x, axis[currentPoint].y);
}
对于这个函数的几个参数讲解:
- data:顺时针开始最外层每一个点的文本;
- axios:最外层多边形每一个点的坐标信息;
- currentPoint:当前循环到的多边形的一个点坐标;
- radarX:雷达图中心坐标的 x 坐标轴。
在drawStria
函数循环体内调用该函数完成最外层的文本渲染:
绘制数据区域
接下来就是雷达图最重要的部分了。雷达图中每一条直线上该文本所达到的值在此直线上进行移动,连接这些点构成一块区域,就是数据区域。
需要为数据函数提供一个data
,这个是数据区域的信息,最外层多边形每一个点对应的值:
以上就是绘制数据区域的函数的完整代码。drawDataAreaTop
是圈画数据区域的函数,而drawDataArea
完成最后的颜色填充工作。drawDataAreaTop
接收以下几个参数:
- axis:顺时针开始最外层每一个点的文本;
- currentPoint:当前循环到的多边形的一个点坐标;
- radarX:雷达图中心坐标的 x 坐标轴;
- radarY:雷达图中心坐标的 y 坐标轴。
这里需要特别说明data[currentPoint].star - 1
,因为 star 是从 0 开始,最大值为 5,必须要减 1,不然数组索引值越界。
调用雷达图函数
在绘制雷达图的函数下面添加drawStria
和drawDataArea
两个函数,完整一个完整的雷达图绘制
function drawRadarMap(radarLayers, polygonPerStep, radarMapTotalSides, radarX, radarY, ctx) {
// ...
// ...
drawStria(radarMapTotalSides, axis, radarX, radarY, ctx);
drawDataArea(radarMapTotalSides, axis, radarX, radarY, data, ctx);
}
drawRadarMap(5, 40, 10, 300, 300, ctx);
雷达图浮动面板
这一节是扩展雷达图的功能,当鼠标浮在数据区域节点之上时,就出现一个浮动面板,展示具体的数据信息。浮动面板和雷达图被包裹在一个 div 标签里,并设置为相对定位,浮动面板设置为绝对定位。
<div id="radar-wrap">
<canvas id="radar-map" width="400" height="400">Your browser version is too late.</canvas>
<div id="floating-panel"></div>
</div>
接下来就是最重要的 JS 代码,这里为了方便控制样式,我就用 JQuery 来实现。这里需要改造一下drawDataArea
函数,我们要保存数据区域每一个点的坐标。