Skip to content

Commit f032030

Browse files
committed
resolved issue
1 parent 4511154 commit f032030

5 files changed

Lines changed: 311 additions & 2 deletions

File tree

src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
33
import UnionFindPage from "../src/pages/graph/UnionFind.jsx"; // ✅ Import Union-Find Page
44
import SortingPage from "./pages/sorting/SortingPage";
5+
import Kruskal from "./pages/graph/Kruskal.jsx";
56
import Homepage from "./pages/Homepage.jsx";
67
function App() {
78
return (
@@ -10,6 +11,7 @@ function App() {
1011
<Route path="/" element={<Homepage />} />
1112
<Route path="/graph/union-find" element={<UnionFindPage />} /> {/* ✅ Added route */}
1213
<Route path="/sorting" element={<SortingPage />} />
14+
<Route path="/graph/kruskal" element={<Kruskal />} />
1315
</Routes>
1416
</Router>
1517
);

src/algorithms/graph/kruskal.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// src/algorithms/graph/kruskal.js
2+
3+
class DSU {
4+
constructor(n) {
5+
this.parent = Array.from({ length: n }, (_, i) => i);
6+
this.rank = Array(n).fill(0);
7+
}
8+
9+
find(x) {
10+
if (this.parent[x] !== x) {
11+
this.parent[x] = this.find(this.parent[x]);
12+
}
13+
return this.parent[x];
14+
}
15+
16+
union(x, y) {
17+
const rx = this.find(x);
18+
const ry = this.find(y);
19+
if (rx === ry) return false;
20+
21+
if (this.rank[rx] < this.rank[ry]) this.parent[rx] = ry;
22+
else if (this.rank[rx] > this.rank[ry]) this.parent[ry] = rx;
23+
else {
24+
this.parent[ry] = rx;
25+
this.rank[rx]++;
26+
}
27+
return true;
28+
}
29+
30+
getState() {
31+
return [...this.parent];
32+
}
33+
}
34+
35+
// 🧠 Generator that yields every visualization step
36+
export function* kruskalSteps(edges, nodeCount) {
37+
const sortedEdges = [...edges].sort((a, b) => a.weight - b.weight);
38+
const dsu = new DSU(nodeCount);
39+
const mst = [];
40+
41+
for (let edge of sortedEdges) {
42+
yield { type: "consider", edge, dsu: dsu.getState() };
43+
44+
const merged = dsu.union(edge.from - 1, edge.to - 1);
45+
if (merged) {
46+
mst.push(edge);
47+
yield { type: "add", edge, mst: [...mst], dsu: dsu.getState() };
48+
} else {
49+
yield { type: "skip", edge, dsu: dsu.getState() };
50+
}
51+
}
52+
53+
yield { type: "done", mst, dsu: dsu.getState() };
54+
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import React, { useState, useRef } from "react";
2+
import { kruskalSteps } from "../../algorithms/graph/kruskal";
3+
4+
export default function KruskalVisualizer() {
5+
const svgRef = useRef(null);
6+
const [nodes, setNodes] = useState([]);
7+
const [edges, setEdges] = useState([]);
8+
const [selected, setSelected] = useState({ from: null, to: null });
9+
const [running, setRunning] = useState(false);
10+
const [status, setStatus] = useState("Idle");
11+
const [stepIndex, setStepIndex] = useState(0);
12+
const [dsuState, setDsuState] = useState([]);
13+
const [highlight, setHighlight] = useState(null);
14+
15+
// 🟢 Add node
16+
const addNode = (e) => {
17+
if (running) return;
18+
const rect = svgRef.current.getBoundingClientRect();
19+
const x = e.clientX - rect.left;
20+
const y = e.clientY - rect.top;
21+
const newNode = {
22+
id: nodes.length + 1,
23+
x,
24+
y,
25+
label: `N${nodes.length + 1}`,
26+
};
27+
setNodes([...nodes, newNode]);
28+
};
29+
30+
// 🟣 Add edges
31+
const handleNodeClick = (id) => {
32+
if (running) return;
33+
if (!selected.from) {
34+
setSelected({ from: id, to: null });
35+
} else if (selected.from && !selected.to && selected.from !== id) {
36+
const weight = parseInt(prompt("Enter edge weight:"), 10);
37+
if (!isNaN(weight)) {
38+
setEdges([
39+
...edges,
40+
{ from: selected.from, to: id, weight, colorClass: "stroke-gray-500" },
41+
]);
42+
}
43+
setSelected({ from: null, to: null });
44+
}
45+
};
46+
47+
// 🧩 Kruskal visualization
48+
const startVisualization = async () => {
49+
if (running || edges.length === 0) return;
50+
setRunning(true);
51+
setStatus("Running...");
52+
const steps = kruskalSteps(edges, nodes.length);
53+
const interval = 1000;
54+
55+
for (let step of steps) {
56+
const { type, edge, dsu } = step;
57+
setDsuState(dsu || []);
58+
setHighlight({ from: edge.from - 1, to: edge.to - 1 });
59+
60+
setEdges((prev) =>
61+
prev.map((e) =>
62+
e.from === edge.from && e.to === edge.to
63+
? {
64+
...e,
65+
colorClass:
66+
type === "consider"
67+
? "stroke-blue-400"
68+
: type === "add"
69+
? "stroke-green-400"
70+
: "stroke-red-500",
71+
}
72+
: e
73+
)
74+
);
75+
76+
setStatus(
77+
type === "consider"
78+
? `Considering edge (${edge.from}, ${edge.to})`
79+
: type === "add"
80+
? `Added edge (${edge.from}, ${edge.to}) to MST`
81+
: type === "skip"
82+
? `Skipped edge (${edge.from}, ${edge.to}) — forms cycle`
83+
: "Done"
84+
);
85+
86+
await new Promise((r) => setTimeout(r, interval));
87+
setStepIndex((i) => i + 1);
88+
}
89+
90+
setRunning(false);
91+
setStatus("Completed!");
92+
};
93+
94+
const resetGraph = () => {
95+
setNodes([]);
96+
setEdges([]);
97+
setSelected({ from: null, to: null });
98+
setRunning(false);
99+
setStatus("Idle");
100+
setDsuState([]);
101+
setStepIndex(0);
102+
};
103+
104+
// 🧩 Render edges
105+
const renderEdges = () =>
106+
edges.map((edge, i) => {
107+
const fromNode = nodes.find((n) => n.id === edge.from);
108+
const toNode = nodes.find((n) => n.id === edge.to);
109+
if (!fromNode || !toNode) return null;
110+
111+
const { x: x1, y: y1 } = fromNode;
112+
const { x: x2, y: y2 } = toNode;
113+
const cx = (x1 + x2) / 2;
114+
const cy = (y1 + y2) / 2;
115+
116+
return (
117+
<g key={i}>
118+
<line
119+
x1={x1}
120+
y1={y1}
121+
x2={x2}
122+
y2={y2}
123+
strokeWidth={4}
124+
className={`${edge.colorClass} transition-all duration-500`}
125+
strokeLinecap="round"
126+
/>
127+
<text
128+
x={cx}
129+
y={cy - 8}
130+
fontSize={12}
131+
textAnchor="middle"
132+
className="fill-indigo-300"
133+
>
134+
{edge.weight}
135+
</text>
136+
</g>
137+
);
138+
});
139+
140+
// 🧩 Render nodes
141+
const renderNodes = () =>
142+
nodes.map((n) => (
143+
<g
144+
key={n.id}
145+
transform={`translate(${n.x}, ${n.y})`}
146+
onClick={() => handleNodeClick(n.id)}
147+
className="cursor-pointer"
148+
>
149+
<circle
150+
r={18}
151+
className={`stroke-indigo-400 stroke-2 ${
152+
selected.from === n.id ? "fill-indigo-700" : "fill-gray-900"
153+
} transition-all duration-300`}
154+
/>
155+
<text
156+
x={0}
157+
y={5}
158+
textAnchor="middle"
159+
className="fill-indigo-200 font-semibold"
160+
>
161+
{n.label}
162+
</text>
163+
</g>
164+
));
165+
166+
return (
167+
<div className="w-full">
168+
{/* Controls */}
169+
<div className="flex gap-4 mb-6 justify-center">
170+
<button
171+
className={`px-6 py-2 rounded-lg font-semibold text-white shadow-md transition-all duration-300 ${
172+
running
173+
? "bg-indigo-800 text-gray-400 cursor-not-allowed"
174+
: "bg-indigo-600 hover:bg-indigo-500"
175+
}`}
176+
onClick={startVisualization}
177+
disabled={running}
178+
>
179+
{running ? "Visualizing..." : "Start Visualization"}
180+
</button>
181+
182+
<button
183+
onClick={resetGraph}
184+
className="bg-gray-700 hover:bg-gray-600 px-6 py-2 rounded-lg text-white font-semibold shadow-md transition-all duration-300"
185+
>
186+
Reset
187+
</button>
188+
</div>
189+
190+
<div className="text-sm text-center text-indigo-300 mb-6">
191+
<p>Status: <span className="font-medium text-indigo-400">{status}</span></p>
192+
<p>Step: <span className="font-medium text-indigo-400">{stepIndex}</span></p>
193+
</div>
194+
195+
{/* Layout: DSU Panel + Graph */}
196+
<div className="flex w-full max-w-6xl">
197+
{/* Left DSU Panel */}
198+
<div className="w-1/4 bg-gray-900 border border-gray-700 rounded-lg p-3 mr-6 shadow-lg">
199+
<h2 className="text-lg font-bold mb-3 text-center text-indigo-400">
200+
Disjoint Set (DSU)
201+
</h2>
202+
203+
<div className="flex flex-wrap justify-center gap-3">
204+
{nodes.map((n, i) => (
205+
<div key={n.id} className="relative text-center">
206+
<div className="text-sm font-medium text-indigo-300 mb-1">
207+
{n.label}
208+
</div>
209+
<div
210+
className={`w-10 h-10 flex items-center justify-center rounded-full border border-indigo-500 ${
211+
dsuState[i] === i ? "bg-green-700" : "bg-indigo-800"
212+
}`}
213+
>
214+
<span className="text-indigo-200 font-semibold">
215+
N{(dsuState[i] ?? i) + 1}
216+
</span>
217+
</div>
218+
</div>
219+
))}
220+
</div>
221+
</div>
222+
223+
{/* Right Graph */}
224+
<div className="flex-1 border border-gray-700 rounded-lg overflow-hidden shadow-lg">
225+
<svg
226+
ref={svgRef}
227+
onDoubleClick={addNode}
228+
width="100%"
229+
height={500}
230+
viewBox="0 0 800 500"
231+
className="bg-gray-950"
232+
>
233+
{renderEdges()}
234+
{renderNodes()}
235+
</svg>
236+
</div>
237+
</div>
238+
</div>
239+
);
240+
}

src/pages/Homepage.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ const sections = [
3636
description:
3737
"Explore BFS, DFS, Kruskal’s, Prim’s, and now Union-Find — all brought to life interactively.",
3838
phase: "Phase 2",
39-
img: "",
39+
img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT6OVkS24HaByB5Ha5Og7YYUYu7ZmfM-wfWQw&sz=300x200",
4040
route: "/graph/union-find", // ✅ Route to Union-Find page
4141
link: "/graphs",
42-
flag: true
42+
flag: false
4343
},
4444
{
4545
title: "Recursion & Backtracking",

src/pages/graph/Kruskal.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from "react";
2+
import KruskalVisualizer from "../../components/graph/KruskalVisualizer";
3+
4+
export default function Kruskal() {
5+
return (
6+
<div className="min-h-screen bg-black text-gray-200 flex flex-col items-center p-6">
7+
<h1 className="text-4xl font-extrabold mb-8 text-indigo-400 drop-shadow-lg">
8+
Kruskal’s Minimum Spanning Tree Visualizer
9+
</h1>
10+
<KruskalVisualizer />
11+
</div>
12+
);
13+
}

0 commit comments

Comments
 (0)