Skip to content

Commit ac8d36a

Browse files
committed
Adding rest of website
1 parent 547ee74 commit ac8d36a

File tree

8 files changed

+985
-190
lines changed

8 files changed

+985
-190
lines changed

package-lock.json

Lines changed: 69 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"class-variance-authority": "^0.7.1",
2020
"clsx": "^2.1.1",
2121
"lucide-react": "^0.515.0",
22+
"motion": "^12.19.1",
2223
"next": "15.3.3",
2324
"react": "^19.0.0",
2425
"react-dom": "^19.0.0",
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"use client";
2+
3+
import { motion, useDragControls } from "motion/react";
4+
import { useHasseContext } from "./HasseProvider";
5+
import { ReactNode, useEffect } from "react";
6+
7+
export default function HasseCanvas() {
8+
const {
9+
transitiveReduction,
10+
elements,
11+
12+
currentStep,
13+
setCurrentStep,
14+
totalSteps,
15+
paused,
16+
setPaused,
17+
18+
speedOptions,
19+
speedIndex,
20+
} = useHasseContext();
21+
22+
const controls = useDragControls();
23+
24+
function getGraphSteps() {
25+
const steps: ReactNode[] = [];
26+
27+
const addedNodes: boolean[] = Array(transitiveReduction.length).fill(false);
28+
const [cols, nodeLevels]: [number[], number[]] = getNodeLevels();
29+
30+
function getNodeLevels(): [number[], number[]] {
31+
const n = transitiveReduction.length;
32+
const levels = new Array(n).fill(0);
33+
34+
const visited = new Set<number>();
35+
36+
function dfs(node: number): number {
37+
if (visited.has(node)) return levels[node];
38+
visited.add(node);
39+
40+
let maxPredecessorLevel = 0;
41+
for (let i = 0; i < n; i++) {
42+
if (transitiveReduction[i][node] === 1) {
43+
maxPredecessorLevel = Math.max(maxPredecessorLevel, dfs(i) + 1);
44+
}
45+
}
46+
47+
levels[node] = maxPredecessorLevel;
48+
return levels[node];
49+
}
50+
51+
for (let i = 0; i < n; i++) {
52+
dfs(i);
53+
}
54+
55+
const counts = Array(Math.max(...levels) + 1).fill(0);
56+
const cols = Array(n).fill(0);
57+
58+
for (let i = 0; i < n; i++) {
59+
cols[i] = counts[levels[i]];
60+
counts[levels[i]]++;
61+
}
62+
63+
return [cols, levels];
64+
}
65+
66+
function graphNode(step: number, x: number, y: number) {
67+
return (
68+
<motion.g key={step} transform={`translate(${500 + x}, ${500 + y})`}>
69+
<motion.circle
70+
r={50}
71+
fill="skyblue"
72+
initial={{ scale: 0, opacity: 0 }}
73+
animate={{ scale: 1, opacity: 1 }}
74+
transition={{ duration: 0.5 }}
75+
/>
76+
<text
77+
textAnchor="middle"
78+
dominantBaseline="middle"
79+
className="fill-black text-sm font-semibold"
80+
>
81+
{elements[step]}
82+
</text>
83+
</motion.g>
84+
);
85+
}
86+
87+
function graphEdge(from: number, to: number) {
88+
return (
89+
<motion.line
90+
key={`${from}-${to}`}
91+
x1={500 + 125 * cols[from]}
92+
y1={500 - nodeLevels[from] * 125}
93+
x2={500 + 125 * cols[to]}
94+
y2={500 - nodeLevels[to] * 125}
95+
stroke="black"
96+
strokeWidth={2}
97+
initial={{ pathLength: 0 }}
98+
animate={{ pathLength: 1 }}
99+
transition={{ duration: 0.6 }}
100+
/>
101+
);
102+
}
103+
104+
for (let i = 0; i < transitiveReduction.length; i++) {
105+
for (let j = 0; j < transitiveReduction[i].length; j++) {
106+
if (transitiveReduction[i][j] === 0) continue;
107+
108+
if (!addedNodes[i]) {
109+
steps.push(graphNode(i, 125 * cols[i], -nodeLevels[i] * 125));
110+
addedNodes[i] = true;
111+
}
112+
113+
if (!addedNodes[j]) {
114+
steps.push(graphNode(j, 125 * cols[j], -nodeLevels[j] * 125));
115+
addedNodes[j] = true;
116+
}
117+
118+
steps.push(graphEdge(i, j));
119+
}
120+
}
121+
122+
for (let i = 0; i < addedNodes.length; i++) {
123+
if (!addedNodes[i]) {
124+
steps.push(graphNode(i, 125 * cols[i], -nodeLevels[i] * 125));
125+
}
126+
}
127+
128+
return steps;
129+
}
130+
131+
let steps: ReactNode[] = [];
132+
133+
// Update steps whenever transitiveReduction changes
134+
if (transitiveReduction.length > 0) {
135+
steps = getGraphSteps();
136+
}
137+
138+
useEffect(() => {
139+
if (paused || currentStep >= totalSteps) return;
140+
141+
console.log("Current step:", currentStep, "Total steps:", totalSteps);
142+
143+
const interval = setInterval(() => {
144+
setCurrentStep(prev => {
145+
if (prev >= totalSteps) {
146+
clearInterval(interval);
147+
setPaused(true);
148+
return prev;
149+
}
150+
return prev + 1;
151+
});
152+
}, 1000 / speedOptions[speedIndex]);
153+
154+
return () => clearInterval(interval);
155+
}, [paused, speedIndex, totalSteps]);
156+
157+
return (
158+
<svg
159+
width="100%"
160+
height="100vh"
161+
onPointerDown={e => {
162+
controls.start(e);
163+
e.currentTarget.classList.add("cursor-grabbing");
164+
}}
165+
onPointerUp={e => {
166+
e.currentTarget.classList.remove("cursor-grabbing");
167+
}}
168+
className="cursor-grab"
169+
id="hasse-diagram"
170+
>
171+
<motion.g
172+
drag
173+
dragControls={controls}
174+
dragConstraints={{ right: -400 }}
175+
style={{ cursor: "grab" }}
176+
>
177+
{steps.slice(0, currentStep)}
178+
</motion.g>
179+
</svg>
180+
);
181+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"use client";
2+
3+
import {
4+
createContext,
5+
ReactNode,
6+
useContext,
7+
useState,
8+
Dispatch,
9+
SetStateAction,
10+
} from "react";
11+
12+
type HasseContextType = {
13+
poset: number[][];
14+
setPoset: Dispatch<SetStateAction<number[][]>>;
15+
transitiveReduction: number[][];
16+
setTransitiveReduction: Dispatch<SetStateAction<number[][]>>;
17+
elementsType: string;
18+
setElementsType: Dispatch<SetStateAction<string>>;
19+
elements: number[];
20+
setElements: Dispatch<SetStateAction<number[]>>;
21+
rangeStart: number | "";
22+
setRangeStart: Dispatch<SetStateAction<number | "">>;
23+
rangeEnd: number | "";
24+
setRangeEnd: Dispatch<SetStateAction<number | "">>;
25+
setInput: string;
26+
setSetInput: Dispatch<SetStateAction<string>>;
27+
relation: string;
28+
setRelation: Dispatch<SetStateAction<string>>;
29+
currentStep: number;
30+
setCurrentStep: Dispatch<SetStateAction<number>>;
31+
totalSteps: number;
32+
setTotalSteps: Dispatch<SetStateAction<number>>;
33+
paused: boolean;
34+
setPaused: Dispatch<SetStateAction<boolean>>;
35+
speedOptions: number[];
36+
speedIndex: number;
37+
setSpeedIndex: Dispatch<SetStateAction<number>>;
38+
};
39+
40+
const HasseContext = createContext<HasseContextType | undefined>(undefined);
41+
42+
export default function HasseProvider({ children }: { children: ReactNode }) {
43+
const [poset, setPoset] = useState<number[][]>([]);
44+
const [transitiveReduction, setTransitiveReduction] = useState<number[][]>(
45+
[]
46+
);
47+
48+
const [elementsType, setElementsType] = useState<string>("range");
49+
const [elements, setElements] = useState<number[]>([]);
50+
51+
const [rangeStart, setRangeStart] = useState<number | "">("");
52+
const [rangeEnd, setRangeEnd] = useState<number | "">("");
53+
const [setInput, setSetInput] = useState("");
54+
55+
const [relation, setRelation] = useState<string>("divisibility");
56+
57+
const [currentStep, setCurrentStep] = useState(0);
58+
const [totalSteps, setTotalSteps] = useState(0);
59+
const [paused, setPaused] = useState(true);
60+
61+
const speedOptions = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4, 8, 16];
62+
const [speedIndex, setSpeedIndex] = useState(3);
63+
64+
return (
65+
<HasseContext.Provider
66+
value={{
67+
poset,
68+
setPoset,
69+
transitiveReduction,
70+
setTransitiveReduction,
71+
72+
elementsType,
73+
setElementsType,
74+
elements,
75+
setElements,
76+
77+
rangeStart,
78+
setRangeStart,
79+
rangeEnd,
80+
setRangeEnd,
81+
setInput,
82+
setSetInput,
83+
84+
relation,
85+
setRelation,
86+
87+
currentStep,
88+
setCurrentStep,
89+
totalSteps,
90+
setTotalSteps,
91+
92+
paused,
93+
setPaused,
94+
speedOptions,
95+
speedIndex,
96+
setSpeedIndex,
97+
}}
98+
>
99+
{children}
100+
</HasseContext.Provider>
101+
);
102+
}
103+
104+
export function useHasseContext() {
105+
const context = useContext(HasseContext);
106+
if (!context) {
107+
throw new Error("useHasseContext must be used within a HasseProvider");
108+
}
109+
return context;
110+
}

0 commit comments

Comments
 (0)