【Canvas】绘制风速热力图
前言
?📫 大家好,我是南木元元,热衷分享有趣实用的文章,希望大家多多支持,一起进步!
?🍅?个人主页:南木元元
目录
风速热力图
开始之前,大家先来了解一下风速热力图的概念。
风速热力图:用于显示区域内风速的分布情况。它通过在图表上使用颜色编码来表示风速的强度,可以更好地展示风速的变化规律。
风速热力图的特点就是用不同的颜色来表示风速大小,比如较高的风速会用较深的颜色表示,而较低的风速则用较浅的颜色表示。
前期工作
在上篇文章中,我们已经使用canvas绘制出了风场的空间分布图,效果如下:
本文就来聊一下如何在风场上叠加一个风速热力图,最终的效果如下:
和之前相比,还多了一个最左边的色卡,色卡用于表示不同数值和颜色的映射关系。
数据准备
绘制色卡需要准备两组数据:
- 数值列表
- 颜色值列表
//存储色卡数据
let heatmapLegend = {
colorList: [],
valueList: []
};
//数值列表
heatmapLegend.valueList = [6, 7, 8, ..., 65];
//颜色值列表
heatmapLegend.colorList = [[ 0, 0, 127, 255 ], [ 0, 0, 160, 255 ], [ 0, 0, 194, 255 ], ..., [ 160, 0, 0, 255 ]];
上述两个数组中的数据是一一对应的关系,如数值6对应rgba颜色值[ 0, 0, 127, 255 ]。
还需要的当然是网格点上的风速数据了,如下:
[
8.9, 10.3, 11.1, 13.6, 16.4, 17.8, 19.6, 16.6, 13.9, 14,
4.6, 8.1, 10.6, 12.6, 12.6, 18.1, 18, 20.1, 23, 27.2,
...
]
我们的网格是10 * 12的维度,所以是120个格点数据。
数据稀疏问题
其实目前还存在一个问题,就是当前的数据比较稀疏和分散,如果直接进行数值到颜色的映射,那么只会在每个网格点上形成颜色点,无法渲染出一个平滑的热力图,这就需要我们进行相应的插值,来得到比较密集的点。
什么是插值呢?
简单来说,插值指利用已知的点来“猜”未知的点。
双线性插值
我们这里采取双线性插值算法,思路也很简单,就是在两个方向分别进行一次线性插值。
双线性插值的目的就是为了得到密集的数据,从而渲染出高精度、平滑的热力图。具体的插值过程不是本文的重点,这里就不再详细展开,感兴趣的可以去看一篇文章为你讲透双线性插值。
绘制色卡
绘制色卡可以分成两部分,一部分是渲染左侧的颜色列表,另一部分是渲染右侧的文字和背景区。大体思路就是循环遍历颜色列表,使用fillRect填充矩形的方式去渲染每个颜色方块,循环遍历数值列表,使用fillText绘制文字的方式去绘制字体。
// 色卡配置项
let colorCard = {
posX: 10,//色卡起始x坐标
posY: 20,//色卡起始y坐标
width: 5,//每个方块宽度
height: 5//每个方块高度
}
// 渲染左侧颜色列表
for (let i = 0; i < this.heatmapLegend.colorList.length; i++) {
this.ctx.fillStyle =
"rgba(" +
this.heatmapLegend.colorList[i][0] +
"," +
this.heatmapLegend.colorList[i][1] +
"," +
this.heatmapLegend.colorList[i][2] +
"," +
this.heatmapLegend.colorList[i][3] +
")";
this.ctx.fillRect(
colorCard.posX,
colorCard.posY + colorCard.height * i,
colorCard.width,
colorCard.height
);
}
// 渲染右侧文字背景区
this.ctx.fillStyle = "rgb(0, 0, 0)";
this.ctx.fillRect(
colorCard.posX + colorCard.width,
colorCard.posY,
colorCard.width,
colorCard.height * this.heatmapLegend.colorList.length
);
// 渲染右侧文字
this.ctx.fillStyle = "rgb(255, 255, 255)";
this.ctx.font = 5 + "px Microsoft YaHei";
for (let i = 0; i < this.heatmapLegend.valueList.length; i++) {
this.ctx.fillText(
this.heatmapLegend.valueList[i],
colorCard.posX + (3 * colorCard.width) / 2,
colorCard.posY + colorCard.height * i
);
}
下图就是我们渲染出的色卡:
绘制热力图
在正式绘制热力图之前,我们需要了解下canvas的ImageData对象。
ImageData对象
MDN上的定义。
ImageData描述canvas元素的一个隐含像素数据的区域。用于表示画布上的像素数据,可以通过获取和设置像素值来进行图像处理。
它有三个属性:
- data:描述了一个一维数组,包含以 RGBA 顺序的数据,数据使用
0~
255
的整数表示。 - width:ImageData的宽度
- height:ImageData的高度
接下来看看如何使用它,现在我们想要实现下面的效果。
?代码如下:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
//创建一个新的、空白的100*100的ImageData对象
let imgData = ctx.createImageData(100, 100);
//每个像素都用红色去填充
for (let i = 0; i < imgData.data.length; i += 4) {
? imgData.data[i+0] = 255;
? imgData.data[i+1] = 0;
? imgData.data[i+2] = 0;
? imgData.data[i+3] = 255;
}
ctx.putImageData(imgData,10,10);
上述代码创建了一个100*100像素的ImageData对象,每个像素被设置为红色,是不是很简单。
获取颜色列表
看了上面的例子,不难发现,绘制热力图,我们只需要得到每个像素的颜色值,然后去填充即可。
//存储ImageData对象的每个像素对应的颜色
let colorValue = [];
getColorValue() {
//data为插值后的风速数据
for (let i = 0; i < data.length; i++) {
let color = this.getColor(data[i]);
colorValue.push(color[0]);
colorValue.push(color[1]);
colorValue.push(color[2]);
colorValue.push(color[3]);
}
}
//获取对应数值的颜色值
getColor(value) {
let length = heatmapLegend.valueList.length;
let color = [255, 255, 255, 0];
//超过数值列表中最大值的数值为该最大值
if (value >= heatmapLegend.valueList[length - 1]) {
return heatmapLegend.colorList[length - 1];
//小于数值列表中最小值的数值为0
} else if (value < heatmapLegend.valueList[0]) {
return color;
}
//根据值去查找颜色
for (let i = 0; i < length; i++) {
if (value < heatmapLegend.valueList[i]) {
color = heatmapLegend.colorList[i];
break;
}
}
return color;
}
通过上述代码,我们可以得到大致如下的数据:
[0, 0, 227, 255, 0, 6, 227, 255, ...]
填充像素
最后,遍历上述数组,用对应颜色的像素去填充我们新建的ImageData对象就可以了。
//绘制热力图
drawHeatmap() {
// 热力图的宽高为整个canvas的宽高减去边上一圈格子的宽高
let heatmapWidth = this.canvas2d.width - 2 * this.offsetX;
let heatmapHeight = this.canvas2d.height - 2 * this.offsetY;
let imgData = this.ctx.createImageData(heatmapWidth, heatmapHeight);
for (let i = 0; i < imgData.data.length; i += 4) {
imgData.data[i + 0] = colorValue[i + 0];
imgData.data[i + 1] = colorValue[i + 1];
imgData.data[i + 2] = colorValue[i + 2];
imgData.data[i + 3] = colorValue[i + 3];
}
this.ctx.putImageData(imgData, this.offsetX, this.offsetY);
}
至此,热力图就已经绘制完成了:
再叠加上之前绘制的风场就可以得到我们想要的最终效果了。
结语?
本文分享了如何使用canvas来绘制色卡和风速热力图,以及整个过程中所涉及到的数据处理。
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、?收藏、??评论,支持一下博主~?
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!