Force-directed graph layout simulation using velocity Verlet integration.
Runs as a GenServer that can broadcast position updates to LiveView processes or PubSub topics.
Examples
nodes = [
%{id: "a", x: 0, y: 0},
%{id: "b", x: 100, y: 0},
%{id: "c", x: 50, y: 100}
]
links = [
%{source: "a", target: "b"},
%{source: "b", target: "c"}
]
{:ok, sim} = Visualize.Layout.Force.Simulation.start_link(
nodes: nodes,
links: links,
forces: [
{:center, x: 200, y: 200},
{:many_body, strength: -30},
{:link, distance: 50}
],
# In LiveView, receive updates from the first tick
subscribers: [self()]
)A subscriber given in :subscribers is registered in init/1, before the first tick
is armed, so it receives every tick. subscribe/2 is for a process that joins later:
it is a cast sent after start_link/1 returns, and a caller descheduled for longer
than a short simulation runs would find every tick already sent to nobody
(spec/06 §8.4, #483).
Visualize.Layout.Force.Simulation.subscribe(sim, other_pid)
Summary
Functions
Gets the current alpha
Returns a specification to start this module under a supervisor.
Fixes a node's position
Gets the current links with resolved source/target
Gets the current nodes
Resets alpha to 1.0; a running simulation keeps running, a stopped one stays stopped
Sets the alpha value, clamped to [0, 1]
Sets the target alpha, clamped to [0, 1]
Updates links
Updates nodes
Starts the simulation
Starts a new force simulation.
Stops/pauses the simulation
Subscribe a process to receive tick updates
Manually advance the simulation by one tick
Unfixes a node's position
Unsubscribe a process from tick updates
Types
Functions
@spec alpha(GenServer.server()) :: float()
Gets the current alpha
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec fix_node(GenServer.server(), any(), number(), number()) :: :ok
Fixes a node's position
@spec links(GenServer.server()) :: [graph_link()]
Gets the current links with resolved source/target
@spec nodes(GenServer.server()) :: [graph_node()]
Gets the current nodes
@spec restart(GenServer.server()) :: :ok
Resets alpha to 1.0; a running simulation keeps running, a stopped one stays stopped
@spec set_alpha(GenServer.server(), float()) :: :ok
Sets the alpha value, clamped to [0, 1]
@spec set_alpha_target(GenServer.server(), float()) :: :ok
Sets the target alpha, clamped to [0, 1]
@spec set_links(GenServer.server(), [graph_link()]) :: :ok
Updates links
@spec set_nodes(GenServer.server(), [graph_node()]) :: :ok
Updates nodes
@spec start(GenServer.server()) :: :ok
Starts the simulation
@spec start_link(keyword()) :: GenServer.on_start()
Starts a new force simulation.
Options
:nodes- list of node maps (required):links- list of link maps (optional):forces- list of force configurations (optional):alpha- initial alpha value (default: 1.0):alpha_min- minimum alpha to stop simulation (default: 0.001):alpha_decay- alpha decay rate (default: 0.0228):alpha_target- target alpha (default: 0):velocity_decay- velocity decay (default: 0.4):auto_start- start simulation immediately (default: true):subscribers- pids registered and monitored before the first tick is armed, so each receives every tick (default:[]); anything but a list of pids raisesArgumentError
@spec stop(GenServer.server()) :: :ok
Stops/pauses the simulation
@spec subscribe(GenServer.server(), pid()) :: :ok
Subscribe a process to receive tick updates
@spec tick(GenServer.server()) :: :ok
Manually advance the simulation by one tick
@spec unfix_node(GenServer.server(), any()) :: :ok
Unfixes a node's position
@spec unsubscribe(GenServer.server(), pid()) :: :ok
Unsubscribe a process from tick updates