基于含多行多边CSV构建D3(v3) Force Layout的技术咨询
没问题,我来帮你搞定用D3 v3处理这种多重复边CSV构建力导向图的事儿!
处理多重复边CSV构建D3(v3) Force Layout
第一步:先理清你的数据逻辑
看你给的CSV示例,每一行其实对应一条重复的连接关系(比如同一个蛋白质节点和基因节点之间有多条记录)。我们要先把数据整理成D3力导向图需要的标准格式:唯一的节点列表,以及清晰的边列表(可以选择合并重复边统计次数,或者保留所有原始边)。
你的核心连接关系应该是mt.entry(比如A0A059UE90)和tt.gene_name(比如TcCLB.507993.80)之间的关联,对吧?其他字段可以作为节点或边的附加属性来用。
第二步:数据预处理(这是关键!)
先用d3.csv加载数据,然后把它转换成nodes和links格式:
- Nodes: 收集所有唯一的蛋白质和基因节点,带上对应的名称信息
- Links: 这里我做了重复边合并(统计连接次数),如果你想保留所有原始边也可以调整逻辑
代码示例:数据预处理
d3.csv("your-data-file.csv", function(error, rawData) { if (error) throw error; // 1. 收集所有唯一节点,用Map避免重复 let nodeMap = {}; rawData.forEach(row => { // 处理蛋白质节点(mt.entry) if (!nodeMap[row["mt.entry"]]) { nodeMap[row["mt.entry"]] = { id: row["mt.entry"], name: row["mt.protein_names"], type: "protein" // 标记节点类型,方便后续区分样式 }; } // 处理基因节点(tt.gene_name) if (!nodeMap[row["tt.gene_name"]]) { nodeMap[row["tt.gene_name"]] = { id: row["tt.gene_name"], name: row["tt.gene_product"], type: "gene" }; } }); // 把Map转成D3需要的数组格式 const nodes = d3.values(nodeMap); // 2. 处理边:合并重复边并统计次数 let linkMap = {}; rawData.forEach(row => { // 用"源节点|目标节点"作为唯一键,避免重复边 const linkKey = `${row["mt.entry"]}|${row["tt.gene_name"]}`; if (!linkMap[linkKey]) { linkMap[linkKey] = { source: row["mt.entry"], target: row["tt.gene_name"], count: 1, // 初始计数1 clusterId: row["omcl.omcl_clusterid"] }; } else { linkMap[linkKey].count++; // 重复边计数加1 } }); const links = d3.values(linkMap); // 把links里的source/target转成节点对象(D3 v3要求这样关联) links.forEach(link => { link.source = nodeMap[link.source]; link.target = nodeMap[link.target]; }); // 数据整理完成,开始构建力导向图 renderForceGraph(nodes, links); });
第三步:构建D3(v3)力导向图
现在有了整理好的nodes和links,就可以实现力导向图了:
代码示例:力导向图渲染
function renderForceGraph(nodes, links) { const width = 960; const height = 600; // 创建SVG容器 const svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); // 初始化力导向图布局 const force = d3.layout.force() .nodes(nodes) .links(links) .size([width, height]) .linkDistance(150) // 边的默认长度 .charge(-400) // 节点之间的排斥力,数值越小排斥越强 .on("tick", updateGraph) // 每次力模拟更新时触发的回调 .start(); // 绘制边 const link = svg.selectAll(".link") .data(links) .enter().append("line") .attr("class", "link") // 用重复次数控制边的粗细 .style("stroke-width", d => Math.sqrt(d.count) * 2); // 绘制节点 const node = svg.selectAll(".node") .data(nodes) .enter().append("circle") .attr("class", "node") .attr("r", 10) // 节点大小 // 按节点类型设置不同颜色 .style("fill", d => d.type === "protein" ? "#3366cc" : "#ff6600") .call(force.drag); // 允许拖拽节点 // 给节点添加悬浮提示 node.append("title") .text(d => d.name); // 力模拟更新时,同步更新节点和边的位置 function updateGraph() { link.attr("x1", d => d.source.x) .attr("y1", d => d.source.y) .attr("x2", d => d.target.x) .attr("y2", d => d.target.y); node.attr("cx", d => d.x) .attr("cy", d => d.y); } }
几个实用的小调整建议
- 保留原始边: 如果你不需要合并重复边,直接把
rawData的每一行转成links即可,但记得要保证节点是唯一的,不然会出现大量重复节点。 - 边的集群样式: 可以用
omcl.omcl_clusterid给不同集群的边设置不同颜色,比如:.style("stroke", d => d.clusterId === "OG5_126888" ? "#999" : "#333") - D3版本注意: 这段代码完全针对D3 v3编写,和v4/v5的API差异很大,别搞错版本哦!
内容的提问来源于stack exchange,提问作者Leitouran
相关产品推荐
相关产品推荐

