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
- Read graph data from instance (
getNodes,getLines). - Build third-party algorithm input model.
- Run layout algorithm.
- Write computed coordinates back with
updateNodePosition. - 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/zoomToFitonly 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.