成品演示

绘制雷达图

雷达图里外层

function calcPolygonX(radarX, radius, increaseAngle) {
return radarX + radius * Math.cos(increaseAngle);
} function calcPolygonY(radarY, radius, increaseAngle) {
return radarY - radius * Math.sin(increaseAngle);
} /**
* @param radarMapTotalSides 雷达图有多少面,5 就是五面
* @param radius 每一层圆的半径长度,步数
* @param radarX 雷达图圆心在 Canvas 的 x 轴坐标
* @param radarY 雷达图圆心在 Canvas 的 y 轴坐标
* @param ctx Canvas 对象
*/
function drawPolygon(radarMapTotalSides, radius, radarX, radarY, ctx) {
let averageAngle = (Math.PI * 2) / radarMapTotalSides;
let increaseAngle = 0;
let targetX, targetY; ctx.beginPath();
for (let i = 0; i < radarMapTotalSides; i++) { // 绘制当前层的面
targetX = calcPolygonX(radarX, radius, increaseAngle);
targetY = calcPolygonY(radarY, radius, increaseAngle);
ctx.lineTo(targetX, targetY);
increaseAngle += averageAngle;
}
ctx.closePath();
ctx.stroke();
} /**
* @param radarLayers 雷达图层数
* @param polygonPerStep 每一层圆的半径长度,步数
* @param radarMapTotalSides 雷达图有多少面,5 就是五面
* @param radarX 雷达图圆心在 Canvas 的 x 轴坐标
* @param radarY 雷达图圆心在 Canvas 的 y 轴坐标
* @param ctx Canvas 对象
*/
function drawRadarMap(radarLayers, polygonPerStep, radarMapTotalSides, radarX, radarY, ctx) {
let radius = polygonPerStep; for (let j = 0; j < radarLayers; j++) { // 绘制 radarLayers 层的雷达图
drawPolygon(radarMapTotalSides, radius, radarX, radarY, ctx);
radius = radius + polygonPerStep;
}
}

通过以上代码,就可以绘制雷达图的基本框架,基于此基础之上,我们可以继续往下完善:

连接雷达图里外层

第一层的面与面之间的交界处与第二层的相连就可以形成一条线,而我们雷达图上的每一个节点就是处于这一条线上的。

保存点坐标

drawPolygon()函数是绘制雷达图的主要函数,函数里面循环了我们指定多少层雷达图,每一层雷达图的每一个面与面之间的交界处,即点的坐标都需要被保存,才能够在我们后续过程中为雷达图添加连线。

drawRadarMap()函数中声明一个 axis 数组,保存雷达图每一层的交界点的 x、y 坐标信息。下面的代码演示中省略了重复的代码:

function drawPolygon(radarMapTotalSides, currentPolygonLayer, radius, radarX, radarY, axis, ctx) {
// ... ctx.beginPath();
axis.push({ layer: currentPolygonLayer, coords: [] }); // 保存点坐标的数组
for (let i = 0; i < radarMapTotalSides; i++) { // 绘制当前层的面
// ...
axis[currentPolygonLayer].coords.push({ x: targetX, y: targetY }); // 添加点坐标到数组中
} ctx.closePath();
ctx.stroke();
} function drawRadarMap(radarLayers, polygonPerStep, radarMapTotalSides, radarX, radarY, ctx) {
let axis = []; // 用于保存每一个多边形的每一个点坐标
// ...
for (let j = 0; j < radarLayers; j++) { // 绘制 radarLayers 层的雷达图
drawPolygon(radarMapTotalSides, j, radius, radarX, radarY, axis, ctx);
// ...
}
}

axis 的数组元素是一个对象,对象包含两个字段:layer 和 coords。layer 代表当前的雷达图索引,即第几层;coords 保存当前层数的每一个交界点的 x、y 坐标轴信息。

绘制直线函数

有了每一层的交界点的点坐标信息,我们就可以从里到外依次连线了。连线只需要最外层的交界点坐标信息,把圆心到每一个交界点连成一条直线。专门写一个函数drawStria

