| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>AE Content-Defined Chunking Visualization</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <link rel="preconnect" href="https://fonts.googleapis.com"> |
| | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| | <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> |
| | <style> |
| | |
| | body { |
| | font-family: 'Inter', sans-serif; |
| | background-color: #f3f4f6; |
| | } |
| | canvas { |
| | background-color: #ffffff; |
| | border-radius: 0.5rem; |
| | box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); |
| | } |
| | |
| | button { |
| | transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out; |
| | } |
| | button:hover { |
| | box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); |
| | } |
| | button:active { |
| | transform: translateY(1px); |
| | } |
| | |
| | input[type="range"] { |
| | -webkit-appearance: none; |
| | appearance: none; |
| | width: 100%; |
| | height: 8px; |
| | background: #d1d5db; |
| | border-radius: 9999px; |
| | outline: none; |
| | } |
| | input[type="range"]::-webkit-slider-thumb { |
| | -webkit-appearance: none; |
| | appearance: none; |
| | width: 16px; |
| | height: 16px; |
| | background: #3b82f6; |
| | border-radius: 9999px; |
| | cursor: pointer; |
| | } |
| | input[type="range"]::-moz-range-thumb { |
| | width: 16px; |
| | height: 16px; |
| | background: #3b82f6; |
| | border-radius: 9999px; |
| | cursor: pointer; |
| | border: none; |
| | } |
| | |
| | #explanation { |
| | background-color: #e0f2fe; |
| | border-left: 4px solid #0ea5e9; |
| | color: #0369a1; |
| | } |
| | </style> |
| | </head> |
| | <body class="p-4 md:p-8 bg-gray-100"> |
| |
|
| | <div class="max-w-6xl mx-auto bg-white p-6 rounded-lg shadow-lg"> |
| | <h1 class="text-2xl md:text-3xl font-bold mb-4 text-center text-gray-800">Asymmetric Extremum (AE) CDC Visualization</h1> |
| | <p class="text-sm text-gray-600 mb-6 text-center">Based on the paper by Zhang et al. (INFOCOM 2015). This visualization simplifies the 'value' to be the direct byte value (S=1).</p> |
| |
|
| | <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6 items-center bg-gray-50 p-4 rounded-lg border border-gray-200"> |
| | <div class="flex flex-wrap justify-center md:justify-start gap-2"> |
| | <button id="startBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-md shadow">Start</button> |
| | <button id="pauseBtn" class="bg-yellow-500 hover:bg-yellow-600 text-white font-semibold py-2 px-4 rounded-md shadow" disabled>Pause</button> |
| | <button id="stepBtn" class="bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded-md shadow">Step</button> |
| | <button id="resetBtn" class="bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-4 rounded-md shadow">Reset</button> |
| | </div> |
| |
|
| | <div class="flex flex-col items-center gap-2"> |
| | <div class="w-full max-w-xs"> |
| | <label for="windowSize" class="block text-sm font-medium text-gray-700 text-center">Window Size (w): <span id="windowSizeValue">8</span></label> |
| | <input type="range" id="windowSize" min="2" max="20" value="8" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> |
| | </div> |
| | <div class="w-full max-w-xs"> |
| | <label for="dataSize" class="block text-sm font-medium text-gray-700 text-center">Data Size: <span id="dataSizeValue">100</span></label> |
| | <input type="range" id="dataSize" min="20" max="300" value="100" step="10" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> |
| | </div> |
| | </div> |
| |
|
| | <div class="flex flex-col items-center md:items-end gap-2"> |
| | <div class="w-full max-w-xs"> |
| | <label for="speed" class="block text-sm font-medium text-gray-700 text-center md:text-right">Animation Speed: <span id="speedValue">500</span> ms</label> |
| | <input type="range" id="speed" min="50" max="2000" value="500" step="50" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <div class="mb-6"> |
| | <canvas id="cdcCanvas" width="1000" height="300" class="w-full"></canvas> |
| | </div> |
| |
|
| | <div id="explanation" class="p-4 rounded-md text-sm mb-4 border-l-4 border-blue-500 bg-blue-50 text-blue-800"> |
| | Click 'Start' or 'Step' to begin the visualization. The algorithm scans the data stream (represented by bars) to find chunk boundaries (cut-points). |
| | </div> |
| |
|
| | <div class="flex flex-wrap justify-center gap-4 text-xs text-gray-600"> |
| | <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-sm bg-gray-400"></div><span>Data Point</span></div> |
| | <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-sm bg-blue-500"></div><span>Current Scan Position (i)</span></div> |
| | <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-sm bg-red-500"></div><span>Potential Max Point (max_position)</span></div> |
| | <div class="flex items-center gap-1"><div class="w-3 h-3 rounded-sm border border-red-500 bg-red-200"></div><span>Right Window (w)</span></div> |
| | <div class="flex items-center gap-1"><div class="w-1 h-4 bg-green-600"></div><span>Cut-Point</span></div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | |
| | const canvas = document.getElementById('cdcCanvas'); |
| | const ctx = canvas.getContext('2d'); |
| | const dpr = window.devicePixelRatio || 1; |
| | |
| | function resizeCanvas() { |
| | const rect = canvas.getBoundingClientRect(); |
| | canvas.width = rect.width * dpr; |
| | canvas.height = rect.height * dpr; |
| | ctx.scale(dpr, dpr); |
| | draw(); |
| | } |
| | |
| | |
| | const startBtn = document.getElementById('startBtn'); |
| | const pauseBtn = document.getElementById('pauseBtn'); |
| | const stepBtn = document.getElementById('stepBtn'); |
| | const resetBtn = document.getElementById('resetBtn'); |
| | const windowSizeSlider = document.getElementById('windowSize'); |
| | const windowSizeValueSpan = document.getElementById('windowSizeValue'); |
| | const dataSizeSlider = document.getElementById('dataSize'); |
| | const dataSizeValueSpan = document.getElementById('dataSizeValue'); |
| | const speedSlider = document.getElementById('speed'); |
| | const speedValueSpan = document.getElementById('speedValue'); |
| | const explanationDiv = document.getElementById('explanation'); |
| | |
| | |
| | let dataStream = []; |
| | let windowSize = parseInt(windowSizeSlider.value); |
| | let dataSize = parseInt(dataSizeSlider.value); |
| | let animationSpeed = parseInt(speedSlider.value); |
| | let isRunning = false; |
| | let animationFrameId = null; |
| | let timeoutId = null; |
| | |
| | |
| | let currentIndex = 0; |
| | let maxPosition = 0; |
| | let maxValue = -1; |
| | let chunkStart = 0; |
| | let cutPoints = []; |
| | |
| | |
| | function init() { |
| | isRunning = false; |
| | if (animationFrameId) cancelAnimationFrame(animationFrameId); |
| | if (timeoutId) clearTimeout(timeoutId); |
| | animationFrameId = null; |
| | timeoutId = null; |
| | |
| | windowSize = parseInt(windowSizeSlider.value); |
| | dataSize = parseInt(dataSizeSlider.value); |
| | animationSpeed = parseInt(speedSlider.value); |
| | |
| | |
| | dataStream = Array.from({ length: dataSize }, () => Math.floor(Math.random() * 256)); |
| | |
| | |
| | currentIndex = 0; |
| | maxPosition = 0; |
| | maxValue = dataStream.length > 0 ? dataStream[0] : -1; |
| | chunkStart = 0; |
| | cutPoints = []; |
| | |
| | |
| | startBtn.disabled = false; |
| | pauseBtn.disabled = true; |
| | stepBtn.disabled = false; |
| | windowSizeSlider.disabled = false; |
| | dataSizeSlider.disabled = false; |
| | updateExplanation(`Ready. Data Size: ${dataSize}, Window Size (w): ${windowSize}. Click Start or Step.`); |
| | |
| | resizeCanvas(); |
| | } |
| | |
| | |
| | function draw() { |
| | if (!ctx) return; |
| | |
| | const width = canvas.width / dpr; |
| | const height = canvas.height / dpr; |
| | const barWidth = width / dataStream.length; |
| | const maxBarHeight = height * 0.9; |
| | |
| | |
| | ctx.clearRect(0, 0, width, height); |
| | |
| | |
| | dataStream.forEach((value, index) => { |
| | const barHeight = (value / 255) * maxBarHeight; |
| | const x = index * barWidth; |
| | const y = height - barHeight; |
| | |
| | |
| | ctx.fillStyle = '#9ca3af'; |
| | |
| | |
| | if (index === currentIndex && currentIndex < dataStream.length) { |
| | ctx.fillStyle = '#3b82f6'; |
| | } |
| | |
| | |
| | if (index === maxPosition && currentIndex < dataStream.length) { |
| | ctx.fillStyle = '#ef4444'; |
| | } |
| | |
| | |
| | if (index > maxPosition && index <= maxPosition + windowSize && currentIndex < dataStream.length) { |
| | |
| | if (index !== currentIndex && index !== maxPosition) { |
| | ctx.fillStyle = '#fecaca'; |
| | } |
| | |
| | ctx.strokeStyle = '#ef4444'; |
| | ctx.lineWidth = 1; |
| | ctx.strokeRect(x, y, barWidth, barHeight); |
| | } |
| | |
| | |
| | ctx.fillRect(x, y, barWidth * 0.9, barHeight); |
| | }); |
| | |
| | |
| | ctx.strokeStyle = '#16a34a'; |
| | ctx.lineWidth = 2; |
| | cutPoints.forEach(cutIndex => { |
| | const x = (cutIndex + 1) * barWidth; |
| | if (x <= width) { |
| | ctx.beginPath(); |
| | ctx.moveTo(x, height * 0.05); |
| | ctx.lineTo(x, height * 0.95); |
| | ctx.stroke(); |
| | } |
| | }); |
| | } |
| | |
| | |
| | function algorithmStep() { |
| | if (currentIndex >= dataStream.length) { |
| | updateExplanation(`End of data stream reached. Found ${cutPoints.length} cut-points.`); |
| | stop(); |
| | return false; |
| | } |
| | |
| | const currentValue = dataStream[currentIndex]; |
| | let explanation = `Step: Scanning index ${currentIndex} (Value: ${currentValue}). Current Max: Value ${maxValue} at index ${maxPosition}.`; |
| | |
| | |
| | |
| | if (currentValue <= maxValue) { |
| | explanation += `\nValue ${currentValue} <= Max Value ${maxValue}. Checking window condition.`; |
| | |
| | if (currentIndex === maxPosition + windowSize) { |
| | explanation += `\nIndex ${currentIndex} == Max Position ${maxPosition} + Window Size ${windowSize}. Found Cut-Point!`; |
| | |
| | const cutPointIndex = currentIndex; |
| | |
| | if (cutPointIndex < dataStream.length && !cutPoints.includes(cutPointIndex)) { |
| | cutPoints.push(cutPointIndex); |
| | } |
| | |
| | |
| | chunkStart = cutPointIndex + 1; |
| | currentIndex = chunkStart; |
| | |
| | |
| | if (currentIndex < dataStream.length) { |
| | maxPosition = currentIndex; |
| | maxValue = dataStream[currentIndex]; |
| | explanation += `\nResetting search for next chunk starting at index ${chunkStart}. New Max: Value ${maxValue} at index ${maxPosition}.`; |
| | } else { |
| | explanation += `\nEnd of data stream reached after finding cut-point.`; |
| | updateExplanation(explanation); |
| | stop(); |
| | return false; |
| | } |
| | } else { |
| | explanation += `\nIndex ${currentIndex} != Max Position ${maxPosition} + Window Size ${windowSize}. Continue scanning.`; |
| | currentIndex++; |
| | } |
| | } |
| | |
| | else { |
| | explanation += `\nValue ${currentValue} > Max Value ${maxValue}. Updating Max Point.`; |
| | |
| | maxValue = currentValue; |
| | maxPosition = currentIndex; |
| | explanation += `\nNew Max: Value ${maxValue} at index ${maxPosition}. Continue scanning.`; |
| | currentIndex++; |
| | } |
| | |
| | updateExplanation(explanation); |
| | return true; |
| | } |
| | |
| | |
| | function loop() { |
| | if (!isRunning) return; |
| | |
| | const continueProcessing = algorithmStep(); |
| | draw(); |
| | |
| | if (continueProcessing) { |
| | |
| | timeoutId = setTimeout(() => { |
| | animationFrameId = requestAnimationFrame(loop); |
| | }, animationSpeed); |
| | } else { |
| | |
| | stop(); |
| | } |
| | } |
| | |
| | |
| | function start() { |
| | if (isRunning) return; |
| | isRunning = true; |
| | startBtn.disabled = true; |
| | pauseBtn.disabled = false; |
| | stepBtn.disabled = true; |
| | windowSizeSlider.disabled = true; |
| | dataSizeSlider.disabled = true; |
| | updateExplanation("Running..."); |
| | loop(); |
| | } |
| | |
| | function pause() { |
| | if (!isRunning) return; |
| | isRunning = false; |
| | if (animationFrameId) cancelAnimationFrame(animationFrameId); |
| | if (timeoutId) clearTimeout(timeoutId); |
| | animationFrameId = null; |
| | timeoutId = null; |
| | startBtn.disabled = false; |
| | pauseBtn.disabled = true; |
| | stepBtn.disabled = false; |
| | updateExplanation("Paused."); |
| | } |
| | |
| | function step() { |
| | if (isRunning) pause(); |
| | isRunning = false; |
| | startBtn.disabled = false; |
| | pauseBtn.disabled = true; |
| | stepBtn.disabled = false; |
| | windowSizeSlider.disabled = true; |
| | dataSizeSlider.disabled = true; |
| | |
| | algorithmStep(); |
| | draw(); |
| | if (currentIndex >= dataStream.length) { |
| | stepBtn.disabled = true; |
| | } |
| | } |
| | |
| | function reset() { |
| | pause(); |
| | init(); |
| | } |
| | |
| | function updateExplanation(text) { |
| | explanationDiv.innerHTML = text.replace(/\n/g, '<br>'); |
| | } |
| | |
| | |
| | startBtn.addEventListener('click', start); |
| | pauseBtn.addEventListener('click', pause); |
| | stepBtn.addEventListener('click', step); |
| | resetBtn.addEventListener('click', reset); |
| | |
| | windowSizeSlider.addEventListener('input', (e) => { |
| | windowSizeValueSpan.textContent = e.target.value; |
| | if (!isRunning) { |
| | windowSize = parseInt(e.target.value); |
| | init(); |
| | } |
| | }); |
| | dataSizeSlider.addEventListener('input', (e) => { |
| | dataSizeValueSpan.textContent = e.target.value; |
| | if (!isRunning) { |
| | dataSize = parseInt(e.target.value); |
| | init(); |
| | } |
| | }); |
| | speedSlider.addEventListener('input', (e) => { |
| | animationSpeed = parseInt(e.target.value); |
| | speedValueSpan.textContent = e.target.value; |
| | |
| | if (isRunning && timeoutId) { |
| | clearTimeout(timeoutId); |
| | timeoutId = setTimeout(() => { |
| | animationFrameId = requestAnimationFrame(loop); |
| | }, animationSpeed); |
| | } |
| | }); |
| | |
| | window.addEventListener('resize', resizeCanvas); |
| | |
| | |
| | init(); |
| | |
| | </script> |
| | </body> |
| | </html> |
| |
|