阅读时间:7min
G6介绍
G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。
框架比较
目前 AntV 有两个关于图的库:G6、X6(详细区别)
-
G6
- 探索数据、获得洞察以及其他的辅助能力
- 底层canvas、svg都支持(可选),大量节点优选
- 节点内部图元复杂的优先
-
X6
- 创建、编辑数据样式与形状
- 底层基于svg,200以内节点的图编辑场景优选
- 节点内部图元简单的优先
本场景主要是展示节点,没有编辑需求,所以最终决定采用G6可视化引擎的决策树图来实现。
什么是数据血缘
数据血缘是指数据之间的关系和依赖。它描述了数据来源和数据流动的路径,可以帮助我们理解数据的来源、变化和影响。
用途
通过分析数据血缘,我们可以追溯数据的历史和变化,并了解数据之间的依赖关系,从而更好地管理和利用数据。在数据工程和数据分析领域,数据血缘是一个重要的概念,可以帮助我们构建数据管道、处理数据质量问题和分析数据影响等。
功能实现
- 树形图展示数据流向
- 缩放视口
- 拖拽画布
- 收起和展开
代码实现
下载:
npm
npm install --save @antv/g6
模拟数据源:
ts
// mock.ts
const mockData = {
id: 'g1',
name: '入库',
date: '2021-1-1',
version: '2022-2-2',
executor: '小明',
owner: '大d',
status: 'warehouse',
children: [
{
id: 'g12',
rate: 0.627,
status: 'primary',
versionName: '我是第一次清洗后版本',
children: [
{
id: 'g121',
name: '清洗策略:第1种清洗策略名',
date: '2021-1-1',
version: '2022-2-2',
collapsed: true,
status: 'clean',
executor: '小明',
owner: '小d',
children: [
{
id: 'g1211',
rate: 1.0,
status: 'primary',
versionName: '我是第二次清洗后版本',
children: [
{
id: 'g1221',
name: '转bin策略:转bin',
version: '2022-2-2',
date: '2021-1-1',
status: 'bin',
executor: '小明',
owner: '小d',
},
{
id: 'g1222',
name: '质检策略:我是一种质检策略',
date: '2021-1-1',
version: '2022-2-2',
status: 'inspect',
executor: '小明',
owner: '小d',
},
{
id: 'g1223',
name: '打标策略:我是一种打标策略',
date: '2021-1-1',
version: '2022-2-2',
status: 'marking',
executor: '小明',
owner: '小d',
},
{
id: 'g10011',
name: '清洗策略:第二种清洗策略',
date: '2021-1-1',
collapsed: true,
version: '2022-2-2',
status: 'clean',
executor: '小明',
owner: '小d',
children: [
{
id: 'g12211',
status: 'primary',
versionName: '最终版本',
},
],
},
],
},
],
},
],
},
],
};
export default mockData;
数据血缘组件:最终作为一个canvas呈现在页面上
tsx
// 基本结构
import React, { useEffect, useRef } from 'react';
import G6, { G6GraphEvent } from '@antv/g6';
import mockData from './mock';
const DataLineAge = () => {
const ref: any = useRef(null);
let graph: any = null;
// 组件config
const config = {
...
};
// 获取到节点标识值后再开始渲染
useEffect(() => {
if (!graph) {
// 默认配置
const defaultConfig = {
// ...
};
// 自定义节点
G6.registerNode(
'flow-rect',
{
shapeType: 'flow-rect',
draw(cfg: any, group: any) { // 绘制节点的方法
//...
// 向节点添加图形
const rect = group.addShape('rect', {
// ..
});
// 向节点添加文字
group.addShape('text', {
...
});
return rect;
},
setState(name:any, value:any, item:any) { // 当外部调用 graph.setItemState时的响应函数
...
},
},
'rect',
);
graph = new G6.TreeGraph({
container: ref.current, // 容器
...defaultConfig, // 默认配置
...config, // 其他配置
});
}
graph.data(mockData); // 配置数据源
graph.render(); // 渲染
graph.zoom(config.defaultZoom || 1); // 改变缩放比例
// 单击扩展卡片事件绑定
graph.on('collapse-text:click', (e: G6GraphEvent) => {
...
});
// 单击收起卡片事件绑定
graph.on('collapse-back:click', (e: G6GraphEvent) => {
...
});
}, []);
return <div ref={ref}></div>;
};
export default DataLineAge;
完整代码:
tsx
// dataLineage.tsx
import React, { useEffect, useRef } from 'react';
import G6, { G6GraphEvent } from '@antv/g6';
import mockData from './mock';
type IColorType = {
warehouse: string; // 入库
primary: string; // 默认
clean: string; // 清洗
bin: string; // 打bin
inspect: string; // 质检
marking: string; // 打标
};
interface IObj {
date: string;
version: string;
executor: string;
owner: string;
}
const colors: IColorType = {
warehouse: '#7d5edf',
primary: '#5B8FF9',
bin: '#EEBC20',
inspect: '#00ba6f',
marking: '#A7A7A7',
clean: '#6dc8dc',
};
const DataLineAge = () => {
const ref: any = useRef(null);
let graph: any = null;
// 组件config
const config = {
padding: [20, 50],
defaultLevel: 5, // 默认层级
defaultZoom: 0.8, // 默认缩放
};
// 控制折叠展开卡片
const handleCollapse = (e: G6GraphEvent) => {
const target = e.target;
const id = target.get('modelId');
const item = graph.findById(id);
const nodeModel = item.getModel();
nodeModel.collapsed = !nodeModel.collapsed;
graph.layout();
graph.setItemState(item, 'collapse', nodeModel.collapsed);
};
// 获取到节点标识值后再开始渲染
useEffect(() => {
if (!graph) {
// 宽高由内容撑开
const width = ref.current?.scrollWidth;
const height = ref.current?.scrollHeight || 1000;
// 默认配置
const defaultConfig = {
width,
height,
modes: {
default: ['zoom-canvas', 'drag-canvas'], // 支持缩放、拖拽
},
fitView: true, // 开启画布自适应,图适配画布大小
animate: true, // 启用全局动画
defaultNode: {
// 默认状态下节点的配置,会被写入的data覆盖
type: 'flow-rect',
},
defaultEdge: {
// 默认状态下边的配置
type: 'cubic-horizontal', // 水平方向的三贝塞尔曲线
style: {
stroke: '#CED4D9',
},
},
layout: {
// 定义布局
type: 'indented', // 缩进树
direction: 'LR', // 根节点在左,往右布局
dropCap: false, // 子节点默认不在下一行
indent: 300, // 列间间距
getHeight: () => {
return 200; // 纵向块与块之间的距离
},
},
};
// 自定义节点
G6.registerNode(
'flow-rect',
{
shapeType: 'flow-rect',
draw(cfg: any, group: any) {
const {
name = '',
date,
version,
executor,
owner,
versionName = '',
collapsed,
status,
} = cfg;
// 节点样式
const rectConfig = {
width: 202,
height: 150,
lineWidth: 1,
fontSize: 12,
fill: '#fff',
radius: 4,
stroke: '#0077fa',
opacity: 0.8,
};
// 添加按钮的位置,若都为0则原点在节点正上方的中点
const nodeOrigin = {
x: -rectConfig.width / 2, // 减往右下,加往左上
y: -rectConfig.height / 2,
};
// 文字默认对齐样式
const textConfig = {
textAlign: 'left',
textBaseline: 'bottom',
};
// 向分组添加新的图形
const rect = group.addShape('rect', {
attrs: {
x: nodeOrigin.x,
y: nodeOrigin.y,
...rectConfig,
},
});
const baseOriginY = 28 + nodeOrigin.y;
const marginTop = 24;
// name
group.addShape('text', {
attrs: {
...textConfig,
x: 12 + nodeOrigin.x,
y: baseOriginY,
text:
(name as string).length > 10
? (name as string).substr(0, 10) + '...'
: name,
fontSize: 16,
opacity: 0.85,
fill: '#000',
cursor: 'pointer',
},
name: 'name-shape',
});
// versionName
group.addShape('text', {
attrs: {
...textConfig,
textAlign: 'center',
text:
(versionName as string).length > 10
? (versionName as string).substr(0, 10) + '...'
: versionName,
fontSize: 16,
opacity: 0.85,
fill: '#000',
cursor: 'pointer',
},
});
// collapse rect
if (cfg.children && cfg.children.length) {
group.addShape('rect', {
attrs: {
x: rectConfig.width / 2 - 8,
y: -8,
width: 16,
height: 16,
stroke: 'rgba(0, 0, 0, 0.25)',
cursor: 'pointer',
fill: '#fff',
opacity: 0.8,
},
name: 'collapse-back',
modelId: cfg.id,
});
// collpase text
group.addShape('text', {
attrs: {
x: rectConfig.width / 2,
y: -1,
textAlign: 'center',
textBaseline: 'middle',
text: collapsed ? '+' : '-',
fontSize: 16,
cursor: 'pointer',
fill: 'rgba(0, 0, 0, 0.25)',
},
name: 'collapse-text',
modelId: cfg.id,
});
}
const titleMap: IObj = {
date: '日期',
version: '版本',
executor: '执行人',
owner: '负责人',
};
const obj: IObj = {
date,
version,
executor,
owner,
};
Object.keys(obj).forEach((key: string, index: number) => {
if (obj[key as keyof IObj]) {
group.addShape('text', {
attrs: {
...textConfig,
x: 12 + nodeOrigin.x,
y: baseOriginY + (index + 1) * marginTop,
text: `${titleMap[key as keyof IObj]}: ${
obj[key as keyof IObj]
}`,
fontSize: 14,
fill: '#000',
opacity: 0.85,
},
});
}
});
const rectBBox = rect.getBBox();
// 底部边框
group.addShape('rect', {
attrs: {
x: nodeOrigin.x,
y: rectBBox.maxY - 4,
width: rectBBox.width,
height: 4,
radius: [0, 0, 0, rectConfig.radius],
fill: colors[status as keyof IColorType],
},
});
return rect;
},
setState(name:any, value:any, item:any) { // 当外部调用 graph.setItemState时的响应函数
if (name === 'collapse') {
const group = item.getContainer();
const collapseText = group.find((e:any) => e.get('name') === 'collapse-text');
if (collapseText) {
if (!value) {
collapseText.attr({
text: '-',
});
} else {
collapseText.attr({
text: '+',
});
}
}
}
},
},
'rect',
);
graph = new G6.TreeGraph({
container: ref.current,
...defaultConfig,
...config,
});
}
graph.data(mockData);
graph.render();
graph.zoom(config.defaultZoom || 1);
// 单击扩展卡片事件绑定
graph.on('collapse-text:click', (e: G6GraphEvent) => {
handleCollapse(e);
});
// 单击收起卡片事件绑定
graph.on('collapse-back:click', (e: G6GraphEvent) => {
handleCollapse(e);
});
}, []);
return <div ref={ref}></div>;
};
export default DataLineAge;
最终效果

动图展示(视频转gif有点模糊,求好用gif工具推荐):

参考:
AntV G6官网