Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save hi2rashid/324838068eb35059225a02b6917e97cc to your computer and use it in GitHub Desktop.
Save hi2rashid/324838068eb35059225a02b6917e97cc to your computer and use it in GitHub Desktop.
TreeGraph using JS v1 - Mindmap alternative with 2 sub branches
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Interactive Tree with Sub-leaves</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
}
canvas {
display: block;
margin: auto;
background: #ffffff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
cursor: pointer;
}
</style>
</head>
<body>
<canvas id="treeCanvas" width="900" height="1600"></canvas>
<script>
const canvas = document.getElementById("treeCanvas");
const ctx = canvas.getContext("2d");
const branches = [
{ year: "1971", icon: "🏠", text: "Founded", sub: "Started in a small garage" },
{ year: "1985", icon: "πŸ“Š", text: "Analytics", sub: "Introduced data tools" },
{ year: "1998", icon: "πŸ’°", text: "Product Launch", sub: "Financial software" },
{ year: "2000", icon: "πŸ“±", text: "Mobile App", sub: "First app release" },
{ year: "2019", icon: "πŸ’‘", text: "Innovation", sub: "Opened R&D lab" }
];
const centerX = canvas.width / 2;
const baseY = canvas.height - 100;
const verticalGap = 180;
const leafWidth = 240;
const leafHeight = 80;
const subLeafWidth = 200;
const subLeafHeight = 50;
const hoverColor = "#388E3C";
// Store leaf positions for hover detection
const leafBoxes = [];
CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius) {
if (typeof radius === "number") {
radius = { tl: radius, tr: radius, br: radius, bl: radius };
}
this.beginPath();
this.moveTo(x + radius.tl, y);
this.lineTo(x + width - radius.tr, y);
this.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
this.lineTo(x + width, y + height - radius.br);
this.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
this.lineTo(x + radius.bl, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
this.lineTo(x, y + radius.tl);
this.quadraticCurveTo(x, y, x + radius.tl, y);
this.closePath();
};
function drawTrunk(count) {
ctx.strokeStyle = "#3b3b3b";
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(centerX, canvas.height);
ctx.lineTo(centerX, baseY - ((count - 1) * verticalGap));
ctx.stroke();
}
function drawCurvedBranch(x1, y1, x2, y2, direction) {
const controlX = x1 + direction * 60;
ctx.strokeStyle = "#3b3b3b";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.bezierCurveTo(controlX, y1, controlX, y2, x2, y2);
ctx.stroke();
}
function drawBranch(index, data, hoveredIndex) {
const direction = index % 2 === 0 ? -1 : 1;
const y = baseY - index * verticalGap;
const branchLength = 150;
const leafX = centerX + direction * branchLength;
const leafY = y;
const leafLeft = direction === -1 ? leafX - leafWidth : leafX;
// Save position for hover
leafBoxes[index] = { x: leafLeft, y: leafY, width: leafWidth, height: leafHeight };
// Branch curve
drawCurvedBranch(centerX, y + leafHeight / 2, leafX, y + leafHeight / 2, direction);
// Main Leaf
ctx.fillStyle = (hoveredIndex === index) ? hoverColor : (index % 2 === 0 ? "#6CC24A" : "#4CAF50");
ctx.beginPath();
ctx.roundRect(leafLeft, leafY, leafWidth, leafHeight, 20);
ctx.fill();
// Main Leaf Text
ctx.fillStyle = "#fff";
ctx.font = "bold 16px Arial";
ctx.textAlign = "center";
ctx.fillText(`${data.icon} ${data.year}`, leafLeft + leafWidth / 2, leafY + 28);
ctx.font = "14px Arial";
ctx.fillText(data.text, leafLeft + leafWidth / 2, leafY + 52);
// Sub-leaf offset
const subX = leafLeft + (direction === -1 ? 20 : leafWidth - subLeafWidth - 20);
const subY = leafY + leafHeight + 20;
// Connector line
ctx.strokeStyle = "#888";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(leafLeft + leafWidth / 2, leafY + leafHeight);
ctx.lineTo(leafLeft + leafWidth / 2, subY);
ctx.stroke();
// Sub-leaf box
ctx.fillStyle = "#A5D6A7";
ctx.beginPath();
ctx.roundRect(subX, subY, subLeafWidth, subLeafHeight, 10);
ctx.fill();
// Sub-leaf text
ctx.fillStyle = "#000";
ctx.font = "13px Arial";
ctx.textAlign = "center";
ctx.fillText(data.sub, subX + subLeafWidth / 2, subY + 30);
}
// Redraw everything with optional hover
function drawTree(hoveredIndex = null) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawTrunk(branches.length);
branches.forEach((data, i) => drawBranch(i, data, hoveredIndex));
}
// Hover effect logic
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
let hovered = null;
for (let i = 0; i < leafBoxes.length; i++) {
const box = leafBoxes[i];
if (x >= box.x && x <= box.x + box.width &&
y >= box.y && y <= box.y + box.height) {
hovered = i;
break;
}
}
drawTree(hovered);
});
canvas.addEventListener("mouseleave", () => drawTree());
// Initial draw
drawTree();
</script>
</body>
</html>
@hi2rashid
Copy link
Author

With custom Emoji plant icons support for branching



<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Emoji Tree Timeline</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color: #f0f0f0;
      margin: 0;
      padding: 20px;
    }

    canvas {
      display: block;
      margin: auto;
      background: #ffffff;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
      cursor: pointer;
    }
  </style>
</head>
<body>

<canvas id="treeCanvas" width="900" height="1600"></canvas>

