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

如何修改D3多力导向布局实现节点按网格分组?

嘿,刚上手D3碰到没注释的示例确实头大,我来一步步帮你搞定这个按网格分组的力导向布局!

实现按Category1/Category2网格分组的D3力导向布局

第一步:先修正并解析你的CSV数据

首先你的CSV格式有点小问题,正确的换行和分隔应该是这样:

Name,Category1,Category2
1,1,1
2,1,2
3,1,1
4,2,2
5,3,1
6,1,4
7,5,5
8,1,5
9,2,4
10,3,3
11,4,4
12,4,5
13,3,4
14,1,2
15,1,1
16,2,2
17,3,1
18,2,1
19,4,5
20,3,1

加载数据时要把Category1和Category2转成数字(默认解析是字符串):

d3.csv("你的数据文件路径.csv", function(d) {
  return {
    name: d.Name,
    cat1: +d.Category1, // 加号把字符串转成数字
    cat2: +d.Category2
  };
}).then(function(data) {
  // 所有后续代码都写在这个回调里
});

第二步:计算网格的基础参数

先确定网格的行列数量和每个单元的大小,比如我们给每个网格单元设置150x150的尺寸:

// 获取分类的最大值,确定网格的行列数
const maxCol = d3.max(data, d => d.cat1);
const maxRow = d3.max(data, d => d.cat2);
const cellWidth = 150; // 单个网格单元的宽度
const cellHeight = 150; // 单个网格单元的高度

第三步:修改力导向布局的核心规则

原示例是让所有节点往画布中心聚集,我们要改成让每个节点往对应网格单元的中心聚集,用D3的forceX和forceY来实现:

// 创建力导向模拟
const simulation = d3.forceSimulation(data)
  // 如果原示例有连线需求就保留,没有可以删掉这一行
  .force("link", d3.forceLink().id(d => d.name))
  // 节点之间的排斥力,数值可以根据需求调整(绝对值越大排斥越强)
  .force("charge", d3.forceManyBody().strength(-30))
  // 让节点往对应列的单元中心移动:(列号-1)转成0索引,乘以宽度加半宽得到中心X
  .force("x", d3.forceX(d => (d.cat1 - 1) * cellWidth + cellWidth/2))
  // 让节点往对应行的单元中心移动,逻辑和X轴一致
  .force("y", d3.forceY(d => (d.cat2 - 1) * cellHeight + cellHeight/2))
  // 防止节点重叠,半径根据你的节点大小调整
  .force("collision", d3.forceCollide().radius(10));

第四步:优化初始位置(可选但推荐)

为了让布局更快收敛,给每个节点设置接近目标网格的初始位置,加一点随机偏移避免完全重叠:

data.forEach(d => {
  d.x = (d.cat1 - 1) * cellWidth + cellWidth/2 + Math.random()*20;
  d.y = (d.cat2 - 1) * cellHeight + cellHeight/2 + Math.random()*20;
});

第五步:更新节点渲染逻辑

把原示例里的tick事件回调改成这样,确保节点跟随力导向布局更新位置:

simulation.on("tick", () => {
  // 更新节点位置
  node.attr("cx", d => d.x)
      .attr("cy", d => d.y);
  // 如果有连线的话,同步更新连线位置
  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);
});

额外加分:给网格加背景(可选)

如果想让分组更直观,可以给每个网格单元添加浅色背景:

// 生成所有网格单元的数据
const gridData = d3.range(maxRow).flatMap(y => 
  d3.range(maxCol).map(x => ({x, y}))
);

// 绘制网格背景
svg.selectAll(".grid-cell")
  .data(gridData)
  .enter().append("rect")
  .attr("class", "grid-cell")
  .attr("x", d => d.x * cellWidth)
  .attr("y", d => d.y * cellHeight)
  .attr("width", cellWidth)
  .attr("height", cellHeight)
  .style("fill", "#f5f5f5")
  .style("stroke", "#ddd");

替换原示例的关键部分

你只需要把原示例里的力导向模拟(d3.forceSimulation相关)代码替换成上面的simulation部分,再把数据加载逻辑换成我们处理CSV的代码,就能实现按网格分组的效果了。

内容的提问来源于stack exchange,提问作者LBes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:38:11