ucalyptus's picture
Update index.html
bc1737b verified
<!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>
/* 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>