weixin_39609573
weixin_39609573
2020-12-30 13:21

Try to replicate a dagre layout

I have experience with https://github.com/dagrejs/dagre. And below is a layout produced by it:

image

Let's ignore the shapes and assume all of them are rectangles.

I tried the following:

js
const elk = new ELK({
  defaultLayoutOptions: {
    'elk.algorithm': 'layered',
    'elk.direction': 'RIGHT',
    'elk.padding': '[top=25,left=25,bottom=25,right=25]',
    'elk.spacing.componentComponent': 25, // unconnected nodes are individual subgraphs, referred to as named components
    'elk.layered.spacing.nodeNodeBetweenLayers': 25, // this has effect, but only if there are edges.
    'elk.edgeLabels.inline': true
  }
})
js
const graph = {
      id: 'root',
      layoutOptions: { 'elk.direction': 'DOWN' },
      children: [
        {
          id: 'n1',
          width: 100,
          height: 50,
          labels: [{ text: 'Chrismas' }]
        },
        {
          id: 'n2',
          width: 100,
          height: 50,
          labels: [{ text: 'Go shopping' }]
        },
        {
          id: 'n3',
          width: 100,
          height: 50,
          labels: [{ text: 'Let me think' }]
        },
        {
          id: 'n4',
          width: 100,
          height: 50,
          labels: [{ text: 'Laptop' }]
        },
        {
          id: 'n5',
          width: 100,
          height: 50,
          labels: [{ text: 'iPhone' }]
        },
        {
          id: 'n6',
          width: 100,
          height: 50,
          labels: [{ text: 'Car' }]
        }
      ],
      edges: [
        {
          id: 'e1',
          sources: ['n1'],
          targets: ['n2'],
          type: 'DIRECTED',
          labels: [{ width: 80, height: 20, text: 'Get money' }]
        },
        {
          id: 'e2',
          sources: ['n2'],
          targets: ['n3'],
          type: 'DIRECTED'
        },
        {
          id: 'e3',
          sources: ['n3'],
          targets: ['n4'],
          type: 'DIRECTED',
          labels: [{ width: 60, height: 20, text: 'One' }]
        },
        {
          id: 'e4',
          sources: ['n3'],
          targets: ['n5'],
          type: 'DIRECTED',
          labels: [{ width: 60, height: 20, text: 'Two' }]
        },
        {
          id: 'e5',
          sources: ['n3'],
          targets: ['n6'],
          type: 'DIRECTED',
          labels: [{ width: 60, height: 20, text: 'Three' }]
        }
      ]
    }

What I produced is:

image

It is actually not bad!

But I am wondering is it possible to do the following:

  1. make the layout horizontally symmetric
  2. reserve the order of edge labels One, Two & Three.

With above two points achieved, it will be a perfect dagre layout replication.

I am not saying dagre is better. Actually I think elkjs is more flexible. Here I just investigate and learn something about elkjs.

该提问来源于开源项目:kieler/elkjs

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

