Skip to content

Commit 58e9cf8

Browse files
committed
feat: tree visualizer develop
1 parent e1bf458 commit 58e9cf8

File tree

5 files changed

+212
-2
lines changed

5 files changed

+212
-2
lines changed

index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
<link rel="stylesheet" href="./src/output.css" />
99
</head>
1010
<body>
11-
<div id="app">
11+
<div id="app" class="overflow-hidden">
1212
<!-- header -->
1313
<div class="mt-3 text-gray-800 flex flex-col items-center w-full">
14-
<h1 class="text-xl font-medium font-mono">Leetcode tree visualizer</h1>
14+
<h1 class="text-xl font-medium font-mono text-center">
15+
Leetcode tree visualizer
16+
</h1>
1517
<!-- public links -->
1618
<div class="flex space-x-4 mt-2">
1719
<a
@@ -57,6 +59,7 @@ <h1 class="text-xl font-medium font-mono">Leetcode tree visualizer</h1>
5759

5860
<!-- Input field to take test case -->
5961
<!-- place for tree render -->
62+
<canvas></canvas>
6063
</div>
6164
<script type="module" src="/src/main.ts"></script>
6265
</body>

src/data-structures/TreeNode.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
class TreeNode {
2+
private data: number | null
3+
private left: TreeNode | null
4+
private right: TreeNode | null
5+
6+
constructor(value: number | null) {
7+
this.data = value
8+
this.left = null
9+
this.right = null
10+
}
11+
12+
setLeftNode(node: TreeNode | null) {
13+
this.left = node
14+
}
15+
16+
setRightNode(node: TreeNode | null) {
17+
this.right = node
18+
}
19+
20+
getHeight():number {
21+
let leftHeight = this.left?.getHeight() || 0
22+
let rightHeight = this.right?.getHeight() || 0
23+
24+
return Math.max(leftHeight, rightHeight) + 1;
25+
}
26+
27+
getNodeValue(node: TreeNode): number | null {
28+
return node.data;
29+
}
30+
31+
getLeftNode(node: TreeNode): TreeNode {
32+
if (this.isLeftNodeAvailable(node)) {
33+
return node.left!;
34+
}
35+
36+
return new TreeNode(null);
37+
}
38+
39+
getRightNode(node: TreeNode): TreeNode {
40+
if (node.isRightNodeAvailable(node)) {
41+
return node.right!;
42+
}
43+
44+
return new TreeNode(null);
45+
}
46+
47+
isLeftNodeAvailable(node: TreeNode): boolean {
48+
if(node.left) {
49+
return true;
50+
}
51+
return false;
52+
}
53+
54+
isRightNodeAvailable(node: TreeNode): boolean {
55+
if(node.right) {
56+
return true;
57+
}
58+
return false;
59+
}
60+
}
61+
62+
export default TreeNode;

src/main.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import TreeNode from "./data-structures/TreeNode";
2+
import renderTree from "./render/drawTree";
3+
4+
const canvas = document.querySelector("canvas");
5+
const root = new TreeNode(10);
6+
const root1 = new TreeNode(12);
7+
root.setLeftNode(root1);
8+
9+
const root2 = new TreeNode(13);
10+
root.setRightNode(root2);
11+
12+
13+
const root3 = new TreeNode(13);
14+
root2.setRightNode(root3);
15+
root1.setLeftNode(root3);
16+
17+
18+
const root4 = new TreeNode(11);
19+
root2.setLeftNode(root4);
20+
21+
22+
renderTree(root, canvas)

