fredmo commited on
Commit
30c110e
·
verified ·
1 Parent(s): b5c2694

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +138 -107
index.html CHANGED
@@ -110,12 +110,12 @@
110
  <div id="info-panel">
111
  <!-- Info about hovered location will be populated here -->
112
  </div>
113
-
114
  <script>
115
  // --- 1. Data Processing ---
116
 
117
- // Raw data based on the provided table
118
  const rawData = [
 
119
  { accel: "TPU v2", region: "us-central1", zones: "b, c, f", location: "Council Bluffs, Iowa, USA" },
120
  { accel: "TPU v2", region: "europe-west4", zones: "a", location: "Eemshaven, Netherlands" },
121
  { accel: "TPU v2", region: "asia-east1", zones: "c", location: "Changhua County, Taiwan" },
@@ -234,8 +234,9 @@
234
  { accel: "N1+P4", region: "europe-west4", zones: "b, c", location: "Eemshaven, Netherlands" }
235
  ];
236
 
237
- // Geocode locations (Approximate coordinates)
238
  const locationsCoords = {
 
239
  "Council Bluffs, Iowa, USA": { lat: 41.2619, lng: -95.8513 },
240
  "Eemshaven, Netherlands": { lat: 53.4421, lng: 6.8349 },
241
  "Changhua County, Taiwan": { lat: 24.0729, lng: 120.5446 },
@@ -266,57 +267,92 @@
266
  "Los Angeles, California, USA": { lat: 34.0522, lng: -118.2437 }
267
  };
268
 
269
- // Aggregate data by location
270
- const locationsData = {};
 
 
 
 
 
 
271
  const uniqueAccelerators = new Set();
272
 
273
  rawData.forEach(item => {
274
  const locName = item.location;
275
  if (!locationsCoords[locName]) {
276
  console.warn(`Coordinates not found for: ${locName}. Skipping this entry.`);
277
- return; // Skip if no coordinates
278
  }
279
 
280
- if (!locationsData[locName]) {
281
- locationsData[locName] = {
282
  name: locName,
283
  lat: locationsCoords[locName].lat,
284
  lng: locationsCoords[locName].lng,
285
- accelerators: []
 
 
286
  };
287
  }
288
 
289
- // Add accelerator details only if it's not a duplicate entry *for this specific location*
290
- // (We combine regions/zones if the same accelerator appears multiple times for the same location,
291
- // although the provided data seems mostly unique per location-accelerator pair already)
292
- const existingAccelIndex = locationsData[locName].accelerators.findIndex(a => a.name === item.accel);
293
- if (existingAccelIndex === -1) {
294
- locationsData[locName].accelerators.push({
295
- name: item.accel,
296
- regions: [item.region], // Store regions as an array
297
- zones: [item.zones], // Store zones as an array
298
- notes: item.notes ? [item.notes] : [] // Store notes as an array
299
- });
300
- } else {
301
- // If accelerator already exists, add region/zone/notes if they differ (simple check)
302
- const existingAccel = locationsData[locName].accelerators[existingAccelIndex];
303
- if (!existingAccel.regions.includes(item.region)) {
304
- existingAccel.regions.push(item.region);
305
- }
306
- if (!existingAccel.zones.includes(item.zones)) {
307
- existingAccel.zones.push(item.zones);
308
- }
309
- if (item.notes && !existingAccel.notes.includes(item.notes)) {
310
- existingAccel.notes.push(item.notes);
311
- }
312
- }
313
  uniqueAccelerators.add(item.accel);
 
 
 
 
 
 
 
 
314
  });
315
 
316
- // Convert aggregated data to array format for globe.gl
317
- const pointsData = Object.values(locationsData);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
- // Sort accelerator names for consistent filter order
320
  const sortedAccelerators = Array.from(uniqueAccelerators).sort();
321
 
322
  // --- 2. Globe Initialization & Configuration ---
@@ -324,89 +360,68 @@
324
  const globeVizElement = document.getElementById('globeViz');
325
  const infoPanel = document.getElementById('info-panel');
326
 
