cyrilsnare commited on
Commit
d02abfa
·
verified ·
1 Parent(s): ebb6fdf

implement the global event heatmap in 8bit style

Browse files
Files changed (1) hide show
  1. index.html +234 -56
index.html CHANGED
@@ -19,30 +19,103 @@
19
  background: #000;
20
  border: 4px solid #00ff00;
21
  image-rendering: pixelated;
 
 
22
  }
23
 
24
- .country:hover {
25
- stroke: #ffff00;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  stroke-width: 2px;
27
- filter: brightness(1.5);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
 
30
- .heat-gradient {
31
- stop-color: #00ff00;
32
- stop-opacity: 0.6;
 
 
 
33
  }
34
 
35
- .heat-gradient-stop-2 {
36
- stop-color: #ffff00;
37
- stop-opacity: 0.8;
 
 
 
 
 
 
 
 
 
38
  }
39
 
40
- .heat-gradient-stop-3 {
41
- stop-color: #ff0000;
42
- stop-opacity: 1;
43
  }
44
 
45
- .pulse-ring {
 
 
 
 
 
46
  animation: pulse 1s infinite;
47
  image-rendering: pixelated;
48
  }
@@ -285,26 +358,46 @@
285
  <div class="text-sm text-slate-600 dark:text-slate-400">98.3% uptime</div>
286
  </div>
287
  </div>
288
-
289
  <!-- Interactive World Map -->
290
- <div class="glass-effect rounded-xl p-6 mb-8">
291
  <div class="flex items-center justify-between mb-6">
292
- <h2 class="text-xl font-bold">Global Event Heat Map</h2>
293
  <div class="flex space-x-2">
294
- <button class="px-4 py-2 rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors">
295
- <i data-feather="download" class="w-4 h-4 mr-2 inline"></i>
296
- Export
297
  </button>
298
- <button class="px-4 py-2 rounded-lg glass-effect hover:bg-slate-200/50 dark:hover:bg-slate-700/50 transition-colors">
299
- <i data-feather="refresh-cw" class="w-4 h-4 mr-2 inline"></i>
300
- Refresh
301
  </button>
302
  </div>
303
  </div>
304
- <div id="worldMap" class="world-map rounded-lg"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  </div>
306
-
307
- <!-- Scrape Runs & Metrics -->
308
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
309
  <!-- Recent Scrape Runs -->
310
  <div class="glass-effect rounded-xl p-6">
@@ -397,45 +490,130 @@
397
  document.getElementById('pluginsCounter').textContent = plugins.toLocaleString();
398
  }, 5000);
399
  }
