图可视化之从零开始学G6

为何写这篇文章?

  • A 我想学习,我喜欢学习。
  • B 工做须要,接下来会和 G6 打交道,就是和钱包有关系。
  • C 整理下最近看的文章,用本身的文字输出一遍,加深印象。

目标

但愿读完系列文章后,可以对 G6 的功能分布、主要流程有大概的认知。 关注点在 G6 的介绍和主要流程,不涉及到具体 api 的使用。node

G6 是什么?

下面各个维度解释下G6git

G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户得到关系数据的 Insight。github

G6 是 antv 体系的一个图可视化品牌,主要关注关系图的绘制,antv 还有其余应用,好比关注图表的 G二、F2,关注地理数据渲染的 L7 等算法

G6 是一个开源的 JavaScript 图形库,能够支持 PC、移动端、小程序多个平台。npm

更多G6使用的示例参见图可视化引擎 G6json

G6 的上下游

G6 依赖渲染引擎 G 提供渲染能力,G 是一款Antv体系开源的支持 canvas、svg 渲染,跨 PC、mobile 平台的强大渲染引擎。 Graphin是基于 G6 的 React 库,经过接入 React 生态,使用起来更加简单。bootstrap

功能分布

上图是官方 v4 的框架图,比较能看出 G6 基础的功能分布。canvas

  • 跨 PC、mobile 平台
  • 核心功能:状态管理、事件及交互、动画、布局;
  • 扩展能力:插件、自定义基础类型
  • 底层的 GPU 加速计算、g-base 的渲染能力等;

工程结构

G6 是一个多项目共存的库,使用monorepo的思想来管理项目。monorepo不作过多介绍。思路就是把相关联的项目都放在一个仓库里管理,包括开发、构建、发布等。我本身经验来看,这种模式确实在同时多个项目开发时有优点,免去管理、link 多个项目的麻烦。不过上手阶段我常常会有疑惑,不能确认问题出在哪一个项目,要总体build,苦笑,应该是我太菜。G6 使用 lerna 来管理全部的子项目,目录结构和简介以下:小程序

G6
|-packages
  |--core // G6核心库,实现了大部分功能,主流程、动画、交互、状态管理
  |--pc // 扩展core,提供更多的交互,pc的事件等
  |--mobile // 扩展core,提供mobile、小程序平台的支持,移动端事件支持等
  |--element // 提供预制的自定义图元素(节点、边、combo)
  |--plugin // 插件,好比[鱼眼效果](https://g6.antv.vision/zh/examples/tool/fisheye#fisheye)
  |--...
复制代码

构建项目:npm install , npm run bootstrap ,更多指令能够看package.json文件api

渲染引擎G

功能

提供渲染能力,向上提供

主流程

  1. 顶层抽象流程是输入节点树,遍历并渲染两个步骤。
  2. 深度遍历,遍历的顺序就是节点的绘制顺序,能够用来控制 z-index。
  3. G的渲染循环触发是通知触发的,不是每帧渲染。初次进入、有事件或动画的时候触发渲染,每次收集一波更新后在下一次 requestFrame 回调时绘制,这样处理应该能节省很多静止时绘制消耗。动画则使用额外的依赖 d3-timer 来驱动。
  4. 游戏引擎里会在节点树和绘制接口draw之间增长渲染队列,方便对渲染队列作合批优化。

概念

  • group:节点分组,组织子节点,构建树结构。
  • shape:子节点,承载具体的绘图能力和渲染数据,g-base 提供了圆、矩形、线、多边形等基础能力。
  • 详细的数据结构官方文档有找到shape的,参见文档。数据结构描述了类型的形状和状态,数据流和状态转变能跟踪整个系统的运转,对理解和把控系统颇有帮助。各类类型的shape有一些共同的样式结构,好比fill、shadowColor。各自又有一些特殊属性,好比圆形的半径、矩形的宽高。

小结

本小节简单描述了G的流程和输入的数据结构,用来辅助理解G6。一些更细的点,好比局部渲染、形状拾取、碰撞检测、坐标系统等,就暂时无论。从流程图能够看出,G 跑起来须要 Group 树结构,因此接下来 G6 须要作的工做就是构建这样的结构,交给 G 来渲染。

G6

前置文档说明

