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

222 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>美国关键矿产安全战略</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 20px;
}
svg {
background-color: #e9e9e9;
border: 1px solid #ccc;
}
.node rect {
stroke: #333;
stroke-width: 1px;
}
.node-goal rect {
fill: #84a94b;
stroke: none;
}
.node-pillar rect {
fill: #231f20;
stroke: none;
}
.node-action rect {
fill: #cccccc;
}
.node-intermediate rect {
fill: #ffffff;
}
.node text {
font-size: 12px;
fill: #000;
pointer-events: none;
}
.node-goal text, .node-pillar text {
fill: #ffffff;
}
.kpi-text {
font-size: 10px;
font-style: italic;
}
.link {
fill: none;
stroke: #555;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<svg id="chart" width="1200" height="850"></svg>
<script>
const data = {
nodes: [
// Main Goal
{ id: 'goal', type: 'goal', x: 450, y: 20, width: 300, height: 60, text: ['OVERALL GOAL', 'US critical minerals security'] },
// Pillars
{ id: 'pillar1', type: 'pillar', x: 50, y: 120, width: 320, height: 80, text: ['1 Rebuild US critical minerals', 'industry competitiveness'], kpi: 'KPIs: Average time from discovery to construction; no. of workers in CM industry; no. of projects in pipeline' },
{ id: 'pillar2', type: 'pillar', x: 440, y: 120, width: 320, height: 80, text: ['2 Reduce import reliance from', 'non-allied countries'], kpi: 'KPIs: Percentage of US critical minerals imported from or processed by non-allied countries' },
{ id: 'pillar3', type: 'pillar', x: 830, y: 120, width: 320, height: 80, text: ['3 Reduce the risk of critical minerals', 'market disruptions'], kpi: 'KPIs: No. of months of critical mineral reserves; percentage of value chain data collected' },
// Level 1 Intermediates
{ id: 'increase_domestic_prod', type: 'intermediate', x: 260, y: 300, width: 150, height: 50, text: ['Increase', 'domestic', 'production'] },
{ id: 'increase_allied_prod', type: 'intermediate', x: 480, y: 300, width: 150, height: 50, text: ['Increase allied', 'production'] },
{ id: 'monitor_vuln', type: 'intermediate', x: 750, y: 300, width: 150, height: 50, text: ['Monitor critical', 'minerals value', 'chain', 'vulnerabilities'] },
{ id: 'reduce_supply_risk', type: 'intermediate', x: 920, y: 300, width: 150, height: 50, text: ['Reduce supply', 'disruption risks'] },
{ id: 'reduce_price_risk', type: 'intermediate', x: 1090, y: 300, width: 150, height: 50, text: ['Reduce price', 'disruption risks'] },
// Level 2 Intermediates
{ id: 'expand_us_pipeline', type: 'intermediate', x: 150, y: 650, width: 150, height: 50, text: ['Expand US', 'critical minerals', 'project pipeline'] },
{ id: 'expand_allied_pipeline', type: 'intermediate', x: 550, y: 570, width: 150, height: 50, text: ['Expand allied', 'critical minerals', 'project pipeline'] },
// Actions (Grey Boxes)
{ id: '1e', type: 'action', x: 50, y: 300, width: 150, height: 50, text: ['(1e) Develop', 'critical minerals', 'workforce'] },
{ id: '1f', type: 'action', x: 50, y: 380, width: 150, height: 50, text: ['(1f) Increase', 'investment in', 'R&D for new', 'tech and AI usage'] },
{ id: '1c', type: 'action', x: 50, y: 540, width: 150, height: 50, text: ['(1c) Improve', 'permitting and', 'regulations'] },
{ id: '1d', type: 'action', x: 50, y: 650, width: 150, height: 50, text: ['(1d) Support', 'domestic', 'exploration with', 'funding, data,', 'and AI tools'] },
{ id: '1b_2d', type: 'action', x: 260, y: 460, width: 150, height: 50, text: ['(1b)/(2d)', 'Support', 'secondary', 'processing', 'expansion'] },
{ id: '1a', type: 'action', x: 260, y: 570, width: 150, height: 50, text: ['(1a) Increase', 'domestic', 'financing and', 'incentives'] },
{ id: '2a', type: 'action', x: 480, y: 460, width: 150, height: 50, text: ['(2a) Expand the', 'role and use of', 'existing', 'multilateral', 'arrangements'] },
{ id: '2b', type: 'action', x: 670, y: 460, width: 150, height: 50, text: ['(2b) Expand US', 'government', 'financing tools'] },
{ id: '2c', type: 'action', x: 550, y: 680, width: 150, height: 50, text: ['(2c) Support', 'exploration and', 'data initiatives'] },
{ id: '3a', type: 'action', x: 750, y: 460, width: 150, height: 50, text: ['(3a) Develop a', 'real-time value', 'chain mapping', 'and monitoring', 'system'] },
{ id: '3b', type: 'action', x: 920, y: 460, width: 150, height: 50, text: ['(3b) Expand', 'purpose and', 'use of National', 'Defense', 'Stockpile'] },
{ id: '3c', type: 'action', x: 1090, y: 460, width: 150, height: 50, text: ['(3c) Identify and', 'deploy targeted', 'market', 'incentives'] },
],
links: [
// Arrows
{ source: 'pillar1', target: 'increase_domestic_prod', path: 'M 210 200 V 300' },
{ source: 'pillar2', target: 'increase_allied_prod', path: 'M 600 200 V 300' },
{ source: 'pillar3', target: 'monitor_vuln', path: 'M 825 200 V 300' },
{ source: 'pillar3', target: 'reduce_supply_risk', path: 'M 995 200 V 300' },
{ source: 'pillar3', target: 'reduce_price_risk', path: 'M 1165 200 V 300' },
{ source: 'goal', target: 'pillar1', path: 'M 600 80 V 100 H 210 V 120' },
{ source: 'goal', target: 'pillar2', path: 'M 600 80 V 120' },
{ source: 'goal', target: 'pillar3', path: 'M 600 80 V 100 H 990 V 120' },
{ source: '1e', target: 'increase_domestic_prod', path: 'M 200 325 H 260' },
{ source: '1f', target: 'increase_domestic_prod', path: 'M 125 380 V 360 H 260' },
{ source: '1c', target: 'expand_us_pipeline', path: 'M 125 540 V 640 H 225' },
{ source: '1d', target: 'expand_us_pipeline', path: 'M 200 675 H 225' },
{ source: 'expand_us_pipeline', target: 'increase_domestic_prod', path: 'M 225 650 V 520 H 335 V 350' },
{ source: '1a', target: 'expand_us_pipeline', path: 'M 335 570 V 650' },
{ source: '1b_2d', target: 'increase_domestic_prod', path: 'M 335 460 V 350' },
{ source: '1b_2d', target: 'increase_allied_prod', path: 'M 410 485 H 450 V 350' },
{ source: '2a', target: 'increase_allied_prod', path: 'M 555 460 V 350' },
{ source: 'expand_allied_pipeline', target: 'increase_allied_prod', path: 'M 625 570 V 520 H 555 V 350' },
{ source: '2c', target: 'expand_allied_pipeline', path: 'M 625 680 V 620' },
{ source: '2b', target: 'expand_allied_pipeline', path: 'M 745 510 H 700 V 570' },
{ source: '2a', target: 'expand_allied_pipeline', path: 'M 555 510 H 580 V 570' },
{ source: '3a', target: 'monitor_vuln', path: 'M 825 460 V 350' },
{ source: '3b', target: 'reduce_supply_risk', path: 'M 995 460 V 350' },
{ source: '3c', target: 'reduce_price_risk', path: 'M 1165 460 V 350' },
]
};
const svg = d3.select("#chart");
// Define arrow marker
svg.append("defs").append("marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 5)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#555");
// Draw links
svg.append("g")
.selectAll("path")
.data(data.links)
.enter().append("path")
.attr("class", "link")
.attr("d", d => d.path)
.attr("marker-end", "url(#arrow)");
// Draw nodes
const node = svg.append("g")
.selectAll("g")
.data(data.nodes)
.enter().append("g")
.attr("class", d => `node node-${d.type}`)
.attr("transform", d => `translate(${d.x}, ${d.y})`);
node.append("rect")
.attr("width", d => d.width)
.attr("height", d => d.height);
// Add text to nodes
node.each(function(d) {
const group = d3.select(this);
const lineHeight = 14;
const y_start = (d.height - (d.text.length * lineHeight)) / 2 + 12;
d.text.forEach((line, i) => {
group.append("text")
.attr("x", d.width / 2)
.attr("y", y_start + (i * lineHeight))
.attr("text-anchor", "middle")
.text(line);
});
if (d.kpi) {
const kpiLines = wrapText(d.kpi, d.width - 20);
const kpi_y_start = 45;
kpiLines.forEach((line, i) => {
group.append("text")
.attr("class", "kpi-text")
.attr("x", d.width / 2)
.attr("y", kpi_y_start + (i * 12))
.attr("text-anchor", "middle")
.text(line);
});
}
});
// Text wrapping function for KPIs
function wrapText(text, width) {
const words = text.split(/\s+/).reverse();
let word;
const lines = [];
let line = [];
let tspan = d3.select(document.createElement("tspan"));
const testSvg = d3.select("body").append("svg").attr("width", 0).attr("height", 0);
const textElement = testSvg.append("text").attr("class", "kpi-text");
while (word = words.pop()) {
line.push(word);
textElement.text(line.join(" "));
if (textElement.node().getComputedTextLength() > width && line.length > 1) {
line.pop();
lines.push(line.join(" "));
line = [word];
}
}
lines.push(line.join(" "));
testSvg.remove();
return lines;
}
</script>
</body>
</html>