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

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. Acyclicity, single-parent, and full reachability must be
verified post-hoc via `Analysis.validate/1`.

## 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

  * [Mind map (Wikipedia)](https://en.wikipedia.org/wiki/Mind_map)
  * [Tony Buzan — Mind Mapping](https://www.tonybuzan.com/about/mind-mapping/)

## 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

<div class="graphviz">
  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];
  }
</div>

## 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)

# `t`

```elixir
@type t() :: %Choreo.MindMap{
  edge_meta: %{optional({Yog.node_id(), Yog.node_id()}) =&gt; map()},
  graph: Yog.graph(),
  root: Yog.node_id() | nil,
  strict: boolean()
}
```

# `add_note`

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

Adds a note node (annotation or detail).

## Options

* `:label` (`t:String.t/0`)

* `:description` (`t:String.t/0`)

* `:shape` (`t:atom/0`)

* `:fillcolor` (`t:String.t/0`)

* `:fontcolor` (`t:String.t/0`)

* `:style` (`t:String.t/0`)

* `:penwidth`

* `:image` (`t:String.t/0`)

## 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

<div class="graphviz">
  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"];
  }
</div>

# `add_subtopic`

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

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

## Options

* `:label` (`t:String.t/0`)

* `:description` (`t:String.t/0`)

* `:shape` (`t:atom/0`)

* `:fillcolor` (`t:String.t/0`)

* `:fontcolor` (`t:String.t/0`)

* `:style` (`t:String.t/0`)

* `:penwidth`

* `:image` (`t:String.t/0`)

## 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

<div class="graphviz">
  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"];
  }
</div>

# `add_topic`

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

Adds a topic node (main branch).

## Options

* `:label` (`t:String.t/0`)

* `:description` (`t:String.t/0`)

* `:shape` (`t:atom/0`)

* `:fillcolor` (`t:String.t/0`)

* `:fontcolor` (`t:String.t/0`)

* `:style` (`t:String.t/0`)

* `:penwidth`

* `:image` (`t:String.t/0`)

## 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

<div class="graphviz">
  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"];
  }
</div>

# `associate`

```elixir
@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

* `:label` (`t:String.t/0`)

## 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`

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

Creates a hierarchical branch from parent to child.

## Options

* `:label` (`t:String.t/0`)

## 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`

```elixir
@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`

```elixir
@spec new(keyword()) :: t()
```

Creates a new empty mind map.

## Options

  * `:strict` — if `true`, `branch/4` and `associate/4` raise when an
    endpoint does not already exist in the map. Default `false`
    (auto-creates missing nodes as `:topic`).

## Examples

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

# `nodes`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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

* `:label` (`t:String.t/0`)

* `:description` (`t:String.t/0`)

* `:shape` (`t:atom/0`)

* `:fillcolor` (`t:String.t/0`)

* `:fontcolor` (`t:String.t/0`)

* `:style` (`t:String.t/0`)

* `:penwidth`

* `:image` (`t:String.t/0`)

## 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

<div class="graphviz">
  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"];
  }
</div>

    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 root :a

# `subtopics`

```elixir
@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`

```elixir
@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`

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

Renders the mind map to DOT format.

## Options

  * `:theme` — `:default`, `:dark`, `:minimal`, `:warm`, `:forest`, `:ocean`, or a `Choreo.Theme` struct
  * `:highlighted_nodes` — list of node IDs to highlight
  * `:highlighted_edges` — list of `{from, to}` tuples to highlight
  * Any other option accepted by `Yog.Render.DOT.to_dot/2`, such as `:rankdir`

## 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`

```elixir
@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

# `to_mermaid`

```elixir
@spec to_mermaid(t(), keyword()) :: String.t()
```

Renders the mind map to Mermaid.js flowchart, native mindmap, or native Ishikawa syntax.

Native `:mindmap` and `:ishikawa` syntax is hierarchical: associative cross-links
are omitted, labels are normalized to single-line text, and cyclic maps raise
before rendering. Use the default `:flowchart` syntax when cross-links or full
edge styling matter.

## Options

  * `:syntax` — `:flowchart` (default), `:mindmap`, or `:ishikawa`
  * `:theme` — `:default`, `:dark`, `:minimal`, `:warm`, `:forest`, `:ocean`, or a `Choreo.Theme` struct (for `:flowchart` syntax)
  * `:direction` — `:td` (default), `:lr`, `:bt`, `:rl` (for `:flowchart` syntax)
  * `:highlighted_nodes` — list of node IDs to highlight (for `:flowchart` syntax)
  * `:highlighted_edges` — list of `{from, to}` tuples to highlight (for `:flowchart` syntax)

## Examples

    iex> map = Choreo.MindMap.new() |> Choreo.MindMap.set_root(:ideas, label: "Ideas")
    iex> mermaid = Choreo.MindMap.to_mermaid(map)
    iex> String.contains?(mermaid, "graph TD")
    true

    iex> map = Choreo.MindMap.new() |> Choreo.MindMap.set_root(:ideas, label: "Ideas")
    iex> native = Choreo.MindMap.to_mermaid(map, syntax: :mindmap)
    iex> String.contains?(native, "mindmap")
    true
    iex> String.contains?(native, "Ideas")
    true

    iex> map = Choreo.MindMap.new() |> Choreo.MindMap.set_root(:latency, label: "Checkout Latency")
    iex> ishikawa = Choreo.MindMap.to_mermaid(map, syntax: :ishikawa)
    iex> String.contains?(ishikawa, "ishikawa")
    true
    iex> String.contains?(ishikawa, "Checkout Latency")
    true

# `topics`

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

---

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