Choreo.MindMap (Choreo v0.7.1)

Copy Markdown View Source

Mind-map builder on top of Yog.

Choreo.MindMap models hierarchical concept maps where a central idea radiates into topics, sub-topics, and notes. Cross-links (associations) between branches are supported for non-hierarchical relationships.

When to use

Use Choreo.MindMap when brainstorming, documenting knowledge domains, planning talks, or structuring documentation. It enforces a single-root invariant and detects orphaned concepts automatically.

Node types

  • :root — the central concept (exactly one per map)
  • :topic — main branch radiating from the root
  • :subtopic — nested idea under a topic
  • :note — annotation or detail node

Edge types

  • :branch — hierarchical parent→child relationship
  • :associates — cross-link between any two nodes

Further reading

Quick Start

map =
  Choreo.MindMap.new()
  |> Choreo.MindMap.set_root(:elixir, label: "Elixir")
  |> Choreo.MindMap.add_topic(:concurrency, label: "Concurrency")
  |> Choreo.MindMap.add_topic(:ecosystem, label: "Ecosystem")
  |> Choreo.MindMap.add_subtopic(:processes, label: "Processes")
  |> Choreo.MindMap.add_note(:beam, label: "BEAM VM")
  |> Choreo.MindMap.branch(:elixir, :concurrency)
  |> Choreo.MindMap.branch(:elixir, :ecosystem)
  |> Choreo.MindMap.branch(:concurrency, :processes)
  |> Choreo.MindMap.branch(:ecosystem, :beam)
  |> Choreo.MindMap.associate(:processes, :beam, label: "runs on")

dot = Choreo.MindMap.to_dot(map)

Diagram