327
- // Define colors
328
- const tpuColor = '#00FFFF'; // Cyan
329
- const gpuColor = '#67ff94'; // Green
330
- const mixedColor = '#FFA500'; // Orange
331
- const defaultColor = '#FFFFFF'; // White (fallback)
332
-
333
- // List of substrings identifying GPU types in your data
334
- const gpuIdentifiers = ['H100', 'A100', 'L4', 'T4', 'V100', 'P100', 'P4'];
335
-
336
- // Function to determine point color based on accelerator types
337
- function getPointColor(data) {
338
- let hasTPU = false;
339
- let hasGPU = false;
340
-
341
- data.accelerators.forEach(accel => {
342
- if (accel.name.startsWith('TPU')) {
343
- hasTPU = true;
344
- }
345
- if (gpuIdentifiers.some(gpuId => accel.name.includes(gpuId))) {
346
- hasGPU = true;
347
- }
348
- });
349
-
350
- if (hasTPU && hasGPU) return mixedColor;
351
- if (hasTPU) return tpuColor;
352
- if (hasGPU) return gpuColor;
353
- return defaultColor;
354
- }
355
-
356
-
357
  myGlobe(globeVizElement)
358
  .globeImageUrl('//unpkg.com/three-globe/example/img/earth-night.jpg')
359
  .backgroundImageUrl('//unpkg.com/three-globe/example/img/night-sky.png')
360
- .pointsData(pointsData)
361
- .pointColor(d => getPointColor(d)) // Use the function for color
 
 
 
 
 
 
362
  .pointAltitude(0.01)
363
- .pointRadius(0.4) // Adjusted radius (make bigger or smaller as needed)
364
- .pointLabel(d => d.name) // Basic label on hover (can be improved)
365
- .onPointHover(d => { // Show enhanced info panel on hover
366
- globeVizElement.style.cursor = d ? 'pointer' : 'default'; // Change cursor on hover
367
  if (d) {
368
- const pointColor = getPointColor(d); // Get color for the hovered point
 
 
369
  infoPanel.style.display = 'block';
370
 
371
  let accelHtml = '<ul>';
372
- // Sort accelerators within the panel for consistency
373
  const sortedAccels = [...d.accelerators].sort((a, b) => a.name.localeCompare(b.name));
374
 
375
  sortedAccels.forEach(accel => {
376
- // Join arrays for display if multiple entries were merged
377
- const regionStr = accel.regions.join(', ');
378
- const zoneStr = accel.zones.join('; ');
379
- const notesStr = accel.notes.length > 0 ? ` <i>(${accel.notes.join(', ')})</i>` : '';
380
- accelHtml += `<li><b>${accel.name}</b> (${regionStr}: ${zoneStr})${notesStr}</li>`;
381
  });
382
  accelHtml += '</ul>';
383
 
 
384
  infoPanel.innerHTML = `
385
  <div style="display: flex; align-items: center; margin-bottom: 5px;">
386
  <span class="color-indicator" style="background-color: ${pointColor};"></span>
387
- <h4>${d.name}</h4>
388
  </div>
 
389
  ${accelHtml}`;
390
  } else {
391
- infoPanel.style.display = 'none'; // Hide if not hovering over a point
392
  }
393
  });
394
 
395
- // Auto-rotate globe initially
396
  myGlobe.controls().autoRotate = true;
397
  myGlobe.controls().autoRotateSpeed = 0.2;
398
 
399
- // Stop auto-rotate on user interaction
400
  let userInteracted = false;
401
  const controls = myGlobe.controls();
402
  controls.addEventListener('start', () => {
403
  if (!userInteracted) {
404
  controls.autoRotate = false;
405
- userInteracted = true; // Ensure auto-rotate stops permanently after first interaction
406
  console.log('Globe auto-rotation stopped.');
407
  }
408
  });
409
 
 
410
  // --- 3. Filter Implementation ---
411
  const filtersContainer = document.getElementById('accelerator-filters');
412
  const filterCheckboxes = [];
@@ -416,7 +431,7 @@
416
  const checkbox = document.createElement('input');
417
  checkbox.type = 'checkbox';
418
  checkbox.value = accel;
419
- checkbox.checked = true; // Start with all checked
420
  checkbox.addEventListener('change', updateFilters);
421
 
422
  label.appendChild(checkbox);
@@ -426,33 +441,56 @@
426
  });
427
 
