如何在D3.js力导向模拟中将节点固定在指定位置?
在D3力导向模拟中固定节点到指定位置的正确方法
你遇到的问题根源在于:即使关闭了forceX/forceY的作用力(strength设为0),你还启用了forceLink链接力,这个力会拉动节点产生位移,所以节点没办法停留在初始位置。
要固定节点到指定位置,最可靠的方式是使用D3力导向模拟的固定位置属性fx和fy——这两个属性会强制节点保持在指定坐标,忽略所有其他力的影响。
具体实现方式
1. 初始化时直接固定所有节点
在初始化节点数据或启动模拟前,给每个节点设置fx和fy为目标坐标:
data.nodes.forEach(d => { d.fx = d.x; // 固定X坐标 d.fy = d.y; // 固定Y坐标 });
同时可以移除不需要的力(比如forceX/forceY,因为已经用fx/fy固定了),简化模拟配置:
var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.name; }).strength(0)) // 也可以关闭链接力,避免额外计算 .alpha(1).restart();
2. 结合拖拽功能:拖拽时固定,结束后保持固定
如果需要拖拽节点后让其保持在新位置,修改你的拖拽事件处理函数:
function dragended(event,d) { if (!event.active) simulation.alphaTarget(.1); // 不要将fx/fy设为null,而是保留当前位置 d.fx = d.x; d.fy = d.y; }
修改后的完整示例代码
drag_nodes() function add_grids(g,width,height) { var m = 10 var n = 10 var xScale = d3.scaleLinear() .domain([0,m]) .range([0, width]) var yScale = d3.scaleLinear() .domain([0,n]) .range([0,height]) var xaxis = g.append('g') .attr('class','xaxis') .call(d3.axisTop(xScale) .tickFormat((d,i) => null) .tickSizeOuter(0) .tickSize(0) ) .call(g => g.select(".domain").remove()) var yaxis = g.append('g') .attr('class','yaxis') .call(d3.axisLeft(yScale) .tickFormat((d,i) => null) .tickSizeOuter(0) .tickSize(0) ) .call(g => g.select(".domain").remove()) g.selectAll("g.yaxis g.tick") .append("line") .attr("class", "gridline") .attr("x1", 0) .attr("y1", 0) .attr("x2", width) .attr("y2", 0) .attr("stroke", "#9ca5aecf") g.selectAll("g.xaxis g.tick") .append("line") .attr("class", "gridline") .attr("x1", 0) .attr("y1", height) .attr("x2", 0) .attr("y2", 0) .attr("stroke", "#9ca5aecf") } function drag_nodes() { var data = { nodes: [{ name: "A", x: 100, y: 100 }, { name: "B", x: 100, y: 400 }, { name: "C", x: 400, y: 400 }, { name: "D", x: 400, y: 100 }], links: [{ source: 'A', target: 'B' }, { source: 'B', target: 'C' }, { source: 'C', target: 'D' }, ] }; // 初始化时固定节点位置 data.nodes.forEach(d => { d.fx = d.x; d.fy = d.y; }); var width = 500 var height = 500 var colors = d3.scaleOrdinal(d3.schemeCategory10); var svg = d3.select("body") .append("svg") .attr("width", width) .attr("height", height) .style('border','2px solid red') add_grids(svg,width,height) var drag = d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended); function dragstarted(event,d) { if (!event.active) simulation.alphaTarget(.1).restart(); d.fx = d.x; d.fy = d.y; } function dragged(event,d) { d.fx = event.x; d.fy = event.y; } function dragended(event,d) { if (!event.active) simulation.alphaTarget(.1); // 保持固定,不重置fx/fy d.fx = d.x; d.fy = d.y; } var link = svg.selectAll("link") .data(data.links) .enter() .append("line") .attr("class", "link") .attr("fill", "none") .attr("stroke", "black"); var node = svg.selectAll("node") .data(data.nodes) .enter() .append("circle") .attr("class", "node") .attr("cx", function(d) { return d.x }) .attr("cy", function(d) { return d.y }) .attr("r", 15) .attr("fill", function(d, i) { return colors(i); }) .call(drag); var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.name; })) // 移除forceX/forceY,因为已经用fx/fy固定位置 .alpha(1).restart(); simulation.nodes(data.nodes) .on("tick", ticked); simulation.force("link") .links(data.links); function ticked() { link.attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) } }
关键说明
fx和fy是D3力导向模拟专门用于固定节点的属性,优先级高于所有其他力。- 如果需要取消固定,只需将
d.fx和d.fy设为null,节点会重新受到模拟力的影响。 - 即使保留
forceLink,只要设置了fx/fy,节点也不会被链接拉动,链接会自适应节点的固定位置。
内容的提问来源于stack exchange,提问作者lucky1928
相关产品推荐
相关产品推荐

