Mind map
Spatial topic hierarchy with connections, custom renderers, and branch-from-selection.
What happens
The root topic sits at the center with branches radiating outward. Each topic is a custom renderer that styles itself based on depth (root, branch, leaf). Connections use bezier routing to draw the edges between parent and child topics.
Select any topic and click "Add branch" to grow the map from that point. The new node auto-connects to its parent using the connections plugin. History tracks every change so you can undo freely.
Topic text stays as topic text. The renderer gets depth from the connection
graph, so branch styling is derived from the map structure instead of hidden in
the first line of node.text.
The code
app.vue
<script setup lang="ts">
import { computed } from 'vue'
import { asNodeId, createBoardEngine } from '@lupinum/board-core'
import { connectionsPlugin } from '@lupinum/board-connections'
import { BoardConnectionLayer } from '@lupinum/board-connections/vue'
import { historyPlugin } from '@lupinum/board-history'
import TopicNode from './TopicNode.vue'
const ROOT_ID = asNodeId('root')
const engine = createBoardEngine({
grid: { size: 20, snap: true, pattern: 'dot' },
plugins: [historyPlugin(), connectionsPlugin({ routing: 'bezier' })],
})
const topicDepthsById = computed(() => {
const depths = new Map([[ROOT_ID, 0]])
for (const edge of engine.plugins.connections.getEdges()) {
const fromDepth = depths.get(edge.from)
if (fromDepth !== undefined) {
depths.set(edge.to, fromDepth + 1)
}
}
return depths
})
</script>
<template>
<BoardRoot :engine="engine" style="height: 100vh">
<template #node:text="{ node, selected }">
<TopicNode
:node="node"
:selected="selected"
:depth="topicDepthsById.get(node.id) ?? 2"
/>
</template>
<BoardConnectionLayer routing="bezier" />
</BoardRoot>
</template><script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
node: { text?: string }
depth: number
}>()
const topic = computed(() => {
const [title = 'Topic', detail = ''] = props.node.text?.split('\n') ?? []
return { title, detail }
})
</script>
<template>
<div class="topic-card" :class="depthClass(depth)">
<strong>{{ topic.title }}</strong>
<p>
{{ topic.detail }}
</p>
</div>
</template>Try these things
- Select the root topic, then add a branch. The new node connects automatically.
- Drag a branch away from the center. The bezier edge stretches to follow.
- Undo a few times to watch the map shrink back.
- Zoom out to see the full map structure.