You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于含多行多边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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:27:36