成品演示

绘制雷达图

雷达图里外层

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. 【Java EE】Day02 MySQL概念、软件、语句

    〇.总结 1. 一.数据库的基本概念 1.概念 用于存储和管理数据的仓库 特点: 持久化存储,本质是文件系统 方便存储和管理数据 使用统一方式(MySQL)操作 常见的数据库软件: MySQL:Ora ...

  2. GitHub 开源了多款字体「GitHub 热点速览 v.22.48」

    本期 News 快读有 GitHub 官方大动作一下子开源了两款字体,同样大动作的还有 OpenAI 发布的对话模型 ChatGPT,引燃了一波人机对话. 项目这块,也许会成为新的 Web 开发生产力 ...

  3. [数据结构][洛谷]P3375模板题 KMP

    主要还是KMP算法,上学期没学,只是考前抱了抱佛脚,也没怎么弄明白. 先放代码: //KMP #include <bits/stdc++.h>//万能头 using namespace s ...

  4. PowerDotNet平台化软件架构设计与实现系列(14):平台建设指南

    软件开发中常见的几种不同服务模型包括SaaS(软件即服务).LaaS(许可即服务).PaaS(平台即服务).CaaS(容器即服务).IaaS(基础设施即服务)和FaaS(功能即服务). 很多人认为Ia ...

  5. python 之集合(set)

    集合是一个无序的,不允许重复的元素列表,根据这个特性,可以利用集合对列表进行去重操作 集合创建 # 集合中不能含list.dict set2 = {"rice", 1, (True ...

  6. 通过GitHub和阿里云自定义域名实现https认证

    在GitHub中的操作 登录GitHub, 点击"Your repositories",进入个人仓库页面: 点击"new",进入新建仓库页面: 仓库名称填写&l ...

  7. 结合商业项目深入理解Go知识点

    这篇文章比较硬核,爆肝5千字,把之前整理的知识点都串起来了.建议先收藏,慢慢看. 前言 上一篇文章 #[Go WEB进阶实战]开源的电商前后台API系统 很受大家欢迎,有好多小伙伴私信我问题:&quo ...

  8. Redis 中ZSET数据类型命令使用及对应场景总结

    转载请注明出处: 目录 1.zadd添加元素 2.zrem 从有序集合key中删除元素 3.zscore 返回有序集合key中元素member的分值 4.zincrby 为有序集合key中元素增加分值 ...

  9. [C++]C++11右值引用

    右值引用的概念(摘自C++Primer) 左值和右值的概念 1.左值和右值是表达式的属性,一些表达式要求生成左值,一些表达式要求生成右值:左值表达式通常是一个对象的身份,而一个右值表达式表示的是对象的 ...

  10. Java学习笔记:2022年1月6日(补充)

    Java学习笔记:2022年1月6日(补充) ​ 摘要:这篇笔记主要记录了2022年1月6日下午的笔记,主要内容为Java语言中的基础操作,以及基础知识点,了解这些后基本上就可以使用Java写算法了. ...