G6的核心概念 在官方文档有讲,此处再也不赘述,能够先创建下概念和基础用法。一个简单的示例以下:

const data = {
  nodes: [
    {
      id: "node1",
      label: "Circle1",
      x: 150,
      y: 150
    },
    {
      id: "node2",
      label: "Circle2",
      x: 400,
      y: 150
    }
  ],
  edges: [
    {
      source: "node1",
      target: "node2"
    }
  ]
};

const graph = new G6.Graph({
  container: "container",
  width: 500,
  height: 500,
});

graph.data(data);
graph.render();

复制代码

例子简单,但也能基本说明问题。能够看出:

  • 使用流程:就是准备数据和配置,建立Graph实例,传入数据,而后render
  • 数据结构:G6使用节点和边两个类型描述整个图,节点就是图的节点,边描述节点之间的关系。节点:边是1:n的关系。

在此基础上有交互(behavior)、事件、状态管理、动画、布局几大功能。下面就按照G6的执行顺序,分析下G6的基础流程和相关设计。

graph实例

建立graph实例后,同时会初始化如下单例,管理不一样的功能:

  • graph.cfg.canvas

    G 对应的 Canvas 类实例,调控整个渲染流程,经过该单例与 G 对接起来

  • graph.cfg.itemController

    管理 Item 实例,Item 是 G6 包装的节点类,输入的边和节点数据会组织成 item 实例数组。这个模块打通从数据到最终渲染的整个流程,下面的章节会着重分析这个模块相关的部分。

  • graph.cfg.layoutController

    控制布局相关逻辑,布局算法比较复杂,G6 拆出了一个 npm 包(@antv/layout)专门放布局相关算法。

  • graph.cfg.viewController

    控制显示相关逻辑。核心逻辑是计算视口居中,提供坐标转换等功能。

  • graph.cfg.eventController

    控制事件相关逻辑,核心逻辑是拾取 G 传来的事件,出发用户定义事件,最终反馈到 item 上。

  • graph.cfg.modeController

    控制交互,管理 behavior。

  • ShapeFactory

    图元素的工厂函数,管理(crud、函数调用)定义的节点、边、combo。

绘制流程

下图展现了一个节点数据(参照前置文档示例中节点的数据结构)从输入,到最终渲染,数据的流动状态,以及经历的流程。

  1. 建立 graph 实例,建立 G 的 Canvas 模块实例,输入节点数据。
  2. 调用 graph.itemController.addItem, 使用data 建立出 Item 实例并保存在 itemController 中。
  3. 建立出的 item 使用 factory 找到对应绘制节点,包括自定义节点shapeA。
  4. shapeA.drawShape 将 shape 添加到 group 中,构建出渲染树。
  5. G 绘制 Group 树。

节点的组织

data输入后,须要依靠itemController建立出Item实例来管理。Item做为G6与渲染层的桥接,能够仔细分析下。Item类设计以下:

Item

  • Item 关注G6这一层的功能的支撑,图元素的组织、状态等。
  • Item 分为三个子类,Node,Edge,Combo 管理不一样的行为。好比Node会有链接点,Edge会有箭头等,在子类中定义。状态、绘制流程相关公用的逻辑就放Item类。
  • Item 的子类和ShapeFactory的子factory是n:1的关系(参见Item的init方法)。好比Node类,会对应到NodeFactory。若是扩展新的ShapeFactory,须要考虑Item子类的联动。

ShapeFactory

  • 目前默认有三个子factory,NodeFactory、EdgeFactory、ComboFactory,分别对应三种图元素类型。
  • 职责是管理(新增,查找)图元素,能够理解是图元素工具类的集合。
  • 使用抽象工厂模式,能够新增子factory或在子factory增长新的类型,扩展性很强。

图元素

  • 图元素类的关注的是 G 的shape,给 G6 支持到绘制能力。自身不保存任何状态,属于工具类。
  • 图元素类与shape的关系是1:n,能够经过组合不一样的shape新增图元素类型。
  • 图元素官方定义三种基础类型,节点、边、combo,介绍和数据结构详见文档
  • 图元素类中的固定接口,会在程序运行时调用。好比drawShape,Item在添加的时候,会调用该函数,给渲染层增长shape。

