Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Homepage from "./pages/Homepage";
import UnionFindPage from "../src/pages/graph/UnionFind.jsx"; // ✅ Import Union-Find Page
import SortingPage from "./pages/sorting/SortingPage";

import Homepage from "./pages/Homepage.jsx";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/graph/union-find" element={<UnionFindPage />} /> {/* ✅ Added route */}
<Route path="/sorting" element={<SortingPage />} />
</Routes>
</Router>
Expand Down
72 changes: 72 additions & 0 deletions src/algorithms/graph/unionFind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// src/algorithms/graph/unionFind.js

class UnionFind {
constructor(n) {
this.parent = Array.from({ length: n }, (_, i) => i);
this.rank = Array(n).fill(0);
this.steps = []; // For visualization steps
}

find(x) {
this.steps.push({ type: "find-start", node: x });
if (this.parent[x] !== x) {
const root = this.find(this.parent[x]);
this.steps.push({
type: "path-compression",
node: x,
newParent: root,
});
this.parent[x] = root;
}
this.steps.push({ type: "find-end", node: x, root: this.parent[x] });
return this.parent[x];
}

union(x, y) {
const rootX = this.find(x);
const rootY = this.find(y);

this.steps.push({
type: "union-start",
x,
y,
rootX,
rootY,
});

if (rootX === rootY) {
this.steps.push({ type: "same-set", x, y });
return;
}

if (this.rank[rootX] < this.rank[rootY]) {
this.parent[rootX] = rootY;
this.steps.push({
type: "union",
parent: rootY,
child: rootX,
});
} else if (this.rank[rootX] > this.rank[rootY]) {
this.parent[rootY] = rootX;
this.steps.push({
type: "union",
parent: rootX,
child: rootY,
});
} else {
this.parent[rootY] = rootX;
this.rank[rootX]++;
this.steps.push({
type: "union-rank",
parent: rootX,
child: rootY,
});
}
}

getSteps() {
return this.steps;
}
}

export default UnionFind;
121 changes: 121 additions & 0 deletions src/components/graph/UnionFindVisualizer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// src/components/graph/UnionFindVisualizer.jsx
import { motion } from "framer-motion";

