Powered by AppSignal & Oban Pro

Choreo MindMap: Concept Maps, Root Cause Maps, and Graph Lenses

mind_map_walkthrough.livemd

Choreo MindMap: Concept Maps, Root Cause Maps, and Graph Lenses

Mix.install([
  # {:choreo, "~> 0.12.0"},
  {:choreo, path: Path.expand("../..", __DIR__), force: true},
  {:kino_vizjs, "~> 0.9.0"}
])

Why this Livebook exists

Choreo.MindMap models structured thinking as code. Use it for:

  • brainstorming and knowledge maps;
  • product planning and documentation outlines;
  • incident/root-cause analysis;
  • validated idea hierarchies that are diffable, testable, and renderable in multiple ways.

Mind maps are intentionally simple:

  • one root concept;
  • branch edges for hierarchy;
  • optional associate edges for cross-links;
  • analysis functions for depth, breadth, leaves, paths, orphans, cycles, and validation.

Mermaid syntax options

MindMap.to_mermaid/2 supports three views:

Syntax Best for Notes
:flowchart Full-fidelity Choreo rendering Styled nodes, cross-links, themes, highlights
:mindmap Native Mermaid hierarchy Compact hierarchy; branch edges only
:ishikawa Root-cause/cause-and-effect maps Mermaid 11.12.3+; branch edges only

Choreo.Lab.Siren and Choreo.Lab.Sketch render Mermaid strings interactively in Livebook. Kino.VizJS renders Graphviz DOT.

alias Choreo.MindMap
alias Choreo.MindMap.Analysis
alias Choreo.View
import Choreo.Lab.DSL.MindMap
mermaid_source = fn source ->
  fence = String.duplicate("`", 3)
  Kino.Markdown.new(fence <> "\n" <> source <> "\n" <> fence)
end

render_mind_map = fn map, opts ->
  syntax = Keyword.get(opts, :syntax, :flowchart)
  mermaid = MindMap.to_mermaid(map, opts)

  tabs = [
    Siren: Choreo.Lab.Siren.new(mermaid),
    Graphviz: Kino.VizJS.render(MindMap.to_dot(map), height: Keyword.get(opts, :height, "800px")),
    Sketch: Choreo.Lab.Sketch.new(mermaid),
    Source: mermaid_source.(mermaid)
  ]

  title = "#{syntax}"
  Kino.Layout.tabs([{title, Kino.Layout.tabs(tabs)}])
end

1. Legend: Node Types and Edges

A mind map has four node types:

Type Meaning
:root central idea or problem
:topic major branch
:subtopic nested idea
:note annotation/detail
legend =
  mind_map do
    root = root("Root")
    topic = topic("Topic")
    subtopic = subtopic("Subtopic")
    note = note("Note")

    root ~> topic
    topic ~> subtopic
    subtopic ~> note
  end

Kino.Layout.tabs(
  Flowchart: Choreo.Lab.Siren.new(MindMap.to_mermaid(legend, syntax: :flowchart, direction: :lr)),
  Mindmap: Choreo.Lab.Siren.new(MindMap.to_mermaid(legend, syntax: :mindmap)),
  Ishikawa: Choreo.Lab.Siren.new(MindMap.to_mermaid(legend, syntax: :ishikawa)),
  Graphviz: Kino.VizJS.render(
    MindMap.to_dot(legend, theme: Choreo.MindMap.theme(:default, graph_rankdir: :lr))
  )
)

2. Brainstorming: Talk Outline

This map is best viewed as a native Mermaid mindmap: it is a clean hierarchy and the central goal is idea organization.

