Choreo.View (Choreo v0.7.1)

Copy Markdown View Source

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)

Summary

Functions

Collapses all nodes matching predicate into a single aggregate node.

Returns a new diagram keeping only nodes that match predicate.

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

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

Returns a new diagram filtered to the given zoom level.

Types

viewable()

@type viewable() :: struct()

Functions

collapse(diagram, predicate, new_id, opts \\ [])

@spec collapse(
  viewable(),
  ({Yog.node_id(), map()} -> 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(diagram, predicate, opts \\ [])

@spec filter(viewable(), ({Yog.node_id(), map()} -> 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(diagram, node, opts \\ [])

@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(diagram, from, to, opts \\ [])

@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]

zoom(diagram, opts \\ [])

@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]