数据血缘可视化之Open-Lineage(三)

609 阅读2分钟

前言

这一小节来讲一下如何自定义布局,首先说下为什么要自定义布局,没错,就是因为 Antv 自带的层次布局并不能满足我们的需求,如下是采用 Antv 自带的层次布局实现的效果。

使用 Antv 自带的层次布局效果图

使用 Antv 自带的层次布局实现存在以下几点问题:

  1. 从图中可以明显看到有部分表重叠了
  2. 节点之间的距离有时也不相等
  3. 整个图并没有完全沿中轴线对称

所以为了解决上述的几个问题,我们需要自定义布局,先来看下我们自定义布局之后的效果

自定义布局效果图

可见完美的解决了上述的三个问题,自定义布局有如下几个步骤:

1. 确定布局方式

我们还是采用层次布局,我们希望达到的效果由图一变为图二

图一 图一

图二 图二

2. 在构建节点时加入布局需要的参数

{ 
"id": key, 
"key": key, 
"label": key, 
"x": 100, 
"y": 100, 
"level": level, 
"order": order, 
"attrs": attrs, 
"size": [400, height] 
}

其中 id、key、label、x、y、attrs、size 是节点必须要有的属性,level 和 order 是我们自定义布局需要的属性,level 用在实现分层,order 用来实现同层排序。

3. 编码实现

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)
        );
      }
    });

    // TODO 重新调整层级
    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;

至此,自定义布局完结。