Antv如何使用G6做数据血缘可视化

1,956 阅读3分钟

前言

大数据数据血缘是指数据产生的链路,直白点说,就是我们这个数据是怎么来的,经过了哪些过程和阶段。而我们要做的就是将这个过程通过可视化技术展现出来。

产品功能

这是一个企业级的大数据血缘可视化项目,目前支持功能如下。目前版本 V1.0.1。

  1. 支持解析 Hive sql 生成血缘图
  2. 支持字段级血缘与表级血缘切换展示
  3. 支持完整血缘与不完整血缘链路切换展示
  4. 支持血缘高亮显示
  5. 支持设置血缘高亮颜色
  6. 支持画布水印
  7. 支持画布拖拽、放大、缩小、自适应、视图居中显示
  8. 支持血缘图图片下载
  9. 支持小地图拖拽
  10. 编辑器支持编写 Sql,美化 Sql 功能
  11. 编辑器支持切换主题色
  12. 编辑器支持语法高亮

在线预览地址:openbytecode.com/openLineage…

踩坑记录

这里再分享下在项目开发的过程中遇到的一些问题,帮助大家在遇到类似的问题的时候该如何解决。

1. 渲染之后调用 fitView() 方法不生效

/**  
* 渲染视图  
*/  
export const renderGraph = (graph: any, lineageData: any) => {  
if (!graph || !lineageData) return;  
graph.data(lineageData);  
graph.render();  
graph.fitView();  
};  

最后在 AntV G6 官网找到答案

g6.antv.antgroup.com/api/graph#g…

也就是说要在初始化 Graph 的时候设置 fitView 为 true 才生效

graphRef.current = new G6.Graph({  
container: container || '',  
width: width,  
height: height,  
plugins: [grid, minimap, toolbar],  
fitView: true,  
modes: {  
default: ['drag-canvas', 'zoom-canvas', 'drag-node'],  
},  
});  

2. 点击字段空白处没有触发事件

img1.png

定义的事件如下,点击图中可触发事件区域可以触发事件,但是点击图中不可触发事件区域未能触发事件

// 监听节点点击事件  
graph.off('node:click').on('node:click', (evt: any) => {  
const { item, target } = evt;  
const currentAnchor = target.get('name');  
if (!currentAnchor) return;  
  
if (fieldCheckedRef.current) {  
handleNodeClick(graph, item, currentAnchor, 'highlight');  
} else {  
handleNodeClick(graph, item, currentAnchor, 'tableHighlight');  
}  
});  

这个问题比较有意思,最后在也是通过对比官网案例最终找到了答案

官网案例:g6.antv.antgroup.com/examples/in…

经过对比发现:例子中的蓝色小圆是填充了蓝色,而我们的没有填充,所以猜测可能是我们的字段矩形没有填充东西,也就是说矩形是空的,所以监听不到事件。

填充之前的代码如下:

attrs.forEach((e: any, i: any) => {  
const { key } = e;  
// group部分图形控制  
listContainer.addShape('rect', {  
attrs: {  
x: 0,  
y: i * itemHeight + itemHeight,  
width: width,  
height: itemHeight,  
cursor: 'pointer',  
},  
name: key,  
draggable: true,  
});  
});  

给矩形填充白色

attrs.forEach((e: any, i: any) => {  
const { key } = e;  
// group部分图形控制  
listContainer.addShape('rect', {  
attrs: {  
x: 0,  
y: i * itemHeight + itemHeight,  
fill: '#ffffff',  
width: width,  
height: itemHeight,  
cursor: 'pointer',  
},  
name: key,  
draggable: true,  
});  
});  

正如我们猜测的那样,填充颜色之后就能够监听到事件了。

3. 使用 dagre 布局有一些表局部有重叠和间距不一致问题

img2.png

最终使用 G6 自定义布局解决了该问题,自定义布局代码如下:

class CustomDagreLayout extends Base {
  /** 布局的起始(左上角)位置 */
  public begin: number[] = [0, 0];

  /** 节点水平间距(px) */
  public nodesep: number = 50;

  /** 每一层节点之间间距 */
  public ranksep: number = 50;

  constructor(options?: DagreLayoutOptions) {
    super();
    this.updateCfg(options);
  }

  public getDefaultCfg() {
    return {
      nodesep: 50, // 节点水平间距(px)
      ranksep: 50, // 每一层节点之间间距
      begin: [0, 0], // 布局的起点位置
    };
  }

  /**
   * 执行布局
   */
  public execute() {
    const self = this;
    const { nodes, edges, ranksep, nodesep, begin } = self;
    if (!nodes) return;
    const layerMap: Map<number, Node[]> = new Map();
    nodes.forEach((item: any, index, arr) => {
      if (!layerMap.has(item.level)) {
        layerMap.set(
          item.level,
          arr.filter((node: any) => node.level === item.level)
        );
      }
    });

    const startX = begin[0];
    const startY = begin[1];
    const size = layerMap.size;
    const maxWidth = size * nodeWidth + (size - 1) * ranksep;
    const hr = Array.from(layerMap.values()).map((list: any[]) => {
      const sum = list.reduce((pre: any, curr: any) => {
        return pre + curr.size[1];
      }, 0);
      return sum + (list.length - 1) * nodesep;
    });
    const maxHeight = Math.max(...hr);
    const offsetX = startX + maxWidth;
    const offsetY = startY + maxHeight;
    const centerLine = offsetY - maxHeight / 2;

    layerMap.forEach((value, key) => {
      let d = key === maxLevel ? size - 1 : key;
      const x = offsetX - d * (nodeWidth + ranksep);
      const y = centerLine + hr[d] / 2;
      const sortNodes = value.sort((x: any, y: any) => y.order - x.order);
      let preY = y;
      sortNodes.forEach((e: any, index) => {
        const { size } = e;
        const margin = index === 0 ? 0 : nodesep;
        preY = preY - size[1] - margin;
        e.x = x;
        e.y = preY;
      });
    });
    if (self.onLayoutEnd) self.onLayoutEnd();
  }

  public getType() {
    return 'lineageLayout';
  }
}

export default CustomDagreLayout;

自定义布局效果如下:

img3.png