可视化效果之热力图

811 阅读2分钟

热力图同样是可视化应用中常见手段之一。各种热力图的解决方案数不胜数,本文博主从原理的角度,利用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);

效果: heatmap.jpg

具体可以查看我的github仓库。