Skip to Content
ExamplesLayout

Elkjs Tree

Like our dagre example, this example shows how you can integrate elkjs with React Flow for more advanced tree layouts. The code for this example builds a similar tree to the dagre example, but you can look at the reference here to see what you can configure (hint: it’s a lot).

import { initialNodes, initialEdges } from './initialElements.js'; import ELK from 'elkjs/lib/elk.bundled.js'; import React, { useCallback, useLayoutEffect } from 'react'; import { Background, ReactFlow, ReactFlowProvider, addEdge, Panel, useNodesState, useEdgesState, useReactFlow, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; const elk = new ELK(); // Elk has a *huge* amount of options to configure. To see everything you can // tweak check out: // // - https://www.eclipse.org/elk/reference/algorithms.html // - https://www.eclipse.org/elk/reference/options.html const elkOptions = { 'elk.algorithm': 'layered', 'elk.layered.spacing.nodeNodeBetweenLayers': '100', 'elk.spacing.nodeNode': '80', }; const getLayoutedElements = (nodes, edges, options = {}) => { const isHorizontal = options?.['elk.direction'] === 'RIGHT'; const graph = { id: 'root', layoutOptions: options, children: nodes.map((node) => ({ ...node, // Adjust the target and source handle positions based on the layout // direction. targetPosition: isHorizontal ? 'left' : 'top', sourcePosition: isHorizontal ? 'right' : 'bottom', // Hardcode a width and height for elk to use when layouting. width: 150, height: 50, })), edges: edges, }; return elk .layout(graph) .then((layoutedGraph) => ({ nodes: layoutedGraph.children.map((node) => ({ ...node, // React Flow expects a position property on the node instead of `x` // and `y` fields. position: { x: node.x, y: node.y }, })), edges: layoutedGraph.edges, })) .catch(console.error); }; function LayoutFlow() { const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const { fitView } = useReactFlow(); const onConnect = useCallback( (params) => setEdges((eds) => addEdge(params, eds)), [], ); const onLayout = useCallback( ({ direction, useInitialNodes = false }) => { const opts = { 'elk.direction': direction, ...elkOptions }; const ns = useInitialNodes ? initialNodes : nodes; const es = useInitialNodes ? initialEdges : edges; getLayoutedElements(ns, es, opts).then( ({ nodes: layoutedNodes, edges: layoutedEdges }) => { setNodes(layoutedNodes); setEdges(layoutedEdges); window.requestAnimationFrame(() => fitView()); }, ); }, [nodes, edges], ); // Calculate the initial layout on mount. useLayoutEffect(() => { onLayout({ direction: 'DOWN', useInitialNodes: true }); }, []); return ( <ReactFlow nodes={nodes} edges={edges} onConnect={onConnect} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} fitView style={{ backgroundColor: "#F7F9FB" }} > <Panel position="top-right"> <button onClick={() => onLayout({ direction: 'DOWN' })}> vertical layout </button> <button onClick={() => onLayout({ direction: 'RIGHT' })}> horizontal layout </button> </Panel> <Background /> </ReactFlow> ); } export default () => ( <ReactFlowProvider> <LayoutFlow /> </ReactFlowProvider> );
Last updated on