fdaudens HF Staff commited on
Commit
4a19518
·
verified ·
1 Parent(s): 1d3fc5f

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +572 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: World Indicators
3
- emoji: 🐠
4
- colorFrom: green
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: world-indicators
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,572 @@
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>World Data Visualizer</title>
7
+ <script src="https://d3js.org/d3.v7.min.js"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ body {
11
+ font-family: 'Arial', sans-serif;
12
+ margin: 0;
13
+ padding: 20px;
14
+ background-color: #f5f7fa;
15
+ color: #333;
16
+ }
17
+
18
+ .container {
19
+ max-width: 1200px;
20
+ margin: 0 auto;
21
+ background-color: #fff;
22
+ border-radius: 10px;
23
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
24
+ padding: 20px;
25
+ }
26
+
27
+ h1 {
28
+ text-align: center;
29
+ color: #2c3e50;
30
+ margin-bottom: 10px;
31
+ }
32
+
33
+ .subtitle {
34
+ text-align: center;
35
+ color: #7f8c8d;
36
+ margin-bottom: 30px;
37
+ }
38
+
39
+ .controls {
40
+ display: flex;
41
+ flex-wrap: wrap;
42
+ gap: 15px;
43
+ margin-bottom: 25px;
44
+ align-items: center;
45
+ padding: 10px;
46
+ background-color: #f8f9fa;
47
+ border-radius: 8px;
48
+ }
49
+
50
+ .filter-buttons {
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ gap: 8px;
54
+ margin-right: auto;
55
+ }
56
+
57
+ .filter-btn {
58
+ padding: 8px 12px;
59
+ border: none;
60
+ border-radius: 20px;
61
+ cursor: pointer;
62
+ font-weight: 600;
63
+ transition: all 0.3s;
64
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
65
+ }
66
+
67
+ .filter-btn:hover {
68
+ opacity: 0.8;
69
+ transform: translateY(-2px);
70
+ }
71
+
72
+ .filter-btn.active {
73
+ transform: scale(1.05);
74
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
75
+ }
76
+
77
+ .year-controls {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 10px;
81
+ }
82
+
83
+ .year-slider {
84
+ width: 200px;
85
+ }
86
+
87
+ .play-btn {
88
+ background-color: #2ecc71;
89
+ color: white;
90
+ border: none;
91
+ padding: 8px 16px;
92
+ border-radius: 4px;
93
+ cursor: pointer;
94
+ transition: all 0.3s;
95
+ }
96
+
97
+ .play-btn:hover {
98
+ background-color: #27ae60;
99
+ }
100
+
101
+ .play-btn i {
102
+ margin-right: 5px;
103
+ }
104
+
105
+ .year-display {
106
+ font-weight: bold;
107
+ min-width: 60px;
108
+ text-align: center;
109
+ }
110
+
111
+ .chart-container {
112
+ width: 100%;
113
+ height: 600px;
114
+ position: relative;
115
+ }
116
+
117
+ .tooltip {
118
+ position: absolute;
119
+ padding: 10px;
120
+ background: rgba(0, 0, 0, 0.8);
121
+ color: #fff;
122
+ border-radius: 5px;
123
+ pointer-events: none;
124
+ text-align: center;
125
+ opacity: 0;
126
+ transition: opacity 0.2s;
127
+ font-size: 14px;
128
+ z-index: 10;
129
+ }
130
+
131
+ .tooltip:after {
132
+ content: "";
133
+ position: absolute;
134
+ top: 100%;
135
+ left: 50%;
136
+ margin-left: -8px;
137
+ border-width: 8px;
138
+ border-style: solid;
139
+ border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
140
+ }
141
+
142
+ .loading {
143
+ text-align: center;
144
+ padding: 50px;
145
+ font-size: 18px;
146
+ color: #7f8c8d;
147
+ }
148
+
149
+ .spinner {
150
+ border: 4px solid rgba(0, 0, 0, 0.1);
151
+ border-radius: 50%;
152
+ border-top: 4px solid #3498db;
153
+ width: 30px;
154
+ height: 30px;
155
+ animation: spin 1s linear infinite;
156
+ margin: 0 auto 20px;
157
+ }
158
+
159
+ @keyframes spin {
160
+ 0% { transform: rotate(0deg); }
161
+ 100% { transform: rotate(360deg); }
162
+ }
163
+
164
+ .axis-label {
165
+ font-size: 12px;
166
+ fill: #555;
167
+ }
168
+
169
+ @media (max-width: 768px) {
170
+ .controls {
171
+ flex-direction: column;
172
+ align-items: stretch;
173
+ }
174
+
175
+ .filter-buttons {
176
+ margin-right: 0;
177
+ margin-bottom: 15px;
178
+ justify-content: center;
179
+ }
180
+
181
+ .year-controls {
182
+ justify-content: center;
183
+ }
184
+
185
+ .chart-container {
186
+ height: 450px;
187
+ }
188
+ }
189
+ </style>
190
+ </head>
191
+ <body>
192
+ <div class="container">
193
+ <h1>World Development Indicators</h1>
194
+ <p class="subtitle">Life Expectancy vs. GDP per capita (1990-2023)</p>
195
+
196
+ <div class="controls">
197
+ <div class="filter-buttons" id="region-buttons">
198
+ <!-- Region buttons will be dynamically generated here -->
199
+ </div>
200
+
201
+ <div class="year-controls">
202
+ <button class="play-btn" id="play-btn">
203
+ <i class="fas fa-play"></i> Play
204
+ </button>
205
+ <span class="year-display" id="year-value">1990</span>
206
+ <input type="range" class="year-slider" id="year-slider" min="1990" max="2023" value="1990">
207
+ </div>
208
+ </div>
209
+
210
+ <div class="chart-container" id="chart-area">
211
+ <div class="loading" id="loading">
212
+ <div class="spinner"></div>
213
+ <p>Loading data...</p>
214
+ </div>
215
+ </div>
216
+
217
+ <div class="tooltip" id="tooltip"></div>
218
+ </div>
219
+
220
+ <script>
221
+ // Configuration
222
+ const config = {
223
+ margin: { top: 40, right: 40, bottom: 60, left: 70 },
224
+ minBubbleSize: 5,
225
+ maxBubbleSize: 40,
226
+ animationDuration: 300,
227
+ playInterval: 1000,
228
+ regions: {
229
+ "Africa": { color: "#e74c3c" },
230
+ "Asia": { color: "#3498db" },
231
+ "Europe": { color: "#2ecc71" },
232
+ "North America": { color: "#f1c40f" },
233
+ "Oceania": { color: "#9b59b6" },
234
+ "South America": { color: "#1abc9c" },
235
+ "Antarctica": { color: "#95a5a6" }
236
+ }
237
+ };
238
+
239
+ // State
240
+ let state = {
241
+ data: null,
242
+ filteredData: null,
243
+ currentYear: 1990,
244
+ activeRegions: Object.keys(config.regions),
245
+ isPlaying: false,
246
+ playIntervalId: null,
247
+ svg: null,
248
+ x: null,
249
+ y: null,
250
+ size: null
251
+ };
252
+
253
+ // DOM elements
254
+ const dom = {
255
+ chartArea: d3.select("#chart-area"),
256
+ loading: d3.select("#loading"),
257
+ tooltip: d3.select("#tooltip"),
258
+ yearSlider: d3.select("#year-slider"),
259
+ yearValue: d3.select("#year-value"),
260
+ playBtn: d3.select("#play-btn"),
261
+ regionButtons: d3.select("#region-buttons")
262
+ };
263
+
264
+ // Initialize the visualization
265
+ async function init() {
266
+ // Load data
267
+ await loadData();
268
+
269
+ // Setup UI controls
270
+ setupControls();
271
+
272
+ // Initialize chart
273
+ initChart();
274
+
275
+ // Render initial view
276
+ updateChart();
277
+
278
+ // Hide loading
279
+ dom.loading.style("display", "none");
280
+ }
281
+
282
+ // Load and process data
283
+ async function loadData() {
284
+ try {
285
+ // Load the CSV data
286
+ const rawData = await d3.csv("world-data.csv");
287
+
288
+ // Process the data
289
+ state.data = rawData.map(d => {
290
+ return {
291
+ entity: d.Entity,
292
+ code: d.Code,
293
+ year: +d.Year,
294
+ lifeExpectancy: d["Life expectancy - Sex: all - Age: 0 - Variant: estimates"] !== ""
295
+ ? +d["Life expectancy - Sex: all - Age: 0 - Variant: estimates"]
296
+ : null,
297
+ gdpPerCapita: d["GDP per capita, PPP (constant 2021 international $)"] !== ""
298
+ ? +d["GDP per capita, PPP (constant 2021 international $)"]
299
+ : null,
300
+ population: d["Population (historical)"] !== ""
301
+ ? +d["Population (historical)"]
302
+ : null,
303
+ region: d["World regions according to OWID"] || "Unknown"
304
+ };
305
+ }).filter(d => d.gdpPerCapita !== null && d.lifeExpectancy !== null && d.population !== null);
306
+ } catch (error) {
307
+ console.error("Error loading data:", error);
308
+ dom.loading.html("<p>Error loading data. Please try again.</p>");
309
+ }
310
+ }
311
+
312
+ // Setup UI controls
313
+ function setupControls() {
314
+ // Create region filter buttons
315
+ Object.keys(config.regions).forEach(region => {
316
+ dom.regionButtons.append("button")
317
+ .classed("filter-btn", true)
318
+ .style("background-color", config.regions[region].color)
319
+ .text(region)
320
+ .attr("data-region", region)
321
+ .on("click", function() {
322
+ toggleRegion(region);
323
+ d3.select(this).classed("active", state.activeRegions.includes(region));
324
+ });
325
+ });
326
+
327
+ // Year slider
328
+ dom.yearSlider.on("input", function() {
329
+ state.currentYear = +this.value;
330
+ dom.yearValue.text(state.currentYear);
331
+ if (!state.isPlaying) {
332
+ updateChart();
333
+ }
334
+ });
335
+
336
+ // Play button
337
+ dom.playBtn.on("click", togglePlay);
338
+ }
339
+
340
+ // Toggle play/pause animation
341
+ function togglePlay() {
342
+ state.isPlaying = !state.isPlaying;
343
+
344
+ if (state.isPlaying) {
345
+ dom.playBtn.html('<i class="fas fa-pause"></i> Pause');
346
+ dom.playBtn.style("background-color", "#e74c3c");
347
+ playAnimation();
348
+ } else {
349
+ dom.playBtn.html('<i class="fas fa-play"></i> Play');
350
+ dom.playBtn.style("background-color", "#2ecc71");
351
+ pauseAnimation();
352
+ }
353
+ }
354
+
355
+ // Play the animation
356
+ function playAnimation() {
357
+ clearInterval(state.playIntervalId);
358
+
359
+ state.playIntervalId = setInterval(() => {
360
+ if (state.currentYear < 2023) {
361
+ state.currentYear++;
362
+ dom.yearSlider.property("value", state.currentYear);
363
+ dom.yearValue.text(state.currentYear);
364
+ updateChart();
365
+ } else {
366
+ pauseAnimation();
367
+ }
368
+ }, config.playInterval);
369
+ }
370
+
371
+ // Pause the animation
372
+ function pauseAnimation() {
373
+ clearInterval(state.playIntervalId);
374
+ state.isPlaying = false;
375
+ dom.playBtn.html('<i class="fas fa-play"></i> Play');
376
+ dom.playBtn.style("background-color", "#2ecc71");
377
+ }
378
+
379
+ // Toggle region filter
380
+ function toggleRegion(region) {
381
+ if (state.activeRegions.includes(region)) {
382
+ state.activeRegions = state.activeRegions.filter(r => r !== region);
383
+ } else {
384
+ state.activeRegions = [...state.activeRegions, region];
385
+ }
386
+
387
+ if (state.activeRegions.length === 0) {
388
+ // If all regions are deselected, select all
389
+ state.activeRegions = Object.keys(config.regions);
390
+ }
391
+
392
+ updateChart();
393
+ }
394
+
395
+ // Initialize chart
396
+ function initChart() {
397
+ // Create SVG
398
+ const width = dom.chartArea.node().clientWidth - config.margin.left - config.margin.right;
399
+ const height = dom.chartArea.node().clientHeight - config.margin.top - config.margin.bottom;
400
+
401
+ state.svg = dom.chartArea.append("svg")
402
+ .attr("width", width + config.margin.left + config.margin.right)
403
+ .attr("height", height + config.margin.top + config.margin.bottom)
404
+ .append("g")
405
+ .attr("transform", `translate(${config.margin.left},${config.margin.top})`);
406
+
407
+ // Create scales
408
+ const gdpExtent = d3.extent(state.data, d => d.gdpPerCapita);
409
+ const lifeExpectancyExtent = d3.extent(state.data, d => d.lifeExpectancy);
410
+ const populationExtent = d3.extent(state.data, d => d.population);
411
+
412
+ state.x = d3.scaleLog()
413
+ .domain([100, gdpExtent[1]])
414
+ .range([0, width]);
415
+
416
+ state.y = d3.scaleLinear()
417
+ .domain([20, lifeExpectancyExtent[1] + 5])
418
+ .range([height, 0]);
419
+
420
+ state.size = d3.scaleSqrt()
421
+ .domain(populationExtent)
422
+ .range([config.minBubbleSize, config.maxBubbleSize]);
423
+
424
+ // Add axes
425
+ state.svg.append("g")
426
+ .attr("class", "x-axis")
427
+ .attr("transform", `translate(0,${height})`);
428
+
429
+ state.svg.append("g")
430
+ .attr("class", "y-axis");
431
+
432
+ // Add axis labels
433
+ state.svg.append("text")
434
+ .attr("class", "axis-label")
435
+ .attr("x", width / 2)
436
+ .attr("y", height + config.margin.bottom - 10)
437
+ .attr("text-anchor", "middle")
438
+ .text("GDP per capita (PPP, constant 2021 international $)");
439
+
440
+ state.svg.append("text")
441
+ .attr("class", "axis-label")
442
+ .attr("transform", "rotate(-90)")
443
+ .attr("x", -height / 2)
444
+ .attr("y", -config.margin.left + 15)
445
+ .attr("text-anchor", "middle")
446
+ .text("Life Expectancy (years)");
447
+ }
448
+
449
+ // Update chart with current filters and year
450
+ function updateChart() {
451
+ // Filter data for current year
452
+ const yearData = state.data.filter(d =>
453
+ d.year === state.currentYear &&
454
+ state.activeRegions.includes(d.region)
455
+ );
456
+
457
+ // Update the chart
458
+ updateBubbles(yearData);
459
+ }
460
+
461
+ // Update bubbles on the chart
462
+ function updateBubbles(data) {
463
+ // Update scales domain for current data
464
+ const gdpExtent = d3.extent(data, d => d.gdpPerCapita);
465
+ const lifeExpectancyExtent = d3.extent(data, d => d.lifeExpectancy);
466
+
467
+ state.x.domain([Math.max(10, gdpExtent[0]), gdpExtent[1]]);
468
+ state.y.domain([Math.max(20, lifeExpectancyExtent[0] - 5), lifeExpectancyExtent[1] + 5]);
469
+
470
+ // Update axes
471
+ const width = dom.chartArea.node().clientWidth - config.margin.left - config.margin.right;
472
+ const height = dom.chartArea.node().clientHeight - config.margin.top - config.margin.bottom;
473
+
474
+ state.svg.select(".x-axis")
475
+ .transition()
476
+ .duration(config.animationDuration)
477
+ .call(d3.axisBottom(state.x).tickFormat(d3.format("$,.0f")));
478
+
479
+ state.svg.select(".y-axis")
480
+ .transition()
481
+ .duration(config.animationDuration)
482
+ .call(d3.axisLeft(state.y));
483
+
484
+ // Bind data to bubbles
485
+ const bubbles = state.svg.selectAll(".bubble")
486
+ .data(data, d => d.entity);
487
+
488
+ // Remove old bubbles
489
+ bubbles.exit()
490
+ .transition()
491
+ .duration(config.animationDuration)
492
+ .attr("r", 0)
493
+ .style("opacity", 0)
494
+ .remove();
495
+
496
+ // Update existing bubbles
497
+ bubbles.transition()
498
+ .duration(config.animationDuration)
499
+ .attr("cx", d => state.x(d.gdpPerCapita))
500
+ .attr("cy", d => state.y(d.lifeExpectancy))
501
+ .attr("r", d => state.size(d.population))
502
+ .style("fill", d => config.regions[d.region]?.color || "#ccc")
503
+ .style("opacity", 0.8);
504
+
505
+ // Add new bubbles
506
+ bubbles.enter()
507
+ .append("circle")
508
+ .attr("class", "bubble")
509
+ .attr("cx", d => state.x(d.gdpPerCapita))
510
+ .attr("cy", d => state.y(d.lifeExpectancy))
511
+ .attr("r", d => state.size(d.population))
512
+ .style("fill", d => config.regions[d.region]?.color || "#ccc")
513
+ .style("opacity", 0)
514
+ .on("mouseover", showTooltip)
515
+ .on("mouseout", hideTooltip)
516
+ .transition()
517
+ .duration(config.animationDuration)
518
+ .style("opacity", 0.8);
519
+ }
520
+
521
+ // Show tooltip
522
+ function showTooltip(event, d) {
523
+ const populationFormatted = d3.format(",")(Math.round(d.population / 1000000) * 1000000);
524
+
525
+ dom.tooltip.html(`
526
+ <strong>${d.entity}</strong><br>
527
+ Region: ${d.region}<br>
528
+ GDP: ${d3.format("$,.0f")(d.gdpPerCapita)}<br>
529
+ Life Expectancy: ${d.lifeExpectancy.toFixed(1)} years<br>
530
+ Population: ${populationFormatted}
531
+ `)
532
+ .style("left", (event.pageX) + "px")
533
+ .style("top", (event.pageY - 28) + "px")
534
+ .style("opacity", 1);
535
+ }
536
+
537
+ // Hide tooltip
538
+ function hideTooltip() {
539
+ dom.tooltip.style("opacity", 0);
540
+ }
541
+
542
+ // Handle window resize
543
+ function handleResize() {
544
+ const width = dom.chartArea.node().clientWidth - config.margin.left - config.margin.right;
545
+ const height = dom.chartArea.node().clientHeight - config.margin.top - config.margin.bottom;
546
+
547
+ state.svg.attr("width", width + config.margin.left + config.margin.right)
548
+ .attr("height", height + config.margin.top + config.margin.bottom);
549
+
550
+ state.x.range([0, width]);
551
+ state.y.range([height, 0]);
552
+
553
+ state.svg.select(".x-axis")
554
+ .attr("transform", `translate(0,${height})`)
555
+ .call(d3.axisBottom(state.x).tickFormat(d3.format("$,.0f")));
556
+
557
+ state.svg.select(".y-axis")
558
+ .call(d3.axisLeft(state.y));
559
+
560
+ state.svg.select(".axis-label")
561
+ .attr("x", width / 2)
562
+ .attr("y", height + config.margin.bottom - 10);
563
+
564
+ updateChart();
565
+ }
566
+
567
+ // Initialize the visualization when the window loads
568
+ window.addEventListener("load", init);
569
+ window.addEventListener("resize", handleResize);
570
+ </script>
571
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
572
+ </html>