DEV3 Technologies home

Mind map auto layout

Position a tree of ideas automatically with a subtree-height layout and render it as curved SVG connectors in React.

React / Next.jsChartsmind-maptree-layoutsvgreact

Live preview

Code

export type Idea = { label: string; children?: Idea[] };
type Placed = { label: string; x: number; y: number; branch: number };
type Edge = { from: Placed; to: Placed };

const ROW = 44; // vertical slot per leaf
const COL = 190; // horizontal distance between levels
const W = 140; // node width
const H = 30; // node height

// Pass 1: how many leaf rows does each subtree need?
const rows = (n: Idea): number => (n.children?.length ? n.children.reduce((s, c) => s + rows(c), 0) : 1);

export function layout(root: Idea) {
  const nodes: Placed[] = [];
  const edges: Edge[] = [];
  // Pass 2: place a node in the middle of the vertical slice reserved for its subtree.
  const place = (n: Idea, depth: number, top: number, branch: number): Placed => {
    const height = rows(n) * ROW;
    const me: Placed = { label: n.label, x: depth * COL, y: top + height / 2, branch };
    nodes.push(me);
    let cursor = top;
    n.children?.forEach((c, i) => {
      const child = place(c, depth + 1, cursor, depth === 0 ? i : branch);
      edges.push({ from: me, to: child });
      cursor += rows(c) * ROW;
    });
    return me;
  };
  place(root, 0, 0, 0);
  return { nodes, edges, width: Math.max(...nodes.map((n) => n.x)) + W, height: rows(root) * ROW };
}

const COLORS = ['#2563eb', '#db2777', '#059669', '#d97706', '#7c3aed', '#0891b2'];

export function MindMap({ root }: { root: Idea }) {
  const { nodes, edges, width, height } = layout(root);
  return (
    <svg viewBox={`-8 -8 ${width + 16} ${height + 16}`} role="img" aria-label={`Mind map: ${root.label}`} className="w-full">
      {edges.map((e, i) => {
        const x1 = e.from.x + W, y1 = e.from.y, x2 = e.to.x, y2 = e.to.y, mx = (x1 + x2) / 2;
        return <path key={i} d={`M${x1} ${y1}C${mx} ${y1} ${mx} ${y2} ${x2} ${y2}`} fill="none"
          stroke={COLORS[e.to.branch % COLORS.length]} strokeWidth={2} />;
      })}
      {nodes.map((n, i) => (
        <g key={i} transform={`translate(${n.x} ${n.y - H / 2})`}>
          <rect width={W} height={H} rx={8} fill="#fff" stroke={COLORS[n.branch % COLORS.length]} strokeWidth={2} />
          <text x={W / 2} y={H / 2 + 4} textAnchor="middle" fontSize={13} fill="#111827">{n.label}</text>
        </g>
      ))}
    </svg>
  );
}

How it works

What it solves

Manually dragging every node of a mind map is tedious, and overlapping branches make the result unreadable. An automatic layout lets users type ideas and see a tidy tree immediately, which is the main reason people use these tools.

How it works

The data is a nested tree of { label, children }. layout runs two passes. The first pass measures each subtree: a leaf occupies one row of height ROW, and a parent occupies the sum of its children's heights, so siblings can never overlap vertically. The second pass assigns coordinates. Depth decides the x position (depth * COL), and each node is placed at the vertical centre of its own slice of space, which gives the balanced look of a mind map where parents sit midway between their first and last child.

The function returns a flat list of positioned nodes and a list of edges. The component draws each edge as a cubic Bezier path with horizontal control points, which makes smooth S-shaped connectors, and each node as a rounded rectangle with centred text. The SVG viewBox is computed from the largest coordinates so the diagram always fits.

Colours are chosen by the top-level branch index, making branches visually distinct with no extra data.

Adapting it

  • Mirror half of the root's children to the left for a radial look.
  • Measure text width with a canvas to size boxes dynamically.
  • Store collapsed state on a node and skip its children in both passes.

Pitfalls

  • Very wide trees need scrolling or zoom support.
  • Labels are not wrapped; truncate or measure them.

How to use it

  1. Save as components/MindMap.tsx (works on server or client).
  2. <MindMap root={{ label: 'Launch', children: [{ label: 'Design' }, { label: 'Build', children: [{ label: 'API' }] }] }} />.
  3. Call layout(root) directly to get coordinates for your own renderer.
  4. Adjust ROW, COL and W constants for spacing.

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets