如何修改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
相关产品推荐
相关产品推荐

