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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 00:54:55