talk =
  mind_map do
    talk = root("Building\nResilient\nElixir\nSystems")
    supervision = topic("Supervision\nTrees")
    patterns = topic("Fault-Tolerance\nPatterns")
    observability = topic("Observability")
    one_for_one = subtopic("One-for-One")
    one_for_all = subtopic("One-for-All")
    circuit_breaker = subtopic("Circuit Breaker")
    bulkhead = subtopic("Bulkhead")
    telemetry = subtopic("Telemetry")
    tracing = subtopic("OpenTelemetry")
    demo = note("Live demo at 15 min")
    story = note("Tell outage story")

    talk ~> supervision
    talk ~> patterns
    talk ~> observability
    supervision ~> one_for_one
    supervision ~> one_for_all
    patterns ~> circuit_breaker
    patterns ~> bulkhead
    observability ~> telemetry
    observability ~> tracing
    circuit_breaker ~> demo
    bulkhead ~> story
    circuit_breaker ~> telemetry |> associate("improves")
  end

Kino.Layout.tabs(
  Mindmap: Choreo.Lab.Siren.new(MindMap.to_mermaid(talk, syntax: :mindmap)),
  Flowchart: Choreo.Lab.Siren.new(MindMap.to_mermaid(talk, syntax: :flowchart)),
  Graphviz: Kino.VizJS.render(MindMap.to_dot(talk), height: "850px")
)

The native :mindmap view omits the associate/4 cross-link. Use :flowchart when cross-links matter.

%{
  depth: Analysis.depth(talk),
  leaves: Analysis.breadth(talk),
  max_width: Analysis.max_width(talk),
  composition: Analysis.type_frequencies(talk),
  validation: Analysis.validate(talk)
}

Root-to-leaf paths become complete narrative threads:

Analysis.paths(talk)
|> Enum.map(fn path -> Enum.join(path, " → ") end)

3. Knowledge Map: Flowchart vs Native Mindmap

A knowledge map often needs both hierarchy and cross-links. Here, CAP theorem, replication, and sharding are related beyond the tree.

domain =
  mind_map do
    dist_sys = root("Distributed\nSystems")
    consistency = topic("Consistency")
    availability = topic("Availability")
    partitioning = topic("Partitioning")
    cap = subtopic("CAP Theorem")
    paxos = subtopic("Paxos")
    raft = subtopic("Raft")
    sharding = subtopic("Sharding")
    replication = subtopic("Replication")
    brewer = note("Eric Brewer, 2000")

    dist_sys ~> consistency
    dist_sys ~> availability
    dist_sys ~> partitioning
    consistency ~> cap
    consistency ~> paxos
    consistency ~> raft
    partitioning ~> sharding
    partitioning ~> replication
    cap ~> brewer
    cap ~> sharding |> associate("influences")
    raft ~> replication |> associate("uses")
  end

Kino.Layout.tabs(
  "Flowchart — full fidelity": Choreo.Lab.Siren.new(MindMap.to_mermaid(domain, syntax: :flowchart)),
  "Mindmap — hierarchy only": Choreo.Lab.Siren.new(MindMap.to_mermaid(domain, syntax: :mindmap)),
  Graphviz: Kino.VizJS.render(MindMap.to_dot(domain), height: "750px", width: "100%")
)
Analysis.validate(domain)
|> case do
  [] -> Kino.Markdown.new("✅ **Knowledge map is structurally sound.**")
  issues -> issues |> inspect(pretty: true) |> Kino.Text.new()
end

4. Root Cause Analysis with Mermaid Ishikawa

Ishikawa diagrams are cause-and-effect diagrams. In Choreo.MindMap, the root is the effect/problem and branch children are causes.

This makes MindMap useful for incident reviews:

