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); }); }); });