AntV G2 渲染流程学习笔记

数据体验技术团队(ProtoTeam) @ 蚂蚁金服(杭州)网络技术有限公司

学习和梳理 G2 的源码,了解怎么开发和使用一个可视化图表库。

G

AntV G 是整个 G2 G6 底层的渲染引擎,可以理解上层做的所有事情,搜索在构建 G 的渲染数据结构。

先感受一下 G 的 API:

import { Canvas } from '@antv/g-canvas';

// create a canvas
const canvas = new Canvas({
  container: 'container',
  width: 600,
  height: 500,
});

// add a circle to canvas
const circle = canvas.addShape('circle', {
  attrs: {
    x: 300,
    y: 200,
    r: 100,
    fill: '#1890FF',
    stroke: '#F04864',
    lineWidth: 4,
  },
});

circle.render();
复制代码

G 核心的逻辑就是:

  • 通过 Group、Shape 来组织整个选人的树形数据结构
  • 通过 addShape 的 API 来填充这个数据结构
  • 最后使用递归,去转换为 canvas ctx API

G2

G2 是基于 G 去做绘制的。废话先少说,一图胜千言,先来一个 drawio 的流程图。

image.png 费了我半天时间整理的流程图

上图的渲染流程,其实是指他的核心类 View(视图)的数据处理、映射、渲染的流程。

G2 整体是使用 view 去组织渲染结构的,最终暴露的 Chart 类继承自 View,view 可以包含有几个子信息:

  • 包含的子 view
  • 包含的图形 geometry(折、柱、点等等)
  • 包含的组件 component(图例、坐标轴、缩略轴等)

所以不难得知,本质是使用 view 作为容器组织的树形结构,其渲染也是直接是一个大的递归逻辑。分成几个阶段:

  • 实例化
  • 初始化
  • 渲染
  • 销毁

分别介绍。每一项都可以参照上面的图形一起看。

实例化

import { Chart } from '@antv/g2';

const chart = new Chart('container', {
	width: 400,
  height: 300,
  padding: 16,
});
复制代码

Chart 是 G2 暴露的最重要的入口 API,用来创建一个图表实例。他继承自 View 容器,所以他特有的逻辑就很简单了:

  1. 创建 G.Canvas 画布
  2. 绑定事件,处理 autoFit 参数(图表自动适配 DOM 容器的大小)
  3. 生成三层 G.Group 然后走 View 的构造函数逻辑

初始化

chart.data([ /* ... */ ]);

chart.coordinate('rect');

chart.scale('x', {});

chart.axis('x', {});

chart.position('x*y').color('type');

// ...
复制代码

上述 G2 的这类 API 的功能都是类似的:设置配置项并存储起来。这些配置项包含:

  • 数据
  • 坐标系
  • scale 数据列定义
  • 组件配置
  • 图形配置
  • 图形映射配置
  • 分面
  • ...

渲染

chart.render();
复制代码

在设置了各项配置之后,最后在 render 的时候,会消费这些配置,然后去生成衍生数据、数据映射、绘制图形组件、G 渲染。

前面已经提到,View 包含有子 View,然后无限嵌套的树形结构,所以渲染逻辑是一个递归的渲染过程,这其中的逻辑主要分成为几个阶段:

1. 数据处理阶段

数据处理,主要是将初始化过程中的配置项,进行处理,产生一些衍生数据,用于做渲染。主要包含:

  • **经过过滤后的数据(**比如 legend 的过滤)

image.png 这里逻辑很简单,就是存储一个内部状态,然后直接数组的 filter 即可,过滤之后的数据才是最终的渲染数据。

  • 创建 coordinate 实例

coordinate 坐标系是一个相对坐标映射到画布绝对坐标的函数。

// 0 ~ 1 的相对坐标点位置
const point = { x: 0.1, y: 0.8 };

const pointPixel = coordinate.convent(point);
复制代码

不同的坐标系会将一个相对坐标映射到不同的像素点上,从而实现图形的变化。比如主题和饼图其实就是一个图形经过坐标系变化而来的。

