About the principle of heat map:
1. First obtain the heat map data, the data is composed of xy coordinates and value
2. According to the coordinates of the heat map data, draw a circle with a gray gradient from the center to the outside on the canvas map
3. Using the principle that gray levels can be superimposed, calculate the gray value obtained by cross-overlapping each pixel data;
4. According to the gray value calculated by each pixel, color mapping is performed in a color ribbon, and finally the image is colored to obtain a heat map
The demo is as follows:
First write a heat map fake data
initdata() {
var data = [
{ x: 71, y: 77, value: 25 },
{ x: 38, y: 75, value: 97 },
{ x: 73, y: 19, value: 71 },
{ x: 72, y: 42, value: 63 },
{ x: 63, y: 95, value: 97 },
{ x: 90, y: 37, value: 34 },
{ x: 77, y: 42, value: 66 },
{ x: 171, y: 254, value: 20 },
{ x: 6, y: 82, value: 64 },
{ x: 87, y: 77, value: 14 },
{ x: 100, y: 200, value: 80 }
]
this.data = data
},
Next we create a canvas and draw a radial gradient circle on the canvas.
<template>
<div class="">
<canvas id="mycanvas" width="500" height="200" />
</div>
</template>
initcanvas() {
// 找到该canvas节点
var c = document.getElementById('mycanvas')
// 创建文本对象
var context = c.getContext('2d')
// 遍历热力图数据
this.data.forEach(point => {
// 解构赋值
const { x, y, value } = point
// 开始画圆
context.beginPath()
// 通过art方法画圆,x-起始点x坐标,y-起始点y坐标,30-圆半径,0-初始角度,2*Math.PI最终角度实际上就是360度
context.arc(x, y, 30, 0, 2 * Math.PI)
context.closePath()
// 创建渐变色: r,g,b取值比较自由,我们只关注alpha的数值,这里是径向渐变,xy都代表圆点位置,只不过一个半径是0一个半径是30
const radialGradient = context.createRadialGradient(x, y, 0, x, y, 30)
// 渐变
radialGradient.addColorStop(0.0, 'rgba(0,0,0,1)')
radialGradient.addColorStop(1.0, 'rgba(0,0,0,0)')
// 填充渐变色
context.fillStyle = radialGradient
// 设置globalAlpha: 需注意取值需规范在0-1之间,我们这里的14是value的最小值,100是value的最大值,可以自行调整
const globalAlpha = (value - 14) / (100 - 14)
context.globalAlpha = Math.max(Math.min(globalAlpha, 1), 0)
// 打印看到每个圆的灰色值是不一样的,然后每个像素叠加的地方也会累积灰度值,这就是核心原理
console.log(context.globalAlpha)
// 填充颜色
context.fill()
// 像素着色,获取我们整个画布的所有像素点的rgba
const imageData = context.getImageData(0, 0, 1000, 800)
const data = imageData.data
// rgba,a在第四个所以我们只看a
for (var i = 3; i < data.length; i += 4) {
const alpha = data[i]
// 根据a的值,来找我们彩色映射带的值,将彩色映射带的值重新赋予该canvas的imgdata
const color = this.colorPicker(alpha)
data[i - 3] = color[0]
data[i - 2] = color[1]
data[i - 1] = color[2]
}
context.putImageData(imageData, 0, 0)
})
},
// 初始化彩色映射带
inittool() {
// 定义彩色映射带的渐变值
const colorstops = {
0.2: 'rgb(0,0,255)',
0.3: 'rgb(43,111,231)',
0.4: 'rgb(2,192,241)',
0.6: 'rgb(44,222,148)',
0.8: 'rgb(254,237,83)',
0.9: '#f00',
1.0: '#f00'
}
// 这里显示彩色映射带
const canvas = document.createElement('canvas')
canvas.width = 240
canvas.height = 30
const ctx = canvas.getContext('2d')
const linearGradient = ctx.createLinearGradient(0, 0, 256, 30)
for (const key in colorstops) {
linearGradient.addColorStop(key, colorstops[key])
}
ctx.fillStyle = linearGradient
ctx.fillRect(0, 0, 240, 30)
// 我们取彩色应色带height设为1,因为多取也用不到,只需要第一行像素点的rgba
this.imageData = ctx.getImageData(0, 0, 250, 1).data
console.log(this.imageData)
document.body.appendChild(canvas)
},
Replace imgdata method
colorPicker(position) {
// 这里根据a的值也就是灰度值来截取对应彩色映射带的值,a值越小,对应的彩色映射带的值越靠前
return this.imageData.slice(position * 4, position * 4 + 3)
}
We should initialize the colormap band first
mounted() {
this.initdata()
this.inittool()
this.initcanvas()
},
The final display: