从零开始学G6 | 您所在的位置:网站首页 › canvas模块 › 从零开始学G6 |
为什么写这篇文章?A 我想学习,我喜欢学习。B 工作需要,接下来会和 G6 打交道,就是和钱包有关系。C 整理下最近看的文章,用自己的文字输出一遍,加深印象。目标 希望读完系列文章后,能够对 G6 的功能分布、主要流程有大概的认知。 关注点在 G6 的介绍和主要流程,不涉及到具体 api 的使用。 G6 是什么?下面各个维度解释下G6 G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。 G6 是 antv 体系的一个图可视化品牌,主要关注关系图的绘制,antv 还有其他应用,比如关注图表的 G2、F2,关注地理数据渲染的 L7 等 G6 是一个开源的 JavaScript 图形库,可以支持 PC、移动端、小程序多个平台。 更多G6使用的示例参见图可视化引擎 G6 G6 的上下游G6 依赖渲染引擎 G 提供渲染能力,G 是一款Antv体系开源的支持 canvas、svg 渲染,跨 PC、mobile 平台的强大渲染引擎。 Graphin是基于 G6 的 React 库,通过接入 React 生态,使用起来更加简单。 功能分布上图是官方 v4 的框架图,比较能看出 G6 基础的功能分布。 跨 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文件 渲染引擎G功能提供渲染能力,向上提供 主流程本小节简单描述了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。 绘制流程下图展示了一个节点数据(参照前置文档示例中节点的数据结构)从输入,到最终渲染,数据的流动状态,以及经历的流程。 data输入后,需要依靠itemController创建出Item实例来管理。Item作为G6与渲染层的桥接,可以仔细分析下。Item类设计如下: 前面里串通了绘制流程,已经能画出了图。除此之外,作为图可视化引擎的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为例说明下事件的流程: G6创建item时,给对应的group挂上自己的引用用户触发click触发画布原生事件G根据点击坐标做形状拾取,事件冒泡G6观察到根部元素事件触发,并拿到传递来的shape,进一步就可以做事件的派发、驱动交互功能形状拾取通过上篇文章,我们知道,G将绘图的节点组织成group树结构,shape是树的子节点。要确定到底点击到哪个shape/group,思路其实显而易见就是遍历这棵树,找到节点即可。下面是quickHit模式下的节点拾取流程,源码参见G项目中g-base包里event.ts文件。(quickHit模式经看源码,主要是忽略了group的点击检测,只检测叶子节点,通过这样处理提升性能。) 触发点击获取到源事件对象获取到点击点坐标从右向左开始深度遍历Group树(渲染的时候是从左到右,最后渲染的在上面,最先触发事件)根据点击坐标过滤(跳过隐藏的、禁止拾取、画布外的树节点)点击检测到坐标点在形状内,判断找到形状,返回形状遍历完成未命中则判定未触发其中点击检测每个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获取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,关注更多动态。 |
CopyRight 2018-2019 实验室设备网 版权所有 |