查找图元素流程

  • 一个具体的Item实例,好比Node 实例A;
  • 输入 A.type 给 ShapeFactory 找到 NodeFactory;
  • 输入 A.cfg.type 给 NodeFactory就能够找到注册的图元素类;
  • 使用图元素绘制或执行动画等

其余功能

前面里串通了绘制流程,已经能画出了图。除此以外,做为图可视化引擎的G6,还支持了哪些功能,又怎么实现的呢?

  • 事件、交互、状态,这个不用多说,只看图不能交互,味道是差了些
  • 动画,加点缓冲效果,视觉更好了
  • 布局,内置了丰富的布局样式以匹配各类场景。
  • ... ...

本文总体的思路是,分析这些功能互相的关系、内部的一些流程、设计 、实现点,这样基本能从大到小,解释清楚整个系统是怎么运行的。

总体关系

各个功能的分层关系以下:

整个G6就这些核心功能,从图中能够看出各个功能之间的层次关系,能了解到功能的所处位置。有了大方向上的认知后,能减小些迷茫。

这些功能除了动画,均有对应的Controller,由Graph封装并放在Graph.cfg上,并对外提供入口。G6有个设计的特色,就是全部状态相关的都会放在cfg里,表明的应该是这个类型的model。好比Graph类中的cfg,Item类中的Cfg等。不过把Controller这种逻辑密集的也挂在上面总感受有些奇怪。

事件、动画、Item节点管理会直接依赖G。而交互、状态管理、布局则更多基于G6本身的封装。

事件

官方文档对事件有清晰的分类。总共分为三类事件:画布层次的事件(canvas:mousedown ...),节点层次的事件(node:click ...),按时机的事件(afterlayout ...)。事件包括事件名,以及事件的回调。事件名在不一样平台会有差别,好比pc平台的mouse事件,移动平台的touch事件。详细介绍见文档中核心概念章节的事件小节。

事件咱们都比较熟悉,观察者、发布订阅之类。G6事件也不例外,有个全局的事件中心,graph.cfg.eventController,提供事件的订阅和发布。而与咱们认识的Dom的事件不一样的是,G6面对的可能只有一个Dom元素,Canvas,因此不能依赖平台自己解析事件触发的节点、事件对象,须要本身实现及封装。

  • 按时机的事件使用eventController作发布订阅就能够了。
  • 画布、节点层次的事件,G和G6都有部分实现。G的树节点继承了@antv/EventEmiter,让每一个节点支持事件注册及冒泡,相似dom。G6接管了G的树节点根元素的全部事件,并统一派发。从这个实现思路看,其实shape是能够本身注册事件的(G里树节点支持on/off),G6的文档则只提到经过Graph注册事件,应该是认为G更加底层,使用时不该该接触这一层的行为。
  • 以click为例说明下事件的流程:
    1. G6建立item时,给对应的group挂上本身的引用
    2. 用户触发click
    3. 触发画布原生事件
    4. G根据点击坐标作形状拾取事件冒泡
    5. G6观察到根部元素事件触发,并拿到传递来的shape,进一步就能够作事件的派发、驱动交互功能

形状拾取

经过上篇文章,咱们知道,G将绘图的节点组织成group树结构,shape是树的子节点。要肯定到底点击到哪一个shape/group,思路其实显而易见就是遍历这棵树,找到节点便可。下面是quickHit模式下的节点拾取流程,源码参见G项目中g-base包里event.ts文件。(quickHit模式经看源码,主要是忽略了group的点击检测,只检测叶子节点,经过这样处理提高性能。)

  1. 触发点击
  2. 获取到源事件对象
  3. 获取到点击点坐标
  4. 从右向左开始深度遍历Group树(渲染的时候是从左到右,最后渲染的在上面,最早触发事件)
  5. 根据点击坐标过滤(跳过隐藏的、禁止拾取、画布外的树节点)
  6. 点击检测到坐标点在形状内,判断找到形状,返回形状
  7. 遍历完成未命中则断定未触发

其中点击检测每一个shape类型有实现本身的检测方法,好比圆使用点到圆心的距离等。每次触发事件都要遍历整颗树,作点击检测,是个性能点,所以G自己也作了一些缓存处理。

事件冒泡

事件冒泡是G作的,比较简单,核心思路是检测命中shape后,依次向上触发parent的事件,直到事件对象里的 propagationStopped 属性为true或者到达根元素。  

交互模式

