antvis / G6

♾ A Graph Visualization Framework in JavaScript.
https://g6.antv.antgroup.com/
MIT License
10.92k stars 1.29k forks source link

[v5]comboCombined(内外侧均使用antv-dagre)布局不理想 #5980

Open liseri opened 1 week ago

liseri commented 1 week ago

Describe the bug / 问题描述

V5

  1. 示例链接:https://g6-next.antv.antgroup.com/zh/examples/layout/dagre#antv-dagre-combo
  2. 问题:comboCombined(内外侧均使用antv-dagre)布局不理想,有重叠;如下图:

image

  1. 述求:1)麻烦帮忙看看,是bug 还是我的用法不对配置不对;2)我是使用的单独布局,麻烦看看是不是这么用的;数据转换用的代码是从g6或layout源码中扒出来后改了改;

4.代码:

import { Graph, AntVDagreLayout, ComboCombinedLayout } from '@antv/g6';
import { Graph as GraphLib } from '@antv/graphlib';

const customLayoutData = {
    "nodes": [
        {
            "id": "node-f6080309-1ef1-4cac-a097-932dbc993f25",
            "data": {

            }
        },
        {
            "id": "node-27641e0a-f158-498f-84ca-d8914d3bdd51",
            "data": {

            }
        },
        {
            "id": "node-82cbdfa3-b666-4495-baa2-32132053370a",
            "data": {

            }
        },
        {
            "id": "node-9c854399-cd39-4dc2-9520-39d603c61ea2",
            "data": {

            }
        },
        {
            "id": "node-f5f54df8-0436-41f0-b883-5e86bf6eac52",
            "data": {
                "_isCombo": true,

            }
        },
        {
            "id": "node-de645c23-0e7a-4175-bd62-c455a2fe4913",
            "data": {
                "parentId": "node-f5f54df8-0436-41f0-b883-5e86bf6eac52",

            }
        },
        {
            "id": "node-da5ee6dc-1fdf-420d-abc5-71bdd5cd96cc",
            "data": {
                "parentId": "node-f5f54df8-0436-41f0-b883-5e86bf6eac52",

            }
        },
        {
            "id": "node-79394712-37de-4353-8307-81a4ce63dacd",
            "data": {
                "parentId": "node-f5f54df8-0436-41f0-b883-5e86bf6eac52",

            }
        },
        {
            "id": "node-12f351ba-b265-46a9-b0cc-0a773fac5ca8",
            "data": {

            }
        },
        {
            "id": "node-2bdbebbc-58c4-47d0-8264-ed343b8506fa",
            "data": {

            }
        }
    ],
    "edges": [
        {
            "id": "edge-18c062a5-2677-4b14-92b1-050a49e26f86",
            "source": "node-f6080309-1ef1-4cac-a097-932dbc993f25",
            "target": "node-27641e0a-f158-498f-84ca-d8914d3bdd51",
            "data": {}
        },
        {
            "id": "edge-0ba36fda-c523-4715-868e-da6e6f8ef7c2",
            "source": "node-f6080309-1ef1-4cac-a097-932dbc993f25",
            "target": "node-2bdbebbc-58c4-47d0-8264-ed343b8506fa",
            "data": {}
        },
        {
            "id": "edge-d4e5d739-ff90-4e5d-971d-c334302acc42",
            "source": "node-27641e0a-f158-498f-84ca-d8914d3bdd51",
            "target": "node-82cbdfa3-b666-4495-baa2-32132053370a",
            "data": {}
        },
        {
            "id": "edge-07f91b13-0415-4d3f-8346-6381c827a7a1",
            "source": "node-27641e0a-f158-498f-84ca-d8914d3bdd51",
            "target": "node-9c854399-cd39-4dc2-9520-39d603c61ea2",
            "data": {}
        },
        {
            "id": "edge-7b4ea309-d018-4b37-b46f-9a158b733fd6",
            "source": "node-82cbdfa3-b666-4495-baa2-32132053370a",
            "target": "node-12f351ba-b265-46a9-b0cc-0a773fac5ca8",
            "data": {}
        },
        {
            "id": "edge-4fb7755c-ff4e-44dc-87ea-4770b97b97fc",
            "source": "node-9c854399-cd39-4dc2-9520-39d603c61ea2",
            "target": "node-de645c23-0e7a-4175-bd62-c455a2fe4913",
            "data": {}
        },
        {
            "id": "edge-7b3ef5e2-d6e6-4e90-805a-4e6f7d6f9b12",
            "source": "node-de645c23-0e7a-4175-bd62-c455a2fe4913",
            "target": "node-da5ee6dc-1fdf-420d-abc5-71bdd5cd96cc",
            "data": {}
        },
        {
            "id": "edge-03564922-64c9-4312-b26c-717e28d78eab",
            "source": "node-de645c23-0e7a-4175-bd62-c455a2fe4913",
            "target": "node-79394712-37de-4353-8307-81a4ce63dacd",
            "data": {}
        }
    ]
}

