aldigobbler commited on
Commit
0d3897e
·
verified ·
1 Parent(s): 9be9504

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +449 -19
index.html CHANGED
@@ -1,19 +1,449 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Embeddings Visualizer</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ overflow: hidden;
11
+ font-family: Arial, sans-serif;
12
+ }
13
+ canvas {
14
+ display: block;
15
+ }
16
+ #info {
17
+ position: absolute;
18
+ top: 10px;
19
+ left: 10px;
20
+ background-color: rgba(0, 0, 0, 0.7);
21
+ color: white;
22
+ padding: 10px;
23
+ border-radius: 5px;
24
+ display: none;
25
+ max-width: 300px;
26
+ max-height: 200px;
27
+ overflow: auto;
28
+ }
29
+ #loading {
30
+ position: absolute;
31
+ top: 50%;
32
+ left: 50%;
33
+ transform: translate(-50%, -50%);
34
+ background-color: rgba(0, 0, 0, 0.7);
35
+ color: white;
36
+ padding: 20px;
37
+ border-radius: 5px;
38
+ font-size: 18px;
39
+ }
40
+ #legend {
41
+ position: absolute;
42
+ top: 10px;
43
+ right: 10px;
44
+ background-color: rgba(0, 0, 0, 0.7);
45
+ color: white;
46
+ padding: 10px;
47
+ border-radius: 5px;
48
+ max-width: 200px;
49
+ }
50
+ .color-box {
51
+ display: inline-block;
52
+ width: 15px;
53
+ height: 15px;
54
+ margin-right: 8px;
55
+ border-radius: 3px;
56
+ }
57
+ .legend-item {
58
+ margin: 5px 0;
59
+ display: flex;
60
+ align-items: center;
61
+ }
62
+ #controls {
63
+ position: absolute;
64
+ bottom: 10px;
65
+ left: 10px;
66
+ background-color: rgba(0, 0, 0, 0.7);
67
+ color: white;
68
+ padding: 10px;
69
+ border-radius: 5px;
70
+ }
71
+ select, button {
72
+ margin: 5px 0;
73
+ padding: 5px;
74
+ border-radius: 3px;
75
+ border: none;
76
+ }
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <div id="loading">Loading embeddings, please wait...</div>
81
+ <div id="info"></div>
82
+ <div id="legend"></div>
83
+ <div id="controls">
84
+ <div>
85
+ <label for="category-filter">Filter by category:</label>
86
+ <select id="category-filter">
87
+ <option value="all">All Categories</option>
88
+ </select>
89
+ </div>
90
+ <div>
91
+ <label for="point-size">Point Size:</label>
92
+ <input type="range" id="point-size" min="0.05" max="0.3" step="0.01" value="0.1">
93
+ </div>
94
+ <button id="reset-view">Reset View</button>
95
+ </div>
96
+
97
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
98
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
99
+
100
+ <script>
101
+ // Set up the scene
102
+ const scene = new THREE.Scene();
103
+ scene.background = new THREE.Color(0x111111);
104
+
105
+ // Set up the camera
106
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
107
+ camera.position.z = 5;
108
+
109
+ // Set up the renderer
110
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
111
+ renderer.setSize(window.innerWidth, window.innerHeight);
112
+ document.body.appendChild(renderer.domElement);
113
+
114
+ // Add controls for rotating, panning, and zooming
115
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
116
+ controls.enableDamping = true;
117
+ controls.dampingFactor = 0.05;
118
+
119
+ // Add ambient light
120
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
121
+ scene.add(ambientLight);
122
+
123
+ // Add directional light
124
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
125
+ directionalLight.position.set(1, 1, 1);
126
+ scene.add(directionalLight);
127
+
128
+ // Variables for raycasting (detecting when mouse hovers over points)
129
+ const raycaster = new THREE.Raycaster();
130
+ const mouse = new THREE.Vector2();
131
+ let hoveredPoint = null;
132
+ let points = [];
133
+ let pointsData = [];
134
+ let pointCloud = null;
135
+ let pointSize = 0.1;
136
+ let categoryColors = {};
137
+ let categories = [];
138
+ let originalPositions = null;
139
+ let visibleCategories = new Set();
140
+
141
+ // Create a color scheme for categories
142
+ function generateColorMap(categories) {
143
+ const colorMap = {};
144
+ const hueStep = 1 / categories.length;
145
+
146
+ categories.forEach((category, index) => {
147
+ const color = new THREE.Color();
148
+ color.setHSL(hueStep * index, 0.7, 0.5);
149
+ colorMap[category] = color;
150
+ });
151
+
152
+ return colorMap;
153
+ }
154
+
155
+ // Create the legend
156
+ function createLegend(colorMap) {
157
+ const legendEl = document.getElementById('legend');
158
+ legendEl.innerHTML = '<h3>Categories</h3>';
159
+
160
+ Object.entries(colorMap).forEach(([category, color]) => {
161
+ const item = document.createElement('div');
162
+ item.className = 'legend-item';
163
+
164
+ const colorBox = document.createElement('span');
165
+ colorBox.className = 'color-box';
166
+ colorBox.style.backgroundColor = `#${color.getHexString()}`;
167
+
168
+ const label = document.createElement('span');
169
+ label.textContent = category;
170
+
171
+ item.appendChild(colorBox);
172
+ item.appendChild(label);
173
+ legendEl.appendChild(item);
174
+ });
175
+ }
176
+
177
+ // Update category filter dropdown
178
+ function updateCategoryFilter(categories) {
179
+ const filterEl = document.getElementById('category-filter');
180
+
181
+ // Clear existing options except the first one
182
+ while (filterEl.options.length > 1) {
183
+ filterEl.remove(1);
184
+ }
185
+
186
+ // Add categories
187
+ categories.forEach(category => {
188
+ const option = document.createElement('option');
189
+ option.value = category;
190
+ option.textContent = category;
191
+ filterEl.appendChild(option);
192
+ });
193
+ }
194
+
195
+ // Filter points by category
196
+ function filterByCategory(category) {
197
+ if (!pointCloud || !originalPositions) return;
198
+
199
+ const positions = pointCloud.geometry.attributes.position.array;
200
+ const colors = pointCloud.geometry.attributes.color.array;
201
+ const visible = new Float32Array(positions.length);
202
+
203
+ if (category === 'all') {
204
+ // Show all points
205
+ for (let i = 0; i < positions.length; i++) {
206
+ positions[i] = originalPositions[i];
207
+ }
208
+ visibleCategories = new Set(categories);
209
+ } else {
210
+ // Show only points of the selected category
211
+ visibleCategories = new Set([category]);
212
+
213
+ for (let i = 0; i < pointsData.length; i++) {
214
+ const idx = i * 3;
215
+
216
+ if (pointsData[i].category === category) {
217
+ positions[idx] = originalPositions[idx];
218
+ positions[idx + 1] = originalPositions[idx + 1];
219
+ positions[idx + 2] = originalPositions[idx + 2];
220
+ } else {
221
+ // Move points of other categories far away (effectively hiding them)
222
+ positions[idx] = 10000;
223
+ positions[idx + 1] = 10000;
224
+ positions[idx + 2] = 10000;
225
+ }
226
+ }
227
+ }
228
+
229
+ pointCloud.geometry.attributes.position.needsUpdate = true;
230
+ }
231
+
232
+ // Reset camera and controls to initial view
233
+ function resetView() {
234
+ if (!pointCloud) return;
235
+
236
+ // Calculate center of visible points
237
+ const visiblePoints = pointsData.filter(p => visibleCategories.has(p.category));
238
+ if (visiblePoints.length === 0) return;
239
+
240
+ const center = new THREE.Vector3(0, 0, 0);
241
+ const boundingSphere = new THREE.Sphere(center, 5);
242
+ const distance = boundingSphere.radius / Math.sin(camera.fov * Math.PI / 360);
243
+
244
+ camera.position.set(0, 0, distance * 1.2);
245
+ camera.lookAt(center);
246
+
247
+ controls.target.copy(center);
248
+ controls.update();
249
+ }
250
+
251
+ // Load the embeddings data
252
+ Promise.all([
253
+ fetch('/static/embeddings.json').then(res => res.json()),
254
+ fetch('/static/categories.json').then(res => res.json()).catch(() => ({ categories: [] }))
255
+ ]).then(([data, categoryData]) => {
256
+ // Hide loading message
257
+ document.getElementById('loading').style.display = 'none';
258
+
259
+ // Store data for tooltip
260
+ pointsData = data;
261
+
262
+ // Extract categories from points if not provided separately
263
+ if (categoryData.categories && categoryData.categories.length > 0) {
264
+ categories = categoryData.categories;
265
+ } else {
266
+ const categorySet = new Set();
267
+ data.forEach(point => {
268
+ if (point.category) categorySet.add(point.category);
269
+ });
270
+ categories = Array.from(categorySet);
271
+ }
272
+
273
+ // Generate colors for categories
274
+ categoryColors = generateColorMap(categories);
275
+ createLegend(categoryColors);
276
+ updateCategoryFilter(categories);
277
+ visibleCategories = new Set(categories);
278
+
279
+ // Normalize the positions to keep them within reasonable bounds
280
+ let xValues = data.map(p => p.x);
281
+ let yValues = data.map(p => p.y);
282
+ let zValues = data.map(p => p.z);
283
+
284
+ let xMin = Math.min(...xValues), xMax = Math.max(...xValues);
285
+ let yMin = Math.min(...yValues), yMax = Math.max(...yValues);
286
+ let zMin = Math.min(...zValues), zMax = Math.max(...zValues);
287
+
288
+ // Create a geometry for all points
289
+ const geometry = new THREE.BufferGeometry();
290
+ const positions = new Float32Array(data.length * 3);
291
+ const colors = new Float32Array(data.length * 3);
292
+
293
+ for (let i = 0; i < data.length; i++) {
294
+ // Normalize to a range of approximately -5 to 5
295
+ const x = ((data[i].x - xMin) / (xMax - xMin) * 10) - 5;
296
+ const y = ((data[i].y - yMin) / (yMax - yMin) * 10) - 5;
297
+ const z = ((data[i].z - zMin) / (zMax - zMin) * 10) - 5;
298
+
299
+ positions[i * 3] = x;
300
+ positions[i * 3 + 1] = y;
301
+ positions[i * 3 + 2] = z;
302
+
303
+ // Use category color
304
+ let color;
305
+ if (data[i].category && categoryColors[data[i].category]) {
306
+ color = categoryColors[data[i].category];
307
+ } else {
308
+ // Fallback to a random color if no category
309
+ color = new THREE.Color();
310
+ color.setHSL(Math.random(), 0.7, 0.5);
311
+ }
312
+
313
+ colors[i * 3] = color.r;
314
+ colors[i * 3 + 1] = color.g;
315
+ colors[i * 3 + 2] = color.b;
316
+ }
317
+
318
+ // Store original positions for filtering
319
+ originalPositions = positions.slice();
320
+
321
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
322
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
323
+
324
+ // Create the point material
325
+ const material = new THREE.PointsMaterial({
326
+ size: pointSize,
327
+ vertexColors: true,
328
+ sizeAttenuation: true
329
+ });
330
+
331
+ // Create the point cloud
332
+ pointCloud = new THREE.Points(geometry, material);
333
+ scene.add(pointCloud);
334
+
335
+ // Store individual points for raycasting
336
+ for (let i = 0; i < data.length; i++) {
337
+ points.push({
338
+ position: new THREE.Vector3(
339
+ positions[i * 3],
340
+ positions[i * 3 + 1],
341
+ positions[i * 3 + 2]
342
+ ),
343
+ index: i
344
+ });
345
+ }
346
+
347
+ // Set camera position to view the entire scene
348
+ resetView();
349
+
350
+ // Setup event listeners
351
+ document.getElementById('point-size').addEventListener('input', (e) => {
352
+ pointSize = parseFloat(e.target.value);
353
+ if (pointCloud) {
354
+ pointCloud.material.size = pointSize;
355
+ }
356
+ });
357
+
358
+ document.getElementById('category-filter').addEventListener('change', (e) => {
359
+ filterByCategory(e.target.value);
360
+ });
361
+
362
+ document.getElementById('reset-view').addEventListener('click', resetView);
363
+ })
364
+ .catch(error => {
365
+ console.error('Error loading embeddings:', error);
366
+ document.getElementById('loading').textContent = 'Error loading embeddings. Check console for details.';
367
+ });
368
+
369
+ // Handle mouse movement for hover effects
370
+ function onMouseMove(event) {
371
+ // Calculate mouse position in normalized device coordinates (-1 to +1)
372
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
373
+ mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
374
+ }
375
+
376
+ // Handle window resize
377
+ function onWindowResize() {
378
+ camera.aspect = window.innerWidth / window.innerHeight;
379
+ camera.updateProjectionMatrix();
380
+ renderer.setSize(window.innerWidth, window.innerHeight);
381
+ }
382
+
383
+ // Check if mouse is hovering over a point
384
+ function checkIntersection() {
385
+ if (!pointCloud) return;
386
+
387
+ raycaster.setFromCamera(mouse, camera);
388
+
389
+ let intersects = [];
390
+ for (let i = 0; i < points.length; i++) {
391
+ const point = points[i];
392
+ const pointData = pointsData[i];
393
+
394
+ // Skip points that aren't in visible categories
395
+ if (!visibleCategories.has(pointData.category)) continue;
396
+
397
+ const distance = raycaster.ray.distanceToPoint(point.position);
398
+
399
+ // If mouse is close enough to a point (adjust the threshold as needed)
400
+ if (distance < 0.1) {
401
+ intersects.push({ distance, index: point.index });
402
+ }
403
+ }
404
+
405
+ // Sort by distance to get the closest point
406
+ intersects.sort((a, b) => a.distance - b.distance);
407
+
408
+ // Reset previous hover
409
+ if (hoveredPoint !== null) {
410
+ document.getElementById('info').style.display = 'none';
411
+ hoveredPoint = null;
412
+ }
413
+
414
+ // Show info for the new hovered point
415
+ if (intersects.length > 0) {
416
+ const index = intersects[0].index;
417
+ const data = pointsData[index];
418
+
419
+ hoveredPoint = index;
420
+
421
+ const infoElement = document.getElementById('info');
422
+ infoElement.innerHTML = `
423
+ <h3>${data.title}</h3>
424
+ <p><strong>Category:</strong> ${data.category || 'Uncategorized'}</p>
425
+ <p>${data.text}</p>
426
+ `;
427
+ infoElement.style.display = 'block';
428
+ }
429
+ }
430
+
431
+ // Animation loop
432
+ function animate() {
433
+ requestAnimationFrame(animate);
434
+
435
+ controls.update();
436
+ checkIntersection();
437
+
438
+ renderer.render(scene, camera);
439
+ }
440
+
441
+ // Add event listeners
442
+ window.addEventListener('mousemove', onMouseMove, false);
443
+ window.addEventListener('resize', onWindowResize, false);
444
+
445
+ // Start animation
446
+ animate();
447
+ </script>
448
+ </body>
449
+ </html>