业务场景

流程图或树图中,节点中包含丰富的信息。
image.png

图片来自 http://aperturejs.com/demos/,如有侵权,请联系 @聚则(moyee-bzn) 删除。

可选方案

要完成上面的需求,我们可以通过以下三种方案来实现。

使用 DOM 或 SVG 实现

优势

  1. 实现起来比较方便,每个节点就是一个 div,图表可以使用 G2plot / G2 / BizChart 等。

不足

  1. 布局需要自己实现,成本较高;
  2. 展开收起的逻辑需要自己实现,成本较高;
  3. 节点数量多了以后,会遇到性能瓶颈。

使用 X6 实现

优势

  1. 实现起来比较方便,每个节点就是一个 DOM 元素,图表可以使用 G2plot / G2 / BizChart 等;
  2. 展开收起逻辑已内置,可以直接使用。

不足

  1. 布局能力弱,二次开发体验差;
  2. 节点数量多了以后,会遇到性能瓶颈。

使用 G6 实现

优势

  1. 有丰富的布局可选,且支持自定义布局;
  2. 展开收起逻辑已内置,可以直接使用;
  3. 相比 DOM 或 SVG,性能较好。

不足

  1. 已解决节点中定义折线图、面积图上手难度大、成本高;
  2. 已解决官方没有提供节点中嵌入 G2plot / G2 的实践及方案,不确定 G6 中的节点是否支持使用 G2 中的图表。

G6 的选择

G6 中的节点能否支持 G2plot 或 G2 中的图表,如果能够支持,选择 G2plot 还是 G2?
通过调研以后,我们选择 G2,原因如下:

  1. G2plot 不够稳定,很可能要被重写,G6 现在接入 G2plot 风险比较大,且 G2plot 的设计导致 G6 根本接入不了
  2. G2 更偏向底层,且 G2 按 chart、view、shape、geometry 的分层设计更灵活;
  3. G2 中 view、shape、geometry 等支持将 G 的 canvas 及 group 作为容器,可以复用 G6 中实例化的 canvas 及 group。

目标

自定义节点时,支持接入 G2 的折线图、面积图等图表。
官方提供接入方案及案例。

方案

先来看一个 G2 的 demo。
G6 中如何使用 G2 的图表 - 图2

上面的示例,我们可以通过实例化 View 的方式来实现。
G6 中如何使用 G2 的图表 - 图3

也可以通过实例化 Interval 的方式来实现。
G6 中如何使用 G2 的图表 - 图4

通过上面的 Demo,我们可以发现:

  1. 通过实例化 Chart 的方式,代码是最简洁的,但实例化时需要传入一个 DOM 元素或 ID 作为容器,而 G6 的节点不支持 DOM 元素;
  2. 通过实例化 View 的方式,能够实现和实例化 Chart 完全一样的功能,唯一麻烦的就是需要我们手动注册 geometry 和 componentController,但实例化时支持将 G 的 canvas 作为容器,这里就可以复用 G6 中已经实例化的 canvas;
  3. 通过实例化具体的 Geometry 来的方式来实现,实例化 Geometry 时,也可以复用 G6 中已经实例化的 canvas,但这种方式需要用户定义的地方比较多,且不支持所有的 component,如坐标轴、tooltip、legend 等。

从上的分析可以看出,在 G6 中使用 G2 中图表,根据具体需求的不同,有两种实现方式:View 和 Geometry,推荐使用 View

从 View 层接入

关键点:

  1. 实例化 View:使用 G6 中实例化的 canvas,创建三个 group;
  2. 绑定数据;
  3. 选择图表类型,并设置映射关系;
  4. 其他配置:坐标轴是否显示、legend 是否显示等;
  5. 渲染。

    1. G6.registerNode('node-with-bar', {
    2. draw(cfg, group) {
    3. const canvas = group.get('canvas')
    4. const keyShape = group.addShape('rect', {
    5. attrs: {
    6. x: 0,
    7. y: 0,
    8. width: 400,
    9. height: 200,
    10. fill: '#e6f7ff'
    11. }
    12. })
    13. // 其他部分省略
    14. const backgroundGroup = group.addGroup();
    15. const middleGroup = group.addGroup();
    16. const foregroundGroup = group.addGroup();
    17. const view = new View({
    18. parent: null,
    19. canvas,
    20. foregroundGroup,
    21. middleGroup,
    22. backgroundGroup,
    23. padding: 5,
    24. visible: true,
    25. region: {
    26. start: {
    27. x: 0.01,
    28. y: 0.2
    29. },
    30. end: {
    31. x: 0.8,
    32. y: 0.35
    33. }
    34. },
    35. });
    36. const data = [
    37. { genre: "Sports", sold: 275 },
    38. { genre: "Strategy", sold: 115 },
    39. { genre: "Action", sold: 120 },
    40. { genre: "Shooter", sold: 350 },
    41. { genre: "Other", sold: 150 }
    42. ];
    43. view.data(data);
    44. view
    45. .interval()
    46. .position("genre*sold")
    47. .color("genre");
    48. view.legend("genre", false);
    49. view.axis('sold', false)
    50. view.render();
    51. return keyShape
    52. }
    53. })

    从 Geometry 层接入

    关键点:

  6. 实例化 Geometry:

    1. 数据;
    2. 容器:支持使用 G6 中实例化的 group;
    3. 设置 scales;
    4. 设置 coordinate。
  7. 选择图表类型,并设置映射关系;
  8. 初始化:调用 init 方法;
  9. 绘制:调用 paint 方法。

