Draw tree with nodes and lines

by Rajesh Danabal

HTML

<div>
  <label>Node Size: <input type="number" id="nodeSize" value="20" min="10" max="50" /></label>
  <button onclick="redraw()">Redraw</button>
</div>
<canvas id="treeCanvas" width="900" height="600" style="border:1px solid #ccc; margin-top: 10px;"></canvas>

JavaScript

const canvas = document.getElementById("treeCanvas");
const ctx = canvas.getContext("2d");
const nodeSizeInput = document.getElementById("nodeSize");

let paginationState = {}; // { level: pageIndex }
let nodesPerPage = {}; // { level: count }

const verticalSpacing = 100;

// Sample tree (3 levels deep)
const tree = {
  label: "Root",
  children: Array.from({ length: 10 }, (_, i) => ({
    label: `Child ${i + 1}`,
    children: Array.from({ length: i % 2 === 0 ? 4 : 2 }, (_, j) => ({
      label: `Grandchild ${i + 1}.${j + 1}`,
      children: []
    }))
  }))
};

// Flatten tree into levels and track parent-child X mappings
function flattenByLevel(root) {
  const result = [];
  const queue = [{ node: root, level: 0, parent: null }];

  while (queue.length) {
    const { node, level, parent } = queue.shift();
    if (!result[level]) result[level] = [];
    result[level].push({ ...node, parent });

    if (node.children) {
      node.children.forEach(child => {
        queue.push({ node: child, level: level + 1, parent: node });
      });
    }
  }
  return result;
}

function computeNodesPerPage(radius) {
  const margin = 20;
  const spacing = radius * 2 + margin;
  return Math.max(1, Math.floor(canvas.width / spacing));
}

function drawNode(x, y, radius, label) {
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fillStyle = "#ffe082";
  ctx.fill();
  ctx.strokeStyle = "#ff6f00";
  ctx.stroke();

  ctx.fillStyle = "#000";
  ctx.font = `${Math.min(radius, 14)}px Arial`;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(label, x, y);
}

function drawTree(treeData, radius) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  const levels = flattenByLevel(treeData);
  paginationState = paginationState || {};
  const yStart = radius + 20;

  const nodePositions = new Map(); // Map node => {x, y}

  levels.forEach((nodes, level) => {
    const page = paginationState[level] || 0;
    nodesPerPage[level] =...