Layout, Fit, And Navigate Graphs

Copy Markdown View Source

Graph apps need ways to arrange nodes and move the camera.

LivePanels provides headless layout helpers, graph fit commands, bounds calculation, and navigation queries. The application decides when users invoke them and how the UI presents the result.

Built-In Layout Strategies

Use LivePanels.Graph.layout/3 from the canvas LiveView:

socket = LivePanels.Graph.layout!(socket, :layered)

Supported strategies:

StrategyUse when
:layeredDirected graphs where downstream nodes should move by depth.
:treeRooted or mostly rooted structures.
:orthogonalGraphs rendered with orthogonal connector paths.
:componentsGraphs with separate connected components.

Layout commands move ordinary canvas items, so history, persistence, policy, and shared mode all see normal placement changes.

Layout Options

Pass options to tune spacing and axis:

socket =
  LivePanels.Graph.layout!(socket, :components,
    strategy: :layered,
    axis: :horizontal,
    component_axis: :vertical,
    layer_gap: 380,
    item_gap: 180,
    component_gap: 260
  )

Use application controls for common layout actions:

<button phx-click="graph_layout" phx-value-strategy="layered">Layered</button>
<button phx-click="graph_layout" phx-value-strategy="tree">Tree</button>
<button phx-click="graph_layout" phx-value-strategy="orthogonal">Tracks</button>

Handle the event:

def handle_event("graph_layout", %{"strategy" => strategy}, socket) do
  strategy =
    case strategy do
      "layered" -> :layered
      "tree" -> :tree
      "orthogonal" -> :orthogonal
      "components" -> :components
      _ -> :layered
    end

  {:noreply, LivePanels.Graph.layout!(socket, strategy)}
end

External Layout Engines

Use the adapter payload when an application wants an external layout engine:

state = LivePanels.Canvas.Read.state(socket)
adapter = LivePanels.Graph.Layout.port_aware_adapter(state)

The adapter names runtime nodes, ports, connectors, components, and boundaries. After an external engine returns node coordinates, convert them to placement commands:

commands =
  LivePanels.Graph.Layout.adapter_commands(state, [
    %{id: "node-a", x: 100, y: 80},
    %{id: "node-b", x: 480, y: 80}
  ])

socket =
  Enum.reduce(commands, socket, fn {:place_item, item_id, placement}, acc ->
    LivePanels.Canvas.Item.place!(acc, item_id, placement)
  end)

Keep external layout results on the command path. Do not directly rewrite item assigns.

Fit The Graph

Fit the whole graph:

<button {graph_fit_attrs()}>Fit graph</button>

Fit the current graph selection:

<button {graph_fit_selection_attrs()}>Fit selection</button>

Server equivalents:

socket = LivePanels.Graph.fit(socket, :graph)
socket = LivePanels.Graph.fit(socket, :selection)

Fit changes the actor's viewport. It does not mutate node placement.

Bounds

Use LivePanels.Graph.Bounds when application UI needs graph extents:

state = LivePanels.Canvas.Read.state(socket)
bounds = LivePanels.Graph.Bounds.bounds(state)

Use bounds for minimaps, overview panels, "empty graph" prompts, or app-level navigation. Use LivePanels.Graph.fit/2 when the canvas viewport should move.

Use LivePanels.Graph.Query.navigation/4 to derive graph navigation targets:

navigation =
  LivePanels.Graph.Query.navigation(
    LivePanels.Canvas.Read.state(socket),
    {:item, "node-a"},
    :outgoing,
    mode: :all
  )

targets = navigation.targets

Directions:

  • :incoming;
  • :outgoing;
  • :connected.

Use navigation queries to build inspector actions such as "select downstream", "jump to parent", or "show connected nodes".

Viewport Anchors

For product-level graph navigation, combine graph queries with LivePanels.Canvas.Viewport anchors:

state = LivePanels.Canvas.Read.state(socket)

socket =
  case LivePanels.Canvas.Viewport.fit_to_items_anchor(state, "current-step", ["step-7"]) do
    nil ->
      socket

    anchor ->
      socket
      |> LivePanels.Canvas.Viewport.put_anchor!(anchor)
      |> LivePanels.Canvas.Viewport.goto_anchor!("current-step")
  end

Use graph fit for topology-driven views. Use viewport anchors for named product locations.

Layout And Shared Mode

Graph layout commands are canvas commands. In shared mode, applying a layout changes shared item placement for everyone.

For collaborative apps, decide whether layout buttons are editor-only, whether viewers can fit their own viewport, and whether automatic layout should be policy-gated.

Viewport fit is actor-scoped. Graph layout is shared state.