// Canvas dimensions
const width = window.innerWidth * 0.9 * 0.75;
const height = 600;
// Create Konva stage & layer
const stage = new Konva.Stage({
container: 'tree-container',
width: width,
height: height
});
const layer = new Konva.Layer();
stage.add(layer);
// Node positions (9 as left child of 8)
const positions = {
1: { x: width/2, y: 80 },
2: { x: width/2 -200, y:180 },
3: { x: width/2 +200, y:180 },
4: { x: width/2 -300, y:280 },
5: { x: width/2 -100, y:280 },
6: { x: width/2 -150, y:380 },
7: { x: width/2 -50, y:380 },
8: { x: width/2 +300, y:280 },
9: { x: width/2 +250, y:380 }
};
// Tree data
const tree = {
val: 1,
left: {
val: 2,
left: { val: 4, left: null, right: null },
right: {
val: 5,
left: { val: 6, left: null, right: null },
right: { val: 7, left: null, right: null }
}
},
right: {
val: 3,
left: null,
right: {
val: 8,
left: { val: 9, left: null, right: null },
right: null
}
}
};
// Store node visuals
const nodes = {};
// Draw a single node
function drawNode(x, y, label) {
const circle = new Konva.Circle({ x, y, radius: 20, fill: 'white', stroke: '#333', strokeWidth: 2 });
const text = new Konva.Text({ x: x - 5, y: y - 8, text: label.toString(), fontSize: 16, fill: '#333' });
layer.add(circle, text);
nodes[label] = { circle, text };
}
// Draw edge between parent and child
function drawEdge(parentVal, childVal) {
const p = positions[parentVal], c = positions[childVal];
const dx = c.x - p.x, dy = c.y - p.y;
const d = Math.hypot(dx, dy), ox = (dx/d) * 20, oy = (dy/d) * 20;
const line = new Konva.Line({ points:[p.x+ox,p.y+oy, c.x-ox,c.y-oy], stroke: '#333', strokeWidth: 2 });
layer.add(line);
}
// Make tree
function renderTree(node, parent = null) {
if (!node) return;
drawNode(positions[node.val].x, positions[node.val].y, node.val);
if (parent) drawEdge(parent.val, node.val);
renderTree(node.left, node);
renderTree(node.right, node);
}
renderTree(tree);
// Stack visualization
const stackStage = new Konva.Stage({ container: 'stack-visual', width: 200, height: 500 });
const stackLayer = new Konva.Layer();
stackStage.add(stackLayer);
let stackVisual = [];
function pushStack(val) {
const y = 400 - stackVisual.length * 30;
const box = new Konva.Rect({ x:50, y, width:40, height:25, fill:'#007bff', stroke:'#0056b3', cornerRadius:4 });
const txt = new Konva.Text({ x:58, y: y+4, text: val.toString(), fontSize:14, fill:'#fff' });
stackLayer.add(box, txt);
stackVisual.push({ box, txt });
stackLayer.draw();
}
function popStack() {
const it = stackVisual.pop();
if (it) { it.box.destroy(); it.txt.destroy(); stackLayer.draw(); }
}
// Highlight current node
function highlight(val) {
for (const v in nodes) nodes[v].circle.fill('white');
if (val != null && nodes[val]) nodes[val].circle.fill('#ffc107');
layer.draw();
}
function updateStatus(current, reason) {
document.getElementById('currentVal').innerText = current != null ? current : 'None';
const reasonEl = document.getElementById('reason');
reasonEl.innerText = reason;
reasonEl.style.whiteSpace = 'normal';
reasonEl.style.display = 'inline-block';
reasonEl.style.maxWidth = '70%';
reasonEl.style.overflowWrap = 'break-word';
}
function logVisit(val) { document.getElementById('seq').innerText += ` ${val}`; }
// Traversal state
let current = tree;
let lastVisited = null;
const stack = [];
let stepReady = true;
// Final sequences for display
const finalSeqs = {
'In-Order': '4 2 6 5 7 1 3 9 8',
'Pre-Order': '1 2 4 5 6 7 3 8 9',
'Post-Order': '4 6 7 5 2 9 8 3 1'
};
// Tabs
const modes = ['In-Order', 'Pre-Order', 'Post-Order'];
let currentMode = 'In-Order';
const tabsDiv = document.createElement('div');
tabsDiv.id = 'tabs'; tabsDiv.style.textAlign = 'center'; tabsDiv.style.margin = '10px';
modes.forEach(mode => {
const btn = document.createElement('button');
btn.innerText = mode; btn.className = 'tab-btn';
btn.onclick = () => {
currentMode = mode;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update final sequence display
finalSeqEl.innerHTML = `Final Sequence (${mode}): ${finalSeqs[mode]}`;
resetAll();
};
if (mode === currentMode) btn.classList.add('active');
tabsDiv.appendChild(btn);
});
document.body.insertBefore(tabsDiv, document.getElementById('controls'));
//Initialize final sequence display
const finalSeqEl = document.getElementById('final-seq');
finalSeqEl.innerHTML = `Final Sequence (${currentMode}): ${finalSeqs[currentMode]}`;
// Step functions
function stepInOrder() {
if (current) {
highlight(current.val);
updateStatus(current.val, `We have reached node ${current.val}. Push it onto the stack to remember it for later, then move to its left child to continue traversing the left subtree.`);
stack.push(current); pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length) {
current = stack.pop(); popStack();
highlight(current.val);
updateStatus(current.val, `Left subtree complete. Now visit node ${current.val} and output it in the in-order sequence, then move to its right child to explore its right subtree.`);
logVisit(current.val);
setTimeout(() => { current = current.right; stepReady = true; }, 500);
} else {
highlight(null);
updateStatus(null, 'In-order traversal complete! All nodes have been visited.');
}
}
function stepPreOrder() {
if (current) {
highlight(current.val);
updateStatus(current.val, `Output node ${current.val} as the next in pre-order sequence, then push it onto the stack so we can revisit its right subtree later, and move to its left child.`);
logVisit(current.val);
stack.push(current); pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length) {
current = stack.pop(); popStack();
highlight(current.val);
updateStatus(current.val, `Finished left subtree of node ${current.val}. Now proceed to explore its right subtree.`);
current = current.right;
} else {
highlight(null);
updateStatus(null, 'Pre-order traversal complete! All nodes have been visited.');
}
stepReady = true;
}
function stepPostOrder() {
if (current) {
highlight(current.val);
updateStatus(current.val, `Push node ${current.val} onto the stack and move to its left child. In post-order, we process children before the node itself.`);
stack.push(current); pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length) {
const peek = stack[stack.length - 1];
if (peek.right && lastVisited !== peek.right) {
updateStatus(peek.val, `Left subtree of node ${peek.val} done. Now move to its right subtree to continue processing children first.`);
current = peek.right;
stepReady = true;
} else {
current = stack.pop(); popStack();
highlight(current.val);
updateStatus(current.val, `Both children of node ${current.val} have been visited. Now output node ${current.val} for post-order sequence.`);
logVisit(current.val);
lastVisited = current;
current = null;
stepReady = true;
}
} else {
highlight(null);
updateStatus(null, 'Post-order traversal complete! All nodes have been visited.');
}
}
// Handles step function
document.getElementById('stepBtn').onclick = () => {
if (!stepReady) return;
stepReady = false;
if (currentMode === 'In-Order') stepInOrder();
else if (currentMode === 'Pre-Order') stepPreOrder();
else if (currentMode === 'Post-Order') stepPostOrder();
};
// Clear/Reset
document.getElementById('resetBtn').onclick = resetAll;
function resetAll() {
while (stackVisual.length) popStack();
stack.length = 0;
current = tree;
lastVisited = null;
stepReady = true;
document.getElementById('seq').innerText = '';
updateStatus(null, 'Ready to begin traversal. Click "Step" to start.');
highlight(null);
}
layer.draw();