除了最常见的图表,还有一类关系图可视化,关系图相对于通用图表,数据之间关系复杂或难以确定,所以可视化时要考虑:
其实社区里除了 antv 系列,还有 echarts、d3 等不少图可视化引擎,但支持小程序甚至移动端的图可视化引擎几乎还没见过。通常可以通过嵌套 webview 的方式做实现,显然再性能、体验都存在问题,开发方式也不够优雅。
因此图可视化引擎 G6 也做了移动端适配,让关系图在 H5 和小程序中的操作和表现都更顺滑,完全贴合原生小程序开发方式,最终以 G6-mobile 包的形式发布。这次适配的两大 pr 如下:
下面我会梳理一遍 G6-mobile 诞生的脉络,讲述在 G6-mobile 实现中遇到的问题和方案选择。
g6 是 antv 图可视化体系下的一个关系图可视化引擎, 如果了解复杂网络图论的同学应该能 get 到 6 (六度分离)的含义,而 g 指的是图形语法(The Grammar of Graphic)。
图形语法简单总结就是:把数据字段映射到图形属性,再把图形属性和图形正交,就可以组成描述能力丰富的可视化图。
再讲个题外话,事实上 antv 体系下各类图或图表库都是以图形语法为基础拓展的,包括 g2、g6,这是不同于其他配置形式的图表库 echarts、highcharts的地方。配置形式图表库的好处是比较适合视觉描述(产品描述)思路,上手开发比较容易,但不易拓展。
而走图形语法的思路,我们会先思考需要显示什么图形,再考虑数据映射到图形属性的逻辑即可,不用担心图形是否支持这么映射,因此可以更灵活拆解式的开发。
配置形式的开发方式缺失了灵活性,但方便业务使用,@antv/g2-plot
就是封装了 g2 的链式语法,提供配置形式的接口,这两种方式怎么平衡是个值得思考的问题。
之前提到关系图可视化需要重点考虑布局、节点、和交互,所以 g6 的设计远比图形语法描述的复杂,这次我们主要讲 g6 现在 4.x 的架构,这关系到我们如何产出 g6-mobile,感兴趣还可以参考 g6 的历史架构
上面是来自官方文档的架构图,不过跟实际代码比起来有些出入和模糊,我们逐个来讲。
从下到上来看:
最底层是基础渲染层,这一层被抽到另一个包 @antv/g
中, 对应我们上文提到的浏览可视化渲染方式 canvas2d、svg、webgl,其中对移动端的支持其实是 f2 在实现过程中 fork 的 g 3.x
的版本改造的,这个 g-mobile 3.x
目前是整合到 f2 中的并没有单独抽包 。
上一层是一个拓展中间层,这一层的目的是为了把 g2 和 g6 的架构统一,提到的名词都是概念,有些 g6 已经实现、有些还没有,有的在 @antv/g6
里实现、有的在 @antv/g
里实现。
约束布局:解决的问题是布局过程中图形之间互相有依赖关系互相影响,约束布局把这些依赖关系在约束计算中内聚,外部布局并不感知,比如可以让多图形在同一方向的时候,均分区域大小,这是衍生自 g2 的方法,g6还没实现。
状态管理:@antv/g6
中实现,这是处理关系图可视化交互的解法之一,图中的节点或边是有状态的,g6 中默认处理交互状态,也支持自定义的业务状态。
事件模型:@antv/g
中实现,配合状态管理处理交互,g6 内置了事件、也支持自定义事件,g6 上的所有基础图形、Item(节点/边)都能通过事件进行操作。注意这里是构建了事件模型、注册和代理事件,我们之前说到 canvas 一大缺点是难以局部控制、事件操作,根本原因是canvas 绘制的是一张图片,用户在图片上点击时时不能获取对应的图形信息,所以还有个重点是定位。
定位:已经在 @antv/g
里实现。canvas 中定位获取图形元素的方法主要有这几种:
isPointInPath
可以判断获取对应的点是否在绘制的图形内部,所以可以遍历每个图形,isPointInPath
方法判断点是否在图形中,缺点就是遍历会导致性能差)isPointInPath
方法: 简单的图形使用几何算法,复杂的很多填充的图形使用包围盒 + isPointInPath
来检测。再往上是组件层,这一层是 g6 无关 g2 架构的能力
拓展 shape:这里提到了 shape
,这是 @antv/g
给出的『图形』概念,其中做的事就是我们前面提到通过 canvas2d api 绘制基础图形。
shape
的基类 element
(event-emit 模式)实现了一个绘图元素,实现可见性属性、矩阵变换、移动等绘制中用到的基本方法。shape
继承 element
实现一个基础图形的基类,这一层包装之后对外暴露统一的绘制和定位方法(获取包围盒)@antv/g6
中也实现一个 shape 类(即拓展 shape 类)。这个 shape 类是工厂模式,circle、rect 等具体图形在这个工厂中注册,代理 @antv/g
中 shape 的可见属性、绘制接口实现具体图形的绘制。分组管理:g6 中 group
的概念,是对基础图形的合并处理,比如同一个 group 的多个绘图元素可以合并包围盒、统一创建销毁、设置状态。group
同样继承自 element
实现。
element
、shape
、group
就是 g 架构图中的三层渲染模型。
状态管理:上一层提过。
plugin:提供一些拓展分析工具组件,比如网格、tooltip之类的,这块被单独拆成了一个 @antv/g6-plugin
包。
animation:配置是否展示动画、以什么函数显示动画。动画的实现在 @antv/g
中,做法就是创建一个 timeline 定时器去执行动画函数,改变 shape 属性、执行 shape 的移动变换绘制。
layout: 前面提到布局是关系图可视化的一个核心,这里被抽到 @antv/layout
中,内置多种布局算法。
最上方是用户接口层,提供了 graph、treegraph(由于数据结构和布局自成一派,单独抽出,想具体了解参考g6 文档)、item (g6 中的 item 就是 node 和 edge,这是关系图中最基本的两种元素,他们也是通过拓展 shape 类注册具体图形实现的,g6 内置了多种具体图形作为默认 node 和 edge 供选择)。
g6 体系中还有一部分是数据处理,涉及到不少模块:
@antv/data-set
包中专门实现)@antv/webgpu
包中独立封装了 webgpu 的接口,涉及到许多依赖,后面会讲)@antv/algorithim
包中单独实现,调用 dfs 之类的算法处理 node 和 edge)尽管这架构图看起来还算清晰,但每一块的实现分散在 g6 相关的多个包里,所以最后需要梳理一下 g6、 g 各个包之间的关系:
了解完 g6,可以开始实现 g6-mobile 了,先看下面临的问题:
移动端包括 h5 和小程序,h5 中的事件及部分 api 在小程序中不能使用,需要做一定适配来同时支持 h5 和小程序。
小程序封装的 canvas api 和 浏览器上有差异,导致 g-canvas 绘制需要调整。
g-base 里包含 document
、window
的引用,在小程序上不能用,需要兼容。
别忘了之前我们提到 f2 在是实现是 fork g 3.x
内部做了一套 g-mobile
。所以我们也就面临多个方案选择:
g-moblie
独立并兼容 g 4.x
。g 4.0
产出 g-mobile
,g-mobile
和 g-canvas
的渲染和 api 保持一致,只是针对事件特殊处理。考虑工作量和未来拓展性,选择了第二种。
渲染:抹平小程序 canvas api 差异
proxy
把 canvascontext
的调用转成了 小程序 canvas context 的接口。交互:增加移动端手势事件、交互 behavor 对接移动端事件
hammer.js
,这个专门处理移动端手势交互(尤其是多指触控)的库,但它同样依赖浏览器环境,所以我们精简了一份,事件由外部触发,hammer 只做事件解析,独立成 g6-hammer
包。g6-hammer
只做事件解析,我们还需要在 g-mobile
中增加 mini-event
模型移除 `document 和
window` 同时触发事件。g6-mobile
中也需要将 behavior 改造适配移动端事件。g6-mobile
中重写 event controller 去除 document
和 window
。布局
g6-mobile
中重写 layout controller 去除 webworker
、webgpu
。根据这些任务我们同样需要拆分到 g6、g 的多包中分别进行,调整之后 g6 的包变成了下面这样:
到此,我们看似完成了 g6-mobile 的开发,但打包构建出来之后我们发现了新的问题,g6-mobile 初始版本打包之后发现体积很大,parse 之后 1.3 M,gzip 之后 330k,这个尺寸移动端难以接受。
这就是我之前的一篇文章做的事: 👉 G6-Mobile 包体积优化
经过上述操作,我们最终完成了 g6-mobile 的开发调整。发布了两个包 https://www.npmjs.com/package/@antv/g6-mobile https://www.npmjs.com/package/@antv/g-mobile
G6-mobile beta 版也跟随 G6 4.2.0 一起面世
如果你也想在小程序或移动端中绘制关系图,欢迎使用 g6-mobile,已经独立成 F6 啦!