From 39b6ec97495376b8628cf3dffda861c2f5a194cc Mon Sep 17 00:00:00 2001 From: mukeshdhadhariya Date: Tue, 14 Oct 2025 23:03:42 +0530 Subject: [PATCH] feat(data-structure): add stack visualizer --- src/App.jsx | 3 + src/algorithms/dataStructure/stack.js | 51 +++++++++ src/components/dataStructure/stack.jsx | 60 ++++++++++ src/pages/dataStructure/stack.jsx | 148 +++++++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 src/algorithms/dataStructure/stack.js create mode 100644 src/components/dataStructure/stack.jsx create mode 100644 src/pages/dataStructure/stack.jsx diff --git a/src/App.jsx b/src/App.jsx index bb325e8..c487e5d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,6 +3,8 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import UnionFindPage from "../src/pages/graph/UnionFind.jsx"; // ✅ Import Union-Find Page import SortingPage from "./pages/sorting/SortingPage"; import Homepage from "./pages/Homepage.jsx"; +import Stack from "./pages/dataStructure/stack.jsx" + function App() { return ( @@ -10,6 +12,7 @@ function App() { } /> } /> {/* ✅ Added route */} } /> + }/> ); diff --git a/src/algorithms/dataStructure/stack.js b/src/algorithms/dataStructure/stack.js new file mode 100644 index 0000000..d16d2b0 --- /dev/null +++ b/src/algorithms/dataStructure/stack.js @@ -0,0 +1,51 @@ +export function* stackOp(array, action) { + + const arr = [...array]; + + const snapshot = () => [...arr]; + + switch (action?.type) { + case "push": { + yield { type: "push-start", value: action.value }; + arr.unshift(action.value); + yield { type: "push", value: action.value, array: snapshot() }; + break; + } + + case "pop": { + if (arr.length === 0) { + yield { type: "underflow" }; + } else { + yield { type: "pop-start", index: 0, value: arr[0] }; + const popped = arr.shift(); + yield { type: "pop", value: popped, array: snapshot() }; + } + break; + } + + case "peek": { + if (arr.length === 0) { + yield { type: "peek-empty" }; + } else { + yield { type: "peek", value: arr[0], index: 0 }; + } + break; + } + + case "clear": { + if (arr.length === 0) { + yield { type: "clear-empty" }; + } else { + yield { type: "clear", array: [] }; + arr.length = 0; + } + break; + } + + default: { + yield { type: "invalid", action }; + } + } + + yield { type: "done", array: snapshot() }; +} diff --git a/src/components/dataStructure/stack.jsx b/src/components/dataStructure/stack.jsx new file mode 100644 index 0000000..debd3fd --- /dev/null +++ b/src/components/dataStructure/stack.jsx @@ -0,0 +1,60 @@ +import React from "react"; + +const COLORS = { + default: "bg-slate-700 shadow-[0_0_8px_rgba(15,23,42,0.6)]", + push: "bg-green-500 shadow-[0_0_12px_rgba(34,197,94,0.4)]", + pop: "bg-red-500 shadow-[0_0_12px_rgba(239,68,68,0.35)]", + peek: "bg-yellow-400 shadow-[0_0_12px_rgba(250,204,21,0.3)]", + underflow: "bg-rose-600/60", +}; + +export default function StackVisualizer({ array = [], highlight = null }) { + return ( +
+
Top
+ +
+
+ {array.length === 0 ? ( +
Stack is empty
+ ) : ( + array.map((value, idx) => { + let cls = COLORS.default; + + if (highlight) { + if (highlight.type === "push" && highlight.array && highlight.array[0] === value && idx === 0) { + cls = COLORS.push; + } + if ((highlight.type === "pop" || highlight.type === "pop-start") && idx === 0) { + cls = COLORS.pop; + } + if (highlight.type === "peek" && highlight.index === idx) { + cls = COLORS.peek; + } + if (highlight.type === "underflow") { + cls = COLORS.underflow; + } + if (highlight.type === "clear" && array.length === 0) { + cls = COLORS.default; + } + } + + return ( +
+
{String(value)}
+
#{array.length - idx}
+
+ ); + }) + )} +
+
+ +
Bottom
+
+ ); +} diff --git a/src/pages/dataStructure/stack.jsx b/src/pages/dataStructure/stack.jsx new file mode 100644 index 0000000..cba0537 --- /dev/null +++ b/src/pages/dataStructure/stack.jsx @@ -0,0 +1,148 @@ +import React, { useState } from "react"; +import { Toaster, toast } from "react-hot-toast"; +import StackVisualizer from "../../components/dataStructure/stack.jsx"; +import { stackOp } from "../../algorithms/dataStructure/stack.js"; + +export default function StackPage() { + const [array, setArray] = useState([]); + const [input, setInput] = useState(""); + const [highlight, setHighlight] = useState(null); + const [isRunning, setIsRunning] = useState(false); + + const delay = (ms) => new Promise((r) => setTimeout(r, ms)); + + const runAction = async (action) => { + if (isRunning) return; + setIsRunning(true); + + const gen = stackOp(array, action); + for (let step of gen) { + setHighlight(step); + if (step.array) setArray([...step.array]); + await delay(400); + } + + await delay(150); + setHighlight(null); + setIsRunning(false); + }; + + const handlePush = async () => { + if (!input.trim()) { + toast.error("Enter a value to push"); + return; + } + const parsed = input.trim(); + const value = parsed.length && !Number.isNaN(Number(parsed)) ? Number(parsed) : parsed; + await runAction({ type: "push", value }); + setInput(""); + }; + + const handlePop = async () => { + await runAction({ type: "pop" }); + }; + + const handlePeek = async () => { + await runAction({ type: "peek" }); + }; + + const handleClear = async () => { + await runAction({ type: "clear" }); + }; + + const handleReset = () => { + setArray([]); + setInput(""); + setHighlight(null); + }; + + const sampleLoad = () => { + setArray(["X", "Y", "Z"]); + setHighlight({ type: "done", array: ["X", "Y", "Z"] }); + }; + + return ( +
+ +

+ Stack Visualizer +

+ +
+
+
+ setInput(e.target.value)} + placeholder="Value to push (number or string)" + className="flex-1 w-full sm:w-auto border-2 border-indigo-500 bg-gray-900 text-indigo-200 rounded-lg p-3 text-center outline-none shadow-inner" + /> + +
+ +
+ + + + + + + + + +
+
+ +
+ +
+ +
+ Tip: top of stack is shown at the top (index 0). Push adds to top; Pop removes the top (LIFO). +
+
+
+ ); +} \ No newline at end of file