<!
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Sorting Algorithm Visualizer</title>
<style>
@import
url('[Link]
+Grotesk:wght@400;600;700&display=swap');
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #0d0f1a;
color: #e8e6f0;
font-family: 'Space Grotesk', sans-serif;
min-height: 100vh;
}
.app {
max-width: 960px;
margin: 0 auto;
padding: 28px 24px 60px;
}
.header {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-bottom: 28px;
padding-bottom: 16px;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.title-block h1 {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.5px;
background: linear-gradient(135deg, #a78bfa, #60a5fa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.title-block p {
font-size: 12px;
color: #6b7280;
margin-top: 4px;
font-family: 'JetBrains Mono', monospace;
}
.controls-row {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 20px;
align-items: center;
}
.algo-tabs {
display: flex;
gap: 6px;
background: rgba(255,255,255,0.04);
border-radius: 10px;
padding: 4px;
border: 1px solid rgba(255,255,255,0.07);
}
.tab {
padding: 7px 16px;
border-radius: 7px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
background: none;
border: none;
color: #6b7280;
transition: all 0.18s;
font-family: 'Space Grotesk', sans-serif;
}
.[Link] {
background: linear-gradient(135deg, #7c3aed, #2563eb);
color: #fff;
}
.tab:hover:not(.active) {
color: #e8e6f0;
background: rgba(255,255,255,0.06);
}
.ctrl-group {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.label-sm {
font-size: 12px;
color: #6b7280;
font-family: 'JetBrains Mono', monospace;
}
input[type=range] {
-webkit-appearance: none;
width: 90px;
height: 4px;
background: rgba(255,255,255,0.12);
border-radius: 2px;
outline: none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #7c3aed;
cursor: pointer;
}
.btn-row {
display: flex;
gap: 8px;
margin-bottom: 20px;
}
.btn {
padding: 9px 22px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
border: none;
font-family: 'Space Grotesk', sans-serif;
transition: all 0.15s;
}
.btn-primary {
background: linear-gradient(135deg, #7c3aed, #2563eb);
color: #fff;
}
.btn-primary:hover { opacity: 0.88; transform: translateY(-1px); }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
.btn-ghost {
background: rgba(255,255,255,0.05);
color: #a1a1aa;
border: 1px solid rgba(255,255,255,0.1);
}
.btn-ghost:hover { background: rgba(255,255,255,0.09); color: #e8e6f0; }
.canvas-wrap {
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 14px;
padding: 20px 20px 12px;
margin-bottom: 16px;
}
canvas {
display: block;
width: 100%;
border-radius: 4px;
}
.legend {
display: flex;
gap: 16px;
flex-wrap: wrap;
padding: 0 0 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #9ca3af;
font-family: 'JetBrains Mono', monospace;
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 3px;
}
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 20px;
}
.stat-card {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.07);
border-radius: 10px;
padding: 14px 16px;
}
.stat-label {
font-size: 11px;
color: #6b7280;
font-family: 'JetBrains Mono', monospace;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.6px;
}
.stat-val {
font-size: 22px;
font-weight: 700;
font-family: 'JetBrains Mono', monospace;
}
.[Link] { color: #a78bfa; }
.[Link] { color: #60a5fa; }
.[Link] { color: #34d399; }
.[Link] { color: #fbbf24; }
.info-box {
background: rgba(167,139,250,0.08);
border: 1px solid rgba(167,139,250,0.2);
border-radius: 10px;
padding: 12px 16px;
margin-bottom: 18px;
font-size: 13px;
color: #c4b5fd;
line-height: 1.6;
}
.info-box strong { color: #a78bfa; font-weight: 600; }
.code-panel {
background: #0a0c14;
border: 1px solid rgba(255,255,255,0.08);
border-radius: 12px;
overflow: hidden;
}
.code-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid rgba(255,255,255,0.06);
background: rgba(255,255,255,0.03);
}
.code-header span {
font-size: 12px;
color: #6b7280;
font-family: 'JetBrains Mono', monospace;
}
.dot-row { display: flex; gap: 5px; }
.dot { width: 10px; height: 10px; border-radius: 50%; }
.dot-r { background: #ff5f57; }
.dot-y { background: #ffbd2e; }
.dot-g { background: #28ca41; }
pre {
padding: 16px 20px;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
line-height: 1.75;
overflow-x: auto;
color: #c9c7d9;
}
.kw { color: #c084fc; font-weight: 700; }
.fn { color: #60a5fa; }
.cm { color: #4b5563; font-style: italic; }
.str { color: #34d399; }
.num { color: #fbbf24; }
.op { color: #fb7185; }
.status-bar {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
background: rgba(255,255,255,0.02);
border-top: 1px solid rgba(255,255,255,0.06);
font-size: 12px;
color: #6b7280;
font-family: 'JetBrains Mono', monospace;
}
.pulse {
width: 8px; height: 8px; border-radius: 50%;
background: #34d399;
animation: pulse 1.5s ease-in-out infinite;
}
.[Link] { background: #6b7280; animation: none; }
.[Link] { background: #a78bfa; animation: none; }
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0.4); }
50% { box-shadow: 0 0 0 5px rgba(52,211,153,0); }
}
@media (max-width: 640px) {
.stats-row { grid-template-columns: repeat(2, 1fr); }
.ctrl-group { margin-left: 0; width: 100%; }
.controls-row { flex-direction: column; align-items: flex-start; }
}
</style>
</head>
<body>
<div class="app">
<div class="header">
<div class="title-block">
<h1>Sorting Algorithm Visualizer</h1>
<p>bubble · selection · insertion</p>
</div>
</div>
<div class="info-box" id="algo-desc">
<strong>Bubble Sort</strong> — Repeatedly compares adjacent elements and swaps them
if out of order.
</div>
<div class="controls-row">
<div class="algo-tabs">
<button class="tab active" onclick="selectAlgo('bubble')">Bubble Sort</button>
<button class="tab" onclick="selectAlgo('selection')">Selection Sort</button>
<button class="tab" onclick="selectAlgo('insertion')">Insertion Sort</button>
</div>
<div class="ctrl-group">
<span class="label-sm">size</span>
<input type="range" id="sizeSlider" min="10" max="70" value="30"
oninput="[Link]('sizeLabel').textContent=[Link];
N=+[Link]; generateArray()">
<span class="label-sm" id="sizeLabel">30</span>
<span class="label-sm" style="margin-left:10px">speed</span>
<input type="range" id="speedSlider" min="1" max="10" value="5"
oninput="[Link]('speedLabel').textContent=[Link]">
<span class="label-sm" id="speedLabel">5</span>
</div>
</div>
<div class="btn-row">
<button class="btn btn-primary" id="startBtn" onclick="startSort()">▶ Run
Sort</button>
<button class="btn btn-ghost" onclick="generateArray()">↻ New
Array</button>
<input type="file" id="fileInput" accept=".txt" class="btn btn-ghost">
<button class="btn btn-ghost" onclick="stopSort()">■ Stop</button>
</div>
<div class="canvas-wrap">
<div class="legend">
<div class="legend-item"><div class="legend-dot"
style="background:#5b21b6"></div>unsorted</div>
<div class="legend-item"><div class="legend-dot"
style="background:#f59e0b"></div>comparing</div>
<div class="legend-item"><div class="legend-dot"
style="background:#ef4444"></div>swapping</div>
<div class="legend-item"><div class="legend-dot"
style="background:#10b981"></div>sorted</div>
<div class="legend-item"><div class="legend-dot"
style="background:#60a5fa"></div>min / key</div>
</div>
<canvas id="viz" height="240"></canvas>
</div>
<div class="stats-row">
<div class="stat-card">
<div class="stat-label">comparisons</div>
<div class="stat-val purple" id="statComp">0</div>
</div>
<div class="stat-card">
<div class="stat-label">swaps</div>
<div class="stat-val blue" id="statSwaps">0</div>
</div>
<div class="stat-card">
<div class="stat-label">array accesses</div>
<div class="stat-val amber" id="statAccess">0</div>
</div>
<div class="stat-card">
<div class="stat-label">complexity</div>
<div class="stat-val green" id="statComplex">O(n^2)</div>
</div>
</div>
<div class="code-panel">
<div class="code-header">
<div class="dot-row">
<div class="dot dot-r"></div>
<div class="dot dot-y"></div>
<div class="dot dot-g"></div>
</div>
<span id="codeTitle">bubbleSort(arr)</span>
<span>python</span>
</div>
<pre id="codeBlock"></pre>
<div class="status-bar">
<div class="pulse idle" id="statusDot"></div>
<span id="statusText">ready — press Run Sort to begin</span>
</div>
</div>
</div>
<script>
/* ─── Canvas setup ──────────────────────────── */
const canvas = [Link]('viz');
const ctx = [Link]('2d');
/* ─── State ─────────────────────────────────── */
let arr = [];
let highlights = {};
let sortedIdxs = new Set();
let comparisons = 0, swaps = 0, accesses = 0;
let running = false, stopFlag = false;
let currentAlgo = 'bubble';
let N = 30;
/* ─── Bar colours ───────────────────────────── */
const COLORS = {
default: '#5b21b6',
comparing: '#f59e0b',
swapping: '#ef4444',
sorted: '#10b981',
pivot: '#60a5fa',
current: '#c084fc',
};
/* ─── Algorithm metadata ────────────────────── */
const ALGO_INFO = {
bubble: {
complexity: 'O(n^2)',
desc: '<strong>Bubble Sort</strong> — Repeatedly compares adjacent elements and
swaps them if out of order. Demonstrates sequencing, selection (if/else), and
iteration (nested loops).',
codeTitle: 'bubbleSort(arr)',
code: `<span class="cm"># Procedure: bubbleSort</span>
<span class="cm"># Parameter: arr — list of integers</span>
<span class="cm"># Returns: None (modifies list in place)</span>
<span class="kw">def</span> <span class="fn">bubbleSort</span>(arr):
n <span class="op">=</span> <span class="fn">len</span>(arr)
<span class="kw">for</span> i <span class="kw">in</span> <span
class="fn">range</span>(n):
swapped <span class="op">=</span> <span class="kw">False</span>
<span class="kw">for</span> j <span class="kw">in</span> <span
class="fn">range</span>(<span class="num">0</span>, n <span class="op">-</span> i
<span class="op">-</span> <span class="num">1</span>):
<span class="kw">if</span> arr[j] <span class="op">></span> arr[j <span
class="op">+</span> <span class="num">1</span>]:
arr[j], arr[j<span class="op">+</span><span class="num">1</span>] <span
class="op">=</span> arr[j<span class="op">+</span><span class="num">1</span>], arr[j]
swapped <span class="op">=</span> <span class="kw">True</span>
<span class="kw">if not</span> swapped:
<span class="kw">break</span>
data <span class="op">=</span> [<span class="num">64</span>, <span
class="num">34</span>, <span class="num">25</span>, <span class="num">12</span>, <span
class="num">22</span>, <span class="num">11</span>, <span class="num">90</span>]
<span class="fn">bubbleSort</span>(data)
<span class="kw">print</span>(data) <span class="cm"># [11, 12, 22, 25, 34, 64,
90]</span>`
},
selection: {
complexity: 'O(n^2)',
desc: '<strong>Selection Sort</strong> — Finds the minimum element in the
unsorted portion and places it at the correct position. Iterates, compares, and swaps
with selection logic.',
codeTitle: 'selectionSort(arr)',
code: `<span class="cm"># Procedure: selectionSort</span>
<span class="cm"># Parameter: arr — list of integers</span>
<span class="cm"># Returns: None (modifies list in place)</span>
<span class="kw">def</span> <span class="fn">selectionSort</span>(arr):
n <span class="op">=</span> <span class="fn">len</span>(arr)
<span class="kw">for</span> i <span class="kw">in</span> <span
class="fn">range</span>(n):
min_idx <span class="op">=</span> i
<span class="kw">for</span> j <span class="kw">in</span> <span
class="fn">range</span>(i <span class="op">+</span> <span class="num">1</span>, n):
<span class="kw">if</span> arr[j] <span class="op"><</span>
arr[min_idx]:
min_idx <span class="op">=</span> j
<span class="kw">if</span> min_idx <span class="op">!=</span> i:
arr[i], arr[min_idx] <span class="op">=</span> arr[min_idx], arr[i]
data <span class="op">=</span> [<span class="num">29</span>, <span
class="num">10</span>, <span class="num">14</span>, <span class="num">37</span>, <span
class="num">13</span>]
<span class="fn">selectionSort</span>(data)
<span class="kw">print</span>(data) <span class="cm"># [10, 13, 14, 29, 37]</span>`
},
insertion: {
complexity: 'O(n^2)',
desc: '<strong>Insertion Sort</strong> — Builds a sorted subarray one element at
a time, inserting each new element into its correct position by shifting larger
elements right.',
codeTitle: 'insertionSort(arr)',
code: `<span class="cm"># Procedure: insertionSort</span>
<span class="cm"># Parameter: arr — list of integers</span>
<span class="cm"># Returns: None (modifies list in place)</span>
<span class="kw">def</span> <span class="fn">insertionSort</span>(arr):
<span class="kw">for</span> i <span class="kw">in</span> <span
class="fn">range</span>(<span class="num">1</span>, <span class="fn">len</span>(arr)):
key <span class="op">=</span> arr[i]
j <span class="op">=</span> i <span class="op">-</span> <span
class="num">1</span>
<span class="kw">while</span> j <span class="op">>=</span> <span
class="num">0</span> <span class="kw">and</span> arr[j] <span class="op">></span>
key:
arr[j <span class="op">+</span> <span class="num">1</span>] <span
class="op">=</span> arr[j]
j <span class="op">-=</span> <span class="num">1</span>
arr[j <span class="op">+</span> <span class="num">1</span>] <span
class="op">=</span> key
data <span class="op">=</span> [<span class="num">5</span>, <span
class="num">2</span>, <span class="num">4</span>, <span class="num">6</span>, <span
class="num">1</span>, <span class="num">3</span>]
<span class="fn">insertionSort</span>(data)
<span class="kw">print</span>(data) <span class="cm"># [1, 2, 3, 4, 5, 6]</span>`
}
};
/* ─── Array generation ──────────────────────── */
function generateArray() {
stopFlag = true;
running = false;
arr = [Link]({ length: N }, () => [Link]([Link]() * 90) + 10);
highlights = {};
sortedIdxs = new Set();
comparisons = 0; swaps = 0; accesses = 0;
updateStats();
draw();
setStatus('idle', 'ready - press Run Sort to begin');
[Link]('startBtn').disabled = false;
}
/* ─── Stats display ─────────────────────────── */
function updateStats() {
[Link]('statComp').textContent = [Link]();
[Link]('statSwaps').textContent = [Link]();
[Link]('statAccess').textContent = [Link]();
}
/* ─── Algorithm selector ────────────────────── */
function selectAlgo(algo) {
currentAlgo = algo;
[Link]('.tab').forEach((t, i) => {
[Link]('active', ['bubble', 'selection', 'insertion'][i] === algo);
});
const info = ALGO_INFO[algo];
[Link]('algo-desc').innerHTML = [Link];
[Link]('codeTitle').textContent = [Link];
[Link]('codeBlock').innerHTML = [Link];
[Link]('statComplex').textContent = [Link];
generateArray();
}
/* ─── Speed helper ──────────────────────────── */
function getDelay() {
const s = parseInt([Link]('speedSlider').value, 10);
return [Link](2, 220 - s * 20);
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
function analyzeArray(values) {
if (![Link]) return { average: 0, max: 0, min: 0 };
const total = [Link]((sum, value) => sum + value, 0);
return {
average: total / [Link],
max: [Link](...values),
min: [Link](...values)
};
}
function generateArrayFromInput() {
stopFlag = true;
running = false;
highlights = {};
sortedIdxs = new Set();
comparisons = 0;
swaps = 0;
accesses = 0;
updateStats();
draw();
setStatus('idle', 'array loaded from file');
}
/* ─── FILE INPUT HANDLER ───────────────────── */
[Link]('fileInput').addEventListener('change', function(event) {
const file = [Link][0];
if (!file) return;
const reader = new FileReader();
[Link] = function(e) {
const text = [Link];
// Convert file text into list (DATA ABSTRACTION)
arr = [Link](',')
.map(num => parseInt([Link](), 10))
.filter(num => !isNaN(num));
N = [Link];
[Link]('sizeLabel').textContent = N;
generateArrayFromInput();
};
[Link](file);
});
/* ─── Canvas draw ───────────────────────────── */
function draw() {
const dpr = [Link] || 1;
const W = [Link];
const H = 220;
[Link] = W * dpr;
[Link] = H * dpr;
[Link](dpr, 0, 0, dpr, 0, 0);
if ([Link] === 0) {
[Link](0, 0, W, H);
return;
}
const max = [Link](...arr);
const gap = (W - 20) / [Link];
const bw = [Link](2, gap - 1);
[Link](0, 0, W, H);
[Link]((v, i) => {
const bh = (v / max) * H;
const x = 10 + i * gap;
const y = H - bh;
let color = [Link];
if ([Link](i)) color = [Link];
if (highlights[i] === 'pivot') color = [Link];
if (highlights[i] === 'comparing') color = [Link];
if (highlights[i] === 'swapping') color = [Link];
if (highlights[i] === 'current') color = [Link];
[Link] = color;
[Link] = highlights[i] ? color : 'transparent';
[Link] = highlights[i] ? 10 : 0;
const r = [Link](bw / 2, 3);
[Link]();
[Link](x, y, bw, bh, [r, r, 0, 0]);
[Link]();
});
[Link] = 0;
}
/* ─── Status bar helper ─────────────────────── */
function setStatus(state, msg) {
[Link]('statusDot').className = 'pulse ' + (state === 'running' ?
'' : state);
[Link]('statusText').textContent = msg;
}
/* ─── Bubble Sort animation ─────────────────── */
async function bubbleSortAnim() {
const n = [Link];
for (let i = 0; i < n && !stopFlag; i++) {
let swapped = false;
for (let j = 0; j < n - i - 1 && !stopFlag; j++) {
comparisons++; accesses += 2;
highlights = { [j]: 'comparing', [j + 1]: 'comparing' };
setStatus('running', `pass ${i + 1}: comparing index ${j} and ${j + 1}`);
draw(); updateStats();
await sleep(getDelay());
if (arr[j] > arr[j + 1]) {
swaps++; accesses += 4;
highlights = { [j]: 'swapping', [j + 1]: 'swapping' };
draw(); updateStats();
await sleep(getDelay() * 0.5);
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
swapped = true;
}
}
[Link](n - 1 - i);
highlights = {};
draw();
if (!swapped) break;
}
}
/* ─── Selection Sort animation ──────────────── */
async function selectionSortAnim() {
const n = [Link];
for (let i = 0; i < n && !stopFlag; i++) {
let minIdx = i;
for (let j = i + 1; j < n && !stopFlag; j++) {
comparisons++; accesses += 2;
highlights = { [minIdx]: 'pivot', [j]: 'comparing' };
setStatus('running', `finding min from index ${i}: scanning ${j}`);
draw(); updateStats();
await sleep(getDelay());
if (arr[j] < arr[minIdx]) minIdx = j;
}
if (minIdx !== i) {
swaps++; accesses += 4;
highlights = { [i]: 'swapping', [minIdx]: 'swapping' };
draw();
await sleep(getDelay() * 0.8);
[arr[i], arr[minIdx]] = [arr[minIdx], arr[i]];
}
[Link](i);
highlights = {};
draw(); updateStats();
}
}
/* ─── Insertion Sort animation ──────────────── */
async function insertionSortAnim() {
const n = [Link];
[Link](0);
for (let i = 1; i < n && !stopFlag; i++) {
const key = arr[i];
accesses++;
let j = i - 1;
highlights = { [i]: 'current' };
setStatus('running', `inserting index ${i} (value ${key})`);
draw(); updateStats();
await sleep(getDelay());
while (j >= 0 && arr[j] > key && !stopFlag) {
comparisons++; accesses += 3;
highlights = { [j]: 'comparing', [j + 1]: 'swapping', [i]: 'pivot' };
arr[j + 1] = arr[j];
swaps++;
draw(); updateStats();
await sleep(getDelay());
j--;
}
arr[j + 1] = key;
[Link](i);
highlights = {};
draw(); updateStats();
}
}
/* ─── Run sort dispatcher ───────────────────── */
async function startSort() {
if (running) return;
running = true;
stopFlag = false;
sortedIdxs = new Set();
comparisons = 0; swaps = 0; accesses = 0;
updateStats();
[Link]('startBtn').disabled = true;
const analysis = analyzeArray(arr);
setStatus(
'running',
`avg: ${[Link](1)}, max: ${[Link]}, min: ${[Link]}`
);
if (currentAlgo === 'bubble') await bubbleSortAnim();
else if (currentAlgo === 'selection') await selectionSortAnim();
else await insertionSortAnim();
if (!stopFlag) {
[Link]((_, i) => [Link](i));
highlights = {};
draw();
setStatus('done', `done - ${comparisons} comparisons, ${swaps} swaps`);
}
running = false;
[Link]('startBtn').disabled = false;
}
/* ─── Stop ──────────────────────────────────── */
function stopSort() {
stopFlag = true;
running = false;
setStatus('idle', 'stopped - press Run Sort to restart');
[Link]('startBtn').disabled = false;
}
/* ─── Resize redraw ─────────────────────────── */
[Link]('resize', draw);
/* ─── Init ──────────────────────────────────── */
selectAlgo('bubble');
</script>
</body>
</html>