// In-Order / Pre-Order / Post-Order Traversal Visualization (combined) //canvas dimensions for tree visualization const width = window.innerWidth * 0.9 * 0.75; const height = 600; //create the main stage and layer for the tree const stage = new Konva.Stage({ container: 'tree-container', width: width, height: height }); const layer = new Konva.Layer(); stage.add(layer); //adjusted positions: node 9 is 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 } // left child of 8 }; //tree structure 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 references for highlighting const nodes = {}; //recursive tree draw with shortened lines function drawTree(node, parent = null) { if (!node) return; const pos = positions[node.val]; if (parent) { const p = positions[parent.val]; const dx = pos.x - p.x, dy = pos.y - p.y; const dist = Math.sqrt(dx*dx + dy*dy); const offX = (dx/dist)*20, offY = (dy/dist)*20; layer.add(new Konva.Line({ points: [p.x + offX, p.y + offY, pos.x - offX, pos.y - offY], stroke: 'black', strokeWidth: 2 })); } const circle = new Konva.Circle({ x: pos.x, y: pos.y, radius: 20, fill: 'white', stroke: 'black', strokeWidth: 2 }); const text = new Konva.Text({ x: pos.x - 5, y: pos.y - 8, text: node.val.toString(), fontSize: 16, fill: 'black' }); layer.add(circle); layer.add(text); nodes[node.val] = { circle, text }; drawTree(node.left, node); drawTree(node.right, node); } //stack visualization setup 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 box = new Konva.Rect({ x: 50, y: 400 - stackVisual.length * 30, width: 40, height: 25, fill: '#89CFF0', stroke: 'black' }); const label = new Konva.Text({ x: 60, y: 405 - stackVisual.length * 30, text: val.toString(), fontSize: 16, fill: 'black' }); stackLayer.add(box); stackLayer.add(label); stackVisual.push({ box, label }); stackLayer.draw(); } function popStack() { const item = stackVisual.pop(); if (item) { item.box.destroy(); item.label.destroy(); stackLayer.draw(); } } function highlight(val, color = 'yellow') { const n = nodes[val]; if (n) { n.circle.fill(color); layer.draw(); } } function resetHighlights() { for (let v in nodes) { nodes[v].circle.fill('white'); } layer.draw(); } function updateStatus(current, reason) { document.getElementById('currentVal').innerText = current ?? 'None'; document.getElementById('reason').innerText = reason; } function logVisit(val) { document.getElementById('seq').innerText += ` ${val}`; } //traversal state let current = tree; const stack = []; let stepReady = true; let lastVisited = null; //final sequences for display const finalSeqs = { 'In-Order': '4 2 6 5 7 1 3 8 9', 'Pre-Order': '1 2 4 5 6 7 3 8 9', 'Post-Order': '4 6 7 5 2 9 8 3 1' }; //create tabs dynamically const modes = ['In-Order', 'Pre-Order', 'Post-Order']; let currentMode = modes[0]; 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.style.margin = '0 5px'; btn.onclick = () => { currentMode = mode; document.querySelectorAll('.tab-btn').forEach((b) => (b.style.fontWeight = 'normal')); btn.style.fontWeight = 'bold'; document.getElementById('final-seq').innerHTML = `Final Sequence (${mode}): ${finalSeqs[mode]}`; resetAll(); }; if (mode === currentMode) btn.style.fontWeight = 'bold'; tabsDiv.appendChild(btn); }); document.body.insertBefore(tabsDiv, document.getElementById('main-container')); document.getElementById('final-seq').innerHTML = `Final Sequence (${currentMode}): ${finalSeqs[currentMode]}`; //step functions for each traversal function stepInOrder() { if (current) { highlight(current.val, 'yellow'); updateStatus(current.val, `Push ${current.val} into stack`); stack.push(current); pushStack(current.val); current = current.left; stepReady = true; return; } if (stack.length > 0) { const peek = stack[stack.length - 1]; updateStatus(peek.val, `Peek stack (current = ${peek.val})`); current = stack.pop(); popStack(); updateStatus(current.val, `Visit ${current.val}`); highlight(current.val, 'orange'); logVisit(current.val); setTimeout(() => { resetHighlights(); current = current.right; stepReady = true; }, 500); } else { updateStatus('None', 'Done'); } } function stepPreOrder() { if (current) { updateStatus(current.val, `Visit ${current.val}`); highlight(current.val, 'orange'); logVisit(current.val); updateStatus(current.val, `Push ${current.val} into stack`); stack.push(current); pushStack(current.val); current = current.left; stepReady = true; return; } if (stack.length > 0) { const node = stack.pop(); popStack(); updateStatus(node.val, `Move to right of ${node.val}`); current = node.right; stepReady = true; } else { updateStatus('None', 'Done'); } } function stepPostOrder() { if (current) { highlight(current.val, 'yellow'); updateStatus(current.val, `Push ${current.val} into stack`); stack.push(current); pushStack(current.val); current = current.left; stepReady = true; return; } if (stack.length > 0) { const peek = stack[stack.length - 1]; if (peek.right && lastVisited !== peek.right) { updateStatus(peek.val, `Move to right of ${peek.val}`); current = peek.right; stepReady = true; } else { const node = stack.pop(); popStack(); lastVisited = node; updateStatus(node.val, `Visit ${node.val}`); highlight(node.val, 'orange'); logVisit(node.val); current = null; // avoid re-visiting the same node setTimeout(() => { resetHighlights(); stepReady = true; }, 500); } } else { updateStatus('None', 'Done'); } } //bind the step button 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(); }; //reset button and function function resetAll() { while (stackVisual.length) popStack(); stack.length = 0; current = tree; lastVisited = null; stepReady = true; document.getElementById('seq').innerText = ''; updateStatus('None', 'Ready'); resetHighlights(); } document.getElementById('resetBtn').onclick = resetAll; //initial render of the tree drawTree(tree); layer.draw();