Skip to content

Commit

Permalink
feat: add initial sorting algorithm visualizer and implement bubble s…
Browse files Browse the repository at this point in the history
…ort algorithm
  • Loading branch information
vighnesh153 committed Nov 26, 2023
1 parent d13e3d5 commit 50c82b0
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script lang="ts">
import { onMount } from 'svelte';
import {
SortingVisualizerGame,
CanvasWrapperImpl,
BubbleSortSortingAlgorithm,
} from '@vighnesh153/graphics-programming';
import type { CanvasWrapper } from '@vighnesh153/graphics-programming';
import Button from '@/components/Button.svelte';
let canvasElement: HTMLCanvasElement;
let canvasWrapper: CanvasWrapper;
let game: SortingVisualizerGame;
let bgColor: string;
const allAlgorithms = ['Bubble sort', 'Merge sort', 'Selection sort', 'Insertion sort'];
let algorithm = allAlgorithms[0];
function newGame() {
if (canvasWrapper) {
game = new SortingVisualizerGame(canvasWrapper, { bgColor });
const frames = game.start(new BubbleSortSortingAlgorithm());
function showNextFrame() {
if (!frames.next().done) {
requestAnimationFrame(showNextFrame);
}
}
showNextFrame();
}
}
onMount(() => {
bgColor = canvasElement.computedStyleMap().get('background-color')?.toString() ?? 'white';
canvasWrapper = new CanvasWrapperImpl(canvasElement);
newGame();
});
$: {
// this log is needed so that this block runs when algorithm changes
console.log(`Algorithm changed: ${algorithm}`);
newGame();
}
</script>

<div class="flex justify-center items-center gap-10">
<Button variant="primary">Start</Button>

<Button>Randomize Array</Button>

