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.