update animation
This commit is contained in:
committed by
JamesFlare1212
parent
083ae76743
commit
643c7f1038
@@ -1,6 +1,6 @@
|
|||||||
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500&display=swap');
|
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500&display=swap');
|
||||||
|
|
||||||
/*base styling */
|
/* Base styling */
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: #f7f8fa;
|
background-color: #f7f8fa;
|
||||||
@@ -8,7 +8,7 @@ body {
|
|||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*layout containers */
|
/* Layout containers */
|
||||||
#main-container {
|
#main-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
@@ -36,7 +36,7 @@ body {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*stack title */
|
/* Stack title */
|
||||||
#stack-title {
|
#stack-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -44,7 +44,7 @@ body {
|
|||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*controls */
|
/* Controls */
|
||||||
#tabs {
|
#tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -100,7 +100,7 @@ button:active {
|
|||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*status & logs */
|
/* Status & logs */
|
||||||
#status,
|
#status,
|
||||||
#log,
|
#log,
|
||||||
#final-seq {
|
#final-seq {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Iterative Traversal (Step-by-Step)</title>
|
<title>In-Order Traversal (Step-by-Step)</title>
|
||||||
<script src="konva.js"></script>
|
<script src="konva.js"></script>
|
||||||
<link rel="stylesheet" href="traversal.css">
|
<link rel="stylesheet" href="traversal.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
// In-Order / Pre-Order / Post-Order Traversal Visualization (combined)
|
|
||||||
|
|
||||||
//canvas dimensions for tree visualization
|
// Canvas dimensions
|
||||||
const width = window.innerWidth * 0.9 * 0.75;
|
const width = window.innerWidth * 0.9 * 0.75;
|
||||||
const height = 600;
|
const height = 600;
|
||||||
|
|
||||||
//create the main stage and layer for the tree
|
// Create Konva stage & layer
|
||||||
const stage = new Konva.Stage({
|
const stage = new Konva.Stage({
|
||||||
container: 'tree-container',
|
container: 'tree-container',
|
||||||
width: width,
|
width: width,
|
||||||
@@ -13,20 +12,20 @@ const stage = new Konva.Stage({
|
|||||||
const layer = new Konva.Layer();
|
const layer = new Konva.Layer();
|
||||||
stage.add(layer);
|
stage.add(layer);
|
||||||
|
|
||||||
//adjusted positions: node 9 is left child of 8
|
// Node positions (9 as left child of 8)
|
||||||
const positions = {
|
const positions = {
|
||||||
1: { x: width / 2, y: 80 },
|
1: { x: width/2, y: 80 },
|
||||||
2: { x: width / 2 - 200, y: 180 },
|
2: { x: width/2 -200, y:180 },
|
||||||
3: { x: width / 2 + 200, y: 180 },
|
3: { x: width/2 +200, y:180 },
|
||||||
4: { x: width / 2 - 300, y: 280 },
|
4: { x: width/2 -300, y:280 },
|
||||||
5: { x: width / 2 - 100, y: 280 },
|
5: { x: width/2 -100, y:280 },
|
||||||
6: { x: width / 2 - 150, y: 380 },
|
6: { x: width/2 -150, y:380 },
|
||||||
7: { x: width / 2 - 50, y: 380 },
|
7: { x: width/2 -50, y:380 },
|
||||||
8: { x: width / 2 + 300, y: 280 },
|
8: { x: width/2 +300, y:280 },
|
||||||
9: { x: width / 2 + 250, y: 380 } // left child of 8
|
9: { x: width/2 +250, y:380 }
|
||||||
};
|
};
|
||||||
|
|
||||||
//tree structure
|
// Tree data
|
||||||
const tree = {
|
const tree = {
|
||||||
val: 1,
|
val: 1,
|
||||||
left: {
|
left: {
|
||||||
@@ -49,205 +48,185 @@ const tree = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//store node references for highlighting
|
// Store node visuals
|
||||||
const nodes = {};
|
const nodes = {};
|
||||||
|
|
||||||
//recursive tree draw with shortened lines
|
// Draw a single node
|
||||||
function drawTree(node, parent = null) {
|
function drawNode(x, y, label) {
|
||||||
if (!node) return;
|
const circle = new Konva.Circle({ x, y, radius: 20, fill: 'white', stroke: '#333', strokeWidth: 2 });
|
||||||
const pos = positions[node.val];
|
const text = new Konva.Text({ x: x - 5, y: y - 8, text: label.toString(), fontSize: 16, fill: '#333' });
|
||||||
if (parent) {
|
layer.add(circle, text);
|
||||||
const p = positions[parent.val];
|
nodes[label] = { circle, text };
|
||||||
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
|
// 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 stackStage = new Konva.Stage({ container: 'stack-visual', width: 200, height: 500 });
|
||||||
const stackLayer = new Konva.Layer();
|
const stackLayer = new Konva.Layer();
|
||||||
stackStage.add(stackLayer);
|
stackStage.add(stackLayer);
|
||||||
let stackVisual = [];
|
let stackVisual = [];
|
||||||
|
|
||||||
function pushStack(val) {
|
function pushStack(val) {
|
||||||
const box = new Konva.Rect({ x: 50, y: 400 - stackVisual.length * 30, width: 40, height: 25, fill: '#89CFF0', stroke: 'black' });
|
const y = 400 - stackVisual.length * 30;
|
||||||
const label = new Konva.Text({ x: 60, y: 405 - stackVisual.length * 30, text: val.toString(), fontSize: 16, fill: 'black' });
|
const box = new Konva.Rect({ x:50, y, width:40, height:25, fill:'#007bff', stroke:'#0056b3', cornerRadius:4 });
|
||||||
stackLayer.add(box);
|
const txt = new Konva.Text({ x:58, y: y+4, text: val.toString(), fontSize:14, fill:'#fff' });
|
||||||
stackLayer.add(label);
|
stackLayer.add(box, txt);
|
||||||
stackVisual.push({ box, label });
|
stackVisual.push({ box, txt });
|
||||||
stackLayer.draw();
|
stackLayer.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function popStack() {
|
function popStack() {
|
||||||
const item = stackVisual.pop();
|
const it = stackVisual.pop();
|
||||||
if (item) {
|
if (it) { it.box.destroy(); it.txt.destroy(); stackLayer.draw(); }
|
||||||
item.box.destroy();
|
|
||||||
item.label.destroy();
|
|
||||||
stackLayer.draw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlight(val, color = 'yellow') {
|
// Highlight current node
|
||||||
const n = nodes[val];
|
function highlight(val) {
|
||||||
if (n) {
|
for (const v in nodes) nodes[v].circle.fill('white');
|
||||||
n.circle.fill(color);
|
if (val != null && nodes[val]) nodes[val].circle.fill('#ffc107');
|
||||||
layer.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetHighlights() {
|
|
||||||
for (let v in nodes) {
|
|
||||||
nodes[v].circle.fill('white');
|
|
||||||
}
|
|
||||||
layer.draw();
|
layer.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStatus(current, reason) {
|
function updateStatus(current, reason) {
|
||||||
document.getElementById('currentVal').innerText = current ?? 'None';
|
document.getElementById('currentVal').innerText = current != null ? current : 'None';
|
||||||
document.getElementById('reason').innerText = reason;
|
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}`; }
|
||||||
|
|
||||||
function logVisit(val) {
|
// Traversal state
|
||||||
document.getElementById('seq').innerText += ` ${val}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
//traversal state
|
|
||||||
let current = tree;
|
let current = tree;
|
||||||
|
let lastVisited = null;
|
||||||
const stack = [];
|
const stack = [];
|
||||||
let stepReady = true;
|
let stepReady = true;
|
||||||
let lastVisited = null;
|
|
||||||
|
|
||||||
//final sequences for display
|
// Final sequences for display
|
||||||
const finalSeqs = {
|
const finalSeqs = {
|
||||||
'In-Order': '4 2 6 5 7 1 3 8 9',
|
'In-Order': '4 2 6 5 7 1 3 9 8',
|
||||||
'Pre-Order': '1 2 4 5 6 7 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'
|
'Post-Order': '4 6 7 5 2 9 8 3 1'
|
||||||
};
|
};
|
||||||
|
|
||||||
//create tabs dynamically
|
// Tabs
|
||||||
const modes = ['In-Order', 'Pre-Order', 'Post-Order'];
|
const modes = ['In-Order', 'Pre-Order', 'Post-Order'];
|
||||||
let currentMode = modes[0];
|
let currentMode = 'In-Order';
|
||||||
const tabsDiv = document.createElement('div');
|
const tabsDiv = document.createElement('div');
|
||||||
tabsDiv.id = 'tabs';
|
tabsDiv.id = 'tabs'; tabsDiv.style.textAlign = 'center'; tabsDiv.style.margin = '10px';
|
||||||
tabsDiv.style.textAlign = 'center';
|
modes.forEach(mode => {
|
||||||
tabsDiv.style.margin = '10px';
|
|
||||||
modes.forEach((mode) => {
|
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.innerText = mode;
|
btn.innerText = mode; btn.className = 'tab-btn';
|
||||||
btn.className = 'tab-btn';
|
|
||||||
btn.style.margin = '0 5px';
|
|
||||||
btn.onclick = () => {
|
btn.onclick = () => {
|
||||||
currentMode = mode;
|
currentMode = mode;
|
||||||
document.querySelectorAll('.tab-btn').forEach((b) => (b.style.fontWeight = 'normal'));
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||||
btn.style.fontWeight = 'bold';
|
btn.classList.add('active');
|
||||||
document.getElementById('final-seq').innerHTML = `<strong>Final Sequence (${mode}):</strong> ${finalSeqs[mode]}`;
|
// Update final sequence display
|
||||||
|
finalSeqEl.innerHTML = `<strong>Final Sequence (${mode}):</strong> ${finalSeqs[mode]}`;
|
||||||
resetAll();
|
resetAll();
|
||||||
};
|
};
|
||||||
if (mode === currentMode) btn.style.fontWeight = 'bold';
|
if (mode === currentMode) btn.classList.add('active');
|
||||||
tabsDiv.appendChild(btn);
|
tabsDiv.appendChild(btn);
|
||||||
});
|
});
|
||||||
document.body.insertBefore(tabsDiv, document.getElementById('main-container'));
|
document.body.insertBefore(tabsDiv, document.getElementById('controls'));
|
||||||
document.getElementById('final-seq').innerHTML = `<strong>Final Sequence (${currentMode}):</strong> ${finalSeqs[currentMode]}`;
|
|
||||||
|
|
||||||
//step functions for each traversal
|
//Initialize final sequence display
|
||||||
|
const finalSeqEl = document.getElementById('final-seq');
|
||||||
|
finalSeqEl.innerHTML = `<strong>Final Sequence (${currentMode}):</strong> ${finalSeqs[currentMode]}`;
|
||||||
|
|
||||||
|
// Step functions
|
||||||
function stepInOrder() {
|
function stepInOrder() {
|
||||||
if (current) {
|
if (current) {
|
||||||
highlight(current.val, 'yellow');
|
highlight(current.val);
|
||||||
updateStatus(current.val, `Push ${current.val} into stack`);
|
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);
|
stack.push(current); pushStack(current.val);
|
||||||
pushStack(current.val);
|
|
||||||
current = current.left;
|
current = current.left;
|
||||||
stepReady = true;
|
stepReady = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stack.length > 0) {
|
if (stack.length) {
|
||||||
const peek = stack[stack.length - 1];
|
current = stack.pop(); popStack();
|
||||||
updateStatus(peek.val, `Peek stack (current = ${peek.val})`);
|
highlight(current.val);
|
||||||
current = stack.pop();
|
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.`);
|
||||||
popStack();
|
|
||||||
updateStatus(current.val, `Visit ${current.val}`);
|
|
||||||
highlight(current.val, 'orange');
|
|
||||||
logVisit(current.val);
|
logVisit(current.val);
|
||||||
setTimeout(() => {
|
setTimeout(() => { current = current.right; stepReady = true; }, 500);
|
||||||
resetHighlights();
|
|
||||||
current = current.right;
|
|
||||||
stepReady = true;
|
|
||||||
}, 500);
|
|
||||||
} else {
|
} else {
|
||||||
updateStatus('None', 'Done');
|
highlight(null);
|
||||||
|
updateStatus(null, 'In-order traversal complete! All nodes have been visited.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stepPreOrder() {
|
function stepPreOrder() {
|
||||||
if (current) {
|
if (current) {
|
||||||
updateStatus(current.val, `Visit ${current.val}`);
|
highlight(current.val);
|
||||||
highlight(current.val, 'orange');
|
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);
|
logVisit(current.val);
|
||||||
updateStatus(current.val, `Push ${current.val} into stack`);
|
stack.push(current); pushStack(current.val);
|
||||||
stack.push(current);
|
|
||||||
pushStack(current.val);
|
|
||||||
current = current.left;
|
current = current.left;
|
||||||
stepReady = true;
|
stepReady = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stack.length > 0) {
|
if (stack.length) {
|
||||||
const node = stack.pop();
|
current = stack.pop(); popStack();
|
||||||
popStack();
|
highlight(current.val);
|
||||||
updateStatus(node.val, `Move to right of ${node.val}`);
|
updateStatus(current.val, `Finished left subtree of node ${current.val}. Now proceed to explore its right subtree.`);
|
||||||
current = node.right;
|
current = current.right;
|
||||||
stepReady = true;
|
|
||||||
} else {
|
} else {
|
||||||
updateStatus('None', 'Done');
|
highlight(null);
|
||||||
|
updateStatus(null, 'Pre-order traversal complete! All nodes have been visited.');
|
||||||
}
|
}
|
||||||
|
stepReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stepPostOrder() {
|
function stepPostOrder() {
|
||||||
if (current) {
|
if (current) {
|
||||||
highlight(current.val, 'yellow');
|
highlight(current.val);
|
||||||
updateStatus(current.val, `Push ${current.val} into stack`);
|
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);
|
stack.push(current); pushStack(current.val);
|
||||||
pushStack(current.val);
|
|
||||||
current = current.left;
|
current = current.left;
|
||||||
stepReady = true;
|
stepReady = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stack.length > 0) {
|
if (stack.length) {
|
||||||
const peek = stack[stack.length - 1];
|
const peek = stack[stack.length - 1];
|
||||||
if (peek.right && lastVisited !== peek.right) {
|
if (peek.right && lastVisited !== peek.right) {
|
||||||
updateStatus(peek.val, `Move to right of ${peek.val}`);
|
updateStatus(peek.val, `Left subtree of node ${peek.val} done. Now move to its right subtree to continue processing children first.`);
|
||||||
current = peek.right;
|
current = peek.right;
|
||||||
stepReady = true;
|
stepReady = true;
|
||||||
} else {
|
} else {
|
||||||
const node = stack.pop();
|
current = stack.pop(); popStack();
|
||||||
popStack();
|
highlight(current.val);
|
||||||
lastVisited = node;
|
updateStatus(current.val, `Both children of node ${current.val} have been visited. Now output node ${current.val} for post-order sequence.`);
|
||||||
updateStatus(node.val, `Visit ${node.val}`);
|
logVisit(current.val);
|
||||||
highlight(node.val, 'orange');
|
lastVisited = current;
|
||||||
logVisit(node.val);
|
current = null;
|
||||||
current = null; // avoid re-visiting the same node
|
stepReady = true;
|
||||||
setTimeout(() => {
|
|
||||||
resetHighlights();
|
|
||||||
stepReady = true;
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateStatus('None', 'Done');
|
highlight(null);
|
||||||
|
updateStatus(null, 'Post-order traversal complete! All nodes have been visited.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//bind the step button
|
// Handles step function
|
||||||
document.getElementById('stepBtn').onclick = () => {
|
document.getElementById('stepBtn').onclick = () => {
|
||||||
if (!stepReady) return;
|
if (!stepReady) return;
|
||||||
stepReady = false;
|
stepReady = false;
|
||||||
@@ -256,7 +235,8 @@ document.getElementById('stepBtn').onclick = () => {
|
|||||||
else if (currentMode === 'Post-Order') stepPostOrder();
|
else if (currentMode === 'Post-Order') stepPostOrder();
|
||||||
};
|
};
|
||||||
|
|
||||||
//reset button and function
|
// Clear/Reset
|
||||||
|
document.getElementById('resetBtn').onclick = resetAll;
|
||||||
function resetAll() {
|
function resetAll() {
|
||||||
while (stackVisual.length) popStack();
|
while (stackVisual.length) popStack();
|
||||||
stack.length = 0;
|
stack.length = 0;
|
||||||
@@ -264,12 +244,8 @@ function resetAll() {
|
|||||||
lastVisited = null;
|
lastVisited = null;
|
||||||
stepReady = true;
|
stepReady = true;
|
||||||
document.getElementById('seq').innerText = '';
|
document.getElementById('seq').innerText = '';
|
||||||
updateStatus('None', 'Ready');
|
updateStatus(null, 'Ready to begin traversal. Click "Step" to start.');
|
||||||
resetHighlights();
|
highlight(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('resetBtn').onclick = resetAll;
|
|
||||||
|
|
||||||
//initial render of the tree
|
|
||||||
drawTree(tree);
|
|
||||||
layer.draw();
|
layer.draw();
|
||||||
|
|||||||
Reference in New Issue
Block a user