diff --git a/src/components/dsa/GraphVisualizer.jsx b/src/components/dsa/GraphVisualizer.jsx
new file mode 100644
index 0000000..2dada1e
--- /dev/null
+++ b/src/components/dsa/GraphVisualizer.jsx
@@ -0,0 +1,254 @@
+
+import React, { useState, useEffect, useRef, useMemo } from 'react';
+import { Button } from '@/components/ui/button';
+import { Switch } from '@/components/ui/switch';
+import { Move, PlusCircle, Network, Trash2, StepBack, StepForward, Play, Pause } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import * as algorithms from '@/lib/dsaAlgorithms';
+
+const GraphVisualizer = ({ algorithmName, isPlaying, setIsPlaying, speed, onFinished, darkMode }) => {
+ const [nodes, setNodes] = useState([]);
+ const [edges, setEdges] = useState([]);
+ const [isDirected, setIsDirected] = useState(false);
+ const [isWeighted, setIsWeighted] = useState(false);
+ const [editMode, setEditMode] = useState('move');
+ const [history, setHistory] = useState([]);
+ const [currentStep, setCurrentStep] = useState(-1);
+ const [startNode, setStartNode] = useState(0);
+ const [endNode, setEndNode] = useState(null);
+ const [draggingNode, setDraggingNode] = useState(null);
+ const [edgeStartNode, setEdgeStartNode] = useState(null);
+ const containerRef = useRef(null);
+ const animationRef = useRef(null);
+
+ const visualState = useMemo(() => {
+ const visited = new Set();
+ const path = new Set();
+ const dists = {};
+ let activeLink = null;
+
+ if (currentStep >= 0 && currentStep < history.length) {
+ for (let i = 0; i <= currentStep; i++) {
+ const step = history[i];
+ if (step.type === 'visit' || step.type === 'visit-node') {
+ visited.add(step.node);
+ } else if (step.type === 'path') {
+ step.path.forEach(n => path.add(n));
+ } else if (step.type === 'update-dist') {
+ dists[step.node] = step.dist;
+ }
+ if (i === currentStep) {
+ if (step.type === 'traverse' || step.type === 'check-edge') {
+ activeLink = { from: step.from, to: step.to };
+ }
+ }
+ }
+ }
+ return { visited, path, dists, activeLink };
+ }, [currentStep, history]);
+
+ useEffect(() => { resetGraph(); }, []);
+
+ useEffect(() => {
+ if (!isPlaying && nodes.length > 0) {
+ calculateAlgorithmSteps();
+ }
+ }, [nodes, edges, startNode, endNode, algorithmName, isDirected]);
+
+ const calculateAlgorithmSteps = () => {
+ const numNodes = Math.max(...nodes.map(n => n.id), -1) + 1;
+ const adj = Array.from({ length: numNodes }, () => []);
+ edges.forEach(edge => {
+ if (nodes.find(n => n.id === edge.from) && nodes.find(n => n.id === edge.to)) {
+ adj[edge.from].push({ to: edge.to, weight: edge.weight });
+ if (!isDirected) adj[edge.to].push({ to: edge.from, weight: edge.weight });
+ }
+ });
+ if (!nodes.find(n => n.id === startNode)) return;
+ let actualEnd = endNode;
+ if (endNode === null && nodes.length > 0) actualEnd = nodes[nodes.length - 1].id;
+
+ const algoFunc = algorithms[`generate${algorithmName}Steps`];
+ if (algoFunc) {
+ let steps = [];
+ if (algorithmName === 'AStar') {
+ const nodeObjMap = nodes.reduce((acc, n) => ({ ...acc, [n.id]: n }), {});
+ steps = algoFunc(numNodes, adj, startNode, actualEnd, nodeObjMap);
+ } else {
+ steps = algoFunc(numNodes, adj, startNode, actualEnd);
+ }
+ setHistory(steps);
+ }
+ };
+
+ useEffect(() => {
+ if (isPlaying) {
+ const delay = Math.max(50, 1000 - (speed * 9));
+ animationRef.current = setInterval(() => {
+ setCurrentStep(prev => {
+ if (prev < history.length - 1) return prev + 1;
+ setIsPlaying(false);
+ if (onFinished) onFinished();
+ return prev;
+ });
+ }, delay);
+ } else {
+ clearInterval(animationRef.current);
+ }
+ return () => clearInterval(animationRef.current);
+ }, [isPlaying, history.length, speed]);
+
+ const resetGraph = () => {
+ setIsPlaying(false);
+ setCurrentStep(-1);
+ const width = containerRef.current ? containerRef.current.clientWidth : 800;
+ const height = 400;
+ const numNodes = 10;
+ const newNodes = [];
+ for (let i = 0; i < numNodes; i++) newNodes.push({ id: i, x: Math.random() * (width - 100) + 50, y: Math.random() * (height - 100) + 50 });
+ const newEdges = [];
+ for (let i = 1; i < numNodes; i++) {
+ const target = Math.floor(Math.random() * i);
+ const weight = Math.floor(Math.random() * 20) + 1;
+ newEdges.push({ from: i, to: target, weight });
+ }
+ for (let i = 0; i < numNodes / 2; i++) {
+ const u = Math.floor(Math.random() * numNodes);
+ const v = Math.floor(Math.random() * numNodes);
+ if (u !== v && !newEdges.find(e => (e.from === u && e.to === v) || (e.from === v && e.to === u))) {
+ newEdges.push({ from: u, to: v, weight: Math.floor(Math.random() * 20) + 1 });
+ }
+ }
+ setNodes(newNodes);
+ setEdges(newEdges);
+ setStartNode(0);
+ setEndNode(numNodes - 1);
+ };
+
+ const handleMouseDown = (e, nodeId) => {
+ if (isPlaying) return;
+ e.stopPropagation();
+ if (editMode === 'move' && nodeId !== undefined) setDraggingNode(nodeId);
+ else if (editMode === 'add-edge' && nodeId !== undefined) {
+ if (edgeStartNode === null) setEdgeStartNode(nodeId);
+ else {
+ if (edgeStartNode !== nodeId) {
+ const weight = isWeighted ? Math.floor(Math.random() * 20) + 1 : 1;
+ const exists = edges.some(edge => (edge.from === edgeStartNode && edge.to === nodeId) || (!isDirected && edge.from === nodeId && edge.to === edgeStartNode));
+ if (!exists) setEdges(prev => [...prev, { from: edgeStartNode, to: nodeId, weight }]);
+ }
+ setEdgeStartNode(null);
+ }
+ } else if (editMode === 'delete' && nodeId !== undefined) {
+ setNodes(prev => prev.filter(n => n.id !== nodeId));
+ setEdges(prev => prev.filter(e => e.from !== nodeId && e.to !== nodeId));
+ if (startNode === nodeId) setStartNode(nodes.length > 0 ? nodes[0].id : null);
+ }
+ };
+
+ const handleCanvasClick = (e) => {
+ if (isPlaying) return;
+ if (editMode === 'add-node') {
+ const rect = containerRef.current.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ const newId = nodes.length > 0 ? Math.max(...nodes.map(n => n.id)) + 1 : 0;
+ setNodes(prev => [...prev, { id: newId, x, y }]);
+ }
+ };
+
+ const handleMouseMove = (e) => {
+ if (draggingNode === null || !containerRef.current) return;
+ const rect = containerRef.current.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+ setNodes(prev => prev.map(n => n.id === draggingNode ? { ...n, x, y } : n));
+ };
+
+ const handleMouseUp = () => setDraggingNode(null);
+
+ const handleNodeContext = (e, nodeId) => {
+ e.preventDefault();
+ if (editMode === 'move') { setStartNode(nodeId); setCurrentStep(-1); }
+ };
+
+ return (
+
+
+ {/* Graph Toolbar Buttons: Added explicit colors for visibility */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Start: {startNode}
+
End: {endNode ?? 'Last'}
+
{editMode === 'move' ? "Drag nodes. Right-click to set Start." : "Click to edit."}
+
+
+
+
+
+
+ );
+};
+
+export default GraphVisualizer;
diff --git a/src/components/dsa/SortingVisualizer.jsx b/src/components/dsa/SortingVisualizer.jsx
new file mode 100644
index 0000000..e5a7764
--- /dev/null
+++ b/src/components/dsa/SortingVisualizer.jsx
@@ -0,0 +1,120 @@
+
+import React, { useState, useEffect, useRef } from 'react';
+import * as algorithms from '@/lib/dsaAlgorithms';
+import { cn } from '@/lib/utils';
+
+const SortingVisualizer = ({ array, algorithmName, isPlaying, speed, onFinished, className, searchTarget }) => {
+ const [displayArray, setDisplayArray] = useState([...array]);
+ const timeoutsRef = useRef([]);
+
+ const showLabels = array.length <= 35;
+
+ useEffect(() => {
+ setDisplayArray([...array]);
+ clearAllTimeouts();
+ }, [array]);
+
+ useEffect(() => {
+ if (isPlaying) {
+ runAlgorithm();
+ } else {
+ clearAllTimeouts();
+ }
+ return () => clearAllTimeouts();
+ }, [isPlaying, algorithmName]);
+
+ const clearAllTimeouts = () => {
+ timeoutsRef.current.forEach(clearTimeout);
+ timeoutsRef.current = [];
+ };
+
+ const runAlgorithm = () => {
+ clearAllTimeouts();
+ let steps = [];
+ const algoFunc = algorithms[`generate${algorithmName}Steps`];
+ if (!algoFunc) return;
+
+ // Pass searchTarget if it's a search algorithm
+ if (algorithmName.includes('Search')) {
+ steps = algoFunc(array, searchTarget);
+ } else {
+ steps = algoFunc(array);
+ }
+
+ animate(steps);
+ };
+
+ const animate = (steps) => {
+ const delay = Math.max(1, 100 - speed);
+ steps.forEach((step, index) => {
+ const timeoutId = setTimeout(() => {
+ setDisplayArray(prev => {
+ // Use a new array reference to trigger re-renders
+ const newArr = [...prev];
+ if (step.type === 'swap') {
+ newArr[step.indices[0]] = step.values[0];
+ newArr[step.indices[1]] = step.values[1];
+ } else if (step.type === 'overwrite') {
+ newArr[step.indices[0]] = step.value;
+ }
+ return newArr;
+ });
+
+ const bars = document.getElementsByClassName(`bar-${className}`);
+ if(bars.length > 0) {
+ if (step.type === 'compare') {
+ step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '#ef4444' });
+ } else if (step.type === 'swap' || step.type === 'overwrite') {
+ step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '#eab308' });
+ } else if (step.type === 'sorted' || step.type === 'found') {
+ step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '#10b981' });
+ } else if (step.type === 'revert') {
+ step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '' });
+ } else if (step.type === 'discard') {
+ // Gray out discarded parts of the array for Binary Search
+ if (step.range) {
+ for(let i=step.range[0]; i<=step.range[1]; i++) {
+ if(bars[i]) bars[i].style.opacity = '0.2';
+ }
+ }
+ }
+ }
+
+ if (index === steps.length - 1) {
+ if (onFinished) onFinished();
+ }
+ }, index * delay);
+ timeoutsRef.current.push(timeoutId);
+ });
+ };
+
+ return (
+
+ {displayArray.map((val, idx) => (
+
+ {showLabels && (
+
+ {val}
+
+ )}
+
+
+ ))}
+
+ );
+};
+
+export default SortingVisualizer;
diff --git a/src/components/dsa/StackVisualizer.jsx b/src/components/dsa/StackVisualizer.jsx
new file mode 100644
index 0000000..60ab920
--- /dev/null
+++ b/src/components/dsa/StackVisualizer.jsx
@@ -0,0 +1,118 @@
+
+import React, { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { ArrowDown, ArrowUp, Trash2, Layers } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { motion, AnimatePresence } from 'framer-motion';
+
+const StackVisualizer = ({ darkMode }) => {
+ const [stack, setStack] = useState([]);
+ const [inputValue, setInputValue] = useState('');
+ const [message, setMessage] = useState('Stack is empty');
+
+ const handlePush = () => {
+ if (!inputValue) return;
+ if (stack.length >= 10) {
+ setMessage('Stack Overflow! Max size reached.');
+ return;
+ }
+ const newVal = inputValue;
+ setStack(prev => [...prev, newVal]);
+ setInputValue('');
+ setMessage(`Pushed ${newVal} onto the stack`);
+ };
+
+ const handlePop = () => {
+ if (stack.length === 0) {
+ setMessage('Stack Underflow! Stack is empty.');
+ return;
+ }
+ const popped = stack[stack.length - 1];
+ setStack(prev => prev.slice(0, -1));
+ setMessage(`Popped ${popped} from the stack`);
+ };
+
+ const handlePeek = () => {
+ if (stack.length === 0) {
+ setMessage('Stack is empty');
+ } else {
+ setMessage(`Top element is ${stack[stack.length - 1]}`);
+ }
+ };
+
+ const clearStack = () => {
+ setStack([]);
+ setMessage('Stack cleared');
+ };
+
+ return (
+
+
+
+
setInputValue(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && handlePush()}
+ />
+
+
+
+
+
+
+ {message}
+
+
+
+
+ {/* Stack Container */}
+
+
+ {stack.map((item, index) => (
+
+ {item}
+ {index === stack.length - 1 && (
+
+ ← Top
+
+ )}
+
+ ))}
+
+ {stack.length === 0 && (
+
+ Empty
+
+ )}
+
+
+
+
+ LIFO (Last-In, First-Out) Principle
+
+
+ );
+};
+
+export default StackVisualizer;
diff --git a/src/data/contributors.js b/src/data/contributors.js
index 1e75b78..e475b3a 100644
--- a/src/data/contributors.js
+++ b/src/data/contributors.js
@@ -29,5 +29,12 @@ export const dsaContributors = [
role: "Backend Architecture",
github: "https://github.com/jamesw",
avatar: "https://images.unsplash.com/photo-1599566150163-29194dcaad36?w=150&h=150&fit=crop&crop=faces"
+ },
+ {
+ name: "Jeevan vishnu",
+ role: "Full Stack Developer",
+ github: "https://github.com/jeevanvishnu",
+ avatar: "https://github.com/jeevanvishnu.png"
}
+
];
diff --git a/src/lib/dsaAlgorithms.js b/src/lib/dsaAlgorithms.js
index f6a4286..0627210 100644
--- a/src/lib/dsaAlgorithms.js
+++ b/src/lib/dsaAlgorithms.js
@@ -31,11 +31,11 @@ export const generateBubbleSortSteps = (array) => {
// Mark as sorted
steps.push({ type: 'sorted', indices: [n - i - 1] });
if (!swapped) {
- // Mark remaining as sorted
- for(let k=0; k < n-i-1; k++) {
- steps.push({ type: 'sorted', indices: [k] });
- }
- break;
+ // Mark remaining as sorted
+ for (let k = 0; k < n - i - 1; k++) {
+ steps.push({ type: 'sorted', indices: [k] });
+ }
+ break;
}
}
steps.push({ type: 'sorted', indices: [0] }); // First element is sorted
@@ -88,14 +88,14 @@ export const generateInsertionSortSteps = (array) => {
steps.push({ type: 'compare', indices: [j, j + 1] });
steps.push({ type: 'overwrite', indices: [j + 1], value: arr[j] }); // Shift
arr[j + 1] = arr[j];
- steps.push({ type: 'revert', indices: [j, j+1] });
+ steps.push({ type: 'revert', indices: [j, j + 1] });
j = j - 1;
}
steps.push({ type: 'overwrite', indices: [j + 1], value: key }); // Insert
arr[j + 1] = key;
-
+
// Mark all up to i as sorted (conceptually)
- for(let k=0; k<=i; k++) steps.push({ type: 'sorted', indices: [k] });
+ for (let k = 0; k <= i; k++) steps.push({ type: 'sorted', indices: [k] });
}
return steps;
};
@@ -132,18 +132,18 @@ function doMerge(mainArray, startIdx, middleIdx, endIdx, auxArray, steps) {
steps.push({ type: 'overwrite', indices: [k], value: auxArray[j] });
mainArray[k++] = auxArray[j++];
}
- steps.push({ type: 'revert', indices: [i-1, j-1] }); // Rough revert for visualization
- steps.push({ type: 'sorted', indices: [k-1] }); // Mark placed items as sorted (temporarily)
+ steps.push({ type: 'revert', indices: [i - 1, j - 1] }); // Rough revert for visualization
+ steps.push({ type: 'sorted', indices: [k - 1] }); // Mark placed items as sorted (temporarily)
}
while (i <= middleIdx) {
steps.push({ type: 'overwrite', indices: [k], value: auxArray[i] });
mainArray[k++] = auxArray[i++];
- steps.push({ type: 'sorted', indices: [k-1] });
+ steps.push({ type: 'sorted', indices: [k - 1] });
}
while (j <= endIdx) {
steps.push({ type: 'overwrite', indices: [k], value: auxArray[j] });
mainArray[k++] = auxArray[j++];
- steps.push({ type: 'sorted', indices: [k-1] });
+ steps.push({ type: 'sorted', indices: [k - 1] });
}
}
@@ -162,7 +162,7 @@ function quickSortHelper(arr, low, high, steps) {
quickSortHelper(arr, low, pi - 1, steps);
quickSortHelper(arr, pi + 1, high, steps);
} else if (low === high) {
- steps.push({ type: 'sorted', indices: [low] });
+ steps.push({ type: 'sorted', indices: [low] });
}
}
@@ -179,7 +179,7 @@ function partition(arr, low, high, steps) {
swap(arr, i, j);
steps.push({ type: 'revert', indices: [i, j] });
} else {
- steps.push({ type: 'revert', indices: [j] });
+ steps.push({ type: 'revert', indices: [j] });
}
}
steps.push({ type: 'swap', indices: [i + 1, high], values: [arr[high], arr[i + 1]] });
@@ -192,236 +192,236 @@ function partition(arr, low, high, steps) {
// --- Searching Algorithms ---
export const generateLinearSearchSteps = (array, target) => {
- const steps = [];
- for (let i = 0; i < array.length; i++) {
- steps.push({ type: 'compare', indices: [i] });
- if (array[i] === target) {
- steps.push({ type: 'found', indices: [i] });
- return steps;
- }
- steps.push({ type: 'visited', indices: [i] });
+ const steps = [];
+ for (let i = 0; i < array.length; i++) {
+ steps.push({ type: 'compare', indices: [i] });
+ if (array[i] === target) {
+ steps.push({ type: 'found', indices: [i] });
+ return steps;
}
- return steps;
+ steps.push({ type: 'visited', indices: [i] });
+ }
+ return steps;
}
export const generateBinarySearchSteps = (array, target) => {
- // Expects sorted array, but for visualization we might run it on unsorted to show failure or sort first.
- // We will assume the UI sorts it or provides sorted data for BS.
- const steps = [];
- let left = 0;
- let right = array.length - 1;
-
- while (left <= right) {
- const mid = Math.floor((left + right) / 2);
- steps.push({ type: 'compare', indices: [mid], range: [left, right] }); // Highlight mid and range
-
- if (array[mid] === target) {
- steps.push({ type: 'found', indices: [mid] });
- return steps;
- }
-
- if (array[mid] < target) {
- steps.push({ type: 'discard', indices: [], range: [left, mid] }); // Grey out left half
- left = mid + 1;
- } else {
- steps.push({ type: 'discard', indices: [], range: [mid, right] }); // Grey out right half
- right = mid - 1;
- }
+ // Expects sorted array, but for visualization we might run it on unsorted to show failure or sort first.
+ // We will assume the UI sorts it or provides sorted data for BS.
+ const steps = [];
+ let left = 0;
+ let right = array.length - 1;
+
+ while (left <= right) {
+ const mid = Math.floor((left + right) / 2);
+ steps.push({ type: 'compare', indices: [mid], range: [left, right] }); // Highlight mid and range
+
+ if (array[mid] === target) {
+ steps.push({ type: 'found', indices: [mid] });
+ return steps;
}
- return steps;
+
+ if (array[mid] < target) {
+ steps.push({ type: 'discard', indices: [], range: [left, mid] }); // Grey out left half
+ left = mid + 1;
+ } else {
+ steps.push({ type: 'discard', indices: [], range: [mid, right] }); // Grey out right half
+ right = mid - 1;
+ }
+ }
+ return steps;
}
// --- Graph Algorithms ---
// Helper to get Euclidean distance for A* heuristic
const getDist = (nodeA, nodeB) => {
- return Math.sqrt(Math.pow(nodeA.x - nodeB.x, 2) + Math.pow(nodeA.y - nodeB.y, 2));
+ return Math.sqrt(Math.pow(nodeA.x - nodeB.x, 2) + Math.pow(nodeA.y - nodeB.y, 2));
};
export const generateBFSSteps = (numNodes, adj, startNode, endNode) => {
- const steps = [];
- const visited = new Array(numNodes).fill(false);
- const queue = [startNode];
- visited[startNode] = true;
- const prev = new Array(numNodes).fill(null);
-
- steps.push({ type: 'visit', node: startNode });
-
- while(queue.length > 0) {
- const u = queue.shift();
- if (u === endNode) break;
-
- for(let edge of adj[u]) {
- const v = edge.to;
- if(!visited[v]) {
- visited[v] = true;
- prev[v] = u;
- steps.push({ type: 'traverse', from: u, to: v });
- steps.push({ type: 'visit', node: v });
- queue.push(v);
- if (v === endNode) break;
- }
- }
+ const steps = [];
+ const visited = new Array(numNodes).fill(false);
+ const queue = [startNode];
+ visited[startNode] = true;
+ const prev = new Array(numNodes).fill(null);
+
+ steps.push({ type: 'visit', node: startNode });
+
+ while (queue.length > 0) {
+ const u = queue.shift();
+ if (u === endNode) break;
+
+ for (let edge of adj[u]) {
+ const v = edge.to;
+ if (!visited[v]) {
+ visited[v] = true;
+ prev[v] = u;
+ steps.push({ type: 'traverse', from: u, to: v });
+ steps.push({ type: 'visit', node: v });
+ queue.push(v);
+ if (v === endNode) break;
+ }
}
+ }
- // Reconstruct path
- if (visited[endNode]) {
- const path = [];
- let curr = endNode;
- while(curr !== null) {
- path.unshift(curr);
- curr = prev[curr];
- }
- steps.push({ type: 'path', path });
+ // Reconstruct path
+ if (visited[endNode]) {
+ const path = [];
+ let curr = endNode;
+ while (curr !== null) {
+ path.unshift(curr);
+ curr = prev[curr];
}
+ steps.push({ type: 'path', path });
+ }
- return steps;
+ return steps;
};
export const generateDFSSteps = (numNodes, adj, startNode, endNode) => {
- const steps = [];
- const visited = new Array(numNodes).fill(false);
- const prev = new Array(numNodes).fill(null);
-
- const dfs = (u) => {
- visited[u] = true;
- steps.push({ type: 'visit', node: u });
-
- if (u === endNode) return true;
-
- for(let edge of adj[u]) {
- const v = edge.to;
- if(!visited[v]) {
- prev[v] = u;
- steps.push({ type: 'traverse', from: u, to: v });
- if (dfs(v)) return true;
- }
- }
- return false;
- };
-
- dfs(startNode);
-
- // Reconstruct path
- if (visited[endNode]) {
- const path = [];
- let curr = endNode;
- while(curr !== null) {
- path.unshift(curr);
- curr = prev[curr];
- }
- steps.push({ type: 'path', path });
+ const steps = [];
+ const visited = new Array(numNodes).fill(false);
+ const prev = new Array(numNodes).fill(null);
+
+ const dfs = (u) => {
+ visited[u] = true;
+ steps.push({ type: 'visit', node: u });
+
+ if (u === endNode) return true;
+
+ for (let edge of adj[u]) {
+ const v = edge.to;
+ if (!visited[v]) {
+ prev[v] = u;
+ steps.push({ type: 'traverse', from: u, to: v });
+ if (dfs(v)) return true;
+ }
}
- return steps;
+ return false;
+ };
+
+ dfs(startNode);
+
+ // Reconstruct path
+ if (visited[endNode]) {
+ const path = [];
+ let curr = endNode;
+ while (curr !== null) {
+ path.unshift(curr);
+ curr = prev[curr];
+ }
+ steps.push({ type: 'path', path });
+ }
+ return steps;
};
export const generateDijkstraSteps = (numNodes, adj, startNode, endNode) => {
- const steps = [];
- const dist = new Array(numNodes).fill(Infinity);
- const prev = new Array(numNodes).fill(null);
- const visited = new Array(numNodes).fill(false);
-
- dist[startNode] = 0;
- // Using array as priority queue for simplicity in visualization
- const pq = [{ node: startNode, dist: 0 }];
-
- steps.push({ type: 'update-dist', node: startNode, dist: 0 });
-
- while (pq.length > 0) {
- pq.sort((a, b) => a.dist - b.dist);
- const { node: u } = pq.shift();
-
- if (visited[u]) continue;
- visited[u] = true;
- steps.push({ type: 'visit', node: u });
-
- if (u === endNode) break;
-
- for (let edge of adj[u]) {
- const v = edge.to;
- const weight = edge.weight;
- if (!visited[v]) {
- steps.push({ type: 'check-edge', from: u, to: v });
- const alt = dist[u] + weight;
- if (alt < dist[v]) {
- dist[v] = alt;
- prev[v] = u;
- pq.push({ node: v, dist: alt });
- steps.push({ type: 'update-dist', node: v, dist: alt });
- }
- }
+ const steps = [];
+ const dist = new Array(numNodes).fill(Infinity);
+ const prev = new Array(numNodes).fill(null);
+ const visited = new Array(numNodes).fill(false);
+
+ dist[startNode] = 0;
+ // Using array as priority queue for simplicity in visualization
+ const pq = [{ node: startNode, dist: 0 }];
+
+ steps.push({ type: 'update-dist', node: startNode, dist: 0 });
+
+ while (pq.length > 0) {
+ pq.sort((a, b) => a.dist - b.dist);
+ const { node: u } = pq.shift();
+
+ if (visited[u]) continue;
+ visited[u] = true;
+ steps.push({ type: 'visit', node: u });
+
+ if (u === endNode) break;
+
+ for (let edge of adj[u]) {
+ const v = edge.to;
+ const weight = edge.weight;
+ if (!visited[v]) {
+ steps.push({ type: 'check-edge', from: u, to: v });
+ const alt = dist[u] + weight;
+ if (alt < dist[v]) {
+ dist[v] = alt;
+ prev[v] = u;
+ pq.push({ node: v, dist: alt });
+ steps.push({ type: 'update-dist', node: v, dist: alt });
}
+ }
}
-
- // Path reconstruction
- if (dist[endNode] !== Infinity) {
- const path = [];
- let curr = endNode;
- while (curr !== null) {
- path.unshift(curr);
- curr = prev[curr];
- }
- steps.push({ type: 'path', path });
+ }
+
+ // Path reconstruction
+ if (dist[endNode] !== Infinity) {
+ const path = [];
+ let curr = endNode;
+ while (curr !== null) {
+ path.unshift(curr);
+ curr = prev[curr];
}
-
- return steps;
+ steps.push({ type: 'path', path });
+ }
+
+ return steps;
};
export const generateAStarSteps = (numNodes, adj, startNode, endNode, nodes) => {
- const steps = [];
- const gScore = new Array(numNodes).fill(Infinity);
- const fScore = new Array(numNodes).fill(Infinity);
- const prev = new Array(numNodes).fill(null);
- const visited = new Array(numNodes).fill(false);
-
- gScore[startNode] = 0;
- fScore[startNode] = getDist(nodes[startNode], nodes[endNode]);
-
- const pq = [{ node: startNode, f: fScore[startNode] }];
-
- steps.push({ type: 'update-dist', node: startNode, dist: 0 }); // Visually similar to dist update
-
- while (pq.length > 0) {
- pq.sort((a, b) => a.f - b.f);
- const { node: u } = pq.shift();
-
- if (visited[u]) continue;
- visited[u] = true;
- steps.push({ type: 'visit', node: u });
-
- if (u === endNode) break;
-
- for (let edge of adj[u]) {
- const v = edge.to;
- const weight = edge.weight;
-
- // A* specific: neighbor check
- steps.push({ type: 'check-edge', from: u, to: v });
-
- const tentativeG = gScore[u] + weight;
- if (tentativeG < gScore[v]) {
- prev[v] = u;
- gScore[v] = tentativeG;
- fScore[v] = gScore[v] + getDist(nodes[v], nodes[endNode]); // h(n) = euclidean
-
- // Update visual dist (show gScore)
- steps.push({ type: 'update-dist', node: v, dist: Math.round(tentativeG) });
-
- // Add to open set if not present (or re-add, simplified)
- pq.push({ node: v, f: fScore[v] });
- }
- }
+ const steps = [];
+ const gScore = new Array(numNodes).fill(Infinity);
+ const fScore = new Array(numNodes).fill(Infinity);
+ const prev = new Array(numNodes).fill(null);
+ const visited = new Array(numNodes).fill(false);
+
+ gScore[startNode] = 0;
+ fScore[startNode] = getDist(nodes[startNode], nodes[endNode]);
+
+ const pq = [{ node: startNode, f: fScore[startNode] }];
+
+ steps.push({ type: 'update-dist', node: startNode, dist: 0 }); // Visually similar to dist update
+
+ while (pq.length > 0) {
+ pq.sort((a, b) => a.f - b.f);
+ const { node: u } = pq.shift();
+
+ if (visited[u]) continue;
+ visited[u] = true;
+ steps.push({ type: 'visit', node: u });
+
+ if (u === endNode) break;
+
+ for (let edge of adj[u]) {
+ const v = edge.to;
+ const weight = edge.weight;
+
+ // A* specific: neighbor check
+ steps.push({ type: 'check-edge', from: u, to: v });
+
+ const tentativeG = gScore[u] + weight;
+ if (tentativeG < gScore[v]) {
+ prev[v] = u;
+ gScore[v] = tentativeG;
+ fScore[v] = gScore[v] + getDist(nodes[v], nodes[endNode]); // h(n) = euclidean
+
+ // Update visual dist (show gScore)
+ steps.push({ type: 'update-dist', node: v, dist: Math.round(tentativeG) });
+
+ // Add to open set if not present (or re-add, simplified)
+ pq.push({ node: v, f: fScore[v] });
+ }
}
+ }
- if (gScore[endNode] !== Infinity) {
- const path = [];
- let curr = endNode;
- while (curr !== null) {
- path.unshift(curr);
- curr = prev[curr];
- }
- steps.push({ type: 'path', path });
+ if (gScore[endNode] !== Infinity) {
+ const path = [];
+ let curr = endNode;
+ while (curr !== null) {
+ path.unshift(curr);
+ curr = prev[curr];
}
+ steps.push({ type: 'path', path });
+ }
- return steps;
+ return steps;
}
diff --git a/src/pages/DsaVisualization.jsx b/src/pages/DsaVisualization.jsx
index 5caefa3..45ffcc4 100644
--- a/src/pages/DsaVisualization.jsx
+++ b/src/pages/DsaVisualization.jsx
@@ -2,23 +2,23 @@
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Helmet } from 'react-helmet';
-import {
- Play, Pause, RotateCcw, Settings, ChevronRight, ChevronLeft,
- LayoutGrid, List, BarChart2, Activity, Moon, Sun,
- GitCompare, SplitSquareHorizontal, ArrowRight, Network,
- PlusCircle, Move, MousePointer2, Trash2, StepForward,
- StepBack, Binary, BoxSelect, Layers, Search, Github
+import {
+ Play, Pause, RotateCcw, Settings, ChevronRight, ChevronLeft,
+ LayoutGrid, List, BarChart2, Activity, Moon, Sun,
+ GitCompare, SplitSquareHorizontal, ArrowRight, Network,
+ PlusCircle, Move, MousePointer2, Trash2, StepForward,
+ StepBack, Binary, BoxSelect, Layers, Search, Github
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Slider } from '@/components/ui/slider';
import { Switch } from '@/components/ui/switch';
import { Input } from '@/components/ui/input';
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@/components/ui/select";
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
@@ -31,369 +31,11 @@ import LinkedListVisualizer from '@/components/dsa/LinkedListVisualizer';
import TreeVisualizer from '@/components/dsa/TreeVisualizer';
import HeapVisualizer from '@/components/dsa/HeapVisualizer';
import DPVisualizer from '@/components/dsa/DPVisualizer';
+import SortingVisualizer from '@/components/dsa/SortingVisualizer';
+import GraphVisualizer from '@/components/dsa/GraphVisualizer';
+import StackVisualizer from '@/components/dsa/StackVisualizer';
import ContributorsSection from '@/components/dsa/ContributorsSection';
-// --- Existing Sorting/Graph Components ---
-
-const SortingVisualizer = ({ array, algorithmName, isPlaying, speed, onFinished, className, searchTarget }) => {
- const [displayArray, setDisplayArray] = useState([...array]);
- const timeoutsRef = useRef([]);
-
- const showLabels = array.length <= 35;
-
- useEffect(() => {
- setDisplayArray([...array]);
- clearAllTimeouts();
- }, [array]);
-
- useEffect(() => {
- if (isPlaying) {
- runAlgorithm();
- } else {
- clearAllTimeouts();
- }
- return () => clearAllTimeouts();
- }, [isPlaying, algorithmName]);
-
- const clearAllTimeouts = () => {
- timeoutsRef.current.forEach(clearTimeout);
- timeoutsRef.current = [];
- };
-
- const runAlgorithm = () => {
- clearAllTimeouts();
- let steps = [];
- const algoFunc = algorithms[`generate${algorithmName}Steps`];
- if (!algoFunc) return;
-
- // Pass searchTarget if it's a search algorithm
- if (algorithmName.includes('Search')) {
- steps = algoFunc(array, searchTarget);
- } else {
- steps = algoFunc(array);
- }
-
- animate(steps);
- };
-
- const animate = (steps) => {
- const delay = Math.max(1, 100 - speed);
- steps.forEach((step, index) => {
- const timeoutId = setTimeout(() => {
- setDisplayArray(prev => {
- // Use a new array reference to trigger re-renders
- const newArr = [...prev];
- if (step.type === 'swap') {
- newArr[step.indices[0]] = step.values[0];
- newArr[step.indices[1]] = step.values[1];
- } else if (step.type === 'overwrite') {
- newArr[step.indices[0]] = step.value;
- }
- return newArr;
- });
-
- const bars = document.getElementsByClassName(`bar-${className}`);
- if(bars.length > 0) {
- if (step.type === 'compare') {
- step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '#ef4444' });
- } else if (step.type === 'swap' || step.type === 'overwrite') {
- step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '#eab308' });
- } else if (step.type === 'sorted' || step.type === 'found') {
- step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '#10b981' });
- } else if (step.type === 'revert') {
- step.indices.forEach(idx => { if(bars[idx]) bars[idx].style.backgroundColor = '' });
- } else if (step.type === 'discard') {
- // Gray out discarded parts of the array for Binary Search
- if (step.range) {
- for(let i=step.range[0]; i<=step.range[1]; i++) {
- if(bars[i]) bars[i].style.opacity = '0.2';
- }
- }
- }
- }
-
- if (index === steps.length - 1) {
- if (onFinished) onFinished();
- }
- }, index * delay);
- timeoutsRef.current.push(timeoutId);
- });
- };
-
- return (
-
- {displayArray.map((val, idx) => (
-
- {showLabels && (
-
- {val}
-
- )}
-
-
- ))}
-
- );
-};
-
-const GraphVisualizer = ({ algorithmName, isPlaying, setIsPlaying, speed, onFinished, darkMode }) => {
- const [nodes, setNodes] = useState([]);
- const [edges, setEdges] = useState([]);
- const [isDirected, setIsDirected] = useState(false);
- const [isWeighted, setIsWeighted] = useState(false);
- const [editMode, setEditMode] = useState('move');
- const [history, setHistory] = useState([]);
- const [currentStep, setCurrentStep] = useState(-1);
- const [startNode, setStartNode] = useState(0);
- const [endNode, setEndNode] = useState(null);
- const [draggingNode, setDraggingNode] = useState(null);
- const [edgeStartNode, setEdgeStartNode] = useState(null);
- const containerRef = useRef(null);
- const animationRef = useRef(null);
-
- const visualState = useMemo(() => {
- const visited = new Set();
- const path = new Set();
- const dists = {};
- let activeLink = null;
-
- if (currentStep >= 0 && currentStep < history.length) {
- for (let i = 0; i <= currentStep; i++) {
- const step = history[i];
- if (step.type === 'visit' || step.type === 'visit-node') {
- visited.add(step.node);
- } else if (step.type === 'path') {
- step.path.forEach(n => path.add(n));
- } else if (step.type === 'update-dist') {
- dists[step.node] = step.dist;
- }
- if (i === currentStep) {
- if (step.type === 'traverse' || step.type === 'check-edge') {
- activeLink = { from: step.from, to: step.to };
- }
- }
- }
- }
- return { visited, path, dists, activeLink };
- }, [currentStep, history]);
-
- useEffect(() => { resetGraph(); }, []);
-
- useEffect(() => {
- if (!isPlaying && nodes.length > 0) {
- calculateAlgorithmSteps();
- }
- }, [nodes, edges, startNode, endNode, algorithmName, isDirected]);
-
- const calculateAlgorithmSteps = () => {
- const numNodes = Math.max(...nodes.map(n => n.id), -1) + 1;
- const adj = Array.from({ length: numNodes }, () => []);
- edges.forEach(edge => {
- if(nodes.find(n=>n.id === edge.from) && nodes.find(n=>n.id === edge.to)) {
- adj[edge.from].push({ to: edge.to, weight: edge.weight });
- if (!isDirected) adj[edge.to].push({ to: edge.from, weight: edge.weight });
- }
- });
- if (!nodes.find(n => n.id === startNode)) return;
- let actualEnd = endNode;
- if (endNode === null && nodes.length > 0) actualEnd = nodes[nodes.length-1].id;
-
- const algoFunc = algorithms[`generate${algorithmName}Steps`];
- if (algoFunc) {
- let steps = [];
- if (algorithmName === 'AStar') {
- const nodeObjMap = nodes.reduce((acc, n) => ({...acc, [n.id]: n}), {});
- steps = algoFunc(numNodes, adj, startNode, actualEnd, nodeObjMap);
- } else {
- steps = algoFunc(numNodes, adj, startNode, actualEnd);
- }
- setHistory(steps);
- }
- };
-
- useEffect(() => {
- if (isPlaying) {
- const delay = Math.max(50, 1000 - (speed * 9));
- animationRef.current = setInterval(() => {
- setCurrentStep(prev => {
- if (prev < history.length - 1) return prev + 1;
- setIsPlaying(false);
- if (onFinished) onFinished();
- return prev;
- });
- }, delay);
- } else {
- clearInterval(animationRef.current);
- }
- return () => clearInterval(animationRef.current);
- }, [isPlaying, history.length, speed]);
-
- const resetGraph = () => {
- setIsPlaying(false);
- setCurrentStep(-1);
- const width = containerRef.current ? containerRef.current.clientWidth : 800;
- const height = 400;
- const numNodes = 10;
- const newNodes = [];
- for (let i = 0; i < numNodes; i++) newNodes.push({ id: i, x: Math.random() * (width - 100) + 50, y: Math.random() * (height - 100) + 50 });
- const newEdges = [];
- for(let i = 1; i < numNodes; i++) {
- const target = Math.floor(Math.random() * i);
- const weight = Math.floor(Math.random() * 20) + 1;
- newEdges.push({ from: i, to: target, weight });
- }
- for(let i=0; i(e.from===u&&e.to===v) || (e.from===v&&e.to===u))) {
- newEdges.push({ from: u, to: v, weight: Math.floor(Math.random()*20)+1 });
- }
- }
- setNodes(newNodes);
- setEdges(newEdges);
- setStartNode(0);
- setEndNode(numNodes-1);
- };
-
- const handleMouseDown = (e, nodeId) => {
- if (isPlaying) return;
- e.stopPropagation();
- if (editMode === 'move' && nodeId !== undefined) setDraggingNode(nodeId);
- else if (editMode === 'add-edge' && nodeId !== undefined) {
- if (edgeStartNode === null) setEdgeStartNode(nodeId);
- else {
- if (edgeStartNode !== nodeId) {
- const weight = isWeighted ? Math.floor(Math.random() * 20) + 1 : 1;
- const exists = edges.some(edge => (edge.from === edgeStartNode && edge.to === nodeId) || (!isDirected && edge.from === nodeId && edge.to === edgeStartNode));
- if (!exists) setEdges(prev => [...prev, { from: edgeStartNode, to: nodeId, weight }]);
- }
- setEdgeStartNode(null);
- }
- } else if (editMode === 'delete' && nodeId !== undefined) {
- setNodes(prev => prev.filter(n => n.id !== nodeId));
- setEdges(prev => prev.filter(e => e.from !== nodeId && e.to !== nodeId));
- if(startNode === nodeId) setStartNode(nodes.length > 0 ? nodes[0].id : null);
- }
- };
-
- const handleCanvasClick = (e) => {
- if (isPlaying) return;
- if (editMode === 'add-node') {
- const rect = containerRef.current.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
- const newId = nodes.length > 0 ? Math.max(...nodes.map(n=>n.id)) + 1 : 0;
- setNodes(prev => [...prev, { id: newId, x, y }]);
- }
- };
-
- const handleMouseMove = (e) => {
- if (draggingNode === null || !containerRef.current) return;
- const rect = containerRef.current.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
- setNodes(prev => prev.map(n => n.id === draggingNode ? { ...n, x, y } : n));
- };
-
- const handleMouseUp = () => setDraggingNode(null);
-
- const handleNodeContext = (e, nodeId) => {
- e.preventDefault();
- if(editMode === 'move') { setStartNode(nodeId); setCurrentStep(-1); }
- };
-
- return (
-
-
- {/* Graph Toolbar Buttons: Added explicit colors for visibility */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Start: {startNode}
-
End: {endNode ?? 'Last'}
-
{editMode === 'move' ? "Drag nodes. Right-click to set Start." : "Click to edit."}
-
-
-
-
-
-
- );
-};
-
// --- Main Page ---
const DsaVisualization = () => {
@@ -401,14 +43,14 @@ const DsaVisualization = () => {
const [activeTab, setActiveTab] = useState('sorting');
const [algorithm, setAlgorithm] = useState('BubbleSort');
const [secondAlgorithm, setSecondAlgorithm] = useState('QuickSort');
- const [arraySize, setArraySize] = useState(20);
+ const [arraySize, setArraySize] = useState(20);
const [speed, setSpeed] = useState(50);
const [isPlaying, setIsPlaying] = useState(false);
const [isComparisonMode, setIsComparisonMode] = useState(false);
const [darkMode, setDarkMode] = useState(true);
const [customInput, setCustomInput] = useState('');
const [searchTarget, setSearchTarget] = useState(42); // Default target
-
+
const [array, setArray] = useState([]);
useEffect(() => {
@@ -423,7 +65,7 @@ const DsaVisualization = () => {
// To keep it simple, we generate random.
// If user selects BinarySearch, we might want to auto-sort or let them visualize fail/sort.
const newArr = Array.from({ length: arraySize }, () => Math.floor(Math.random() * 100) + 5);
-
+
// If searching, ensure the target is present randomly 50% of the time for fun, or just random.
// Let's just keep random.
setArray(newArr);
@@ -433,11 +75,11 @@ const DsaVisualization = () => {
bar.style.opacity = '1';
});
};
-
+
// Auto-sort if switching to Binary Search to avoid confusion
useEffect(() => {
if (algorithm === 'BinarySearch') {
- setArray(prev => [...prev].sort((a,b) => a-b));
+ setArray(prev => [...prev].sort((a, b) => a - b));
}
}, [algorithm]);
@@ -456,7 +98,8 @@ const DsaVisualization = () => {
linkedlist: [],
trees: [],
heaps: [],
- dp: []
+ dp: [],
+ stack: []
};
return (
@@ -499,8 +142,8 @@ const DsaVisualization = () => {
-