<div class="flex flex-col items-center gap-1">
<label for="algorithm">Algorithm</label>
<select name="algorithm" id="algorithm" class="min-w-[100px] text-secondary" bind:value={algorithm}>
{#each allAlgorithms as algorithm (algorithm)}
<option value={algorithm}>{algorithm}</option>
{/each}
</select>
</div>
</div>
<canvas class="mt-6 mx-auto w-full max-w-3xl aspect-video bg-text" bind:this={canvasElement}>
Sorry your browser doesn't support the canvas element
</canvas>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
import { graphicsProjectsMap } from '@vighnesh153/graphics-programming';
import { classes, verifyGraphicsProjectPath } from '@/utils';
import { projectNavItems } from '@/constants';
import ContentLayout from '@/layouts/ContentLayout.astro';
import ProjectRoot from '@/components/projects/graphics/sorting-visualizer/ProjectRoot.svelte';
const project = graphicsProjectsMap.sortingVisualizer;
verifyGraphicsProjectPath(project, Astro.request.url);
const title = `Vighnesh Raut | Graphics Projects - Sorting Visualizer`;
const description = `Using animations to visualize different sorting algorithms for fun`;
---

<ContentLayout title={title} description={description} navItems={projectNavItems} showFooter={false}>
<div class={classes(`mt-28 mb-12 max-w-xl mx-auto lg:max-w-[unset] scroll-mt-8`)}>
<h1 class="text-3xl mb-6 text-center">Sorting Visualizer</h1>
<ProjectRoot client:load />
</div>
</ContentLayout>
1 change: 1 addition & 0 deletions nodejs-tools/nodejs-lib/graphics-programming/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './bonding-particles';
export * from './grid-path-finder';
export * from './pseudo-hilbert-curve';
export * from './sierpinskis-triangle';
export * from './sorting-visualizer';
export * from './symmetric-binary-tree';
export * from './tower-of-hanoi';
export * from './canvas-wrapper';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class PseudoHilbertCurveGame {
constructor(canvasWrapper: CanvasWrapper, config: GameConfig) {
this.#canvasWrapper = canvasWrapper;
this.#bgColor = config.bgColor ?? 'white';
this.#lineWidth = config.lineWidth ?? 2;
this.#lineWidth = config.lineWidth ?? 3;
this.#lineColor = config.lineColor ?? 'black';
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { CanvasWrapper } from '@/canvas-wrapper';
import { buildInitialLineHeightPercentsArray } from './buildInitialLineHeightPercentsArray';
import { SortingAlgorithm } from './SortingAlgorithm';

interface GameOptions {
gap?: number;
bgColor?: string;
lineWidth?: number;
lineColor?: string;
modifiedIndexLineColor?: string;
linesCount?: number;
}

export class SortingVisualizerGame {
readonly #canvasWrapper: CanvasWrapper;
readonly #gap: number;
readonly #lineWidth: number;
readonly #lineColor: string;
readonly #modifiedIndexLineColor: string;
readonly #bgColor: string;

#lineHeightPercents: number[];

constructor(canvasWrapper: CanvasWrapper, options: GameOptions = {}) {
this.#canvasWrapper = canvasWrapper;

this.#bgColor = options.bgColor ?? 'white';

this.#gap = options.gap ?? 20;
this.#lineWidth = options.lineWidth ?? 2;
this.#lineColor = options.lineColor ?? 'black';
this.#modifiedIndexLineColor = options.modifiedIndexLineColor ?? 'red';

const linesCount = options.linesCount ?? 200;
this.#lineHeightPercents = buildInitialLineHeightPercentsArray(linesCount);
}

*start(sortingAlgorithm: SortingAlgorithm) {
sortingAlgorithm.initializeArray(this.#lineHeightPercents);

for (const frame of sortingAlgorithm.sort()) {
this.#lineHeightPercents = sortingAlgorithm.intermediateArrayState;
this.clear();
this.drawLines(sortingAlgorithm.intermediateModifiedIndicesState);
yield frame;
}
}

clear() {
const rect = this.#canvasWrapper.getBoundingClientRect();
const canvasWidth = rect.width;
const canvasHeight = rect.height;
this.#canvasWrapper.drawFilledRect(0, 0, canvasWidth, canvasHeight, this.#bgColor);
}

private drawLines(modifiedIndices: number[]): void {
const canvasHeight = this.#canvasWrapper.height;
const canvasWidth = this.#canvasWrapper.width;
const gap = this.#gap;
const lineWidth = this.#lineWidth;
const availableHeight = canvasHeight - gap * 2;
const availableWidth = canvasWidth - gap * 2;
const widthPerLine = availableWidth / this.#lineHeightPercents.length;
const defaultLineColor = this.#lineColor;
const modifiedIndexLineColor = this.#modifiedIndexLineColor;

this.#lineHeightPercents.forEach((lineHeightPercent, index) => {
const x = gap + index * widthPerLine;
const bottomY = canvasHeight - gap;
const topY = bottomY - (availableHeight * lineHeightPercent) / 100;

const lineColor = modifiedIndices.includes(index) ? modifiedIndexLineColor : defaultLineColor;

this.#canvasWrapper.drawLine(x, bottomY, x, topY, lineWidth, lineColor);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export abstract class SortingAlgorithm {
protected array: number[] = [];
protected modifiedIndices: number[] = [];

get intermediateArrayState(): number[] {
return [...this.array];
}

get intermediateModifiedIndicesState(): number[] {
return [...this.modifiedIndices];
}

initializeArray(array: number[]): void {
this.array = array;
}

abstract sort(): Generator<undefined, void, unknown>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { SortingAlgorithm } from './SortingAlgorithm';

export class BubbleSortSortingAlgorithm extends SortingAlgorithm {
*sort() {
const { array } = this;
const size = this.array.length;
for (let i = 0; i < size; i++) {
for (let j = 0; j < size - i; j++) {
if (array[j] > array[j + 1]) {
const temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;

this.modifiedIndices = [j, j + 1];
yield;
}
}
}
}
}

export class MergeSortSortingAlgorithm extends SortingAlgorithm {
*sort() {
yield;
}
}

export class SelectionSortSortingAlgorithm extends SortingAlgorithm {
*sort() {
yield;
}
}

export class InsertionSortSortingAlgorithm extends SortingAlgorithm {
*sort() {
yield;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { randomInteger } from '@vighnesh153/utils';

export function buildInitialLineHeightPercentsArray(size: number, from = 10, to = 100): Array<number> {
return Array.from({ length: size }).map(() => randomInteger(from, to));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { SortingVisualizerGame } from './Game';

export {
BubbleSortSortingAlgorithm,
MergeSortSortingAlgorithm,
SelectionSortSortingAlgorithm,
InsertionSortSortingAlgorithm,
} from './SortingAlgorithmImpls';

0 comments on commit 50c82b0

Please sign in to comment.