222 lines
10 KiB
HTML
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> |