incident =
  mind_map do
    checkout_latency = root("Checkout Latency")
    people = topic("People")
    process = topic("Process")
    platform = topic("Platform")
    data = topic("Data")
    environment = topic("Environment")
    missing_runbook = subtopic("Missing Runbook")
    unclear_ownership = subtopic("Unclear Ownership")
    manual_release = subtopic("Manual Release")
    no_canary = subtopic("No Canary")
    slow_queries = subtopic("Slow Queries")
    missing_index = subtopic("Missing Index")
    cold_cache = subtopic("Cold Cache")
    regional_spike = subtopic("Regional Traffic Spike")
    action_runbook = note("Action: write checkout runbook")
    action_index = note("Action: add order_items index")

    checkout_latency ~> people
    checkout_latency ~> process
    checkout_latency ~> platform
    checkout_latency ~> data
    checkout_latency ~> environment
    people ~> unclear_ownership
    people ~> missing_runbook
    process ~> manual_release
    process ~> no_canary
    platform ~> cold_cache
    data ~> slow_queries
    slow_queries ~> missing_index
    environment ~> regional_spike
    missing_runbook ~> action_runbook
    missing_index ~> action_index
    manual_release ~> cold_cache |> associate("amplified")
  end

ishikawa = MindMap.to_mermaid(incident, syntax: :ishikawa)

Kino.Layout.tabs(
  Ishikawa: Choreo.Lab.Siren.new(ishikawa),
  Flowchart: Choreo.Lab.Siren.new(MindMap.to_mermaid(incident, syntax: :flowchart)),
  Graphviz: Kino.VizJS.render(MindMap.to_dot(incident, theme: :warm), height: "950px"),
  Source: mermaid_source.(ishikawa)
)

Use :ishikawa when the question is “why did this happen?” Use :flowchart when cross-links and visual styling matter.

%{
  validation: Analysis.validate(incident),
  root_to_leaf_causes: Analysis.paths(incident),
  leaves: Analysis.leaves(incident)
}

5. Product Planning Map

Product planning benefits from the full-fidelity flowchart renderer because cross-links often matter.

roadmap =
  mind_map do
    product = root("SaaS Platform v2")
    auth = topic("Auth & Security")
    billing = topic("Billing")
    performance = topic("Performance")
    integrations = topic("Integrations")
    sso = subtopic("SSO (SAML)")
    mfa = subtopic("MFA")
    stripe = subtopic("Stripe Checkout")
    usage = subtopic("Usage-Based Pricing")
    caching = subtopic("Redis Caching")
    cdn = subtopic("CDN")
    webhooks = subtopic("Webhooks")
    api = subtopic("REST API v2")
    gdpr = note("GDPR review required")
    load_test = note("Load test before release")

    product ~> auth
    product ~> billing
    product ~> performance
    product ~> integrations
    auth ~> sso
    auth ~> mfa
    auth ~> gdpr
    billing ~> stripe
    billing ~> usage
    performance ~> caching
    performance ~> cdn
    performance ~> load_test
    integrations ~> webhooks
    integrations ~> api
    webhooks ~> caching |> associate("affects")
    stripe ~> gdpr |> associate("data review")
  end

Kino.Layout.tabs(
  Flowchart: Choreo.Lab.Siren.new(MindMap.to_mermaid(roadmap, syntax: :flowchart, theme: :ocean)),
  Mindmap: Choreo.Lab.Siren.new(MindMap.to_mermaid(roadmap, syntax: :mindmap)),
  Graphviz: Kino.VizJS.render(MindMap.to_dot(roadmap, theme: :ocean), height: "550px")
)
Analysis.orphan_nodes(roadmap)
|> case do
  [] -> Kino.Markdown.new("✅ **All roadmap items are reachable from the product root.**")
  orphans -> Kino.Markdown.new("⚠️ Orphans: `#{inspect(orphans)}`")
end

6. Fixing Broken Maps

The builder lets you iterate quickly, then Analysis.validate/1 catches structural issues.

broken =
  mind_map do
    a = root("A")
    b = topic("B")
    c = topic("Orphan C")
    d = topic("D")

    a ~> b
    b ~> d
    a ~> d
  end

Analysis.validate(broken)
|> Enum.map(fn {severity, message} -> "* `#{severity}` — #{message}" end)
|> Enum.join("\n")
|> Kino.Markdown.new()

