Skip to content

Commit 4511154

Browse files
Merge pull request #16 from ADARSHsri2004/feat/algo
Feature: Implemented Union-Find (Disjoint Set) Visualizer #2
2 parents ca18504 + d03965b commit 4511154

File tree

5 files changed

+724
-15
lines changed

5 files changed

+724
-15
lines changed

src/App.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import React from "react";
22
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
3-
import Homepage from "./pages/Homepage";
3+
import UnionFindPage from "../src/pages/graph/UnionFind.jsx"; // ✅ Import Union-Find Page
44
import SortingPage from "./pages/sorting/SortingPage";
5-
5+
import Homepage from "./pages/Homepage.jsx";
66
function App() {
77
return (
88
<Router>
99
<Routes>
1010
<Route path="/" element={<Homepage />} />
11+
<Route path="/graph/union-find" element={<UnionFindPage />} /> {/* ✅ Added route */}
1112
<Route path="/sorting" element={<SortingPage />} />
1213
</Routes>
1314
</Router>

src/algorithms/graph/unionFind.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// src/algorithms/graph/unionFind.js
2+
3+
class UnionFind {
4+
constructor(n) {
5+
this.parent = Array.from({ length: n }, (_, i) => i);
6+
this.rank = Array(n).fill(0);
7+
this.steps = []; // For visualization steps
8+
}
9+
10+
find(x) {
11+
this.steps.push({ type: "find-start", node: x });
12+
if (this.parent[x] !== x) {
13+
const root = this.find(this.parent[x]);
14+
this.steps.push({
15+
type: "path-compression",
16+
node: x,
17+
newParent: root,
18+
});
19+
this.parent[x] = root;
20+
}
21+
this.steps.push({ type: "find-end", node: x, root: this.parent[x] });
22+
return this.parent[x];
23+
}
24+
25+
union(x, y) {
26+
const rootX = this.find(x);
27+
const rootY = this.find(y);
28+
29+
this.steps.push({
30+
type: "union-start",
31+
x,
32+
y,
33+
rootX,
34+
rootY,
35+
});
36+
37+
if (rootX === rootY) {
38+
this.steps.push({ type: "same-set", x, y });
39+
return;
40+
}
41+
42+
if (this.rank[rootX] < this.rank[rootY]) {
43+
this.parent[rootX] = rootY;
44+
this.steps.push({
45+
type: "union",
46+
parent: rootY,
47+
child: rootX,
48+
});
49+
} else if (this.rank[rootX] > this.rank[rootY]) {
50+
this.parent[rootY] = rootX;
51+
this.steps.push({
52+
type: "union",
53+
parent: rootX,
54+
child: rootY,
55+
});
56+
} else {
57+
this.parent[rootY] = rootX;
58+
this.rank[rootX]++;
59+
this.steps.push({
60+
type: "union-rank",
61+
parent: rootX,
62+
child: rootY,
63+
});
64+
}
65+
}
66+
67+
getSteps() {
68+
return this.steps;
69+
}
70+
}
71+
72+
export default UnionFind;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// src/components/graph/UnionFindVisualizer.jsx
2+
import { motion } from "framer-motion";
3+
4+
export default function UnionFindVisualizer({ nodes, parent, highlights }) {
5+
// Calculate positions for nodes in a grid layout
6+
const getNodePosition = (index) => {
7+
const cols = Math.ceil(Math.sqrt(nodes.length));
8+
const row = Math.floor(index / cols);
9+
const col = index % cols;
10+
return { row, col };
11+
};
12+
13+
// Generate connections for visualization
14+
const connections = [];
15+
nodes.forEach((node, index) => {
16+
if (parent[index] !== index) {
17+
const parentPos = getNodePosition(parent[index]);
18+
const childPos = getNodePosition(index);
19+
connections.push({
20+
from: childPos,
21+
to: parentPos,
22+
child: index,
23+
parent: parent[index]
24+
});
25+
}
26+
});
27+
28+
return (
29+
<div className="relative">
30+
{/* SVG for connections */}
31+
<svg
32+
className="absolute inset-0 w-full h-full pointer-events-none"
33+
style={{ minHeight: '300px' }}
34+
>
35+
{connections.map((conn, index) => {
36+
const fromX = (conn.from.col + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';
37+
const fromY = (conn.from.row + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';
38+
const toX = (conn.to.col + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';
39+
const toY = (conn.to.row + 0.5) * (100 / Math.ceil(Math.sqrt(nodes.length))) + '%';
40+
41+
const isHighlighted = highlights.union.includes(conn.child) ||
42+
highlights.current === conn.child ||
43+
highlights.root === conn.parent;
44+
45+
return (
46+
<motion.line
47+
key={index}
48+
x1={fromX}
49+
y1={fromY}
50+
x2={toX}
51+
y2={toY}
52+
stroke={isHighlighted ? "#ef4444" : "#6b7280"}
53+
strokeWidth={isHighlighted ? "3" : "2"}
54+
strokeDasharray={isHighlighted ? "5,5" : "none"}
55+
initial={{ pathLength: 0 }}
56+
animate={{ pathLength: 1 }}
57+
transition={{ duration: 0.5, delay: index * 0.1 }}
58+
/>
59+
);
60+
})}
61+
</svg>
62+
63+
{/* Nodes */}
64+
<div className="grid gap-4 justify-center" style={{
65+
gridTemplateColumns: `repeat(${Math.ceil(Math.sqrt(nodes.length))}, 1fr)`,
66+
maxWidth: '600px',
67+
margin: '0 auto'
68+
}}>
69+
{nodes.map((node, index) => {
70+
const isRoot = parent[index] === index;
71+
const color =
72+
highlights.current === index
73+
? "bg-blue-500"
74+
: highlights.root === index
75+
? "bg-green-500"
76+
: highlights.union.includes(index)
77+
? "bg-red-500"
78+
: highlights.path.includes(index)
79+
? "bg-purple-500"
80+
: "bg-gray-600";
81+
82+
return (
83+
<motion.div
84+
key={index}
85+
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`}
86+
layout
87+
animate={{
88+
scale: highlights.current === index ? 1.2 : 1,
89+
boxShadow: highlights.current === index ? "0 0 20px rgba(59, 130, 246, 0.5)" : "0 4px 6px rgba(0, 0, 0, 0.1)"
90+
}}
91+
transition={{ duration: 0.3 }}
92+
>
93+
<div className="text-lg font-bold">{node}</div>
94+
<div className="text-xs opacity-80">p:{parent[index]}</div>
95+
{isRoot && (
96+
<div className="absolute -top-2 -right-2 w-6 h-6 bg-yellow-400 rounded-full flex items-center justify-center">
97+
<span className="text-[10px] font-bold text-black">R</span>
98+
</div>
99+
)}
100+
{highlights.current === index && (
101+
<div className="absolute -top-3 -left-3 w-6 h-6 bg-blue-400 rounded-full flex items-center justify-center animate-pulse">
102+
<span className="text-[10px] font-bold text-white">F</span>
103+
</div>
104+
)}
105+
</motion.div>
106+
);
107+
})}
108+
</div>
109+
110+
{/* Additional Info */}
111+
<div className="mt-6 text-center text-sm text-gray-300">
112+
<p>Each node shows: <span className="text-white font-semibold">Node Number</span> | <span className="text-gray-400">Parent</span></p>
113+
<p className="mt-1">
114+
<span className="text-yellow-400">R</span> = Root |
115+
<span className="text-blue-400"> F</span> = Finding |
116+
<span className="text-red-400"> Red</span> = Unioning
117+
</p>
118+
</div>
119+
</div>
120+
);
121+
}

src/pages/Homepage.jsx

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ import { ArrowRight, Github } from "lucide-react";
33
import sort from "../assets/sorting.png"
44
import { useNavigate } from "react-router-dom";
55

6-
76
const sections = [
87
{
98
title: "Sorting Algorithms",
109
description:
1110
"Visualize step-by-step how sorting algorithms organize data efficiently.",
1211
phase: "Phase 1 (MVP)",
1312
img: "https://tamimehsan.github.io/AlgorithmVisualizer/images/sort.png?height=200&width=300",
14-
link: "/sorting",
15-
flag: false
13+
link: "/sorting",
14+
flag: false
1615
},
1716
{
1817
title: "Searching Algorithms",
@@ -35,9 +34,10 @@ const sections = [
3534
{
3635
title: "Graph Algorithms",
3736
description:
38-
"Explore BFS, DFS, Kruskal’s, Prim’s, and more — all brought to life interactively.",
37+
"Explore BFS, DFS, Kruskal’s, Prim’s, and now Union-Find — all brought to life interactively.",
3938
phase: "Phase 2",
4039
img: "",
40+
route: "/graph/union-find", // ✅ Route to Union-Find page
4141
link: "/graphs",
4242
flag: true
4343
},
@@ -47,6 +47,7 @@ const sections = [
4747
"Visualize recursive calls and backtracking patterns like N-Queens or Sudoku.",
4848
phase: "Phase 2",
4949
img: "",
50+
route: "",
5051
link: "/recursion",
5152
flag: true
5253
},
@@ -56,6 +57,7 @@ const sections = [
5657
"Interactively understand stacks, queues, linked lists, trees, and heaps.",
5758
phase: "Phase 2",
5859
img: "",
60+
route: "",
5961
link: "/data-structures",
6062
flag: true
6163
},
@@ -65,18 +67,18 @@ const sections = [
6567
"Step through state transitions and table updates to grasp DP intuitively.",
6668
phase: "Phase 3",
6769
img: "",
70+
route: "",
6871
link: "/dynamic-programming",
6972
flag: true
7073
},
7174
];
7275

73-
7476
const Homepage = () => {
7577
const navigate = useNavigate();
7678

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

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

9496
<div className="mt-8 flex flex-wrap gap-4 justify-center">
95-
<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">
97+
<button
98+
onClick={() => window.scrollTo({ top: 600, behavior: "smooth" })}
99+
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"
100+
>
96101
Explore Now <ArrowRight size={18} />
97102
</button>
98103
<a
@@ -132,17 +137,24 @@ const Homepage = () => {
132137
</p>
133138

134139
{/* Explore Button */}
135-
<button className="mt-4 text-indigo-400 font-semibold flex items-center gap-1 hover:gap-2 transition-all">
140+
<button
141+
onClick={() =>
142+
section.route
143+
? navigate(section.route)
144+
: alert("Coming soon 🚀")
145+
}
146+
className="mt-4 text-indigo-400 font-semibold flex items-center gap-1 hover:gap-2 transition-all"
147+
>
136148
Explore <ArrowRight size={16} />
137149
</button>
138150
</div>
139151

140152
{/* SaaS-style “Coming Soon” Overlay */}
141-
{section.flag && (
142-
<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">
143-
Coming Soon 🚀
144-
</div>
145-
)}
153+
{section.flag && (
154+
<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">
155+
Coming Soon 🚀
156+
</div>
157+
)}
146158
</div>
147159
))}
148160
</section>

0 commit comments

Comments
 (0)