428
  function updateFilters() {
429
- const selectedAccelerators = new Set( // Use a Set for faster lookups
430
  filterCheckboxes
431
  .filter(cb => cb.checked)
432
  .map(cb => cb.value)
433
  );
434
 
435
- if (selectedAccelerators.size === 0) {
436
- // If nothing is selected, show nothing
437
- myGlobe.pointsData([]);
438
- } else {
439
- const filteredPoints = pointsData.filter(location => {
440
- // Keep location if it has at least one of the selected accelerators
441
- return location.accelerators.some(accel => selectedAccelerators.has(accel.name));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  });
443
- myGlobe.pointsData(filteredPoints); // Update the globe
444
  }
445
 
446
- // Update info panel if it was visible for a point that is now filtered out
447
- if (infoPanel.style.display === 'block') {
 
 
448
  const currentHoverName = infoPanel.querySelector('h4')?.textContent;
449
- if (currentHoverName && !myGlobe.pointsData().some(p => p.name === currentHoverName)) {
 
450
  infoPanel.style.display = 'none';
451
  }
452
  }
453
  }
454
 
455
- // Show/Hide All buttons
 
456
  document.getElementById('show-all').addEventListener('click', () => {
457
  filterCheckboxes.forEach(cb => cb.checked = true);
458
  updateFilters();
@@ -463,19 +501,12 @@
463
  updateFilters();
464
  });
465
 
466
- // --- 4. Initial Setup & Resize ---
467
-
468
- // Optional: Center the view on a specific point initially
469
- // myGlobe.pointOfView({ lat: 20, lng: 0, altitude: 2.5 }, 1000); // Example: Atlantic view
470
-
471
- // Handle window resize
472
  window.addEventListener('resize', () => {
473
  myGlobe.width(window.innerWidth);
474
  myGlobe.height(window.innerHeight);
475
  });
476
-
477
- // Initial filter application in case some start unchecked (though they don't here)
478
- updateFilters();
479
 
480
  </script>
481
 
 
110
  <div id="info-panel">
111
  <!-- Info about hovered location will be populated here -->
112
  </div>
 
113
  <script>
114
  // --- 1. Data Processing ---
115
 
116
+ // Raw data (keep as is)
117
  const rawData = [
118
+ // ... (Your full rawData array remains here) ...
119
  { accel: "TPU v2", region: "us-central1", zones: "b, c, f", location: "Council Bluffs, Iowa, USA" },
120
  { accel: "TPU v2", region: "europe-west4", zones: "a", location: "Eemshaven, Netherlands" },
121
  { accel: "TPU v2", region: "asia-east1", zones: "c", location: "Changhua County, Taiwan" },
 
234
  { accel: "N1+P4", region: "europe-west4", zones: "b, c", location: "Eemshaven, Netherlands" }
235
  ];
236
 
237
+ // Geocode locations (keep as is)
238
  const locationsCoords = {
239
+ // ... (Your full locationsCoords object remains here) ...
240
  "Council Bluffs, Iowa, USA": { lat: 41.2619, lng: -95.8513 },
241
  "Eemshaven, Netherlands": { lat: 53.4421, lng: 6.8349 },
242
  "Changhua County, Taiwan": { lat: 24.0729, lng: 120.5446 },
 
267
  "Los Angeles, California, USA": { lat: 34.0522, lng: -118.2437 }
268
  };
269
 
270
+ // Define colors and identifiers (keep as is)
271
+ const tpuColor = '#00FFFF'; // Cyan
272
+ const gpuColor = '#67ff94'; // Green
273
+ const defaultColor = '#FFFFFF'; // White (fallback for errors)
274
+ const gpuIdentifiers = ['H100', 'A100', 'L4', 'T4', 'V100', 'P100', 'P4'];
275
+
276
+ // --- Data Aggregation (Intermediate Step) ---
277
+ const locationsAggregated = {};
278
  const uniqueAccelerators = new Set();
279
 