<script>
  const canvas = document.getElementById("treeCanvas");
  const ctx = canvas.getContext("2d");

  const branches = [
    {
      year: "1971",
      icon: "🌱",
      shape: "emoji",
      minWidth: 100,
      minHeight: 80,
      text: "Seed planted",
      sub: "Idea born"
    },
    {
      year: "1985",
      icon: "🌳",
      shape: "emoji",
      minWidth: 100,
      minHeight: 100,
      text: "Grew roots",
      sub: "Team formed"
    },
    {
      year: "1998",
      icon: "🍁",
      shape: "rect",
      text: "Autumn Growth",
      sub: "Branch launched"
    },
    {
      year: "2000",
      icon: "πŸ“±",
      shape: "rect",
      text: "Mobile App",
      sub: "App store debut"
    },
    {
      year: "2019",
      icon: "πŸ’‘",
      shape: "rect",
      text: "R&D Lab",
      sub: "Innovation hub"
    }
  ];

  const centerX = canvas.width / 2;
  const baseY = canvas.height - 100;
  const verticalGap = 180;
  const defaultLeafWidth = 220;
  const defaultLeafHeight = 80;
  const subLeafWidth = 200;
  const subLeafHeight = 50;
  const hoverColor = "#388E3C";

  const leafBoxes = [];

  CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius) {
    if (typeof radius === "number") {
      radius = { tl: radius, tr: radius, br: radius, bl: radius };
    }
    this.beginPath();
    this.moveTo(x + radius.tl, y);
    this.lineTo(x + width - radius.tr, y);
    this.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
    this.lineTo(x + width, y + height - radius.br);
    this.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
    this.lineTo(x + radius.bl, y + height);
    this.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
    this.lineTo(x, y + radius.tl);
    this.quadraticCurveTo(x, y, x + radius.tl, y);
    this.closePath();
  };

  function drawTrunk(count) {
    ctx.strokeStyle = "#3b3b3b";
    ctx.lineWidth = 6;
    ctx.beginPath();
    ctx.moveTo(centerX, canvas.height);
    ctx.lineTo(centerX, baseY - ((count - 1) * verticalGap));
    ctx.stroke();
  }

  function drawCurvedBranch(x1, y1, x2, y2, direction) {
    const controlX = x1 + direction * 60;
    ctx.strokeStyle = "#3b3b3b";
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.bezierCurveTo(controlX, y1, controlX, y2, x2, y2);
    ctx.stroke();
  }

  function drawBranch(index, data, hoveredIndex) {
    const direction = index % 2 === 0 ? -1 : 1;
    const y = baseY - index * verticalGap;

    const leafWidth = data.minWidth || defaultLeafWidth;
    const leafHeight = data.minHeight || defaultLeafHeight;

    const leafX = centerX + direction * 150;
    const leafY = y;
    const leafLeft = direction === -1 ? leafX - leafWidth : leafX;

    // Save for hover detection
    leafBoxes[index] = { x: leafLeft, y: leafY, width: leafWidth, height: leafHeight };

    // Connector branch
    drawCurvedBranch(centerX, y + leafHeight / 2, leafX, y + leafHeight / 2, direction);

    if (data.shape === "emoji") {
      // Draw big emoji as "leaf"
      ctx.font = `${leafHeight}px serif`;
      ctx.textAlign = "center";
      ctx.fillText(data.icon, leafLeft + leafWidth / 2, leafY + leafHeight - 10);

      // Label above emoji
      ctx.fillStyle = "#000";
      ctx.font = "14px Arial";
      ctx.fillText(`${data.year}: ${data.text}`, leafLeft + leafWidth / 2, leafY - 10);
    } else {
      // Regular rectangular leaf
      ctx.fillStyle = hoveredIndex === index ? hoverColor : (index % 2 === 0 ? "#6CC24A" : "#4CAF50");
      ctx.beginPath();
      ctx.roundRect(leafLeft, leafY, leafWidth, leafHeight, 20);
      ctx.fill();

      ctx.fillStyle = "#fff";
      ctx.font = "bold 16px Arial";
      ctx.textAlign = "center";
      ctx.fillText(`${data.icon} ${data.year}`, leafLeft + leafWidth / 2, leafY + 28);
      ctx.font = "14px Arial";
      ctx.fillText(data.text, leafLeft + leafWidth / 2, leafY + 52);
    }

    // Sub-leaf setup
    const subX = leafLeft + (direction === -1 ? 20 : leafWidth - subLeafWidth - 20);
    const subY = leafY + leafHeight + 20;

    // Connector to subleaf
    ctx.strokeStyle = "#888";
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(leafLeft + leafWidth / 2, leafY + leafHeight);
    ctx.lineTo(leafLeft + leafWidth / 2, subY);
    ctx.stroke();

    // Subleaf box
    ctx.fillStyle = "#A5D6A7";
    ctx.beginPath();
    ctx.roundRect(subX, subY, subLeafWidth, subLeafHeight, 10);
    ctx.fill();

    // Subleaf text
    ctx.fillStyle = "#000";
    ctx.font = "13px Arial";
    ctx.textAlign = "center";
    ctx.fillText(data.sub, subX + subLeafWidth / 2, subY + 30);
  }

  function drawTree(hoveredIndex = null) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawTrunk(branches.length);
    branches.forEach((data, i) => drawBranch(i, data, hoveredIndex));
  }

  // Hover interaction
  canvas.addEventListener("mousemove", (e) => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    let hovered = null;
    for (let i = 0; i < leafBoxes.length; i++) {
      const box = leafBoxes[i];
      if (x >= box.x && x <= box.x + box.width &&
          y >= box.y && y <= box.y + box.height) {
        hovered = i;
        break;
      }
    }
    drawTree(hovered);
  });

  canvas.addEventListener("mouseleave", () => drawTree());

  // Initial render
  drawTree();
</script>

</body>
</html>



image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment