背景
没有丰富的事件,就没有可视化图表的交互。G2 基于 G 实现,G 底层会包装好一些原子粒度的事件,比如 mousedown、mousemove、touch、click 等等。
原子粒度的事件,使用起来灵活多变,但是并不方便。所以 G2 层的事件,是指在 G 事件的基础上,在增加一层包装,让事件具备一些可视化含义。
View 继承自 @antv/event-emitter,view 的事件主要包含:
- 生命周期事件:便于上层开发者进行在对应的周期,做自己事情。
- 组件和图形标记事件:便于做交互开发。
Event 类
event 类中,包含有事件的信息,主要包含:
- view:所属的 View
- target:事件触发的 Shape
- gevent:G 的 Event
- event:原生的 dom 事件
- data?:携带的一些数据
- x y clientX clientY
生命周期
生命周期可以标记一个 view 的过程,目前的生命周期包含:
export enum ViewLifeCircle {
BEFORE_RENDER = 'beforerender',
AFTER_RENDER = 'afterrender',
BEFORE_CHANGE_DATA = 'beforechangedata',
AFTER_CHANGE_DATA = 'afterchangedata',
BEFORE_CLEAR = 'beforeclear',
AFTER_CLEAR = 'afterclear',
BEFORE_DESTROY = 'beforedestroy',
}
目前的生命周期阶段仅仅包含四类:
- render
- change data
- clear
- destroy
和 3.x 的生命周期略有很大的不同,生命周期不求多、全、细粒度,而是要确定哪些场景需要哪些生命周期。
组件 & Geometry 事件
这部分才是我们通常意义的事件。
前面我们知道 View 是嵌套结构,每 view 中有:
- 子 view
- Geometry
- Component
其中 Geometry 和 Component 都是基于 G.Group 的一些 UI 组件,这些组件都绘制在 View 的上中下三层 Group 中。
G 事件机制
我们先了解一下 G 事件的两个大机制:
- 冒泡
G 的原子粒度事件,会从 shape 逐一向上冒泡到最顶层的 Group,也就是 Canvas。
- name:event
如果 shape 有 name 属性,那么当触发原子粒度事件 event 的时候,会同时 emit 一个名为 name:event
的事件。
G2 事件机制
那么在这两个机制的保障下,View 的事件可以做的非常简单。因为 View 是一个层级嵌套结构,所以同样,会提供事件冒泡机制。
- 监听事件
View 中的所有事件,都最终会冒泡到 view 的上中下三层 Group 中,所以只要监听三层 Group 的事件,就可以拿到所有的组件事件了。
this.foregroundGroup.on('*', this.onEvents);
this.middleGroup.on('*', this.onEvents);
this.backgroundGroup.on('*', this.onEvents);
监听的这些事件,既包括原子事件,也包括有 name:event 组合事件。
这样的写法,可以让我们无需感知 view 中有哪些 Component、Geometry,是要组件按照标准来写,就可以被 view 代理到。当然需要对 @antv/event-emitter 增加 * 事件名支持。
- 冒泡机制
当前 view 监听到事件之后,将事件向上层 view 逐级遍历 emit 即可实现事件的冒泡即可。
所以一个 view 监听到的事件,包括两类:
- 自己监听自己的三层 Group 事件
- 子 view 冒泡上来的事件
Plot 事件
G2 这层还需要自己封装下:plotenter、plotleave 和 plotleave 事件,用于但不仅限于以下场景:
- tooltip 的触发(是鼠标进入绘图区域后触发)
- 框选行为也是应该进入绘图区域后才触发的
备注:建议事件名不做特殊化,名字依然使用 plot:mouseenter、plot:mouseleave、plot:click 这样的规范命名。
实现方案:
- View Coordinate Rect Background
通过一个透明背景的 Rect,大小等于 Coordinate BBox 大小,然后监听这个 Rect 的事件,从而获得所有的 plot 事件。
优点:可以除了 enter、move、leave 事件之外,所有的事件 click、mouse、touch 等,且命名可以和 Component、Geometry shape 的事件都保持一致。
缺点:可能被 Geometry 遮挡
- BBox 数学计算
通过计算鼠标点和 Coordinate BBox 来计算是否在其中,来决定是否触发方法,然后去 emit 事件。
优点:多 view 层叠可以监听多 view。
缺点:事件需要遍历,目前就支持 mouse 事件。
建议:暂时采用第二种,后续有功能需求无法满足再看。
TODO
目前 View 事件机制已经完成了,但是前提有:
- G 4.0 支持好 name:event 事件机制
- Geometry 中的 shape 有 name 属性
- Component 中的 shape 都有 name 属性
- Component 组件按照规范开发
- event-emitter 包支持 * 事件匹配机制
- 联调 Geometry、Component
- plot 事件(plot:mouseenter、plot:mousemove、plot:museleave)