从用户的角度来看,G2 4.0 要释放的亮点主要有两个:
- 图表交互的各种可能性
- 全新的图表组件
为了支撑以上两点,G2 4.0 在架构上需要进行的改造主要包括:
- Chart、View、Geometry、Shape、Component 概念以及组织结构的梳理,同时引入 Element 概念
- 支持数据更新机制
- View 的嵌套支持
- 图表组件
- 事件 && 交互机制
- 自定义 Shape
1. 图元结构
在 G2 3.x 架构中,我们设计了如下的概念:
- Chart,作为 G2 图表的统一入口,负责画布、View 的创建以及图表整体的渲染
- View 视图,包含自己的数据源、坐标系,负责数据的装载、图表组件与几何标记的组装、图表组件的各种配置,以及图表生命周期的管理
- Geometry 几何标记,图形语法的核心层,处理数据到图形的映射逻辑。Coordinate、Scale、Attribute、Adjust 模块都是服务于这一映射逻辑的。
- Shape 图形,最后绘制到画布的几何图形
- Component 图表组件,包含坐标轴 Axis、图例 Legend、提示信息 Tooltip、静态辅助标记 Annotation
而 Chart、View、Geometry 这三个都是抽象概念,只有 Shape 属于真正的实体,从用户的角度出发,这些概念可能因为过于抽象而不好理解,另外因为一条数据对应的图形元素可能会有多个 shape,之前无论是交互还是动画,都是以 shape 为操作单位,所以导致很多交互和动画实现得非常繁琐甚至无法完成,所以在 G2 4.0 中决定将目前这些概念以及组织结构做一层梳理,将 Chart、View、Geometry、Shape、Component 同 G 中 Canvas、Group、Shape 进行统一:
- Chart、View、Geometry 仍作为抽象概念,负责画布创建、数据装载、组装、生命周期管理以及数据处理等。
- Component 实现为 Group
- Geometry 中引入 Element 图形元素的概念,同时 Element 也实现为 Group,即将一份数据对应的所有 shape 都作为 Element,以 Element 作为操作单位
以下是 G2 3.X 的图层结构,并不以各个 View 来组织 Group,而且每个 group 的创建都是由 View 和 Geometry 实例在内部进行创建 此次 4.0 的重构中,将画布的组织结构调整如下,同时 Geometry 和 Component 也直接作为 Group 子类进行实现,同时加入 Element 概念,将一份数据对应的所有 shape 称为一个 Element。
2. 数据更新机制
我们可以将引起图表更新的行为概括为以下四种:
数据源更新 chart.changeData()
虽然是更新整个数据源,但是数据仍然是同构的,会引起 Component、Element 的更新
View -> Geometry -> Element组件的交互操作
- 会引起数据的变化(数据的过滤、排序等操作,比如图例、slider 等组件),进而引起 Component、Element 的更新。这种情况 View 的数据源不会发生变化,但是会在 View 做数据相应的操作(过滤),然后再传入给每个 Geometry,引起每个 Geometry 的数据更新
- Element 的状态变化:显示隐藏,交互状态的更新
- 组件本身变化:通常还会关联 Element 的变化
交互行为,如框选、拖拽等。交互行为将会引发:
- Component、Element 的图形样式变化
- Element 状态变化
- Element 数据变化,进而引起 Component、Element 的更新。Element 本身的数据更新引起 view data 的更新
Geometry 等的映射规则变化
- mapping 规则的变化
- Geometry 类型的变化
从以上行为引起的更新进行总结,我们可以将 G2 图表上的更新分为三大类:
- 数据更新:数据源更新、数据过滤、Element 局部数据更新
- 图形状态更新:组件、Element 的状态以及样式变化
- 映射规则的更新:几何元素的变化和图形属性的映射变化
目前的设计先不考虑第 3 点,针对 1、2 两点,对 Geometry 层的数据流设计如下,相比于现有数据流,只是中间多了一步 Element 的创建。
数据更新机制描述
- 第一次初始化绘制的时候维护一份 map 映射,存储每一条数据同 Element 的对应关系
- 当发生数据变化时,Geometry 进行更新,对更新后的每条数据根据同样的 id 策略生成一份 id 集
- 通过新老 id 的对比,获取发生增、删、改的id:
- 增,新生成 Element
- 删,删除对应的 Element
- 改,更新对应的 Element
- 具体的更新逻辑在 Shape 层进行实现,同时变更时的动画也可以在每个具体的 shape 进行实现
以上数据流的改造,需要引入 Element 类之外,还需要对自定义 Shape 进行重构
Element
Element 除了负责 Shape 的创建外,还应该支持:
style()
样式的更新setState()
状态的更新update()
数据的更新,当 Element 上的数据发生变化时,需要设计一种机制,通知上层 View 进行数据更新,走数据更新逻辑。
🤔🤔🤔讨论点 批量更新 Elements 怎么办?即同时调用多次 element.update()
自定义 Shape
draw(cfg: object, element: Element)
绘制 ShapesetState(type: string, value: boolean, element: Element)
响应状态量update(cfg: object, element: Element)
更新 Shaperemove(element: Element)
用于配置销毁动画(这个待讨论,加这个接口主要用于支持 Shape 销毁动画的配置)
🤔🤔🤔讨论点 因为支持了更新机制,那么完全可以在自定义 Shape 层定义对应的出场、入场、更新动画,状态量发生变更的时候也可以设置动画,但是就需要考虑:
- 不同坐标系下的 shape 动画怎么区分,因为我们 Shape 是使用关键点进行绘制的,完全不关心坐标系的概念,但是不同坐标系对应的动画形态是不同的,这个时候就要根据坐标系类型区别对待了,比如柱状图和玫瑰图的动画就不同
- 如何支持自定义动画?我们默认会为自定义的 Shape 定制动画,但是如果用户想要自定义怎么办
- Geometry 的群组动画怎么支持?
Geometry Label
🤔🤔🤔讨论点 目前 label 和 shape 是属于不同的 group 的,是否要将 label 同 group 保持在同一层?即直接在 Element 上绘制,但是这样的话 Label 的遮挡问题怎么解?另外还有 Label 的动画。
- 将 label 放置在 Element?
- 添加一个 Text Geometry 专门绘制文本?
3. View 的嵌套设计
Chart 和 View 的关系与 3.x 保持一致,但是我们会在 4.0 中支持 View 的嵌套设计。对于 View 的嵌套比较纠结点讨论确定如下:
- 组件的归属
之前比较纠结的点是 View 和 Component 的归属关系,通过讨论确定如下:
- 每个 View 拥有自己的 Axis、Tooltip、Annotation
- Legend 由 Chart 来统一管理,我们只支持 80% 的场景(80% 的场景多 View 的数据都是关联的)
Auto padding 的计算也由 Chart 层来统一计算,但是也开放 View 层进行 padding 设置,保持概念的统一。
![image.png](https://cdn.nlark.com/yuque/0/2019/png/98090/1567085574087-19944607-28cf-4047-adb0-20dce59d39c8.png#align=left&display=inline&height=278&name=image.png&originHeight=652&originWidth=1322&size=48769&status=done&width=563)
另外我们规划的 Component Layout 也自然就放置在 Chart 层。
4. 图表组件
图表组件这一期的改造包括:
- 提升默认提供的图表组件的质量,默认提供的图表组件包括:Axis 坐标轴、Tooltip 提示信息、Legend 图例、Annotation 静态标注、Slider 滑块、Timeline 时间轴
- 组件的插拔机制,这种机制需要支持:
- 装载自定义组件
- 组件使用的选择权,即用户在使用 G2 的图表时,可以选择使用哪些组件(G2 默认的和自定义的),生成哪些组件。
- 自定义组件
G2 默认提供的图表组件包括:
- Axis 坐标轴
- Tooltip 提示信息
- Legend 图例
- Annotation 静态标注
- Slider 滑块
- Timeline 时间轴
对于默认组件,此次 4.0 必须在功能、体验上都要投入时间做精做优。
在 4.0 的图表组件,涉及到生成、布局、更新、交互四点。
- 对于生成,首先明确的是组件将实现为纯 GUI,组件就是一个 Group
- 组件和图表的交互都会通过交互行为实现
- 组件需要提供更新接口
- 组件的动画也在组件内部自己实现
- 组件的布局通过上层的 Component layout 进行统一管理
自定义组件
自定义组件包括两种场景:
- 用户定制新类型组件,比如 Title。这类需求通过提供 View 上细粒度的生命周期事件,让用户将自己编写的组件装载进图表
- 基于现有组件进行扩展,比如加入一种新的 Axis 类型,这就需要根据现有的 Axis 基类去继承实现了。
5. 事件 && 交互
事件
目前 G 层已经对事件进行了改造,所以事件这块直接就在 Chart 层进行透传就好了,所以事件这层也在 Chart 层进行管理了。
- 参见 G 4.0 的事件设计:链接