RectPolar
image.pngimage.png

当然,坐标系还能做一些变化能力,比如 translate,rotate,scale 等我们 CSS 中常见的 transform。

因为后续所有的数据转换,渲染,都会用到 coordinate,所以首先先创建好实例。而坐标系的宽高默认为画布的宽高。

  • Geometry 的初始化

Geometry 是 G2 很重要的代码,承载技术所有的数据处理过程,将图形语法的理论写到其中。而在这个阶段,主要做的事情是初始化。初始化主要包含:

  • 根据字段配置,生成 scale 信息

  • 数据加工

    • 分组:根据分组字段(color、shape、size)将数据分组,每个分组的数据组成一簇。
    • 数字化:使用 scale 将数据数字化,好处在于便于后续经过 coordinate,然后获取数据的实际画布坐标。数字化逻辑很简单,就是对于分类的信息使用其 index 作为数据。
    • 调整:主要是对于堆积图,需要调整数据。
    • scale 调整:对于堆积图,调整 scale 的 min max;对于线图、柱图修改 range 范围。
  • fact 分面处理

这是高级功能,逻辑也不难。根据 fact 配置 + 数据,生成子 view,以及他们的位置。

2. 计算 auto padding

padding 可能是 G2 特有的一个概念了,他的含义是:图形区域上右下左的间距,这个间距是保留给图形组件使用的。 image.png 在 G2 中,padding 是可以设置一个固定值,或者 auto 的,而对于 auto,就存在一个过程需要计算这个 padding 具体值。所以 auto padding 的逻辑就是将 auto 转换成 类似 css 的 padding 确定的数据。

auto padding --------->  [16, 16, 16, 16]
复制代码

而这个逻辑也不难,就是根据 axis、legend、slider 等组件的方位,以及他们的 width height,最终计算出实际的 padding 值。

这个 padding 有了之后,就可以更新坐标系的宽高了,这很重要。(也就是画布的宽高 - padding)

3. 布局

布局是在有了 padding 值,有了 coordinate 的具体宽高之后,然后根据组件(axis、legend、slider)的 position(上右下左),来计算组件具体的 x y。

这么一来,组件的 x y width height 都确定了,然后绘制就没有什么问题了。

4. 渲染绘制

布局完成之后,最后的一步就是渲染了。主要分成为图形的渲染、组件的渲染。

  • Geometry 渲染

** 前面初始化过程中,做了一部分的数据处理,将数据进行分组、数字化。那么到了渲染这一步的时候,主要的逻辑就是 shapeFactory 的逻辑了。

不同的图形有不同的 shapeFactory,每个 shapeFactory 都有自己的渲染方法(转化成 G 的 shape)。主要分成为几步:

  1. 分组数据变成单向链表,这样数据之间就能互相索引了
  2. 计算关键点,这个是有 shapeFactory 的 getPoints 决定的。比如柱形图的关键点就是:

image.png

  1. 将图形属性配置映射后的数据,比如 x y 就映射陈 0 ~ 1 的位置,color 就映射出具体的颜色。等等
  2. 每个分组数据,创建一个 Element 实例,这个实例会管理自己的数据 model,然后会根据 shape 获取对应的 shapeFactory 去绘制。

** 在这个流程中,我们暴露了 registerShape API 来做图形的自定义。** **

  • Component 渲染

组件的渲染就很简单了。

  1. axis 根据字段对应 scale + 设置的 axisCfg,去生成 G 的结构即可
  2. legend 根据 legendCfg 去绘制 marker + text
  3. tooltip 使用 html 绘制
  4. ...

销毁

不多说了,见最上面的流程图。

最后

一天扫描代码下来,变成一个流程图和文字版本。 内容其实还是挺多的,还是需要对照流程图 + 代码逻辑过一过,才能好好消化。

G2 本身对于自定义能力还是做了不少的,理解这个之后,对我们使用 G2 做业务定制需要还是有很大帮助的。

文章分类
前端
文章标签