# Mindmap NextGen
Mindmap NextGen is an Obsidian plugin that transforms Markdown notes into interactive mindmaps using the Markmap visualization library. It enables users to view their hierarchical note structures as dynamic, zoomable mindmaps with support for wikilinks, embeds, LaTeX, code highlighting, and checkboxes. The plugin integrates seamlessly into Obsidian's workspace, providing both dedicated tab views and inline code block rendering.
The plugin offers flexible configuration through a three-tier settings system (global, file-level, and code-block-level), multiple coloring strategies for visual organization, and advanced features including screenshot export, customizable styling, and reactive rendering. Built on a functional reactive programming architecture using Callbag streams, it provides smooth integration with Obsidian's API while maintaining high performance and extensibility.
## APIs and Key Functions
### Plugin Entry Point
Initialize the plugin and lazy-load main functionality.
```typescript
import { Plugin } from 'obsidian'
export const __entry = {} as { plugin: Plugin }
export default class extends Plugin {
onload() {
__entry.plugin = this
import('./main')
}
}
```
### Global Settings Configuration
Define and manage plugin-wide settings with automatic persistence.
```typescript
import { globalSettings, defaultSettings, settingsLoaded } from 'src/settings/filesystem'
// Wait for settings to load
await settingsLoaded
// Access current settings
console.log(globalSettings.coloring) // 'depth' | 'branch' | 'single'
console.log(globalSettings.initialExpandLevel) // -1 for fully expanded
// Modify settings (automatically saves)
globalSettings.depth1Color = '#ff0000'
globalSettings.animationDuration = 1000
globalSettings.spacingHorizontal = 100
// Default settings structure
const defaults = {
splitDirection: 'horizontal',
nodeMinHeight: 16,
lineHeight: '1em',
spacingVertical: 5,
spacingHorizontal: 80,
paddingX: 8,
initialExpandLevel: -1,
colorFreezeLevel: 0,
animationDuration: 500,
maxWidth: 0,
highlight: true,
coloring: 'depth',
depth1Color: '#cb4b16',
depth2Color: '#6c71c4',
depth3Color: '#859900',
defaultColor: '#b58900',
depth1Thickness: '3',
depth2Thickness: '1.5',
depth3Thickness: '1',
defaultThickness: '1',
screenshotBgColor: '#002b36',
screenshotBgStyle: 'color',
screenshotTextColor: '#fdf6e3',
screenshotTextColorEnabled: false,
titleAsRootNode: true
}
```
### File-Level Settings (Frontmatter)
Override global settings on a per-file basis using YAML frontmatter.
```markdown
---
markmap:
coloring: branch
depth1Color: "#ff0000"
initialExpandLevel: 2
spacingHorizontal: 120
animationDuration: 1000
titleAsRootNode: false
highlight: true
screenshotBgColor: "#ffffff"
---
# My Mindmap Root
## First Branch
### Sub-item 1
### Sub-item 2
## Second Branch
### Another item
```
### Code Block Rendering
Render mindmaps inline within markdown documents with optional per-block settings.
````markdown
```markmap
# Project Planning
## Phase 1: Research
- Market analysis
- Competitor study
- User interviews
## Phase 2: Development
- Frontend implementation
- Backend API
- Database design
## Phase 3: Testing
- Unit tests
- Integration tests
- User acceptance
---
markmap:
height: 300
coloring: depth
depth1Color: "#e74c3c"
depth2Color: "#3498db"
initialExpandLevel: 2
```
````
### Transform Markdown to Mindmap
Convert markdown text into a Markmap node tree with internal link parsing.
```typescript
import { transformMarkdown } from 'src/rendering/renderer-common'
const markdown = `
# Root Node
## Child 1
- Item A with [[internal-link]]
- Item B
## Child 2
- [[another-link|Display Text]]
`
const rootNode = transformMarkdown(markdown)
// Returns INode tree structure with parsed wikilinks
// Links converted to: Display Text
```
### Create and Configure Markmap Instance
Initialize a Markmap visualization with customizable options.
```typescript
import { createMarkmap, getOptions } from 'src/rendering/renderer-common'
const container = document.querySelector('.my-mindmap-container')
// Create without toolbar
const { svg, markmap } = createMarkmap({
parent: container,
toolbar: false
})
// Create with toolbar
const { svg, markmap, toolbar } = createMarkmap({
parent: container,
toolbar: true
})
// Configure and render
const settings = {
coloring: 'depth',
depth1Color: '#e74c3c',
depth2Color: '#3498db',
depth3Color: '#2ecc71',
defaultColor: '#95a5a6',
initialExpandLevel: 2,
animationDuration: 500,
spacingHorizontal: 80,
spacingVertical: 5
}
const options = getOptions(settings)
const rootNode = transformMarkdown(markdown)
await markmap.setData(rootNode, options)
markmap.fit() // Fit to viewport
```
### Code Block Handler
Track and manage all mindmap code blocks in documents.
```typescript
import { codeBlockCreated, getCodeBlocksByPath } from 'src/new/codeBlockHandler'
import Callbag from 'src/utilities/callbag'
// Subscribe to new code blocks
Callbag.subscribe(codeBlockCreated, (codeBlock) => {
console.log('New code block:', codeBlock.file.path)
console.log('Markdown content:', codeBlock.markdown)
console.log('Container element:', codeBlock.containerEl)
})
// Get all code blocks in a specific file
const filePath = 'notes/project-plan.md'
const codeBlocks = getCodeBlocksByPath(filePath)
codeBlocks.forEach(block => {
const sectionInfo = block.getSectionInfo()
console.log(`Block at lines ${sectionInfo.lineStart}-${sectionInfo.lineEnd}`)
})
```
### Settings Merging Strategy
Implement three-tier settings inheritance: Global → File → Code Block.
```typescript
import { splitMarkdown } from 'src/rendering/renderer-common'
import { globalSettings } from 'src/settings/filesystem'
// Get file-level settings from frontmatter
const fileContent = editor.getValue()
const { settings: fileSettings } = splitMarkdown('file', fileContent)
// Get code block settings
const codeBlockMarkdown = `
# Mindmap
---
markmap:
height: 250
coloring: branch
`
const { settings: codeBlockSettings, body } = splitMarkdown('codeBlock', codeBlockMarkdown)
// Merge settings (later overrides earlier)
const mergedSettings = {
...globalSettings,
...fileSettings,
...codeBlockSettings
}
console.log(mergedSettings.coloring) // 'branch' (from code block)
console.log(mergedSettings.depth1Color) // from file or global
console.log(mergedSettings.height) // 250 (code block only)
```
### Internal Link Parsing
Convert Obsidian wikilinks to clickable HTML anchors within mindmaps.
```typescript
import { parseInternalLinks } from 'src/internal-links/parse-internal-links'
const node = {
content: 'Check [[documentation]] and [[guides|User Guides]]',
children: [
{ content: 'Sub-item with [[another-link]]', children: [] }
]
}
parseInternalLinks(node)
// node.content becomes:
// 'Check documentation and User Guides'
// Recursively processes all children
```
### Custom Embed Support
Embed markdown files within mindmap nodes using custom syntax.
```typescript
// In markdown:
// ![[embedded-file.md]]
import { embedPlugin } from 'src/embeds/embeds'
// Plugin automatically registers markdown-it rule
// Converts: ![[file.md]] →
// Custom element resolves file path and renders content
// Example usage in mindmap markdown:
const markdown = `
# Project Structure
## Documentation
![[README.md]]
## Guides
![[user-guide.md]]
![[developer-guide.md#installation]]
`
```
### Mindmap View Management
Create and manage dedicated mindmap tab views.
```typescript
import MindmapView from 'src/views/view'
import { leafManager } from 'src/views/leaf-manager'
import { getActiveFile } from 'src/views/get-active-file'
// Create unpinned view (follows active file)
leafManager.new('unpinned')
// Create pinned view for specific file
const file = app.vault.getAbstractFileByPath('notes/project.md')
if (file instanceof TFile) {
leafManager.new(file)
}
// Access view instance
const instances = MindmapView.instances
instances.forEach(view => {
console.log('Display text:', view.getDisplayText())
view.render(file) // Render specific file
view.renderer.collapseAll() // Collapse all nodes
view.renderer.takeScreenshot() // Export as PNG
})
// Reveal existing view
leafManager.reveal('unpinned')
// Close view
leafManager.close('unpinned')
```
### Reactive Event Streams
Subscribe to workspace events using Callbag functional reactive programming.
```typescript
import Callbag from 'src/utilities/callbag'
import {
fileOpen,
fileChanged,
layoutChange,
isDarkMode,
commandOpenUnpinned
} from 'src/core/events'
// File opened
Callbag.subscribe(fileOpen, (file) => {
console.log('Opened file:', file?.path)
})
// File content changed (debounced 300ms)
Callbag.subscribe(fileChanged, ({ editor, info }) => {
console.log('Editor changed:', info.file.path)
const content = editor.getValue()
})
// Workspace layout changed
Callbag.subscribe(layoutChange, () => {
console.log('Layout updated')
})
// Dark mode toggled
Callbag.subscribe(isDarkMode, (isDark) => {
console.log('Dark mode:', isDark)
document.body.classList[isDark ? 'add' : 'remove']('markmap-dark')
})
// Command executed
Callbag.subscribe(commandOpenUnpinned, () => {
console.log('Open unpinned mindmap command triggered')
})
```
### Custom Callbag Operators
Create reactive streams from Obsidian events with type-safe operators.
```typescript
import { fromObsidianEvent, fromCommand } from 'src/utilities/callbag'
// Create stream from Obsidian event
const fileDeleted$ = fromObsidianEvent(app.vault, 'delete')
.unary() // Take only first argument
const fileCreated$ = fromObsidianEvent(app.vault, 'create')
.object('file') // Map to { file: TAbstractFile }
const metadataChange$ = fromObsidianEvent(app.metadataCache, 'changed')
.object('file', 'data', 'cache')
// Maps to { file: TFile, data: string, cache: CachedMetadata }
// Create command that emits when executed
const myCommand$ = fromCommand(
'mindmapnextgen:custom-command',
'Custom Command Name'
)
Callbag.subscribe(myCommand$, () => {
console.log('Command executed')
})
```
### Screenshot Export
Export mindmap as PNG with customizable colors.
```typescript
import { takeScreenshot } from 'src/rendering/screenshot'
import type { Markmap } from 'markmap-view'
const markmap: Markmap // Existing markmap instance
// Use theme colors (current light/dark mode)
await takeScreenshot(markmap, {
background: getComputedStyle(document.body).backgroundColor,
text: getComputedStyle(document.body).color
})
// Use custom colors
await takeScreenshot(markmap, {
background: '#ffffff',
text: '#000000'
})
// Use transparent background
await takeScreenshot(markmap, {
background: 'transparent',
text: '#2c3e50'
})
// Screenshot automatically copied to clipboard
```
### Drag and Drop Resize
Create resizable mindmap containers with drag handles.
```typescript
import { dragAndDrop } from 'src/utilities/callbag'
import Callbag from 'src/utilities/callbag'
const resizeHandle = document.createElement('hr')
resizeHandle.classList.add('workspace-leaf-resize-handle')
container.prepend(resizeHandle)
const drag$ = dragAndDrop(resizeHandle)
let currentHeight = 300
Callbag.subscribe(drag$, (drag) => {
// drag: { start, current, changeFromStart, changeFromPrevious }
currentHeight += drag.changeFromPrevious.y
svg.style.height = currentHeight + 'px'
})
// Save height on mouse up
Callbag.subscribe(fromEvent(document, 'mouseup'), () => {
console.log('Final height:', currentHeight)
// Update frontmatter or settings
})
```
### Settings Change Listeners
React to specific setting changes without re-rendering entire view.
```typescript
import { settingChanges } from 'src/settings/filesystem'
// Listen to single setting
const unsubscribe = settingChanges.listen('coloring', (newValue) => {
console.log('Coloring changed to:', newValue)
// Re-render mindmaps with new coloring strategy
})
// Listen to multiple settings
settingChanges.listen('animationDuration', (duration) => {
console.log('Animation duration:', duration)
})
settingChanges.listen('depth1Color', (color) => {
console.log('Depth 1 color:', color)
})
// Unsubscribe when done
unsubscribe()
```
### Layout Serialization
Save and restore workspace layout with pinned mindmap views.
```typescript
import { layoutManager } from 'src/views/layout-manager'
import { layout } from 'src/settings/filesystem'
// Save current layout
layoutManager.serialise()
// Stores to plugin data: { version: '2.0', settings: {...}, layout: [...] }
// On plugin load, restore layout
await layoutManager.deserialise()
// Recreates all pinned mindmap tabs in correct positions
// Access raw layout data
const currentLayout = layout.load()
console.log('Saved layout:', currentLayout)
// Manually update layout
layout.save([
{
type: 'tabs',
mindmaps: [
{ file: 'notes/project.md', pinned: true }
]
}
])
```
### Coloring Strategies
Apply different coloring algorithms to mindmap branches.
```typescript
import { depthColoring } from 'src/rendering/renderer-common'
// Depth-based coloring
const depthColorFn = depthColoring({
coloring: 'depth',
depth1Color: '#e74c3c',
depth2Color: '#3498db',
depth3Color: '#2ecc71',
defaultColor: '#95a5a6'
})
const node = { state: { depth: 2 } }
const color = depthColorFn(node) // '#3498db'
// Branch coloring (random per branch)
const options = {
coloring: 'branch',
colorFreezeLevel: 2 // Stop color changes after depth 2
}
// Single color (uniform)
const singleColorOptions = {
coloring: 'single',
defaultColor: '#34495e'
}
```
### Translation Support
Access localized strings based on user's Obsidian language.
```typescript
import { strings } from 'src/translation'
// Command names
console.log(strings.commands.unpinned) // "Open as unpinned mindmap"
console.log(strings.commands.pinned) // "Open as pinned mindmap"
// Menu items
console.log(strings.menu.copyScreenshot) // "Copy screenshot"
console.log(strings.menu.collapseAll) // "Collapse all"
console.log(strings.menu.pin.pin) // "Pin"
console.log(strings.menu.pin.unpin) // "Unpin"
console.log(strings.menu.toolbar.show) // "Show"
console.log(strings.menu.toolbar.hide) // "Hide"
// Settings UI
console.log(strings.settings.level.global) // "Global Settings"
console.log(strings.settings.level.file) // "File Settings"
console.log(strings.settings.level.codeBlock) // "Code Block Settings"
// Supported languages: en, zh-CN, zh-TW
// Auto-detects from Obsidian locale
```
### Custom Subject/Stream Pattern
Create imperative push-based streams for event emission.
```typescript
import Callbag from 'src/utilities/callbag'
const { source, push } = Callbag.subject()
// Subscribe to stream
Callbag.subscribe(source, (value) => {
console.log('Received:', value)
})
// Push values
push('Hello')
push.next('World')
// Handle errors
push.error(new Error('Something went wrong'))
// Complete stream
push.complete()
// Use with reactive operators
Callbag.pipe(
source,
Callbag.filter(x => x.length > 5),
Callbag.map(x => x.toUpperCase()),
Callbag.subscribe(console.log)
)
```
### Dynamic Style Management
Inject and update CSS dynamically based on settings.
```typescript
import { globalStyle } from 'src/rendering/style-tools'
// Register style that updates on setting changes
globalStyle.add(
['depth1Thickness', 'depth2Thickness'], // Triggers
(settings) => `
.markmap-node[data-depth="1"] {
stroke-width: ${settings.depth1Thickness}px;
}
.markmap-node[data-depth="2"] {
stroke-width: ${settings.depth2Thickness}px;
}
`
)
// Styles auto-update when settings change
globalSettings.depth1Thickness = '5'
// CSS automatically regenerated and applied
```
### Utility Functions
Helper functions for common operations.
```typescript
import { assert, notNullish, isObjectEmpty, nextTick } from 'src/utilities/utilities'
// Type-safe assertions
const file = app.vault.getFileByPath('notes/project.md')
assert(notNullish, file, 'File not found')
// Now TypeScript knows file is not null
// Check empty objects
const settings = {}
if (isObjectEmpty(settings)) {
console.log('No custom settings')
}
// Wait for next event loop tick
await nextTick()
console.log('Executed after current call stack')
// Custom Set with functional methods
import { Set } from 'src/utilities/set'
const numbers = new Set([1, 2, 3, 4, 5])
const evens = numbers.filter(n => n % 2 === 0) // Set { 2, 4 }
const doubled = numbers.map(n => n * 2) // Set { 2, 4, 6, 8, 10 }
const found = numbers.find(n => n > 3) // 4
```
## Summary and Integration
Mindmap NextGen serves as a powerful visualization layer for Obsidian notes, enabling users to transform hierarchical markdown structures into interactive mindmaps. Primary use cases include project planning and brainstorming, knowledge base visualization, document outlining, meeting notes organization, and hierarchical data representation. The plugin's flexible three-tier settings system allows customization at global, file, and code-block levels, while features like internal linking, embeds, and screenshot export enhance note-taking workflows.
Integration patterns leverage Obsidian's plugin API through event subscriptions, workspace management, and markdown post-processing. Developers can extend functionality by subscribing to Callbag event streams, creating custom coloring strategies, implementing new markdown-it plugins for embed support, or building UI components using the settings framework. The reactive architecture based on functional programming principles ensures efficient rendering and state management, making it suitable for both simple note visualization and complex knowledge management systems. The plugin's modular design allows easy customization of rendering pipeline, settings persistence, view management, and link handling behaviors.