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 neighbourhoodfocus_between/3— keep only the shortest path between two nodeszoom/2— filter by module-defined zoom levelfilter/2— keep only nodes matching a predicatecollapse/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
@type viewable() :: struct()
Functions
@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
Returns a new diagram keeping only nodes that match predicate.
predicate receives {node_id, node_data} and must return a boolean.
Options
:transitive— whentrue, 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]
@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]
@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]
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— whentrue, adds virtual edges between remaining nodes that were connected through removed intermediate nodes. Default:false.
MindMap levels
0— root only1— root + topics2— root + topics + subtopics3(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]