/**
 * 图布局数据转为GraphLibData
 */
function layoutData2GraphLib(layoutData, isTree) {
    const graphLibData = new GraphLib(layoutData);
    if (isTree) {
      graphLibData.attachTreeStructure('combo')
      graphLibData.getAllNodes().forEach((node) => {
            const { parentId } = node.data;
            if (parentId === undefined) return;
            if (graphLibData.hasNode(parentId)) {
              graphLibData.setParent(node.id, parentId, 'combo');
            }
          });
    }
    return graphLibData;

  }

/**
 * 将图布局数据转换为 G6 数据
 */
function layoutData2G6Data(layoutData) {
  const { nodes, edges } = layoutData;
  const data = { nodes: [], edges: [], combos: [] };

  nodes.forEach((nodeLike) => {
    const target = nodeLike.data._isCombo ? data.combos : data.nodes;
    const { x, y, z = 0, parentId } = nodeLike.data;
    target?.push({
      id: nodeLike.id,
      style: { x, y, z },
      ...(parentId ? { combo: parentId } : {})
    });
  });

  edges.forEach((edge) => {
    const {
      id,
      source,
      target,
      data: { points = [], controlPoints = points.slice(1, points.length - 1) },
    } = edge;

    data.edges.push({
      id: id,
      source: source,
      target: target,
      style: {
        /**
         * antv-dagre 返回 controlPoints,dagre 返回 points
         * antv-dagre returns controlPoints, dagre returns points
         */
        ...(controlPoints?.length ? { controlPoints: controlPoints.map(parsePoint) } : {}),
      },
    });
  });

  return data;
}

// 使用layout只布局; 布局后的数据使用g6渲染
async function comboCombinedLayoutAndRenderWithG6(layoutData) {
  console.log('//==comboCombined布局,数据使用layoutData -> 单独使用布局 -> 使用G6渲染')

  console.log('布局前的layoutData=', layoutData)
  // 实例化布局
  const layout = new ComboCombinedLayout({
    spacing: 20,
    innerLayout: new AntVDagreLayout({
      // begin: [0, 0],
      // align: 'UR',
      nodeSize: [150, 80],
      ranksep: 20,
      nodesep: 10,
      sortByCombo: true,
    }),
    outerLayout: new AntVDagreLayout({
      // begin: [0, 0],
      // align: 'UR',
      nodeSize: [150, 80],
      ranksep: 20,
      nodesep: 10,
      sortByCombo: true,
    })
  });
  // 将layoutData包到graphLib
  const graphLibData = layoutData2GraphLib(layoutData, true);
  console.log('布局前的 graphLibData=', graphLibData)
  // 执行布局
  await layout.assign(graphLibData);
  console.log('布局后的layoutData=', layoutData)

  // 布局后的数据转换为G6数据
  const afterLayoutG6Data = layoutData2G6Data(layoutData);
  console.log('布局后的g6Data=', afterLayoutG6Data)

  // 使用G6渲染
  const graph = new Graph({
    container: 'container',
    autoFit: 'view',
    data: afterLayoutG6Data,
    node: {
      type: 'rect',
      style: {
        size: [150, 80],
        radius: 8,
        labelText: (d) => d.id,
        labelPlacement: 'center',
        // ports: [{ placement: 'top' }, { placement: 'bottom' }],
      },
      palette: {
        field: (d) => d.combo,
      },
    },
    edge: {
      type: 'cubic-vertical',
      style: {
        endArrow: true,
      },
    },
    combo: {
      type: 'rect',
      style: {
        radius: 8,
        labelText: (d) => d.id,
        lineDash: 0,
        collapsedLineDash: [5, 5],
      },
    },
    behaviors: ['drag-element', 'drag-canvas', 'zoom-canvas', 'collapse-expand'],
  });

  graph.render();
}

comboCombinedLayoutAndRenderWithG6(customLayoutData)

Reproduction link / 重现链接

https://g6-next.antv.antgroup.com/zh/examples/layout/dagre#antv-dagre-combo

Steps to Reproduce the Bug or Issue / 重现步骤

  1. 打开示例:https://g6-next.antv.antgroup.com/zh/examples/layout/dagre#antv-dagre-combo
  2. 将上面的代码 粘贴到 javascript中

G6 Version / G6 版本

🆕 5.x

Operating System / 操作系统

Windows

Browser / 浏览器

Chrome

Additional context / 补充说明

No response

Aarebecca commented 1 week ago

