1. 基本概念
1.1 热力图是什么
热力图是一种常用的数据可视化方式,它通过颜色编码的方式表示数据在二维平面上的分布情况。热力图主要有以下特点:
-
数据呈现方式:
- 热力图通常以二维矩阵的形式呈现数据,每个单元格代表一个数据点。
- 每个单元格的颜色深浅表示该位置数据值的大小,颜色越深表示数据值越大。
-
应用场景:
- 热力图适用于表示二维平面上的数据分布,如地图上的人口密度、销售情况等。
- 也可用于表示任意二维数据,如网页点击热力图、产品使用热力图等。
-
制作方式:
- 热力图通常由专业的数据可视化工具如 D3.js、Echarts、Tableau等生成。
- 这些工具提供丰富的配色方案和交互功能,用户可根据需求进行定制。
-
信息呈现:
- 热力图能直观地展示数据在二维空间的分布特点,如数据密集区域、高低值区域等。
- 通过颜色编码,可快速识别数据值的相对大小关系。
总的来说,热力图是一种有效的数据可视化方式,能帮助用户更好地理解和分析二维数据的分布特征。它广泛应用于各行各业,是数据分析和决策支持的重要工具
1.2 热力图插件选择
我们选择heatmap.js官网来生成热力效果,这是一个专注于热力图的轻量级Javascript可视化库。上手简单,适合简单的热力图需求。heatmap代码仓github,这个项目的代码最近的更新时间是八年前……, 但是目前依旧有大量的项目选择它作为热力图的首选插件。
2. 场景搭建
2.1 底图数据
构成底图的元素有一组json数据,然后由正方形的平面拼接而成,平面间隔为单位1,在每个平面上,展示对应的value值。
json数据我们这里只展示一层,现实情况可能是多个层的展示,数据及格式如下所示:
const data = [
{
"data": {
"1": [14,22,16,27,18,13,45,45,22,22,15,31],
"2": [29,26,22,15,14,31,45,18,12,11,15,31],
"3": [17,19,21,15,11,22,25,12,27,32,15,31],
"4": [31,14,23,17,16,24,19,21,10,12,15,31]
}
}
]
2.2 字体加载
首先要引入
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
FontLoader 负责加载字体文件并返回 Font 对象,TextGeometry 则利用该 Font 对象生成 3D 文本几何体。通过结合使用这两个组件,可以在 ThreeJS 场景中渲染 3D 文本。
- FontLoader:
FontLoader用于加载字体文件,支持常见的字体格式,如.ttf、.otf等。- 加载字体文件需要一定的时间,因此通常将其放在异步加载的过程中。
- 字体加载完成后,会返回一个
Font对象,包含了该字体的相关信息,如字形、基线等。 - 示例代码如下:
const fontLoader = new THREE.FontLoader();
fontLoader.load('path/to/font.json', (font) => {
// 使用 font 对象创建 TextGeometry
});
- TextGeometry:
-
TextGeometry是一个 3D 几何体,用于表示由字体渲染而成的文本。 -
创建
TextGeometry需要提供以下参数:text: 要渲染的文本内容parameters: 一个对象,包含以下属性:font: 加载完成的Font对象size: 文本的大小height: 文本的厚度curveSegments: 曲线细分程度bevelEnabled: 是否启用斜角bevelThickness: 斜角厚度bevelSize: 斜角大小
-
创建
TextGeometry的示例代码如下:
-
const textGeometry = new THREE.TextGeometry('Hello, ThreeJS!', {
font: font,
size: 0.5,
height: 0.2,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02
});
创建好 TextGeometry 后,可以像使用其他几何体一样进行渲染和材质设置。
//层间距
const layerGap = 2;
//得到所有的层级数目
const layerCount = data.length;
let layerGroup = [];
//为每个层级创建一个group
for(let i = 0; i < layerCount; i++) {
layerGroup[i] = new THREE.Group();
initLayer(i);
}
//初始化每一层的数据
function initLayer(currentLayer) {
let points = [];
//获取第一层的数据
const layerData = data[currentLayer].data;
//获取第一层的行和列
const layer1Row = Object.keys(layerData).length;
const layer1Col = Object.values(layerData)[0].length;
//从data中获取数据
for(let row = 0; row < layer1Row; row++) {
for(let col = 0; col < layer1Col; col++) {
let temperature = layerData[row + 1][col];
points.push({ x: col + 1,y: row + 1,value: temperature })
let color = 0x158fec
let geometry = new THREE.PlaneGeometry(2,2);
let material = new THREE.MeshBasicMaterial({ color,side: THREE.DoubleSide });
let plane = new THREE.Mesh(geometry,material);
plane.position.set(col * 2.2 + 1 - layer1Col * 1.1,row * 2.2 + 1 - layer1Row * 1.1,currentLayer * layerGap);
let textMesh
new FontLoader().load('https://esm.sh/three@0.166.1/examples/fonts/gentilis_regular.typeface.json',font => {
const textGeometry = new TextGeometry(temperature.toString(),{
font: font,
size: 0.5,
depth: 0.01,
curveSegments: 0.12,//指定曲线的分段数,这会影响文本曲线的光滑程度。分段数越大,曲线越光滑
bevelEnabled: false,//指定是否启用斜角(bevel),即是否给文本添加倒角效果
});
const textMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
textMesh = new THREE.Mesh(textGeometry,textMaterial)
//更改文字在z轴上的位置
textMesh.position.z = 0.1 + currentLayer * layerGap; // 将文本位置设置为2
textMesh.position.x = col * 2.2 - layer1Col * 1.1 + 0.35;
textMesh.position.y = row * 2.2 - layer1Row * 1.1 + 0.5;
layerGroup[currentLayer].add(textMesh)
})
layerGroup[currentLayer].add(plane)
}
}
scene.add(layerGroup[currentLayer])
}
3. 上热力图
3.1 热力图的引入
方式一(通过npm方式安装依赖包):
安装
npm install heatmap.js
引用
import h337 from 'heatmap.js';
使用
h337.create({
container: document.getElementById("heatmap"),
blur: '.8',
radius: 30
})
但是这种方式会报错,需要到node_module里面修改对应包的源码内容。不建议使用
方式二:本地加载
文件下载地址github.com/pa7/heatmap.js/blob/master/build/heatmap.js 将源码js移动到项目当中,再来更改源码,在文件中找到下列代码
img.data = imgData
this.ctx.putImageData(img,x,y);
this._renderBoundaries = [1000,1000,0,0];
将img.data=imgData注释掉,就解决了。
方式三:
在index.html引入
<script src="https://cdn.bootcdn.net/ajax/libs/heatmap.js/2.0.0/heatmap.min.js"></script>
在js文件中可以直接使用。
热力图实现的方式
heatmap.js生成canvas图,然后将canvas图作为贴图盖到3d场景中。有个主要的点就是canvas坐标系和3d右手坐标系的y轴是相反的。
在 ThreeJS 中,ShaderMaterial 是一种非常强大和灵活的材质类型,它允许开发者直接使用自定义的 GLSL 着色器程序来控制材质的渲染过程。我们这里定义
相关的片元着色器和顶点着色器的代码如下所示:
let fragmentShader =/*glsl*/`
varying float hValue;
varying vec3 cl;
void main() {
float v = abs(hValue - 1.);
gl_FragColor = vec4(cl, .8 - v * v) ;
}
`
let vertexShader =/*glsl*/ `
uniform sampler2D heightMap;
uniform float heightRatio;
varying vec2 vUv;
varying float hValue;
varying vec3 cl;
void main() {
vUv = uv;
vec3 pos = position;
cl = texture2D(heightMap, vUv).rgb;
hValue = texture2D(heightMap, vUv).r;
pos.z = hValue * heightRatio;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos,1.0);
}
`
用heightRatio定义贴图z方向的高度,heightMap对应canvas贴图。
//添加热力图
function TDHeatMap(points = [],row,col) {
//points要进行比例换算,x值对应的高度,y值对应的宽度,value对应的温度
//获取id为heatmap的宽度和高度
const height = document.getElementById('heatmap').clientHeight;
const width = document.getElementById('heatmap').clientWidth;
//3d贴图与2d贴图的y坐标的值是相反的
const newPoints = points.map((item) => {
let newItems = {}
newItems.x = (item.x-0.7) * width/col;
newItems.y = (4.5-item.y) * (height / (row*1.0));
newItems.value = item.value;
return newItems
})
// 热力图
var heatmap = h337.create({
container: document.getElementById("heatmap"),
blur: '.8',
radius: 30
});
var data = {
max: 40,
min: 0,
data: newPoints,
width,
height
};
heatmap.setData(data);
//使用上图的canvas作为贴图
let texture = new THREE.CanvasTexture(heatmap._renderer.canvas);
let geometry = new THREE.PlaneGeometry(col * 2.2,row * 2.2,2000,2000);
let material = new THREE.ShaderMaterial({
uniforms: {
heightMap: { value: texture },
heightRatio: { value: 2 }
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true,
});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
}
其中有段代码拎出来看下
//3d贴图与2d贴图的y坐标的值是相反的
const newPoints = points.map((item) => {
let newItems = {}
newItems.x = (item.x-0.7) * width/col;
newItems.y = (4.5-item.y) * (height / (row*1.0));
newItems.value = item.value;
return newItems
})
这里的数字0.7只是我设置的一个偏移量,让图层居中展示;4.5是取y的最大值4加上了一个0.5的偏移量。可以根据blur和radius参数,和实际情况进行微调。
4. 总结
八青妹这个案例只是一个简易的小demo,现实中使用场景比这个要复杂得多,高楼大厦也是平地起,咱先找到思路,然后对症下药。