infinite-agents-public/d3_test/d3_viz_2.html

749 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3 Visualization 2: Multi-Scale Temperature Analysis</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
min-height: 100vh;
}
#visualization-container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
overflow: hidden;
}
h1 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
font-size: 2.2em;
letter-spacing: 1px;
}
#controls {
padding: 20px 40px;
background: #f8f9fa;
border-bottom: 2px solid #e9ecef;
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
}
.control-group {
display: flex;
align-items: center;
gap: 10px;
}
.control-group label {
font-weight: 600;
color: #495057;
}
.control-group select {
padding: 8px 12px;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.control-group select:hover {
border-color: #667eea;
}
#viz {
padding: 40px;
min-height: 600px;
display: flex;
flex-direction: column;
gap: 40px;
}
.chart-section {
background: #f8f9fa;
padding: 30px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.chart-title {
font-size: 1.4em;
color: #495057;
margin-bottom: 10px;
font-weight: 600;
}
.chart-description {
color: #6c757d;
margin-bottom: 20px;
font-size: 0.95em;
}
#insights {
padding: 30px 40px;
background: #f8f9fa;
border-top: 2px solid #e9ecef;
}
#insights h2 {
color: #495057;
margin-bottom: 15px;
font-size: 1.5em;
}
.insight-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.insight-card {
background: white;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.insight-card h3 {
color: #667eea;
margin-bottom: 10px;
font-size: 1.1em;
}
.insight-card p {
color: #6c757d;
line-height: 1.6;
font-size: 0.9em;
}
.scale-legend {
margin-top: 15px;
padding: 15px;
background: white;
border-radius: 6px;
font-size: 0.9em;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
margin: 8px 0;
}
.legend-color {
width: 30px;
height: 20px;
border-radius: 4px;
border: 1px solid #dee2e6;
}
footer {
margin-top: 0;
padding: 30px 40px;
background: #343a40;
color: white;
}
footer h3 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.6em;
}
footer ul {
list-style: none;
}
footer li {
margin: 12px 0;
padding-left: 20px;
position: relative;
line-height: 1.6;
}
footer li:before {
content: "▸";
position: absolute;
left: 0;
color: #667eea;
}
footer strong {
color: #a5b4fc;
}
.axis {
font-size: 12px;
}
.axis path,
.axis line {
stroke: #495057;
}
.axis text {
fill: #495057;
}
.bar {
transition: opacity 0.3s;
}
.bar:hover {
opacity: 0.7;
}
.data-point {
transition: all 0.3s;
}
.data-point:hover {
r: 8;
stroke-width: 3;
}
</style>
</head>
<body>
<div id="visualization-container">
<h1>Multi-Scale Temperature Analysis Dashboard</h1>
<div id="controls">
<div class="control-group">
<label for="scaleType">Time Scale Type:</label>
<select id="scaleType">
<option value="linear">Linear Scale</option>
<option value="time" selected>Time Scale</option>
</select>
</div>
<div class="control-group">
<label for="colorScheme">Color Scheme:</label>
<select id="colorScheme">
<option value="sequential" selected>Sequential (Cool to Hot)</option>
<option value="diverging">Diverging (Below/Above Average)</option>
</select>
</div>
</div>
<div id="viz">
<div class="chart-section">
<div class="chart-title">1. Time Scale + Sequential Color Scale</div>
<div class="chart-description">
Using <strong>scaleTime()</strong> for temporal data mapping and <strong>scaleSequential()</strong> for temperature color encoding
</div>
<svg id="timeChart"></svg>
</div>
<div class="chart-section">
<div class="chart-title">2. Band Scale + Linear Scale + Threshold Color Scale</div>
<div class="chart-description">
Using <strong>scaleBand()</strong> for categorical positioning, <strong>scaleLinear()</strong> for height, and <strong>scaleThreshold()</strong> for discrete color ranges
</div>
<svg id="barChart"></svg>
</div>
<div class="chart-section">
<div class="chart-title">3. Point Scale + Log Scale</div>
<div class="chart-description">
Using <strong>scalePoint()</strong> for precise categorical positioning and <strong>scaleLog()</strong> for exponential temperature variance display
</div>
<svg id="pointChart"></svg>
</div>
</div>
<div id="insights">
<h2>Scale Mappings Explained</h2>
<div class="insight-grid">
<div class="insight-card">
<h3>Time Scale (scaleTime)</h3>
<p>Maps JavaScript Date objects to pixel positions. Domain: [earliest date, latest date] → Range: [0, chartWidth]. Automatically handles date arithmetic and tick formatting.</p>
</div>
<div class="insight-card">
<h3>Sequential Color Scale</h3>
<p>Maps continuous temperature values to smooth color gradients. Domain: [minTemp, maxTemp] → Range: color interpolation. Perfect for showing intensity variations.</p>
</div>
<div class="insight-card">
<h3>Band Scale (scaleBand)</h3>
<p>Maps categorical city names to positions with automatic padding. Divides available space evenly with configurable inner/outer padding for bar charts.</p>
</div>
<div class="insight-card">
<h3>Linear Scale (scaleLinear)</h3>
<p>Standard numeric mapping. Domain: [0, maxTemp] → Range: [chartHeight, 0]. Inverted for SVG coordinate system where y=0 is at top.</p>
</div>
<div class="insight-card">
<h3>Threshold Scale</h3>
<p>Maps continuous input to discrete output using breakpoints. Temperature thresholds: [10°, 20°, 30°] create 4 color categories for "cold", "cool", "warm", "hot".</p>
</div>
<div class="insight-card">
<h3>Log Scale (scaleLog)</h3>
<p>Maps wide-range data using logarithmic transformation. Domain: [1, maxVariance] → Range: [0, height]. Compresses large values, expands small ones for better visibility.</p>
</div>
</div>
</div>
</div>
<script>
// Embedded realistic temperature data for multiple cities over time
const rawData = [
{ date: new Date(2024, 0, 1), city: 'New York', temp: 2, variance: 5 },
{ date: new Date(2024, 1, 1), city: 'New York', temp: 4, variance: 8 },
{ date: new Date(2024, 2, 1), city: 'New York', temp: 10, variance: 12 },
{ date: new Date(2024, 3, 1), city: 'New York', temp: 16, variance: 15 },
{ date: new Date(2024, 4, 1), city: 'New York', temp: 22, variance: 10 },
{ date: new Date(2024, 5, 1), city: 'New York', temp: 27, variance: 8 },
{ date: new Date(2024, 6, 1), city: 'New York', temp: 30, variance: 6 },
{ date: new Date(2024, 7, 1), city: 'New York', temp: 28, variance: 7 },
{ date: new Date(2024, 8, 1), city: 'New York', temp: 23, variance: 11 },
{ date: new Date(2024, 9, 1), city: 'New York', temp: 16, variance: 14 },
{ date: new Date(2024, 10, 1), city: 'New York', temp: 9, variance: 18 },
{ date: new Date(2024, 11, 1), city: 'New York', temp: 3, variance: 9 },
{ date: new Date(2024, 0, 1), city: 'Los Angeles', temp: 15, variance: 3 },
{ date: new Date(2024, 1, 1), city: 'Los Angeles', temp: 16, variance: 4 },
{ date: new Date(2024, 2, 1), city: 'Los Angeles', temp: 17, variance: 5 },
{ date: new Date(2024, 3, 1), city: 'Los Angeles', temp: 19, variance: 4 },
{ date: new Date(2024, 4, 1), city: 'Los Angeles', temp: 21, variance: 3 },
{ date: new Date(2024, 5, 1), city: 'Los Angeles', temp: 24, variance: 2 },
{ date: new Date(2024, 6, 1), city: 'Los Angeles', temp: 26, variance: 2 },
{ date: new Date(2024, 7, 1), city: 'Los Angeles', temp: 27, variance: 2 },
{ date: new Date(2024, 8, 1), city: 'Los Angeles', temp: 25, variance: 3 },
{ date: new Date(2024, 9, 1), city: 'Los Angeles', temp: 22, variance: 4 },
{ date: new Date(2024, 10, 1), city: 'Los Angeles', temp: 18, variance: 5 },
{ date: new Date(2024, 11, 1), city: 'Los Angeles', temp: 15, variance: 4 },
{ date: new Date(2024, 0, 1), city: 'Chicago', temp: -5, variance: 12 },
{ date: new Date(2024, 1, 1), city: 'Chicago', temp: -2, variance: 15 },
{ date: new Date(2024, 2, 1), city: 'Chicago', temp: 6, variance: 18 },
{ date: new Date(2024, 3, 1), city: 'Chicago', temp: 13, variance: 16 },
{ date: new Date(2024, 4, 1), city: 'Chicago', temp: 20, variance: 12 },
{ date: new Date(2024, 5, 1), city: 'Chicago', temp: 26, variance: 9 },
{ date: new Date(2024, 6, 1), city: 'Chicago', temp: 29, variance: 7 },
{ date: new Date(2024, 7, 1), city: 'Chicago', temp: 27, variance: 8 },
{ date: new Date(2024, 8, 1), city: 'Chicago', temp: 21, variance: 11 },
{ date: new Date(2024, 9, 1), city: 'Chicago', temp: 14, variance: 15 },
{ date: new Date(2024, 10, 1), city: 'Chicago', temp: 5, variance: 20 },
{ date: new Date(2024, 11, 1), city: 'Chicago', temp: -3, variance: 14 },
{ date: new Date(2024, 0, 1), city: 'Miami', temp: 20, variance: 4 },
{ date: new Date(2024, 1, 1), city: 'Miami', temp: 21, variance: 5 },
{ date: new Date(2024, 2, 1), city: 'Miami', temp: 23, variance: 6 },
{ date: new Date(2024, 3, 1), city: 'Miami', temp: 25, variance: 5 },
{ date: new Date(2024, 4, 1), city: 'Miami', temp: 28, variance: 4 },
{ date: new Date(2024, 5, 1), city: 'Miami', temp: 30, variance: 3 },
{ date: new Date(2024, 6, 1), city: 'Miami', temp: 32, variance: 3 },
{ date: new Date(2024, 7, 1), city: 'Miami', temp: 32, variance: 3 },
{ date: new Date(2024, 8, 1), city: 'Miami', temp: 30, variance: 4 },
{ date: new Date(2024, 9, 1), city: 'Miami', temp: 27, variance: 5 },
{ date: new Date(2024, 10, 1), city: 'Miami', temp: 24, variance: 5 },
{ date: new Date(2024, 11, 1), city: 'Miami', temp: 21, variance: 4 },
{ date: new Date(2024, 0, 1), city: 'Seattle', temp: 6, variance: 8 },
{ date: new Date(2024, 1, 1), city: 'Seattle', temp: 8, variance: 9 },
{ date: new Date(2024, 2, 1), city: 'Seattle', temp: 11, variance: 10 },
{ date: new Date(2024, 3, 1), city: 'Seattle', temp: 13, variance: 11 },
{ date: new Date(2024, 4, 1), city: 'Seattle', temp: 16, variance: 9 },
{ date: new Date(2024, 5, 1), city: 'Seattle', temp: 19, variance: 7 },
{ date: new Date(2024, 6, 1), city: 'Seattle', temp: 22, variance: 6 },
{ date: new Date(2024, 7, 1), city: 'Seattle', temp: 23, variance: 6 },
{ date: new Date(2024, 8, 1), city: 'Seattle', temp: 19, variance: 8 },
{ date: new Date(2024, 9, 1), city: 'Seattle', temp: 14, variance: 10 },
{ date: new Date(2024, 10, 1), city: 'Seattle', temp: 9, variance: 11 },
{ date: new Date(2024, 11, 1), city: 'Seattle', temp: 6, variance: 9 }
];
// Calculate average temperatures by city for bar chart
const cityAverages = d3.rollup(
rawData,
v => ({
avgTemp: d3.mean(v, d => d.temp),
maxVariance: d3.max(v, d => d.variance)
}),
d => d.city
);
const barData = Array.from(cityAverages, ([city, stats]) => ({
city,
avgTemp: stats.avgTemp,
maxVariance: stats.maxVariance
}));
// CHART 1: Time Scale + Sequential Color Scale
function renderTimeChart() {
const margin = { top: 20, right: 30, bottom: 50, left: 60 };
const width = 1200 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// Clear existing chart
d3.select('#timeChart').selectAll('*').remove();
const svg = d3.select('#timeChart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// SCALE 1: Time Scale - maps dates to x positions
const xScale = d3.scaleTime()
.domain(d3.extent(rawData, d => d.date))
.range([0, width]);
// SCALE 2: Linear Scale - maps temperature to y positions
const yScale = d3.scaleLinear()
.domain([
d3.min(rawData, d => d.temp) - 5,
d3.max(rawData, d => d.temp) + 5
])
.range([height, 0]);
// SCALE 3: Sequential Color Scale - maps temperature to colors
const colorScale = d3.scaleSequential()
.domain([d3.min(rawData, d => d.temp), d3.max(rawData, d => d.temp)])
.interpolator(d3.interpolateRdYlBu)
.unknown('#ccc');
// Group data by city for line rendering
const cities = d3.group(rawData, d => d.city);
// Draw lines for each city
const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.temp))
.curve(d3.curveMonotoneX);
cities.forEach((values, city) => {
svg.append('path')
.datum(values)
.attr('fill', 'none')
.attr('stroke', '#667eea')
.attr('stroke-width', 2)
.attr('opacity', 0.3)
.attr('d', line);
});
// Draw data points with color scale
svg.selectAll('.data-point')
.data(rawData)
.join('circle')
.attr('class', 'data-point')
.attr('cx', d => xScale(d.date))
.attr('cy', d => yScale(d.temp))
.attr('r', 5)
.attr('fill', d => colorScale(d.temp))
.attr('stroke', 'white')
.attr('stroke-width', 2)
.append('title')
.text(d => `${d.city}\n${d.date.toLocaleDateString()}\n${d.temp}°C`);
// Add X axis using time scale
svg.append('g')
.attr('class', 'axis')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(xScale).ticks(12))
.selectAll('text')
.attr('transform', 'rotate(-45)')
.style('text-anchor', 'end');
// Add Y axis
svg.append('g')
.attr('class', 'axis')
.call(d3.axisLeft(yScale))
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -45)
.attr('x', -height / 2)
.attr('fill', '#495057')
.style('text-anchor', 'middle')
.text('Temperature (°C)');
// Color legend
const legendWidth = 300;
const legendHeight = 20;
const legendScale = d3.scaleLinear()
.domain(colorScale.domain())
.range([0, legendWidth]);
const legendGroup = svg.append('g')
.attr('transform', `translate(${width - legendWidth - 10}, ${height + 40})`);
// Gradient for legend
const defs = svg.append('defs');
const gradient = defs.append('linearGradient')
.attr('id', 'temp-gradient');
gradient.selectAll('stop')
.data(d3.range(0, 1.01, 0.1))
.join('stop')
.attr('offset', d => `${d * 100}%`)
.attr('stop-color', d => colorScale(
colorScale.domain()[0] + d * (colorScale.domain()[1] - colorScale.domain()[0])
));
legendGroup.append('rect')
.attr('width', legendWidth)
.attr('height', legendHeight)
.style('fill', 'url(#temp-gradient)')
.attr('stroke', '#495057');
legendGroup.append('g')
.attr('transform', `translate(0, ${legendHeight})`)
.call(d3.axisBottom(legendScale).ticks(5).tickFormat(d => `${d}°C`));
}
// CHART 2: Band Scale + Linear Scale + Threshold Color Scale
function renderBarChart() {
const margin = { top: 20, right: 30, bottom: 60, left: 60 };
const width = 1200 - margin.left - margin.right;
const height = 350 - margin.top - margin.bottom;
d3.select('#barChart').selectAll('*').remove();
const svg = d3.select('#barChart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// SCALE 4: Band Scale - maps cities to x positions with automatic spacing
const xScale = d3.scaleBand()
.domain(barData.map(d => d.city))
.range([0, width])
.padding(0.2); // Automatic padding between bars
// SCALE 5: Linear Scale - maps temperature to bar height
const yScale = d3.scaleLinear()
.domain([0, d3.max(barData, d => d.avgTemp) + 5])
.range([height, 0]);
// SCALE 6: Threshold Scale - discrete color categories
const colorScale = d3.scaleThreshold()
.domain([10, 20, 30]) // Temperature breakpoints
.range(['#3b82f6', '#10b981', '#f59e0b', '#ef4444']); // cold, cool, warm, hot
// Draw bars
svg.selectAll('.bar')
.data(barData)
.join('rect')
.attr('class', 'bar')
.attr('x', d => xScale(d.city))
.attr('y', d => yScale(d.avgTemp))
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.avgTemp))
.attr('fill', d => colorScale(d.avgTemp))
.attr('stroke', '#495057')
.attr('stroke-width', 2)
.append('title')
.text(d => `${d.city}: ${d.avgTemp.toFixed(1)}°C`);
// Add value labels on bars
svg.selectAll('.bar-label')
.data(barData)
.join('text')
.attr('class', 'bar-label')
.attr('x', d => xScale(d.city) + xScale.bandwidth() / 2)
.attr('y', d => yScale(d.avgTemp) - 5)
.attr('text-anchor', 'middle')
.attr('fill', '#495057')
.attr('font-weight', 'bold')
.attr('font-size', '14px')
.text(d => `${d.avgTemp.toFixed(1)}°C`);
// Add axes
svg.append('g')
.attr('class', 'axis')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(xScale))
.selectAll('text')
.attr('transform', 'rotate(-15)')
.style('text-anchor', 'end');
svg.append('g')
.attr('class', 'axis')
.call(d3.axisLeft(yScale))
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -45)
.attr('x', -height / 2)
.attr('fill', '#495057')
.style('text-anchor', 'middle')
.text('Average Temperature (°C)');
// Threshold legend
const thresholds = [
{ range: '< 10°C', color: '#3b82f6', label: 'Cold' },
{ range: '10-20°C', color: '#10b981', label: 'Cool' },
{ range: '20-30°C', color: '#f59e0b', label: 'Warm' },
{ range: '> 30°C', color: '#ef4444', label: 'Hot' }
];
const legend = svg.append('g')
.attr('transform', `translate(${width - 150}, 20)`);
thresholds.forEach((t, i) => {
const item = legend.append('g')
.attr('transform', `translate(0, ${i * 25})`);
item.append('rect')
.attr('width', 20)
.attr('height', 20)
.attr('fill', t.color)
.attr('stroke', '#495057');
item.append('text')
.attr('x', 28)
.attr('y', 15)
.attr('font-size', '12px')
.attr('fill', '#495057')
.text(`${t.label}: ${t.range}`);
});
}
// CHART 3: Point Scale + Log Scale
function renderPointChart() {
const margin = { top: 20, right: 30, bottom: 60, left: 80 };
const width = 1200 - margin.left - margin.right;
const height = 350 - margin.top - margin.bottom;
d3.select('#pointChart').selectAll('*').remove();
const svg = d3.select('#pointChart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// SCALE 7: Point Scale - precise categorical positioning
const xScale = d3.scalePoint()
.domain(barData.map(d => d.city))
.range([0, width])
.padding(0.5);
// SCALE 8: Log Scale - logarithmic mapping for variance
const yScale = d3.scaleLog()
.domain([1, d3.max(barData, d => d.maxVariance) + 5])
.range([height, 0])
.clamp(true);
// Size scale for visual emphasis
const sizeScale = d3.scaleLinear()
.domain([0, d3.max(barData, d => d.avgTemp)])
.range([5, 20]);
// Draw connecting line
svg.append('path')
.datum(barData)
.attr('fill', 'none')
.attr('stroke', '#667eea')
.attr('stroke-width', 3)
.attr('opacity', 0.3)
.attr('d', d3.line()
.x(d => xScale(d.city))
.y(d => yScale(d.maxVariance))
);
// Draw points
svg.selectAll('.variance-point')
.data(barData)
.join('circle')
.attr('class', 'variance-point data-point')
.attr('cx', d => xScale(d.city))
.attr('cy', d => yScale(d.maxVariance))
.attr('r', d => sizeScale(d.avgTemp))
.attr('fill', '#764ba2')
.attr('stroke', '#fff')
.attr('stroke-width', 2)
.append('title')
.text(d => `${d.city}\nVariance: ${d.maxVariance}°C\nAvg Temp: ${d.avgTemp.toFixed(1)}°C`);
// Add axes
svg.append('g')
.attr('class', 'axis')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(xScale))
.selectAll('text')
.attr('transform', 'rotate(-15)')
.style('text-anchor', 'end');
svg.append('g')
.attr('class', 'axis')
.call(d3.axisLeft(yScale).ticks(5, '.0f'))
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -60)
.attr('x', -height / 2)
.attr('fill', '#495057')
.style('text-anchor', 'middle')
.text('Temperature Variance (°C) - Log Scale');
// Add note about log scale
svg.append('text')
.attr('x', width / 2)
.attr('y', -5)
.attr('text-anchor', 'middle')
.attr('fill', '#6c757d')
.attr('font-size', '13px')
.attr('font-style', 'italic')
.text('Point size represents average temperature | Log scale compresses high variance values');
}
// Initial render
renderTimeChart();
renderBarChart();
renderPointChart();
// Event listeners for controls
document.getElementById('scaleType').addEventListener('change', renderTimeChart);
document.getElementById('colorScheme').addEventListener('change', renderTimeChart);
</script>
<footer>
<h3>Iteration 2 - Web Learning Applied</h3>
<ul>
<li><strong>Web Source:</strong> https://d3js.org/d3-scale</li>
<li><strong>Topic:</strong> D3 Scale Functions for Data-to-Visual Mapping</li>
<li><strong>Techniques Learned:</strong>
<ol style="margin-top: 10px; margin-left: 20px;">
<li><strong>scaleTime()</strong> - Temporal data mapping with automatic date handling and tick formatting</li>
<li><strong>scaleSequential()</strong> - Continuous color encoding with smooth interpolation (RdYlBu color scheme)</li>
<li><strong>scaleBand()</strong> - Categorical positioning with automatic padding for bar charts</li>
<li><strong>scaleLinear()</strong> - Standard numeric domain-to-range mapping with inverted y-axis</li>
<li><strong>scaleThreshold()</strong> - Discrete binning with custom breakpoints (10°, 20°, 30°) for color categories</li>
<li><strong>scalePoint()</strong> - Precise categorical positioning without bandwidth (point charts)</li>
<li><strong>scaleLog()</strong> - Logarithmic transformation for wide-range variance data compression</li>
</ol>
</li>
<li><strong>What This Demonstrates:</strong> Scales are the foundation of D3 visualizations - they map abstract data domains (temperatures from -5°C to 32°C, dates from Jan-Dec, city names) to visual ranges (pixel positions, colors, sizes). Each scale type solves specific mapping challenges: time scales handle temporal data, band scales divide categorical space evenly, threshold scales create discrete bins, and log scales compress exponential ranges.</li>
<li><strong>Improvement over Iteration 1:</strong> While Iteration 1 focused on DOM selection and manipulation (the "how to change elements" foundation), Iteration 2 demonstrates the "how to map data" layer. We now transform raw temperature data through 8 different scale types to create meaningful visual encodings. The progression shows D3's architecture: selections manipulate the DOM, scales transform the data before it reaches the DOM.</li>
<li><strong>Scale Mapping Examples:</strong>
<ul style="margin-top: 8px; margin-left: 20px; list-style: circle;">
<li>Time: <code>new Date(2024, 6, 1)</code> → 600px (middle of chart)</li>
<li>Sequential: <code>30°C</code><code>#d73027</code> (red/hot)</li>
<li>Band: <code>"Miami"</code> → x position with automatic 20% padding</li>
<li>Threshold: <code>25°C</code><code>#f59e0b</code> (warm category)</li>
<li>Log: <code>variance=20</code> → compressed visual height vs linear display</li>
</ul>
</li>
</ul>
</footer>
</body>
</html>