Skip to Content
ExamplesLayout

Elkjs Multiple Handles

This example demonstrates how to configure elkjs  to use specific handles (called ‘ports’ in elkjs). This is helpful to reduce edge crossings and have more control over the layout. The important things to configure are unique ids for the handles / ports, the actual ports for elkjs with a correct side property and 'org.eclipse.elk.portConstraints' : 'FIXED_ORDER' for all nodes.

import { useEffect } from 'react'; import ELK from 'elkjs/lib/elk.bundled.js'; import { type Edge, useNodesInitialized, useReactFlow } from '@xyflow/react'; import { type ElkNode } from './nodes'; // elk layouting options can be found here: // 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 const getLayoutedNodes = async (nodes: ElkNode[], edges: Edge[]) => { const graph = { id: 'root', layoutOptions, children: nodes.map((n) => { const targetPorts = n.data.targetHandles.map((t) => ({ id: t.id, // ⚠️ it's important to let elk know on which side the port is // in this example 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, // ⚠️ we need to tell elk that the ports are fixed, in order to reduce edge crossings properties: { 'org.eclipse.elk.portConstraints': 'FIXED_ORDER', }, // we are also passing the id, so we can also handle edges without a sourceHandle or targetHandle option 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); const layoutedNodes = nodes.map((node) => { const layoutedNode = layoutedGraph.children?.find((lgNode) => lgNode.id === node.id); return { ...node, position: { x: layoutedNode?.x ?? 0, y: layoutedNode?.y ?? 0, }, }; }); return layoutedNodes; }; export default function useLayoutNodes() { const nodesInitialized = useNodesInitialized(); const { getNodes, getEdges, setNodes, fitView } = useReactFlow<ElkNode>(); useEffect(() => { if (nodesInitialized) { const layoutNodes = async () => { const layoutedNodes = await getLayoutedNodes(getNodes() as ElkNode[], getEdges()); setNodes(layoutedNodes); fitView(); }; layoutNodes(); } }, [nodesInitialized, getNodes, getEdges, setNodes, fitView]); return null; }
Last updated on