File size: 19,167 Bytes
a3a2838
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc1737b
7979aa4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
<!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>