Force Layout
The Force Layout addon provides force-directed graph layout using d3-force. It produces organic, physics-based layouts where connected nodes attract and unconnected nodes repel each other.
Click "Apply" to run the force simulation — nodes settle into a balanced arrangement:
<div x-data="flowCanvas({
nodes: [
{ id: 'a', position: { x: 50, y: 50 }, data: { label: 'A' } },
{ id: 'b', position: { x: 60, y: 60 }, data: { label: 'B' } },
{ id: 'c', position: { x: 70, y: 70 }, data: { label: 'C' } },
{ id: 'd', position: { x: 80, y: 80 }, data: { label: 'D' } },
{ id: 'e', position: { x: 90, y: 90 }, data: { label: 'E' } },
],
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' },
{ id: 'e6', source: 'c', target: 'e' },
],
background: 'dots',
fitViewOnInit: true,
controls: false,
pannable: false,
zoomable: false,
})" class="flow-container" style="height: 250px;"
x-init="
document.getElementById('demo-force-apply').addEventListener('click', () => $flow.forceLayout({ duration: 500 }));
document.getElementById('demo-force-reset').addEventListener('click', () => {
let i = 0;
for (const n of nodes) { n.position = { x: 50 + i * 10, y: 50 + i * 10 }; i++; }
fitView({ padding: 0.2 });
});
">
<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 d3-force
Then register the plugin:
import AlpineFlowForce from '@getartisanflow/alpineflow/force'
Alpine.plugin(AlpineFlowForce)
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 AlpineFlowForce from '@getartisanflow/alpineflow/force';
document.addEventListener('alpine:init', () => {
window.Alpine.plugin(AlpineFlow);
window.Alpine.plugin(AlpineFlowForce);
});
Usage
Trigger a force layout from any Alpine expression or action using the $flow magic:
$flow.forceLayout({ charge: -500, duration: 500 })
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 values 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 the viewport to the laid-out graph after layout completes |
duration |
number |
0 |
Animation duration in milliseconds (0 for instant) |
Try different charge values — stronger repulsion spreads nodes further apart:
<div x-data="flowCanvas({
nodes: [
{ id: 'a', position: { x: 0, y: 0 }, data: { label: 'A' } },
{ id: 'b', position: { x: 10, y: 10 }, data: { label: 'B' } },
{ id: 'c', position: { x: 20, y: 20 }, data: { label: 'C' } },
{ id: 'd', position: { x: 30, y: 30 }, data: { label: 'D' } },
{ id: 'e', position: { x: 40, y: 40 }, data: { label: 'E' } },
{ id: 'f', position: { x: 50, y: 50 }, data: { label: 'F' } },
],
edges: [
{ id: 'e1', source: 'a', target: 'b' },
{ id: 'e2', source: 'a', target: 'c' },
{ id: 'e3', source: 'b', target: 'd' },
{ id: 'e4', source: 'c', target: 'e' },
{ id: 'e5', source: 'd', target: 'f' },
{ id: 'e6', source: 'e', target: 'f' },
],
background: 'dots',
fitViewOnInit: true,
controls: false,
pannable: false,
zoomable: false,
})" class="flow-container" style="height: 250px;"
x-init="
document.getElementById('demo-force-tight').addEventListener('click', () => $flow.forceLayout({ charge: -100, duration: 500 }));
document.getElementById('demo-force-normal').addEventListener('click', () => $flow.forceLayout({ charge: -300, duration: 500 }));
document.getElementById('demo-force-spread').addEventListener('click', () => $flow.forceLayout({ charge: -800, duration: 500 }));
">
<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>