业务场景
流程图或树图中,节点中包含丰富的信息。
图片来自 http://aperturejs.com/demos/,如有侵权,请联系 @聚则(moyee-bzn) 删除。
可选方案
要完成上面的需求,我们可以通过以下三种方案来实现。
使用 DOM 或 SVG 实现
优势
- 实现起来比较方便,每个节点就是一个 div,图表可以使用 G2plot / G2 / BizChart 等。
不足
- 布局需要自己实现,成本较高;
- 展开收起的逻辑需要自己实现,成本较高;
- 节点数量多了以后,会遇到性能瓶颈。
使用 X6 实现
优势
- 实现起来比较方便,每个节点就是一个 DOM 元素,图表可以使用 G2plot / G2 / BizChart 等;
- 展开收起逻辑已内置,可以直接使用。
不足
- 布局能力弱,二次开发体验差;
- 节点数量多了以后,会遇到性能瓶颈。
使用 G6 实现
优势
- 有丰富的布局可选,且支持自定义布局;
- 展开收起逻辑已内置,可以直接使用;
- 相比 DOM 或 SVG,性能较好。
不足
- 已解决
节点中定义折线图、面积图上手难度大、成本高; - 已解决
官方没有提供节点中嵌入 G2plot / G2 的实践及方案,不确定 G6 中的节点是否支持使用 G2 中的图表。
G6 的选择
G6 中的节点能否支持 G2plot 或 G2 中的图表,如果能够支持,选择 G2plot 还是 G2?
通过调研以后,我们选择 G2,原因如下:
- G2plot 不够稳定,很可能要被重写,G6 现在接入 G2plot 风险比较大,
且 G2plot 的设计导致 G6 根本接入不了; - G2 更偏向底层,且 G2 按 chart、view、shape、geometry 的分层设计更灵活;
- G2 中 view、shape、geometry 等支持将 G 的 canvas 及 group 作为容器,可以复用 G6 中实例化的 canvas 及 group。
目标
自定义节点时,支持接入 G2 的折线图、面积图等图表。
官方提供接入方案及案例。
方案
上面的示例,我们可以通过实例化 View 的方式来实现。
也可以通过实例化 Interval 的方式来实现。
通过上面的 Demo,我们可以发现:
- 通过实例化 Chart 的方式,代码是最简洁的,但实例化时需要传入一个 DOM 元素或 ID 作为容器,而 G6 的节点不支持 DOM 元素;
- 通过实例化 View 的方式,能够实现和实例化 Chart 完全一样的功能,唯一麻烦的就是需要我们手动注册 geometry 和 componentController,但实例化时支持将 G 的 canvas 作为容器,这里就可以复用 G6 中已经实例化的 canvas;
- 通过实例化具体的 Geometry 来的方式来实现,实例化 Geometry 时,也可以复用 G6 中已经实例化的 canvas,但这种方式需要用户定义的地方比较多,且不支持所有的 component,如坐标轴、tooltip、legend 等。
从上的分析可以看出,在 G6 中使用 G2 中图表,根据具体需求的不同,有两种实现方式:View 和 Geometry,推荐使用 View。
从 View 层接入
关键点:
- 实例化 View:使用 G6 中实例化的 canvas,创建三个 group;
- 绑定数据;
- 选择图表类型,并设置映射关系;
- 其他配置:坐标轴是否显示、legend 是否显示等;
渲染。
G6.registerNode('node-with-bar', {
draw(cfg, group) {
const canvas = group.get('canvas')
const keyShape = group.addShape('rect', {
attrs: {
x: 0,
y: 0,
width: 400,
height: 200,
fill: '#e6f7ff'
}
})
// 其他部分省略
const backgroundGroup = group.addGroup();
const middleGroup = group.addGroup();
const foregroundGroup = group.addGroup();
const view = new View({
parent: null,
canvas,
foregroundGroup,
middleGroup,
backgroundGroup,
padding: 5,
visible: true,
region: {
start: {
x: 0.01,
y: 0.2
},
end: {
x: 0.8,
y: 0.35
}
},
});
const data = [
{ genre: "Sports", sold: 275 },
{ genre: "Strategy", sold: 115 },
{ genre: "Action", sold: 120 },
{ genre: "Shooter", sold: 350 },
{ genre: "Other", sold: 150 }
];
view.data(data);
view
.interval()
.position("genre*sold")
.color("genre");
view.legend("genre", false);
view.axis('sold', false)
view.render();
return keyShape
}
})
从 Geometry 层接入
关键点:
实例化 Geometry:
- 数据;
- 容器:支持使用 G6 中实例化的 group;
- 设置 scales;
- 设置 coordinate。
- 选择图表类型,并设置映射关系;
- 初始化:调用 init 方法;
- 绘制:调用 paint 方法。
存在的问题:
- 只能绘制基本的图形,不支持 axis、Tooltip、legend 等组件;
和 View 相比,更偏底层,实现起来需要了解更多的内容。
G6.registerNode('node-with-geometry', {
draw(cfg, group) {
const canvas = group.get('canvas')
const keyShape = group.addShape('rect', {
attrs: {
x: 0,
y: 0,
width: 400,
height: 200,
fill: '#e6f7ff'
}
})
// 其他部分省略
const container = group.addGroup();
const data = [
{ genre: "Sports", sold: 275 },
{ genre: "Strategy", sold: 115 },
{ genre: "Action", sold: 120 },
{ genre: "Shooter", sold: 350 },
{ genre: "Other", sold: 150 }
];
const scaleDefs = {
a: { range: [0.25, 0.75] }
};
const scales = {
genre: createScaleByField("genre", data, scaleDefs),
sold: createScaleByField("sold", data, scaleDefs)
};
const CartesianCoordinate = getCoordinate("rect");
const rectCoord = new CartesianCoordinate({
start: { x: 20, y: 100 },
end: { x: 380, y: 200 }
});
const interval = new Interval({
data,
scales,
coordinate: rectCoord,
container
})
interval.position('genre*sold')
.color('genre')
const theme = getTheme('default')
interval.init({
theme: theme
})
interval.paint()
return keyShape
}
})
案例
存在的问题
- 目前的用法过于复杂,对用户来说很不友好,用户需要非常清楚 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,但限制了部分交互。