@逍为(hustcc)

背景

没有丰富的事件,就没有可视化图表的交互。G2 基于 G 实现,G 底层会包装好一些原子粒度的事件,比如 mousedown、mousemove、touch、click 等等。

原子粒度的事件,使用起来灵活多变,但是并不方便。所以 G2 层的事件,是指在 G 事件的基础上,在增加一层包装,让事件具备一些可视化含义。

View 继承自 @antv/event-emitter,view 的事件主要包含:

  1. 生命周期事件:便于上层开发者进行在对应的周期,做自己事情。
  2. 组件和图形标记事件:便于做交互开发。

Event 类

event 类中,包含有事件的信息,主要包含:

  • view:所属的 View
  • target:事件触发的 Shape
  • gevent:G 的 Event
  • event:原生的 dom 事件
  • data?:携带的一些数据
  • x y clientX clientY

生命周期

生命周期可以标记一个 view 的过程,目前的生命周期包含:

  1. export enum ViewLifeCircle {
  2. BEFORE_RENDER = 'beforerender',
  3. AFTER_RENDER = 'afterrender',
  4. BEFORE_CHANGE_DATA = 'beforechangedata',
  5. AFTER_CHANGE_DATA = 'afterchangedata',
  6. BEFORE_CLEAR = 'beforeclear',
  7. AFTER_CLEAR = 'afterclear',
  8. BEFORE_DESTROY = 'beforedestroy',
  9. }

目前的生命周期阶段仅仅包含四类:

  • 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 的事件,就可以拿到所有的组件事件了。

  1. this.foregroundGroup.on('*', this.onEvents);
  2. this.middleGroup.on('*', this.onEvents);
  3. this.backgroundGroup.on('*', this.onEvents);

监听的这些事件,既包括原子事件,也包括有 name:event 组合事件。

这样的写法,可以让我们无需感知 view 中有哪些 Component、Geometry,是要组件按照标准来写,就可以被 view 代理到。当然需要对 @antv/event-emitter 增加 * 事件名支持。

  • 冒泡机制

当前 view 监听到事件之后,将事件向上层 view 逐级遍历 emit 即可实现事件的冒泡即可。

所以一个 view 监听到的事件,包括两类:

  1. 自己监听自己的三层 Group 事件
  2. 子 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)