function drawStria(radarMapTotalSides, axis, radarX, radarY, ctx) {
let coords = axis[axis.length - 1].coords;
for (let i = 0; i < radarMapTotalSides; i++) {
ctx.beginPath();
ctx.moveTo(radarX, radarY);
ctx.lineTo(coords[i].x, coords[i].y);
ctx.closePath();
ctx.stroke();
}
} function drawRadarMap(radarLayers, polygonPerStep, radarMapTotalSides, radarX, radarY, ctx) {
let axis = []; // 用于保存每一个多边形的每一个点坐标
// ...
for (let j = 0; j < radarLayers; j++) { // 绘制 radarLayers 层的雷达图
drawPolygon(radarMapTotalSides, j, radius, radarX, radarY, axis, ctx);
// ...
}
drawStria(radarMapTotalSides, axis, radarX, radarY, ctx);
}

axis[length - 1].coords代表雷达图中最外层的多边形的所有点坐标。顺时针遍历其中元素,ctx.lineTo(coords[i].x, coords[i].y)从圆点开始依次连接最外层的多边形的点,从而构成一条条直线。

绘制雷达图外层文本

每一条直线都代表一个量化的数据,跟柱状图一样,底部都有一个标题表示什么量化的数据:

/**
* @param data 顺时针开始最外层每一个点的文本;
* @param axios 最外层多边形每一个点的坐标信息;
* @param currentPoint 当前循环到的多边形的一个点坐标;
* @param radarX 雷达图中心坐标的 x 坐标轴。
*/
function drawPointText(axis, currentPoint, radarX, ctx, data) {
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);
}

从这里开始涉及到数据了,这里开始提供数组:

点击查看代码
let dataArea = [
{
title: "js",
star: 4
},
{
title: "ts",
star: 2
},
{
title: "html",
star: 4
},
{
title: "css",
star: 4
},
{
title: "vue",
star: 4
},
{
title: "uniapp",
star: 4
},
{
title: "java",
star: 2
},
{
title: "flutter",
star: 3
},
{
title: "dart",
star: 4
},
{
title: "python",
star: 0
}
];

drawPointText()也是需要我们前面保存的交界点的坐标信息,而文本只需要存在于最外层的坐标信息。这个函数应该放在drawStria()函数内的 for 循环里面:

function drawStria(radarMapTotalSides, axis, radarX, radarY, ctx, data) {
let coords = axis[axis.length - 1].coords;
for (let i = 0; i < radarMapTotalSides; i++) {
// ...
drawPointText(coords, coords[i], radarX, radarY, ctx, data);
}
}

注意:从这里开始,函数又需要新增 data 参数,从最上层往下依次传递这个 data,请自行添加。

绘制数据区域

每一条直线与最外层的交界点相连的点,我称之为数据点。这些数据点又相连构成一个闭合的数据区域,再填充一些颜色,这一项工作完成雷达图就完整了。