combo-combine + dagre 结合使用还没有经过充分实践,可能并不能正常工作

liseri commented 1 week ago

combo-combine + dagre 结合使用还没有经过充分实践,可能并不能正常工作

你好,那想请教下 我这单独使用布局,用法对么,文档中找了半天也没找到个完整的示例,数据需要先包到graphLib中才能用,而且还有一个特殊处理(从源码中扒出来的),不知道对不对,如下代码:

/**
 * 图布局数据转为GraphLibData
 */
function layoutData2GraphLib(layoutData, isTree) {
    const graphLibData = new GraphLib(layoutData);
   // 下面这段是从g6源码里扒出来,改的; 如果使用AntVDagreLayout不需要,因为AntVDagreLayout中就有这段代码(g6中有,且layout也有);但如果使用ComboCombinedLayout就需要(g6中有,但comboCombinedlayout中没有,单独使用布局时就会出问题)
    if (isTree) {
      graphLibData.attachTreeStructure('combo')
      graphLibData.getAllNodes().forEach((node) => {
            const { parentId } = node.data;
            if (parentId === undefined) return;
            if (graphLibData.hasNode(parentId)) {
              graphLibData.setParent(node.id, parentId, 'combo');
            }
          });
    }
    return graphLibData;
  }
liseri commented 1 week ago

combo-combine + dagre 结合使用还没有经过充分实践,可能并不能正常工作

你好,那想请教下 我这单独使用布局,用法对么,文档中找了半天也没找到个完整的示例,数据需要先包到graphLib中才能用,而且还有一个特殊处理(从源码中扒出来的),不知道对不对,如下代码:

/**
 * 图布局数据转为GraphLibData
 */
function layoutData2GraphLib(layoutData, isTree) {
    const graphLibData = new GraphLib(layoutData);
   // 下面这段是从g6源码里扒出来,改的; 如果使用AntVDagreLayout不需要,因为AntVDagreLayout中就有这段代码(g6中有,且layout也有);但如果使用ComboCombinedLayout就需要(g6中有,但comboCombinedlayout中没有,单独使用布局时就会出问题)
    if (isTree) {
      graphLibData.attachTreeStructure('combo')
      graphLibData.getAllNodes().forEach((node) => {
            const { parentId } = node.data;
            if (parentId === undefined) return;
            if (graphLibData.hasNode(parentId)) {
              graphLibData.setParent(node.id, parentId, 'combo');
            }
          });
    }
    return graphLibData;
  }