400
-
401
- // World Map with D3.js
402
  function initWorldMap() {
403
- const svg = d3.select("#worldMap")
404
- .append("svg")
405
- .attr("width", "100%")
406
- .attr("height", "100%");
407
 
408
- // Simplified world map with sample data
409
- const projection = d3.geoNaturalEarth1()
410
- .scale(150)
411
  .translate([400, 300]);
412
 
413
  const path = d3.geoPath().projection(projection);
414
 
415
- // Sample data for heat map
416
  const sampleData = {
417
- "USA": { events: 800000, music: "Rock", capacity: 2500000 },
418
- "GBR": { events: 450000, music: "Pop", capacity: 1200000 },
419
- "DEU": { events: 320000, music: "Electronic", capacity: 900000 },
420
- "FRA": { events: 280000, music: "Hip Hop", capacity: 750000 },
421
- "JPN": { events: 210000, music: "J-Pop", capacity: 600000 }
 
 
 
 
 
422
  };
423
 
424
- // Draw base map
425
- d3.json("https://cdn.jsdelivr.net/npm/world-atlas@1/countries-110m.json").then(function(world) {
426
- svg.selectAll("path")
427
- .data(topojson.feature(world, world.objects.countries).features)
428
- .enter()
429
- .append("path")
430
- .attr("d", path)
431
- .attr("class", "country")
432
- .style("fill", function(d) {
433
- const countryData = sampleData[d.id];
434
- return countryData ? `rgba(59, 130, 246, ${countryData.events / 1000000})` : "#374151";
435
- })
436
- .style("stroke", "#4B5563")
437
- .style("stroke-width", 0.5)
438
- .on("mouseover", function(event, d) {
439
- const countryData = sampleData[d.id];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  </body>
441
  </html>
 
19
  background: #000;
20
  border: 4px solid #00ff00;
21
  image-rendering: pixelated;
22
+ position: relative;
23
+ overflow: hidden;
24
  }
25
 
26
+ .pixel-map {
27
+ image-rendering: pixelated;
28
+ background: #000;
29
+ }
30
+
31
+ .map-grid {
32
+ position: absolute;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ background-image:
38
+ linear-gradient(#00ff00 1px, transparent 1px),
39
+ linear-gradient(90deg, #00ff00 1px, transparent 1px);
40
+ background-size: 20px 20px;
41
+ opacity: 0.3;
42
+ pointer-events: none;
43
+ }
44
+
45
+ .country-pixel {
46
+ fill: #000;
47
+ stroke: #00ff00;
48
  stroke-width: 2px;
49
+ image-rendering: pixelated;
50
+ transition: all 0.2s;
51
+ }
52
+
53
+ .country-pixel:hover {
54
+ stroke: #ffff00;
55
+ stroke-width: 3px;
56
+ filter: drop-shadow(0 0 4px #ffff00);
57
+ }
58
+
59
+ .heat-1 { fill: #004400; }
60
+ .heat-2 { fill: #008800; }
61
+ .heat-3 { fill: #00cc00; }
62
+ .heat-4 { fill: #00ff00; }
63
+ .heat-5 { fill: #88ff00; }
64
+ .heat-6 { fill: #ffff00; }
65
+ .heat-7 { fill: #ff8800; }
66
+ .heat-8 { fill: #ff0000; }
67
+
68
+ .map-legend {
69
+ position: absolute;
70
+ bottom: 10px;
71
+ right: 10px;
72
+ background: #000;
73
+ border: 3px solid #00ff00;
74
+ padding: 8px;
75
+ image-rendering: pixelated;
76
+ }
77
+
78
+ .legend-item {
79
+ display: flex;
80
+ align-items: center;
81
+ margin: 4px 0;
82
+ font-family: 'Courier New', monospace;
83
+ font-size: 12px;
84
  }
85
 
86
+ .legend-color {
87
+ width: 16px;
88
+ height: 16px;
89
+ margin-right: 8px;
90
+ border: 1px solid #00ff00;
91
+ image-rendering: pixelated;
92
  }
93
 
94
+ .map-tooltip {
95
+ position: absolute;
96
+ background: #000;
97
+ border: 3px solid #ffff00;
98
+ padding: 8px;
99
+ font-family: 'Courier New', monospace;
100
+ font-size: 12px;
101
+ image-rendering: pixelated;
102
+ pointer-events: none;
103
+ opacity: 0;
104
+ transition: opacity 0.2s;
105
+ z-index: 100;
106
  }
107
 
108
+ .pulse-dot {
109
+ animation: pixel-pulse 1.5s infinite;
110
+ image-rendering: pixelated;
111
  }
112
 
113
+ @keyframes pixel-pulse {
114
+ 0% { transform: scale(1); opacity: 1; }
115
+ 50% { transform: scale(1.3); opacity: 0.7; }
116
+ 100% { transform: scale(1); opacity: 1; }
117
+ }
118
+ .pulse-ring {
119
  animation: pulse 1s infinite;
120
  image-rendering: pixelated;
121
  }
 
358
  <div class="text-sm text-slate-600 dark:text-slate-400">98.3% uptime</div>
359
  </div>
360
  </div>
 
361
  <!-- Interactive World Map -->
362
+ <div class="pixel-card p-6 mb-8">
363
  <div class="flex items-center justify-between mb-6">
364
+ <h2 class="text-xl font-bold pixel-text">GLOBAL_EVENT_HEAT_MAP</h2>
365
  <div class="flex space-x-2">
366
+ <button class="pixel-button">
367
+ <i data-feather="download" class="w-4 h-4 mr-2"></i>
368
+ EXPORT
369
  </button>
370
+ <button class="pixel-button">
371
+ <i data-feather="refresh-cw" class="w-4 h-4 mr-2"></i>
372
+ REFRESH
373
  </button>
374
  </div>
375
  </div>
376
+ <div id="worldMap" class="world-map">
377
+ <div class="map-grid"></div>
378
+ <svg id="pixelMap" class="pixel-map" width="100%" height="100%"></svg>
379
+ <div id="mapTooltip" class="map-tooltip"></div>
380
+ <div class="map-legend">
381
+ <div class="legend-item">
382
+ <div class="legend-color heat-1"></div>
383
+ <span>0-100K</span>
384
+ </div>
385
+ <div class="legend-item">
386
+ <div class="legend-color heat-3"></div>
387
+ <span>100K-500K</span>
388
+ </div>
389
+ <div class="legend-item">
390
+ <div class="legend-color heat-5"></div>
391
+ <span>500K-1M</span>
392
+ </div>
393
+ <div class="legend-item">
394
+ <div class="legend-color heat-7"></div>
395
+ <span>1M+</span>
396
+ </div>
397
+ </div>
398
+ </div>
399
  </div>
400
+ <!-- Scrape Runs & Metrics -->
 
401
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
402
  <!-- Recent Scrape Runs -->
403
  <div class="glass-effect rounded-xl p-6">
 
490
  document.getElementById('pluginsCounter').textContent = plugins.toLocaleString();
491
  }, 5000);
492
  }
493
+ // 8-bit World Map with D3.js
 
494
  function initWorldMap() {
495
+ const svg = d3.select("#pixelMap");
496
+ const tooltip = d3.select("#mapTooltip");
 
 
497
 
498
+ // Simplified projection for pixelated look
499
+ const projection = d3.geoMercator()
500
+ .scale(120)
501
  .translate([400, 300]);
502
 
503
  const path = d3.geoPath().projection(projection);
504
 
505
+ // Enhanced sample data for heat map
506
  const sampleData = {
507
+ "USA": { events: 842000, music: "Rock", capacity: 2850000, cities: 245 },
508
+ "GBR": { events: 467000, music: "Pop", capacity: 1280000, cities: 89 },
509
+ "DEU": { events: 328000, music: "Electronic", capacity: 920000, cities: 76 },
510
+ "FRA": { events: 291000, music: "Hip Hop", capacity: 780000, cities: 68 },
511
+ "JPN": { events: 218000, music: "J-Pop", capacity: 640000, cities: 52 },
512
+ "CAN": { events: 187000, music: "Indie", capacity: 520000, cities: 45 },
513
+ "AUS": { events: 156000, music: "Rock", capacity: 410000, cities: 38 },
514
+ "BRA": { events: 134000, music: "Samba", capacity: 380000, cities: 41 },
515
+ "ITA": { events: 123000, music: "Opera", capacity: 320000, cities: 35 },
516
+ "ESP": { events: 118000, music: "Flamenco", capacity: 290000, cities: 32 }
517
  };
518
 
519
+ // Get heat level based on event count
520
+ function getHeatLevel(events) {
521
+ if (events > 1000000) return 8;
522
+ if (events > 800000) return 7;
523
+ if (events > 600000) return 6;
524
+ if (events > 400000) return 5;
525
+ if (events > 200000) return 4;
526
+ if (events > 100000) return 3;
527
+ if (events > 50000) return 2;
528
+ return 1;
529
+ }
530
+
531
+ // Draw simplified pixelated map
532
+ const simplifiedWorld = {
533
+ type: "FeatureCollection",
534
+ features: [
535
+ // North America
536
+ { type: "Feature", geometry: { type: "Polygon", coordinates: [[[-130, 25], [-130, 50], [-65, 50], [-65, 25], [-130, 25]]] }, properties: { name: "North America", id: "USA" }},
537
+ // Europe
538
+ { type: "Feature", geometry: { type: "Polygon", coordinates: [[[-10, 35], [-10, 60], [40, 60], [40, 35], [-10, 35]]] }, properties: { name: "Europe", id: "EUR" }},
539
+ // Asia
540
+ { type: "Feature", geometry: { type: "Polygon", coordinates: [[[60, 10], [60, 60], [140, 60], [140, 10], [60, 10]]] }, properties: { name: "Asia", id: "ASIA" }},
541
+ // South America
542
+ { type: "Feature", geometry: { type: "Polygon", coordinates: [[[-80, -55], [-80, 15], [-35, 15], [-35, -55], [-80, -55]]] }, properties: { name: "South America", id: "SAM" }},
543
+ // Africa
544
+ { type: "Feature", geometry: { type: "Polygon", coordinates: [[[-20, -35], [-20, 35], [50, 35], [50, -35], [-20, -35]]] }, properties: { name: "Africa", id: "AFR" }},
545
+ // Australia
546
+ { type: "Feature", geometry: { type: "Polygon", coordinates: [[[110, -45], [110, -10], [155, -10], [155, -45], [110, -45]]] }, properties: { name: "Australia", id: "AUS" }}
547
+ ]
548
+ };
549
+
550
+ // Draw pixelated countries
551
+ svg.selectAll("path")
552
+ .data(simplifiedWorld.features)
553
+ .enter()
554
+ .append("path")
555
+ .attr("d", path)
556
+ .attr("class", "country-pixel")
557
+ .attr("fill", function(d) {
558
+ const countryData = sampleData[d.properties.id];
559
+ if (countryData) {
560
+ const heatLevel = getHeatLevel(countryData.events);
561
+ return `var(--heat-${heatLevel})`;
562
+ }
563
+ return "#000";
564
+ })
565
+ .on("mouseover", function(event, d) {
566
+ const countryData = sampleData[d.properties.id];
567
+ if (countryData) {
568
+ tooltip
569
+ .style("opacity", 1)
570
+ .html(`
571
+ <div class="font-bold">${d.properties.name}</div>
572
+ <div>Events: ${countryData.events.toLocaleString()}</div>
573
+ <div>Top Genre: ${countryData.music}</div>
574
+ <div>Cities: ${countryData.cities}</div>
575
+ <div>Capacity: ${countryData.capacity.toLocaleString()}</div>
576
+ `)
577
+ .style("left", (event.pageX + 10) + "px")
578
+ .style("top", (event.pageY - 10) + "px");
579
+ }
580
+
581
+ d3.select(this)
582
+ .style("filter", "drop-shadow(0 0 6px #ffff00)");
583
+ })
584
+ .on("mousemove", function(event) {
585
+ tooltip
586
+ .style("left", (event.pageX + 10) + "px")
587
+ .style("top", (event.pageY - 10) + "px");
588
+ })
589
+ .on("mouseout", function() {
590
+ tooltip.style("opacity", 0);
591
+ d3.select(this)
592
+ .style("filter", "none");
593
+ });
594
+
595
+ // Add pulse dots for major cities
596
+ const majorCities = [
597
+ { name: "New York", coords: [-74, 40.7], events: 284000 },
598
+ { name: "London", coords: [-0.1, 51.5], events: 198000 },
599
+ { name: "Tokyo", coords: [139.7, 35.7], events: 167000 },
600
+ { name: "Berlin", coords: [13.4, 52.5], events: 142000 },
601
+ { name: "Paris", coords: [2.3, 48.9], events: 128000 },
602
+ { name: "Sydney", coords: [151.2, -33.9], events: 98000 },
603
+ { name: "São Paulo", coords: [-46.6, -23.6], events: 87000 }
604
+ ];
605
+
606
+ svg.selectAll("circle")
607
+ .data(majorCities)
608
+ .enter()
609
+ .append("circle")
610
+ .attr("cx", d => projection(d.coords)[0])
611
+ .attr("cy", d => projection(d.coords)[1])
612
+ .attr("r", 4)
613
+ .attr("class", "pulse-dot")
614
+ .attr("fill", "#ffff00")
615
+ .attr("stroke", "#000")
616
+ .attr("stroke-width", 1);
617
+ }
618
  </body>
619
  </html>