function calcDataAreaTopX(areaTopLayer, axis, radarX, currentPoint) {
if (areaTopLayer < 0) {
return radarX;
} else {
return axis[areaTopLayer].coords[currentPoint].x;
}
} function calcDataAreaTopY(areaTopLayer, axis, radarY, currentPoint) {
if (areaTopLayer < 0) {
return radarY;
} else {
return axis[areaTopLayer].coords[currentPoint].y;
}
} function drawDataAreaTop(axis, currentPoint, radarX, radarY, ctx, data) {
let x = calcDataAreaTopX(data[currentPoint].star - 1, axis, radarX, currentPoint);
let y = calcDataAreaTopY(data[currentPoint].star - 1, axis, radarY, currentPoint);
if (currentPoint === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
return { x: x, y: y };
} function drawDataArea(radarMapTotalSides, axis, radarX, radarY, ctx, data) {
let areaTopAxis = []; // 数据区域的所有点坐标
ctx.beginPath();
for (let i = 0; i < radarMapTotalSides; i++) {
let { x, y } = drawDataAreaTop(axis, i, radarX, radarY, ctx, data);
areaTopAxis.push({ title: data[i].title, star: data[i].star, x: x, y: y });
}
ctx.closePath();
ctx.strokeStyle = "rgba(68,226,155, 1)";
ctx.stroke();
ctx.fillStyle = "rgba(81,182,137, 0.6)";
ctx.fill();
return areaTopAxis;
}

这里需要特别说明data[currentPoint].star - 1,因为 star 是从 0 开始,最大值为 5,必须要减 1,不然数组索引值越界。drawDataArea()将返回数据区域的顶点坐标。drawDataArea有以下五个参数:

  1. axis:顺时针开始最外层每一个点的文本;
  2. currentPoint:当前循环到的多边形的一个点坐标;
  3. radarX:雷达图中心坐标的 x 坐标轴;
  4. radarY:雷达图中心坐标的 y 坐标轴。
  5. data: 数据源
function drawRadarMap(radarLayers, polygonPerStep, radarMapTotalSides, radarX, radarY, ctx, data) {
// ...
// ...
let topCoords = drawDataArea(radarMapTotalSides, axis, radarX, radarY, ctx, data);
}

雷达图浮动面板

本小节是对雷达图的一个扩展内容,实现鼠标悬浮在数据点至上时可以显示详细信息的功能:

<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>
点击查看 CSS 代码
#radar-map {
cursor: pointer;
position: absolute;
border: 1px solid rgba(110, 110, 110, 0.8);
border-radius: 10px;
} #radar-wrap {
width: 400px;
height: 400px;
box-sizing: border-box;
position: relative;
} #floating-panel {
position: absolute;
display: none;
border-style: solid;
white-space: nowrap;
z-index: 9999999;
transition: left 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s, top 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s;
background-color: rgba(50, 50, 50, 0.7);
border-width: 0;
border-color: rgb(51, 51, 51);
border-radius: 4px;
color: rgb(255, 255, 255);
font: 14px / 21px "Microsoft YaHei";
padding: 5px;
left: 29px;
top: 145px;
pointer-events: none;
}

鼠标停留在数据区域的顶点时,出现该点的详细信息,而顶点的坐标信息由drawDataArea函数提供。在下面的这个函数中,只需要遍历坐标信息是否接近于我们鼠标的 offsetX 和 offsetY 区域就显示详细信息:

function drawFloatingPanel(axis) {
let floatingPanel = $("#floating-panel");
let timeout = null;
$("#radar-map").on({
mousemove: function (e) {
if (timeout != null) clearTimeout(timeout);
timeout = setTimeout(() => {
axis.forEach((value, index) => {
if (value.x >= e.offsetX - 5 && value.x < e.offsetX + 5 && value.y >= e.offsetY - 5 && value.y < e.offsetY + 5) {
$(floatingPanel).css({
display: "block",
left: `${e.offsetX}px`,
top: `${e.offsetY}px`
});
$(floatingPanel).empty().append(`
<div class="tech">技术:${value.title}</div>
<div class="star">掌握程度:${value.star} 颗星</div>
`);
}
});
}, 50);
},
mouseleave: function (e) {
$(floatingPanel).css({ display: "none" });
}
});
}

GitHub 仓库

  1. 可配置的雷达图
  2. 雷达图的基础代码
  3. 在线浏览雷达图

