Created
May 13, 2025 15:27
-
-
Save hi2rashid/324838068eb35059225a02b6917e97cc to your computer and use it in GitHub Desktop.
TreeGraph using JS v1 - Mindmap alternative with 2 sub branches
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With custom Emoji plant icons support for branching