Force-directed graph layout.
Provides a physics-based simulation for positioning nodes in a graph. The simulation runs as a GenServer, making it suitable for real-time updates in LiveView applications.
Basic Usage
# Define nodes and links
nodes = [
%{id: "a"},
%{id: "b"},
%{id: "c"}
]
links = [
%{source: "a", target: "b"},
%{source: "b", target: "c"}
]
# Start simulation; a subscriber given here receives every tick from the first
{:ok, sim} = Visualize.Layout.Force.start_link(
nodes: nodes,
links: links,
subscribers: [self()]
)
# A process that joins a running simulation later subscribes
Visualize.Layout.Force.subscribe(sim, other_pid)LiveView Integration
def mount(_params, _session, socket) do
{:ok, sim} = Visualize.Layout.Force.start_link(
nodes: @nodes,
links: @links,
forces: [
{:center, x: 300, y: 200},
{:many_body, strength: -100},
{:link, distance: 50}
],
subscribers: [self()]
)
{:ok, assign(socket, sim: sim, nodes: [], links: [])}
end
def handle_info({:force_tick, %{nodes: nodes, links: links}}, socket) do
{:noreply, assign(socket, nodes: nodes, links: links)}
endAvailable Forces
:center- Keeps the graph centeredx,y: center coordinatesstrength: how strongly to center (default: 1)
:many_body- Repulsion/attraction between all nodesstrength: negative for repulsion, positive for attraction (default: -30)theta: Barnes-Hut approximation threshold (default: 0.9)distance_min,distance_max: distance bounds
:link- Spring forces between connected nodesdistance: target distance (default: 30)strength: spring strength (default: 1)iterations: constraint iterations per tick (default: 1)
:collision- Prevents node overlapradius: node radius or function (default: 10)strength: collision strength (default: 1)
:x- Push nodes toward an x positionx: target x or functionstrength: force strength (default: 0.1)
:y- Push nodes toward a y positiony: target y or functionstrength: force strength (default: 0.1)
:radial- Push nodes toward a circleradius: target radius or functionx,y: circle centerstrength: force strength (default: 0.1)
Summary
Functions
Runs a simulation synchronously until it reaches equilibrium.
Functions
Runs a simulation synchronously until it reaches equilibrium.
Useful for generating static layouts without a GenServer.
Options
:nodes- list of nodes (required):links- list of links (optional):forces- force configuration (optional):iterations- maximum iterations (default: 300):alpha- the starting temperature (default: 1.0); below 1 a reheat, d3'salpha(a).restart(), for nodes that already carry their positions:alpha_decay- the fraction of alpha lost per iteration (default:1 - 0.001 ^ (1 / iterations)); d3's own is1 - 0.001 ^ (1 / 300)
A node given with :x and :y starts there, and with :vx and :vy keeps that
velocity, so passing a previous run's nodes with a lower :alpha and a few
:iterations continues that layout rather than starting a new one (spec/06 §8.3,
D-132). With every option at its default the layout starts from the spiral.
Returns
A map with :nodes and :links containing final positions, each node carrying its
final :x, :y, :vx and :vy.
Examples
result = Visualize.Layout.Force.run(
nodes: nodes,
links: links,
forces: [{:center, x: 200, y: 200}]
)
positioned_nodes = result.nodes