# Layout, Fit, And Navigate Graphs

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:

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

Supported strategies:

| Strategy | Use when |
|---|---|
| `:layered` | Directed graphs where downstream nodes should move by depth. |
| `:tree` | Rooted or mostly rooted structures. |
| `:orthogonal` | Graphs rendered with orthogonal connector paths. |
| `:components` | Graphs 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:

```elixir
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:

```heex
<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:

```elixir
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:

```elixir
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:

```elixir
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:

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

Fit the current graph selection:

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

Server equivalents:

```elixir
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:

```elixir
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.

## Navigation Queries

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

```elixir
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:

```elixir
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.
