diff --git a/public/graph.png b/public/graph.png new file mode 100644 index 0000000..95eaef6 Binary files /dev/null and b/public/graph.png differ diff --git a/src/App.jsx b/src/App.jsx index bb325e8..f4a6d32 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,15 +1,18 @@ import React from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import UnionFindPage from "../src/pages/graph/UnionFind.jsx"; // ✅ Import Union-Find Page +// import UnionFindPage from "../src/pages/graph/UnionFind.jsx"; // ✅ Import Union-Find Page import SortingPage from "./pages/sorting/SortingPage"; +import GraphPage from "./pages/graph/GraphPage"; import Homepage from "./pages/Homepage.jsx"; + function App() { return ( } /> - } /> {/* ✅ Added route */} + {/* } /> */} } /> + } /> ); diff --git a/src/algorithms/graph/bellmanFord.js b/src/algorithms/graph/bellmanFord.js new file mode 100644 index 0000000..e89e302 --- /dev/null +++ b/src/algorithms/graph/bellmanFord.js @@ -0,0 +1,37 @@ +export default function runBellmanFord(nodes, edges, source) { + const dist = {}; + nodes.forEach(n => dist[n] = Infinity); + dist[source] = 0; + + const steps = []; + + // Relax edges |V|-1 times + for (let i = 0; i < nodes.length - 1; i++) { + for (const edge of edges) { + const { from, to, weight } = edge; + const step = { + type: "relax", + iteration: i + 1, + edge, + prevDistance: dist[to] + }; + + if (dist[from] + weight < dist[to]) { + dist[to] = dist[from] + weight; + step.updatedDistance = dist[to]; + } + + steps.push(step); + } + } + + // Check for negative weight cycles + for (const edge of edges) { + const { from, to, weight } = edge; + if (dist[from] + weight < dist[to]) { + steps.push({ type: "negativeCycle", edge }); + } + } + + return steps; +} diff --git a/src/components/graph/BellmanFordGraph.jsx b/src/components/graph/BellmanFordGraph.jsx new file mode 100644 index 0000000..023b19e --- /dev/null +++ b/src/components/graph/BellmanFordGraph.jsx @@ -0,0 +1,317 @@ +import React, { useState, useEffect, useMemo } from "react"; + +export default function BellmanFordGraph({ nodes = [], edges = [], distances = {}, highlight = {} }) { + const [positions, setPositions] = useState({}); + + // Arrange nodes in a circle when nodes change + useEffect(() => { + if (nodes.length === 0) return; + + const radius = 180; + const centerX = 300; + const centerY = 250; + const newPositions = {}; + nodes.forEach((node, index) => { + const angle = (2 * Math.PI * index) / nodes.length; + newPositions[node] = { + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + setPositions(newPositions); + }, [nodes]); + + // ✅ Memoize dummy nodes and edges at top level + const dummyGraph = useMemo(() => { + const dummyNodes = Array.from({ length: 6 }).map((_, i) => { + const radius = 180; + const centerX = 300; + const centerY = 250; + const angle = (2 * Math.PI * i) / 6; + return { + id: i, + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + + const dummyEdges = Array.from({ length: 8 }).map(() => { + const u = Math.floor(Math.random() * 6); + const v = (u + 1 + Math.floor(Math.random() * 5)) % 6; + return { u, v }; + }); + + return { dummyNodes, dummyEdges }; + }, []); // runs only once + + return ( +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Dummy graph if no nodes yet */} + {nodes.length === 0 && ( + <> + {dummyGraph.dummyNodes.map((n) => ( + + + + ? + + + ))} + + {dummyGraph.dummyEdges.map((e, i) => { + const x1 = dummyGraph.dummyNodes[e.u].x; + const y1 = dummyGraph.dummyNodes[e.u].y; + const x2 = dummyGraph.dummyNodes[e.v].x; + const y2 = dummyGraph.dummyNodes[e.v].y; + return ( + + ); + })} + + + 🎯 Demo graph: edges light up like traversal! + + + + + )} + + {/* Real edges and nodes rendering (same as before) */} + + + {/* Edges */} + {edges.map((e, idx) => { + const from = positions[e.u]; + const to = positions[e.v]; + if (!from || !to) return null; + + const isHighlighted = + highlight?.edge && highlight.edge.u === e.u && highlight.edge.v === e.v; + + return ( + + + + {e.w} + + + ); + })} + + {/* Nodes */} + {nodes.map((n) => { + const pos = positions[n]; + if (!pos) return null; + + const isHighlighted = highlight?.dist && highlight.dist[n] !== undefined; + + return ( + + + + {n} + + + {distances[n] === undefined || distances[n] === Infinity ? "∞" : distances[n]} + + + ); + })} + +
+ + {/* Result / Details Panel */} +
+

