Download index.html from ucalyptus/quickcdc-visualization: direct link, hf CLI and curl.
- Browser
- Download file 19.2 kB
-
https://huggingface.co/spaces/ucalyptus/quickcdc-visualization/resolve/main/index.html
- Command line
-
hf download hf://spaces/ucalyptus/quickcdc-visualization/index.html
-
curl -L -o index.html https://huggingface.co/spaces/ucalyptus/quickcdc-visualization/resolve/main/index.html
19.2 kB
| <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> | |
| /* Custom styles */ | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #f3f4f6; /* Tailwind gray-100 */ | |
| } | |
| canvas { | |
| background-color: #ffffff; /* White background */ | |
| border-radius: 0.5rem; /* Tailwind rounded-lg */ | |
| box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); /* Tailwind shadow-md */ | |
| } | |
| /* Style buttons */ | |
| 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); /* Tailwind shadow-sm */ | |
| } | |
| button:active { | |
| transform: translateY(1px); | |
| } | |
| /* Style sliders */ | |
| input[type="range"] { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 100%; | |
| height: 8px; | |
| background: #d1d5db; /* Tailwind gray-300 */ | |
| border-radius: 9999px; /* Tailwind rounded-full */ | |
| outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 16px; | |
| height: 16px; | |
| background: #3b82f6; /* Tailwind blue-500 */ | |
| border-radius: 9999px; /* Tailwind rounded-full */ | |
| cursor: pointer; | |
| } | |
| input[type="range"]::-moz-range-thumb { | |
| width: 16px; | |
| height: 16px; | |
| background: #3b82f6; /* Tailwind blue-500 */ | |
| border-radius: 9999px; /* Tailwind rounded-full */ | |
| cursor: pointer; | |
| border: none; | |
| } | |
| /* Explanation box styling */ | |
| #explanation { | |
| background-color: #e0f2fe; /* Tailwind sky-100 */ | |
| border-left: 4px solid #0ea5e9; /* Tailwind sky-500 */ | |
| color: #0369a1; /* Tailwind sky-800 */ | |
| } | |
| </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> | |
| // --- Canvas Setup --- | |
| const canvas = document.getElementById('cdcCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const dpr = window.devicePixelRatio || 1; // Handle high-DPI displays | |
| function resizeCanvas() { | |
| const rect = canvas.getBoundingClientRect(); | |
| canvas.width = rect.width * dpr; | |
| canvas.height = rect.height * dpr; | |
| ctx.scale(dpr, dpr); | |
| draw(); // Redraw after resize | |
| } | |
| // --- DOM Elements --- | |
| 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'); | |
| // --- Algorithm State --- | |
| 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; | |
| // AE specific state | |
| let currentIndex = 0; // Position 'i' in Algorithm 1 | |
| let maxPosition = 0; // Position of the current max value found since last cut-point | |
| let maxValue = -1; // Value at maxPosition | |
| let chunkStart = 0; // Index where the current chunk search started | |
| let cutPoints = []; // Array to store found cut-point indices | |
| // --- Initialization --- | |
| 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); | |
| // Generate random data (byte values 0-255) | |
| dataStream = Array.from({ length: dataSize }, () => Math.floor(Math.random() * 256)); | |
| // Reset AE state | |
| currentIndex = 0; | |
| maxPosition = 0; | |
| maxValue = dataStream.length > 0 ? dataStream[0] : -1; // Initialize with first value | |
| chunkStart = 0; | |
| cutPoints = []; | |
| // Update UI | |
| 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(); // Initial draw | |
| } | |
| // --- Drawing Functions --- | |
| 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; // Leave some margin at top | |
| // Clear canvas | |
| ctx.clearRect(0, 0, width, height); | |
| // Draw data bars | |
| dataStream.forEach((value, index) => { | |
| const barHeight = (value / 255) * maxBarHeight; | |
| const x = index * barWidth; | |
| const y = height - barHeight; | |
| // Default color | |
| ctx.fillStyle = '#9ca3af'; // Tailwind gray-400 | |
| // Highlight current scan position | |
| if (index === currentIndex && currentIndex < dataStream.length) { | |
| ctx.fillStyle = '#3b82f6'; // Tailwind blue-500 | |
| } | |
| // Highlight potential max point | |
| if (index === maxPosition && currentIndex < dataStream.length) { | |
| ctx.fillStyle = '#ef4444'; // Tailwind red-500 | |
| } | |
| // Highlight right window | |
| if (index > maxPosition && index <= maxPosition + windowSize && currentIndex < dataStream.length) { | |
| // Check if it's also the current index or max position | |
| if (index !== currentIndex && index !== maxPosition) { | |
| ctx.fillStyle = '#fecaca'; // Tailwind red-200 | |
| } | |
| // Draw border for window | |
| ctx.strokeStyle = '#ef4444'; // Tailwind red-500 | |
| ctx.lineWidth = 1; | |
| ctx.strokeRect(x, y, barWidth, barHeight); | |
| } | |
| ctx.fillRect(x, y, barWidth * 0.9, barHeight); // Add small gap between bars | |
| }); | |
| // Draw cut points | |
| ctx.strokeStyle = '#16a34a'; // Tailwind green-600 | |
| ctx.lineWidth = 2; | |
| cutPoints.forEach(cutIndex => { | |
| const x = (cutIndex + 1) * barWidth; // Cut point is *after* the index | |
| if (x <= width) { // Ensure cut point is within canvas bounds | |
| ctx.beginPath(); | |
| ctx.moveTo(x, height * 0.05); | |
| ctx.lineTo(x, height * 0.95); | |
| ctx.stroke(); | |
| } | |
| }); | |
| } | |
| // --- AE Algorithm Step Logic --- | |
| function algorithmStep() { | |
| if (currentIndex >= dataStream.length) { | |
| updateExplanation(`End of data stream reached. Found ${cutPoints.length} cut-points.`); | |
| stop(); | |
| return false; // Indicate end | |
| } | |
| const currentValue = dataStream[currentIndex]; | |
| let explanation = `Step: Scanning index ${currentIndex} (Value: ${currentValue}). Current Max: Value ${maxValue} at index ${maxPosition}.`; | |
| // Algorithm 1 Logic (simplified for visualization) | |
| // Check if current value is less than or equal to the tracked max value | |
| if (currentValue <= maxValue) { | |
| explanation += `\nValue ${currentValue} <= Max Value ${maxValue}. Checking window condition.`; | |
| // Check if we've reached the end of the potential max point's window | |
| if (currentIndex === maxPosition + windowSize) { | |
| explanation += `\nIndex ${currentIndex} == Max Position ${maxPosition} + Window Size ${windowSize}. Found Cut-Point!`; | |
| // Found a cut point! | |
| const cutPointIndex = currentIndex; | |
| // Ensure cut point isn't beyond data length and avoid duplicate points | |
| if (cutPointIndex < dataStream.length && !cutPoints.includes(cutPointIndex)) { | |
| cutPoints.push(cutPointIndex); | |
| } | |
| // Start search for the next chunk from the byte *after* the cut-point | |
| chunkStart = cutPointIndex + 1; | |
| currentIndex = chunkStart; // Move to the start of the next potential chunk | |
| // Reset max for the new chunk search | |
| 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; // End of stream | |
| } | |
| } else { | |
| explanation += `\nIndex ${currentIndex} != Max Position ${maxPosition} + Window Size ${windowSize}. Continue scanning.`; | |
| currentIndex++; // Continue scanning | |
| } | |
| } | |
| // Current value is greater than the tracked max value | |
| else { | |
| explanation += `\nValue ${currentValue} > Max Value ${maxValue}. Updating Max Point.`; | |
| // Update the maximum point | |
| maxValue = currentValue; | |
| maxPosition = currentIndex; | |
| explanation += `\nNew Max: Value ${maxValue} at index ${maxPosition}. Continue scanning.`; | |
| currentIndex++; // Continue scanning | |
| } | |
| updateExplanation(explanation); | |
| return true; // Indicate continue | |
| } | |
| // --- Animation Loop --- | |
| function loop() { | |
| if (!isRunning) return; | |
| const continueProcessing = algorithmStep(); | |
| draw(); | |
| if (continueProcessing) { | |
| // Schedule next step | |
| timeoutId = setTimeout(() => { | |
| animationFrameId = requestAnimationFrame(loop); | |
| }, animationSpeed); | |
| } else { | |
| // Stop if algorithm indicated end | |
| stop(); | |
| } | |
| } | |
| // --- Control Functions --- | |
| 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; // Allow stepping after pause | |
| updateExplanation("Paused."); | |
| } | |
| function step() { | |
| if (isRunning) pause(); // Pause if running before stepping | |
| isRunning = false; // Ensure not running | |
| startBtn.disabled = false; | |
| pauseBtn.disabled = true; | |
| stepBtn.disabled = false; | |
| windowSizeSlider.disabled = true; // Keep disabled during step | |
| dataSizeSlider.disabled = true; // Keep disabled during step | |
| algorithmStep(); | |
| draw(); | |
| if (currentIndex >= dataStream.length) { | |
| stepBtn.disabled = true; // Disable step if at the end | |
| } | |
| } | |
| function reset() { | |
| pause(); // Ensure stopped | |
| init(); | |
| } | |
| function updateExplanation(text) { | |
| explanationDiv.innerHTML = text.replace(/\n/g, '<br>'); // Replace newlines with <br> for HTML | |
| } | |
| // --- Event Listeners --- | |
| 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) { // Only reset if not running | |
| windowSize = parseInt(e.target.value); | |
| init(); // Re-initialize with new parameter | |
| } | |
| }); | |
| 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 running, clear the current timeout and set a new one with the updated speed | |
| if (isRunning && timeoutId) { | |
| clearTimeout(timeoutId); | |
| timeoutId = setTimeout(() => { | |
| animationFrameId = requestAnimationFrame(loop); | |
| }, animationSpeed); | |
| } | |
| }); | |
| window.addEventListener('resize', resizeCanvas); | |
| // --- Initial Setup --- | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |