问题
目前 G2 4.0 的代码已经都改成 ts,从功能上来看仅剩下 facet 的部分功能未实现,但是一些细节还需要调整,主要体现在:
- 图表画布的组织方式
- view 的无限嵌套
- 扩展机制的统一
- 事件机制的调整
- 动画机制的梳理
- 自定义 shape 的思考
画布的组织方式
chart、view、geometry、shape 和 components 同 canvas、group、shape 的组织关系梳理出来,整体看看是否合理。这些结构决定了很多方案的实施,会对事件机制、动画机制都有影响。
3.x 的组织方式
- 没有 view 的情况下 ```
- canvas: chart
- group: plotRange (背景)
- group: backPlot
- group: axis-x
- group: axis-y
- group: axis-x
- group: middlePlot
- group: geometry1
- shape
- shape
- group: geometry2
- shape
- shape
- group: geometry1-labels
- group: geometry2-labels
- group: geometry1
- group: frontPlot
- group: legend1
- group: legend2
- group: legend1
- 有 view 的情况下同上面的结构一样, view 层并没有 group 分组,出发点是保证所有 labels 都在最上面不被遮挡。
<a name="9Erho"></a>
#### 4.x 的组织方式
4.x 中会在 geometry 和 图形的 shape 中间增加一层概念 Element,其组织结构
```javascript
- canvas: chart
- group: plotRange (背景)
- group: backPlot
- group: axis-x
- group: axis-y
- group: middlePlot
- group: geometry1
- group: element1
- shape
- shape
- label
- group: element2
- shape
- shape
- label
- group: geometry2
- group: element1
- shape
- shape
- group: element2
- shape
- shape
- group: geometry1-labels
- group: geometry2-labels
- group: frontPlot
- group: legend1
- group: legend2
element 是一个 group,可以在其中创建多种图形,可以增加文本,但是会存在一些问题:
- 文本会其他 element 的图形相覆盖,
- 文本的布局不容易做
一个折中的方案,是把所有的文本放到另一个并行的 group 中,既可以解决遮挡问题,也可以解决布局问题。
view 的嵌套
在 2.x 版本后期增加了 view 的无限嵌套,但是事实的价值没有发挥出来,就进行了 3.x 的改造,自此就仅有 chart 和 view 两级当时的出发点如下:
仅两层非常容易处理 chart 和 view 的差异,例如图例、tooltip 在chart 层上管理,axis, guide 等在各个 view 上都有。
- auto padding 等功能仅在 chart 上考虑
view 实现无限级嵌套存在以下的问题:
- 度量的统一问题
- 刷新问题,在数据源不变化的时候刷新比较简单,紧跟自己的 view 相关,但是一旦数据发生改变,因为度量需要统一,需要在父 view 上刷新,可能会引起整个 chart 的刷新。在有些场景下 chart 层面的刷新性能太差。
- 组件的组织问题,是否每个 view 都可以有独立的 tooltip,legend
- 导致 auto padding 功能更加复杂
扩展机制
G2 提供的扩展包括:
- 自定义 shape 的扩展
- 交互的扩展
- 组件的扩展
- 动画的扩展
- scale、coord 等数据映射层的扩展
之前在 3.x 版本中,对一般用户仅开放了 自定义 shape、自定义动画和自定义交互的扩展功能,但事实上仅自定 shape 用户熟悉和使用。在 4.0 版本中对外抛出所有的扩展机制,需要考虑统一性、易理解、好实现、易用性,否则只会带来使用上的困难。
组件的扩展机制
这里主要讲组件的扩展,交互、动画扩展在下面的章节考虑,其他扩展暂不讨论。
组件的扩展主要包括:
- tooltip 的扩展
- legend 的扩展
- axis 的扩展
- guide 的扩展
- labels 的扩展
- 其他组件:slider, timeLine 等
这些扩展都需要考虑几个问题:
- 默认提供什么样的方案?
- 谁来扩展?
- 怎么扩展?
-
统一的扩展机制
内容扩展,通过配置项、回调函数扩展
- 整体类的替换,需要注册机制和类的继承机制
- 扩展的接口需要仔细设计一下,否则会同扩展 shape 和扩展 interaction 给用户的感受不一致。 ```javascript // 一种扩展方案 class CustomAxis extends G2.Axis.Line {
} // 注册可以通过接口 G2.registerAxis(‘custom’, CustomAxis); // 也可以绑定到基类的命名空间上 G2.Axis.Custom = CustomAxis;
// 另一种扩展方案 G2.registerAxis(‘custom’, { // 我们希望用户扩展的接口 });
// 使用 chart.axis(‘xxx’, { type: ‘custom’ });
- 上面列了两种方案,各有利弊
<a name="qVZsN"></a>
##### tooltip
目前 tooltip 仅提供了内容级的扩展,扩展方式并不友好。不同的 geometry,不同的 view 上的 tooltip 定义没有放开,组件层次想显示 tooltip 怎么办?<br />大家一起思考一下
tooltip 的行为其实可以跟 interaction 组合起来,没有写死的固定方案,仅有一个 interaction ,上层可以复写或者重写。
<a name="4tzC8"></a>
#### legend
谁来扩展 legend?貌似legend 就只有那么几种形式,更多的功能的扩展:
- 分页、滚动、下拉等
- 多维度的图例支持
内容的扩展在前面的版本中已经实现,但是不太优雅。实现单个字段的绘制简单,多个字段的显示就非常麻烦,由于上下文中缺少分类对应的整个条数据的信息。
<a name="zHAK1"></a>
#### axis 的扩展
目前想得到的是增加一些特殊的坐标轴:
- 分组坐标轴
- 不均匀坐标轴(中断的坐标轴)
<a name="OO2CN"></a>
#### guide 的扩展
guide 的扩展,主要是一些同业务有关的辅助元素不适合直接放入到 G2 中来,例如:
- 同统计值相关的最大值、最小值、95% 区间等
- 个性化的 guide,例如表示两条数据间的变化
- 复杂 path 的 guide
这类问题是业务上很痛的点,但是一直没找到好的方式,一直拖着。
<a name="Upu03"></a>
#### labels 的扩展
目前方案已经不错,继续细化,在不同的 geometry 上单独实现即可。
<a name="PScs2"></a>
#### 其他组件
slider 是用户使用频率很高的组件,可以考虑加强这个组件的地位。 timeLine 可能是时序可视化的一个必备组件。
<a name="XjqBB"></a>
### 事件机制
目前 G2 支持两层的事件机制:
- canvas 层
- view 层
事实上还有两层常用事件:
- 组件层
- shape 层
目前这两层是通过委托方式在 canvas 和 view 层实现的:
```javascript
canvas.on('axis-label:click', ev=> {});
这种方案的优缺点同样明显:
- 统一收口到 canvas 和 view,写法比较统一,性能比较好
- 不能识别具体组件的事件,必须通过额外的信息来判定
- 通过在 shape 上添加 name 的方案,非常 hack 而且很容易出错
方案的调整
在 G2 4.0 中前两层的事件继续支持,但是需要做以下调整:
- 不再使用 shape.name 标识元素,更加统一的方式来标记元素
- 目前是否在 view 上触发事件的逻辑需要修改,很容易出错
组件层和 shape 层的事件支持,可以放出来:
- 在每个组件上增加一个 events 的配置项,可以监听组件自定义的事件
- 自定义 shape 时可以直接添加事件
组件上绑定事件
chart.axis('xxx',{
events: {
click: function(ev) {},
change: function(ev) {},
// 自定义标记的事件是否在这里抛出可以再讨论
'axis-label:click': function(ev) {}
}
});
chart.guide().line({
events: {
mouseenter: ev => {},
mouseout: ev => {}
}
});
图形上绑定事件
G2.registerShape('xxx', {
draw(cfg, group) {
const shape = group.addShape({
...
});
shape.on('click', ev=> {
});
return shape;
}
});
// 也可以,通过标记来触发事件
G2.registerShape('xxx', {
draw(cfg, group) {
const shape = group.addShape({
name: 'custom-xxx'
});
return shape;
}
});
chart.on('custom-xxx:click', ev=> {
});
动画机制
在 g2 2.x 就增加了自定义动画的能力,可以让用户注册自己的动画,但是发现并不实用,原因在于:
- 概念太多,
- 开放的接口太模糊
- 矩阵操作用户并不熟悉
geometry的动画
就 geometry 的动画来说,目前最好的方式还是每种图表类型定制动画最好,但是需要对 plot 层的接口更加友好。目前存在的一些问题:
- 动画太生硬,很多图表更新动画的体验很差,折线图、饼图
- 不完全遵循 动画的一些原则
- 坐标系转换、geometry 切换的动画没有精心设计
组件的动画
我们的一些图动画效果不好有部分原因是组件的动画同 geometry 的动画不同步导致的:
- 坐标轴上 tick、text、grid 的动画都要跟随图表的主要图形变化
- guide ,labels 元素也要有对应的动画
自定义 shape
看了步茗分享的 powerBI 团队的工作,发现我们除了 link 机制没想好外,通过图形拖拽的方式自定义 shape 其实已经成熟。
但是在交互语法层面,目前的方案需要进行一些调整:
- 直接对外暴露出 geometry 下一层的概念 geometry-shape?
- 每个 geometry-shape 使用一个 group 组织,可以附加事件,可以进行一些更通用的行为,active,selected,drag 等
chart --> view --> geometry --> element
| | |
canvas-----------> group ----> group ----> shape
更多
仔细思考 G2 定位,就是图形语法、交互语法、统计语法 ,我们能够给用户提供的是:
- 不受限制的视觉映射
- 个性化定制的图形
- 统一的交互模式
- 舒服的动画体验
最终的形态可能 G2 这一层非常稳定,它的周边却非常丰富,要想做到这一点,一切都需要精雕细琢。