Elkjs Multiple Handles
import type { Edge } from '@xyflow/vue';
import type { ElkNode as ElkLayoutNode } from 'elkjs/lib/elk-api';
import type { ElkNode } from './nodes';
import { watch } from 'vue';
import { useNodesInitialized, useVueFlow } from '@xyflow/vue';
import ELK from 'elkjs/lib/elk.bundled.js';
// elk layouting options: https://www.eclipse.org/elk/reference/algorithms/org-eclipse-elk-layered.html
const layoutOptions = {
'elk.algorithm': 'layered',
'elk.direction': 'RIGHT',
'elk.layered.spacing.edgeNodeBetweenLayers': '40',
'elk.spacing.nodeNode': '40',
'elk.layered.nodePlacement.strategy': 'SIMPLE',
};
const elk = new ELK();
// uses elkjs to give each node a layouted position
export async function getLayoutedNodes(nodes: ElkNode[], edges: readonly Edge[]): Promise<ElkNode[]> {
const graph: ElkLayoutNode = {
id: 'root',
layoutOptions,
children: nodes.map((n) => {
const targetPorts = n.data.targetHandles.map((t) => ({
id: t.id,
// ⚠️ it's important to tell elk which side the port is on — targets are on the
// left (WEST) and sources on the right (EAST)
properties: { side: 'WEST' },
}));
const sourcePorts = n.data.sourceHandles.map((s) => ({
id: s.id,
properties: { side: 'EAST' },
}));
return {
id: n.id,
width: n.width ?? 150,
height: n.height ?? 50,
// ⚠️ tell elk the ports are fixed, to reduce edge crossings
properties: { 'org.eclipse.elk.portConstraints': 'FIXED_ORDER' },
// pass the node id too, so we can handle edges without a source/target handle
ports: [{ id: n.id }, ...targetPorts, ...sourcePorts],
};
}),
edges: edges.map((e) => ({
id: e.id,
sources: [e.sourceHandle || e.source],
targets: [e.targetHandle || e.target],
})),
};
const layoutedGraph = await elk.layout(graph);
return nodes.map((node) => {
const layoutedNode = layoutedGraph.children?.find((lgNode) => lgNode.id === node.id);
return {
...node,
position: {
x: layoutedNode?.x ?? 0,
y: layoutedNode?.y ?? 0,
},
};
});
}
export default function useLayoutNodes() {
const nodesInitialized = useNodesInitialized();
const { getNodes, getEdges, setNodes, fitView } = useVueFlow();
watch(
nodesInitialized,
async (initialized) => {
if (!initialized) {
return;
}
const layoutedNodes = await getLayoutedNodes(getNodes.value as unknown as ElkNode[], getEdges.value);
setNodes(layoutedNodes);
fitView();
},
{ immediate: true },
);
}Last updated on