简介
G6 是基于canvas 系列的绘图lib , 但是不是 最底层的 lib , 还是基于例外一个 lib (g-master )来作为底层的lib实现的。关键是在canvas 这种基本的绘图api 上进行封装组合,实现类似HTML element的效果,即:嵌套的DIV(G6是node )和 style (在 嵌套的 node 之间有style 覆盖和继承关系),然后在不同的DIV(node)之间实现 event的 传递 。这就基本在cavans的绘图上实现了类似HTML的机制,这种实现可以模拟类似编写HTML的规范,实现统一的效果,一些在普通DOM上的思想可以借鉴来。对canvas的API操作封装,易于操作,对复杂类型的图形编写,操作 降低了难度。作为G6的核心依赖lib g-master 不止在G6上使用 ,G 系列的底层lib都是依赖这个lib , 性能分析 BizCharts → G2 → g-master。下面会从底层的lib(g-master) 先分析,然后会分析G6 以及 一些辅助 libs。
g-master
底层核心 lib ,直接操作canvas 负责 render 和 node的事件管理,master 有好几个lib ,这里只分析 g-canvas 和 g-base
g-master 和 g-base 包括几个核心的class ,是一个继承链表,每个class 都实现了部分的对应功能
绘图基本元素 class
| class 名称 | 继承关系 | 所属lib | 内容描述 |
|---|---|---|---|
| EE | null | antv/event-emitter | 一个简单的event emit , 类似node的events , 增加了类似regex的 eventname , 例如emit('*' ),可以on 和 off event |
| Base | EE | base | 最基本的class,实现最常规的接口:1. getDefaultCfg(), 这个是g系列内容class基本都遵守的一个属性定义方式,子类可以拿到父类的 init 属性,然后进行覆盖,存储在this.cfg = {} 内,子类的所有属性都放在 cfg内 |
- set , 和 get 最 cfg 的 getter 和 setter
- destory() 类似销毁接口 | | Element | Base | base | 这里开始元素具有了类似HTML的特性,例如 属性 , parent , 动画 ,矩阵等1. attrs ,属性 设置 元素的属性会导致 canvas 重新绘制, 属性可以是任何key value , 属性在 构造函数的时候也会有一个类似cfg的 getDefaultAttrs() ,的默认属性会混合(mix)默认的和函数参数的数据, 以及对应的 getter 和 setter
- 类似 css 相关的属性 : show , hide 设置cfg的 visible 然后 rerender 。
- 类似 css 相关的属性 :zindex:重排列parent 列表达到zindex 效果,Container 按照Zindex 排序 children ,绘制顺序
- 类似 css 相关的属性 :删除 , 从parent delete
- 类似 css 相关的属性 : position , 使用 matrix 实现 , 所有的元素通过 matrix (矩阵)来实现的,这种有一个好处,子集可以在父集的apply矩阵下实现相应的 矩阵操作,一般矩阵的操作是可逆的,矩阵操作简单 , 可以参考文档
- 类似 css 相关的属性 :clip
- 类似 css 相关的属性 : animate ,动画效果。核心是实现一个需要执行动画的列表,然后在一定的时间范围内对动画效果进行插值,然后按照差值每次rerender的时候 就是一个动画了,具体可以看G6文档
- getBBox:获取元素的范围信息(x,y,width,height等)
- getCanvasBBox:获取相对canvas的bbox | | Container | Element | base | 容器,和element的区别就是,一对多的关系,具有 children 属性容器具有一些容器的特别方法,例如添加,查找,删除,排列,等类似数组的方法,只不过操作的对象是包括在容器的内的元素(继承 Element)。一些特殊的方法1. applyMatrix: 会对children 使用新的matrix
- addShape:添加形状(Shape) ,会从基本的Shapes内拿对应的Shape 来init 作为一个 child
- getBBox:计算children的
- getCanvasBBox:计算children的 | | AbstractGroup | Container | base | 组,一个抽象概念,实现虚拟的容器 | | Group | AbstractGroup | canvas | 组,具体的组。这里会实现组内元素的render :draw() , 调用 canvas render,实现clip1. draw:绘制 children ,在绘制之前(context.save)会 apply 属性到 canvas的context ,绘制完成之后会删除属性(context.restore)
- onCanvasChange:这是一个给子集调用的方法,出发render,最后会调用canvas的draw | | Canvas(abstract) | Container | base | 不是最后的canvas ,实现能够复用的canvas 方法,容器 创建 和 init (默认是 html的 canvas),events 管理器 , 时间轴 init 等initContainer , initDom , initEvents , initTimeLine . | | Canvas | Canvas(abstract) | canvas | 实现draw 的class如何实现draw1. 之前有属性修改或者 Group的 触发render , 都会在canvas的 refreshElements内插入需要render的element,就是渲染队列
- 在执行draw的时候,draw 会有一个 渲染帧(drawFrame),避免重复渲染。
- 在获取到对应的渲染帧 之后,会执行 HTML的API requestAnimationFrame(这个是一个垫片,会会退到setTimeout=60的帧率:antv-util lib),因为这个需要执行的函数不是同步的,后面可以继续在 refreshElements内插入需要 重新渲染的 element ,达到批量处理的效果
- 执行渲染,会区分是否执行 部分 还是 全部渲染,只讲全部渲染
- 全部渲染(_drawAll): canvas 也是继承了container的,所以渲染是对所有的 children 执行的,apply canvas的属性之后 对 所有的children 执行 draw ,只对visible的执行。child 有的是 container 会依次递归,达到canvas的所有层级的 children 都调用对应的 draw,那么子children 想绘出什么图,这个就是 需要 实现的方法,下面的Shape就是 用到的图形 | | AbstractShape | Element | base | 图形的base class,可以理解为上面的class 都是 虚拟的,都是不能绘制的,Shape才是最后绘制在canvas上的图形主要实现,bbox的操作 , 图形是否可以点击(Hit),矩阵 , clip ,图形都具有的基本方法 | | ShapeBase | AbstractShape | canvas | 抽象出了图形绘制的基本方法,fill , stroke 1. draw:绘制图形 ,apply 图形的属性到 context , 执行 drawPath
- drawPath: 创建path , 填充 fill() 和 stroke() ,具体 的图形会覆盖对应的方法
| | Circle , Ellipse , Image , Line , Rect, Text 等 具体的图形实现 | ShapeBase | canvas | 1. 以Rect为例,实现CreatePath 用属性绘制边,然后在draw内绘制,其他的图形类似实现特色的绘制路径
|
event 实现
在init canvas的时候,会initEvents,这里就已经绑定了能处理的所有事件。事件都是基于canvas 的 事件来获取,然后根据当前事件出发的 position 来对 ,相应的 Shape 进行判断,然后根据
Shape直接的包含关系 ,实现冒泡类似HTML事件机制。因为所有的class都继承与 事件class,所以都有事件订阅发送的能力(on , off),从而达到事件的传递效果
G6
绘图使用g-base,然后对Shapes 进行封装一个Group,组合为对应的Node , Line , Combo (都继承Item):这些都是由多个 图形组成的单一实例,然后多个 Nodes ,Lines 组合成一个集群 就是最后
绘制的效果。
class 类别 和 实现
| class 名称 | 内容描述 |
|---|---|
| AbstractGraph | 基本的图形class , 实现 图形的 Canvas,View , Mode , Item , State ,Layout , Event , Group , Plugin,Stack 的 init 和管理,实现cfg的配置系统1. Canvas:子类实现,实例g-Canvas |
-
View : 实现 center 和 fit view的操作 ,就是对 Graph的顶层group apply matrix
-
Mode:Graph有 Behavior的 概念,每个 Mode 就是 多个 Behavior的集合,在init 和 set , mode的时候 ,会 init 对应的 Behavior 实现
-
Item:管理 Item(Node :图形, Edge:线 , Combo:组合):add , delete , find 等
-
State:管理State , 每个Item 都可以有对应的State ,各种State 会对应 各种的Style,所以在修改State的时候 就是apply了对应的Style到Item,实现类似 hover到Item 的时候颜色的修改等
-
Layout:布局,对不同的Item 计算 x , y 实现对应的布局算法。布局算法依赖lib:layout (力学布局等), hierarchy (层级布局,tree布局)
-
Group:Graph所管理的顶层group,group 包括了所有的Item
-
Plugin:Graph 插件, 由继承的子类实现,就是调用的 plugin的initPlugin(this:Graph)
-
Stack:操作栈,redo 和 undo 关键 函数initGroup:init Graph管理的顶层Group,获得root , edge , node , combo Group ,root 包括其他的group , 其他的group根据名称是对应Item的容器,就是所有类别的Item都在对应的Group下面,rootGroup具有顶层容器,类似顶层的DIV,可以很方便的控制子集的一些position 和style 。对于Item的一些操作函数:获取nodes /edges/combo等,都是对group的操作对于Position的一些操作函数:zoom,translatge ,moveTo ,fit 等,都是对rootGroup的操作render():绘制Graph,首先会将用户init的data都是 ,实例为对应的Item(Combo,Node,Edge)添加,然后执行Layout 计算Item的postion,最后调用g-canvas的draw() | | Graph | 基本Graph,还有TreeGraph。主要实现Layout的和Event和Canvas的init。1. event实现:通过绑定canvas的所有事件
-
canvas:主要是选择svg 还是 canvas画图 |
基本Graph的基本绘图单元Item
Item是图形的组合抽象,一个Item 会有很多的Shape组成,但是会有一个KeyShape。通过ItemController , addItem 添加。addItem 会合并 一些默认的配置然后实例对应类型的Item。
每个Item都有对应的类型,类型通过registerNode/Edge/Combo等注册到类型表,然后在绘制Item的时候会从ShapeFactory 获取对应的图形实例(注意:这里的图形和之前说到的Shape不是一个概念),然后调用draw方法,draw会对Item所设置的属性拼接为Shapes集,设置对应的Sytle(就是Element的attr),然后在Graph render的时候会读取Shape的图形实现绘制效果
ShapeFactory:
对于不同的Item类型(Node , Edge , Combo),都会在 class Shape 内对应的静态属性下 注册了框架Base函数,然后在不同的Item 会调用registerNode/Edge/Combo , 在此静态属性上设置Item的类型所对应的Shape 属性(这个Shape属性都从ShapeFramework 基本对象继承而来,是覆盖关系 nodeShape = { ... ShapeFramework , ...customShape})。然后在Item 拿到对应的ShapeFactory的时候,还需要拿到Item的Shape(getShape),最后执行Shape的draw(),Item 在添加的时候都会init一个Group,所有的图形的add,都会在group的children
各种图形Shape
图形Shape都是通过 registerXXX注册到 ShapeFactory的静态属性上的。以registerNode的rect (矩形)为例。
registerNode的rect 实现是类似继承的mix方式,即子类会覆盖,但是不能调用覆盖super的方法 : child = { ...base , ...customOptions}
shapeBase:所有图形的基类 ,就有cfg 属性,可以计算style 和 各种 state下的state
- draw():绘制图像drawShape , 绘制标签:drawLabel
singleNode : 最基本的Shape属性 ,包括drawShape , getShapeStyle (获取Shape的sytle),
- drawShape:绘制图像,group.addShape (attrs:style )就是将绘制的图形添加到group ,
simple-rect:矩形,包括矩形的基本配置
Graph Behavior :
Behavior 实现很简单就是在init的时候必定了这个Behavior需要处理的event 到 graph的event(从canvas emit),然后在对应的event 的时候调用graph的方法,behavior的register 和 Shape 类似
Layout:
就是对Graph的nodes 计算 x ,y 。如果是force 类型 ,可以使用webworker 和 gpu 功能,在force的每次修改计算完成了所有的node的x,y之后刷新, Graph rerender , force 布局都基于 d3。
force布局:force 需要计算大量的信息 使用webworker 计算是很好的性能提升方式,就是将 计算 内容放到webworker 内执行,然后通过消息 传递到 Layout ,执行 refreshLayout 方法
edges:
连线,连线只自动生成的,只需要给顶target 和 source ,就能绘制线段。线段会计算 是从target 或者 source的那个 方向连线 ,可以将 target 模拟成一个 1 * 1 的 正方形 ,线段的开始点就在,x ,y {0 , 1} 的范围内
这就是锚点,然后连线应该怎么从亮点直接连接,如果是直线就很简单。如果是曲线 ,需要将整个坐标系 网格化,然后在锚点直接计算路径(A*算法),如果是折线,就需要多个控制点作为线段的点
Combo:
这个是一个组合的概念,将不同的Item或者Combo 包括起来组成一个Combo ,Combo 也是由图形组成,会在Item 的 增加 和 删除 重新计算 图形 ,rerender