01#Web 实战:雷达图的更多相关文章

  1. [2014.01.27]wfRadar 雷达图组件 2.5

    全新开发的雷达图组件--wfRadar,使用简单,功能强大,图像处理效果极佳. 组件支持多种图片格式,包括bmp,jpg,gif,wmf,emf,ico,png,pcx,tif,tga,pcx,dcx ...

  2. Web 前端实战(三):雷达图

    前言 在<Canvas 线性图形(五):多边形>实现了绘制多边形的函数.本篇文章将记录如何绘制雷达图.最终实现的效果是这样的: 绘制雷达图 雷达图里外层 如动图中所示,雷达图从里到外一共有 ...

  3. Web 前端实战:雷达图

    前言 在Canvas 线性图形(五):多边形实现了绘制多边形的函数.本篇文章将记录如何绘制雷达图.最终实现的效果是这样的: 绘制雷达图 雷达图里外层 如动图中所示,雷达图从里到外一共有 6 层,所以, ...

  4. HTML5 Canvas制作雷达图实战

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

  5. asp.net comp雷达图

    <system.web> <httpHandlers> <add path="ChartImg.axd" verb="GET,HEAD,PO ...

  6. konva canvas插件写雷达图示例

    最近,做了一个HTML5的项目,里面涉及到了雷达图效果,这里,我将react实战项目中,用到的雷达图单拎出来写一篇博客,供大家学习. 以下内容涉及的代码在我的gitlab仓库中:Konva canva ...

  7. 数据可视化基础专题(十二):Matplotlib 基础(四)常用图表(二)气泡图、堆叠图、雷达图、饼图、

    1 气泡图 气泡图和上面的散点图非常类似,只是点的大小不一样,而且是通过参数 s 来进行控制的,多的不说,还是看个示例: 例子一: import matplotlib.pyplot as plt im ...

  8. Python绘制雷达图(俗称六芒星)

    原文链接:https://blog.csdn.net/Just_youHG/article/details/83904618 背景 <Python数据分析与挖掘实战> 案例2–航空公司客户 ...

  9. 帆软报表(finereport)雷达图钻取详细点新页面展示

    添加参数栏,季度下拉框的空间名为combobox0 添加雷达图,通过第三页面做跳转 雷达图钻取.cpt为联动钻取的第三页面 添加纬度(所点击钻取的点) 参数   wd 添加季度参数 jd    值为季 ...

  10. WPF DevExpress 设置雷达图Radar样式

      DevExpress中定义的ChartControl很不错,很多项目直接使用这种控件. 本节讲述雷达图的样式设置 <Grid> <Grid.Resources> <D ...

随机推荐

  1. 【每日一题】【map存值】2022年2月25日-NC112 进制转换

    描述给定一个十进制数 M ,以及需要转换的进制数 N .将十进制数 M 转化为 N 进制数. 当 N 大于 10 以后, 应在结果中使用大写字母表示大于 10 的一位,如 'A' 表示此位为 10 , ...

  2. STM32标准库中GPIO_ReadInputData与GPIO_ReadInputDataBit的区别

    GPIO_ReadInputData读的是GPIOx的整个IDR寄存器的数据,返回一个十六位数,对应IDR寄存器的十六位.反映GPIOx所有端口的电平状态,所以参数只用传入GPIOx. uint16_ ...

  3. 手把手教你一套完善且高效的k8s离线部署方案

    作者:郝建伟 背景 面对更多项目现场交付,偶而会遇到客户环境不具备公网条件,完全内网部署,这就需要有一套完善且高效的离线部署方案. 系统资源 编号 主机名称 IP 资源类型 CPU 内存 磁盘 01 ...

  4. Jmeter 之提取的值为null时,if控制器中的判断表达式

    场景:当level的值为null时则执行 {"code":0, "msg":null, "data": [ { "level&qu ...

  5. uniapp微信小程序 选择日期时间

    一.根据需要点击选择时间日期,效果如下图: (1)新建一个dateTimePicker.js文件 function withData(param){ return param < 10 ? '0 ...

  6. vivo 低代码平台【后羿】的探索与实践

    作者:vivo 互联网前端团队- Wang Ning 本文根据王宁老师在"2022 vivo开发者大会"现场演讲内容整理而成.公众号回复[2022 VDC]获取互联网技术分会场议题 ...

  7. 使用 Helm 安装 MQTT 服务器-EMQX

    EMQX ️ Info: 使用 EMQX 通过 Helm3 在 Kubernetes 上部署 EMQX 4.0 集群 | EMQ emqx/deploy/charts/emqx at main-v4. ...

  8. Hadoop详解(01)-概论

    Hadoop详解(01)概论 概念 大数据(Big Data):指无法在一定时间范围内用常规软件工具进行捕捉.管理和处理的数据集合,是需要新处理模式才能具有更强的决策力.洞察发现力和流程优化能力的海量 ...

  9. VSCode运行C/C++配置

    将MinGw安装目录下的 1.安装 VSCode 2.安装 MinGW 链接:点击跳转 3.MinGW 内安装两个模块 1.右键 Mark for Installation 勾选 (此处已安装好,所以 ...

  10. python进阶之路13 二分法 三元表达式 各种生成式 匿名函数

    算法简介及二分法 1.什么是算法 算法就是解决问题的有效方法 不是所有的算法都很高效也有不合格的算法 2.算法应用场景 推荐算法(抖音视频推送 淘宝商品推送) 成像算法(AI相关)...... 几乎涵 ...