From 01a48403936cf1d3c593216c49cbf803716e1bd4 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Mon, 6 Apr 2026 19:58:05 +0200 Subject: [PATCH 01/10] Add slider hover tooltip and CSV/paste import for custom arrays --- sorting_visualization.html | 101 ++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/sorting_visualization.html b/sorting_visualization.html index 263b931..22ae361 100644 --- a/sorting_visualization.html +++ b/sorting_visualization.html @@ -73,6 +73,24 @@ cursor: pointer; touch-action: none; } + + /* ── Slider Tooltip ── */ + .slider-tooltip { + position: fixed; + background: var(--c-surface2, #1e2235); + border: 1px solid var(--c-border, #2a2d3d); + border-radius: 6px; + padding: 3px 8px; + font-size: 11px; + font-family: monospace; + color: var(--c-accent, #4a7cff); + pointer-events: none; + opacity: 0; + transition: opacity 0.15s ease; + z-index: 9999; + white-space: nowrap; + } + .slider-tooltip.visible { opacity: 1; } input[type="range"]::-webkit-slider-runnable-track { height: 6px; background: var(--c-surface2); @@ -197,7 +215,7 @@

- Sortier-Algorithmen v0.2.8 + Sortier-Algorithmen v0.2.9

Interaktive Visualisierung mit schrittweiser Animation

@@ -275,10 +293,14 @@
- + @@ -316,6 +338,9 @@
+ +
+
@@ -809,6 +834,8 @@ const $algoSelect = document.getElementById('algoSelect'); const $presetSelect = document.getElementById('presetSelect'); const $customArray = document.getElementById('customArray'); const $btnClearCustom = document.getElementById('btnClearCustom'); +const $btnPasteCustom = document.getElementById('btnPasteCustom'); +const $customArrayCount = document.getElementById('customArrayCount'); const $sizeSlider = document.getElementById('sizeSlider'); const $speedSlider = document.getElementById('speedSlider'); const $threadSlider = document.getElementById('threadSlider'); @@ -2840,14 +2867,41 @@ $presetSelect.addEventListener('change', function() { $customArray.addEventListener('change', function() { if (!isRunning) doReset(); + updateCustomArrayCount(); }); +$customArray.addEventListener('input', function() { + updateCustomArrayCount(); +}); + +function updateCustomArrayCount() { + const text = $customArray.value.trim(); + if (!text) { + $customArrayCount.textContent = ''; + return; + } + const values = text.split(/[,;\s\n\t]+/).filter(function(v) { return v !== '' && !isNaN(parseFloat(v)); }); + $customArrayCount.textContent = values.length + ' Wert' + (values.length === 1 ? '' : 'e'); +} + $btnClearCustom.addEventListener('click', function() { $customArray.value = ''; + $customArrayCount.textContent = ''; $sizeSlider.disabled = false; if (!isRunning) doReset(); }); +$btnPasteCustom.addEventListener('click', async function() { + try { + const text = await navigator.clipboard.readText(); + $customArray.value = text.replace(/[\r\n]+/g, ', ').replace(/,/g, ', '); + updateCustomArrayCount(); + $customArray.dispatchEvent(new Event('change')); + } catch (e) { + // Clipboard API not available or permission denied + } +}); + $btnTheme.addEventListener('click', function() { const isDark = document.documentElement.classList.contains('dark'); const newTheme = !isDark; @@ -2922,6 +2976,49 @@ makeTouchSlider($sizeSlider); makeTouchSlider($speedSlider); makeTouchSlider($threadSlider); +// ================================================================ +// Slider Tooltip on Hover +// ================================================================ + +const $sliderTooltip = document.getElementById('sliderTooltip'); +const $allSliders = [$sizeSlider, $speedSlider, $threadSlider].filter(Boolean); + +$allSliders.forEach(function(slider) { + slider.addEventListener('mouseenter', function() { + updateSliderTooltip(slider); + $sliderTooltip.classList.add('visible'); + }); + slider.addEventListener('mousemove', function(e) { + updateSliderTooltipPosition(e.clientX, e.clientY); + }); + slider.addEventListener('mouseleave', function() { + $sliderTooltip.classList.remove('visible'); + }); +}); + +function updateSliderTooltip(slider) { + if (slider.id === 'sizeSlider') { + $sliderTooltip.textContent = slider.value + ' Elemente'; + } else if (slider.id === 'speedSlider') { + const ms = Math.round(1000 * Math.pow(0.001, (parseInt(slider.value) - 1) / 99)); + $sliderTooltip.textContent = ms + ' ms/Schritt'; + } else if (slider.id === 'threadSlider') { + $sliderTooltip.textContent = slider.value + ' Thread(s)'; + } +} + +function updateSliderTooltipPosition(x, y) { + const tw = $sliderTooltip.offsetWidth; + const th = $sliderTooltip.offsetHeight; + let left = x - tw / 2; + let top = y - th - 12; + if (left < 8) left = 8; + if (left + tw > window.innerWidth - 8) left = window.innerWidth - tw - 8; + if (top < 8) top = y + 16; + $sliderTooltip.style.left = left + 'px'; + $sliderTooltip.style.top = top + 'px'; +} + // ================================================================ // URL State Persistence // ================================================================ From 209a11aef3d4f1a5cfed7ceb6b657864986b239e Mon Sep 17 00:00:00 2001 From: dschlueter Date: Mon, 6 Apr 2026 20:13:08 +0200 Subject: [PATCH 02/10] Fix mobile viewport (prevent zoom on touch) and remove slider tooltip --- sorting_visualization.html | 75 +++++--------------------------------- 1 file changed, 9 insertions(+), 66 deletions(-) diff --git a/sorting_visualization.html b/sorting_visualization.html index 22ae361..b68d759 100644 --- a/sorting_visualization.html +++ b/sorting_visualization.html @@ -2,7 +2,8 @@ - + + Sortier-Algorithmen Visualisierung @@ -57,7 +58,12 @@ } /* ── Viewport-relative Fonts ── */ - body { font-size: clamp(14px, 1.6vw, 16px); } + html, body { + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + overscroll-behavior: contain; + } + body { font-size: clamp(14px, 1.6vw, 16px); overflow-x: hidden; } .title-text { font-size: clamp(1.25rem, 3.5vw, 2.25rem); } .stat-value { font-size: clamp(1.5rem, 4vw, 2.25rem); } .label-text { font-size: clamp(9px, 1.2vw, 11px); } @@ -74,23 +80,6 @@ touch-action: none; } - /* ── Slider Tooltip ── */ - .slider-tooltip { - position: fixed; - background: var(--c-surface2, #1e2235); - border: 1px solid var(--c-border, #2a2d3d); - border-radius: 6px; - padding: 3px 8px; - font-size: 11px; - font-family: monospace; - color: var(--c-accent, #4a7cff); - pointer-events: none; - opacity: 0; - transition: opacity 0.15s ease; - z-index: 9999; - white-space: nowrap; - } - .slider-tooltip.visible { opacity: 1; } input[type="range"]::-webkit-slider-runnable-track { height: 6px; background: var(--c-surface2); @@ -215,7 +204,7 @@

- Sortier-Algorithmen v0.2.9 + Sortier-Algorithmen v0.2.10

Interaktive Visualisierung mit schrittweiser Animation

@@ -338,9 +327,6 @@
- -
-
@@ -2976,49 +2962,6 @@ makeTouchSlider($sizeSlider); makeTouchSlider($speedSlider); makeTouchSlider($threadSlider); -// ================================================================ -// Slider Tooltip on Hover -// ================================================================ - -const $sliderTooltip = document.getElementById('sliderTooltip'); -const $allSliders = [$sizeSlider, $speedSlider, $threadSlider].filter(Boolean); - -$allSliders.forEach(function(slider) { - slider.addEventListener('mouseenter', function() { - updateSliderTooltip(slider); - $sliderTooltip.classList.add('visible'); - }); - slider.addEventListener('mousemove', function(e) { - updateSliderTooltipPosition(e.clientX, e.clientY); - }); - slider.addEventListener('mouseleave', function() { - $sliderTooltip.classList.remove('visible'); - }); -}); - -function updateSliderTooltip(slider) { - if (slider.id === 'sizeSlider') { - $sliderTooltip.textContent = slider.value + ' Elemente'; - } else if (slider.id === 'speedSlider') { - const ms = Math.round(1000 * Math.pow(0.001, (parseInt(slider.value) - 1) / 99)); - $sliderTooltip.textContent = ms + ' ms/Schritt'; - } else if (slider.id === 'threadSlider') { - $sliderTooltip.textContent = slider.value + ' Thread(s)'; - } -} - -function updateSliderTooltipPosition(x, y) { - const tw = $sliderTooltip.offsetWidth; - const th = $sliderTooltip.offsetHeight; - let left = x - tw / 2; - let top = y - th - 12; - if (left < 8) left = 8; - if (left + tw > window.innerWidth - 8) left = window.innerWidth - tw - 8; - if (top < 8) top = y + 16; - $sliderTooltip.style.left = left + 'px'; - $sliderTooltip.style.top = top + 'px'; -} - // ================================================================ // URL State Persistence // ================================================================ From a2e01443e65f970c0469ca7faa12b67799ad3ce3 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Mon, 6 Apr 2026 20:32:33 +0200 Subject: [PATCH 03/10] Remove Bitonic Sort and Threads slider (cleaner, simpler UI) --- sorting_visualization.html | 229 +------------------------------------ test_algorithms.js | 1 - 2 files changed, 4 insertions(+), 226 deletions(-) diff --git a/sorting_visualization.html b/sorting_visualization.html index b68d759..b5d8dfa 100644 --- a/sorting_visualization.html +++ b/sorting_visualization.html @@ -204,7 +204,7 @@

- Sortier-Algorithmen v0.2.10 + Sortier-Algorithmen v0.2.11

Interaktive Visualisierung mit schrittweiser Animation

@@ -247,9 +247,6 @@ - - - @@ -307,16 +304,6 @@
- - -
- - -
-
@@ -802,6 +820,7 @@ const $btnPlayPause = document.getElementById('btnPlayPause'); const $btnStepBack = document.getElementById('btnStepBack'); const $btnStep = document.getElementById('btnStep'); const $btnReset = document.getElementById('btnReset'); +const $btnShuffle = document.getElementById('btnShuffle'); const $btnTheme = document.getElementById('btnTheme'); const $progressFill = document.getElementById('progressFill'); const $stepCounter = document.getElementById('stepCounter'); @@ -1756,6 +1775,21 @@ function buildSteps(algoName) { break; } + case 'shuffle': { + const n = arr.length; + if (n === 0) break; + // Fisher-Yates Shuffle (Knuth variant) + for (let i = n - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + if (j !== i) { + [arr[i], arr[j]] = [arr[j], arr[i]]; + swaps++; + snap('swap', [i, j]); + } + } + break; + } + case 'pancake': { const n = arr.length; function flip(end) { @@ -2606,6 +2640,28 @@ $btnStepBack.addEventListener('click', function() { $btnReset.addEventListener('click', doReset); +$btnShuffle.addEventListener('click', function() { + stopTimer(); + isRunning = false; + isPaused = false; + // Fisher-Yates Shuffle on current array + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + if (j !== i) { + [array[i], array[j]] = [array[j], array[i]]; + } + } + steps = []; + stepIndex = 0; + startTime = 0; + elapsedMs = 0; + setStats(0, 0, 0, 0); + $statTime.textContent = '0 ms'; + renderCurrent(); + updateButtonStates(); + $stepExplanation.textContent = 'Array gemischt \u2713'; +}); + // ================================================================ // Slider & Input Events // ================================================================ diff --git a/test_algorithms.js b/test_algorithms.js index 5d49682..0396fb6 100644 --- a/test_algorithms.js +++ b/test_algorithms.js @@ -234,7 +234,7 @@ const ALGORITHMS = [ 'merge', 'heap', 'quick', 'quick3way', 'dualpivot', 'introsort', 'shell', 'tree', 'timsort', 'counting', 'radix', 'bucket', - 'pancake', 'cycle', 'bogo', + 'pancake', 'cycle', 'bogo', 'shuffle', ]; const Bogo_MAX_SIZE = 6; @@ -331,6 +331,7 @@ console.log('═'.repeat(50)); const NORMAL_SIZES = [10, 50]; for (const algo of ALGORITHMS) { + if (algo === 'shuffle') continue; if (verbose) console.log(`\n📦 ${algo}:`); for (const preset of PRESETS) { for (const size of NORMAL_SIZES) { @@ -340,6 +341,44 @@ for (const algo of ALGORITHMS) { } } +// ── Shuffle-Tests (speziell: nicht sortiert, nur permutiert) ── +if (verbose) console.log('\n📦 shuffle:'); +for (const preset of PRESETS) { + for (const size of NORMAL_SIZES) { + totalTests++; + const arr = generatePresetArray(preset, size); + const sortedArr = arr.slice().sort((a, b) => a - b); + context.array = arr.slice(); + + try { + const steps = buildSteps('shuffle'); + const lastStep = steps[steps.length - 1]; + + if (lastStep.type !== 'done') { + throw new Error(`Letzter Step type '${lastStep.type}', erwartet 'done'`); + } + + if (lastStep.array.length !== arr.length) { + throw new Error(`Array-Länge geändert: ${lastStep.array.length} statt ${arr.length}`); + } + + const sortedResult = lastStep.array.slice().sort((a, b) => a - b); + if (!arraysEqual(sortedResult, sortedArr)) { + throw new Error(`Werte stimmen nicht überein nach Shuffle`); + } + + passed++; + if (verbose) { + console.log(` ✅ shuffle + ${preset} (${size} Elemente, ${steps.length} Steps)`); + } + } catch (e) { + failed++; + failures.push({ algo: 'shuffle', preset, size, error: e.message }); + console.log(` ❌ shuffle + ${preset} (${size} Elemente): ${e.message}`); + } + } +} + // Edge Cases if (verbose) console.log('\n📦 Edge Cases:'); From e0a10cf92fa72e0422c3d620e36867944adc75e2 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Mon, 6 Apr 2026 21:00:49 +0200 Subject: [PATCH 05/10] Add keyboard shortcut 'S' for Shuffle --- sorting_visualization.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sorting_visualization.html b/sorting_visualization.html index f3c97fb..6605f41 100644 --- a/sorting_visualization.html +++ b/sorting_visualization.html @@ -204,7 +204,7 @@

- Sortier-Algorithmen v0.2.12 + Sortier-Algorithmen v0.2.13

Interaktive Visualisierung mit schrittweiser Animation

@@ -336,7 +336,7 @@
+ +
+ + + Bedienung + + +
+ +

Was ist das? Dieses Programm visualisiert die Funktionsweise von 20 Sortieralgorithmen — von Bubble Sort bis Fisher-Yates Shuffle. Es zeigt Schritt für Schritt, wie ein Algorithmus ein Array sortiert, macht die Datenstrukturen und Speichernutzung sichtbar und vermittelt ein intuitives Verständnis dafür, warum der eine Algorithmus schneller ist als der andere.

+ + +
+

Quick-Start

+
    +
  1. 1. Wähle einen Algorithmus aus dem Dropdown-Menü.
  2. +
  3. 2. Stelle die Array-Größe und Geschwindigkeit ein.
  4. +
  5. 3. Drücke Space zum Starten — oder nutze die Schritt-für-Schritt-Navigation.
  6. +
+
+ + +
+

Steuerung

+
+
+ + Play / Pause — Animation starten oder anhalten +
+
+ + Ein Schritt vor — nächste Operation +
+
+ + Ein Schritt zurück — vorherige Operation +
+
+ + Reset — Array in Ausgangszustand zurücksetzen +
+
+ 🔀 + Mischen — Fisher-Yates Shuffle auf aktuelles Array +
+
+ 🌙/☀ + Theme — zwischen hellem und dunklem Modus wechseln +
+
+
+ + +
+

Tastaturkürzel

+
+
Space Play/Pause
+
K Play/Pause
+
Schritt zurück
+
J Schritt zurück
+
Schritt vor
+
L Schritt vor
+
R Reset
+
T Theme
+
S Shuffle
+
+
+ + +
+

Datensätze

+
+
Zufällig — zufällig verteilte Werte, der Standard-Testfall.
+
Sortiert — bereits in aufsteigender Reihenfolge. Manche Algorithmen (z.B. Quick Sort mit erstem Element als Pivot) werden dadurch extrem langsam.
+
Umgekehrt — absteigend sortiert. Das Gegenstück zu „Sortiert" und ein公平er Test für die meisten Algorithmen.
+
Fast sortiert — nur wenige Elemente sind an der falschen Stelle. Adaptiven Algorithmen (Insertion Sort, Timsort) spielen hier ihre Stärke aus.
+
Duplikate — viele gleiche Werte. Das ist der Härtetest für Algorithmen wie Quick Sort (degeneriert zu O(n²)), während 3-Way Quick Sort hier brilliert.
+
+
+ + +
+

Warum sind Sortieralgorithmen wichtig?

+

Sortieren ist eines der am intensivsten erforschten Probleme der Informatik — nicht aus akademischer Überheblichkeit, sondern weil es in der Praxis überall vorkommt: Datenbankabfragen müssen Ergebnisse sortieren, Betriebssysteme verwalten Prioritätswarteschlangen, Compiler nutzen Sortieralgorithmen für die Optimierung. Die Wahl des richtigen Algorithmus kann den Unterschied ausmachen zwischen einer Antwort in Millisekunden und in Minuten.

+

Jeder Algorithmus repräsentiert einen grundlegenden Kompromiss: Manche sind einfach zu verstehen (Bubble Sort), andere extrem schnell im Durchschnitt (Quick Sort), wieder andere garantieren auch im schlimmsten Fall gute Leistung (Heap Sort). Wer diese Kompromisse versteht, kann für jede Situation die richtige Wahl treffen — sei es beim Sortieren von 100 Datensätzen oder 100 Millionen.

+

Dieses Programm hilft dabei, ein intuitives Gespür für diese Kompromisse zu entwickeln. Beobachten Sie, wie Bubble Sort bei sortierten Daten plötzlich in O(n) abschließt, während er bei umgekehrt sortierten Daten genauso langsam ist wie immer. Oder wie Timsort die Ordnung in fast-sortierten Daten sofort erkennt und ausnutzt. Visuelles Lernen macht sichtbar, was Paper und Pseudocode nur schwer vermitteln.

+
+ +
+
+