Skip to content

Instantly share code, notes, and snippets.

@sanjeevkse
Created October 5, 2023 04:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sanjeevkse/917abba04b596bef6a8ec877c6055b3c to your computer and use it in GitHub Desktop.
Save sanjeevkse/917abba04b596bef6a8ec877c6055b3c to your computer and use it in GitHub Desktop.
v
<!DOCTYPE html>
<html>
<head>
<style>
#artboard {
width: 800px; /* Initial width */
height: 600px; /* Initial height */
overflow: hidden;
border: 1px solid #000;
position: relative;
}
#draggable-container {
width: 2000px; /* Set a large width to make it effectively infinite */
height: 2000px; /* Set a large height to make it effectively infinite */
cursor: grab;
transform-origin: top left;
transition: transform 0.2s ease-in-out;
/* Add an initial transform scale */
transform: scale(1);
}
#treeSVG {
position: absolute;
}
</style>
</head>
<body>
<div id="artboard">
<div id="draggable-container">
<svg id="treeSVG" width="2000" height="2000"></svg>
</div>
</div>
<div id="nodeInfo"></div>
<button id="zoom-in">Zoom In</button>
<button id="zoom-out">Zoom Out</button>
<script>
const artboard = document.getElementById("artboard");
const draggableContainer = document.getElementById("draggable-container");
const svg = document.getElementById("treeSVG");
// Define your tree hierarchy data here
const treeData = {
name: "Root",
level: 0,
children: [
{
name: "Node 1",
level: 1,
children: [
{
name: "Node 1.1",
level: 2,
children: [
{ name: "Node 1.1.1" },
{ name: "Node 1.1.2" },
{ name: "Node 1.1.3" },
{ name: "Node 1.1.4" },
],
},
{ name: "Node 1.2", level: 2 },
],
},
{
name: "Node 2",
level: 1,
children: [
{ name: "Node 2.1" },
{ name: "Node 2.2" },
{ name: "Node 2.3" },
{ name: "Node 2.4" },
],
},
{
name: "Node 3",
level: 1,
children: [{ name: "Node 3.1" }, { name: "Node 3.2" }],
},
],
};
function drawTree(node, x, y, level) {
const spacingX = 160;
const spacingY = 80;
const textPadding = 10;
// Create a group (g) element to contain both the rectangle and text
const group = document.createElementNS(
"http://www.w3.org/2000/svg",
"g"
);
// Calculate rectangle dimensions
const rectWidth = 80;
const textElement = document.createElementNS(
"http://www.w3.org/2000/svg",
"text"
);
textElement.textContent = node.name;
svg.appendChild(textElement);
const textHeight = textElement.getBBox().height;
svg.removeChild(textElement);
const rectHeight = textHeight + textPadding * 2;
const borderRadius = 10;
// Draw the rectangle and add it to the group
const rect = document.createElementNS(
"http://www.w3.org/2000/svg",
"rect"
);
rect.setAttribute("x", x - rectWidth / 2);
rect.setAttribute("y", y - rectHeight / 2);
rect.setAttribute("width", rectWidth);
rect.setAttribute("height", rectHeight);
rect.setAttribute("rx", borderRadius);
rect.setAttribute("fill", "#0050FE");
group.appendChild(rect);
// Add text to the group
const text = document.createElementNS(
"http://www.w3.org/2000/svg",
"text"
);
text.setAttribute("x", x);
text.setAttribute("y", y);
text.setAttribute("text-anchor", "middle");
text.setAttribute("alignment-baseline", "middle");
text.setAttribute("fill", "white");
text.textContent = node.name;
group.appendChild(text);
// Add the group to the SVG
svg.appendChild(group);
// Draw connections to children with Bezier curves
if (node.children) {
const childX = x + spacingX;
node.children.forEach((child) => {
const childY =
y -
(spacingY * (node.children.length - 1)) / 2 +
spacingY * node.children.indexOf(child);
// Draw Bezier curve
const curve = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
const curveD = `M${x + rectWidth / 2} ${y} C${
x + rectWidth / 2 + spacingX / 2
} ${y}, ${childX - spacingX / 2} ${childY}, ${
childX - rectWidth / 2
} ${childY}`;
curve.setAttribute("d", curveD);
curve.setAttribute("fill", "transparent");
curve.setAttribute("stroke", "#BCC4CC");
curve.setAttribute("stroke-width", "2");
svg.appendChild(curve);
// Recursively draw child nodes
drawTree(child, childX, childY, level + 1);
});
}
// Add click event listener to the group (for both text and rectangle)
group.addEventListener("click", () => {
updateNodeInfo(node);
});
}
function updateNodeInfo(selectedNode) {
const nodeInfo = document.getElementById("nodeInfo");
nodeInfo.textContent = `Selected Node: ${JSON.stringify(selectedNode)}`;
}
// Start drawing from the root node
drawTree(treeData, svg.clientWidth / 2, 300, 0);
// Implement drag and pan functionality
let isDragging = false;
let startX, startY, scrollLeft, scrollTop;
draggableContainer.addEventListener("mousedown", (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
scrollLeft = artboard.scrollLeft;
scrollTop = artboard.scrollTop;
draggableContainer.style.cursor = "grabbing";
});
window.addEventListener("mousemove", (e) => {
if (isDragging) {
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
artboard.scrollLeft = scrollLeft - deltaX;
artboard.scrollTop = scrollTop - deltaY;
}
});
window.addEventListener("mouseup", () => {
isDragging = false;
draggableContainer.style.cursor = "grab";
});
// Zoom-in and zoom-out buttons
document.getElementById("zoom-in").addEventListener("click", () => {
const currentScale = getComputedStyle(draggableContainer).transform;
const scaleValue = currentScale.match(/matrix\(([^,]+),/)[1];
const newScale = parseFloat(scaleValue) * 1.2; // Adjust the zoom factor as needed
draggableContainer.style.transform = `scale(${newScale})`;
});
document.getElementById("zoom-out").addEventListener("click", () => {
const currentScale = getComputedStyle(draggableContainer).transform;
const scaleValue = currentScale.match(/matrix\(([^,]+),/)[1];
const newScale = parseFloat(scaleValue) / 1.2; // Adjust the zoom factor as needed
draggableContainer.style.transform = `scale(${newScale})`;
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment