Dagre Layout

The Dagre Layout addon provides automatic hierarchical graph layout using the dagre algorithm. It is well-suited for directed acyclic graphs, org charts, and tree-like structures.

Click the buttons to apply different layout directions:

<div x-data="flowCanvas({
    nodes: [
        { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Start' } },
        { id: 'b', position: { x: 50, y: 50 }, data: { label: 'Process' } },
        { id: 'c', position: { x: 100, y: 100 }, data: { label: 'Review' } },
        { id: 'd', position: { x: 150, y: 50 }, data: { label: 'Approve' } },
        { id: 'e', position: { x: 200, y: 150 }, data: { label: 'End' } },
    ],
    edges: [
        { id: 'e1', source: 'a', target: 'b' },
        { id: 'e2', source: 'a', target: 'c' },
        { id: 'e3', source: 'b', target: 'd' },
        { id: 'e4', source: 'c', target: 'd' },
        { id: 'e5', source: 'd', target: 'e' },
    ],
    background: 'dots',
    fitViewOnInit: true,
    controls: false,
    pannable: false,
    zoomable: false,
})" class="flow-container" style="height: 250px;"
   x-init="
        document.getElementById('demo-dagre-tb').addEventListener('click', () => $flow.layout({ direction: 'TB', duration: 300 }));
        document.getElementById('demo-dagre-lr').addEventListener('click', () => $flow.layout({ direction: 'LR', duration: 300 }));
        document.getElementById('demo-dagre-bt').addEventListener('click', () => $flow.layout({ direction: 'BT', duration: 300 }));
   ">
    <div x-flow-viewport>
        <template x-for="node in nodes" :key="node.id">
            <div x-flow-node="node">
                <div x-flow-handle:target></div>
                <span x-text="node.data.label"></span>
                <div x-flow-handle:source></div>
            </div>
        </template>
    </div>
</div>

Installation

Install the required peer dependency:

npm install @dagrejs/dagre

Then register the plugin:

import AlpineFlowDagre from '@getartisanflow/alpineflow/dagre'

Alpine.plugin(AlpineFlowDagre)

With WireFlow

If you're using WireFlow (AlpineFlow's Livewire integration), the core is loaded from the WireFlow vendor bundle. Addons work seamlessly — they share a global registry with the core, regardless of how each was loaded.

// Core from WireFlow vendor bundle
import AlpineFlow from '../../vendor/getartisanflow/wireflow/dist/alpineflow.bundle.esm.js';
// Addon from npm
import AlpineFlowDagre from '@getartisanflow/alpineflow/dagre';

document.addEventListener('alpine:init', () => {
    window.Alpine.plugin(AlpineFlow);
    window.Alpine.plugin(AlpineFlowDagre);
});

Usage

Trigger a layout from any Alpine expression or action using the $flow magic:

$flow.layout({ direction: 'TB', duration: 300 })

Options

Option Type Default Description
direction string 'TB' Layout direction: 'TB' (top-bottom), 'LR' (left-right), 'BT' (bottom-top), 'RL' (right-left)
nodesep number 50 Horizontal spacing between nodes in the same rank
ranksep number 50 Spacing between ranks (layers)
adjustHandles boolean true Automatically set handle positions to match the layout direction
fitView boolean true Fit the viewport to the laid-out graph after layout completes
duration number 0 Animation duration in milliseconds (0 for instant)

Auto-Layout

You can configure dagre as the automatic layout algorithm. When enabled, the graph re-layouts on structural changes (node/edge additions and removals):

<div x-data="flowCanvas({
    nodes,
    edges,
    autoLayout: {
        algorithm: 'dagre',
        direction: 'LR',
    },
})">
</div>

Add a node and watch the layout update automatically:

<div x-data="flowCanvas({
    nodes: [
        { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Root' } },
        { id: 'b', position: { x: 0, y: 0 }, data: { label: 'Child 1' } },
        { id: 'c', position: { x: 0, y: 0 }, data: { label: 'Child 2' } },
    ],
    edges: [
        { id: 'e1', source: 'a', target: 'b' },
        { id: 'e2', source: 'a', target: 'c' },
    ],
    autoLayout: { algorithm: 'dagre', direction: 'TB' },
    background: 'dots',
    fitViewOnInit: true,
    controls: false,
})" class="flow-container" style="height: 250px;"
   x-init="
        let counter = 0;
        document.getElementById('demo-autolayout-add').addEventListener('click', () => {
            counter++;
            let parent = nodes[Math.floor(Math.random() * nodes.length)].id;
            let id = 'new-' + counter;
            addNodes([{ id, position: { x: 0, y: 0 }, data: { label: 'Node ' + counter } }]);
            addEdges([{ id: 'enew-' + counter, source: parent, target: id }]);
        });
        document.getElementById('demo-autolayout-reset').addEventListener('click', () => {
            counter = 0;
            removeNodes(nodes.map(n => n.id));
            $nextTick(() => {
                addNodes([
                    { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Root' } },
                    { id: 'b', position: { x: 0, y: 0 }, data: { label: 'Child 1' } },
                    { id: 'c', position: { x: 0, y: 0 }, data: { label: 'Child 2' } },
                ]);
                addEdges([
                    { id: 'e1', source: 'a', target: 'b' },
                    { id: 'e2', source: 'a', target: 'c' },
                ]);
            });
        });
   ">
    <div x-flow-viewport>
        <template x-for="node in nodes" :key="node.id">
            <div x-flow-node="node">
                <div x-flow-handle:target></div>
                <span x-text="node.data.label"></span>
                <div x-flow-handle:source></div>
            </div>
        </template>
    </div>
</div>

See Also