Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Interactive GDP vs Life Expectancy Scatterplot</title> | |
<script src="https://d3js.org/d3.v7.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
body { | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
margin: 0; | |
padding: 20px; | |
background-color: #f5f7fa; | |
color: #333; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
background-color: white; | |
border-radius: 10px; | |
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); | |
padding: 30px; | |
} | |
h1 { | |
text-align: center; | |
color: #2c3e50; | |
margin-bottom: 10px; | |
} | |
.subtitle { | |
text-align: center; | |
color: #7f8c8d; | |
margin-bottom: 30px; | |
font-size: 1.1em; | |
} | |
.chart-container { | |
position: relative; | |
margin-top: 20px; | |
} | |
.tooltip { | |
position: absolute; | |
padding: 10px; | |
background: rgba(255, 255, 255, 0.95); | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
pointer-events: none; | |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | |
font-size: 14px; | |
max-width: 200px; | |
z-index: 10; | |
opacity: 0; | |
transition: opacity 0.3s; | |
} | |
.tooltip h3 { | |
margin: 0 0 5px 0; | |
color: #2c3e50; | |
} | |
.tooltip p { | |
margin: 5px 0; | |
} | |
.legend { | |
margin-top: 20px; | |
display: flex; | |
justify-content: center; | |
flex-wrap: wrap; | |
} | |
.legend-item { | |
display: flex; | |
align-items: center; | |
margin: 0 15px 10px 15px; | |
} | |
.legend-color { | |
width: 15px; | |
height: 15px; | |
border-radius: 50%; | |
margin-right: 8px; | |
} | |
.controls { | |
display: flex; | |
justify-content: center; | |
margin-bottom: 20px; | |
gap: 15px; | |
flex-wrap: wrap; | |
} | |
.control-group { | |
display: flex; | |
align-items: center; | |
background: #f8f9fa; | |
padding: 8px 15px; | |
border-radius: 30px; | |
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); | |
} | |
.control-group label { | |
margin-right: 10px; | |
font-weight: 500; | |
color: #555; | |
} | |
select { | |
padding: 6px 12px; | |
border: 1px solid #ddd; | |
border-radius: 4px; | |
background-color: white; | |
cursor: pointer; | |
} | |
.reset-btn { | |
background-color: #3498db; | |
color: white; | |
border: none; | |
padding: 8px 16px; | |
border-radius: 4px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
gap: 5px; | |
transition: background-color 0.2s; | |
} | |
.reset-btn:hover { | |
background-color: #2980b9; | |
} | |
.axis-label { | |
font-size: 14px; | |
font-weight: 500; | |
fill: #555; | |
} | |
.grid line { | |
stroke: #e0e0e0; | |
stroke-dasharray: 2,2; | |
} | |
.grid .domain { | |
stroke: none; | |
} | |
.continent-filter { | |
display: flex; | |
gap: 10px; | |
flex-wrap: wrap; | |
justify-content: center; | |
margin-bottom: 20px; | |
} | |
.continent-btn { | |
padding: 6px 12px; | |
border-radius: 20px; | |
border: 1px solid #ddd; | |
background-color: white; | |
cursor: pointer; | |
transition: all 0.2s; | |
font-size: 14px; | |
} | |
.continent-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | |
} | |
.continent-btn.active { | |
border-color: transparent; | |
color: white; | |
font-weight: 500; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1><i class="fas fa-globe-americas"></i> Global Development Indicators</h1> | |
<p class="subtitle">Explore the relationship between GDP, Life Expectancy, and Population across countries</p> | |
<div class="controls"> | |
<div class="control-group"> | |
<label for="x-scale">X-Axis Scale:</label> | |
<select id="x-scale"> | |
<option value="linear">Linear</option> | |
<option value="log">Logarithmic</option> | |
</select> | |
</div> | |
<div class="control-group"> | |
<label for="y-scale">Y-Axis Scale:</label> | |
<select id="y-scale"> | |
<option value="linear">Linear</option> | |
<option value="log">Logarithmic</option> | |
</select> | |
</div> | |
<button class="reset-btn" id="reset-zoom"> | |
<i class="fas fa-search-minus"></i> Reset Zoom | |
</button> | |
</div> | |
<div class="continent-filter"> | |
<button class="continent-btn active" data-continent="all">All Continents</button> | |
<button class="continent-btn" data-continent="Africa" style="background-color: #FF6384;">Africa</button> | |
<button class="continent-btn" data-continent="Asia" style="background-color: #36A2EB;">Asia</button> | |
<button class="continent-btn" data-continent="Europe" style="background-color: #FFCE56;">Europe</button> | |
<button class="continent-btn" data-continent="North America" style="background-color: #4BC0C0;">North America</button> | |
<button class="continent-btn" data-continent="South America" style="background-color: #9966FF;">South America</button> | |
<button class="continent-btn" data-continent="Oceania" style="background-color: #FF9F40;">Oceania</button> | |
</div> | |
<div class="chart-container"> | |
<div id="scatterplot"></div> | |
<div class="tooltip" id="tooltip"></div> | |
</div> | |
<div class="legend"> | |
<div class="legend-item"> | |
<div class="legend-color" style="background-color: #FF6384;"></div> | |
<span>Africa</span> | |
</div> | |
<div class="legend-item"> | |
<div class="legend-color" style="background-color: #36A2EB;"></div> | |
<span>Asia</span> | |
</div> | |
<div class="legend-item"> | |
<div class="legend-color" style="background-color: #FFCE56;"></div> | |
<span>Europe</span> | |
</div> | |
<div class="legend-item"> | |
<div class="legend-color" style="background-color: #4BC0C0;"></div> | |
<span>North America</span> | |
</div> | |
<div class="legend-item"> | |
<div class="legend-color" style="background-color: #9966FF;"></div> | |
<span>South America</span> | |
</div> | |
<div class="legend-item"> | |
<div class="legend-color" style="background-color: #FF9F40;"></div> | |
<span>Oceania</span> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Generate dummy data | |
function generateDummyData() { | |
const continents = ['Africa', 'Asia', 'Europe', 'North America', 'South America', 'Oceania']; | |
const continentColors = { | |
'Africa': '#FF6384', | |
'Asia': '#36A2EB', | |
'Europe': '#FFCE56', | |
'North America': '#4BC0C0', | |
'South America': '#9966FF', | |
'Oceania': '#FF9F40' | |
}; | |
const countryNames = { | |
'Africa': ['Nigeria', 'Egypt', 'South Africa', 'Kenya', 'Ethiopia', 'Ghana', 'Morocco', 'Tanzania'], | |
'Asia': ['China', 'India', 'Japan', 'South Korea', 'Indonesia', 'Thailand', 'Vietnam', 'Philippines'], | |
'Europe': ['Germany', 'France', 'UK', 'Italy', 'Spain', 'Poland', 'Netherlands', 'Sweden'], | |
'North America': ['USA', 'Canada', 'Mexico', 'Cuba', 'Guatemala', 'Panama', 'Costa Rica', 'Jamaica'], | |
'South America': ['Brazil', 'Argentina', 'Colombia', 'Chile', 'Peru', 'Venezuela', 'Ecuador', 'Bolivia'], | |
'Oceania': ['Australia', 'New Zealand', 'Fiji', 'Papua New Guinea', 'Samoa', 'Tonga', 'Vanuatu'] | |
}; | |
let data = []; | |
continents.forEach(continent => { | |
countryNames[continent].forEach(country => { | |
// Generate random but somewhat realistic values | |
const gdpPerCapita = continent === 'Africa' ? | |
Math.random() * 5000 + 500 : | |
continent === 'Asia' ? | |
Math.random() * 15000 + 3000 : | |
continent === 'Europe' ? | |
Math.random() * 30000 + 20000 : | |
continent === 'North America' ? | |
Math.random() * 40000 + 10000 : | |
continent === 'South America' ? | |
Math.random() * 15000 + 5000 : | |
Math.random() * 30000 + 10000; // Oceania | |
const lifeExpectancy = continent === 'Africa' ? | |
Math.random() * 10 + 55 : | |
continent === 'Asia' ? | |
Math.random() * 10 + 65 : | |
continent === 'Europe' ? | |
Math.random() * 5 + 75 : | |
continent === 'North America' ? | |
Math.random() * 5 + 75 : | |
continent === 'South America' ? | |
Math.random() * 5 + 70 : | |
Math.random() * 5 + 75; // Oceania | |
const population = continent === 'Africa' ? | |
Math.random() * 100000000 + 10000000 : | |
continent === 'Asia' ? | |
Math.random() * 1000000000 + 100000000 : | |
continent === 'Europe' ? | |
Math.random() * 50000000 + 5000000 : | |
continent === 'North America' ? | |
Math.random() * 200000000 + 10000000 : | |
continent === 'South America' ? | |
Math.random() * 100000000 + 10000000 : | |
Math.random() * 20000000 + 1000000; // Oceania | |
data.push({ | |
country: country, | |
continent: continent, | |
gdpPerCapita: gdpPerCapita, | |
lifeExpectancy: lifeExpectancy, | |
population: population, | |
color: continentColors[continent] | |
}); | |
}); | |
}); | |
return data; | |
} | |
// Main visualization function | |
function createScatterplot(data) { | |
// Set dimensions and margins | |
const margin = {top: 60, right: 80, bottom: 80, left: 80}; | |
const width = 900 - margin.left - margin.right; | |
const height = 600 - margin.top - margin.bottom; | |
// Create SVG | |
const svg = d3.select("#scatterplot") | |
.html("") // Clear previous content | |
.append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", `translate(${margin.left},${margin.top})`); | |
// Add clip path to prevent circles from appearing outside the chart area | |
svg.append("defs").append("clipPath") | |
.attr("id", "clip") | |
.append("rect") | |
.attr("width", width) | |
.attr("height", height); | |
// Create scales (initial linear scales) | |
let xScale = d3.scaleLinear() | |
.domain([d3.min(data, d => d.gdpPerCapita) * 0.9, d3.max(data, d => d.gdpPerCapita) * 1.1]) | |
.range([0, width]) | |
.nice(); | |
let yScale = d3.scaleLinear() | |
.domain([d3.min(data, d => d.lifeExpectancy) * 0.9, d3.max(data, d => d.lifeExpectancy) * 1.1]) | |
.range([height, 0]) | |
.nice(); | |
// Create size scale for bubbles | |
const sizeScale = d3.scaleSqrt() | |
.domain([0, d3.max(data, d => d.population)]) | |
.range([3, 30]); | |
// Add grid lines | |
svg.append("g") | |
.attr("class", "grid") | |
.call(d3.axisLeft(yScale) | |
.tickSize(-width) | |
.tickFormat("") | |
); | |
svg.append("g") | |
.attr("class", "grid") | |
.call(d3.axisBottom(xScale) | |
.tickSize(-height) | |
.tickFormat("") | |
); | |
// Add axes | |
const xAxis = svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", `translate(0,${height})`) | |
.call(d3.axisBottom(xScale)); | |
const yAxis = svg.append("g") | |
.attr("class", "y axis") | |
.call(d3.axisLeft(yScale)); | |
// Add axis labels | |
svg.append("text") | |
.attr("class", "axis-label") | |
.attr("x", width / 2) | |
.attr("y", height + margin.bottom - 20) | |
.attr("text-anchor", "middle") | |
.text("GDP per Capita (USD)"); | |
svg.append("text") | |
.attr("class", "axis-label") | |
.attr("transform", "rotate(-90)") | |
.attr("x", -height / 2) | |
.attr("y", -margin.left + 20) | |
.attr("text-anchor", "middle") | |
.text("Life Expectancy (years)"); | |
// Add chart title | |
svg.append("text") | |
.attr("x", width / 2) | |
.attr("y", -20) | |
.attr("text-anchor", "middle") | |
.style("font-size", "18px") | |
.style("font-weight", "bold") | |
.text("GDP vs Life Expectancy by Country"); | |
// Create zoom behavior | |
const zoom = d3.zoom() | |
.scaleExtent([0.5, 8]) | |
.on("zoom", zoomed); | |
// Apply zoom to the SVG | |
d3.select("#scatterplot svg") | |
.call(zoom) | |
.on("dblclick.zoom", null); // Disable double-click zoom | |
// Store original scales for reset | |
const originalXScale = xScale.copy(); | |
const originalYScale = yScale.copy(); | |
// Zoom function | |
function zoomed(event) { | |
const newXScale = event.transform.rescaleX(originalXScale); | |
const newYScale = event.transform.rescaleY(originalYScale); | |
xScale = newXScale; | |
yScale = newYScale; | |
updateChart(); | |
} | |
// Reset zoom button | |
document.getElementById("reset-zoom").addEventListener("click", function() { | |
d3.select("#scatterplot svg") | |
.transition() | |
.duration(750) | |
.call(zoom.transform, d3.zoomIdentity); | |
xScale = originalXScale; | |
yScale = originalYScale; | |
updateChart(); | |
}); | |
// Tooltip | |
const tooltip = d3.select("#tooltip"); | |
// Create circles for each data point | |
let circles = svg.append("g") | |
.attr("clip-path", "url(#clip)") | |
.selectAll("circle") | |
.data(data) | |
.enter() | |
.append("circle") | |
.attr("cx", d => xScale(d.gdpPerCapita)) | |
.attr("cy", d => yScale(d.lifeExpectancy)) | |
.attr("r", d => sizeScale(d.population)) | |
.attr("fill", d => d.color) | |
.attr("opacity", 0.7) | |
.attr("stroke", "#fff") | |
.attr("stroke-width", 1) | |
.on("mouseover", function(event, d) { | |
d3.select(this) | |
.attr("opacity", 1) | |
.attr("stroke-width", 2); | |
tooltip.transition() | |
.duration(200) | |
.style("opacity", 0.9); | |
tooltip.html(` | |
<h3>${d.country}</h3> | |
<p><strong>Continent:</strong> ${d.continent}</p> | |
<p><strong>GDP per capita:</strong> $${d3.format(",.0f")(d.gdpPerCapita)}</p> | |
<p><strong>Life expectancy:</strong> ${d3.format(".1f")(d.lifeExpectancy)} years</p> | |
<p><strong>Population:</strong> ${d3.format(",.0f")(d.population)}</p> | |
`) | |
.style("left", (event.pageX + 10) + "px") | |
.style("top", (event.pageY - 10) + "px"); | |
}) | |
.on("mouseout", function() { | |
d3.select(this) | |
.attr("opacity", 0.7) | |
.attr("stroke-width", 1); | |
tooltip.transition() | |
.duration(500) | |
.style("opacity", 0); | |
}); | |
// Update chart function | |
function updateChart() { | |
// Update axes | |
svg.select(".x.axis") | |
.transition() | |
.duration(750) | |
.call(d3.axisBottom(xScale)); | |
svg.select(".y.axis") | |
.transition() | |
.duration(750) | |
.call(d3.axisLeft(yScale)); | |
// Update grid lines | |
svg.select(".grid.x") | |
.transition() | |
.duration(750) | |
.call(d3.axisBottom(xScale) | |
.tickSize(-height) | |
.tickFormat("") | |
); | |
svg.select(".grid.y") | |
.transition() | |
.duration(750) | |
.call(d3.axisLeft(yScale) | |
.tickSize(-width) | |
.tickFormat("") | |
); | |
// Update circles | |
circles | |
.transition() | |
.duration(750) | |
.attr("cx", d => xScale(d.gdpPerCapita)) | |
.attr("cy", d => yScale(d.lifeExpectancy)); | |
} | |
// Scale type toggle | |
document.getElementById("x-scale").addEventListener("change", function() { | |
const scaleType = this.value; | |
const currentDomain = xScale.domain(); | |
if (scaleType === "log") { | |
xScale = d3.scaleLog() | |
.domain([Math.max(1, currentDomain[0]), currentDomain[1]]) | |
.range([0, width]) | |
.nice(); | |
} else { | |
xScale = d3.scaleLinear() | |
.domain(currentDomain) | |
.range([0, width]) | |
.nice(); | |
} | |
updateChart(); | |
}); | |
document.getElementById("y-scale").addEventListener("change", function() { | |
const scaleType = this.value; | |
const currentDomain = yScale.domain(); | |
if (scaleType === "log") { | |
yScale = d3.scaleLog() | |
.domain([Math.max(1, currentDomain[0]), currentDomain[1]]) | |
.range([height, 0]) | |
.nice(); | |
} else { | |
yScale = d3.scaleLinear() | |
.domain(currentDomain) | |
.range([height, 0]) | |
.nice(); | |
} | |
updateChart(); | |
}); | |
// Continent filter buttons | |
document.querySelectorAll(".continent-btn").forEach(btn => { | |
btn.addEventListener("click", function() { | |
const continent = this.dataset.continent; | |
// Update button states | |
document.querySelectorAll(".continent-btn").forEach(b => { | |
b.classList.remove("active"); | |
}); | |
this.classList.add("active"); | |
// Filter data | |
let filteredData = data; | |
if (continent !== "all") { | |
filteredData = data.filter(d => d.continent === continent); | |
} | |
// Update circles | |
circles = svg.selectAll("circle") | |
.data(filteredData, d => d.country); | |
circles.exit() | |
.transition() | |
.duration(500) | |
.attr("r", 0) | |
.remove(); | |
circles.enter() | |
.append("circle") | |
.attr("cx", d => xScale(d.gdpPerCapita)) | |
.attr("cy", d => yScale(d.lifeExpectancy)) | |
.attr("r", 0) | |
.attr("fill", d => d.color) | |
.attr("opacity", 0.7) | |
.attr("stroke", "#fff") | |
.attr("stroke-width", 1) | |
.on("mouseover", function(event, d) { | |
d3.select(this) | |
.attr("opacity", 1) | |
.attr("stroke-width", 2); | |
tooltip.transition() | |
.duration(200) | |
.style("opacity", 0.9); | |
tooltip.html(` | |
<h3>${d.country}</h3> | |
<p><strong>Continent:</strong> ${d.continent}</p> | |
<p><strong>GDP per capita:</strong> $${d3.format(",.0f")(d.gdpPerCapita)}</p> | |
<p><strong>Life expectancy:</strong> ${d3.format(".1f")(d.lifeExpectancy)} years</p> | |
<p><strong>Population:</strong> ${d3.format(",.0f")(d.population)}</p> | |
`) | |
.style("left", (event.pageX + 10) + "px") | |
.style("top", (event.pageY - 10) + "px"); | |
}) | |
.on("mouseout", function() { | |
d3.select(this) | |
.attr("opacity", 0.7) | |
.attr("stroke-width", 1); | |
tooltip.transition() | |
.duration(500) | |
.style("opacity", 0); | |
}) | |
.transition() | |
.duration(500) | |
.attr("r", d => sizeScale(d.population)); | |
circles | |
.transition() | |
.duration(500) | |
.attr("r", d => sizeScale(d.population)); | |
}); | |
}); | |
} | |
// Initialize the chart | |
document.addEventListener("DOMContentLoaded", function() { | |
const data = generateDummyData(); | |
createScatterplot(data); | |
// For future CSV loading | |
window.loadCSVData = function(csvData) { | |
// Process CSV data here | |
// Format should be: country,continent,gdpPerCapita,lifeExpectancy,population | |
createScatterplot(csvData); | |
}; | |
}); | |
</script> | |
<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> | |
</html> |