389 lines
14 KiB
HTML
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> |