**交互模式解决的问题?**是用来批量操做用户交互行为,好比切换编辑和查看模式这种场景。详细介绍见文档中核心概念章节的Behavior相关小节。 类设计:

Behavior是个工厂类,使用对像存储各个Behavior类型。每一种Behavior子类型,定义了所需的事件行为集合,提供事件的绑定与解绑。 ModeController,根据输入的mode配置(Behavior类型的索引组成的数组),或动态切换不一样的Behavior子类实例。

ModeControler绑定Behavior流程:

核心思路就是ModeController调用Behavior工厂建立子类型实例,实例完成绑定过程。触发时机是初始化Graph或系统运行期间动态调用Graph.setMode。

状态管理

状态管理解决的问题? 实现交互时或者业务逻辑中节点状态变化后,触发节点样式的更新或其余自定义行为。这里的状态能够是交互中的hover、active,也能够是自定义的running任何一种状态。对状态的响应若是只是样式变化,能够直接在建立Graph实例时输入的节点配置中设置。若是其余更加复杂的行为,好比加个动画,就须要自定义节点,复写默认的setState方法。详细介绍见文档核心概念章节的交互mode、状态state小节

类设计:

Graph初始化后会实例化StateController和ItemController,能够在Graph实例的cfg中访问到。

StateController目前看来只是维护了各类状态下的Item数组,以及更新状态后的事件触发,不影响整个state的流程。

设置状态流程

  • 输入数据中配置不一样状态的样式
  • 事件触发或流程触发,调用graph.setItemState更新状态
  • 查找配置中状态对应的样式
  • item(须要更新的节点)定位到具体的shape,调用shape的setState,更新样式到G渲染层

动画

动画G6没有作什么处理,直接使用的G的动画能力。根据使用场景,分为全局动画自定义节点动画两部分。详细介绍见文档核心概念章节的基础动画小节。 **全局动画:**拿到节点树根部元素,调用animate接口;

自定义节点动画:在自定义图元素(能够看上篇了解图元素,G6用来对接G,封装了具体的shape)时,拿到shape或group节点,调用animate接口便可。

类设计:

  • Element实现了animate接口,子类都具备了动画能力。
  • Animation这个类源码里没有单独的文件体现,不过有用ts声明区分出来,记录了动画的详细配置。
  • Canvas类是G这一层的主控类和入口。根节点、动画入口、绘制入口等,都在这个类上。
  • Timeline类使用Element和Animation实现具体的动画算法。使用的时候做为单例挂在Canvas实例上。

动画流程:

核心思路就是Element获取Canvas中的TimeLine实例,传入动画数据,交给d3-timer去逐帧执行,经过插值实现连续的动画效果。

插值计算使用了d3-ease、d3-interpolate辅助计算。

更新到画布是Element的能力。

触发流程时机是全局动画或自定义动画调用animate时。

布局

布局是采用算法,使节点和边可以以某种方式分布。分为通常图布局和树图布局,目前我只看到了通常图布局,就只分析通常图布局。详细介绍见文档核心概念章节的图布局小节。

相关的类:

布局使用在初始化Graph时建立的LayoutController控制。具体的布局算法来自npm包** @antv/layout** 。

通常图布局的具体流程

能够看出,布局和Graph经过数据解耦。Graph面向数据,数据正确就能正常渲染。布局算法也只用关注对数据的生产,不用关注Graph或者其余系统。

布局算法对节点坐标更新以达到布局的目的,其余数据项也能够更新,不过就显得职责不清晰,因此不建议布局算法对其余数据项的更新。

PC和Mobile平台布局的流程不一致。PC会额外支持worker布局,就是图中第三列的布局流程,把布局拆成了三个步骤异步通知进行。

触发流程的时机是在初始化或者切换数据、布局的时候,具体是在调用Graph的render/changeData/updateLayout时。

这里是一个自定义布局的例子,看完这个例子应该会对布局算法的职责有更清晰的认知。

小结

经过对各个功能的层次分布,以及各自实现流程的分析,应该对G6实现思路有了大体的认知。有兴趣的话,能够对着流程看看源码,看看具体某个步骤的实现逻辑。通过这段时间G6的梳理,对G6也有了更多的了解,最后感谢阅读。

最后

微信搜索公众号Eval Studio,关注更多动态。

相关文章
相关标签/搜索