Each example runs through the standalone elk package and is shown beside a precomputed elkjs result for the same graph, options, and SVG renderer — no mermaid, no diagram DSL. Both panels use the same coordinate scale, so spacing and routing differences stay visible. Arrowheads mark confirmed target endpoints in both panels. This is a behavioral comparison, not a claim of pixel or layout parity.

The dashed amber rectangle marks the bounds returned by each layout engine. Select Actual size to inspect routes closely; large diagrams then scroll inside their panel.

Branch & merge

A directed graph laid out in layers, top-to-bottom. Note the right-angle (orthogonal) edge routing — the ELK signature.

Dart ELK284.0 × 320.0 units
Branch & merge Dart Start Build Test Merge Deploy
elkjs 0.9.3284.0 × 320.0 units
Branch & merge elkjs Start Build Test Merge Deploy
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "DOWN",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "NONE",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "a",
      "width": 110,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "b",
      "width": 110,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "c",
      "width": 110,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "d",
      "width": 110,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "e",
      "width": 110,
      "height": 44,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "e1",
      "sources": [
        "a"
      ],
      "targets": [
        "b"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e2",
      "sources": [
        "a"
      ],
      "targets": [
        "c"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e3",
      "sources": [
        "b"
      ],
      "targets": [
        "d"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e4",
      "sources": [
        "c"
      ],
      "targets": [
        "d"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e5",
      "sources": [
        "d"
      ],
      "targets": [
        "e"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

Nested cluster

Compound nodes (clusters) are sized and positioned by the layout; children stay inside their parent. Edges cross cluster borders orthogonally.

Dart ELK288.0 × 261.0 units
Nested cluster Dart Input Worker A Worker B Output
elkjs 0.9.3268.0 × 270.0 units
Nested cluster elkjs Input Worker A Worker B Output
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "DOWN",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "NONE",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "in",
      "width": 100,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "pool",
      "width": 0,
      "height": 0,
      "layoutOptions": {},
      "children": [
        {
          "id": "w1",
          "width": 100,
          "height": 44,
          "layoutOptions": {}
        },
        {
          "id": "w2",
          "width": 100,
          "height": 44,
          "layoutOptions": {}
        }
      ]
    },
    {
      "id": "out",
      "width": 100,
      "height": 44,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "e1",
      "sources": [
        "in"
      ],
      "targets": [
        "w1"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e2",
      "sources": [
        "in"
      ],
      "targets": [
        "w2"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e3",
      "sources": [
        "w1"
      ],
      "targets": [
        "out"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e4",
      "sources": [
        "w2"
      ],
      "targets": [
        "out"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

Left-to-right dependency graph

The same algorithm with horizontal flow — exactly the shape a package dependency visualization needs. Each node is a package; edges are “depends on”.

Dart ELK784.0 × 152.0 units
Left-to-right dependency graph Dart my_app my_app_ui my_core elk http meta
elkjs 0.9.3784.0 × 152.0 units
Left-to-right dependency graph elkjs my_app my_app_ui my_core elk http meta
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "RIGHT",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "NONE",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "app",
      "width": 120,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "ui",
      "width": 120,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "core",
      "width": 120,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "elk",
      "width": 120,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "http",
      "width": 120,
      "height": 44,
      "layoutOptions": {}
    },
    {
      "id": "meta",
      "width": 120,
      "height": 44,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "e1",
      "sources": [
        "app"
      ],
      "targets": [
        "ui"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e2",
      "sources": [
        "app"
      ],
      "targets": [
        "core"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e3",
      "sources": [
        "ui"
      ],
      "targets": [
        "core"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e4",
      "sources": [
        "core"
      ],
      "targets": [
        "elk"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e5",
      "sources": [
        "core"
      ],
      "targets": [
        "http"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e6",
      "sources": [
        "elk"
      ],
      "targets": [
        "meta"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "e7",
      "sources": [
        "http"
      ],
      "targets": [
        "meta"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

Nested services in three directions

A left-to-right system contains a vertical service group and a right-to-left worker pool. Cross-boundary edges keep their endpoint labels through all three coordinate frames.

elkjs 0.9.3 ignores child directions under INCLUDE_CHILDREN. Dart honors the DOWN service group and LEFT worker pool, so this layout is taller. Broader hierarchy compatibility remains tracked. Track #78

Dart ELK866.0 × 356.0 units
Nested services in three directions Dart Request Service platform API gateway Worker pool Worker A Queue Worker B Cache Audit log Response result 200
elkjs 0.9.3900.0 × 239.0 units
Nested services in three directions elkjs Request Service platform API gateway Worker pool Worker A Queue Worker B Cache Audit log Response result 200
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "RIGHT",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "NONE",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "request",
      "width": 104,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "platform",
      "width": 0,
      "height": 0,
      "layoutOptions": {
        "elk.direction": "DOWN",
        "elk.nodeLabels.placement": "INSIDE V_TOP H_CENTER"
      },
      "children": [
        {
          "id": "gateway",
          "width": 104,
          "height": 42,
          "layoutOptions": {}
        },
        {
          "id": "workers",
          "width": 0,
          "height": 0,
          "layoutOptions": {
            "elk.direction": "LEFT",
            "elk.nodeLabels.placement": "INSIDE V_TOP H_CENTER"
          },
          "children": [
            {
              "id": "workerA",
              "width": 96,
              "height": 40,
              "layoutOptions": {}
            },
            {
              "id": "queue",
              "width": 88,
              "height": 40,
              "layoutOptions": {}
            },
            {
              "id": "workerB",
              "width": 96,
              "height": 40,
              "layoutOptions": {}
            }
          ],
          "edges": [
            {
              "id": "queueA",
              "sources": [
                "queue"
              ],
              "targets": [
                "workerA"
              ],
              "layoutOptions": {
                "elk.edge.thickness": 1
              }
            },
            {
              "id": "queueB",
              "sources": [
                "queue"
              ],
              "targets": [
                "workerB"
              ],
              "layoutOptions": {
                "elk.edge.thickness": 1
              }
            }
          ],
          "labels": [
            {
              "text": "Worker pool",
              "width": 100,
              "height": 16
            }
          ]
        },
        {
          "id": "cache",
          "width": 92,
          "height": 40,
          "layoutOptions": {}
        }
      ],
      "edges": [
        {
          "id": "dispatch",
          "sources": [
            "gateway"
          ],
          "targets": [
            "queue"
          ],
          "layoutOptions": {
            "elk.edge.thickness": 1
          }
        },
        {
          "id": "warm",
          "sources": [
            "cache"
          ],
          "targets": [
            "workerA"
          ],
          "layoutOptions": {
            "elk.edge.thickness": 1
          }
        }
      ],
      "labels": [
        {
          "text": "Service platform",
          "width": 100,
          "height": 16
        }
      ]
    },
    {
      "id": "audit",
      "width": 92,
      "height": 40,
      "layoutOptions": {}
    },
    {
      "id": "response",
      "width": 104,
      "height": 42,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "enter",
      "sources": [
        "request"
      ],
      "targets": [
        "gateway"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "complete",
      "sources": [
        "workerB"
      ],
      "targets": [
        "response"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      },
      "labels": [
        {
          "text": "result",
          "width": 34,
          "height": 12,
          "layoutOptions": {
            "elk.edgeLabels.placement": "TAIL",
            "elk.layered.edgeLabels.sideSelection": "ALWAYS_UP"
          }
        },
        {
          "text": "200",
          "width": 22,
          "height": 12,
          "layoutOptions": {
            "elk.edgeLabels.placement": "HEAD",
            "elk.layered.edgeLabels.sideSelection": "ALWAYS_UP"
          }
        }
      ]
    },
    {
      "id": "record",
      "sources": [
        "gateway"
      ],
      "targets": [
        "audit"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "publish",
      "sources": [
        "audit"
      ],
      "targets": [
        "response"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

Ports, loops, and a message broker

Named ports keep publishers and consumers on deliberate sides. Retry and health loops show route clearance around nodes.

FIXED_SIDE fixes each port side, but permits different positions and node order. Both engines use the same explicit loop clearance; alternative arrangements can still be valid. Track #76

Dart ELK616.0 × 424.0 units
Ports, loops, and a message broker Dart Ingest API Message broker Orders Billing Analytics Archive Operator Status page retry
elkjs 0.9.3616.0 × 424.0 units
Ports, loops, and a message broker elkjs Ingest API Message broker Orders Billing Analytics Archive Operator Status page retry
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "RIGHT",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=10.0,bottom=10.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "NONE",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "ingest",
      "width": 100,
      "height": 54,
      "layoutOptions": {
        "elk.portConstraints": "FIXED_SIDE"
      },
      "ports": [
        {
          "id": "ingestOut",
          "width": 8,
          "height": 8,
          "layoutOptions": {
            "elk.port.side": "EAST"
          }
        },
        {
          "id": "healthOut",
          "width": 8,
          "height": 8,
          "layoutOptions": {
            "elk.port.side": "NORTH"
          }
        },
        {
          "id": "healthIn",
          "width": 8,
          "height": 8,
          "layoutOptions": {
            "elk.port.side": "NORTH"
          }
        }
      ]
    },
    {
      "id": "broker",
      "width": 126,
      "height": 94,
      "layoutOptions": {
        "elk.portConstraints": "FIXED_SIDE"
      },
      "ports": [
        {
          "id": "brokerIn",
          "width": 8,
          "height": 8,
          "layoutOptions": {
            "elk.port.side": "WEST"
          }
        },
        {
          "id": "ordersOut",
          "width": 8,
          "height": 8,
          "layoutOptions": {
            "elk.port.side": "EAST"
          }
        },
        {
          "id": "eventsOut",
          "width": 8,
          "height": 8,
          "layoutOptions": {
            "elk.port.side": "EAST"
          }
        },
        {
          "id": "deadLetter",
          "width": 8,
          "height": 8,
          "layoutOptions": {
            "elk.port.side": "SOUTH"
          }
        }
      ]
    },
    {
      "id": "orders",
      "width": 106,
      "height": 48,
      "layoutOptions": {}
    },
    {
      "id": "billing",
      "width": 106,
      "height": 48,
      "layoutOptions": {}
    },
    {
      "id": "analytics",
      "width": 106,
      "height": 48,
      "layoutOptions": {}
    },
    {
      "id": "archive",
      "width": 106,
      "height": 48,
      "layoutOptions": {}
    },
    {
      "id": "operator",
      "width": 106,
      "height": 48,
      "layoutOptions": {}
    },
    {
      "id": "status",
      "width": 96,
      "height": 44,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "publish",
      "sources": [
        "ingestOut"
      ],
      "targets": [
        "brokerIn"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "ordersFlow",
      "sources": [
        "ordersOut"
      ],
      "targets": [
        "orders"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "billingFlow",
      "sources": [
        "ordersOut"
      ],
      "targets": [
        "billing"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "metricsFlow",
      "sources": [
        "eventsOut"
      ],
      "targets": [
        "analytics"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "archiveFlow",
      "sources": [
        "eventsOut"
      ],
      "targets": [
        "archive"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "retry",
      "sources": [
        "deadLetter"
      ],
      "targets": [
        "brokerIn"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      },
      "labels": [
        {
          "text": "retry",
          "width": 30,
          "height": 12,
          "layoutOptions": {
            "elk.edgeLabels.placement": "CENTER",
            "elk.layered.edgeLabels.sideSelection": "ALWAYS_UP"
          }
        }
      ]
    },
    {
      "id": "healthLoop",
      "sources": [
        "healthOut"
      ],
      "targets": [
        "healthIn"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "alert",
      "sources": [
        "broker"
      ],
      "targets": [
        "operator"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "dashboard",
      "sources": [
        "analytics"
      ],
      "targets": [
        "status"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

Cyclic incident workflow

Feedback paths return failed verification and monitoring alerts to earlier stages. Cycle breaking restores every arrow after the layered order is chosen.

Dart ELK1244.0 × 127.0 units
Cyclic incident workflow Dart Detect Triage Assign Diagnose Mitigate Verify Monitor Resolve Review
elkjs 0.9.31234.0 × 119.0 units
Cyclic incident workflow elkjs Detect Triage Assign Diagnose Mitigate Verify Monitor Resolve Review
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "RIGHT",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "NONE",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY_MODEL_ORDER",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "detect",
      "width": 96,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "triage",
      "width": 96,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "assign",
      "width": 96,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "diagnose",
      "width": 104,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "mitigate",
      "width": 104,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "verify",
      "width": 96,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "monitor",
      "width": 96,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "resolve",
      "width": 96,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "review",
      "width": 96,
      "height": 42,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "d-t",
      "sources": [
        "detect"
      ],
      "targets": [
        "triage"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "t-a",
      "sources": [
        "triage"
      ],
      "targets": [
        "assign"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "a-d",
      "sources": [
        "assign"
      ],
      "targets": [
        "diagnose"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "d-m",
      "sources": [
        "diagnose"
      ],
      "targets": [
        "mitigate"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "m-v",
      "sources": [
        "mitigate"
      ],
      "targets": [
        "verify"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "v-m",
      "sources": [
        "verify"
      ],
      "targets": [
        "mitigate"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "v-o",
      "sources": [
        "verify"
      ],
      "targets": [
        "monitor"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "o-t",
      "sources": [
        "monitor"
      ],
      "targets": [
        "triage"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "o-r",
      "sources": [
        "monitor"
      ],
      "targets": [
        "resolve"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "r-p",
      "sources": [
        "resolve"
      ],
      "targets": [
        "review"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "p-d",
      "sources": [
        "review"
      ],
      "targets": [
        "detect"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

Shared deployment routes

Three build sources fan into one release hub, then share outgoing trunks to four environments. Junctions expose the merged route structure.

Release → Production → Telemetry → Rollback → Release is a cycle, so one edge must run backward. elkjs chooses Production → Telemetry; Dart chooses Rollback → Release. Follow the arrowheads to read the same directed sequence in either layout. Track #77

Dart ELK700.0 × 312.0 units
Shared deployment routes Dart Web build API build Worker build Release hub Development Staging Canary Production Telemetry Rollback
elkjs 0.9.3566.0 × 354.0 units
Shared deployment routes elkjs Web build API build Worker build Release hub Development Staging Canary Production Telemetry Rollback
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "RIGHT",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": true,
    "elk.layered.nodePlacement.bk.fixedAlignment": "NONE",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "web",
      "width": 94,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "api",
      "width": 94,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "worker",
      "width": 94,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "release",
      "width": 112,
      "height": 58,
      "layoutOptions": {}
    },
    {
      "id": "dev",
      "width": 110,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "staging",
      "width": 110,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "canary",
      "width": 88,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "prod",
      "width": 88,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "telemetry",
      "width": 100,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "rollback",
      "width": 100,
      "height": 42,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "bundle-web",
      "sources": [
        "web"
      ],
      "targets": [
        "release"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "bundle-api",
      "sources": [
        "api"
      ],
      "targets": [
        "release"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "bundle-worker",
      "sources": [
        "worker"
      ],
      "targets": [
        "release"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "deploy-dev",
      "sources": [
        "release"
      ],
      "targets": [
        "dev"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "deploy-staging",
      "sources": [
        "release"
      ],
      "targets": [
        "staging"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "deploy-canary",
      "sources": [
        "release"
      ],
      "targets": [
        "canary"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "deploy-prod",
      "sources": [
        "release"
      ],
      "targets": [
        "prod"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "observe",
      "sources": [
        "prod"
      ],
      "targets": [
        "telemetry"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "revert",
      "sources": [
        "telemetry"
      ],
      "targets": [
        "rollback"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "retryRelease",
      "sources": [
        "rollback"
      ],
      "targets": [
        "release"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

BK alignment: upper sweep

LEFTUP fixes the Brandes-Kopf sweep to its upper alignment. Compare the vertical lanes with the balanced variant.

Dart ELK672.0 × 171.0 units
BK alignment: upper sweep Dart Product idea UX design API design Mobile app Backend QA Security Release
elkjs 0.9.3672.0 × 178.0 units
BK alignment: upper sweep elkjs Product idea UX design API design Mobile app Backend QA Security Release
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "RIGHT",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "LEFTUP",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "idea",
      "width": 88,
      "height": 40,
      "layoutOptions": {}
    },
    {
      "id": "ux",
      "width": 102,
      "height": 46,
      "layoutOptions": {}
    },
    {
      "id": "apiDesign",
      "width": 102,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "mobile",
      "width": 110,
      "height": 54,
      "layoutOptions": {}
    },
    {
      "id": "backend",
      "width": 110,
      "height": 48,
      "layoutOptions": {}
    },
    {
      "id": "qa",
      "width": 92,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "security",
      "width": 100,
      "height": 46,
      "layoutOptions": {}
    },
    {
      "id": "ship",
      "width": 88,
      "height": 48,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "idea-ux",
      "sources": [
        "idea"
      ],
      "targets": [
        "ux"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "idea-api",
      "sources": [
        "idea"
      ],
      "targets": [
        "apiDesign"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "ux-mobile",
      "sources": [
        "ux"
      ],
      "targets": [
        "mobile"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "api-mobile",
      "sources": [
        "apiDesign"
      ],
      "targets": [
        "mobile"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "api-backend",
      "sources": [
        "apiDesign"
      ],
      "targets": [
        "backend"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "mobile-qa",
      "sources": [
        "mobile"
      ],
      "targets": [
        "qa"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "backend-qa",
      "sources": [
        "backend"
      ],
      "targets": [
        "qa"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "backend-security",
      "sources": [
        "backend"
      ],
      "targets": [
        "security"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "qa-ship",
      "sources": [
        "qa"
      ],
      "targets": [
        "ship"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "security-ship",
      "sources": [
        "security"
      ],
      "targets": [
        "ship"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

BK alignment: balanced

BALANCED combines all four BK sweeps for the same graph and input order, producing a different but still overlap-free layout.

Dart ELK672.0 × 171.0 units
BK alignment: balanced Dart Product idea UX design API design Mobile app Backend QA Security Release
elkjs 0.9.3672.0 × 178.0 units
BK alignment: balanced elkjs Product idea UX design API design Mobile app Backend QA Security Release
Input graph JSON and layout options
{
  "id": "root",
  "layoutOptions": {
    "elk.algorithm": "layered",
    "elk.direction": "RIGHT",
    "elk.edgeRouting": "ORTHOGONAL",
    "elk.randomSeed": 1,
    "elk.padding": "[top=12.0,left=12.0,bottom=12.0,right=12.0]",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.spacing.nodeNode": 40,
    "elk.spacing.edgeNode": 20,
    "elk.layered.spacing.nodeNodeBetweenLayers": 40,
    "elk.layered.spacing.edgeNodeBetweenLayers": 20,
    "elk.spacing.edgeEdge": 10,
    "elk.layered.spacing.edgeEdgeBetweenLayers": 10,
    "elk.spacing.labelLabel": 0,
    "elk.spacing.edgeLabel": 2,
    "elk.spacing.labelNode": 5,
    "elk.spacing.labelPortHorizontal": 1,
    "elk.spacing.labelPortVertical": 1,
    "elk.spacing.portsSurrounding": "[top=0.0,bottom=0.0]",
    "elk.spacing.nodeSelfLoop": 20,
    "elk.layered.mergeEdges": false,
    "elk.layered.nodePlacement.bk.fixedAlignment": "BALANCED",
    "elk.layered.nodePlacement.bk.edgeStraightening": "NONE",
    "elk.layered.cycleBreaking.strategy": "GREEDY",
    "elk.layered.considerModelOrder.strategy": "NONE",
    "elk.layered.crossingMinimization.forceNodeModelOrder": false
  },
  "children": [
    {
      "id": "idea",
      "width": 88,
      "height": 40,
      "layoutOptions": {}
    },
    {
      "id": "ux",
      "width": 102,
      "height": 46,
      "layoutOptions": {}
    },
    {
      "id": "apiDesign",
      "width": 102,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "mobile",
      "width": 110,
      "height": 54,
      "layoutOptions": {}
    },
    {
      "id": "backend",
      "width": 110,
      "height": 48,
      "layoutOptions": {}
    },
    {
      "id": "qa",
      "width": 92,
      "height": 42,
      "layoutOptions": {}
    },
    {
      "id": "security",
      "width": 100,
      "height": 46,
      "layoutOptions": {}
    },
    {
      "id": "ship",
      "width": 88,
      "height": 48,
      "layoutOptions": {}
    }
  ],
  "edges": [
    {
      "id": "idea-ux",
      "sources": [
        "idea"
      ],
      "targets": [
        "ux"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "idea-api",
      "sources": [
        "idea"
      ],
      "targets": [
        "apiDesign"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "ux-mobile",
      "sources": [
        "ux"
      ],
      "targets": [
        "mobile"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "api-mobile",
      "sources": [
        "apiDesign"
      ],
      "targets": [
        "mobile"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "api-backend",
      "sources": [
        "apiDesign"
      ],
      "targets": [
        "backend"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "mobile-qa",
      "sources": [
        "mobile"
      ],
      "targets": [
        "qa"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "backend-qa",
      "sources": [
        "backend"
      ],
      "targets": [
        "qa"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "backend-security",
      "sources": [
        "backend"
      ],
      "targets": [
        "security"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "qa-ship",
      "sources": [
        "qa"
      ],
      "targets": [
        "ship"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    },
    {
      "id": "security-ship",
      "sources": [
        "security"
      ],
      "targets": [
        "ship"
      ],
      "layoutOptions": {
        "elk.edge.thickness": 1
      }
    }
  ]
}

Laid out as live Flutter widgets

Because elk is pure Dart, it runs inside Flutter too. Below, ELK positions real, interactive Flutter widgets — each card is a Material widget placed at the coordinates ELK computes, with the orthogonal edges drawn by a CustomPainter. Drag to pan, scroll to zoom, tap a node to select it.

Loading Flutter…
// elk is pure Dart, so it runs in Flutter.
final result = const ElkLayered().layout(ElkGraph(
  layoutOptions: ElkLayoutOptions(direction: ElkDirection.down),
  children: [
    for (final n in nodes)
      ElkNode(id: n.id, width: 168, height: 56),
  ],
  edges: [
    for (final (from, to) in edges)
      ElkEdge(id: '..', sources: [from], targets: [to]),
  ],
));

// Place each node as a real Flutter widget at ELK's coordinates…
Stack(children: [
  for (final spec in nodes)
    if (result.nodesById[spec.id] case final n?)
      Positioned(
        left: n.x, top: n.y,
        width: n.width, height: n.height,
        child: NodeCard(spec),      // a Material card
      ),
  // …and stroke ELK's orthogonal edge routes.
  CustomPaint(painter: EdgePainter(result.edges)),
]);

elk is a pure Dart implementation of layered graph layout. It provides an elkjs-style graph model and supports compound graphs, ports, configurable spacing, model-order constraints, and orthogonal edge routing.

The implementation follows the same algorithm family as Eclipse Layout Kernel and elkjs, but it is not a translation of either project and does not promise identical coordinates.

Use

import 'package:elk/elk.dart';
        
        void main() {
          final result = const ElkLayered().layout(
            ElkGraph(
              layoutOptions: const ElkLayoutOptions(direction: ElkDirection.down),
              children: [
                ElkNode(id: 'a', width: 80, height: 40),
                ElkNode(id: 'b', width: 80, height: 40),
                ElkNode(id: 'c', width: 80, height: 40),
              ],
              edges: [
                ElkEdge(id: 'e1', sources: ['a'], targets: ['b']),
                ElkEdge(id: 'e2', sources: ['a'], targets: ['c']),
              ],
            ),
          );
        
          for (final node in result.children) {
            print('${node.id}: ${node.x}, ${node.y}');
          }
        }
        

Node coordinates are relative to their parent. Use result.nodesById for a flat map with absolute coordinates.

Options

ElkLayoutOptions controls direction, spacing, node placement, fixed alignment, hierarchy handling, model-order handling, edge merging, and cycle breaking. For example:

const options = ElkLayoutOptions(
          direction: ElkDirection.right,
          spacingBaseValue: 24,
          forceNodeModelOrder: true,
        );
        

Edges can refer to a node ID or to an ElkPort ID. A node with children is laid out as a compound node. Existing elkjs graph JSON can be loaded with ElkGraph.fromJson.

Hierarchy modes

Set hierarchyHandling on root or compound ElkLayoutOptions. JSON accepts org.eclipse.elk.hierarchyHandling, elk.hierarchyHandling, and hierarchyHandling.

Mode Behavior
includeChildren / INCLUDE_CHILDREN Routes edges across included compound boundaries.
separateChildren / SEPARATE_CHILDREN Lays out groups independently. Edges internal to each group, including links from its own boundary ports to its children, remain routed; edges crossing a separated boundary remain in the result with empty sections.
inherit / INHERIT Uses the enclosing mode; at the root, resolves to SEPARATE_CHILDREN.

Omitting the root mode preserves Dart's existing INCLUDE_CHILDREN default. Omitting a compound override inherits its enclosing mode. An explicit included child does not reconnect a boundary separated by an ancestor. A direction-only compound override changes direction without changing the inherited mode; a hierarchy-only override preserves the inherited direction.

Unlike elkjs 0.9.3, Dart retains cross-boundary edge IDs with empty sections when an included parent contains a separated child, rather than throwing. Unrouted edges have no positioned labels. Their original label metadata remains on the input graph. Consumers should draw only the sections that are present.

Dart also retains its existing mixed-direction support under INCLUDE_CHILDREN. elkjs 0.9.3 ignores nested directions in that mode. The broader coordinated hierarchy optimization remains tracked in issue #78; these modes do not promise identical coordinates or crossing order.

Validation

The comparison tool under tool/validation runs the same graph set through this package and elkjs and writes side-by-side SVG output:

$ cd tool/validation
        $ npm install
        $ node run_elkjs.mjs
        $ cd ../..
        $ dart run tool/validation/compare.dart
        

The comparison checks layer assignment, node overlap, ordering, and graph bounds. Differences in within-layer ordering and exact coordinates are expected.

The hierarchy tests also read retained elkjs 0.9.3 inputs and outputs from test/fixtures/hierarchy_handling_elkjs.json. After an intentional reference update, regenerate them from the package root with node tool/validation/generate_hierarchy_reference.mjs. Ordinary tests never regenerate reference data.

License

MIT. The package contains a vendored derivative of dart_dagre (Apache-2.0). See NOTICE and lib/src/dagre/LICENSE.