init
This commit is contained in:
16
test1/index.html
Normal file
16
test1/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!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 Strategy</title>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="chart-container">
|
||||
<!-- 图表将通过D3.js渲染到此处 -->
|
||||
</div>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
239
test1/script.js
Normal file
239
test1/script.js
Normal file
@@ -0,0 +1,239 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const width = document.getElementById('chart-container').clientWidth;
|
||||
const height = document.getElementById('chart-container').clientHeight;
|
||||
|
||||
const svg = d3.select("#chart-container")
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
// 添加箭头标记定义
|
||||
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 nodes = [
|
||||
// 目标节点
|
||||
{id: "goal", label: "OVERALL GOAL\nUS critical minerals security", x: width/2, y: 70, width: 300, height: 80, type: "goal"},
|
||||
|
||||
// 三大核心策略节点
|
||||
{id: "strategy1", label: "1", x: 160, y: 180, width: 240, height: 100, type: "primary"},
|
||||
{id: "strategy1_text", label: "Rebuild US critical minerals\nindustry competitiveness", x: 160, y: 175, width: 200, height: 30, type: "label"},
|
||||
{id: "kpi1", label: "KPIs: Average time from discovery to construction; no.\nof workers in CM industry; no. of projects in pipeline", x: 160, y: 220, width: 240, height: 40, type: "kpi"},
|
||||
|
||||
{id: "strategy2", label: "2", x: 480, y: 180, width: 240, height: 100, type: "primary"},
|
||||
{id: "strategy2_text", label: "Reduce import reliance from\nnon-allied countries", x: 480, y: 175, width: 200, height: 30, type: "label"},
|
||||
{id: "kpi2", label: "KPIs: Percentage of US critical minerals imported from\nor processed by non-allied countries", x: 480, y: 220, width: 240, height: 40, type: "kpi"},
|
||||
|
||||
{id: "strategy3", label: "3", x: 800, y: 180, width: 240, height: 100, type: "primary"},
|
||||
{id: "strategy3_text", label: "Reduce the risk of critical minerals\nmarket disruptions", x: 800, y: 175, width: 200, height: 30, type: "label"},
|
||||
{id: "kpi3", label: "KPIs: No. of months of critical mineral reserves;\npercentage of value chain data collected", x: 800, y: 220, width: 240, height: 40, type: "kpi"},
|
||||
|
||||
// 二级节点
|
||||
{id: "1e", label: "(1e) Develop\ncritical minerals\nworkforce", x: 60, y: 320, width: 100, height: 80, type: "grey"},
|
||||
{id: "1d", label: "(1d) Increase\ninvestment in\nR&D for new\ntech and AI\nusage", x: 60, y: 440, width: 100, height: 80, type: "grey"},
|
||||
{id: "1c", label: "(1c) Improve\npermitting and\nregulations", x: 60, y: 560, width: 100, height: 80, type: "grey"},
|
||||
{id: "1b", label: "(1b)/(2d)\nSupport\nsecondary\nprocessing\nexpansion", x: 260, y: 440, width: 100, height: 80, type: "grey"},
|
||||
|
||||
{id: "domestic_prod", label: "Increase\ndomestic\nproduction", x: 260, y: 320, width: 100, height: 80, type: "secondary"},
|
||||
{id: "allied_prod", label: "Increase allied\nproduction", x: 480, y: 320, width: 100, height: 80, type: "secondary"},
|
||||
|
||||
{id: "2a", label: "(2a) Expand the\nrole and use of\nexisting\nmultilateral\narrangements", x: 380, y: 440, width: 100, height: 80, type: "grey"},
|
||||
|
||||
{id: "2b", label: "(2b) Expand US\ngovernment\nfinancing tools", x: 580, y: 440, width: 100, height: 80, type: "grey"},
|
||||
|
||||
{id: "1a", label: "(1a) Increase\ndomestic\nfinancing and\nincentives", x: 260, y: 560, width: 100, height: 80, type: "grey"},
|
||||
|
||||
{id: "allied_pipeline", label: "Expand allied\ncritical minerals\nproject pipeline", x: 480, y: 560, width: 100, height: 80, type: "secondary"},
|
||||
|
||||
{id: "2c", label: "(2c) Support\nexploration and\ndata initiatives", x: 480, y: 680, width: 100, height: 80, type: "grey"},
|
||||
|
||||
{id: "3a", label: "(3a) Develop a\nreal-time value\nchain mapping\nand monitoring\nsystem", x: 690, y: 440, width: 100, height: 80, type: "grey"},
|
||||
{id: "3b", label: "(3b) Expand\npurpose and\nuse of National\nDefense\nStockpile", x: 790, y: 440, width: 100, height: 80, type: "grey"},
|
||||
{id: "3c", label: "(3c) Identify and\ndeploy targeted\nmarket\nincentives", x: 890, y: 440, width: 100, height: 80, type: "grey"},
|
||||
|
||||
{id: "value_chain", label: "Monitor critical\nminerals value\nchain\nvulnerabilities", x: 690, y: 320, width: 100, height: 80, type: "secondary"},
|
||||
{id: "supply_risk", label: "Reduce supply\ndisruption risks", x: 790, y: 320, width: 100, height: 80, type: "secondary"},
|
||||
{id: "price_risk", label: "Reduce price\ndisruption risks", x: 890, y: 320, width: 100, height: 80, type: "secondary"},
|
||||
|
||||
{id: "1d_pipeline", label: "Expand US\ncritical minerals\nproject pipeline", x: 160, y: 680, width: 100, height: 80, type: "secondary"}
|
||||
];
|
||||
|
||||
// 定义连接数据
|
||||
const links = [
|
||||
{source: "goal", target: "strategy1"},
|
||||
{source: "goal", target: "strategy2"},
|
||||
{source: "goal", target: "strategy3"},
|
||||
|
||||
{source: "1e", target: "domestic_prod"},
|
||||
{source: "domestic_prod", target: "strategy1"},
|
||||
|
||||
{source: "1d", target: "1e"},
|
||||
|
||||
{source: "1c", target: "1b"},
|
||||
{source: "1b", target: "domestic_prod"},
|
||||
|
||||
{source: "domestic_prod", target: "allied_prod"},
|
||||
{source: "allied_prod", target: "strategy2"},
|
||||
|
||||
{source: "2a", target: "allied_prod"},
|
||||
{source: "2a", target: "2b"},
|
||||
{source: "2b", target: "2a"},
|
||||
|
||||
{source: "1a", target: "1b"},
|
||||
{source: "1a", target: "allied_pipeline"},
|
||||
{source: "allied_pipeline", target: "allied_prod"},
|
||||
|
||||
{source: "2c", target: "allied_pipeline"},
|
||||
|
||||
{source: "1d", target: "1d_pipeline"},
|
||||
{source: "1d_pipeline", target: "1c"},
|
||||
|
||||
{source: "3a", target: "value_chain"},
|
||||
{source: "value_chain", target: "strategy3"},
|
||||
|
||||
{source: "3b", target: "supply_risk"},
|
||||
{source: "supply_risk", target: "strategy3"},
|
||||
|
||||
{source: "3c", target: "price_risk"},
|
||||
{source: "price_risk", target: "strategy3"}
|
||||
];
|
||||
|
||||
// 绘制连接线
|
||||
svg.selectAll("path.link")
|
||||
.data(links)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "link")
|
||||
.attr("d", function(d) {
|
||||
const source = nodes.find(n => n.id === d.source);
|
||||
const target = nodes.find(n => n.id === d.target);
|
||||
|
||||
// 确定连接点位置
|
||||
let sourceX, sourceY, targetX, targetY;
|
||||
|
||||
if (source.y < target.y) {
|
||||
sourceX = source.x;
|
||||
sourceY = source.y + source.height/2;
|
||||
targetX = target.x;
|
||||
targetY = target.y - target.height/2;
|
||||
} else if (source.y > target.y) {
|
||||
sourceX = source.x;
|
||||
sourceY = source.y - source.height/2;
|
||||
targetX = target.x;
|
||||
targetY = target.y + target.height/2;
|
||||
} else if (source.x < target.x) {
|
||||
sourceX = source.x + source.width/2;
|
||||
sourceY = source.y;
|
||||
targetX = target.x - target.width/2;
|
||||
targetY = target.y;
|
||||
} else {
|
||||
sourceX = source.x - source.width/2;
|
||||
sourceY = source.y;
|
||||
targetX = target.x + target.width/2;
|
||||
targetY = target.y;
|
||||
}
|
||||
|
||||
// 创建路径
|
||||
return `M${sourceX},${sourceY}L${targetX},${targetY}`;
|
||||
})
|
||||
.attr("marker-end", "url(#arrowhead)");
|
||||
|
||||
// 绘制节点
|
||||
const nodeGroups = svg.selectAll("g.node")
|
||||
.data(nodes.filter(n => !n.id.includes("_text") && !n.id.includes("kpi")))
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", function(d) { return "node " + d.type; });
|
||||
|
||||
// 添加矩形
|
||||
nodeGroups.append("rect")
|
||||
.attr("x", d => d.x - d.width/2)
|
||||
.attr("y", d => d.y - d.height/2)
|
||||
.attr("width", d => d.width)
|
||||
.attr("height", d => d.height)
|
||||
.attr("rx", 4)
|
||||
.attr("ry", 4);
|
||||
|
||||
// 添加标签文本
|
||||
nodeGroups.append("text")
|
||||
.attr("x", d => d.x)
|
||||
.attr("y", d => d.y)
|
||||
.attr("dy", ".35em")
|
||||
.attr("text-anchor", "middle")
|
||||
.text(d => d.label)
|
||||
.each(function(d) {
|
||||
// 处理多行文本
|
||||
const text = d3.select(this);
|
||||
const words = d.label.split('\n');
|
||||
|
||||
text.text(null);
|
||||
|
||||
words.forEach((word, i) => {
|
||||
text.append("tspan")
|
||||
.attr("x", d.x)
|
||||
.attr("dy", i ? "1.2em" : "0")
|
||||
.text(word);
|
||||
});
|
||||
});
|
||||
|
||||
// 添加策略标题文本
|
||||
svg.selectAll("text.strategy-text")
|
||||
.data(nodes.filter(n => n.id.includes("_text")))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("class", "strategy-text")
|
||||
.attr("x", d => d.x)
|
||||
.attr("y", d => d.y)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "white")
|
||||
.each(function(d) {
|
||||
// 处理多行文本
|
||||
const text = d3.select(this);
|
||||
const words = d.label.split('\n');
|
||||
|
||||
text.text(null);
|
||||
|
||||
words.forEach((word, i) => {
|
||||
text.append("tspan")
|
||||
.attr("x", d.x)
|
||||
.attr("dy", i ? "1.2em" : "0")
|
||||
.text(word);
|
||||
});
|
||||
});
|
||||
|
||||
// 添加KPI文本
|
||||
svg.selectAll("text.kpi")
|
||||
.data(nodes.filter(n => n.id.includes("kpi")))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("class", "kpi")
|
||||
.attr("x", d => d.x)
|
||||
.attr("y", d => d.y)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "white")
|
||||
.attr("font-size", "10px")
|
||||
.each(function(d) {
|
||||
// 处理多行文本
|
||||
const text = d3.select(this);
|
||||
const words = d.label.split('\n');
|
||||
|
||||
text.text(null);
|
||||
|
||||
words.forEach((word, i) => {
|
||||
text.append("tspan")
|
||||
.attr("x", d.x)
|
||||
.attr("dy", i ? "1.2em" : "0")
|
||||
.text(word);
|
||||
});
|
||||
});
|
||||
});
|
||||
59
test1/style.css
Normal file
59
test1/style.css
Normal file
@@ -0,0 +1,59 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#chart-container {
|
||||
width: 100%;
|
||||
height: 800px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.node {
|
||||
fill: #ccc;
|
||||
stroke: #fff;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.node.goal {
|
||||
fill: #8BC34A; /* 绿色背景,对应顶部框 */
|
||||
}
|
||||
|
||||
.node.primary {
|
||||
fill: #000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.node.secondary {
|
||||
fill: #f0f0f0;
|
||||
}
|
||||
|
||||
.node.grey {
|
||||
fill: #ccc;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 12px;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: middle;
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.node.primary text {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.link {
|
||||
fill: none;
|
||||
stroke: #666;
|
||||
stroke-width: 1.5px;
|
||||
marker-end: url(#arrowhead);
|
||||
}
|
||||
|
||||
.number-circle {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
fill: white;
|
||||
}
|
||||
Reference in New Issue
Block a user