Choreo.View (Choreo v0.9.0)

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 containing only the trace path from from to to (using trace edges).

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]

focus_trace(diagram, from, to, opts \\ [])

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