The validator catches:

  • missing root;
  • cycles;
  • orphan nodes;
  • nodes with multiple branch parents.

Native :mindmap and :ishikawa renderers reject cycles because Mermaid hierarchy syntaxes cannot represent them safely.

cyclic =
  mind_map do
    a = root(:a)
    b = topic(:b)

    a ~> b
    b ~> a
  end

try do
  MindMap.to_mermaid(cyclic, syntax: :ishikawa)
rescue
  error in ArgumentError -> error.message
end

7. Graph Lenses with Choreo.View

Choreo.View lets you derive smaller maps without mutating the original.

zoom_by = fn level -> View.zoom(talk, level: level) end

Kino.Layout.tabs(
  "Level 1": Choreo.Lab.Siren.new(MindMap.to_mermaid(zoom_by.(1), syntax: :mindmap)),
  "Level 2": Choreo.Lab.Siren.new(MindMap.to_mermaid(zoom_by.(2), syntax: :mindmap)),
  "Level 3": Choreo.Lab.Siren.new(MindMap.to_mermaid(zoom_by.(3), syntax: :mindmap))
)

Focus on a branch and its immediate neighborhood:

focused = View.focus(talk, :patterns, radius: 1)

Kino.Layout.tabs(
  Flowchart: Choreo.Lab.Siren.new(MindMap.to_mermaid(focused, syntax: :flowchart)),
  Graphviz: Kino.VizJS.render(MindMap.to_dot(focused), height: "650px")
)

Trace a narrative path between two ideas:

path_view = View.focus_between(talk, :talk, :demo, radius: 1)

Kino.Layout.tabs(
  Flowchart: Choreo.Lab.Siren.new(MindMap.to_mermaid(path_view, syntax: :flowchart)),
  Graphviz: Kino.VizJS.render(MindMap.to_dot(path_view), height: "650px")
)

Remove notes for a clean stakeholder deck:

clean = View.filter(talk, fn _id, data -> data[:node_type] != :note end)

Choreo.Lab.Siren.new(MindMap.to_mermaid(clean, syntax: :mindmap))

8. Custom Theming

Themes apply to the :flowchart and DOT renderers. Native Mermaid :mindmap and :ishikawa use Mermaid's own renderer styles.

brand_theme =
  Choreo.Theme.custom(
    colors: %{
      root: "#8b5cf6",
      topic: "#3b82f6",
      subtopic: "#06b6d4",
      note: "#f59e0b"
    },
    node_fontcolor: "white",
    edge_color: "#94a3b8",
    graph_bgcolor: "#0f172a"
  )

Kino.Layout.tabs(
  Flowchart: Choreo.Lab.Siren.new(MindMap.to_mermaid(talk, theme: brand_theme, syntax: :flowchart)),
  Graphviz: Kino.VizJS.render(MindMap.to_dot(talk, theme: brand_theme), height: "750px")
)

Summary

Question Function / Syntax
Full-fidelity styled rendering? MindMap.to_mermaid(map, syntax: :flowchart)
Compact native hierarchy? MindMap.to_mermaid(map, syntax: :mindmap)
Cause-and-effect/root-cause view? MindMap.to_mermaid(map, syntax: :ishikawa)
DOT / Graphviz rendering? MindMap.to_dot/2
How deep is the map? Analysis.depth/1
How many leaf ideas? Analysis.breadth/1
What is the widest level? Analysis.max_width/1
Which ideas are terminal? Analysis.leaves/1
What are all narrative/root-cause paths? Analysis.paths/1
Which ideas are disconnected? Analysis.orphan_nodes/1
Is the map structurally sound? Analysis.validate/1
Zoom/focus/filter/collapse? Choreo.View

Mind maps as code turn brainstorming, planning, and root-cause analysis into version-controlled artifacts that can be rendered in multiple visual forms and checked before they go stale.