热力图同样是可视化应用中常见手段之一。各种热力图的解决方案数不胜数,本文博主从原理的角度,利用canvas API根据摄像机距离实时实现热力图。
实现原理:生成一个和之前地球一样的Geometry,根据数据在canvas上绘制热力图,将生成的canvas做成贴图,生成材质,与Geometry结合生成Mesh,加入到Scene中即可。
需要了解canvas原生API
Arc:绘制圆弧路径
createRadialGradient:绘制圆形放射性渐变
createLinearGradient:绘制线性放射性渐变
实现过程:
确定热力图配置:
const config = {
radius: 10,
gradient: {
0.1: "rgba(0,0,255,1)",
0.3: "rgba(43,111,231,1)",
0.4: "rgba(2,192,241,1)",
0.6: "rgba(44,222,148,1)",
0.8: "rgba(254,246,104,1)",
1.0: "rgba(255,64,28,1)",
},
};
请求数据,拿到的数据点按之前介绍的进行坐标转换,使用arc方法绘制圆弧路径。
创建圆形放射性渐变,作为填充色填充近之前画的圆中。输出当前栅格图。
注:每个数据点应该具有一个value,minnum、maxnum表示强弱的阈值,value越逼近maxnum,表示作用越强。
this.context.beginPath();
const alpha = (point[2] - this.minnum) / (this.maxnum - this.minnum);
this.context.globalAlpha = alpha;
this.context.arc(
point[0],
point[1],
this.config.radius,
0,
Math.PI * 2,
true
);
const gradient = this.context.createRadialGradient(
point[0],
point[1],
0,
point[0],
point[1],
this.config.radius
);
gradient.addColorStop(0, "rgba(0,0,0,1)");
gradient.addColorStop(1, "rgba(0,0,0,0)");
this.context.fillStyle = gradient;
this.context.closePath();
this.context.fill();
按照热力图配置,生成一个线性色带
const paletteCanvas = document.createElement("canvas");
const paletteCtx: any = paletteCanvas.getContext("2d");
const gradientConfig = this.config.gradient;
paletteCanvas.width = 256;
paletteCanvas.height = 1;
const gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
for (const key in gradientConfig) {
gradient.addColorStop(parseFloat(key), gradientConfig[key]);
}
paletteCtx.fillStyle = gradient;
paletteCtx.fillRect(0, 0, 256, 1);
return paletteCtx.getImageData(0, 0, 256, 1).data;
根据线性色带为之前的栅格图像素着色
for (let i = 3; i < imageData.length; i+=4) {
const alpha = imageData[i];
const offset = alpha * 4;
if (!offset) continue;
imageData[i - 3] = palette[offset];
imageData[i - 2] = palette[offset + 1];
imageData[i - 1] = palette[offset + 2];
}
将做好的canvas做成material,绘制geometry,生成Mesh,加入Scene即可。
const heatmapMateria = new THREE.MeshBasicMaterial({
map: new THREE.CanvasTexture(heatmap.canvas),
transparent: true,
depthTest: false,
});
heatmapMesh = new THREE.Mesh(earth.geometry, heatmapMateria);
earth.add(heatmapMesh);
效果:
具体可以查看我的github仓库。