Skip to content

Commit

Permalink
Add sorting visualisation page
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabau committed Oct 12, 2024
1 parent 4b8ac4e commit ac14f36
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 90 deletions.
59 changes: 59 additions & 0 deletions gabau.github.io/src/components/games/SortVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Stage } from "@pixi/react";
import { Graphics as GraphicComponent } from "@pixi/react";
import { Color, Graphics } from "pixi.js";
import { useCallback, useContext } from "react";
import ThemeContext from "../../context/ThemeContext";

const sortingColor = {
dark: {
bgColor: new Color("black"),
barColor: new Color("green"),
selectedColor: new Color("white")
},
light: {
bgColor: new Color("white"),
barColor: new Color("black"),
selectedColor: new Color("grey")
}
}

const SortVisualizer: React.FC<{
values: number[],
currentPosition: number,
width: number,
height: number,
base_offset?: number,
gap?: number
}> = ({ gap, values, width, height, currentPosition, base_offset }) => {
const {theme} = useContext(ThemeContext);
const currColors = theme === "dark" ? sortingColor.dark : sortingColor.light;
const m = base_offset ? base_offset : 100;
const draw = useCallback((g: Graphics) => {
g.clear();
g.beginFill(currColors.bgColor);
g.drawRect(0, 0, width, height);
// render each of the values in the item
let max_val = values[0];
let min_val = values[0];
for (const v of values) {
max_val = Math.max(v, max_val);
min_val = Math.min(v, min_val);
}
const w = width / values.length;

const gapWrapper = gap ? gap : 0.5 * w;
for (let i = 0; i < values.length; ++i) {
const perc = Math.round( (values[i] - min_val) / (max_val - min_val) * (height - m)) + m;
g.beginFill(currColors.barColor);
if (currentPosition === i) {
g.beginFill(currColors.selectedColor);
}
g.drawRect(i*w, height - perc, w - gapWrapper, perc);
}
}, [currColors.bgColor, currColors.barColor, currColors.selectedColor, width, height, values, gap, m, currentPosition]);
return <Stage width={width} height={height}>
<GraphicComponent draw={draw} />
</Stage>
}

export default SortVisualizer;
11 changes: 11 additions & 0 deletions gabau.github.io/src/components/layouts/CenteredDivLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@


export default function CenteredDivLayout({ children }: {
children: JSX.Element | string
}) {
return <div className="flex flex-col items-center justify-center">
<div className="flex flex-row items-center justify-center">
{children}
</div>
</div>
}
32 changes: 3 additions & 29 deletions gabau.github.io/src/pages/Playground.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Link } from "react-router-dom";
import TypeWriterHeader from "../components/TypeWriterHeader";
import { playgroundRoutes } from "../routes/playground";