export default function UnionFindVisualizer({ nodes, parent, highlights }) {
// Calculate positions for nodes in a grid layout
const getNodePosition = (index) => {
const cols = Math.ceil(Math.sqrt(nodes.length));
const row = Math.floor(index / cols);
const col = index % cols;
return { row, col };
};

// Generate connections for visualization
const connections = [];
nodes.forEach((node, index) => {
if (parent[index] !== index) {
const parentPos = getNodePosition(parent[index]);
const childPos = getNodePosition(index);
connections.push({
from: childPos,
to: parentPos,
child: index,
parent: parent[index]
});
}
});

return (
<div className="relative">
{/* SVG for connections */}
<svg
className="absolute inset-0 w-full h-full pointer-events-none"
style={{ minHeight: '300px' }}
>
{connections.map((conn, index) => {
const fromX = (conn.from.col + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';
const fromY = (conn.from.row + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';
const toX = (conn.to.col + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';
const toY = (conn.to.row + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';

const isHighlighted = highlights.union.includes(conn.child) ||
highlights.current === conn.child ||
highlights.root === conn.parent;

return (
<motion.line
key={index}
x1={fromX}
y1={fromY}
x2={toX}
y2={toY}
stroke={isHighlighted ? "#ef4444" : "#6b7280"}
strokeWidth={isHighlighted ? "3" : "2"}
strokeDasharray={isHighlighted ? "5,5" : "none"}
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
/>
);
})}
</svg>

{/* Nodes */}
<div className="grid gap-4 justify-center" style={{
gridTemplateColumns: `repeat(${Math.ceil(Math.sqrt(nodes.length))}, 1fr)`,
maxWidth: '600px',
margin: '0 auto'
}}>
{nodes.map((node, index) => {
const isRoot = parent[index] === index;
const color =
highlights.current === index
? "bg-blue-500"
: highlights.root === index
? "bg-green-500"
: highlights.union.includes(index)
? "bg-red-500"
: highlights.path.includes(index)
? "bg-purple-500"
: "bg-gray-600";

return (
<motion.div
key={index}
className={`w-20 h-20 flex flex-col items-center justify-center text-white font-semibold rounded-full shadow-lg border-2 border-white/20 ${color} relative`}
layout
animate={{
scale: highlights.current === index ? 1.2 : 1,
boxShadow: highlights.current === index ? "0 0 20px rgba(59, 130, 246, 0.5)" : "0 4px 6px rgba(0, 0, 0, 0.1)"
}}
transition={{ duration: 0.3 }}
>
<div className="text-lg font-bold">{node}</div>
<div className="text-xs opacity-80">p:{parent[index]}</div>
{isRoot && (
<div className="absolute -top-2 -right-2 w-6 h-6 bg-yellow-400 rounded-full flex items-center justify-center">
<span className="text-[10px] font-bold text-black">R</span>
</div>
)}
{highlights.current === index && (
<div className="absolute -top-3 -left-3 w-6 h-6 bg-blue-400 rounded-full flex items-center justify-center animate-pulse">
<span className="text-[10px] font-bold text-white">F</span>
</div>
)}
</motion.div>
);
})}
</div>

{/* Additional Info */}
<div className="mt-6 text-center text-sm text-gray-300">
<p>Each node shows: <span className="text-white font-semibold">Node Number</span> | <span className="text-gray-400">Parent</span></p>
<p className="mt-1">
<span className="text-yellow-400">R</span> = Root |
<span className="text-blue-400"> F</span> = Finding |
<span className="text-red-400"> Red</span> = Unioning
</p>
</div>
</div>
);
}
38 changes: 25 additions & 13 deletions src/pages/Homepage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { ArrowRight, Github } from "lucide-react";
import sort from "../assets/sorting.png"
import { useNavigate } from "react-router-dom";


const sections = [
{
title: "Sorting Algorithms",
description:
"Visualize step-by-step how sorting algorithms organize data efficiently.",
phase: "Phase 1 (MVP)",
img: "https://tamimehsan.github.io/AlgorithmVisualizer/images/sort.png?height=200&width=300",
link: "/sorting",
flag: false
link: "/sorting",
flag: false
},
{
title: "Searching Algorithms",
Expand All @@ -35,9 +34,10 @@ const sections = [
{
title: "Graph Algorithms",
description:
"Explore BFS, DFS, Kruskal’s, Prim’s, and more — all brought to life interactively.",
"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
},
Expand All @@ -47,6 +47,7 @@ const sections = [
"Visualize recursive calls and backtracking patterns like N-Queens or Sudoku.",
phase: "Phase 2",
img: "",
route: "",
link: "/recursion",
flag: true
},
Expand All @@ -56,6 +57,7 @@ const sections = [
"Interactively understand stacks, queues, linked lists, trees, and heaps.",
phase: "Phase 2",
img: "",
route: "",
link: "/data-structures",
flag: true
},
Expand All @@ -65,18 +67,18 @@ const sections = [
"Step through state transitions and table updates to grasp DP intuitively.",
phase: "Phase 3",
img: "",
route: "",
link: "/dynamic-programming",
flag: true
},
];


const Homepage = () => {
const navigate = useNavigate();

return (
<div className="min-h-screen w-full text-white flex flex-col items-center overflow-x-hidden relative">
{/* Full-Page Animated Gradient Background */}
{/* Background */}
<div className="fixed inset-0 bg-gradient-to-br from-[#1b0b3a] via-[#120a2a] to-black animate-gradient-x bg-[length:400%_400%] -z-20" />
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm -z-10" />

Expand All @@ -92,7 +94,10 @@ const Homepage = () => {
</p>

<div className="mt-8 flex flex-wrap gap-4 justify-center">
<button className="px-6 py-3 bg-indigo-500 hover:bg-indigo-600 text-white font-semibold rounded-full flex items-center gap-2 shadow-lg hover:shadow-indigo-500/50 transition-all duration-300">
<button
onClick={() => window.scrollTo({ top: 600, behavior: "smooth" })}
className="px-6 py-3 bg-indigo-500 hover:bg-indigo-600 text-white font-semibold rounded-full flex items-center gap-2 shadow-lg hover:shadow-indigo-500/50 transition-all duration-300"
>
Explore Now <ArrowRight size={18} />
</button>
<a
Expand Down Expand Up @@ -132,17 +137,24 @@ const Homepage = () => {
</p>

{/* Explore Button */}
<button className="mt-4 text-indigo-400 font-semibold flex items-center gap-1 hover:gap-2 transition-all">
<button
onClick={() =>
section.route
? navigate(section.route)
: alert("Coming soon 🚀")
}
className="mt-4 text-indigo-400 font-semibold flex items-center gap-1 hover:gap-2 transition-all"
>
Explore <ArrowRight size={16} />
</button>
</div>

{/* SaaS-style “Coming Soon” Overlay */}
{section.flag && (
<div className="absolute inset-0 flex items-center justify-center text-white text-lg font-semibold bg-gradient-to-br from-indigo-700/70 to-purple-900/70 backdrop-blur-md opacity-0 group-hover:opacity-100 transition-opacity duration-300">
Coming Soon 🚀
</div>
)}
{section.flag && (
<div className="absolute inset-0 flex items-center justify-center text-white text-lg font-semibold bg-gradient-to-br from-indigo-700/70 to-purple-900/70 backdrop-blur-md opacity-0 group-hover:opacity-100 transition-opacity duration-300">
Coming Soon 🚀
</div>
)}
</div>
))}
</section>
Expand Down
Loading
Loading