13条回答

  • weixin_39795419 weixin_39795419 4月前

    Can you try setting the positions like so: '(10,20)'. I guess we need to improve the documentation on how to specify these kind of options.

    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    I confirm the following works:

    js
    const graph = {
          id: 'root',
          layoutOptions: {
            'elk.direction': 'DOWN',
            'elk.layered.crossingMinimization.semiInteractive': true
          },
          children: [
            {
              id: 'n1',
              width: 100,
              height: 50,
              labels: [{ text: 'Chrismas' }]
            },
            {
              id: 'n2',
              width: 100,
              height: 50,
              labels: [{ text: 'Go shopping' }]
            },
            {
              id: 'n3',
              width: 100,
              height: 50,
              labels: [{ text: 'Let me think' }]
            },
            {
              id: 'n4',
              width: 100,
              height: 50,
              labels: [{ text: 'Laptop' }],
              layoutOptions: { 'elk.position': '(1,0)' }
            },
            {
              id: 'n5',
              width: 100,
              height: 50,
              labels: [{ text: 'iPhone' }],
              layoutOptions: { 'elk.position': '(2,0)' }
            },
            {
              id: 'n6',
              width: 100,
              height: 50,
              labels: [{ text: 'Car' }],
              layoutOptions: { 'elk.position': '(3,0)' }
            }
          ],
          edges: [
            {
              id: 'e1',
              sources: ['n1'],
              targets: ['n2'],
              type: 'DIRECTED',
              labels: [{ width: 80, height: 20, text: 'Get money' }]
            },
            {
              id: 'e2',
              sources: ['n2'],
              targets: ['n3'],
              type: 'DIRECTED'
            },
            {
              id: 'e3',
              sources: ['n3'],
              targets: ['n4'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'One' }]
            },
            {
              id: 'e4',
              sources: ['n3'],
              targets: ['n5'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Two' }]
            },
            {
              id: 'e5',
              sources: ['n3'],
              targets: ['n6'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Three' }]
            }
          ]
        }
    

    So the correct way to specify position is (1,2) instead of [x=1,y=2].

    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    And if I specify 'elk.layered.crossingMinimization.strategy': 'INTERACTIVE', I don't need to specify anything else (semiInteractive and node positions) and it just works.

    Sample:

    js
        const graph = {
          id: 'root',
          layoutOptions: {
            'elk.direction': 'DOWN',
            // 'elk.layered.crossingMinimization.semiInteractive': true,
            'elk.layered.crossingMinimization.strategy': 'INTERACTIVE'
          },
          children: [
            {
              id: 'n1',
              width: 100,
              height: 50,
              labels: [{ text: 'Chrismas' }]
            },
            {
              id: 'n2',
              width: 100,
              height: 50,
              labels: [{ text: 'Go shopping' }]
            },
            {
              id: 'n3',
              width: 100,
              height: 50,
              labels: [{ text: 'Let me think' }]
            },
            {
              id: 'n4',
              width: 100,
              height: 50,
              labels: [{ text: 'Laptop' }]
              // layoutOptions: { 'elk.position': '(1,0)' }
            },
            {
              id: 'n5',
              width: 100,
              height: 50,
              labels: [{ text: 'iPhone' }]
              // layoutOptions: { 'elk.position': '(2,0)' }
            },
            {
              id: 'n6',
              width: 100,
              height: 50,
              labels: [{ text: 'Car' }]
              // layoutOptions: { 'elk.position': '(3,0)' }
            }
          ],
          edges: [
            {
              id: 'e1',
              sources: ['n1'],
              targets: ['n2'],
              type: 'DIRECTED',
              labels: [{ width: 80, height: 20, text: 'Get money' }]
            },
            {
              id: 'e2',
              sources: ['n2'],
              targets: ['n3'],
              type: 'DIRECTED'
            },
            {
              id: 'e3',
              sources: ['n3'],
              targets: ['n4'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'One' }]
            },
            {
              id: 'e4',
              sources: ['n3'],
              targets: ['n5'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Two' }]
            },
            {
              id: 'e5',
              sources: ['n3'],
              targets: ['n6'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Three' }]
            }
          ]
        }
    
    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    Do you have any idea why 'elk.layered.crossingMinimization.strategy': 'INTERACTIVE' works without me specifying position for nodes?

    点赞 评论 复制链接分享
  • weixin_39795419 weixin_39795419 4月前

    I'd assume that it's by chance.

    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    Many thanks!

    点赞 评论 复制链接分享
  • weixin_39795419 weixin_39795419 4月前

    Balanced: There's an option 'favorStraightEdges'. If you set it to false it should give you a balanced layout (I'll check later).

    Regarding the order: We (so far) do not regard the input order of the elements (as dagre does afaik). But you could set 'crossingMinimization.semiInteractive' and define an order for the three nodes via the 'elk.position' option: e.g. assign an x of 0 to one, an x of 10 to two and so on.

    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    I set 'elk.edgeRouting': 'POLYLINE' and it is now balanced:

    image

    I think I've found a bug: edge label is not inline. I will create a new issue to track it.

    Update: as said, the key is favourStraightEdges. different edgeRouting value has different default favourStraightEdges values that's why changing edgeRouting value also has effects.

    点赞 评论 复制链接分享
  • weixin_39795419 weixin_39795419 4月前

    That's because the favourStraightEdges option has different default values for orthogonal and polyline edges. Unless you set it explicitly, that is.

    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    I cannot make the crossingMinimization.semiInteractive solution work. I tried the following:

    js
    const graph = {
          id: 'root',
          layoutOptions: {
            'elk.direction': 'DOWN',
            'elk.layered.crossingMinimization.semiInteractive': true
          },
          children: [
            {
              id: 'n1',
              width: 100,
              height: 50,
              labels: [{ text: 'Chrismas' }]
            },
            {
              id: 'n2',
              width: 100,
              height: 50,
              labels: [{ text: 'Go shopping' }]
            },
            {
              id: 'n3',
              width: 100,
              height: 50,
              labels: [{ text: 'Let me think' }]
            },
            {
              id: 'n4',
              width: 100,
              height: 50,
              labels: [{ text: 'Laptop' }],
              layoutOptions: { 'elk.position': '[x=0,y=0]' }
            },
            {
              id: 'n5',
              width: 100,
              height: 50,
              labels: [{ text: 'iPhone' }],
              layoutOptions: { 'elk.position': '[x=10,y=0]' }
            },
            {
              id: 'n6',
              width: 100,
              height: 50,
              labels: [{ text: 'Car' }],
              layoutOptions: { 'elk.position': '[x=20,y=0]' }
            }
          ],
          edges: [
            {
              id: 'e1',
              sources: ['n1'],
              targets: ['n2'],
              type: 'DIRECTED',
              labels: [{ width: 80, height: 20, text: 'Get money' }]
            },
            {
              id: 'e2',
              sources: ['n2'],
              targets: ['n3'],
              type: 'DIRECTED'
            },
            {
              id: 'e3',
              sources: ['n3'],
              targets: ['n4'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'One' }]
            },
            {
              id: 'e4',
              sources: ['n3'],
              targets: ['n5'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Two' }]
            },
            {
              id: 'e5',
              sources: ['n3'],
              targets: ['n6'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Three' }]
            }
          ]
        }
    

    But it has no effect. Could you please kindly advise?

    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    I've got it:

    js
    const elk = new ELK({
      defaultLayoutOptions: {
        'elk.algorithm': 'layered',
        'elk.direction': 'RIGHT',
        'elk.padding': '[top=25,left=25,bottom=25,right=25]',
        'elk.spacing.componentComponent': 25,
        'elk.layered.spacing.nodeNodeBetweenLayers': 25,
        'elk.edgeLabels.inline': true,
        'elk.edgeRouting': 'SPLINES'
      }
    })
    
    js
    const graph = {
          id: 'root',
          layoutOptions: {
            'elk.direction': 'DOWN',
            'elk.layered.crossingMinimization.semiInteractive': true,
            'elk.layered.crossingMinimization.strategy': 'INTERACTIVE'
          },
          children: [
            {
              id: 'n1',
              width: 100,
              height: 50,
              labels: [{ text: 'Chrismas' }]
            },
            {
              id: 'n2',
              width: 100,
              height: 50,
              labels: [{ text: 'Go shopping' }]
            },
            {
              id: 'n3',
              width: 100,
              height: 50,
              labels: [{ text: 'Let me think' }]
            },
            {
              id: 'n4',
              width: 100,
              height: 50,
              labels: [{ text: 'Laptop' }],
              layoutOptions: { 'elk.position': '[x=0,y=0]' }
            },
            {
              id: 'n5',
              width: 100,
              height: 50,
              labels: [{ text: 'iPhone' }],
              layoutOptions: { 'elk.position': '[x=10,y=0]' }
            },
            {
              id: 'n6',
              width: 100,
              height: 50,
              labels: [{ text: 'Car' }],
              layoutOptions: { 'elk.position': '[x=20,y=0]' }
            }
          ],
          edges: [
            {
              id: 'e1',
              sources: ['n1'],
              targets: ['n2'],
              type: 'DIRECTED',
              labels: [{ width: 80, height: 20, text: 'Get money' }]
            },
            {
              id: 'e2',
              sources: ['n2'],
              targets: ['n3'],
              type: 'DIRECTED'
            },
            {
              id: 'e3',
              sources: ['n3'],
              targets: ['n4'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'One' }]
            },
            {
              id: 'e4',
              sources: ['n3'],
              targets: ['n5'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Two' }]
            },
            {
              id: 'e5',
              sources: ['n3'],
              targets: ['n6'],
              type: 'DIRECTED',
              labels: [{ width: 60, height: 20, text: 'Three' }]
            }
          ]
        }
    

    image

    点赞 评论 复制链接分享
  • weixin_39795419 weixin_39795419 4月前

    Setting 'elk.layered.crossingMinimization.strategy': 'INTERACTIVE' has more effects than you may want. Still, solely setting the semiInteractive doesn't work for me either. I'll double check it later.

    点赞 评论 复制链接分享
  • weixin_39609573 weixin_39609573 4月前

    Ok, let's not close it until you post the update.

    点赞 评论 复制链接分享

相关推荐