280
  rawData.forEach(item => {
281
  const locName = item.location;
282
  if (!locationsCoords[locName]) {
283
  console.warn(`Coordinates not found for: ${locName}. Skipping this entry.`);
284
+ return;
285
  }
286
 
287
+ if (!locationsAggregated[locName]) {
288
+ locationsAggregated[locName] = {
289
  name: locName,
290
  lat: locationsCoords[locName].lat,
291
  lng: locationsCoords[locName].lng,
292
+ accelerators: [], // Store full details here
293
+ hasTPU: false,
294
+ hasGPU: false
295
  };
296
  }
297
 
298
+ // Add accelerator details (simplified - assumes unique accel names per location from source)
299
+ locationsAggregated[locName].accelerators.push({
300
+ name: item.accel,
301
+ region: item.region,
302
+ zones: item.zones,
303
+ notes: item.notes || ''
304
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  uniqueAccelerators.add(item.accel);
306
+
307
+ // Check type for the location
308
+ if (item.accel.startsWith('TPU')) {
309
+ locationsAggregated[locName].hasTPU = true;
310
+ }
311
+ if (gpuIdentifiers.some(gpuId => item.accel.includes(gpuId))) {
312
+ locationsAggregated[locName].hasGPU = true;
313
+ }
314
  });
315
 
316
+ // --- *** NEW: Create Final pointsData with Offset for Mixed Locations *** ---
317
+ const pointsData = [];
318
+ const locationOffset = 0.4; // Degrees latitude offset - adjust as needed
319
+
320
+ Object.values(locationsAggregated).forEach(loc => {
321
+ if (loc.hasTPU && loc.hasGPU) {
322
+ // Location has BOTH: Create two points with offset
323
+ pointsData.push({
324
+ ...loc, // Spread operator copies name, original lat/lng, full accelerators list
325
+ lat: loc.lat + locationOffset, // Offset TPU marker slightly North
326
+ lng: loc.lng,
327
+ markerType: 'TPU', // Tag for coloring and identification
328
+ displayName: `${loc.name} (TPU)`, // Name for label/hover
329
+ });
330
+ pointsData.push({
331
+ ...loc, // Spread operator copies name, original lat/lng, full accelerators list
332
+ lat: loc.lat - locationOffset, // Offset GPU marker slightly South
333
+ lng: loc.lng,
334
+ markerType: 'GPU', // Tag for coloring and identification
335
+ displayName: `${loc.name} (GPU)`, // Name for label/hover
336
+ });
337
+ } else if (loc.hasTPU) {
338
+ // Location has ONLY TPU
339
+ pointsData.push({
340
+ ...loc,
341
+ markerType: 'TPU',
342
+ displayName: loc.name, // No suffix needed
343
+ });
344
+ } else if (loc.hasGPU) {
345
+ // Location has ONLY GPU
346
+ pointsData.push({
347
+ ...loc,
348
+ markerType: 'GPU',
349
+ displayName: loc.name, // No suffix needed
350
+ });
351
+ }
352
+ // Note: Locations with neither (if data errors existed) would be skipped here
353
+ });
354
 
355
+ // Sort accelerator names for filter list (keep as is)
356
  const sortedAccelerators = Array.from(uniqueAccelerators).sort();
357
 
358
  // --- 2. Globe Initialization & Configuration ---
 
360
  const globeVizElement = document.getElementById('globeViz');
361
  const infoPanel = document.getElementById('info-panel');
362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  myGlobe(globeVizElement)
364
  .globeImageUrl('//unpkg.com/three-globe/example/img/earth-night.jpg')
365
  .backgroundImageUrl('//unpkg.com/three-globe/example/img/night-sky.png')
366
+ .pointsData(pointsData) // Use the NEW pointsData with potential duplicates/offsets
367
+ .pointColor(d => { // --- MODIFIED: Color based on markerType ---
368
+ switch (d.markerType) {
369
+ case 'TPU': return tpuColor;
370
+ case 'GPU': return gpuColor;
371
+ default: return defaultColor; // Fallback
372
+ }
373
+ })
374
  .pointAltitude(0.01)
375
+ .pointRadius(0.35) // Adjust radius if needed (maybe slightly smaller if points are close)
376
+ .pointLabel(d => d.displayName) // Use displayName for labels
377
+ .onPointHover(d => { // --- MODIFIED: Use displayName and correct color ---
378
+ globeVizElement.style.cursor = d ? 'pointer' : 'default';
379
  if (d) {
380
+ // Determine color based on the markerType of the hovered point
381
+ const pointColor = d.markerType === 'TPU' ? tpuColor : (d.markerType === 'GPU' ? gpuColor : defaultColor);
382
+
383
  infoPanel.style.display = 'block';
384
 
385
  let accelHtml = '<ul>';
386
+ // Sort the FULL accelerator list for the original location
387
  const sortedAccels = [...d.accelerators].sort((a, b) => a.name.localeCompare(b.name));
388
 
389
  sortedAccels.forEach(accel => {
390
+ // Display region/zone/notes as before
391
+ const notesStr = accel.notes ? ` <i>(${accel.notes})</i>` : '';
392
+ accelHtml += `<li><b>${accel.name}</b> (${accel.region}: ${accel.zones})${notesStr}</li>`;
 
 
393
  });
394
  accelHtml += '</ul>';
395
 
396
+ // Use displayName in the panel title
397
  infoPanel.innerHTML = `
398
  <div style="display: flex; align-items: center; margin-bottom: 5px;">
399
  <span class="color-indicator" style="background-color: ${pointColor};"></span>
400
+ <h4>${d.displayName}</h4>
401
  </div>
402
+ <div style="font-size: 0.9em; color: #ccc; margin-bottom: 5px;">(Showing all accelerators for ${d.name})</div>
403
  ${accelHtml}`;
404
  } else {
405
+ infoPanel.style.display = 'none';
406
  }
407
  });
408
 
409
+ // Auto-rotate globe (keep as is)
410
  myGlobe.controls().autoRotate = true;
411
  myGlobe.controls().autoRotateSpeed = 0.2;
412
 
413
+ // Stop auto-rotate on user interaction (keep as is)
414
  let userInteracted = false;
415
  const controls = myGlobe.controls();
416
  controls.addEventListener('start', () => {
417
  if (!userInteracted) {
418
  controls.autoRotate = false;
419
+ userInteracted = true;
420
  console.log('Globe auto-rotation stopped.');
421
  }
422
  });
423
 
424
+
425
  // --- 3. Filter Implementation ---
426
  const filtersContainer = document.getElementById('accelerator-filters');
427
  const filterCheckboxes = [];
 
431
  const checkbox = document.createElement('input');
432
  checkbox.type = 'checkbox';
433
  checkbox.value = accel;
434
+ checkbox.checked = true;
435
  checkbox.addEventListener('change', updateFilters);
436
 
437
  label.appendChild(checkbox);
 
441
  });
