01#Web 实战:雷达图
成品演示
绘制雷达图
雷达图里外层
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
有以下五个参数:
- axis:顺时针开始最外层每一个点的文本;
- currentPoint:当前循环到的多边形的一个点坐标;
- radarX:雷达图中心坐标的 x 坐标轴;
- radarY:雷达图中心坐标的 y 坐标轴。
- 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 仓库
01#Web 实战:雷达图的更多相关文章
- [2014.01.27]wfRadar 雷达图组件 2.5
全新开发的雷达图组件--wfRadar,使用简单,功能强大,图像处理效果极佳. 组件支持多种图片格式,包括bmp,jpg,gif,wmf,emf,ico,png,pcx,tif,tga,pcx,dcx ...
- Web 前端实战(三):雷达图
前言 在<Canvas 线性图形(五):多边形>实现了绘制多边形的函数.本篇文章将记录如何绘制雷达图.最终实现的效果是这样的: 绘制雷达图 雷达图里外层 如动图中所示,雷达图从里到外一共有 ...
- Web 前端实战:雷达图
前言 在Canvas 线性图形(五):多边形实现了绘制多边形的函数.本篇文章将记录如何绘制雷达图.最终实现的效果是这样的: 绘制雷达图 雷达图里外层 如动图中所示,雷达图从里到外一共有 6 层,所以, ...
- HTML5 Canvas制作雷达图实战
雷达图又叫蜘蛛网图,是一种对各项数据查看很明显的表现图,在很多游戏中,对游戏中的每个角色的分析图一般也用这种图. 下面,用HTML5的Cavas来实现雷达图. 效果 一.创建Canvas var mW ...
- asp.net comp雷达图
<system.web> <httpHandlers> <add path="ChartImg.axd" verb="GET,HEAD,PO ...
- konva canvas插件写雷达图示例
最近,做了一个HTML5的项目,里面涉及到了雷达图效果,这里,我将react实战项目中,用到的雷达图单拎出来写一篇博客,供大家学习. 以下内容涉及的代码在我的gitlab仓库中:Konva canva ...
- 数据可视化基础专题(十二):Matplotlib 基础(四)常用图表(二)气泡图、堆叠图、雷达图、饼图、
1 气泡图 气泡图和上面的散点图非常类似,只是点的大小不一样,而且是通过参数 s 来进行控制的,多的不说,还是看个示例: 例子一: import matplotlib.pyplot as plt im ...
- Python绘制雷达图(俗称六芒星)
原文链接:https://blog.csdn.net/Just_youHG/article/details/83904618 背景 <Python数据分析与挖掘实战> 案例2–航空公司客户 ...
- 帆软报表(finereport)雷达图钻取详细点新页面展示
添加参数栏,季度下拉框的空间名为combobox0 添加雷达图,通过第三页面做跳转 雷达图钻取.cpt为联动钻取的第三页面 添加纬度(所点击钻取的点) 参数 wd 添加季度参数 jd 值为季 ...
- WPF DevExpress 设置雷达图Radar样式
DevExpress中定义的ChartControl很不错,很多项目直接使用这种控件. 本节讲述雷达图的样式设置 <Grid> <Grid.Resources> <D ...
随机推荐
- vulnhub靶场渗透实战12-driftingblues2
vbox导入,网络桥接. 靶机下载地址:https://download.vulnhub.com/driftingblues/driftingblues2.ova 一:信息收集 1:主机发现. 2: ...
- python爬虫爬取网易云音乐(超详细教程,附源码)
一. 前言 先说结论,目前无法下载无损音乐,也无法下载vip音乐. 此代码模拟web网页js加密的过程,向api接口发送参数并获取数据,仅供参考学习,如果需要下载网易云音乐,不如直接在客户端下载,客户 ...
- BUU_RE学习记录
一.easyre 1.010打开,直接搜flag,得到flag 二.reverse1 1.先查壳,得知是64位无壳,直接用IDA打开 2.shiftF12查找字符串,发现关键语句 3.查看相应代码,F ...
- 解决MVVMLight导航VM不重置问题
问题阐述:使用MVVMLight导航发现导航后VM里面的数据并未进行重置,需要界面跳转后,历史VM也进行销毁重置,并释放 解决办法: 方法一:在当前界面进行Unloaded进行VM注销并进行重新注入代 ...
- [OpenCV实战]40 计算机视觉工具对比
文章目录 1 简介 2 适用于计算机视觉的MATLAB 2.1 为什么要使用MATLAB进行计算机视觉:优点 2.2 为什么不应该将MATLAB用于计算机视觉:缺点 3 适用于计算机视觉的OpenCV ...
- JavaBean为何物?
JavaBean为何物? 摘要:初学SSM框架之后,我对JavaBean这个东西开始有了简单的接触,在很久以前听见JavaBean这个词一直以为是一个非常高大上的东西,但是在仔细研究之后发现其本质 ...
- CF1744B Even-Odd Increments
简要题意 \(T\) 组数据,每组数据给定一个长度为 \(n\) 的数列,有 \(q\) 次操作,共有两种操作: \(\texttt{0 x}\),给数列中所有偶数加上 \(x\): \(\textt ...
- 通过Google Cloud Storage(GCS)管理Terraform的状态State
管理Terraform状态文件的最佳方式是通过云端的统一的存储,如谷歌云就用GCS. 首先要创建一个Bucket: $ gsutil mb -p pkslow -l us-west1 -b on gs ...
- strapi系列-如何创建一个定时任务-Cron Jobs
Cron 是什么? Cron 有多种用途. Cron Jobs 用于安排服务器上的任务运行.它们最常用于自动化系统管理或维护.然而,它们也与 Web 应用程序的构建相关. Web 应用程序可能需要在各 ...
- 公司规定所有接口都用 post 请求,这正确么?
目录 背景 get 与 post 的区别 所有接口都用 post 请求? 背景 最近在逛知乎的时候发现一个有趣的问题:公司规定所有接口都用 post 请求,这是为什么? 看到这个问题的时候其实我也挺有 ...