Layout Engines
WireFlow supports four auto-layout algorithms via addon plugins. Each runs on the client to reposition nodes, with optional animated transitions.
Installation
Install the AlpineFlow npm package (core from npm gives you access to addon sub-path imports -- WireFlow's vendor bundle provides the core runtime):
npm install @getartisanflow/alpineflow
Then install only the peer dependencies you need:
npm install @dagrejs/dagre # for dagre layout
npm install d3-force # for force-directed layout
npm install d3-hierarchy # for tree/cluster layout
npm install elkjs # for ELK layout engine
app.js import pattern
Core comes from the WireFlow vendor bundle. Addons come from npm. They share a global registry.
// resources/js/app.js
// Core from WireFlow vendor bundle
import AlpineFlow from '../../vendor/getartisanflow/wireflow/dist/alpineflow.bundle.esm.js';
// Layout addons from npm (only import what you installed)
import AlpineFlowDagre from '@getartisanflow/alpineflow/dagre';
import AlpineFlowForce from '@getartisanflow/alpineflow/force';
import AlpineFlowHierarchy from '@getartisanflow/alpineflow/hierarchy';
import AlpineFlowElk from '@getartisanflow/alpineflow/elk';
document.addEventListener('alpine:init', () => {
window.Alpine.plugin(AlpineFlow);
window.Alpine.plugin(AlpineFlowDagre);
window.Alpine.plugin(AlpineFlowForce);
window.Alpine.plugin(AlpineFlowHierarchy);
window.Alpine.plugin(AlpineFlowElk);
});
Rebuild after adding imports:
npm run build
Dagre Layout
Hierarchical layout using the dagre algorithm. Best for directed acyclic graphs, org charts, and tree-like structures.
Client-side usage
<x-flow :nodes="$nodes" :edges="$edges">
<x-slot:node>
<x-flow-handle type="target" position="top" />
<span x-text="node.data.label"></span>
<x-flow-handle type="source" position="bottom" />
</x-slot:node>
<x-flow-panel position="top-right">
<button class="px-3 py-1.5 text-sm rounded border bg-white dark:bg-zinc-800"
x-on:click="$flow.layout({ direction: 'TB', duration: 300 })">
Auto Layout
</button>
</x-flow-panel>
</x-flow>
Options
| Option | Type | Default | Description |
|---|---|---|---|
direction |
string |
'TB' |
'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 |
Set handle positions to match layout direction |
fitView |
boolean |
true |
Fit viewport after layout |
duration |
number |
0 |
Animation duration in ms (0 for instant) |
Force Layout
Physics-based layout using d3-force. Connected nodes attract, unconnected nodes repel. Produces organic, natural-looking layouts.
Client-side usage
<x-flow :nodes="$nodes" :edges="$edges">
<x-slot:node>
<x-flow-handle type="target" position="top" />
<span x-text="node.data.label"></span>
<x-flow-handle type="source" position="bottom" />
</x-slot:node>
<x-flow-panel position="top-right">
<button class="px-3 py-1.5 text-sm rounded border bg-white dark:bg-zinc-800"
x-on:click="$flow.forceLayout({ charge: -500, duration: 500 })">
Force Layout
</button>
</x-flow-panel>
</x-flow>
Options
| Option | Type | Default | Description |
|---|---|---|---|
strength |
number |
0.1 |
Link force strength -- how strongly connected nodes pull toward each other |
distance |
number |
100 |
Ideal link distance between connected nodes |
charge |
number |
-300 |
Charge force (negative = repel, positive = attract) |
iterations |
number |
300 |
Number of simulation ticks to run |
center |
{ x, y } |
undefined |
Center point for the centering force |
fitView |
boolean |
true |
Fit viewport after layout |
duration |
number |
0 |
Animation duration in ms (0 for instant) |
Tree Layout
Tree and cluster layouts using d3-hierarchy. Ideal for hierarchical data with a single root -- file systems, org charts, decision trees.
Client-side usage
<x-flow :nodes="$nodes" :edges="$edges">
<x-slot:node>
<x-flow-handle type="target" position="top" />
<span x-text="node.data.label"></span>
<x-flow-handle type="source" position="bottom" />
</x-slot:node>
<x-flow-panel position="top-right">
<button class="px-3 py-1.5 text-sm rounded border bg-white dark:bg-zinc-800"
x-on:click="$flow.treeLayout({ layoutType: 'tree', direction: 'TB', duration: 300 })">
Tree Layout
</button>
<button class="px-3 py-1.5 text-sm rounded border bg-white dark:bg-zinc-800"
x-on:click="$flow.treeLayout({ layoutType: 'cluster', direction: 'LR', duration: 300 })">
Cluster Layout
</button>
</x-flow-panel>
</x-flow>
Options
| Option | Type | Default | Description |
|---|---|---|---|
layoutType |
string |
'tree' |
'tree' (tidy tree) or 'cluster' (dendrogram -- all leaves at same depth) |
direction |
string |
'TB' |
'TB', 'LR', 'BT', 'RL' |
nodeWidth |
number |
150 |
Horizontal spacing per node |
nodeHeight |
number |
100 |
Vertical spacing per node |
adjustHandles |
boolean |
true |
Set handle positions to match layout direction |
fitView |
boolean |
true |
Fit viewport after layout |
duration |
number |
0 |
Animation duration in ms (0 for instant) |
Tree vs. Cluster
- Tree (
'tree') -- a tidy tree layout that minimizes width while keeping nodes at their natural depth. - Cluster (
'cluster') -- a dendrogram layout that places all leaf nodes at the same depth, useful for comparing terminal nodes.
ELK Layout
Advanced layout algorithms from the Eclipse Layout Kernel via elkjs. ELK offers the most comprehensive set of layout strategies.
Client-side usage
<x-flow :nodes="$nodes" :edges="$edges">
<x-slot:node>
<x-flow-handle type="target" position="top" />
<span x-text="node.data.label"></span>
<x-flow-handle type="source" position="bottom" />
</x-slot:node>
<x-flow-panel position="top-right">
<button class="px-3 py-1.5 text-sm rounded border bg-white dark:bg-zinc-800"
x-on:click="$flow.elkLayout({ algorithm: 'layered', direction: 'DOWN', duration: 300 })">
ELK Layered
</button>
<button class="px-3 py-1.5 text-sm rounded border bg-white dark:bg-zinc-800"
x-on:click="$flow.elkLayout({ algorithm: 'stress', duration: 300 })">
ELK Stress
</button>
</x-flow-panel>
</x-flow>
Options
| Option | Type | Default | Description |
|---|---|---|---|
algorithm |
string |
'layered' |
Layout algorithm (see algorithms table) |
direction |
string |
'DOWN' |
'DOWN', 'RIGHT', 'UP', 'LEFT' |
nodeSpacing |
number |
50 |
Minimum spacing between nodes |
layerSpacing |
number |
50 |
Minimum spacing between layers |
adjustHandles |
boolean |
true |
Set handle positions to match layout direction |
fitView |
boolean |
true |
Fit viewport after layout |
duration |
number |
0 |
Animation duration in ms (0 for instant) |
Algorithms
| Algorithm | Description |
|---|---|
'layered' |
Layer-based approach for directed graphs. Clean hierarchical layouts with minimal edge crossings. |
'stress' |
Stress-minimization. Graph-theoretic distances match geometric distances. |
'mrtree' |
Optimized for tree structures. |
'radial' |
Concentric circles radiating from a root node. |
'force' |
Force-directed layout (ELK's implementation). |
'box' |
Packs disconnected components into a compact rectangle. |
'random' |
Random placement. Starting point for other algorithms. |
Server-side layout
Use the WithWireFlow trait to trigger layout from the server. The flowLayout() method dispatches the layout command to the client:
use ArtisanFlow\WireFlow\Concerns\WithWireFlow;
class FlowEditor extends Component
{
use WithWireFlow;
public function applyDagreLayout(): void
{
$this->flowLayout([
'algorithm' => 'dagre',
'direction' => 'TB',
'duration' => 300,
]);
}
public function applyForceLayout(): void
{
$this->flowLayout([
'algorithm' => 'force',
'charge' => -500,
'duration' => 500,
]);
}
public function applyTreeLayout(): void
{
$this->flowLayout([
'algorithm' => 'tree',
'direction' => 'LR',
'duration' => 300,
]);
}
public function applyElkLayout(): void
{
$this->flowLayout([
'algorithm' => 'elk',
'elkAlgorithm' => 'layered',
'direction' => 'DOWN',
'duration' => 300,
]);
}
}
<div>
<x-flow :nodes="$nodes" :edges="$edges">
<x-slot:node>
<x-flow-handle type="target" position="top" />
<span x-text="node.data.label"></span>
<x-flow-handle type="source" position="bottom" />
</x-slot:node>
</x-flow>
<div class="mt-4 flex gap-2">
<button wire:click="applyDagreLayout" class="px-3 py-1.5 text-sm rounded border">Dagre</button>
<button wire:click="applyForceLayout" class="px-3 py-1.5 text-sm rounded border">Force</button>
<button wire:click="applyTreeLayout" class="px-3 py-1.5 text-sm rounded border">Tree</button>
<button wire:click="applyElkLayout" class="px-3 py-1.5 text-sm rounded border">ELK</button>
</div>
</div>
Auto-layout config
Configure automatic layout via the :config prop. When enabled, the graph re-layouts on structural changes (node/edge additions and removals):
<x-flow
:nodes="$nodes"
:edges="$edges"
:config="[
'autoLayout' => [
'algorithm' => 'dagre',
'direction' => 'LR',
],
]"
>
<x-slot:node>
<x-flow-handle type="target" position="left" />
<span x-text="node.data.label"></span>
<x-flow-handle type="source" position="right" />
</x-slot:node>
</x-flow>
When autoLayout is set, adding or removing nodes/edges triggers an automatic re-layout with the configured algorithm and options.
<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-layout-tb').addEventListener('click', () => $flow.layout({ direction: 'TB', duration: 300 }));
document.getElementById('demo-layout-lr').addEventListener('click', () => $flow.layout({ direction: 'LR', duration: 300 }));
document.getElementById('demo-layout-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>
Layout animation CSS
The transition duration for animated layouts can be customized via CSS:
.flow-container {
--flow-layout-animation-duration: 0.5s;
}
Related
- Installation -- addon setup
- Server Commands --
flowLayout()trait method - Animation Basics -- animating node positions