awacke1 commited on
Commit
f4aa6b0
·
verified ·
1 Parent(s): 0aeab1d

Create index.html.v1

Browse files
Files changed (1) hide show
  1. index.html.v1 +841 -0
index.html.v1 ADDED
@@ -0,0 +1,841 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Squarified Treemap Explorer</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Arial', sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1400px;
23
+ margin: 0 auto;
24
+ background: white;
25
+ border-radius: 15px;
26
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
27
+ overflow: hidden;
28
+ }
29
+
30
+ .header {
31
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
32
+ color: white;
33
+ padding: 30px;
34
+ text-align: center;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 2.5em;
39
+ margin-bottom: 10px;
40
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
41
+ }
42
+
43
+ .header p {
44
+ opacity: 0.9;
45
+ font-size: 1.1em;
46
+ }
47
+
48
+ .controls {
49
+ padding: 30px;
50
+ background: #f8f9fa;
51
+ border-bottom: 1px solid #e9ecef;
52
+ }
53
+
54
+ .file-input-wrapper {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ gap: 15px;
59
+ }
60
+
61
+ .file-input {
62
+ display: none;
63
+ }
64
+
65
+ .file-input-button {
66
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
67
+ color: white;
68
+ padding: 15px 30px;
69
+ border: none;
70
+ border-radius: 50px;
71
+ font-size: 1.1em;
72
+ cursor: pointer;
73
+ transition: transform 0.2s, box-shadow 0.2s;
74
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
75
+ position: relative;
76
+ overflow: hidden;
77
+ }
78
+
79
+ .file-input-button:hover {
80
+ transform: translateY(-2px);
81
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
82
+ }
83
+
84
+ .demo-button {
85
+ background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
86
+ margin-left: 15px;
87
+ }
88
+
89
+ .button-group {
90
+ display: flex;
91
+ flex-wrap: wrap;
92
+ gap: 15px;
93
+ justify-content: center;
94
+ }
95
+
96
+ .stats {
97
+ display: flex;
98
+ justify-content: center;
99
+ gap: 30px;
100
+ margin-top: 20px;
101
+ flex-wrap: wrap;
102
+ }
103
+
104
+ .stat-item {
105
+ background: white;
106
+ padding: 15px 25px;
107
+ border-radius: 10px;
108
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
109
+ text-align: center;
110
+ }
111
+
112
+ .stat-number {
113
+ font-size: 1.5em;
114
+ font-weight: bold;
115
+ color: #667eea;
116
+ }
117
+
118
+ .stat-label {
119
+ color: #666;
120
+ font-size: 0.9em;
121
+ }
122
+
123
+ .visualization-area {
124
+ padding: 30px;
125
+ min-height: 600px;
126
+ }
127
+
128
+ .treemap-container {
129
+ margin-bottom: 40px;
130
+ border-radius: 10px;
131
+ overflow: hidden;
132
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
133
+ }
134
+
135
+ .treemap-header {
136
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
137
+ color: white;
138
+ padding: 15px 20px;
139
+ font-weight: bold;
140
+ font-size: 1.1em;
141
+ }
142
+
143
+ .treemap {
144
+ position: relative;
145
+ background: #f8f9fa;
146
+ min-height: 400px;
147
+ border: 1px solid #e9ecef;
148
+ }
149
+
150
+ .treemap-node {
151
+ position: absolute;
152
+ border: 1px solid #fff;
153
+ cursor: pointer;
154
+ transition: all 0.3s ease;
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ font-size: 12px;
159
+ font-weight: 500;
160
+ color: #333;
161
+ overflow: hidden;
162
+ }
163
+
164
+ .treemap-node:hover {
165
+ border-color: #667eea;
166
+ border-width: 2px;
167
+ transform: scale(1.02);
168
+ z-index: 100;
169
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
170
+ }
171
+
172
+ .treemap-node.file {
173
+ background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
174
+ color: white;
175
+ }
176
+
177
+ .treemap-node.folder {
178
+ background: linear-gradient(135deg, #fd79a8 0%, #e84393 100%);
179
+ color: white;
180
+ }
181
+
182
+ .tooltip {
183
+ position: absolute;
184
+ background: rgba(0, 0, 0, 0.9);
185
+ color: white;
186
+ padding: 10px 15px;
187
+ border-radius: 5px;
188
+ font-size: 12px;
189
+ pointer-events: none;
190
+ z-index: 1000;
191
+ opacity: 0;
192
+ transition: opacity 0.3s;
193
+ max-width: 250px;
194
+ line-height: 1.4;
195
+ }
196
+
197
+ .tooltip.visible {
198
+ opacity: 1;
199
+ }
200
+
201
+ .loading {
202
+ text-align: center;
203
+ padding: 60px;
204
+ color: #666;
205
+ }
206
+
207
+ .loading-spinner {
208
+ width: 50px;
209
+ height: 50px;
210
+ border: 3px solid #f3f3f3;
211
+ border-top: 3px solid #667eea;
212
+ border-radius: 50%;
213
+ animation: spin 1s linear infinite;
214
+ margin: 0 auto 20px;
215
+ }
216
+
217
+ @keyframes spin {
218
+ 0% { transform: rotate(0deg); }
219
+ 100% { transform: rotate(360deg); }
220
+ }
221
+
222
+ .breadcrumb {
223
+ padding: 10px 20px;
224
+ background: #e9ecef;
225
+ font-size: 14px;
226
+ color: #666;
227
+ }
228
+
229
+ @media (max-width: 768px) {
230
+ .header h1 {
231
+ font-size: 1.8em;
232
+ }
233
+
234
+ .stats {
235
+ gap: 15px;
236
+ }
237
+
238
+ .stat-item {
239
+ padding: 10px 15px;
240
+ font-size: 0.9em;
241
+ }
242
+
243
+ .visualization-area {
244
+ padding: 15px;
245
+ }
246
+ }
247
+ </style>
248
+ </head>
249
+ <body>
250
+ <div class="container">
251
+ <div class="header">
252
+ <h1>🗂️ Squarified Treemap Explorer</h1>
253
+ <p>Visualize hierarchical file structures using advanced treemap algorithms</p>
254
+ </div>
255
+
256
+ <div class="controls">
257
+ <div class="file-input-wrapper">
258
+ <input type="file" id="folderInput" class="file-input" webkitdirectory multiple>
259
+ <div class="button-group">
260
+ <button class="file-input-button" onclick="selectFolder()">
261
+ 📁 Select Folder to Explore
262
+ </button>
263
+ <button class="file-input-button demo-button" onclick="generateDemoData()">
264
+ 🎮 Try Demo Data
265
+ </button>
266
+ </div>
267
+ <div class="stats" id="stats" style="display: none;">
268
+ <div class="stat-item">
269
+ <div class="stat-number" id="totalFiles">0</div>
270
+ <div class="stat-label">Files</div>
271
+ </div>
272
+ <div class="stat-item">
273
+ <div class="stat-number" id="totalFolders">0</div>
274
+ <div class="stat-label">Folders</div>
275
+ </div>
276
+ <div class="stat-item">
277
+ <div class="stat-number" id="totalSize">0 MB</div>
278
+ <div class="stat-label">Total Size</div>
279
+ </div>
280
+ <div class="stat-item">
281
+ <div class="stat-number" id="maxDepth">0</div>
282
+ <div class="stat-label">Max Depth</div>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+
288
+ <div class="visualization-area" id="visualizationArea">
289
+ <div style="text-align: center; padding: 60px; color: #999;">
290
+ <h3>🎯 Ready to Explore</h3>
291
+ <p>Select a folder above to visualize its structure, or try the demo data to see how it works!</p>
292
+ </div>
293
+ </div>
294
+ </div>
295
+
296
+ <div class="tooltip" id="tooltip"></div>
297
+
298
+ <script>
299
+ class SquarifiedTreemapExplorer {
300
+ constructor() {
301
+ this.tooltip = document.getElementById('tooltip');
302
+ this.visualizationArea = document.getElementById('visualizationArea');
303
+ this.folderInput = document.getElementById('folderInput');
304
+ this.fileData = null;
305
+ this.setupEventListeners();
306
+ }
307
+
308
+ setupEventListeners() {
309
+ document.addEventListener('mousemove', (e) => {
310
+ this.tooltip.style.left = e.pageX + 10 + 'px';
311
+ this.tooltip.style.top = e.pageY + 10 + 'px';
312
+ });
313
+
314
+ // Listen for file input changes
315
+ this.folderInput.addEventListener('change', (e) => {
316
+ if (e.target.files.length > 0) {
317
+ this.processFiles(e.target.files);
318
+ }
319
+ });
320
+ }
321
+
322
+ async selectFolder() {
323
+ try {
324
+ // Try modern File System Access API first
325
+ if ('showDirectoryPicker' in window && window.location.protocol === 'https:') {
326
+ const directoryHandle = await window.showDirectoryPicker();
327
+ await this.processDirectory(directoryHandle);
328
+ } else {
329
+ // Fall back to file input
330
+ this.folderInput.click();
331
+ }
332
+ } catch (error) {
333
+ if (error.name !== 'AbortError') {
334
+ console.log('File System Access API not available, using fallback');
335
+ this.folderInput.click();
336
+ }
337
+ }
338
+ }
339
+
340
+ async processFiles(files) {
341
+ this.showLoading();
342
+
343
+ try {
344
+ const fileTree = this.buildFileTreeFromFiles(files);
345
+ this.fileData = fileTree;
346
+ this.updateStats(fileTree);
347
+ this.generateTreemaps(fileTree);
348
+ } catch (error) {
349
+ console.error('Error processing files:', error);
350
+ this.showError('Error processing file structure.');
351
+ }
352
+ }
353
+
354
+ buildFileTreeFromFiles(files) {
355
+ const root = {
356
+ name: 'Selected Folder',
357
+ path: '',
358
+ type: 'directory',
359
+ size: 0,
360
+ children: []
361
+ };
362
+
363
+ const pathMap = new Map();
364
+ pathMap.set('', root);
365
+
366
+ // Sort files by path to ensure directories are created before their contents
367
+ const sortedFiles = Array.from(files).sort((a, b) => a.webkitRelativePath.localeCompare(b.webkitRelativePath));
368
+
369
+ for (const file of sortedFiles) {
370
+ const pathParts = file.webkitRelativePath.split('/');
371
+ let currentPath = '';
372
+
373
+ // Create directory structure
374
+ for (let i = 0; i < pathParts.length - 1; i++) {
375
+ const parentPath = currentPath;
376
+ currentPath = currentPath ? `${currentPath}/${pathParts[i]}` : pathParts[i];
377
+
378
+ if (!pathMap.has(currentPath)) {
379
+ const dirNode = {
380
+ name: pathParts[i],
381
+ path: currentPath,
382
+ type: 'directory',
383
+ size: 0,
384
+ children: []
385
+ };
386
+ pathMap.set(currentPath, dirNode);
387
+ pathMap.get(parentPath).children.push(dirNode);
388
+ }
389
+ }
390
+
391
+ // Add file
392
+ const fileName = pathParts[pathParts.length - 1];
393
+ const filePath = file.webkitRelativePath;
394
+ const parentPath = pathParts.slice(0, -1).join('/');
395
+
396
+ const fileNode = {
397
+ name: fileName,
398
+ path: filePath,
399
+ type: 'file',
400
+ size: file.size,
401
+ lastModified: file.lastModified
402
+ };
403
+
404
+ pathMap.get(parentPath).children.push(fileNode);
405
+ }
406
+
407
+ // Calculate directory sizes and sort children
408
+ this.calculateDirectorySizes(root);
409
+ this.sortChildrenBySize(root);
410
+
411
+ return root;
412
+ }
413
+
414
+ calculateDirectorySizes(node) {
415
+ if (node.type === 'file') {
416
+ return node.size;
417
+ }
418
+
419
+ let totalSize = 0;
420
+ for (const child of node.children || []) {
421
+ totalSize += this.calculateDirectorySizes(child);
422
+ }
423
+ node.size = totalSize;
424
+ return totalSize;
425
+ }
426
+
427
+ sortChildrenBySize(node) {
428
+ if (node.children) {
429
+ node.children.sort((a, b) => b.size - a.size);
430
+ node.children.forEach(child => this.sortChildrenBySize(child));
431
+ }
432
+ }
433
+
434
+ generateDemoData() {
435
+ const demoData = {
436
+ name: 'Demo Project',
437
+ path: '',
438
+ type: 'directory',
439
+ size: 45320000,
440
+ children: [
441
+ {
442
+ name: 'src',
443
+ path: 'src',
444
+ type: 'directory',
445
+ size: 28500000,
446
+ children: [
447
+ { name: 'main.js', path: 'src/main.js', type: 'file', size: 15200000 },
448
+ { name: 'utils.js', path: 'src/utils.js', type: 'file', size: 8500000 },
449
+ { name: 'config.js', path: 'src/config.js', type: 'file', size: 3200000 },
450
+ { name: 'helpers.js', path: 'src/helpers.js', type: 'file', size: 1600000 }
451
+ ]
452
+ },
453
+ {
454
+ name: 'assets',
455
+ path: 'assets',
456
+ type: 'directory',
457
+ size: 12400000,
458
+ children: [
459
+ { name: 'logo.png', path: 'assets/logo.png', type: 'file', size: 5600000 },
460
+ { name: 'background.jpg', path: 'assets/background.jpg', type: 'file', size: 4200000 },
461
+ { name: 'icons.svg', path: 'assets/icons.svg', type: 'file', size: 2600000 }
462
+ ]
463
+ },
464
+ {
465
+ name: 'docs',
466
+ path: 'docs',
467
+ type: 'directory',
468
+ size: 2800000,
469
+ children: [
470
+ { name: 'README.md', path: 'docs/README.md', type: 'file', size: 1200000 },
471
+ { name: 'API.md', path: 'docs/API.md', type: 'file', size: 900000 },
472
+ { name: 'CHANGELOG.md', path: 'docs/CHANGELOG.md', type: 'file', size: 700000 }
473
+ ]
474
+ },
475
+ {
476
+ name: 'tests',
477
+ path: 'tests',
478
+ type: 'directory',
479
+ size: 1320000,
480
+ children: [
481
+ { name: 'main.test.js', path: 'tests/main.test.js', type: 'file', size: 680000 },
482
+ { name: 'utils.test.js', path: 'tests/utils.test.js', type: 'file', size: 440000 },
483
+ { name: 'config.test.js', path: 'tests/config.test.js', type: 'file', size: 200000 }
484
+ ]
485
+ },
486
+ { name: 'package.json', path: 'package.json', type: 'file', size: 180000 },
487
+ { name: 'webpack.config.js', path: 'webpack.config.js', type: 'file', size: 120000 }
488
+ ]
489
+ };
490
+
491
+ this.fileData = demoData;
492
+ this.updateStats(demoData);
493
+ this.generateTreemaps(demoData);
494
+ }
495
+
496
+ async processDirectory(directoryHandle) {
497
+ this.showLoading();
498
+
499
+ try {
500
+ const fileTree = await this.buildFileTree(directoryHandle);
501
+ this.fileData = fileTree;
502
+ this.updateStats(fileTree);
503
+ this.generateTreemaps(fileTree);
504
+ } catch (error) {
505
+ console.error('Error processing directory:', error);
506
+ this.showError('Error processing directory structure.');
507
+ }
508
+ }
509
+
510
+ async buildFileTree(directoryHandle, path = '') {
511
+ const node = {
512
+ name: directoryHandle.name || 'Root',
513
+ path: path,
514
+ type: 'directory',
515
+ size: 0,
516
+ children: []
517
+ };
518
+
519
+ for await (const [name, handle] of directoryHandle.entries()) {
520
+ try {
521
+ const childPath = path ? `${path}/${name}` : name;
522
+
523
+ if (handle.kind === 'file') {
524
+ const file = await handle.getFile();
525
+ node.children.push({
526
+ name: name,
527
+ path: childPath,
528
+ type: 'file',
529
+ size: file.size,
530
+ lastModified: file.lastModified
531
+ });
532
+ node.size += file.size;
533
+ } else if (handle.kind === 'directory') {
534
+ const subDir = await this.buildFileTree(handle, childPath);
535
+ node.children.push(subDir);
536
+ node.size += subDir.size;
537
+ }
538
+ } catch (error) {
539
+ console.warn(`Skipping ${name}:`, error);
540
+ }
541
+ }
542
+
543
+ // Sort children by size (descending) for better treemap layout
544
+ node.children.sort((a, b) => b.size - a.size);
545
+ return node;
546
+ }
547
+
548
+ updateStats(fileTree) {
549
+ const stats = this.calculateStats(fileTree);
550
+
551
+ document.getElementById('totalFiles').textContent = stats.files.toLocaleString();
552
+ document.getElementById('totalFolders').textContent = stats.folders.toLocaleString();
553
+ document.getElementById('totalSize').textContent = this.formatFileSize(stats.size);
554
+ document.getElementById('maxDepth').textContent = stats.depth;
555
+ document.getElementById('stats').style.display = 'flex';
556
+ }
557
+
558
+ calculateStats(node, depth = 0) {
559
+ let stats = {
560
+ files: node.type === 'file' ? 1 : 0,
561
+ folders: node.type === 'directory' ? 1 : 0,
562
+ size: node.size || 0,
563
+ depth: depth
564
+ };
565
+
566
+ if (node.children) {
567
+ for (const child of node.children) {
568
+ const childStats = this.calculateStats(child, depth + 1);
569
+ stats.files += childStats.files;
570
+ stats.folders += childStats.folders;
571
+ stats.size += childStats.size;
572
+ stats.depth = Math.max(stats.depth, childStats.depth);
573
+ }
574
+ }
575
+
576
+ return stats;
577
+ }
578
+
579
+ generateTreemaps(fileTree) {
580
+ this.visualizationArea.innerHTML = '';
581
+
582
+ // Create main treemap
583
+ this.createTreemapContainer(fileTree, 'Root Directory', 0);
584
+
585
+ // Create treemaps for major subdirectories
586
+ if (fileTree.children) {
587
+ const majorFolders = fileTree.children
588
+ .filter(child => child.type === 'directory' && child.children && child.children.length > 0)
589
+ .slice(0, 5); // Show top 5 subdirectories
590
+
591
+ majorFolders.forEach((folder, index) => {
592
+ this.createTreemapContainer(folder, folder.name, index + 1);
593
+ });
594
+ }
595
+ }
596
+
597
+ createTreemapContainer(data, title, level) {
598
+ const container = document.createElement('div');
599
+ container.className = 'treemap-container';
600
+
601
+ const header = document.createElement('div');
602
+ header.className = 'treemap-header';
603
+ header.textContent = `${title} (${this.formatFileSize(data.size)})`;
604
+
605
+ const breadcrumb = document.createElement('div');
606
+ breadcrumb.className = 'breadcrumb';
607
+ breadcrumb.textContent = data.path || '/';
608
+
609
+ const treemap = document.createElement('div');
610
+ treemap.className = 'treemap';
611
+ treemap.style.height = level === 0 ? '500px' : '400px';
612
+
613
+ container.appendChild(header);
614
+ container.appendChild(breadcrumb);
615
+ container.appendChild(treemap);
616
+
617
+ this.visualizationArea.appendChild(container);
618
+
619
+ // Generate squarified treemap layout
620
+ this.renderSquarifiedTreemap(treemap, data);
621
+ }
622
+
623
+ renderSquarifiedTreemap(container, data) {
624
+ if (!data.children || data.children.length === 0) return;
625
+
626
+ const rect = container.getBoundingClientRect();
627
+ const width = rect.width || 800;
628
+ const height = rect.height || 400;
629
+
630
+ const totalSize = data.size;
631
+ const children = data.children.filter(child => child.size > 0);
632
+
633
+ if (children.length === 0) return;
634
+
635
+ // Scale areas to fit container
636
+ const scaledChildren = children.map(child => ({
637
+ ...child,
638
+ scaledSize: (child.size / totalSize) * (width * height)
639
+ }));
640
+
641
+ const layout = this.squarify(scaledChildren, [], width, { x: 0, y: 0, width, height });
642
+ this.renderLayout(container, layout);
643
+ }
644
+
645
+ squarify(children, row, w, container) {
646
+ if (children.length === 0) {
647
+ if (row.length > 0) {
648
+ return this.layoutRow(row, container);
649
+ }
650
+ return [];
651
+ }
652
+
653
+ const c = children[0];
654
+ const newRow = [...row, c];
655
+
656
+ if (row.length === 0 || this.worst(newRow, w) <= this.worst(row, w)) {
657
+ return this.squarify(children.slice(1), newRow, w, container);
658
+ } else {
659
+ const rowLayout = this.layoutRow(row, container);
660
+ const remaining = this.shrinkContainer(container, row, w);
661
+ const restLayout = this.squarify(children, [], this.getShortSide(remaining), remaining);
662
+ return [...rowLayout, ...restLayout];
663
+ }
664
+ }
665
+
666
+ worst(row, w) {
667
+ if (row.length === 0) return Infinity;
668
+
669
+ const areas = row.map(r => r.scaledSize);
670
+ const sum = areas.reduce((a, b) => a + b, 0);
671
+ const max = Math.max(...areas);
672
+ const min = Math.min(...areas);
673
+
674
+ const term1 = (w * w * max) / (sum * sum);
675
+ const term2 = (sum * sum) / (w * w * min);
676
+
677
+ return Math.max(term1, term2);
678
+ }
679
+
680
+ layoutRow(row, container) {
681
+ if (row.length === 0) return [];
682
+
683
+ const sum = row.reduce((acc, r) => acc + r.scaledSize, 0);
684
+ const isVertical = container.width >= container.height;
685
+
686
+ let layouts = [];
687
+ let offset = 0;
688
+
689
+ for (const item of row) {
690
+ let rect;
691
+ if (isVertical) {
692
+ const height = container.height;
693
+ const width = (item.scaledSize / sum) * (sum / height);
694
+ rect = {
695
+ x: container.x + offset,
696
+ y: container.y,
697
+ width: width,
698
+ height: height,
699
+ data: item
700
+ };
701
+ offset += width;
702
+ } else {
703
+ const width = container.width;
704
+ const height = (item.scaledSize / sum) * (sum / width);
705
+ rect = {
706
+ x: container.x,
707
+ y: container.y + offset,
708
+ width: width,
709
+ height: height,
710
+ data: item
711
+ };
712
+ offset += height;
713
+ }
714
+ layouts.push(rect);
715
+ }
716
+
717
+ return layouts;
718
+ }
719
+
720
+ shrinkContainer(container, row, w) {
721
+ const sum = row.reduce((acc, r) => acc + r.scaledSize, 0);
722
+ const isVertical = container.width >= container.height;
723
+
724
+ if (isVertical) {
725
+ const usedWidth = sum / container.height;
726
+ return {
727
+ x: container.x + usedWidth,
728
+ y: container.y,
729
+ width: container.width - usedWidth,
730
+ height: container.height
731
+ };
732
+ } else {
733
+ const usedHeight = sum / container.width;
734
+ return {
735
+ x: container.x,
736
+ y: container.y + usedHeight,
737
+ width: container.width,
738
+ height: container.height - usedHeight
739
+ };
740
+ }
741
+ }
742
+
743
+ getShortSide(container) {
744
+ return Math.min(container.width, container.height);
745
+ }
746
+
747
+ renderLayout(container, layout) {
748
+ container.innerHTML = '';
749
+
750
+ layout.forEach(rect => {
751
+ const element = document.createElement('div');
752
+ element.className = `treemap-node ${rect.data.type}`;
753
+
754
+ element.style.left = `${rect.x}px`;
755
+ element.style.top = `${rect.y}px`;
756
+ element.style.width = `${rect.width}px`;
757
+ element.style.height = `${rect.height}px`;
758
+
759
+ // Show name only if rectangle is large enough
760
+ if (rect.width > 60 && rect.height > 20) {
761
+ element.textContent = rect.data.name;
762
+ }
763
+
764
+ this.addTooltip(element, rect.data);
765
+ container.appendChild(element);
766
+ });
767
+ }
768
+
769
+ addTooltip(element, data) {
770
+ element.addEventListener('mouseenter', () => {
771
+ const tooltipContent = this.createTooltipContent(data);
772
+ this.tooltip.innerHTML = tooltipContent;
773
+ this.tooltip.classList.add('visible');
774
+ });
775
+
776
+ element.addEventListener('mouseleave', () => {
777
+ this.tooltip.classList.remove('visible');
778
+ });
779
+ }
780
+
781
+ createTooltipContent(data) {
782
+ let content = `<strong>${data.name}</strong><br>`;
783
+ content += `Type: ${data.type}<br>`;
784
+ content += `Size: ${this.formatFileSize(data.size)}<br>`;
785
+ content += `Path: ${data.path}<br>`;
786
+
787
+ if (data.type === 'file' && data.lastModified) {
788
+ content += `Modified: ${new Date(data.lastModified).toLocaleDateString()}<br>`;
789
+ }
790
+
791
+ if (data.children) {
792
+ content += `Items: ${data.children.length}`;
793
+ }
794
+
795
+ return content;
796
+ }
797
+
798
+ formatFileSize(bytes) {
799
+ if (bytes === 0) return '0 B';
800
+
801
+ const k = 1024;
802
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
803
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
804
+
805
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
806
+ }
807
+
808
+ showLoading() {
809
+ this.visualizationArea.innerHTML = `
810
+ <div class="loading">
811
+ <div class="loading-spinner"></div>
812
+ <h3>📊 Processing Directory Structure</h3>
813
+ <p>Analyzing files and building treemap visualization...</p>
814
+ </div>
815
+ `;
816
+ }
817
+
818
+ showError(message) {
819
+ this.visualizationArea.innerHTML = `
820
+ <div style="text-align: center; padding: 60px; color: #e74c3c;">
821
+ <h3>❌ Error</h3>
822
+ <p>${message}</p>
823
+ </div>
824
+ `;
825
+ }
826
+ }
827
+
828
+ // Initialize the application
829
+ const app = new SquarifiedTreemapExplorer();
830
+
831
+ // Global functions for button clicks
832
+ function selectFolder() {
833
+ app.selectFolder();
834
+ }
835
+
836
+ function generateDemoData() {
837
+ app.generateDemoData();
838
+ }
839
+ </script>
840
+ </body>
841
+ </html>