📊 Algorithm Details

+ + {Object.keys(distances).length === 0 ? ( +

+ Run the algorithm to see node distances and steps here. +

+ ) : ( + <> + {highlight && highlight.type && ( +
+ {highlight.type === "relax" && ( +

+ Iteration {highlight.iteration}: Relaxing edge{" "} + {highlight.edge.u} → {highlight.edge.v} +

+ )} + {highlight.type === "skip" && ( +

+ Iteration {highlight.iteration}: Skipped edge{" "} + {highlight.edge.u} → {highlight.edge.v} +

+ )} + {highlight.type === "negative-cycle" && ( +

+ ❌ Negative weight cycle detected on edge{" "} + {highlight.edge.u} → {highlight.edge.v} +

+ )} + {highlight.type === "done" && ( +

+ ✅ Algorithm finished. Final distances updated! +

+ )} +
+ )} + +
+ {nodes.map((n) => ( +
+ Node {n} + + {distances[n] === undefined || distances[n] === Infinity ? "∞" : distances[n]} + +
+ ))} +
+ + )} +
+ + +
+ ); +} diff --git a/src/pages/Homepage.jsx b/src/pages/Homepage.jsx index a846dc8..a1ecca0 100644 --- a/src/pages/Homepage.jsx +++ b/src/pages/Homepage.jsx @@ -36,10 +36,9 @@ const sections = [ description: "Explore BFS, DFS, Kruskal’s, Prim’s, and now Union-Find — all brought to life interactively.", phase: "Phase 2", - img: "", - route: "/graph/union-find", // ✅ Route to Union-Find page - link: "/graphs", - flag: true + img: "/graph.png", + link: "/graph", + flag: false }, { title: "Recursion & Backtracking", diff --git a/src/pages/graph/BellmanFord.jsx b/src/pages/graph/BellmanFord.jsx new file mode 100644 index 0000000..43ad0e8 --- /dev/null +++ b/src/pages/graph/BellmanFord.jsx @@ -0,0 +1,248 @@ +import React, { useState } from "react"; +import { Toaster, toast } from "react-hot-toast"; +import BellmanFordGraph from "../../components/graph/BellmanFordGraph"; + +// Bellman-Ford generator function +function* bellmanFord(nodes, edges, source) { + const dist = {}; + nodes.forEach((n) => (dist[n] = Infinity)); + dist[source] = 0; + + for (let i = 0; i < nodes.length - 1; i++) { + let updated = false; + for (let { u, v, w } of edges) { + if (dist[u] !== Infinity && dist[u] + w < dist[v]) { + dist[v] = dist[u] + w; + updated = true; + yield { type: "relax", iteration: i + 1, edge: { u, v, w }, dist: { ...dist } }; + } else { + yield { type: "skip", iteration: i + 1, edge: { u, v, w }, dist: { ...dist } }; + } + } + if (!updated) break; + } + + // Negative cycle check + for (let { u, v, w } of edges) { + if (dist[u] !== Infinity && dist[u] + w < dist[v]) { + yield { type: "negative-cycle", edge: { u, v, w } }; + return; + } + } + + yield { type: "done", dist }; +} + +export default function BellmanFord() { + const [numNodes, setNumNodes] = useState(0); + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const [source, setSource] = useState(null); + const [distances, setDistances] = useState({}); + const [highlight, setHighlight] = useState(null); + const [isRunning, setIsRunning] = useState(false); + + // Edge input state + const [fromNode, setFromNode] = useState(""); + const [toNode, setToNode] = useState(""); + const [weight, setWeight] = useState(""); + + // Generate nodes automatically + const handleGenerateNodes = () => { + if (numNodes < 2) { + toast.error("Please select at least 2 nodes!"); + return; + } + const generated = Array.from({ length: numNodes }, (_, i) => + String.fromCharCode(65 + i) + ); + setNodes(generated); + setEdges([]); + setSource(generated[0]); + setDistances({}); + setHighlight(null); + }; + + // Add or edit edge + const handleAddEdge = () => { + if (!fromNode || !toNode || !weight) { + toast.error("Fill all edge fields!"); + return; + } + const existingIndex = edges.findIndex( + (e) => e.u === fromNode && e.v === toNode + ); + const newEdge = { u: fromNode, v: toNode, w: parseInt(weight) }; + if (existingIndex >= 0) { + const updatedEdges = [...edges]; + updatedEdges[existingIndex] = newEdge; + setEdges(updatedEdges); + } else { + setEdges([...edges, newEdge]); + } + setFromNode(""); + setToNode(""); + setWeight(""); + }; + + // Run Bellman-Ford visualization + const handleStart = async () => { + if (isRunning) return; + if (!source) { + toast.error("Select a source node!"); + return; + } + + setIsRunning(true); + const gen = bellmanFord(nodes, edges, source); + for (let step of gen) { + setHighlight(step); + if (step.dist) setDistances(step.dist); + await new Promise((r) => setTimeout(r, 800)); + if (step.type === "negative-cycle") { + toast.error(`Negative cycle detected on edge ${step.edge.u} → ${step.edge.v}`); + break; + } + } + setIsRunning(false); + }; + + const handleReset = () => { + setDistances({}); + setHighlight(null); + setEdges([]); + }; + + // Load example graph + const handleLoadExample = () => { + const exampleNodes = ["A", "B", "C", "D", "E", "F"]; + const exampleEdges = [ + { u: "A", v: "B", w: 2 }, + { u: "A", v: "C", w: 5 }, + { u: "B", v: "C", w: 1 }, + { u: "B", v: "D", w: 2 }, + { u: "C", v: "E", w: 3 }, + { u: "D", v: "E", w: 1 }, + { u: "E", v: "F", w: 2 }, + ]; + setNodes(exampleNodes); + setEdges(exampleEdges); + setSource("A"); + setDistances({}); + setHighlight(null); + setNumNodes(6); + }; + + return ( +
+ +

+ Bellman–Ford Visualizer 🚀 +

+ +
+ setNumNodes(parseInt(e.target.value))} + className="p-2 rounded-lg text-purple-900 bg-gray-100" + /> + + +
+ + {nodes.length > 0 && ( +
+
+ {nodes.map((n, idx) => ( +
+ {n} +
+ ))} +
+ +
+ + + setWeight(e.target.value)} + className="p-2 rounded-lg text-gray-900 bg-white" + /> + +
+ +
+ + +
+
+ )} + + + +
+ ); +} diff --git a/src/pages/graph/GraphPage.jsx b/src/pages/graph/GraphPage.jsx new file mode 100644 index 0000000..f70ccd9 --- /dev/null +++ b/src/pages/graph/GraphPage.jsx @@ -0,0 +1,98 @@ +import React, { useState } from "react"; +import { Network, Compass, Rocket } from "lucide-react"; +import BellmanFord from "./BellmanFord"; +import UnionFindPage from "./UnionFind"; +// import Dijkstra from "./Dijkstra"; +// import Kruskal from "./Kruskal"; + +export default function GraphPage() { + const [selectedAlgo, setSelectedAlgo] = useState(""); + + const renderAlgorithm = () => { + switch (selectedAlgo) { + case "bellman-ford": + return ( +
+ +
+ ); + case "union-find": + return ( +
+ +
+ ); + // case "dijkstra": + // return ; + // case "kruskal": + // return ; + default: + return ( +
+
+
+
+ +
+

+ Graph Algorithm Visualizer +

+

+ Select a graph algorithm from the sidebar to begin visualization. + Watch how edges, nodes, and distances transform step by step! 🧠✨ +

+
+ + +
+
+
+
+ ); + } + }; + + return ( +
+ {/* Sidebar */} +
+

+ Graph Panel +

+ + + + + + + + ← Back to Home + +
+ + {/* Visualization Area */} +
+
+ {renderAlgorithm()} +
+
+
+ ); +}