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

389 lines
14 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>US Critical Minerals Security Strategy</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.overall-goal {
background: #8bc34a;
color: white;
padding: 20px;
text-align: center;
font-weight: bold;
font-size: 18px;
margin-bottom: 30px;
border-radius: 8px;
}
.goal-title {
font-size: 14px;
margin-bottom: 10px;
}
.goal-text {
font-size: 24px;
}
.node {
cursor: pointer;
}
.goal-node {
fill: #2c3e50;
stroke: #2c3e50;
stroke-width: 2;
}
.action-node {
fill: #95a5a6;
stroke: #7f8c8d;
stroke-width: 1;
}
.kpi-node {
fill: white;
stroke: #2c3e50;
stroke-width: 2;
}
.node-text {
fill: white;
font-size: 12px;
text-anchor: middle;
dominant-baseline: middle;
font-weight: bold;
}
.kpi-text {
fill: #2c3e50;
font-size: 10px;
text-anchor: middle;
dominant-baseline: middle;
}
.arrow {
fill: none;
stroke: #666;
stroke-width: 2;
marker-end: url(#arrowhead);
}
.goal-number {
fill: white;
font-size: 48px;
font-weight: bold;
text-anchor: middle;
dominant-baseline: middle;
}
.goal-title-text {
fill: white;
font-size: 14px;
font-weight: bold;
text-anchor: start;
dominant-baseline: middle;
}
.goal-description {
fill: white;
font-size: 10px;
text-anchor: start;
dominant-baseline: middle;
}
</style>
</head>
<body>
<div class="container">
<div class="overall-goal">
<div class="goal-title">OVERALL GOAL</div>
<div class="goal-text">US critical minerals security</div>
</div>
<svg id="flowchart" width="1160" height="800"></svg>
</div>
<script>
const svg = d3.select("#flowchart");
const width = 1160;
const height = 800;
// 定义箭头标记
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#666");
// 主要目标节点数据
const goalNodes = [
{
id: "goal1",
x: 160,
y: 180,
width: 300,
height: 80,
number: "1",
title: "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: "goal2",
x: 480,
y: 180,
width: 300,
height: 80,
number: "2",
title: "Reduce import reliance from non-allied countries",
kpi: "KPIs: Percentage of US critical minerals imported from or processed by non-allied countries"
},
{
id: "goal3",
x: 800,
y: 180,
width: 300,
height: 80,
number: "3",
title: "Reduce the risk of critical minerals market disruptions",
kpi: "KPIs: No. of months of critical mineral reserves; percentage of value chain data collected"
}
];
// 行动节点数据
const actionNodes = [
// Goal 1 actions
{ id: "1a", x: 50, y: 320, width: 120, height: 60, text: "(1a) Develop critical minerals workforce" },
{ id: "1b", x: 50, y: 400, width: 120, height: 60, text: "(1b) Increase investment in R&D for new tech and AI usage" },
{ id: "1c", x: 50, y: 480, width: 120, height: 60, text: "(1c) Improve permitting and regulations" },
{ id: "1d", x: 50, y: 560, width: 120, height: 60, text: "(1d) Support domestic exploration with funding, data, and AI tools" },
{ id: "increase", x: 240, y: 320, width: 120, height: 60, text: "Increase domestic production" },
{ id: "support", x: 240, y: 440, width: 120, height: 60, text: "(1b)/(2d) Support secondary processing expansion" },
{ id: "financing", x: 240, y: 520, width: 120, height: 60, text: "(1a) Increase domestic financing and incentives" },
{ id: "expand", x: 180, y: 640, width: 120, height: 60, text: "Expand US critical minerals project pipeline" },
// Goal 2 actions
{ id: "2a", x: 380, y: 400, width: 120, height: 60, text: "(2a) Expand the role and use of existing multilateral arrangements" },
{ id: "2b", x: 530, y: 400, width: 120, height: 60, text: "(2b) Expand US government financing tools" },
{ id: "2c", x: 460, y: 520, width: 120, height: 60, text: "(2c) Support exploration and data initiatives" },
{ id: "increase2", x: 480, y: 320, width: 120, height: 60, text: "Increase allied production" },
{ id: "expand2", x: 460, y: 580, width: 120, height: 60, text: "Expand allied critical minerals project pipeline" },
// Goal 3 actions
{ id: "3a", x: 700, y: 400, width: 120, height: 60, text: "(3a) Develop a real-time value chain mapping and monitoring system" },
{ id: "3b", x: 850, y: 400, width: 120, height: 60, text: "(3b) Expand purpose and use of National Defense Stockpile" },
{ id: "3c", x: 1000, y: 400, width: 120, height: 60, text: "(3c) Identify and deploy targeted market incentives" },
{ id: "monitor", x: 720, y: 320, width: 120, height: 60, text: "Monitor critical minerals value chain vulnerabilities" },
{ id: "reduce1", x: 850, y: 320, width: 120, height: 60, text: "Reduce supply disruption risks" },
{ id: "reduce2", x: 1000, y: 320, width: 120, height: 60, text: "Reduce price disruption risks" }
];
// 绘制主要目标节点
goalNodes.forEach(goal => {
const g = svg.append("g").attr("class", "goal-group");
// 主要矩形
g.append("rect")
.attr("x", goal.x)
.attr("y", goal.y)
.attr("width", goal.width)
.attr("height", goal.height)
.attr("class", "goal-node")
.attr("rx", 5);
// 数字圆形
g.append("circle")
.attr("cx", goal.x + 30)
.attr("cy", goal.y + 40)
.attr("r", 25)
.attr("fill", "white");
g.append("text")
.attr("x", goal.x + 30)
.attr("y", goal.y + 40)
.attr("class", "goal-number")
.attr("fill", "#2c3e50")
.text(goal.number);
// 标题文本
const titleLines = goal.title.split(' ');
const midIndex = Math.ceil(titleLines.length / 2);
const firstLine = titleLines.slice(0, midIndex).join(' ');
const secondLine = titleLines.slice(midIndex).join(' ');
g.append("text")
.attr("x", goal.x + 70)
.attr("y", goal.y + 30)
.attr("class", "goal-title-text")
.text(firstLine);
g.append("text")
.attr("x", goal.x + 70)
.attr("y", goal.y + 50)
.attr("class", "goal-title-text")
.text(secondLine);
// KPI文本框
g.append("rect")
.attr("x", goal.x)
.attr("y", goal.y + 90)
.attr("width", goal.width)
.attr("height", 40)
.attr("class", "kpi-node")
.attr("rx", 3);
// 分行显示KPI文本
const kpiLines = wrapText(goal.kpi, 50);
kpiLines.forEach((line, i) => {
g.append("text")
.attr("x", goal.x + 10)
.attr("y", goal.y + 105 + i * 12)
.attr("class", "kpi-text")
.attr("text-anchor", "start")
.text(line);
});
});
// 绘制行动节点
actionNodes.forEach(action => {
const g = svg.append("g").attr("class", "action-group");
g.append("rect")
.attr("x", action.x)
.attr("y", action.y)
.attr("width", action.width)
.attr("height", action.height)
.attr("class", "action-node")
.attr("rx", 3);
// 分行显示文本
const textLines = wrapText(action.text, 15);
textLines.forEach((line, i) => {
g.append("text")
.attr("x", action.x + action.width/2)
.attr("y", action.y + action.height/2 + (i - textLines.length/2 + 0.5) * 12)
.attr("class", "node-text")
.text(line);
});
});
// 绘制连接线
const connections = [
// 从overall goal到三个主要目标的连接
{ from: { x: 580, y: 140 }, to: { x: 310, y: 180 } },
{ from: { x: 580, y: 140 }, to: { x: 630, y: 180 } },
{ from: { x: 580, y: 140 }, to: { x: 950, y: 180 } },
// Goal 1的连接
{ from: { x: 110, y: 320 }, to: { x: 240, y: 340 } },
{ from: { x: 110, y: 400 }, to: { x: 240, y: 380 } },
{ from: { x: 110, y: 480 }, to: { x: 240, y: 460 } },
{ from: { x: 110, y: 560 }, to: { x: 180, y: 640 } },
{ from: { x: 300, y: 640 }, to: { x: 460, y: 580 } },
{ from: { x: 360, y: 340 }, to: { x: 480, y: 320 } },
{ from: { x: 310, y: 260 }, to: { x: 300, y: 320 } },
// Goal 2的连接
{ from: { x: 440, y: 400 }, to: { x: 480, y: 350 } },
{ from: { x: 590, y: 400 }, to: { x: 540, y: 350 } },
{ from: { x: 520, y: 520 }, to: { x: 520, y: 580 } },
{ from: { x: 630, y: 260 }, to: { x: 540, y: 320 } },
// Goal 3的连接
{ from: { x: 760, y: 400 }, to: { x: 780, y: 350 } },
{ from: { x: 910, y: 400 }, to: { x: 910, y: 350 } },
{ from: { x: 1060, y: 400 }, to: { x: 1060, y: 350 } },
{ from: { x: 950, y: 260 }, to: { x: 780, y: 320 } },
{ from: { x: 950, y: 260 }, to: { x: 910, y: 320 } },
{ from: { x: 950, y: 260 }, to: { x: 1060, y: 320 } }
];
connections.forEach(conn => {
svg.append("line")
.attr("class", "arrow")
.attr("x1", conn.from.x)
.attr("y1", conn.from.y)
.attr("x2", conn.to.x)
.attr("y2", conn.to.y);
});
// 从总目标到三个主要目标的向上箭头
svg.append("line")
.attr("class", "arrow")
.attr("x1", 580)
.attr("y1", 100)
.attr("x2", 310)
.attr("y2", 140)
.attr("transform", "rotate(-15 580 100)");
svg.append("line")
.attr("class", "arrow")
.attr("x1", 580)
.attr("y1", 100)
.attr("x2", 630)
.attr("y2", 140);
svg.append("line")
.attr("class", "arrow")
.attr("x1", 580)
.attr("y1", 100)
.attr("x2", 950)
.attr("y2", 140)
.attr("transform", "rotate(15 580 100)");
// 文本换行函数
function wrapText(text, maxLength) {
const words = text.split(' ');
const lines = [];
let currentLine = '';
words.forEach(word => {
if ((currentLine + word).length <= maxLength) {
currentLine += (currentLine ? ' ' : '') + word;
} else {
if (currentLine) lines.push(currentLine);
currentLine = word;
}
});
if (currentLine) lines.push(currentLine);
return lines;
}
// 添加交互性
svg.selectAll(".goal-group, .action-group")
.on("mouseover", function() {
d3.select(this).style("opacity", 0.8);
})
.on("mouseout", function() {
d3.select(this).style("opacity", 1);
})
.on("click", function() {
console.log("节点被点击");
});
</script>
</body>
</html>