云析上图流程汇总
流程总览
Chart.js
首先,云析模块的主页面位于tieshangongzhu
仓库的analyticGraph.vue
文件,当打开云析页面时,会调用Chart.js
的createTemporaryChart
静态方法用来新建一个临时图表,其中接收8个参数,具体含义如下:
在执行上述类的静态方法的同时,内部执行了Chart
的构造函数以及ChartMetadata
构造函数。ChartMetadata
类是用来存储一些元信息,可通过Chart
的实例对象调用getChartMetadata()
方法来获取这些元信息。 Chart
类接收4个参数,具体含义如下:
在实例化Chart
的时候,会创建空的各个图层,首先创建OriginalGraph
,然后根据ChartMetadata
里存储的是否进行叶子结点合并以及是否进行链接合并,来决定是否创建EntityMergingGraph
和LinkMergingGraph
。最后创建FinalGraph
并将上述实例化对象传入。 然后,Chart
内部会实例化CommandManager
,该类用来记录管理前进后退操作,默认最多记录5条。
此外,在Chart
、OriginalGraph
、EntityMergingGraph
、LinkMergingGraph
、FinalGraph
以及CommandManager
里都存在着execute
方法,如果在当前类里找不到相应的方法,那么就会去传入的参数里面找,可以把Chart
创建各个图层理解为入栈操作,最先入栈的是OriginalGraph
,最后入栈的是CommandManager
。把执行execute
理解为出栈操作,因此最先执行到CommandManager
,最后执行OriginalGraph
。
下图演示了Chart
初始化与调用execute
时的操作:
CommandManager.js
CommandManager
类接收一个参数,Chart
的上下文this
,可以更方便的操作Chart
。主要是用来记录前进与撤销操作,默认可以记录的操作有:
- addSubGraph/removeSubGraph:添加与删除子图操作;
- hideSubGraph/showAll:隐藏与展示子图操作;
- setLayoutType:布局操作;
- fullLinkMerge/linkUnmerge:链接合并与取消合并操作;
- linkEliminate:链接消元操作;
- setEntityBorder:设置实体边框操作;
- setEntityScale:设置实体缩放操作;
- setLinkColor:设置链接颜色操作;
- setLinkWidth:设置链接宽度操作;
- clearStyle:清空上述四种样式操作;
- lock:设置实体锁定状态操作;
- unLock:设置实体解锁状态操作;
Graph.js
该类继承自EventEmitter
,可以实现事件监听。内部实现了对实体与链接的增删改查、显示隐藏、设置属性等等操作,同时存储了elpData
。所有图层的事件都会经过该类进行触发,然后由该类里的方法修改最终生成的实体与链接。
FinalGraph.js
FinalGraph
类继承自基类Graph
,并接收一个参数,该参数可以是OriginalGraph
、EntityMergingGraph
、LinkMergingGraph
,传入什么参数取决于是否进行叶子结点合并以及是否进行链接合并。具体来说,如果needLinkMerge
为true
,则传入LinkMergingGraph
的实例对象,如果needEntityMerge
为true
,则传入EntityMergingGraph
的实例对象,都为false
则传入OriginalGraph
的实例对象。
在该类中,主要操作包括监听了由Graph
emit出的changed
事件,执行对实体与链接的增删改查、显示隐藏、设置属性等等操作。中转了elp-changed
、collection-add
、collection-remove
等事件到Chart
中。将大部分对图数据的操作包裹为Promise
,方便进行异步调用。
LinkMergingGraph.js
LinkMergingGraph
类继承自基类Graph
,并接收三个参数,具体参数含义如下: 在该类中,主要操作包括链接合并规则的设置与读取、链接样式的设置、链接合并与取消合并,同样也监听了由
Graph
emit出的事件,对链接进行增删改查,但是最终都是调用基类Graph
的方法,将数据存储在Graph
里。
EntityMergingGraph.js
EntityMergingGraph
类继承自基类Graph
,并接收两个参数,具体含义如下: 该类的作用与
LinkMergingGraph
的作用类似。
OriginalGraph.js
OriginalGraph
类继承自基类Graph
,并接收三个参数,具体含义如下: 在该类中,会执行添加、删除、隐藏、展示子图的操作,以及设置实体与链接的样式。
图层总结
经过上面的概述可以发现,对实体链接的许多操作在很多图层里同时存在,那么真实调用的顺序是怎么样的?以全部显示showAll
方法为例: 首先分为两个阶段,分别是图层初始化阶段与方法调用阶段。
可以看到,在第二步与第三步的时候,都监听了graph
实例对象的changed
事件。而在Graph.js
中,会监听FinalGraph
实例上的changed
方法,同时触发回调函数,在回调函数内会广播graph
实例上的changed
事件,所有监听了graph
实例对象changed
事件的地方都会响应。
在代码执行阶段,首先在analyticGraph.vue
里会调用this.chart.execute('showAll')
,然后会经由CommandManager
、FinalGraph
、OriginalGraph
,在OriginalGraph
里触发showAll
,在Graph.js
里去执行showLink
方法。接下来广播changed
事件,在FinalGraph
里会捕获该事件并广播changed
事件。由上图第五步可知,渲染层的Graph.js
会去监听FinalGraph
实例上changed
事件,因此最终会触发第二、三步里的回调,执行onGraphChanged
事件,进行webGL
的渲染。
两个Graph的区别
注意这里跟Chart
打交道的Graph
与渲染使用的Graph
是两个不同的文件,与Chart
相关的Graph
主要是存储实体与链接并对其进行操作,与渲染相关的Graph
主要是监听事件,并通知到各个图层去执行具体的方法。
上图时间分析
页面初始化
首先,云析页面初始化的时候,WebGL已初始化完毕,包括NodeContainer
和LinkContainer
已经把实体和链接所需的纹理与顶点缓存至pixiRenderer
当中,使用的是pixi
提供的pixi-gl-core
库。初始化webGL的时间在100ms左右。
上图中
然后在上图的过程中,以1w实体,2w链接为例,整体的时间为4s。其中耗时较多的执行方法包括,
OriginalGraph
里的添加子图方法addSubGraph
耗时160msForceLayoutInNGraph.js
里监听graph
变化的回调函数onGraphChanged
耗时146msForceLayoutInNGraph.js
里与默认布局有关的方法step
耗时312mspixiRenderer.js
里的onGraphChanged
函数耗时2.2spixi.js
里内置的渲染webGL的方法WebGLRenderer.js
耗时848ms- 云析UI页面
analyticGraph.vue
里监听graph变化的函数graphChangeListener
耗时150ms
优化方向:
1、优化默认布局相关代码,缩减2、3的耗时; 2、
pixiRenderer.js
里的onGraphChanged
占总耗时的一半以上,是因为针对上图的每个实体和链接都进行了初始化的操作,考虑将初始化的操作放入js原生提供的web worker
内,采用额外的线程执行代码,防止阻塞主线程,该方案需要进一步研究;
上图后
上完图后,当数据量较多的时候,针对实体的拖拽等操作会有明显的卡顿,经查看浏览器的火焰图发现,是因为pixi.js
的核心库InteractionManager
会不断的监听鼠标的事件,包括鼠标移动、单击落下、单击抬起的操作,当鼠标移动的时候,会不断的触发onPointerMove
事件,该事件每次执行时间在70-100ms之间,阻塞了前端动画事件的执行,由此产生卡顿。
优化方向:
1、尝试修改自定义事件文件
customizedEventHandling.js
的逻辑,优化事件的监听,避免频繁触发,产生阻塞。
总结
总体来看,优化方向不涉及到webGL的优化,主要还是以修改业务代码为主;在pixijs
的基础上,对性能进行优化。