// 定义数据资产路径树的数据结构 const treeData = { name: "数据资产入表", children: [ { name: "数据采集", description: "从各种来源收集原始数据", children: [ { name: "结构化数据", description: "数据库、Excel等格式化数据", children: [ { name: "数据库抽取", description: "使用ETL工具从数据库中抽取数据" }, { name: "文件导入", description: "导入Excel、CSV等文件数据" } ] }, { name: "非结构化数据", description: "文档、图片、视频等非结构化数据", children: [ { name: "文本解析", description: "解析文档、日志等文本数据" }, { name: "媒体处理", description: "处理图片、视频等媒体数据" } ] } ] }, { name: "数据处理", description: "对采集的数据进行清洗和转换", children: [ { name: "数据清洗", description: "去除异常值和重复数据", children: [ { name: "质量检查", description: "检查数据完整性和准确性" }, { name: "数据修复", description: "修复和补充缺失数据" } ] }, { name: "数据转换", description: "转换数据格式和结构", children: [ { name: "格式标准化", description: "统一数据格式和编码" }, { name: "结构转换", description: "调整数据结构以适应目标系统" } ] } ] }, { name: "数据入表", description: "将处理后的数据导入目标系统", children: [ { name: "数据映射", description: "建立源数据和目标表的字段映射", children: [ { name: "字段匹配", description: "确定源字段和目标字段的对应关系" }, { name: "转换规则", description: "定义数据转换和计算规则" } ] }, { name: "数据加载", description: "将数据加载到目标表中", children: [ { name: "增量更新", description: "只更新变化的数据" }, { name: "全量替换", description: "完全替换目标表数据" } ] } ] } ] }; // 定义节点详细信息数据 const nodeDetails = { "数据资产入表": { description: "数据资产入表是将各类数据规范化并导入系统的完整流程", steps: [ "制定数据采集计划", "确定数据处理规则", "设计数据存储结构", "实施数据导入流程" ], notes: [ "确保数据质量和完整性", "注意数据安全和隐私保护", "建立数据版本控制机制" ], docs: [ "《数据资产管理规范》", "《数据质量控制指南》", "《数据安全管理制度》" ] }, "数据采集": { description: "从各种来源收集原始数据的过程", steps: [ "识别数据源", "建立采集通道", "制定采集策略", "执行数据采集" ], notes: [ "确保数据源的可靠性", "建立数据采集的监控机制", "注意采集频率的控制" ], docs: [ "《数据采集规范》", "《数据源接入指南》" ] }, "结构化数据": { description: "处理来自数据库、Excel等格式化数据源的数据", steps: [ "连接数据源", "提取目标数据", "验证数据格式", "临时存储数据" ], notes: [ "检查数据完整性", "处理特殊字符", "注意数据量大小" ], docs: [ "《结构化数据处理指南》", "《数据库连接配置手册》" ] }, // ... 为每个节点添加详细信息 }; // 初始化D3树形图 document.addEventListener('DOMContentLoaded', () => { // 设置SVG尺寸和边距 const margin = {top: 60, right: 120, bottom: 60, left: 120}; const width = 1200 - margin.left - margin.right; const height = 800 - margin.top - margin.bottom; // 创建SVG容器 const svg = d3.select('#techTree') .append('svg') .attr('width', '100%') .attr('height', '100%') .attr('viewBox', `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`) .append('g') .attr('transform', `translate(${margin.left},${margin.top})`); // 创建树形布局 const tree = d3.tree() .size([width, height]) .separation((a, b) => (a.parent == b.parent ? 2 : 3)); // 创建层级数据 const root = d3.hierarchy(treeData); // 计算节点位置 tree(root); // 创建连接线 - 使用贝塞尔曲线 const link = svg.selectAll('.link') .data(root.links()) .enter() .append('path') .attr('class', 'link') .attr('d', d3.linkHorizontal() .x(d => d.y) .y(d => d.x)); // 创建节点组 const node = svg.selectAll('.node') .data(root.descendants()) .enter() .append('g') .attr('class', d => `node${d.children ? ' node--internal' : ' node--leaf'}`) .attr('transform', d => `translate(${d.y},${d.x})`); // 添加节点矩形 node.append('rect') .attr('width', 160) .attr('height', 60) .attr('x', -80) .attr('y', -30) .attr('rx', 8) .attr('ry', 8); // 添加节点文本 node.append('text') .attr('dy', '0.35em') .attr('text-anchor', 'middle') .each(function(d) { const text = d3.select(this); const words = d.data.name.split(''); const lineHeight = 1.1; const maxLength = 8; let line = []; let tspans = []; words.forEach((word, i) => { line.push(word); if (line.length === maxLength || i === words.length - 1) { tspans.push(line.join('')); line = []; } }); tspans.forEach((tspan, i) => { text.append('tspan') .attr('x', 0) .attr('dy', i === 0 ? -0.5 * (tspans.length - 1) * lineHeight + 'em' : lineHeight + 'em') .text(tspan); }); }); // 获取详情面板元素 const detailTitle = document.getElementById('detailTitle'); const detailDescription = document.getElementById('detailDescription'); const detailSteps = document.getElementById('detailSteps'); const detailNotes = document.getElementById('detailNotes'); const detailDocs = document.getElementById('detailDocs'); // 修改节点点击事件 node.on('click', (event, d) => { const details = nodeDetails[d.data.name] || { description: d.data.description || "暂无详细描述", steps: [], notes: [], docs: [] }; // 更新详情面板内容 detailTitle.textContent = d.data.name; detailDescription.textContent = details.description; // 更新步骤列表 detailSteps.innerHTML = details.steps.map(step => `
  • ${step}
  • `).join(''); // 更新注意事项 detailNotes.innerHTML = details.notes.map(note => `
  • ${note}
  • `).join(''); // 更新相关文档 detailDocs.innerHTML = details.docs.map(doc => `
  • ${doc}
  • `).join(''); // 添加active类到当前节点 svg.selectAll('.node').classed('active', false); d3.select(event.currentTarget).classed('active', true); }); // 添加缩放功能 const zoom = d3.zoom() .scaleExtent([0.5, 2]) .on('zoom', (event) => { svg.attr('transform', event.transform); }); // 初始化缩放 d3.select('#techTree svg') .call(zoom) .call(zoom.transform, d3.zoomIdentity.translate(margin.left, margin.top)); // 绑定按钮事件 document.getElementById('zoomIn').addEventListener('click', () => { d3.select('#techTree svg').transition() .call(zoom.scaleBy, 1.2); }); document.getElementById('zoomOut').addEventListener('click', () => { d3.select('#techTree svg').transition() .call(zoom.scaleBy, 0.8); }); document.getElementById('resetView').addEventListener('click', () => { d3.select('#techTree svg').transition() .call(zoom.transform, d3.zoomIdentity.translate(margin.left, margin.top)); }); });