我具体化一下吧,给您个具体的例子,这个例子(代码如下)里如下图这么改一下连接数据,就直接报错了,因此我怀疑就是上述扒的代码扒的有问题; 而不单独布局,直接使用g6进行布局且渲染,就没有错误(不报错,但布局有问题,与本报错无关 已单独提issue #5988) image 不出错的代码,如上图改一下连接(从4连6改为 4连5)就报错了

import { Graph, AntVDagreLayout, ComboCombinedLayout } from '@antv/g6';
import { Graph as GraphLib } from '@antv/graphlib';

const customLayoutData = {
    "nodes": [
        {
            "id": "node-1",
            "data": {

            }
        },
        {
            "id": "node-2",
            "data": {

            }
        },
        {
            "id": "node-3",
            "data": {

            }
        },
        {
            "id": "node-4",
            "data": {

            }
        },
        {
            "id": "node-5",
            "data": {
                "_isCombo": true,

            }
        },
        {
            "id": "node-6",
            "data": {
                "parentId": "node-5",

            }
        },
        {
            "id": "node-7",
            "data": {
                "parentId": "node-5",

            }
        },
        {
            "id": "node-8",
            "data": {
                "parentId": "node-5",

            }
        },
        {
            "id": "node-9",
            "data": {

            }
        },
        {
            "id": "node-10",
            "data": {

            }
        }
    ],
    "edges": [
        {
            "id": "edge-18c062a5-2677-4b14-92b1-050a49e26f86",
            "source": "node-1",
            "target": "node-2",
            "data": {}
        },
        {
            "id": "edge-0ba36fda-c523-4715-868e-da6e6f8ef7c2",
            "source": "node-1",
            "target": "node-10",
            "data": {}
        },
        {
            "id": "edge-d4e5d739-ff90-4e5d-971d-c334302acc42",
            "source": "node-2",
            "target": "node-3",
            "data": {}
        },
        {
            "id": "edge-07f91b13-0415-4d3f-8346-6381c827a7a1",
            "source": "node-2",
            "target": "node-4",
            "data": {}
        },
        {
            "id": "edge-7b4ea309-d018-4b37-b46f-9a158b733fd6",
            "source": "node-3",
            "target": "node-9",
            "data": {}
        },
        {
            "id": "edge-4fb7755c-ff4e-44dc-87ea-4770b97b97fc",
            "source": "node-4",
            "target": "node-6",
            "data": {}
        },
        {
            "id": "edge-7b3ef5e2-d6e6-4e90-805a-4e6f7d6f9b12",
            "source": "node-6",
            "target": "node-7",
            "data": {}
        },
        {
            "id": "edge-03564922-64c9-4312-b26c-717e28d78eab",
            "source": "node-6",
            "target": "node-8",
            "data": {}
        }
    ]
}

/**
 * 图布局数据转为GraphLibData
 */
function layoutData2GraphLib(layoutData, isTree) {
    const graphLibData = new GraphLib(layoutData);
    if (isTree) {
      graphLibData.attachTreeStructure('combo')
      graphLibData.getAllNodes().forEach((node) => {
            const { parentId } = node.data;
            if (parentId === undefined) return;
            if (graphLibData.hasNode(parentId)) {
              graphLibData.setParent(node.id, parentId, 'combo');
            }
          });
    }
    return graphLibData;

  }

/**
 * 将图布局数据转换为 G6 数据
 */
function layoutData2G6Data(layoutData) {
  const { nodes, edges } = layoutData;
  const data = { nodes: [], edges: [], combos: [] };

  nodes.forEach((nodeLike) => {
    const target = nodeLike.data._isCombo ? data.combos : data.nodes;
    const { x, y, z = 0, parentId } = nodeLike.data;
    target?.push({
      id: nodeLike.id,
      style: { x, y, z },
      ...(parentId ? { combo: parentId } : {})
    });
  });

  edges.forEach((edge) => {
    const {
      id,
      source,
      target,
      data: { points = [], controlPoints = points.slice(1, points.length - 1) },
    } = edge;

    data.edges.push({
      id: id,
      source: source,
      target: target,
      style: {
        /**
         * antv-dagre 返回 controlPoints,dagre 返回 points
         * antv-dagre returns controlPoints, dagre returns points
         */
        ...(controlPoints?.length ? { controlPoints: controlPoints.map(parsePoint) } : {}),
      },
    });
  });

  return data;
}

// 使用layout只布局; 布局后的数据使用g6渲染
async function comboCombinedLayoutAndRenderWithG6(layoutData) {
  console.log('//==comboCombined布局,数据使用layoutData -> 单独使用布局 -> 使用G6渲染')

  console.log('布局前的layoutData=', layoutData)
  // 实例化布局
  const layout = new AntVDagreLayout({
    // begin: [0, 0],
    // align: 'UR',
    nodeSize: [150, 80],
    ranksep: 10,
    nodesep: 10,
    sortByCombo: true,
  });
  // 将layoutData包到graphLib
  const graphLibData = layoutData2GraphLib(layoutData, true);
  console.log('布局前的 graphLibData=', graphLibData)
  // 执行布局
  await layout.assign(graphLibData);
  console.log('布局后的layoutData=', layoutData)

  // 布局后的数据转换为G6数据
  const afterLayoutG6Data = layoutData2G6Data(layoutData);
  console.log('布局后的g6Data=', afterLayoutG6Data)

  // 使用G6渲染
  const graph = new Graph({
    container: 'container',
    autoFit: 'view',
    data: afterLayoutG6Data,
    node: {
      type: 'rect',
      style: {
        size: [150, 80],
        radius: 8,
        labelText: (d) => d.id,
        labelPlacement: 'center',
        // ports: [{ placement: 'top' }, { placement: 'bottom' }],
      },
      palette: {
        field: (d) => d.combo,
      },
    },
    edge: {
      type: 'cubic-vertical',
      style: {
        endArrow: true,
      },
    },
    combo: {
      type: 'rect',
      style: {
        radius: 8,
        labelText: (d) => d.id,
        lineDash: 0,
        collapsedLineDash: [5, 5],
      },
    },
    behaviors: ['drag-element', 'drag-canvas', 'zoom-canvas', 'collapse-expand'],
  });

  graph.render();
}

comboCombinedLayoutAndRenderWithG6(customLayoutData)
Aarebecca commented 1 week ago

node-5 是一个 id,它应该位于 data.combos 中,通过 _isCombo 标识是底层库的隐式逻辑,G6 并不基于该字段

liseri commented 1 week ago

node-5 是一个 id,它应该位于 data.combos 中,通过 _isCombo 标识是底层库的隐式逻辑,G6 并不基于该字段

您是说 AntVDagreLayout.execute(data), 这里的data应该是 {nodes:[], combos:[], edges:[]} 这样的数据结构么? 哪里能找到一个示例么,这样就不用一遍遍麻烦您了;