From 7cf219149a82bd481b78e5accda66204e4052581 Mon Sep 17 00:00:00 2001
From: jeevanvishnu
Date: Thu, 1 Jan 2026 11:39:05 +0530
Subject: [PATCH 1/2] feat: Add Sorting, Stack, and Graph visualizer components
and integrate them into the DSA visualization page.
---
src/components/dsa/GraphVisualizer.jsx | 254 +++++++++++++
src/components/dsa/SortingVisualizer.jsx | 120 ++++++
src/components/dsa/StackVisualizer.jsx | 118 ++++++
src/pages/DsaVisualization.jsx | 462 +++--------------------
4 files changed, 549 insertions(+), 405 deletions(-)
create mode 100644 src/components/dsa/GraphVisualizer.jsx
create mode 100644 src/components/dsa/SortingVisualizer.jsx
create mode 100644 src/components/dsa/StackVisualizer.jsx
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/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 = () => {
-