const PlayGroundCard = ({
title,
Expand Down Expand Up @@ -29,47 +30,20 @@ const PlayGroundCard = ({
// };
// }

const playgrounds = [
{
title: "Terminal",
description: "Simple terminal that runs in the browser",
link: "/play/terminal",
},
{
title: "A Page",
description: "Just a page",
link: "/play/apage",
},
{
title: "Repeating text",
description: "Simple repeating animation",
link: "/play/rotating",
},
{
title: "Animated Sand",
description: "Simple sand falling in pixi.js",
link: "/play/sand",
},
{
title: "Pong",
description: "Pong game",
link: "/play/pong",
},
];

export default function PlayGround() {
return (
<>
<TypeWriterHeader title="Playground" timeout={300} className="p-10" />
<p>For the miscellaneous stuff</p>
<div className="flex flex-wrap flex-row h-auto w-full justify-center">
{playgrounds.map((v) => {
{playgroundRoutes.map((v) => {
return (
<PlayGroundCard
key={v.title}
title={v.title}
description={v.description}
link={v.link}
link={`/play/${v.path}`}
/>
);
})}
Expand Down
193 changes: 193 additions & 0 deletions gabau.github.io/src/pages/fun/SortVisPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { useEffect, useState } from "react";
import SortVisualizer from "../../components/games/SortVisualizer";
import CenteredDivLayout from "../../components/layouts/CenteredDivLayout";

// stores the states for partition sort
class PartitionSortState {
partitions: { start: number; end: number }[];
currPos: number;
frontPointer: number;
start: number;
end: number;
constructor() {
this.partitions = [];
this.currPos = 0;
this.frontPointer = 0;
this.start = 0;
this.end = 0;
}

swap(values: number[], i: number, j: number) {
if (i >= values.length || j >= values.length) return;
const tmp = values[i];
values[i] = values[j];
values[j] = tmp;
}

getNextStep(values: number[]) {
if (
this.currPos >= this.end ||
this.frontPointer >= this.end ||
this.start >= this.end
) {
// push the current partitions onto the stack
if (this.start < this.end) {
if (this.frontPointer !== this.end) {
this.partitions.push({
start: this.start,
end: this.frontPointer,
});
}
if (this.start !== this.frontPointer + 1) {
this.partitions.push({
start: this.frontPointer + 1,
end: this.end,
});
}
}

// console.log(values);
// return "finished"
// reset the position to the partitions
// Finshed sorting
if (this.partitions.length === 0) {
return "finished";
}
this.currPos = this.partitions[0].start + 1;
this.frontPointer = this.partitions[0].start;
this.end = this.partitions[0].end;
this.start = this.frontPointer;
this.partitions = this.partitions.slice(1);
}

// if (values[this.frontPointer] > values[this.frontPointer + 1]) {
// this.swap(values, this.frontPointer, this.frontPointer + 1);
// this.frontPointer += 1;
// this.currPos = Math.max(this.frontPointer + 1, this.currPos);
// return values;
// }
if (values[this.currPos] <= values[this.frontPointer]) {
this.swap(values, this.currPos, this.frontPointer + 1);
this.swap(values, this.frontPointer, this.frontPointer + 1);
this.frontPointer += 1;
}
this.currPos += 1;
return values;
}
}

export default function SortVisPage() {
const [values, setValues] = useState<number[]>([4, 0]);
const [partSort] = useState(new PartitionSortState());
const [startAnim, setStartAnim] = useState(false);
const [currPosition, setCurrPosition] = useState(0);
const [arraySize, setArraySize] = useState(40);
const [animating, setAnimating] = useState(false);
const [intervalTime, setIntervalTime] = useState(10);
const [currTimeout, setCurrTimeout] = useState<number | null>(null);
useEffect(() => {
setValues(Array.from({ length: 40 }, () => Math.floor(Math.random() * 40)));
}, []);

useEffect(() => {
if (!startAnim) return;
partSort.start = 0;
partSort.end = values.length;
partSort.frontPointer = 0;
partSort.currPos = 1;
const f = () => {
if (!animating) return;
const r = partSort.getNextStep(values);
if (r === "finished") {
setAnimating(false);
return;
}
setCurrPosition(partSort.currPos);
setCurrTimeout(setTimeout(f, intervalTime));
};
const t = setTimeout(f, intervalTime);
return () => clearTimeout(t);
}, [partSort, startAnim, values, animating, intervalTime]);

return (
<CenteredDivLayout>
<div className="flex flex-row bg-slate-500 dark:bg-slate-800">
<div className="flex flex-col items-center w-full h-full flex-auto m-8 space-y-2">
<div className="flex flex-row flex-wrap">
<button
onClick={() => {
if (animating) return;
setAnimating(true);
setStartAnim(true);
setTimeout(() => setStartAnim(false), 1000);
}}
className="bg-gray-200 hover:bg-gray-400 dark:hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-700 dark:focus:ring-gray-700 dark:border-gray-700"
>
Start Animation
</button>
<button
className="bg-gray-200 hover:bg-gray-400 dark:hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-700 dark:focus:ring-gray-700 dark:border-gray-700"
onClick={() => {
setValues(
Array.from({ length: arraySize }, () =>
Math.floor(Math.random() * arraySize)
)
);
}}
>
Randomise array
</button>
<button
className="text-red-800 dark:text-red-300 bg-gray-200 hover:bg-gray-400 dark:hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-700 dark:focus:ring-gray-700 dark:border-gray-700"
onClick={() => {
if (currTimeout !== null) {
clearTimeout(currTimeout);
}
setAnimating(false);
}}
>
Stop Sorting
</button>
</div>
<label>Set the number of values to sort</label>
<input
type="number"
max="1000"
value={arraySize}
onChange={(e) => setArraySize(e.target.value as unknown as number)}
className="rounded-lg bg-slate-200 dark:bg-slate-600 p-3 shadow my-5"
/>
<input
type="range"
max="1000"
value={arraySize}
onChange={(e) => setArraySize(e.target.value as unknown as number)}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>

<label>Set the time in miliseconds </label>
<input
type="number"
max="1000"
value={intervalTime}
onChange={(e) => setIntervalTime(e.target.value as unknown as number)}
className="rounded-lg bg-slate-200 dark:bg-slate-600 p-3 shadow my-5"
/>
<input
type="range"
max="1000"
value={intervalTime}
onChange={(e) => setIntervalTime(e.target.value as unknown as number)}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<SortVisualizer
values={values}
currentPosition={currPosition}
width={800}
height={800}
/>
</div>
</CenteredDivLayout>
);
}
62 changes: 2 additions & 60 deletions gabau.github.io/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import About from "./pages/About";
import Chess from "./components/games/Chess";
import PlayGround from "./pages/Playground";
import NavBarWrapper from "./components/NavBarWrapper";
import Terminal from "./components/Terminal";
import TypeWriterHeader from "./components/TypeWriterHeader";
import APage from "./pages/fun/APage";
import RepeatingBannerPage from "./pages/fun/RepeatingBannerPage";
import AnimatedSandPage from "./pages/fun/AnimatedSandPage";
import PongPage from "./pages/fun/PongPage";
import { playgroundRoutes } from "./routes/playground";

const routes = [
{
Expand Down Expand Up @@ -45,60 +40,7 @@ const routes = [
},
{
path: "/play",
children: [
{
path: "terminal",
element: (
<div>
<TypeWriterHeader
title="Terminal"
timeout={300}
className="p-10"
/>
<p>Basic unix-like terminal. No persistant storage</p>
<Terminal />
</div>
),
},
{
path: "apage",
element: (
<APage
values={[
"We're no strangers",
"to love",
"you know the rules",
"and so do I",
"A full commitment's",
"what i'm thinking of",
"You wouldn't get this",
"from any other guy",
"I just wanna tell you",
"How I'm feeling",
"Gotta make you understand",
"Chorus :)",
"Never gonna give you up",
"Never gonna let you down",
"Never gonna run around and desert you",
"Never gonna say goodbye",
"Never gonna tell a lie and hurt you",
]}
/>
),
},
{
path: "rotating",
element: <RepeatingBannerPage />,
},
{
path: "sand",
element: <AnimatedSandPage />,
},
{
path: "pong",
element: <PongPage />
},
],
children: playgroundRoutes,
},
],
},
Expand Down
Loading

0 comments on commit ac14f36

Please sign in to comment.