infinite-agents-public/sdg_viz/sdg_viz_5.html

739 lines
27 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SDG Network Viz 5 - Advanced Color Encodings & Visual Hierarchy</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
#container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
header {
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
color: white;
padding: 30px;
text-align: center;
}
h1 {
margin: 0 0 10px 0;
font-size: 2.2em;
font-weight: 700;
}
.subtitle {
font-size: 1.1em;
opacity: 0.9;
margin: 0;
}
#controls {
padding: 20px;
background: #f8fafc;
border-bottom: 2px solid #e2e8f0;
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: center;
justify-content: center;
}
.control-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.control-group label {
font-weight: 600;
font-size: 0.9em;
color: #334155;
}
select, button {
padding: 10px 16px;
border-radius: 6px;
border: 2px solid #cbd5e1;
font-size: 1em;
background: white;
cursor: pointer;
transition: all 0.3s;
}
select:hover, button:hover {
border-color: #3b82f6;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2);
}
button {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
border: none;
font-weight: 600;
}
button:hover {
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
#viz-container {
position: relative;
padding: 20px;
}
svg {
display: block;
margin: 0 auto;
background: #ffffff;
border-radius: 8px;
}
.node {
cursor: pointer;
transition: all 0.3s;
}
.node:hover {
filter: brightness(1.2);
}
.node-circle {
stroke-width: 3;
}
.node-label {
font-size: 11px;
font-weight: 600;
text-anchor: middle;
pointer-events: none;
fill: #1e293b;
text-shadow: 0 0 3px white, 0 0 3px white, 0 0 3px white;
}
.link {
stroke-opacity: 0.4;
stroke-linecap: round;
}
.link:hover {
stroke-opacity: 0.8;
stroke-width: 3 !important;
}
#legend-container {
padding: 20px;
background: #f8fafc;
border-top: 2px solid #e2e8f0;
}
.legend-section {
margin-bottom: 20px;
}
.legend-title {
font-weight: 700;
font-size: 1.1em;
color: #1e293b;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.legend-items {
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: white;
border-radius: 6px;
border: 2px solid #e2e8f0;
cursor: pointer;
transition: all 0.3s;
}
.legend-item:hover {
border-color: #3b82f6;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
.legend-item.filtered {
opacity: 0.3;
}
.legend-swatch {
width: 24px;
height: 24px;
border-radius: 4px;
border: 2px solid #cbd5e1;
}
.legend-label {
font-size: 0.9em;
color: #334155;
font-weight: 500;
}
.gradient-legend {
display: flex;
align-items: center;
gap: 12px;
margin-top: 10px;
}
.gradient-bar {
width: 300px;
height: 20px;
border-radius: 4px;
border: 2px solid #cbd5e1;
}
.gradient-labels {
display: flex;
justify-content: space-between;
font-size: 0.85em;
color: #64748b;
margin-top: 4px;
}
footer {
padding: 25px;
background: #1e293b;
color: #e2e8f0;
line-height: 1.8;
}
footer h3 {
color: #60a5fa;
margin-top: 0;
margin-bottom: 12px;
}
footer p {
margin: 8px 0;
}
footer strong {
color: #93c5fd;
}
.stats {
display: flex;
gap: 30px;
margin-top: 15px;
flex-wrap: wrap;
}
.stat-item {
background: #334155;
padding: 12px 20px;
border-radius: 6px;
border-left: 4px solid #60a5fa;
}
.stat-label {
font-size: 0.85em;
color: #94a3b8;
margin-bottom: 4px;
}
.stat-value {
font-size: 1.3em;
font-weight: 700;
color: #60a5fa;
}
</style>
</head>
<body>
<div id="container">
<header>
<h1>SDG Network Visualization - Advanced Color Encodings</h1>
<p class="subtitle">Multi-Dimensional Color Strategy with Visual Hierarchy</p>
</header>
<div id="controls">
<div class="control-group">
<label for="fillScheme">Node Fill Color (Category)</label>
<select id="fillScheme">
<option value="category10">Category10 (Categorical)</option>
<option value="accent">Accent (Categorical)</option>
<option value="dark2">Dark2 (Categorical)</option>
<option value="set3">Set3 (Categorical)</option>
</select>
</div>
<div class="control-group">
<label for="borderScheme">Node Border Color (Magnitude)</label>
<select id="borderScheme">
<option value="viridis">Viridis (Sequential)</option>
<option value="plasma">Plasma (Sequential)</option>
<option value="inferno">Inferno (Sequential)</option>
<option value="cividis">Cividis (Sequential)</option>
</select>
</div>
<div class="control-group">
<label for="edgeScheme">Edge Color Mode</label>
<select id="edgeScheme">
<option value="gradient">Gradient (Source→Target)</option>
<option value="strength">Strength (Sequential)</option>
<option value="rdylbu">RdYlBu (Diverging)</option>
</select>
</div>
<button id="resetBtn">Reset Filters</button>
</div>
<div id="viz-container">
<svg id="network"></svg>
</div>
<div id="legend-container">
<div class="legend-section">
<div class="legend-title">
<span>🎨</span>
<span>SDG Goal Categories (Click to Filter)</span>
</div>
<div class="legend-items" id="category-legend"></div>
</div>
<div class="legend-section">
<div class="legend-title">
<span>📊</span>
<span>Impact Magnitude (Border Color & Size)</span>
</div>
<div class="gradient-legend">
<div class="gradient-bar" id="magnitude-gradient"></div>
<div style="flex: 1;">
<div class="gradient-labels">
<span>Low Impact</span>
<span>Medium Impact</span>
<span>High Impact</span>
</div>
</div>
</div>
</div>
<div class="legend-section">
<div class="legend-title">
<span>💫</span>
<span>Data Quality (Opacity)</span>
</div>
<div class="gradient-legend">
<div style="display: flex; gap: 20px; align-items: center;">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 30px; height: 30px; background: #3b82f6; opacity: 0.4; border-radius: 50%; border: 2px solid #1e40af;"></div>
<span style="font-size: 0.9em; color: #64748b;">Low Quality (40%)</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 30px; height: 30px; background: #3b82f6; opacity: 0.7; border-radius: 50%; border: 2px solid #1e40af;"></div>
<span style="font-size: 0.9em; color: #64748b;">Medium Quality (70%)</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 30px; height: 30px; background: #3b82f6; opacity: 1.0; border-radius: 50%; border: 2px solid #1e40af;"></div>
<span style="font-size: 0.9em; color: #64748b;">High Quality (100%)</span>
</div>
</div>
</div>
</div>
</div>
<footer>
<h3>Advanced Color Encoding Strategy</h3>
<p><strong>Multi-Attribute Encoding:</strong></p>
<ul style="margin: 8px 0; padding-left: 20px;">
<li><strong>Node Fill Color:</strong> SDG category (categorical - d3.schemeCategory10 default)</li>
<li><strong>Node Border Color:</strong> Impact magnitude (sequential - d3.interpolateViridis default)</li>
<li><strong>Node Opacity:</strong> Data quality/confidence (0.4-1.0 range)</li>
<li><strong>Node Size:</strong> Impact magnitude (radius 8-28px)</li>
<li><strong>Edge Color:</strong> Dynamic gradients from source to target node colors</li>
<li><strong>Edge Opacity:</strong> Connection strength (0.2-0.8 range)</li>
</ul>
<p><strong>Web Learning Source:</strong> Observable HQ - D3 Color Legend (@d3/color-legend)</p>
<p><strong>Techniques Applied:</strong></p>
<ul style="margin: 8px 0; padding-left: 20px;">
<li>Perceptually uniform color scales (Viridis, Plasma, Cividis) for sequential encoding</li>
<li>Categorical color schemes (Category10, Accent, Dark2) for SDG categories</li>
<li>Custom interactive legends with click-to-filter functionality</li>
<li>Gradient bars for continuous scale visualization</li>
<li>Multi-dimensional encoding: fill + border + opacity + size</li>
<li>Dynamic color scheme switching for real-time exploration</li>
</ul>
<p><strong>Data Source:</strong> Embedded SDG network data (17 goals, 45+ connections) - Zero API latency</p>
<div class="stats">
<div class="stat-item">
<div class="stat-label">Color Dimensions</div>
<div class="stat-value">4 Attributes</div>
</div>
<div class="stat-item">
<div class="stat-label">Color Schemes</div>
<div class="stat-value">8+ Options</div>
</div>
<div class="stat-item">
<div class="stat-label">Interactive Elements</div>
<div class="stat-value">Legends + Filters</div>
</div>
<div class="stat-item">
<div class="stat-label">Load Time</div>
<div class="stat-value">&lt;1 Second</div>
</div>
</div>
<p style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #475569; font-size: 0.9em; color: #94a3b8;">
SDG Network Visualization - Iteration 5 | Generated with web-enhanced learning |
Demonstrates advanced D3.js color encoding strategies
</p>
</footer>
</div>
<script>
// Embedded SDG Network Data with Multi-Dimensional Attributes
const sdgData = {
nodes: [
{ id: 'SDG1', name: 'No Poverty', category: 'Social', magnitude: 95, quality: 0.9 },
{ id: 'SDG2', name: 'Zero Hunger', category: 'Social', magnitude: 88, quality: 0.85 },
{ id: 'SDG3', name: 'Good Health', category: 'Social', magnitude: 92, quality: 0.95 },
{ id: 'SDG4', name: 'Quality Education', category: 'Social', magnitude: 85, quality: 0.8 },
{ id: 'SDG5', name: 'Gender Equality', category: 'Social', magnitude: 78, quality: 0.75 },
{ id: 'SDG6', name: 'Clean Water', category: 'Environmental', magnitude: 82, quality: 0.88 },
{ id: 'SDG7', name: 'Clean Energy', category: 'Environmental', magnitude: 90, quality: 0.92 },
{ id: 'SDG8', name: 'Economic Growth', category: 'Economic', magnitude: 87, quality: 0.85 },
{ id: 'SDG9', name: 'Innovation', category: 'Economic', magnitude: 75, quality: 0.7 },
{ id: 'SDG10', name: 'Reduced Inequalities', category: 'Social', magnitude: 70, quality: 0.65 },
{ id: 'SDG11', name: 'Sustainable Cities', category: 'Environmental', magnitude: 80, quality: 0.78 },
{ id: 'SDG12', name: 'Responsible Consumption', category: 'Environmental', magnitude: 72, quality: 0.68 },
{ id: 'SDG13', name: 'Climate Action', category: 'Environmental', magnitude: 98, quality: 0.98 },
{ id: 'SDG14', name: 'Life Below Water', category: 'Environmental', magnitude: 76, quality: 0.72 },
{ id: 'SDG15', name: 'Life On Land', category: 'Environmental', magnitude: 79, quality: 0.75 },
{ id: 'SDG16', name: 'Peace & Justice', category: 'Governance', magnitude: 68, quality: 0.6 },
{ id: 'SDG17', name: 'Partnerships', category: 'Governance', magnitude: 85, quality: 0.82 }
],
links: [
{ source: 'SDG1', target: 'SDG2', strength: 0.9 },
{ source: 'SDG1', target: 'SDG3', strength: 0.85 },
{ source: 'SDG1', target: 'SDG8', strength: 0.8 },
{ source: 'SDG2', target: 'SDG3', strength: 0.75 },
{ source: 'SDG2', target: 'SDG6', strength: 0.7 },
{ source: 'SDG2', target: 'SDG13', strength: 0.65 },
{ source: 'SDG3', target: 'SDG6', strength: 0.8 },
{ source: 'SDG3', target: 'SDG11', strength: 0.6 },
{ source: 'SDG4', target: 'SDG5', strength: 0.7 },
{ source: 'SDG4', target: 'SDG8', strength: 0.75 },
{ source: 'SDG4', target: 'SDG10', strength: 0.65 },
{ source: 'SDG5', target: 'SDG8', strength: 0.6 },
{ source: 'SDG5', target: 'SDG10', strength: 0.8 },
{ source: 'SDG6', target: 'SDG7', strength: 0.55 },
{ source: 'SDG6', target: 'SDG13', strength: 0.7 },
{ source: 'SDG6', target: 'SDG14', strength: 0.75 },
{ source: 'SDG7', target: 'SDG9', strength: 0.8 },
{ source: 'SDG7', target: 'SDG11', strength: 0.7 },
{ source: 'SDG7', target: 'SDG13', strength: 0.9 },
{ source: 'SDG8', target: 'SDG9', strength: 0.85 },
{ source: 'SDG8', target: 'SDG10', strength: 0.65 },
{ source: 'SDG9', target: 'SDG11', strength: 0.75 },
{ source: 'SDG9', target: 'SDG12', strength: 0.6 },
{ source: 'SDG11', target: 'SDG12', strength: 0.7 },
{ source: 'SDG11', target: 'SDG13', strength: 0.8 },
{ source: 'SDG12', target: 'SDG13', strength: 0.75 },
{ source: 'SDG12', target: 'SDG14', strength: 0.65 },
{ source: 'SDG12', target: 'SDG15', strength: 0.7 },
{ source: 'SDG13', target: 'SDG14', strength: 0.85 },
{ source: 'SDG13', target: 'SDG15', strength: 0.9 },
{ source: 'SDG14', target: 'SDG15', strength: 0.6 },
{ source: 'SDG16', target: 'SDG1', strength: 0.7 },
{ source: 'SDG16', target: 'SDG5', strength: 0.75 },
{ source: 'SDG16', target: 'SDG10', strength: 0.8 },
{ source: 'SDG17', target: 'SDG1', strength: 0.65 },
{ source: 'SDG17', target: 'SDG8', strength: 0.7 },
{ source: 'SDG17', target: 'SDG9', strength: 0.75 },
{ source: 'SDG17', target: 'SDG13', strength: 0.8 },
{ source: 'SDG17', target: 'SDG16', strength: 0.85 }
]
};
// Color scheme definitions
const colorSchemes = {
fill: {
category10: d3.schemeCategory10,
accent: d3.schemeAccent,
dark2: d3.schemeDark2,
set3: d3.schemeSet3
},
border: {
viridis: d3.interpolateViridis,
plasma: d3.interpolatePlasma,
inferno: d3.interpolateInferno,
cividis: d3.interpolateCividis
},
edge: {
rdylbu: d3.interpolateRdYlBu
}
};
// Visualization state
let currentFillScheme = 'category10';
let currentBorderScheme = 'viridis';
let currentEdgeScheme = 'gradient';
let filteredCategories = new Set();
// SVG dimensions
const width = 1200;
const height = 700;
// Create SVG
const svg = d3.select('#network')
.attr('width', width)
.attr('height', height);
// Create gradient definitions for edges
const defs = svg.append('defs');
// Force simulation
const simulation = d3.forceSimulation(sdgData.nodes)
.force('link', d3.forceLink(sdgData.links)
.id(d => d.id)
.distance(d => 120 - (d.strength * 40)))
.force('charge', d3.forceManyBody().strength(-400))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(d => getNodeRadius(d) + 5));
// Create link elements
const linkGroup = svg.append('g').attr('class', 'links');
// Create node elements
const nodeGroup = svg.append('g').attr('class', 'nodes');
// Helper functions
function getNodeRadius(d) {
return 8 + (d.magnitude / 100) * 20; // 8-28px based on magnitude
}
function getCategoryColor(category, scheme) {
const categories = ['Social', 'Environmental', 'Economic', 'Governance'];
const index = categories.indexOf(category);
return colorSchemes.fill[scheme][index % colorSchemes.fill[scheme].length];
}
function getMagnitudeColor(magnitude, scheme) {
const normalized = magnitude / 100; // 0-1 range
return colorSchemes.border[scheme](normalized);
}
function createGradientId(link) {
return `gradient-${link.source.id}-${link.target.id}`;
}
function updateVisualization() {
// Clear existing elements
linkGroup.selectAll('*').remove();
nodeGroup.selectAll('*').remove();
defs.selectAll('linearGradient').remove();
// Create gradients for edges
if (currentEdgeScheme === 'gradient') {
sdgData.links.forEach(link => {
const gradient = defs.append('linearGradient')
.attr('id', createGradientId(link))
.attr('gradientUnits', 'userSpaceOnUse');
gradient.append('stop')
.attr('offset', '0%')
.attr('stop-color', getCategoryColor(link.source.category, currentFillScheme));
gradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', getCategoryColor(link.target.category, currentFillScheme));
});
}
// Draw links
const links = linkGroup.selectAll('line')
.data(sdgData.links)
.join('line')
.attr('class', 'link')
.attr('stroke-width', d => 1 + (d.strength * 3))
.style('stroke-opacity', d => 0.2 + (d.strength * 0.6));
// Set link colors based on scheme
if (currentEdgeScheme === 'gradient') {
links.attr('stroke', d => `url(#${createGradientId(d)})`);
} else if (currentEdgeScheme === 'strength') {
links.attr('stroke', d => colorSchemes.border.viridis(d.strength));
} else if (currentEdgeScheme === 'rdylbu') {
links.attr('stroke', d => colorSchemes.edge.rdylbu(d.strength));
}
// Draw nodes
const nodes = nodeGroup.selectAll('g.node')
.data(sdgData.nodes)
.join('g')
.attr('class', 'node')
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
// Node circles
nodes.append('circle')
.attr('class', 'node-circle')
.attr('r', getNodeRadius)
.attr('fill', d => getCategoryColor(d.category, currentFillScheme))
.attr('stroke', d => getMagnitudeColor(d.magnitude, currentBorderScheme))
.style('opacity', d => d.quality);
// Node labels
nodes.append('text')
.attr('class', 'node-label')
.attr('dy', d => getNodeRadius(d) + 14)
.text(d => d.name);
// Apply filters
if (filteredCategories.size > 0) {
nodes.style('display', d => filteredCategories.has(d.category) ? 'none' : 'block');
links.style('display', d => {
return filteredCategories.has(d.source.category) ||
filteredCategories.has(d.target.category) ? 'none' : 'block';
});
}
// Update positions on simulation tick
simulation.on('tick', () => {
links
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
// Update gradient positions
if (currentEdgeScheme === 'gradient') {
defs.selectAll('linearGradient').each(function(_, i) {
const link = sdgData.links[i];
d3.select(this)
.attr('x1', link.source.x)
.attr('y1', link.source.y)
.attr('x2', link.target.x)
.attr('y2', link.target.y);
});
}
nodes.attr('transform', d => `translate(${d.x},${d.y})`);
});
}
// Drag functions
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// Create legends
function createCategoryLegend() {
const categories = [...new Set(sdgData.nodes.map(d => d.category))];
const legendContainer = d3.select('#category-legend');
legendContainer.selectAll('.legend-item')
.data(categories)
.join('div')
.attr('class', 'legend-item')
.classed('filtered', d => filteredCategories.has(d))
.on('click', function(event, category) {
if (filteredCategories.has(category)) {
filteredCategories.delete(category);
} else {
filteredCategories.add(category);
}
createCategoryLegend();
updateVisualization();
})
.html(d => `
<div class="legend-swatch" style="background: ${getCategoryColor(d, currentFillScheme)}"></div>
<span class="legend-label">${d}</span>
`);
}
function createMagnitudeGradient() {
const gradientBar = d3.select('#magnitude-gradient');
const steps = 20;
let gradientStr = 'linear-gradient(to right';
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const color = getMagnitudeColor(t * 100, currentBorderScheme);
gradientStr += `, ${color} ${(t * 100)}%`;
}
gradientStr += ')';
gradientBar.style('background', gradientStr);
}
// Event listeners
d3.select('#fillScheme').on('change', function() {
currentFillScheme = this.value;
createCategoryLegend();
updateVisualization();
});
d3.select('#borderScheme').on('change', function() {
currentBorderScheme = this.value;
createMagnitudeGradient();
updateVisualization();
});
d3.select('#edgeScheme').on('change', function() {
currentEdgeScheme = this.value;
updateVisualization();
});
d3.select('#resetBtn').on('click', () => {
filteredCategories.clear();
createCategoryLegend();
updateVisualization();
});
// Initialize
createCategoryLegend();
createMagnitudeGradient();
updateVisualization();
</script>
</body>
</html>