# `Choreo.View`
[🔗](https://github.com/code-shoily/choreo/blob/v0.14.1/lib/choreo/view.ex#L1)

Graph lenses — zoom, focus, and filter any Choreo diagram.

`Choreo.View` provides cross-module **view transforms** that produce
new diagram structs without mutating the original. Think of it as
"folding" or "zooming" a diagram — the same underlying graph, seen
through a different focal length.

Each diagram module must implement the `Choreo.Viewable` protocol to
define how edge metadata is pruned, how roots are resolved, and what
each zoom level means.

## Supported transforms

  * `focus/3` — keep a node and its N-hop neighbourhood
  * `focus_between/3` — keep only the shortest path between two nodes
  * `zoom/2` — filter by module-defined zoom level
  * `filter/2` — keep only nodes matching a predicate
  * `collapse/4` — aggregate multiple nodes into one

## When to use

Use `Choreo.View` when you need:

  * **Context views** — show only high-level concepts before drilling down
  * **Focus views** — highlight one node and its immediate neighbours
  * **Path views** — trace the connection between two specific nodes
  * **Filtered exports** — remove notes or internal details for stakeholder decks

## Examples

    # Focus on the "Concurrency" topic and its neighbourhood
    focused = Choreo.View.focus(mind_map, :concurrency, radius: 2)

    # Show only the shortest path from root to a deep leaf
    path_view = Choreo.View.focus_between(mind_map, :root, :deep_leaf)

    # Zoom out to show only root and main topics
    overview = Choreo.View.zoom(mind_map, level: 1)

    # Hide all note nodes
    no_notes = Choreo.View.filter(mind_map, fn _id, data ->
      data[:node_type] != :note
    end)

    # Render the focused view
    dot = Choreo.MindMap.to_dot(focused)

# `viewable`

```elixir
@type viewable() :: struct()
```

# `collapse`

```elixir
@spec collapse(
  viewable(),
  ({Yog.node_id(), map()} -&gt; boolean()),
  Yog.node_id(),
  keyword()
) ::
  viewable()
```

Collapses all nodes matching `predicate` into a single aggregate node.

All incoming and outgoing edges to/from the collapsed nodes are rewired
to the aggregate. Self-loops and duplicate edges are removed.

## Options

  * `:label` — display label for the aggregate node (default: `"Aggregate"`)
  * `:data` — extra node data to merge (default: `%{}`)

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_topic(:c)
    ...>   |> Choreo.MindMap.add_subtopic(:d)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:a, :c)
    ...>   |> Choreo.MindMap.branch(:b, :d)
    ...>   |> Choreo.MindMap.branch(:c, :d)
    iex> collapsed = Choreo.View.collapse(map, fn id, _data -> id in [:b, :c] end, :topics)
    iex> Enum.sort(Choreo.MindMap.nodes(collapsed))
    [:a, :d, :topics]
    iex> Yog.has_edge?(collapsed.graph, :a, :topics)
    true
    iex> Yog.has_edge?(collapsed.graph, :topics, :d)
    true

# `filter`

```elixir
@spec filter(viewable(), ({Yog.node_id(), map()} -&gt; boolean()), keyword()) ::
  viewable()
```

Returns a new diagram keeping only nodes that match `predicate`.

`predicate` receives `{node_id, node_data}` and must return a boolean.

## Options

  * `:transitive` — when `true`, adds virtual edges between remaining
    nodes that were connected through removed intermediate nodes.
    Default: `false`.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_note(:c)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    iex> filtered = Choreo.View.filter(map, fn _id, data ->
    ...>   data[:node_type] != :note
    ...> end)
    iex> Enum.sort(Choreo.MindMap.nodes(filtered))
    [:a, :b]

# `focus`

```elixir
@spec focus(viewable(), Yog.node_id(), keyword()) :: viewable()
```

Returns a new diagram containing only `node` and nodes within `radius` hops.

## Options

  * `:radius` — number of hops to include (default: `1`)
  * `:mode` — `:successors` (outgoing only), `:neighbors` (bidirectional),
    or `:predecessors` (incoming only). Default: `:neighbors`.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_subtopic(:c)
    ...>   |> Choreo.MindMap.add_subtopic(:d)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:b, :c)
    ...>   |> Choreo.MindMap.branch(:b, :d)
    iex> focused = Choreo.View.focus(map, :b, radius: 1)
    iex> Enum.sort(Choreo.MindMap.nodes(focused))
    [:a, :b, :c, :d]

# `focus_between`

```elixir
@spec focus_between(viewable(), Yog.node_id(), Yog.node_id(), keyword()) :: viewable()
```

Returns a new diagram containing only the shortest path from `from` to `to`.

Optionally includes neighbours within `radius` hops of every node on the
path.

## Options

  * `:radius` — include nodes within this many hops of each path node
    (default: `0`, meaning path nodes only)

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_subtopic(:c)
    ...>   |> Choreo.MindMap.add_topic(:d)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:b, :c)
    ...>   |> Choreo.MindMap.branch(:a, :d)
    iex> path = Choreo.View.focus_between(map, :a, :c)
    iex> Enum.sort(Choreo.MindMap.nodes(path))
    [:a, :b, :c]

# `focus_trace`

```elixir
@spec focus_trace(viewable(), Yog.node_id(), Yog.node_id(), keyword()) :: viewable()
```

Returns a new diagram containing only the trace path from `from` to `to` (using
trace edges).

Optionally includes neighbors within `radius` hops of every node on the
path in the full graph.

## Options

  * `:radius` — include nodes within this many hops of each path node
    in the full graph (default: `0`, meaning path nodes only)

# `zoom`

```elixir
@spec zoom(viewable(), keyword()) :: viewable()
```

Returns a new diagram filtered to the given zoom level.

Zoom levels are module-specific. Missing or unknown levels default to
showing everything.

## Options

  * `:level` — zoom level (default: `3`, meaning "show everything")
  * `:transitive` — when `true`, adds virtual edges between remaining
    nodes that were connected through removed intermediate nodes.
    Default: `false`.

## MindMap levels

  * `0` — root only
  * `1` — root + topics
  * `2` — root + topics + subtopics
  * `3` (or higher) — everything including notes

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_subtopic(:c)
    ...>   |> Choreo.MindMap.add_note(:d)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:b, :c)
    ...>   |> Choreo.MindMap.branch(:c, :d)
    iex> zoomed = Choreo.View.zoom(map, level: 1)
    iex> Enum.sort(Choreo.MindMap.nodes(zoomed))
    [:a, :b]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