存在的问题:

  1. 只能绘制基本的图形,不支持 axis、Tooltip、legend 等组件;
  2. 和 View 相比,更偏底层,实现起来需要了解更多的内容。

    1. G6.registerNode('node-with-geometry', {
    2. draw(cfg, group) {
    3. const canvas = group.get('canvas')
    4. const keyShape = group.addShape('rect', {
    5. attrs: {
    6. x: 0,
    7. y: 0,
    8. width: 400,
    9. height: 200,
    10. fill: '#e6f7ff'
    11. }
    12. })
    13. // 其他部分省略
    14. const container = group.addGroup();
    15. const data = [
    16. { genre: "Sports", sold: 275 },
    17. { genre: "Strategy", sold: 115 },
    18. { genre: "Action", sold: 120 },
    19. { genre: "Shooter", sold: 350 },
    20. { genre: "Other", sold: 150 }
    21. ];
    22. const scaleDefs = {
    23. a: { range: [0.25, 0.75] }
    24. };
    25. const scales = {
    26. genre: createScaleByField("genre", data, scaleDefs),
    27. sold: createScaleByField("sold", data, scaleDefs)
    28. };
    29. const CartesianCoordinate = getCoordinate("rect");
    30. const rectCoord = new CartesianCoordinate({
    31. start: { x: 20, y: 100 },
    32. end: { x: 380, y: 200 }
    33. });
    34. const interval = new Interval({
    35. data,
    36. scales,
    37. coordinate: rectCoord,
    38. container
    39. })
    40. interval.position('genre*sold')
    41. .color('genre')
    42. const theme = getTheme('default')
    43. interval.init({
    44. theme: theme
    45. })
    46. interval.paint()
    47. return keyShape
    48. }
    49. })

案例

综合性案例

G6 中如何使用 G2 的图表 - 图5

存在的问题

  • 目前的用法过于复杂,对用户来说很不友好,用户需要非常清楚 G2 中各个 Geometry、Component 的路径及作用; ```javascript import View, { registerGeometry } from ‘@antv/g2/lib/chart/view’ import { registerComponentController } from ‘@antv/g2/lib/core’ import Line from ‘@antv/g2/lib/geometry/line’ import Point from ‘@antv/g2/lib/geometry/point’ import Axis from “@antv/g2/lib/chart/controller/axis”; import Interval from “@antv/g2/lib/geometry/interval”;

registerGeometry(“Line”, Line); registerGeometry(“Point”, Point); registerGeometry(“Interval”, Interval); registerComponentController(“axis”, Axis); ```

  • 节点上的交互与 G2 图表上的交互之间可能存在冲突,需要找到平衡点;
  • G6 与 G2 坐标转换问题:会导致图表上的tooltip 的位置和 G6 节点上位置不一致;
  • 图表在节点中位置和范围是通过 region 配置,与画布大小强相关:会导致画布大小改变以后,要手动修改图表的 region。

可优化方案

  • G2 暴露出所有的 Geometry、Component,提供 Geometry 及 Component 的使用文档,G6 负责提供具体方案;
  • G6 封装一个类库,把注册 Geometry、Component 的操作封装一下,提供绘制柱状图、折线图、混合图等图表的接口,在 G6 中使用这个新库;
  • G6 封装一个类库,改写一下 G2 的 Chart,支持在实例化 Chart 的时候支持将 G 的 group 作为 container,其他用法完全同 G2 中的 Chart;
  • G2 在 Chart 层支持 G 的 Group 作为容器。

最终方案

经过和 G2 同学沟通及权衡,我们选择了方案 3。我们封装了一个 @antv/chart-node-g6 包,里面实现了一个 Chart 类,支持将 G 的group 作为容器,其他用法完全同 G2 的 Chart,但限制了部分交互。

G6 中如何使用 G2 的图表 - 图6