2025-06-25 18:59:39 +08:00

272 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>US Critical Minerals Security Flowchart</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
background-color: #f0f2f5;
margin: 0;
padding: 2rem;
}
/* --- Node Styles --- */
.node-group .node-rect {
stroke-width: 1.5;
stroke: #333;
}
.node-goal .node-rect {
fill: #8BC34A; /* A green similar to the image */
stroke: none;
}
.node-pillar .node-rect {
fill: #212121;
stroke: none;
}
.node-action .node-rect {
fill: #E0E0E0; /* A light grey */
stroke: #9E9E9E;
}
.node-intermediate .node-rect {
fill: #FFFFFF;
stroke: #424242;
}
/* --- Text Styles --- */
.node-text {
font-size: 11px;
text-anchor: middle;
fill: #000;
pointer-events: none;
}
.node-pillar .node-text, .node-goal .node-text {
fill: #fff;
}
.node-text .kpi-text {
font-size: 9px;
font-style: italic;
}
/* --- Link Styles --- */
.link {
fill: none;
stroke: #616161;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<div id="chart"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// 1. Data Definition
const nodes = [
// Top Goal
{ id: 'goal', type: 'goal', width: 280, height: 50, text: ['OVERALL GOAL', 'US critical minerals security'] },
// 3 Pillars
{ id: 'p1', type: 'pillar', width: 280, height: 75, text: ['1', 'Rebuild US critical minerals', 'industry competitiveness'], kpis: ['KPIs: Average time from discovery to construction; no.', 'of workers in CM industry; no. of projects in pipeline'] },
{ id: 'p2', type: 'pillar', width: 280, height: 75, text: ['2', 'Reduce import reliance from', 'non-allied countries'], kpis: ['KPIs: Percentage of US critical minerals imported from', 'or processed by non-allied countries'] },
{ id: 'p3', type: 'pillar', width: 280, height: 75, text: ['3', 'Reduce the risk of critical minerals', 'market disruptions'], kpis: ['KPIs: No. of months of critical mineral reserves;', 'percentage of value chain data collected'] },
// Intermediate Nodes
{ id: 'int1', type: 'intermediate', width: 130, height: 40, text: ['Increase domestic', 'production'] },
{ id: 'int2', type: 'intermediate', width: 130, height: 40, text: ['Increase allied', 'production'] },
{ id: 'int3', type: 'intermediate', width: 130, height: 40, text: ['Expand US', 'critical minerals', 'project pipeline'] },
{ id: 'int4', type: 'intermediate', width: 130, height: 40, text: ['Expand allied', 'critical minerals', 'project pipeline'] },
{ id: 'int5', type: 'intermediate', width: 130, height: 50, text: ['Monitor critical', 'minerals value', 'chain', 'vulnerabilities'] },
{ id: 'int6', type: 'intermediate', width: 130, height: 40, text: ['Reduce supply', 'disruption risks'] },
{ id: 'int7', type: 'intermediate', width: 130, height: 40, text: ['Reduce price', 'disruption risks'] },
// Action Nodes (Grey)
{ id: 'a1e', type: 'action', width: 130, height: 45, text: ['(1e) Develop', 'critical minerals', 'workforce'] },
{ id: 'a1f', type: 'action', width: 130, height: 55, text: ['(1f) Increase', 'investment in', 'R&D for new', 'tech and AI', 'usage'] },
{ id: 'a1c', type: 'action', width: 130, height: 45, text: ['(1c) Improve', 'permitting and', 'regulations'] },
{ id: 'a1d', type: 'action', width: 130, height: 55, text: ['(1d) Support', 'domestic', 'exploration with', 'funding, data,', 'and AI tools'] },
{ id: 'a1a', type: 'action', width: 130, height: 45, text: ['(1a) Increase', 'domestic', 'financing and', 'incentives'] },
{ id: 'a1b/2d', type: 'action', width: 130, height: 55, text: ['(1b)/(2d)', 'Support', 'secondary', 'processing', 'expansion'] },
{ id: 'a2a', type: 'action', width: 130, height: 55, text: ['(2a) Expand the', 'role and use of', 'existing', 'multilateral', 'arrangements'] },
{ id: 'a2b', type: 'action', width: 130, height: 45, text: ['(2b) Expand US', 'government', 'financing tools'] },
{ id: 'a2c', type: 'action', width: 130, height: 45, text: ['(2c) Support', 'exploration and', 'data initiatives'] },
{ id: 'a3a', type: 'action', width: 130, height: 55, text: ['(3a) Develop a', 'real-time value', 'chain mapping', 'and monitoring', 'system'] },
{ id: 'a3b', type: 'action', width: 130, height: 55, text: ['(3b) Expand', 'purpose and', 'use of National', 'Defense', 'Stockpile'] },
{ id: 'a3c', type: 'action', width: 130, height: 55, text: ['(3c) Identify and', 'deploy targeted', 'market', 'incentives'] },
];
const links = [
{ source: 'goal', target: 'p1' }, { source: 'goal', target: 'p2' }, { source: 'goal', target: 'p3' },
{ source: 'p1', target: 'int1' }, { source: 'p2', target: 'int2' },
{ source: 'p3', target: 'int5' }, { source: 'p3', target: 'int6' }, { source: 'p3', target: 'int7' },
{ source: 'int1', target: 'a1e' }, { source: 'int1', target: 'a1f' }, { source: 'int1', target: 'a1c' },
{ source: 'int1', target: 'a1b/2d' },
{ source: 'int3', target: 'a1d' },
{ source: 'a1a', target: 'int1' },
{ source: 'a1b/2d', target: 'int2' },
{ source: 'a1c', target: 'int3' }, { source: 'a1f', target: 'int3' },
{ source: 'a2a', target: 'int2' }, { source: 'a2b', target: 'a2a' },
{ source: 'a2a', target: 'int4' },
{ source: 'a2b', target: 'int4' },
{ source: 'int4', target: 'a2c' },
{ source: 'a3a', target: 'int5' }, { source: 'a3b', target: 'int6' }, { source: 'a3c', target: 'int7' },
{ source: 'int6', target: 'a3b' }
];
// 2. SVG Setup and Layout
const width = 960;
const height = 850;
const nodePositions = {
'goal': { x: width / 2, y: 50 },
'p1': { x: 150, y: 170 },
'p2': { x: width / 2, y: 170 },
'p3': { x: width - 150, y: 170 },
'int1': { x: 150, y: 320 },
'int2': { x: width / 2, y: 320 },
'int3': { x: 150, y: 510 },
'int4': { x: width / 2, y: 510 },
'int5': { x: 570, y: 320 },
'int6': { x: 710, y: 320 },
'int7': { x: 850, y: 320 },
'a1e': { x: 50, y: 420 },
'a1f': { x: 50, y: 510 },
'a1c': { x: 50, y: 600 },
'a1d': { x: 150, y: 600 },
'a1a': { x: 250, y: 420 },
'a1b/2d': { x: 250, y: 510 },
'a2a': { x: width / 2, y: 420 },
'a2b': { x: 350, y: 510 },
'a2c': { x: width / 2, y: 600 },
'a3a': { x: 570, y: 420 },
'a3b': { x: 710, y: 420 },
'a3c': { x: 850, y: 420 },
};
const svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
// Define arrow markers
svg.append("defs").append("marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#616161");
// 3. Render Links
const linkGroup = svg.append("g").attr("class", "links");
links.forEach(link => {
const sourceNode = nodes.find(n => n.id === link.source);
const targetNode = nodes.find(n => n.id === link.target);
const sourcePos = nodePositions[link.source];
const targetPos = nodePositions[link.target];
let pathData;
const sx = sourcePos.x, sy = sourcePos.y;
const tx = targetPos.x, ty = targetPos.y;
const sourceHalfHeight = sourceNode.height / 2;
const targetHalfHeight = targetNode.height / 2;
const sourceHalfWidth = sourceNode.width / 2;
const targetHalfWidth = targetNode.width / 2;
// Simple routing for orthogonal lines
if (sx === tx) { // Vertical
pathData = `M ${sx},${sy < ty ? sy + sourceHalfHeight : sy - sourceHalfHeight} L ${tx},${sy < ty ? ty - targetHalfHeight : ty + targetHalfHeight}`;
} else if (sy === ty) { // Horizontal
pathData = `M ${sx < tx ? sx + sourceHalfWidth : sx - sourceHalfWidth},${sy} L ${tx < sx ? tx + targetHalfWidth : tx - targetHalfWidth},${ty}`;
} else { // L-shaped connectors
const midY = sy + (ty-sy)/2;
pathData = `M ${sx},${sy < ty ? sy + sourceHalfHeight : sy - sourceHalfHeight} L ${sx},${midY} L ${tx},${midY} L ${tx},${sy < ty ? ty - targetHalfHeight : ty + targetHalfHeight}`;
}
// Custom paths based on the diagram
if( (link.source === 'a1c' && link.target === 'int3') || (link.source === 'a1f' && link.target === 'int3') ) {
pathData = `M ${sx + sourceHalfWidth},${sy} L ${tx - targetHalfWidth},${ty}`;
} else if (link.source === 'a1b/2d' && link.target === 'int2') {
pathData = `M ${sx + sourceHalfWidth},${sy} L ${tx - targetHalfWidth},${ty}`;
} else if (link.source === 'a2b' && link.target === 'a2a') {
pathData = `M ${sx},${sy - sourceHalfHeight} L ${tx},${ty + targetHalfHeight}`;
} else if (link.source === 'a2a' && link.target === 'int4') {
pathData = `M ${sx},${sy + sourceHalfHeight} L ${tx},${ty - targetHalfHeight}`;
} else if (link.source === 'a2b' && link.target === 'int4') {
const midX = sx + (tx-sx)/2;
pathData = `M ${sx-sourceHalfWidth},${sy} L ${midX},${sy} L ${midX},${ty+targetHalfHeight}`;
}
linkGroup.append("path")
.attr("class", "link")
.attr("d", pathData)
.attr("marker-end", "url(#arrow)");
});
// 4. Render Nodes
const nodeGroups = svg.selectAll("g.node-group")
.data(nodes)
.enter()
.append("g")
.attr("class", d => `node-group node-${d.type}`)
.attr("transform", d => `translate(${nodePositions[d.id].x}, ${nodePositions[d.id].y})`);
// Add rectangles
nodeGroups.append("rect")
.attr("class", "node-rect")
.attr("x", d => -d.width / 2)
.attr("y", d => -d.height / 2)
.attr("width", d => d.width)
.attr("height", d => d.height)
.attr("rx", 3) // Rounded corners
.attr("ry", 3);
// Add text labels
const text = nodeGroups.append("text")
.attr("class", "node-text")
.attr("dy", d => (d.kpis ? -5 : 0)); // Adjust vertical position for nodes with KPIs
text.each(function(d) {
const el = d3.select(this);
const lineHeight = 12; // Adjust this value for line spacing
const initialY = -( (d.text.length -1) * lineHeight) / 2; // Calculate starting Y to center the block
d.text.forEach((line, i) => {
el.append("tspan")
.attr("x", 0)
.attr("y", initialY + (i * lineHeight))
.text(line)
.style("font-weight", (i === 0 && (d.type === 'pillar' || d.text[0].startsWith('(')) ? "bold" : "normal"));
});
// Add KPI text for pillar nodes
if (d.kpis) {
const kpiYstart = d.height / 2 - (d.kpis.length * 10) - 4;
d.kpis.forEach((line, i) => {
el.append("tspan")
.attr("class", "kpi-text")
.attr("x", 0)
.attr("y", kpiYstart + (i * 10))
.text(line);
});
}
});
</script>
</body>
</html>