问题

目前 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: middlePlot
      • group: geometry1
        • shape
      • shape
        • group: geometry2
        • shape
      • shape
        • group: geometry1-labels
        • group: geometry2-labels
    • group: frontPlot
      • group: legend1
        • group: legend2
  1. - view 的情况下同上面的结构一样, view 层并没有 group 分组,出发点是保证所有 labels 都在最上面不被遮挡。
  2. <a name="9Erho"></a>
  3. #### 4.x 的组织方式
  4. 4.x 中会在 geometry 图形的 shape 中间增加一层概念 Element,其组织结构
  5. ```javascript
  6. - canvas: chart
  7. - group: plotRange (背景)
  8. - group: backPlot
  9. - group: axis-x
  10. - group: axis-y
  11. - group: middlePlot
  12. - group: geometry1
  13. - group: element1
  14. - shape
  15. - shape
  16. - label
  17. - group: element2
  18. - shape
  19. - shape
  20. - label
  21. - group: geometry2
  22. - group: element1
  23. - shape
  24. - shape
  25. - group: element2
  26. - shape
  27. - shape
  28. - group: geometry1-labels
  29. - group: geometry2-labels
  30. - group: frontPlot
  31. - group: legend1
  32. - 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’ });

  1. - 上面列了两种方案,各有利弊
  2. <a name="qVZsN"></a>
  3. ##### tooltip
  4. 目前 tooltip 仅提供了内容级的扩展,扩展方式并不友好。不同的 geometry,不同的 view 上的 tooltip 定义没有放开,组件层次想显示 tooltip 怎么办?<br />大家一起思考一下
  5. tooltip 的行为其实可以跟 interaction 组合起来,没有写死的固定方案,仅有一个 interaction ,上层可以复写或者重写。
  6. <a name="4tzC8"></a>
  7. #### legend
  8. 谁来扩展 legend?貌似legend 就只有那么几种形式,更多的功能的扩展:
  9. - 分页、滚动、下拉等
  10. - 多维度的图例支持
  11. 内容的扩展在前面的版本中已经实现,但是不太优雅。实现单个字段的绘制简单,多个字段的显示就非常麻烦,由于上下文中缺少分类对应的整个条数据的信息。
  12. <a name="zHAK1"></a>
  13. #### axis 的扩展
  14. 目前想得到的是增加一些特殊的坐标轴:
  15. - 分组坐标轴
  16. - 不均匀坐标轴(中断的坐标轴)
  17. <a name="OO2CN"></a>
  18. #### guide 的扩展
  19. guide 的扩展,主要是一些同业务有关的辅助元素不适合直接放入到 G2 中来,例如:
  20. - 同统计值相关的最大值、最小值、95% 区间等
  21. - 个性化的 guide,例如表示两条数据间的变化
  22. - 复杂 path guide
  23. 这类问题是业务上很痛的点,但是一直没找到好的方式,一直拖着。
  24. <a name="Upu03"></a>
  25. #### labels 的扩展
  26. 目前方案已经不错,继续细化,在不同的 geometry 上单独实现即可。
  27. <a name="PScs2"></a>
  28. #### 其他组件
  29. slider 是用户使用频率很高的组件,可以考虑加强这个组件的地位。 timeLine 可能是时序可视化的一个必备组件。
  30. <a name="XjqBB"></a>
  31. ### 事件机制
  32. 目前 G2 支持两层的事件机制:
  33. - canvas
  34. - view
  35. 事实上还有两层常用事件:
  36. - 组件层
  37. - shape
  38. 目前这两层是通过委托方式在 canvas view 层实现的:
  39. ```javascript
  40. canvas.on('axis-label:click', ev=> {});

这种方案的优缺点同样明显:

  • 统一收口到 canvas 和 view,写法比较统一,性能比较好
  • 不能识别具体组件的事件,必须通过额外的信息来判定
  • 通过在 shape 上添加 name 的方案,非常 hack 而且很容易出错

方案的调整

在 G2 4.0 中前两层的事件继续支持,但是需要做以下调整:

  • 不再使用 shape.name 标识元素,更加统一的方式来标记元素
  • 目前是否在 view 上触发事件的逻辑需要修改,很容易出错

组件层和 shape 层的事件支持,可以放出来:

  • 在每个组件上增加一个 events 的配置项,可以监听组件自定义的事件
  • 自定义 shape 时可以直接添加事件

组件上绑定事件

  1. chart.axis('xxx',{
  2. events: {
  3. click: function(ev) {},
  4. change: function(ev) {},
  5. // 自定义标记的事件是否在这里抛出可以再讨论
  6. 'axis-label:click': function(ev) {}
  7. }
  8. });
  9. chart.guide().line({
  10. events: {
  11. mouseenter: ev => {},
  12. mouseout: ev => {}
  13. }
  14. });

图形上绑定事件

  1. G2.registerShape('xxx', {
  2. draw(cfg, group) {
  3. const shape = group.addShape({
  4. ...
  5. });
  6. shape.on('click', ev=> {
  7. });
  8. return shape;
  9. }
  10. });
  11. // 也可以,通过标记来触发事件
  12. G2.registerShape('xxx', {
  13. draw(cfg, group) {
  14. const shape = group.addShape({
  15. name: 'custom-xxx'
  16. });
  17. return shape;
  18. }
  19. });
  20. chart.on('custom-xxx:click', ev=> {
  21. });

动画机制

在 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 等

    1. chart --> view --> geometry --> element
    2. | | |
    3. canvas-----------> group ----> group ----> shape

更多

仔细思考 G2 定位,就是图形语法、交互语法、统计语法 ,我们能够给用户提供的是:

  • 不受限制的视觉映射
  • 个性化定制的图形
  • 统一的交互模式
  • 舒服的动画体验

最终的形态可能 G2 这一层非常稳定,它的周边却非常丰富,要想做到这一点,一切都需要精雕细琢。