digraph G { graph [rankdir=TB, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=ellipse, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; elixir [label="Elixir", fillcolor="#8b5cf6", shape="doublecircle", penwidth="2.0"]; concurrency [label="Concurrency", fillcolor="#3b82f6", shape="ellipse"]; ecosystem [label="Ecosystem", fillcolor="#10b981", shape="ellipse"]; processes [label="Processes", fillcolor="#06b6d4", shape="box"]; beam [label="BEAM VM", fillcolor="#f59e0b", shape="note"]; elixir -> concurrency [style="solid", color="#64748b"]; elixir -> ecosystem [style="solid", color="#64748b"]; concurrency -> processes [style="solid", color="#64748b"]; ecosystem -> beam [style="solid", color="#64748b"]; processes -> beam [style="dashed", color="#94a3b8", label="runs on", dir=none]; }

Analysis

# Detect orphaned ideas not reachable from the root
Choreo.MindMap.Analysis.orphan_nodes(map)

# Measure map complexity
Choreo.MindMap.Analysis.depth(map)
Choreo.MindMap.Analysis.breadth(map)

# Validate structural soundness
Choreo.MindMap.Analysis.validate(map)

Summary

Functions

Adds a note node (annotation or detail).

Adds a subtopic node (nested idea under a topic).

Adds a topic node (main branch).

Creates an associative (cross-link) edge between two nodes.

Creates a hierarchical branch from parent to child.

Returns all edges as {from, to, weight} tuples.

Creates a new empty mind map.

Returns all node IDs in the mind map.

Returns all note node IDs.

Returns the root node id, or nil if not set.

Sets the root concept node of the mind map.

Returns all subtopic node IDs.

Renders the mind map to DOT format.

Returns the raw Yog.Graph struct underpinning the mind map.

Returns all topic node IDs.

Types

t()

@type t() :: %Choreo.MindMap{
  edge_meta: %{optional({Yog.node_id(), Yog.node_id()}) => map()},
  graph: Yog.graph(),
  root: Yog.node_id() | nil
}

Functions

add_note(map, id, opts \\ [])

@spec add_note(t(), Yog.node_id(), keyword()) :: t()

Adds a note node (annotation or detail).

Options

Examples

iex> map = Choreo.MindMap.new()
iex> map = Choreo.MindMap.add_note(map, :ref, label: "See RFC 7231")
iex> :ref in Choreo.MindMap.nodes(map)
true
iex> Yog.node(map.graph, :ref).node_type
:note

Diagram

digraph G { graph [rankdir=TB, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=ellipse, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; ref [label="See RFC 7231", fillcolor="#f59e0b", shape="note"]; }

add_subtopic(map, id, opts \\ [])

@spec add_subtopic(t(), Yog.node_id(), keyword()) :: t()

Adds a subtopic node (nested idea under a topic).

Options

Examples

iex> map = Choreo.MindMap.new()
iex> map = Choreo.MindMap.add_subtopic(map, :patterns, label: "Patterns")
iex> :patterns in Choreo.MindMap.nodes(map)
true
iex> Yog.node(map.graph, :patterns).node_type
:subtopic

Diagram

digraph G { graph [rankdir=TB, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=ellipse, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; patterns [label="Patterns", fillcolor="#06b6d4", shape="box"]; }

add_topic(map, id, opts \\ [])

@spec add_topic(t(), Yog.node_id(), keyword()) :: t()

Adds a topic node (main branch).

Options

Examples

iex> map = Choreo.MindMap.new()
iex> map = Choreo.MindMap.add_topic(map, :design, label: "Design")
iex> :design in Choreo.MindMap.nodes(map)
true
iex> Yog.node(map.graph, :design).node_type
:topic

Diagram

digraph G { graph [rankdir=TB, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=ellipse, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; design [label="Design", fillcolor="#3b82f6", shape="ellipse"]; }

associate(map, from, to, opts \\ [])

@spec associate(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()

Creates an associative (cross-link) edge between two nodes.

Associative edges are rendered as dashed lines without arrowheads, representing non-hierarchical relationships.

Options

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a, label: "A")
...>   |> Choreo.MindMap.add_topic(:b, label: "B")
...>   |> Choreo.MindMap.add_topic(:c, label: "C")
...>   |> Choreo.MindMap.branch(:a, :b)
...>   |> Choreo.MindMap.branch(:a, :c)
...>   |> Choreo.MindMap.associate(:b, :c, label: "related")
iex> map.edge_meta[{:b, :c}].edge_type
:associates
iex> map.edge_meta[{:b, :c}].label
"related"

branch(map, parent, child, opts \\ [])

@spec branch(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()

Creates a hierarchical branch from parent to child.

Options

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a, label: "A")
...>   |> Choreo.MindMap.add_topic(:b, label: "B")
...>   |> Choreo.MindMap.branch(:a, :b)
iex> Yog.has_edge?(map.graph, :a, :b)
true
iex> map.edge_meta[{:a, :b}].edge_type
:branch

edges(mind_map)

@spec edges(t()) :: [{Yog.node_id(), Yog.node_id(), number()}]

Returns all edges as {from, to, weight} tuples.

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a, label: "A")
...>   |> Choreo.MindMap.add_topic(:b, label: "B")
...>   |> Choreo.MindMap.branch(:a, :b)
iex> Choreo.MindMap.edges(map)
[{:a, :b, 1}]

new()

@spec new() :: t()

Creates a new empty mind map.

Examples

iex> map = Choreo.MindMap.new()
iex> Choreo.MindMap.nodes(map)
[]
iex> Choreo.MindMap.root(map)
nil

nodes(mind_map)

@spec nodes(t()) :: [Yog.node_id()]

Returns all node IDs in the mind map.

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a, label: "A")
...>   |> Choreo.MindMap.add_topic(:b, label: "B")
iex> Enum.sort(Choreo.MindMap.nodes(map))
[:a, :b]

notes(mind_map)

@spec notes(t()) :: [Yog.node_id()]

Returns all note node IDs.

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a)
...>   |> Choreo.MindMap.add_note(:n, label: "Note")
iex> Choreo.MindMap.notes(map)
[:n]

root(mind_map)

@spec root(t()) :: Yog.node_id() | nil

Returns the root node id, or nil if not set.

Examples

iex> map = Choreo.MindMap.new()
iex> Choreo.MindMap.root(map)
nil
iex> map = Choreo.MindMap.set_root(map, :idea, label: "Idea")
iex> Choreo.MindMap.root(map)
:idea

set_root(map, id, opts \\ [])

@spec set_root(t(), Yog.node_id(), keyword()) :: t()

Sets the root concept node of the mind map.

Can only be called once. Raises ArgumentError if the map already has a root.

Options

Examples

iex> map = Choreo.MindMap.new()
iex> map = Choreo.MindMap.set_root(map, :idea, label: "Big Idea")
iex> Choreo.MindMap.root(map)
:idea
iex> Yog.node(map.graph, :idea).node_type
:root
iex> Yog.node(map.graph, :idea).label
"Big Idea"

Diagram

digraph G { graph [rankdir=TB, splines=spline, nodesep=0.6, ranksep=1.2]; node [shape=ellipse, style=filled, fillcolor="white", fontname="Helvetica", fontsize=12, fontcolor="white"]; edge [arrowhead=normal, color="#64748b", style=solid, fontname="Helvetica", fontsize=10, penwidth=1.0]; idea [label="Big Idea", fillcolor="#8b5cf6", shape="doublecircle", penwidth="2.0"]; }
iex> map = Choreo.MindMap.new() |> Choreo.MindMap.set_root(:a, label: "A")
iex> Choreo.MindMap.set_root(map, :b, label: "B")
** (ArgumentError) Mind map already has a root

subtopics(mind_map)

@spec subtopics(t()) :: [Yog.node_id()]

Returns all subtopic node IDs.

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a)
...>   |> Choreo.MindMap.add_topic(:b)
...>   |> Choreo.MindMap.add_subtopic(:c)
iex> Choreo.MindMap.subtopics(map)
[:c]

theme(name \\ :default, overrides \\ [])

@spec theme(
  atom(),
  keyword()
) :: Choreo.Theme.t()

Returns a theme for Choreo.MindMap.

Examples

iex> theme = Choreo.MindMap.theme(:default, graph_rankdir: :lr)
iex> theme.graph_rankdir
:lr

to_dot(map, opts \\ [])

@spec to_dot(
  t(),
  keyword()
) :: String.t()

Renders the mind map to DOT format.

Options

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a, label: "A")
...>   |> Choreo.MindMap.add_topic(:b, label: "B")
...>   |> Choreo.MindMap.branch(:a, :b)
iex> dot = Choreo.MindMap.to_dot(map)
iex> String.contains?(dot, "digraph")
true
iex> String.contains?(dot, "A")
true
iex> String.contains?(dot, "B")
true

to_graph(mind_map)

@spec to_graph(t()) :: Yog.graph()

Returns the raw Yog.Graph struct underpinning the mind map.

Examples

iex> map = Choreo.MindMap.new()
iex> graph = Choreo.MindMap.to_graph(map)
iex> graph.kind
:directed

topics(mind_map)

@spec topics(t()) :: [Yog.node_id()]

Returns all topic node IDs.

Examples

iex> map = Choreo.MindMap.new()
iex> map = map
...>   |> Choreo.MindMap.set_root(:a)
...>   |> Choreo.MindMap.add_topic(:b)
...>   |> Choreo.MindMap.add_subtopic(:c)
iex> Choreo.MindMap.topics(map)
[:b]