关于热力图的原理:
1、首先获取热力图数据,数据构成为xy坐标和value值
2、根据热力图数据的坐标在canvas图上绘制一个个从中心向外灰度渐变的圆
3、利用灰度可以叠加的原理,计算每个像素点数据交叉叠加得到的灰度值;
4、根据每个像素计算得到的灰度值,在一条彩色色带中进行颜色映射,最后对图像进行着色,得到热力图
demo如下:
首先写一个热力图假数据
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 },
接下来我们创建一个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) },
替换imgdata方法
colorPicker(position) { // 这里根据a的值也就是灰度值来截取对应彩色映射带的值,a值越小,对应的彩色映射带的值越靠前 return this.imageData.slice(position * 4, position * 4 + 3) }
我们应该先初始化彩色映射带
mounted() { this.initdata() this.inittool() this.initcanvas() },
最终显示: