Build your own dependency graph below. Add tasks (nodes) and dependencies (directed edges), then click Sort to run Kahn's algorithm and watch a valid ordering appear. Introduce a cycle and the algorithm will catch it.
<div class="hint">Build a dependency graph: type a node name and click <b>Add Node</b>, then choose two nodes and click <b>Add Edge</b> (arrow from A โ B means "A must come before B"). Click <b>Sort</b> to run Kahnโs algorithm.</div>
<div class="controls">
<div class="row">
<input id="nodeName" type="text" placeholder="Node name (e.g. A)" maxlength="12" />
<button id="addNode" type="button">Add Node</button>
</div>
<div class="row">
<select id="edgeFrom"></select>
<span class="arrow-label">โ</span>
<select id="edgeTo"></select>
<button id="addEdge" type="button">Add Edge</button>
</div>
<div class="row">
<button id="sortBtn" type="button" class="primary">Sort</button>
<button id="stepBtn" type="button">Step</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
<button id="exampleBtn" type="button" class="ghost">Load example</button>
</div>
</div>
<canvas id="canvas" width="560" height="220"></canvas>
<div class="result" id="result">Add nodes and edges, then click Sort or Step.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 4px; }
.hint { font-size: .85rem; color: #444; margin: 0 0 .6rem; line-height: 1.4; }
.controls { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .5rem; }
.row { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
input, select { font: 14px system-ui, sans-serif; padding: .3rem .5rem; border: 1px solid #adb1b8; border-radius: 6px; background: #fff; }
input { width: 130px; }
select { max-width: 110px; }
.arrow-label { font-weight: 700; color: #1d3557; }
button { font: 600 13px system-ui, sans-serif; padding: .3rem .75rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 6px; cursor: pointer; }
button.primary { background: #2a7ac5; border-color: #2a7ac5; }
button.ghost { background: #fff; color: #1d3557; }
canvas { display: block; width: 100%; border: 1px solid #cdd9e3; border-radius: 8px; background: #f8fafc; }
.result { font-size: .9rem; font-weight: 600; margin-top: .5rem; min-height: 1.5em; padding: .4rem .6rem; border-radius: 6px; background: #e8eef3; color: #1d3557; white-space: pre-wrap; }
.result.ok { background: #d4edda; color: #155724; }
.result.err { background: #f8d7da; color: #721c24; }
.result.step { background: #fff3cd; color: #856404; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Kahnโs algorithm: topological sort using in-degree counts and a queue.
// Nodes with in-degree 0 are processed first; each processed node reduces
// the in-degree of its successors. If all nodes are processed, we have a valid topological order. If not, a cycle was detected.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Graph state
let nodes = []; // [{id, label, x, y}]
let edges = []; // [{from, to}] (id references)
let nodeIdCounter = 0;
// Animation state for Kahnโs
let kahnState = null; // null = not running, else {inDeg, queue, order, processed, currentId, done, cycle}
// โโ Layout helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function layoutNodes() {
const W = canvas.width, H = canvas.height;
const n = nodes.length;
if (n === 0) return;
// Arrange in a circle with some padding
const cx = W / 2, cy = H / 2, r = Math.min(W, H) * 0.36;
nodes.forEach((node, i) => {
const angle = (2 * Math.PI * i / n) - Math.PI / 2;
node.x = cx + r * Math.cos(angle);
node.y = cy + r * Math.sin(angle);
});
}
// โโ Drawing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const NODE_R = 22;
const COLORS = {
default: { fill: '#e8eef3', stroke: '#1d3557', text: '#1d3557' },
queued: { fill: '#fff3cd', stroke: '#856404', text: '#856404' },
active: { fill: '#2a7ac5', stroke: '#1a5a95', text: '#fff' },
done: { fill: '#d4edda', stroke: '#155724', text: '#155724' },
cycle: { fill: '#f8d7da', stroke: '#721c24', text: '#721c24' },
};
function nodeState(node) {
if (!kahnState) return 'default';
const id = node.id;
if (kahnState.cycleSet && kahnState.cycleSet.has(id)) return 'cycle';
if (kahnState.processed.has(id)) return 'done';
if (id === kahnState.currentId) return 'active';
if (kahnState.queueSet && kahnState.queueSet.has(id)) return 'queued';
return 'default';
}
function drawArrow(x1, y1, x2, y2, color) {
const dx = x2 - x1, dy = y2 - y1;
const len = Math.hypot(dx, dy);
if (len < 1) return;
const ux = dx / len, uy = dy / len;
// Shorten to node edge
const sx = x1 + ux * NODE_R, sy = y1 + uy * NODE_R;
const ex = x2 - ux * NODE_R, ey = y2 - uy * NODE_R;
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.strokeStyle = color;
ctx.lineWidth = 1.8;
ctx.stroke();
// Arrowhead
const aw = 9, ah = 6;
const px = -uy, py = ux;
ctx.beginPath();
ctx.moveTo(ex, ey);
ctx.lineTo(ex - ux * aw + px * ah, ey - uy * aw + py * ah);
ctx.lineTo(ex - ux * aw - px * ah, ey - uy * aw - py * ah);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
function draw() {
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
// Edges
edges.forEach(e => {
const fn = nodes.find(n => n.id === e.from);
const tn = nodes.find(n => n.id === e.to);
if (!fn || !tn) return;
const edgeColor = '#6c757d';
drawArrow(fn.x, fn.y, tn.x, tn.y, edgeColor);
});
// Nodes
nodes.forEach(node => {
const s = COLORS[nodeState(node)];
ctx.beginPath();
ctx.arc(node.x, node.y, NODE_R, 0, 2 * Math.PI);
ctx.fillStyle = s.fill;
ctx.fill();
ctx.strokeStyle = s.stroke;
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = s.text;
ctx.font = 'bold 13px system-ui, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(node.label, node.x, node.y);
// In-degree badge
if (kahnState && !kahnState.done) {
const deg = kahnState.inDeg[node.id] ?? 0;
ctx.fillStyle = '#6c757d';
ctx.font = '10px system-ui, sans-serif';
ctx.fillText('โ' + deg, node.x, node.y + NODE_R + 10);
}
});
}
// โโ Select helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function refreshSelects() {
const from = document.getElementById('edgeFrom');
const to = document.getElementById('edgeTo');
[from, to].forEach(sel => {
const prev = sel.value;
sel.innerHTML = '';
nodes.forEach(n => {
const opt = document.createElement('option');
opt.value = n.id;
opt.textContent = n.label;
sel.appendChild(opt);
});
if (nodes.some(n => String(n.id) === prev)) sel.value = prev;
});
}
// โโ Graph mutations โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function addNode(label) {
if (!label) return;
if (nodes.some(n => n.label === label)) return;
nodes.push({ id: nodeIdCounter++, label, x: 0, y: 0 });
layoutNodes();
refreshSelects();
kahnState = null;
draw();
}
function addEdge(fromId, toId) {
fromId = Number(fromId); toId = Number(toId);
if (fromId === toId) return;
if (edges.some(e => e.from === fromId && e.to === toId)) return;
edges.push({ from: fromId, to: toId });
kahnState = null;
draw();
}
// โโ Kahnโs algorithm, step-by-step โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function initKahn() {
const inDeg = {};
const adj = {};
nodes.forEach(n => { inDeg[n.id] = 0; adj[n.id] = []; });
edges.forEach(e => { inDeg[e.to]++; adj[e.from].push(e.to); });
const queue = nodes.filter(n => inDeg[n.id] === 0).map(n => n.id);
const queueSet = new Set(queue);
kahnState = {
inDeg, adj,
queue: [...queue],
queueSet,
order: [],
processed: new Set(),
currentId: null,
done: false,
cycleSet: null,
};
}
function stepKahn() {
if (!kahnState) initKahn();
if (kahnState.done) return;
if (kahnState.queue.length === 0) {
// Cycle detected
const cycleNodes = nodes.filter(n => !kahnState.processed.has(n.id));
kahnState.cycleSet = new Set(cycleNodes.map(n => n.id));
kahnState.done = true;
const cycleLabels = cycleNodes.map(n => n.label).join(', ');
setResult(fmt(`Cycle detected! These nodes are stuck: %cycleLabels%
No valid topological order exists.`, { cycleLabels: cycleLabels }), 'err');
draw();
return;
}
const id = kahnState.queue.shift();
kahnState.queueSet.delete(id);
kahnState.processed.add(id);
kahnState.order.push(id);
kahnState.currentId = id;
// Reduce in-degrees of successors
kahnState.adj[id].forEach(succ => {
kahnState.inDeg[succ]--;
if (kahnState.inDeg[succ] === 0) {
kahnState.queue.push(succ);
kahnState.queueSet.add(succ);
}
});
const orderLabels = kahnState.order.map(i => nodes.find(n => n.id === i).label).join(' โ ');
if (kahnState.processed.size === nodes.length) {
kahnState.done = true;
kahnState.currentId = null;
setResult(fmt(`โ Topological order: %orderLabels%`, { orderLabels: orderLabels }), 'ok');
console.log(`Topological order:`, orderLabels);
} else {
const remaining = nodes.length - kahnState.processed.size;
setResult(fmt(`Step: processed %label%
Order so far: %orderLabels%
Queue: [%map%]
%remaining% node(s) remaining.`, { label: nodes.find(n => n.id === id).label, orderLabels: orderLabels, map: kahnState.queue.map(i => nodes.find(n => n.id === i).label).join(', '), remaining: remaining }), 'step');
}
draw();
}
function runKahn() {
initKahn();
// Run all steps at once
let guard = 0;
while (!kahnState.done && guard++ < 1000) stepKahn();
}
function setResult(text, cls) {
const el = document.getElementById('result');
el.textContent = text;
el.className = 'result ' + (cls || '');
}
// โโ Wiring โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('addNode').addEventListener('click', () => {
const inp = document.getElementById('nodeName');
addNode(inp.value.trim());
inp.value = '';
});
document.getElementById('nodeName').addEventListener('keydown', e => {
if (e.key === 'Enter') document.getElementById('addNode').click();
});
document.getElementById('addEdge').addEventListener('click', () => {
const from = document.getElementById('edgeFrom').value;
const to = document.getElementById('edgeTo').value;
addEdge(from, to);
});
document.getElementById('sortBtn').addEventListener('click', () => {
if (nodes.length === 0) { setResult(`Add at least one node first.`, 'err'); return; }
runKahn();
});
document.getElementById('stepBtn').addEventListener('click', () => {
if (nodes.length === 0) { setResult(`Add at least one node first.`, 'err'); return; }
stepKahn();
});
document.getElementById('resetBtn').addEventListener('click', () => {
nodes = []; edges = []; nodeIdCounter = 0; kahnState = null;
refreshSelects();
setResult(`Add nodes and edges, then click Sort or Step.`, '');
draw();
});
document.getElementById('exampleBtn').addEventListener('click', () => {
// Course prerequisite example: MathโAlgoโDP, MathโDSโAlgo, ProgโDS
nodes = []; edges = []; nodeIdCounter = 0; kahnState = null;
['Math','Prog','DS','Algo','DP'].forEach(l => addNode(l));
[['Math','Algo'],['Math','DS'],['Prog','DS'],['DS','Algo'],['Algo','DP']].forEach(([a,b]) => {
addEdge(nodes.find(n => n.label===a).id, nodes.find(n => n.label===b).id);
});
setResult(`Example loaded: course prerequisites. Click Sort to order them.`, '');
});
// Initial draw
draw();
<div class="hint">Build a dependency graph: type a node name and click <b>Add Node</b>, then choose two nodes and click <b>Add Edge</b> (arrow from A โ B means "A must come before B"). Click <b>Sort</b> to run Kahnโs algorithm.</div>
<div class="controls">
<div class="row">
<input id="nodeName" type="text" placeholder="Node name (e.g. A)" maxlength="12" />
<button id="addNode" type="button">Add Node</button>
</div>
<div class="row">
<select id="edgeFrom"></select>
<span class="arrow-label">โ</span>
<select id="edgeTo"></select>
<button id="addEdge" type="button">Add Edge</button>
</div>
<div class="row">
<button id="sortBtn" type="button" class="primary">Sort</button>
<button id="stepBtn" type="button">Step</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
<button id="exampleBtn" type="button" class="ghost">Load example</button>
</div>
</div>
<canvas id="canvas" width="560" height="220"></canvas>
<div class="result" id="result">Add nodes and edges, then click Sort or Step.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 4px; }
.hint { font-size: .85rem; color: #444; margin: 0 0 .6rem; line-height: 1.4; }
.controls { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .5rem; }
.row { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
input, select { font: 14px system-ui, sans-serif; padding: .3rem .5rem; border: 1px solid #adb1b8; border-radius: 6px; background: #fff; }
input { width: 130px; }
select { max-width: 110px; }
.arrow-label { font-weight: 700; color: #1d3557; }
button { font: 600 13px system-ui, sans-serif; padding: .3rem .75rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 6px; cursor: pointer; }
button.primary { background: #2a7ac5; border-color: #2a7ac5; }
button.ghost { background: #fff; color: #1d3557; }
canvas { display: block; width: 100%; border: 1px solid #cdd9e3; border-radius: 8px; background: #f8fafc; }
.result { font-size: .9rem; font-weight: 600; margin-top: .5rem; min-height: 1.5em; padding: .4rem .6rem; border-radius: 6px; background: #e8eef3; color: #1d3557; white-space: pre-wrap; }
.result.ok { background: #d4edda; color: #155724; }
.result.err { background: #f8d7da; color: #721c24; }
.result.step { background: #fff3cd; color: #856404; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Kahnโs algorithm: topological sort using in-degree counts and a queue.
// Nodes with in-degree 0 are processed first; each processed node reduces
// the in-degree of its successors. If all nodes are processed, we have a valid topological order. If not, a cycle was detected.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Graph state
let nodes = []; // [{id, label, x, y}]
let edges = []; // [{from, to}] (id references)
let nodeIdCounter = 0;
// Animation state for Kahnโs
let kahnState = null; // null = not running, else {inDeg, queue, order, processed, currentId, done, cycle}
// โโ Layout helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function layoutNodes() {
const W = canvas.width, H = canvas.height;
const n = nodes.length;
if (n === 0) return;
// Arrange in a circle with some padding
const cx = W / 2, cy = H / 2, r = Math.min(W, H) * 0.36;
nodes.forEach((node, i) => {
const angle = (2 * Math.PI * i / n) - Math.PI / 2;
node.x = cx + r * Math.cos(angle);
node.y = cy + r * Math.sin(angle);
});
}
// โโ Drawing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const NODE_R = 22;
const COLORS = {
default: { fill: '#e8eef3', stroke: '#1d3557', text: '#1d3557' },
queued: { fill: '#fff3cd', stroke: '#856404', text: '#856404' },
active: { fill: '#2a7ac5', stroke: '#1a5a95', text: '#fff' },
done: { fill: '#d4edda', stroke: '#155724', text: '#155724' },
cycle: { fill: '#f8d7da', stroke: '#721c24', text: '#721c24' },
};
function nodeState(node) {
if (!kahnState) return 'default';
const id = node.id;
if (kahnState.cycleSet && kahnState.cycleSet.has(id)) return 'cycle';
if (kahnState.processed.has(id)) return 'done';
if (id === kahnState.currentId) return 'active';
if (kahnState.queueSet && kahnState.queueSet.has(id)) return 'queued';
return 'default';
}
function drawArrow(x1, y1, x2, y2, color) {
const dx = x2 - x1, dy = y2 - y1;
const len = Math.hypot(dx, dy);
if (len < 1) return;
const ux = dx / len, uy = dy / len;
// Shorten to node edge
const sx = x1 + ux * NODE_R, sy = y1 + uy * NODE_R;
const ex = x2 - ux * NODE_R, ey = y2 - uy * NODE_R;
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.strokeStyle = color;
ctx.lineWidth = 1.8;
ctx.stroke();
// Arrowhead
const aw = 9, ah = 6;
const px = -uy, py = ux;
ctx.beginPath();
ctx.moveTo(ex, ey);
ctx.lineTo(ex - ux * aw + px * ah, ey - uy * aw + py * ah);
ctx.lineTo(ex - ux * aw - px * ah, ey - uy * aw - py * ah);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
function draw() {
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
// Edges
edges.forEach(e => {
const fn = nodes.find(n => n.id === e.from);
const tn = nodes.find(n => n.id === e.to);
if (!fn || !tn) return;
const edgeColor = '#6c757d';
drawArrow(fn.x, fn.y, tn.x, tn.y, edgeColor);
});
// Nodes
nodes.forEach(node => {
const s = COLORS[nodeState(node)];
ctx.beginPath();
ctx.arc(node.x, node.y, NODE_R, 0, 2 * Math.PI);
ctx.fillStyle = s.fill;
ctx.fill();
ctx.strokeStyle = s.stroke;
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = s.text;
ctx.font = 'bold 13px system-ui, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(node.label, node.x, node.y);
// In-degree badge
if (kahnState && !kahnState.done) {
const deg = kahnState.inDeg[node.id] ?? 0;
ctx.fillStyle = '#6c757d';
ctx.font = '10px system-ui, sans-serif';
ctx.fillText('โ' + deg, node.x, node.y + NODE_R + 10);
}
});
}
// โโ Select helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function refreshSelects() {
const from = document.getElementById('edgeFrom');
const to = document.getElementById('edgeTo');
[from, to].forEach(sel => {
const prev = sel.value;
sel.innerHTML = '';
nodes.forEach(n => {
const opt = document.createElement('option');
opt.value = n.id;
opt.textContent = n.label;
sel.appendChild(opt);
});
if (nodes.some(n => String(n.id) === prev)) sel.value = prev;
});
}
// โโ Graph mutations โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function addNode(label) {
if (!label) return;
if (nodes.some(n => n.label === label)) return;
nodes.push({ id: nodeIdCounter++, label, x: 0, y: 0 });
layoutNodes();
refreshSelects();
kahnState = null;
draw();
}
function addEdge(fromId, toId) {
fromId = Number(fromId); toId = Number(toId);
if (fromId === toId) return;
if (edges.some(e => e.from === fromId && e.to === toId)) return;
edges.push({ from: fromId, to: toId });
kahnState = null;
draw();
}
// โโ Kahnโs algorithm, step-by-step โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function initKahn() {
const inDeg = {};
const adj = {};
nodes.forEach(n => { inDeg[n.id] = 0; adj[n.id] = []; });
edges.forEach(e => { inDeg[e.to]++; adj[e.from].push(e.to); });
const queue = nodes.filter(n => inDeg[n.id] === 0).map(n => n.id);
const queueSet = new Set(queue);
kahnState = {
inDeg, adj,
queue: [...queue],
queueSet,
order: [],
processed: new Set(),
currentId: null,
done: false,
cycleSet: null,
};
}
function stepKahn() {
if (!kahnState) initKahn();
if (kahnState.done) return;
if (kahnState.queue.length === 0) {
// Cycle detected
const cycleNodes = nodes.filter(n => !kahnState.processed.has(n.id));
kahnState.cycleSet = new Set(cycleNodes.map(n => n.id));
kahnState.done = true;
const cycleLabels = cycleNodes.map(n => n.label).join(', ');
setResult(fmt(`Cycle detected! These nodes are stuck: %cycleLabels%
No valid topological order exists.`, { cycleLabels: cycleLabels }), 'err');
draw();
return;
}
const id = kahnState.queue.shift();
kahnState.queueSet.delete(id);
kahnState.processed.add(id);
kahnState.order.push(id);
kahnState.currentId = id;
// Reduce in-degrees of successors
kahnState.adj[id].forEach(succ => {
kahnState.inDeg[succ]--;
if (kahnState.inDeg[succ] === 0) {
kahnState.queue.push(succ);
kahnState.queueSet.add(succ);
}
});
const orderLabels = kahnState.order.map(i => nodes.find(n => n.id === i).label).join(' โ ');
if (kahnState.processed.size === nodes.length) {
kahnState.done = true;
kahnState.currentId = null;
setResult(fmt(`โ Topological order: %orderLabels%`, { orderLabels: orderLabels }), 'ok');
console.log(`Topological order:`, orderLabels);
} else {
const remaining = nodes.length - kahnState.processed.size;
setResult(fmt(`Step: processed %label%
Order so far: %orderLabels%
Queue: [%map%]
%remaining% node(s) remaining.`, { label: nodes.find(n => n.id === id).label, orderLabels: orderLabels, map: kahnState.queue.map(i => nodes.find(n => n.id === i).label).join(', '), remaining: remaining }), 'step');
}
draw();
}
function runKahn() {
initKahn();
// Run all steps at once
let guard = 0;
while (!kahnState.done && guard++ < 1000) stepKahn();
}
function setResult(text, cls) {
const el = document.getElementById('result');
el.textContent = text;
el.className = 'result ' + (cls || '');
}
// โโ Wiring โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('addNode').addEventListener('click', () => {
const inp = document.getElementById('nodeName');
addNode(inp.value.trim());
inp.value = '';
});
document.getElementById('nodeName').addEventListener('keydown', e => {
if (e.key === 'Enter') document.getElementById('addNode').click();
});
document.getElementById('addEdge').addEventListener('click', () => {
const from = document.getElementById('edgeFrom').value;
const to = document.getElementById('edgeTo').value;
addEdge(from, to);
});
document.getElementById('sortBtn').addEventListener('click', () => {
if (nodes.length === 0) { setResult(`Add at least one node first.`, 'err'); return; }
runKahn();
});
document.getElementById('stepBtn').addEventListener('click', () => {
if (nodes.length === 0) { setResult(`Add at least one node first.`, 'err'); return; }
stepKahn();
});
document.getElementById('resetBtn').addEventListener('click', () => {
nodes = []; edges = []; nodeIdCounter = 0; kahnState = null;
refreshSelects();
setResult(`Add nodes and edges, then click Sort or Step.`, '');
draw();
});
document.getElementById('exampleBtn').addEventListener('click', () => {
// Course prerequisite example: MathโAlgoโDP, MathโDSโAlgo, ProgโDS
nodes = []; edges = []; nodeIdCounter = 0; kahnState = null;
['Math','Prog','DS','Algo','DP'].forEach(l => addNode(l));
[['Math','Algo'],['Math','DS'],['Prog','DS'],['DS','Algo'],['Algo','DP']].forEach(([a,b]) => {
addEdge(nodes.find(n => n.label===a).id, nodes.find(n => n.label===b).id);
});
setResult(`Example loaded: course prerequisites. Click Sort to order them.`, '');
});
// Initial draw
draw();
Comments
Loading comments...