Mind Map Generator: From Text Parsing to SVG Visualization
Mind Map Generator: From Text Parsing to SVG Visualization#
Published: May 8, 2026, 08:10
Mind maps are powerful tools for organizing thoughts and visualizing hierarchical relationships. Today, we’ll dive deep into implementing a text-based mind map generator, covering text parsing, tree layout algorithms, SVG rendering, and export optimization.
Core Technology Stack#
- Text Parsing: Indentation-based hierarchy recognition
- Layout Calculation: Recursive tree layout algorithm
- Visualization: SVG Bezier curve connections
- Export: Canvas API image generation
1. Text Parsing: From Indentation to Tree Structure#
The core challenge is converting indented text into a tree data structure. We use a stack-based parser:
interface Node {
id: string;
text: string;
children: Node[];
collapsed?: boolean;
x?: number; // Calculated layout coordinates
y?: number;
}
function parseTree(text: string): Node {
const lines = text.split("\n").filter(l => l.trim());
const root: Node = { id: "root", text: "Mind Map", children: [] };
const stack: { node: Node; level: number }[] = [{ node: root, level: -1 }];
lines.forEach((line, idx) => {
// Calculate indentation level (number of spaces)
const level = line.search(/\S/);
const text = line.trim();
if (!text) return;
const node: Node = { id: `node-${idx}`, text, children: [] };
// Pop stack to correct parent level
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
stack.pop();
}
// Add current node to parent's children
stack[stack.length - 1].node.children.push(node);
stack.push({ node, level });
});
return root;
}
Key insight: line.search(/\S/) gets the position of the first non-whitespace character, which equals the indentation level. The stack ensures correct parent-child relationships:
Project Planning
Requirements
User Research # level = 4, parent is "Requirements"
Competitive Analysis
Design Phase # level = 2, parent backtracks to "Project Planning"
2. Layout Algorithm: Recursive Position Calculation#
The main challenge in tree layout is avoiding node overlap while maintaining visual balance. We use a bottom-up recursive algorithm:
function calculateLayout(node: Node, x = 0, y = 0, levelHeight = 60) {
const nodeWidth = Math.max(120, node.text.length * 14 + 32);
if (node.children.length === 0 || node.collapsed) {
return { node: { ...node, x, y }, width: nodeWidth };
}
// Recursively calculate all children layouts
let totalChildrenWidth = 0;
const childResults = [];
for (const child of node.children) {
const result = calculateLayout(child, 0, y + levelHeight, levelHeight);
childResults.push(result);
totalChildrenWidth += result.width;
}
// Add spacing between children
totalChildrenWidth += (node.children.length - 1) * 40;
// Horizontally center children
const startX = x - totalChildrenWidth / 2;
let currentX = startX;
const updatedChildren = [];
for (let i = 0; i < node.children.length; i++) {
const childX = currentX + childResults[i].width / 2;
const updatedChild = calculateLayout(
node.children[i], childX, y + levelHeight, levelHeight
);
updatedChildren.push(updatedChild.node);
currentX += childResults[i].width + 40;
}
return {
node: { ...node, x, y, children: updatedChildren },
width: Math.max(nodeWidth, totalChildrenWidth)
};
}
Algorithm highlights:
- Width pre-calculation: Recursively calculate subtree widths first, then determine positions
- Center alignment: Children are centered around parent horizontally
- Dynamic spacing: 40px gap between nodes prevents visual crowding
3. SVG Rendering: Bezier Curve Connections#
Connections use cubic Bezier curves for elegant visualization:
// Draw connections
{links.map((link, idx) => {
const fromX = link.from.x || 0;
const fromY = (link.from.y || 0) + 20; // Node bottom
const toX = link.to.x || 0;
const toY = (link.to.y || 0) - 20; // Child node top
// Cubic Bezier curve control points
const midY = (fromY + toY) / 2;
const path = `M ${fromX} ${fromY} C ${fromX} ${midY}, ${toX} ${midY}, ${toX} ${toY}`;
return (
<path
key={idx}
d={path}
fill="none"
stroke="#4b5563"
strokeWidth="2"
opacity="0.6"
/>
);
})}
Bezier curve principle: C x1 y1, x2 y2, x y defines three control points. Setting control points at the vertical midpoint creates elegant S-shaped curves.
Node rendering uses SVG <g> groups for scaling and translation:
{nodes.map((node, idx) => {
const level = Math.floor((node.y || 0) / 60);
const color = colors[level % colors.length];
return (
<g key={node.id} transform={`translate(${node.x}, ${node.y})`}>
<rect
x={-width / 2}
y={-20}
width={width}
height={40}
rx={8}
fill={color}
opacity="0.9"
/>
<text
x={0}
y={5}
textAnchor="middle"
fill="white"
fontSize="14"
>
{node.text}
</text>
</g>
);
})}
4. Image Export: SVG to PNG#
Export functionality requires converting SVG to PNG, with proper Unicode handling:
function exportToPNG() {
const svg = containerRef.current?.querySelector('svg');
if (!svg) return;
const svgData = new XMLSerializer().serializeToString(svg);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx?.drawImage(img, 0, 0);
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = 'mindmap.png';
a.click();
};
// Critical: Handle Unicode characters
img.src = 'data:image/svg+xml;base64,' +
btoa(unescape(encodeURIComponent(svgData)));
}
Encoding pitfall: btoa() only supports ASCII characters. Direct Chinese text input throws an error. The solution is encodeURIComponent for UTF-8 byte sequences, then unescape to restore ASCII string.
5. Performance Optimization & Edge Cases#
1. useMemo for Layout Caching#
const tree = useMemo(() => parseTree(input), [input]);
const layoutTree = useMemo(() => calculateLayout(tree).node, [tree]);
const { nodes, links } = useMemo(
() => collectNodesAndLinks(layoutTree), [layoutTree]
);
2. Dynamic viewBox Calculation#
const bounds = useMemo(() => {
const xs = nodes.map(n => n.x || 0);
const ys = nodes.map(n => n.y || 0);
return {
minX: Math.min(...xs) - 100,
minY: Math.min(...ys) - 50,
maxX: Math.max(...xs) + 100,
maxY: Math.max(...ys) + 100
};
}, [nodes]);
3. Zoom Support#
const [scale, setScale] = useState(1);
<svg
width={svgWidth * scale}
height={svgHeight * scale}
viewBox={`${bounds.minX} ${bounds.minY} ${svgWidth} ${svgHeight}`}
style={{ transform: `scale(${scale})`, transformOrigin: 'top left' }}
>
Real-World Applications#
- Project Planning: Quickly outline requirements, design, development, and testing phases
- Knowledge Organization: Visualize tech stacks and framework features in tree structures
- Meeting Notes: Convert discussion points to structured mind maps in real-time
- Article Outlines: Plan chapter structures before writing
Related Tools#
- JSON Formatter - Format JSON data with clear hierarchy visualization
- Mermaid Editor - Create flowcharts and sequence diagrams with DSL syntax
- Markdown Table Generator - Quickly create structured tables
Implementing a mind map generator demonstrates the integration of text parsing, recursive algorithms, and SVG rendering. The core challenge lies in layout algorithm design—balancing node overlap prevention with visual harmony. Through stack-based parsing and bottom-up layout, we’ve built a clean, efficient mind map generator.