JavaScript is required

Third-party Layouts

relation-graph can integrate third-party layout engines such as d3-force, dagre, and elkjs.

1. Integration Principle

Third-party layout should compute node coordinates externally.

Then apply positions back to relation-graph via instance API:

  • graphInstance.updateNodePosition(nodeId, x, y)

Important: third-party layout is not executed by setJsonData, doLayout, or createLayout automatically.

2. Standard Integration Flow

  1. Read graph data from instance (getNodes, getLines).
  2. Build third-party algorithm input model.
  3. Run layout algorithm.
  4. Write computed coordinates back with updateNodePosition.
  5. Optionally call moveToCenter / zoomToFit.

3. d3-force Example

import * as d3 from 'd3-force';

async function applyD3ForceLayout(graphInstance) {
  const nodes = graphInstance.getNodes().map(n => ({ id: n.id }));
  const links = graphInstance.getLines().map(l => ({ source: l.from, target: l.to }));

  const simulation = d3.forceSimulation(nodes)
    .force('link', d3.forceLink(links).id(d => d.id).distance(100))
    .force('charge', d3.forceManyBody().strength(-200))
    .force('center', d3.forceCenter(0, 0));

  return new Promise(resolve => {
    simulation.on('end', () => {
      nodes.forEach(d => graphInstance.updateNodePosition(d.id, d.x, d.y));
      resolve(undefined);
    });
  });
}

4. dagre Example

import * as dagre from 'dagre';

async function applyDagreLayout(graphInstance) {
  const g = new dagre.graphlib.Graph();
  g.setGraph({});
  g.setDefaultEdgeLabel(() => ({}));

  graphInstance.getNodes().forEach(node => g.setNode(node.id, { width: 100, height: 50 }));
  graphInstance.getLines().forEach(line => g.setEdge(line.from, line.to));

  dagre.layout(g);
  g.nodes().forEach(nodeId => {
    const node = g.node(nodeId);
    graphInstance.updateNodePosition(nodeId, node.x, node.y);
  });
}

5. elkjs Example

import ELK from 'elkjs/lib/elk.bundled.js';

async function applyElkLayout(graphInstance) {
  const elk = new ELK();
  const elkGraph = {
    id: 'root',
    layoutOptions: { 'elk.algorithm': 'layered' },
    children: graphInstance.getNodes().map(n => ({ id: n.id, width: 100, height: 50 })),
    edges: graphInstance.getLines().map(l => ({ id: `${l.from}-${l.to}`, source: l.from, target: l.to }))
  };

  const layout = await elk.layout(elkGraph);
  layout.children.forEach(node => {
    graphInstance.updateNodePosition(node.id, node.x, node.y);
  });
}

6. Practical Notes

  • Ensure node ids in external layout model match relation-graph node ids.
  • After updating coordinates, call moveToCenter/zoomToFit only when desired.
  • For interactive editors, avoid full relayout on every small change; batch updates.
  • Keep node width/height assumptions consistent between external layout model and actual node render size.

7. Next Reading