361 lines
11 KiB
HTML
361 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Global Temperature Trends - Iteration 1</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
width: 100%;
|
|
background: white;
|
|
border-radius: 20px;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
padding: 40px;
|
|
}
|
|
|
|
h1 {
|
|
color: #2d3748;
|
|
font-size: 2.5rem;
|
|
margin-bottom: 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.subtitle {
|
|
color: #718096;
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
#viz-container {
|
|
width: 100%;
|
|
height: 500px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.attribution {
|
|
background: #f7fafc;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
border-left: 4px solid #667eea;
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.attribution h3 {
|
|
color: #2d3748;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.attribution p {
|
|
color: #4a5568;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.attribution a {
|
|
color: #667eea;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.attribution a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.tooltip {
|
|
position: absolute;
|
|
background: rgba(0,0,0,0.9);
|
|
color: white;
|
|
padding: 10px 15px;
|
|
border-radius: 5px;
|
|
font-size: 14px;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding: 20px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
#viz-container {
|
|
height: 400px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Global Temperature Trends</h1>
|
|
<p class="subtitle">Interactive time series visualization with D3.js scales and transitions</p>
|
|
|
|
<div id="viz-container"></div>
|
|
|
|
<div class="attribution">
|
|
<h3>About This Visualization</h3>
|
|
<p><strong>Iteration:</strong> 1 of Example Run</p>
|
|
<p><strong>Web Learning Source:</strong> <a href="https://observablehq.com/@d3/learn-d3-scales" target="_blank">D3 Learn - Scales</a></p>
|
|
<p><strong>Techniques Applied:</strong></p>
|
|
<ul>
|
|
<li>Linear scales for mapping data to pixels</li>
|
|
<li>Time scales for temporal data</li>
|
|
<li>Smooth transitions for visual feedback</li>
|
|
</ul>
|
|
<p style="margin-top: 10px;"><strong>Data Source:</strong> Simulated climate data (example)</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metadata for state tracking (hidden) -->
|
|
<div id="metadata" style="display:none;">
|
|
{
|
|
"iteration": 1,
|
|
"web_source": "https://observablehq.com/@d3/learn-d3-scales",
|
|
"techniques_learned": [
|
|
"Linear scales for mapping data to pixels",
|
|
"Time scales for temporal data",
|
|
"Smooth transitions for visual feedback"
|
|
],
|
|
"data_source": "simulated",
|
|
"created": "2025-03-10T14:30:00Z"
|
|
}
|
|
</div>
|
|
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
<script>
|
|
/**
|
|
* Global Temperature Trends Visualization - Iteration 1
|
|
*
|
|
* Web Source: https://observablehq.com/@d3/learn-d3-scales
|
|
* Techniques Learned:
|
|
* 1. Linear scales for mapping data to pixels
|
|
* 2. Time scales for temporal data
|
|
* 3. Smooth transitions for visual feedback
|
|
*
|
|
* Data Source: Simulated climate data
|
|
* Created: 2025-03-10T14:30:00Z
|
|
* Run ID: example
|
|
*/
|
|
|
|
// Generate simulated temperature data
|
|
function generateData() {
|
|
const data = [];
|
|
const startYear = 1880;
|
|
const endYear = 2024;
|
|
let baseTemp = 13.9; // baseline global average in Celsius
|
|
|
|
for (let year = startYear; year <= endYear; year++) {
|
|
// Simulate warming trend with noise
|
|
const yearsSinceStart = year - startYear;
|
|
const warming = yearsSinceStart * 0.01; // 0.01°C per year trend
|
|
const noise = (Math.random() - 0.5) * 0.3; // random variation
|
|
const temp = baseTemp + warming + noise;
|
|
|
|
data.push({
|
|
date: new Date(year, 0, 1),
|
|
temperature: temp
|
|
});
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// Create visualization
|
|
function createVisualization() {
|
|
const container = d3.select('#viz-container');
|
|
const containerNode = container.node();
|
|
const width = containerNode.clientWidth;
|
|
const height = containerNode.clientHeight;
|
|
const margin = { top: 40, right: 40, bottom: 60, left: 60 };
|
|
const innerWidth = width - margin.left - margin.right;
|
|
const innerHeight = height - margin.top - margin.bottom;
|
|
|
|
// Generate data
|
|
const data = generateData();
|
|
|
|
// Create SVG
|
|
const svg = container.append('svg')
|
|
.attr('width', width)
|
|
.attr('height', height);
|
|
|
|
const g = svg.append('g')
|
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
|
|
// TECHNIQUE 1: Time scales for temporal data
|
|
const xScale = d3.scaleTime()
|
|
.domain(d3.extent(data, d => d.date))
|
|
.range([0, innerWidth]);
|
|
|
|
// TECHNIQUE 1: Linear scales for mapping data to pixels
|
|
const yScale = d3.scaleLinear()
|
|
.domain([
|
|
d3.min(data, d => d.temperature) - 0.5,
|
|
d3.max(data, d => d.temperature) + 0.5
|
|
])
|
|
.range([innerHeight, 0]);
|
|
|
|
// Create axes
|
|
const xAxis = d3.axisBottom(xScale)
|
|
.ticks(10)
|
|
.tickFormat(d3.timeFormat('%Y'));
|
|
|
|
const yAxis = d3.axisLeft(yScale)
|
|
.ticks(8)
|
|
.tickFormat(d => d + '°C');
|
|
|
|
// Add axes
|
|
g.append('g')
|
|
.attr('class', 'x-axis')
|
|
.attr('transform', `translate(0,${innerHeight})`)
|
|
.call(xAxis)
|
|
.selectAll('text')
|
|
.style('font-size', '12px');
|
|
|
|
g.append('g')
|
|
.attr('class', 'y-axis')
|
|
.call(yAxis)
|
|
.selectAll('text')
|
|
.style('font-size', '12px');
|
|
|
|
// Add axis labels
|
|
g.append('text')
|
|
.attr('x', innerWidth / 2)
|
|
.attr('y', innerHeight + 45)
|
|
.attr('text-anchor', 'middle')
|
|
.style('font-size', '14px')
|
|
.style('fill', '#4a5568')
|
|
.text('Year');
|
|
|
|
g.append('text')
|
|
.attr('transform', 'rotate(-90)')
|
|
.attr('x', -innerHeight / 2)
|
|
.attr('y', -45)
|
|
.attr('text-anchor', 'middle')
|
|
.style('font-size', '14px')
|
|
.style('fill', '#4a5568')
|
|
.text('Temperature (°C)');
|
|
|
|
// Create line generator
|
|
const line = d3.line()
|
|
.x(d => xScale(d.date))
|
|
.y(d => yScale(d.temperature))
|
|
.curve(d3.curveMonotoneX);
|
|
|
|
// Add line path with transition (TECHNIQUE 3)
|
|
const path = g.append('path')
|
|
.datum(data)
|
|
.attr('fill', 'none')
|
|
.attr('stroke', '#667eea')
|
|
.attr('stroke-width', 3)
|
|
.attr('d', line);
|
|
|
|
// TECHNIQUE 3: Smooth transitions for visual feedback
|
|
const pathLength = path.node().getTotalLength();
|
|
path
|
|
.attr('stroke-dasharray', pathLength)
|
|
.attr('stroke-dashoffset', pathLength)
|
|
.transition()
|
|
.duration(2000)
|
|
.ease(d3.easeLinear)
|
|
.attr('stroke-dashoffset', 0);
|
|
|
|
// Add dots for data points
|
|
const dots = g.selectAll('.dot')
|
|
.data(data)
|
|
.enter()
|
|
.append('circle')
|
|
.attr('class', 'dot')
|
|
.attr('cx', d => xScale(d.date))
|
|
.attr('cy', d => yScale(d.temperature))
|
|
.attr('r', 0)
|
|
.attr('fill', '#764ba2')
|
|
.style('cursor', 'pointer');
|
|
|
|
// TECHNIQUE 3: Animate dots appearance
|
|
dots.transition()
|
|
.delay((d, i) => i * 10)
|
|
.duration(300)
|
|
.attr('r', 4);
|
|
|
|
// Add tooltip
|
|
const tooltip = d3.select('body').append('div')
|
|
.attr('class', 'tooltip');
|
|
|
|
// Add interactivity
|
|
dots
|
|
.on('mouseover', function(event, d) {
|
|
d3.select(this)
|
|
.transition()
|
|
.duration(200)
|
|
.attr('r', 8)
|
|
.attr('fill', '#f56565');
|
|
|
|
tooltip
|
|
.style('opacity', 1)
|
|
.html(`
|
|
<strong>Year:</strong> ${d.date.getFullYear()}<br>
|
|
<strong>Temperature:</strong> ${d.temperature.toFixed(2)}°C
|
|
`)
|
|
.style('left', (event.pageX + 10) + 'px')
|
|
.style('top', (event.pageY - 30) + 'px');
|
|
})
|
|
.on('mouseout', function() {
|
|
d3.select(this)
|
|
.transition()
|
|
.duration(200)
|
|
.attr('r', 4)
|
|
.attr('fill', '#764ba2');
|
|
|
|
tooltip.style('opacity', 0);
|
|
});
|
|
|
|
// Add reference line for baseline
|
|
const baselineTemp = data[0].temperature;
|
|
g.append('line')
|
|
.attr('x1', 0)
|
|
.attr('x2', innerWidth)
|
|
.attr('y1', yScale(baselineTemp))
|
|
.attr('y2', yScale(baselineTemp))
|
|
.attr('stroke', '#cbd5e0')
|
|
.attr('stroke-width', 2)
|
|
.attr('stroke-dasharray', '5,5');
|
|
|
|
g.append('text')
|
|
.attr('x', innerWidth - 5)
|
|
.attr('y', yScale(baselineTemp) - 10)
|
|
.attr('text-anchor', 'end')
|
|
.style('font-size', '12px')
|
|
.style('fill', '#718096')
|
|
.text('Baseline (1880)');
|
|
}
|
|
|
|
// Initialize on load
|
|
createVisualization();
|
|
|
|
// Handle resize
|
|
window.addEventListener('resize', () => {
|
|
d3.select('#viz-container').selectAll('*').remove();
|
|
createVisualization();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|