src/render/drawTree.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import TreeNode from "../data-structures/TreeNode"
2+
import { CONFIG } from "../utils/tree";
3+
4+
5+
function renderTree(rootNode: TreeNode, canvasEle: HTMLCanvasElement | null) {
6+
7+
// set canvas dimension same as window dimension
8+
const windowWidth = window.innerWidth;
9+
const windowHeight = window.innerHeight;
10+
11+
if(canvasEle) {
12+
canvasEle.width = windowWidth;
13+
canvasEle.height = windowHeight;
14+
}
15+
16+
const { nodeContainerHeight, nodeContainersWidth } = getTreeHeightWidth(rootNode);
17+
18+
const windowCenter = windowWidth / 2;
19+
const nodeContainerWidthCenter = nodeContainersWidth / 2;
20+
21+
const xStart = windowCenter - nodeContainerWidthCenter;
22+
const xEnd = windowCenter + nodeContainerWidthCenter;
23+
24+
const horizontalConfig = { xStart, xEnd };
25+
26+
// Draw tree
27+
drawTreeRecursively(rootNode, canvasEle, 0.5, horizontalConfig);
28+
}
29+
30+
function getTreeHeightWidth(node: TreeNode) {
31+
const heightOfTree = node.getHeight();
32+
const maxLeafNodeAtLlevel = Math.pow(2, heightOfTree);
33+
34+
const nodeContainerHeight = heightOfTree * CONFIG.HEIGHT_SPACING;
35+
const nodeContainersWidth = maxLeafNodeAtLlevel * CONFIG.WIDTH_SPACING;
36+
37+
return {
38+
nodeContainerHeight,
39+
nodeContainersWidth
40+
};
41+
}
42+
43+
function drawTreeRecursively(node: TreeNode, canvasEle: HTMLCanvasElement | null, currentLevel: number, horizontalConfig: {xStart: number, xEnd: number}) {
44+
const { xStart, xEnd } = horizontalConfig;
45+
46+
const xPos = (xStart + xEnd) / 2;
47+
const yPos = currentLevel * CONFIG.HEIGHT_SPACING;
48+
49+
const nodeValue = node.getNodeValue(node)?.toString() || "";
50+
drawNode(nodeValue, canvasEle, xPos, yPos);
51+
52+
53+
if(node.isLeftNodeAvailable(node)) {
54+
const leftNodeHorizontalConfig = {xStart, xEnd: xPos};
55+
drawTreeRecursively(node.getLeftNode(node), canvasEle, currentLevel + 1, leftNodeHorizontalConfig);
56+
57+
const xCord = { xStart: xPos, xEnd: (xStart + xPos)/2 };
58+
const yCord = { yStart: yPos + CONFIG.RADIUS, yEnd: ((currentLevel+1)*CONFIG.HEIGHT_SPACING) - CONFIG.RADIUS};
59+
60+
connectNodesEdge(canvasEle, xCord, yCord )
61+
}
62+
63+
if(node.isRightNodeAvailable(node)) {
64+
const rightNodeHorizontalConfig = {xStart: xPos, xEnd};
65+
drawTreeRecursively(node.getRightNode(node), canvasEle, currentLevel + 1, rightNodeHorizontalConfig);
66+
67+
const xCord = { xStart: xPos, xEnd: (xPos + xEnd)/2 };
68+
const yCord = { yStart: yPos + CONFIG.RADIUS, yEnd: ((currentLevel+1)*CONFIG.HEIGHT_SPACING) - CONFIG.RADIUS};
69+
70+
connectNodesEdge(canvasEle, xCord, yCord )
71+
}
72+
}
73+
74+
function drawNode(value: string, canvasEle: HTMLCanvasElement | null, x: number, y: number) {
75+
76+
const context = canvasEle?.getContext("2d");
77+
78+
// Draw circle
79+
context?.beginPath();
80+
context?.arc(x, y, CONFIG.RADIUS, 0, 2*Math.PI, false);
81+
(context as CanvasRenderingContext2D).fillStyle = "#6a00f4"; // type assertion
82+
context?.fill();
83+
84+
// Draw circle border
85+
context?.arc(x, y, CONFIG.RADIUS, 0, 2*Math.PI, false);
86+
(context as CanvasRenderingContext2D).strokeStyle = "#10002b"; // type assertion
87+
(context as CanvasRenderingContext2D).lineWidth = 3;
88+
context?.stroke();
89+
90+
// fill the node value
91+
(context as CanvasRenderingContext2D).font = `bold ${CONFIG.FONT_SIZE}px Arial`;
92+
(context as CanvasRenderingContext2D).fillStyle = "#f0fff1"; // type assertion
93+
(context as CanvasRenderingContext2D).textAlign = "center";
94+
95+
96+
(context as CanvasRenderingContext2D).fillText(value, x, y + (CONFIG.FONT_SIZE/3));
97+
98+
}
99+
100+
function connectNodesEdge(canvasEle: HTMLCanvasElement | null, xCord: {xStart: number, xEnd: number}, yCord: {yStart: number, yEnd: number}) {
101+
const { xStart, xEnd } = xCord;
102+
const { yStart, yEnd } = yCord;
103+
104+
const start = { x: xStart, y: yStart };
105+
const end = { x: xEnd, y: yEnd };
106+
107+
108+
const context = canvasEle?.getContext("2d");
109+
context?.beginPath();
110+
(context as CanvasRenderingContext2D).strokeStyle = "#6a00f4";
111+
(context as CanvasRenderingContext2D).lineWidth = 2;
112+
context?.moveTo(start.x, start.y);
113+
context?.lineTo(end.x, end.y);
114+
context?.stroke();
115+
}
116+
117+
export default renderTree;

src/utils/tree.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const CONFIG = {
2+
RADIUS: 25,
3+
WIDTH_SPACING: 50,
4+
HEIGHT_SPACING: 90,
5+
FONT_SIZE: 14
6+
}

0 commit comments

Comments
 (0)