Visualize.Layout.Force.Simulation (Visualize v0.2.25)

Copy Markdown View Source

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

force_config()

@type force_config() ::
  {:center, keyword()}
  | {:many_body, keyword()}
  | {:link, keyword()}
  | {:collision, keyword()}
  | {:x, keyword()}
  | {:y, keyword()}
  | {:radial, keyword()}

graph_link()

@type graph_link() :: %{
  :source => any(),
  :target => any(),
  optional(:strength) => number(),
  optional(:distance) => number(),
  optional(any()) => any()
}

graph_node()

@type graph_node() :: %{
  :id => any(),
  optional(:x) => number(),
  optional(:y) => number(),
  optional(:vx) => number(),
  optional(:vy) => number(),
  optional(:fx) => number() | nil,
  optional(:fy) => number() | nil,
  optional(any()) => any()
}

Functions

alpha(sim)

@spec alpha(GenServer.server()) :: float()

Gets the current alpha

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

fix_node(sim, node_id, x, y)

@spec fix_node(GenServer.server(), any(), number(), number()) :: :ok

Fixes a node's position

links(sim)

@spec links(GenServer.server()) :: [graph_link()]

Gets the current links with resolved source/target

nodes(sim)

@spec nodes(GenServer.server()) :: [graph_node()]

Gets the current nodes

restart(sim)

@spec restart(GenServer.server()) :: :ok

Resets alpha to 1.0; a running simulation keeps running, a stopped one stays stopped

set_alpha(sim, alpha)

@spec set_alpha(GenServer.server(), float()) :: :ok

Sets the alpha value, clamped to [0, 1]

set_alpha_target(sim, target)

@spec set_alpha_target(GenServer.server(), float()) :: :ok

Sets the target alpha, clamped to [0, 1]

set_links(sim, links)

@spec set_links(GenServer.server(), [graph_link()]) :: :ok

Updates links

set_nodes(sim, nodes)

@spec set_nodes(GenServer.server(), [graph_node()]) :: :ok

Updates nodes

start(sim)

@spec start(GenServer.server()) :: :ok

Starts the simulation

start_link(opts)

@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 raises ArgumentError

stop(sim)

@spec stop(GenServer.server()) :: :ok

Stops/pauses the simulation

subscribe(sim, pid)

@spec subscribe(GenServer.server(), pid()) :: :ok

Subscribe a process to receive tick updates

tick(sim)

@spec tick(GenServer.server()) :: :ok

Manually advance the simulation by one tick

unfix_node(sim, node_id)

@spec unfix_node(GenServer.server(), any()) :: :ok

Unfixes a node's position

unsubscribe(sim, pid)

@spec unsubscribe(GenServer.server(), pid()) :: :ok

Unsubscribe a process from tick updates