442
 
443
  function updateFilters() {
444
+ const selectedAccelerators = new Set(
445
  filterCheckboxes
446
  .filter(cb => cb.checked)
447
  .map(cb => cb.value)
448
  );
449
 
450
+ let filteredPoints = [];
451
+ if (selectedAccelerators.size > 0) {
452
+ // Filter the pointsData array (which contains potentially duplicated/offset points)
453
+ filteredPoints = pointsData.filter(point => {
454
+ // Check if *any* accelerator associated with this point's original location
455
+ // is in the selected set.
456
+ const hasSelectedAccelerator = point.accelerators.some(accel => selectedAccelerators.has(accel.name));
457
+
458
+ if (!hasSelectedAccelerator) {
459
+ return false; // If none of the location's accelerators are selected, hide this point.
460
+ }
461
+
462
+ // Further refine: only show the point if its *specific type* (TPU/GPU)
463
+ // corresponds to a selected accelerator *of that type*.
464
+ const isTPUMarker = point.markerType === 'TPU';
465
+ const isGPUMarker = point.markerType === 'GPU';
466
+
467
+ let typeIsSelected = false;
468
+ if (isTPUMarker) {
469
+ typeIsSelected = point.accelerators.some(accel => accel.name.startsWith('TPU') && selectedAccelerators.has(accel.name));
470
+ } else if (isGPUMarker) {
471
+ typeIsSelected = point.accelerators.some(accel => gpuIdentifiers.some(gid => accel.name.includes(gid)) && selectedAccelerators.has(accel.name));
472
+ } else {
473
+ typeIsSelected = true; // Should not happen with current data structure
474
+ }
475
+
476
+ return typeIsSelected; // Show the point only if its specific type is represented in the selection
477
  });
 
478
  }
479
 
480
+ myGlobe.pointsData(filteredPoints); // Update the globe
481
+
482
+ // Update info panel visibility (keep as is)
483
+ if (infoPanel.style.display === 'block') {
484
  const currentHoverName = infoPanel.querySelector('h4')?.textContent;
485
+ // Check against displayName now
486
+ if (currentHoverName && !myGlobe.pointsData().some(p => p.displayName === currentHoverName)) {
487
  infoPanel.style.display = 'none';
488
  }
489
  }
490
  }
491
 
492
+
493
+ // Show/Hide All buttons (keep as is)
494
  document.getElementById('show-all').addEventListener('click', () => {
495
  filterCheckboxes.forEach(cb => cb.checked = true);
496
  updateFilters();
 
501
  updateFilters();
502
  });
503
 
504
+ // --- 4. Initial Setup & Resize --- (keep as is)
 
 
 
 
 
505
  window.addEventListener('resize', () => {
506
  myGlobe.width(window.innerWidth);
507
  myGlobe.height(window.innerHeight);
508
  });
509
+ updateFilters(); // Initial application
 
 
510
 
511
  </script>
512