likes
comments
collection
share

「AntV」热力图 heatmap 绘制原理解析

作者站长头像
站长
· 阅读数 18

最近在 G2 5.0 中加上了 heatmap 热力图的 mark,让 G2 5.0 可以轻松绘制热力图,应该社区上大部分绘制热力图的库,原理基本都是类似的,可以参考 heatmap.js 源码,所以这边文档就是对于 heatmap.js 的源码解读。

什么是热力图

「AntV」热力图 heatmap 绘制原理解析

上面是一些热力图的截图效果,一般用红色来代表这个区域的数据更大,更密集。再举一个实际的例子:

「AntV」热力图 heatmap 绘制原理解析

在支付宝或者其他网站中,进场会有些热力分析,通过大量用户的交互情况,在 APP 或者网站页面上,显示出用户交互的热力图,从而可以分析出用户的操作习惯,从而对于产品功能的设计提供一些帮助和指导。

所以,热力图怎么绘制?需要让数据更大的地方显示的颜色更红,数据更小的地方,数据更绿。

绘制原理

「AntV」热力图 heatmap 绘制原理解析

以上面的这个案例,来介绍整个绘图过程的。原始的数据结构为:

[
  {
    "g": 541,
    "l": 85,
    "tmp": 858
  },
  {
    "g": 937,
    "l": 465,
    "tmp": 299
  },
  {
    "g": 566,
    "l": 131,
    "tmp": 326
  },
  // ...
]

这里将 heatmap.js 源码,它的绘制过程主要分成为几个步骤:

1. 使用散点展示数据分布

function getPoint(x: number, y: number, radius: number) {
  const tplCanvas = document.createElement('canvas');
	const tplCtx = tplCanvas.getContext('2d');
  tplCanvas.width = radius * 2;
  tplCanvas.height = radius * 2;

	const x = radius;
	const y = radius;

  tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
  tplCtx.fillStyle = 'rgba(0,0,0,1)';
  tplCtx.fill();

  return tplCanvas;
}

「AntV」热力图 heatmap 绘制原理解析

2. 追加透明度渐变

因为最终效果是一种模糊效果,所以需要使用圆弧渐变 createRadialGradient

const gradient = tplCtx.createRadialGradient(
  x,
  y,
  radius * blurFactor,
  x,
  y,
  radius,
);
gradient.addColorStop(0, 'rgba(0,0,0,1)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');

tplCtx.fillStyle = gradient;
tplCtx.fillRect(0, 0, 2 * radius, 2 * radius);

这样画出来的一系列的渐变透明的点,每个点代表着一个数据,每个点重叠在一起,让透明度包含了数据密度信息,只不过整体的是黑白的,所需要对数据进行指定色板的着色。

「AntV」热力图 heatmap 绘制原理解析

3. 按照指定的渐变色,生成色板

/**
 * Get a color palette with len = 256 base on gradient.
 * @param gradientConfig
 * @returns
 */
function getColorPalette(gradientConfig: HeatmapGradient, createCanvas) {
  const paletteCanvas = document.createElement('canvas');
  const paletteCtx = paletteCanvas.getContext('2d');

  const gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
  parseGradient(gradientConfig).forEach(([r, c]) => {
    gradient.addColorStop(r, c);
  });

  paletteCtx.fillStyle = gradient;
  paletteCtx.fillRect(0, 0, 256, 1);

  return paletteCtx.getImageData(0, 0, 256, 1).data;
}

本质很简单,就是用 createLinearGradient方法讲一个指定的色板生成为色板 rgba 数据。函数入参为以下,也是 G2 默认的色板:

[  [0.25, 'rgb(0,0,255)'],
  [0.55, 'rgb(0,255,0)'],
  [0.85, 'yellow'],
  [1.0, 'rgb(255,0,0)'],
]

「AntV」热力图 heatmap 绘制原理解析

4. 着色,让数据透明度对应到颜色

着色逻辑就很简单了,将第三步生成的色板在第二部生成的散点图中,按照像素点、透明度数据,一一对应即可。

const img = shadowCtx.getImageData(x, y, width, height);
const imgData = img.data;

for (let i = 3; i < imgData.length; i += 4) {
  const alpha = imgData[i];
  const offset = alpha * 4;
  imgData[i - 3] = palette[offset];
  imgData[i - 2] = palette[offset + 1];
  imgData[i - 1] = palette[offset + 2];
}

因为,最后热力图通过透明度迭代,最终其实使用透明度信息来表达数据密度的,而数据密度是通过 256*1 的色板决定的,所以讲不同的透明度数据映射到色板上。

「AntV」热力图 heatmap 绘制原理解析

整体代码可以看 src/shape/heatmap/renderer/index.ts

在 G2 中封装

以上是 heatmap,js 的源码解读,那怎么融入到 G2 的 API 设计中了?首先我们预定在 G2 中使用的 API 风格和方式。

API 设计

chart
  .heatmap()
  .data({
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/heatmap.json',
  })
  .encode('x', 'g')
  .encode('y', 'l')
  .encode('color', 'tmp')
  .encode('size', 20)
  .style('opacity', 0)
  .tooltip(false);

需要指定 x、y、color 通道和数据绑定,以便绘制散点的位置、以及密度数据字段,指定 size 通道设置绘制点的半径,默认为 40。

heatmap mark 处理数据

因为 heatmap 最终的渲染直接使用 heatmap.js,所以需要在 mark 中处理好坐标信息,密度字段、圆半径即可。

  • x
  • y
  • value
  • radius
export const Heatmap: MC<HeatmapOptions> = (options) => {
  return (index, scale, value, coordinate) => {
    const { x: X, y: Y, size: S, color: C } = value;
    const P = Array.from(index, (i) => {
      // Default size = 40.
      const r = S ? +S[i] : 40;
      // Warning: x, y, value, radius.
      return [...coordinate.map([+X[i], +Y[i]]), C[i], r] as unknown as Vector2;
    });

    return [[0], [P]];
  };
};

返回以四个信息作为一个数组单元,组成数组,用来绘制一个 heatmap 图形。

heatmap shape 绘制图形

接受 mark 中的 Array<[x, y, value, radius]> 绘制热力图,这部分就很简单的一个数据适配器,可以直接看代码。最后追加一个文档,DEMO 等即可。

效果

最后来一个 G2 热力图案例吧。获取鼠标在画布交互的的频率,绘制出画布的鼠标交互热力图。

「AntV」热力图 heatmap 绘制原理解析

总结

heatmap 对于 G2 来说,其实更像是一个图表分类的图表,而不是基于图形语法,只不过套在一个看似图形语法的 API 壳子中。但是这些图表绘制能力对于 G2 来说有不能不具备,类似的还有:

  • 韦恩图
  • 仪表盘
  • 水波图
  • ...

在添加这些能力的过程中,其实也是对可视化底层渲染能力的学习和复习,了解他怎么实现才能更好的使用他。