d3-force - 节点被拖离链接,而不是 运行

d3-force - Nodes are dragged away from links, ticks not run

我需要制作一个具有可拖动功能的 "sticky force layout",并尝试在 this link 复制 Mike Bostock 的示例。因为作者在D3 v3中写了那个程序,所以我必须"upgrade"他的代码到D3v4。这意味着必须相应地更改 d3-force 和 d3-drag 语句。虽然原始示例中的逻辑流程很容易理解,但我仍然无法制作自己的版本(请参见下面的代码)。问题:经过一定时间(2 - 3 秒)后,节点被拖走但链接没有更新

我最初的想法主要集中在拖动函数 (.call(drag)) 上,但后来我发现 "tick" 函数在 运行 之后不再 运行上面提到的时间量(我通过在 tick 函数中放置一个计数变量,然后 console.log 得到它)。至此,我的脑子里一片空白,无法进一步探索。

问题出在哪里?

var width = 960,
    height = 500;

var simulation = d3.forceSimulation()
    .force("charge", d3.forceManyBody().strength(-100))


d3.forceX(width)
d3.forceY(height)

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

var drag = d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended);
var link = svg.selectAll(".link")
var node = svg.selectAll(".node")

d3.json("graph.json", function (error, graph) {
    if (error) throw error;

    simulation.nodes(graph.nodes)
            .force("link", d3.forceLink(graph.links))
            .on("tick", tick);

    link = link.data(graph.links)
        .enter().append("line")
        .attr("class", "link");

    node = node.data(graph.nodes)
        .enter().append("circle")
        .attr("class", "node")
        .attr("r", 12)
        .on("dblclick", dblclick)
        .call(drag)

});

var count = 0;

function tick() {
    count++;
    console.log(count);
    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; })
}

function dblclick(d) {
    d3.select(this).classed("fixed", d.fixed = false);
    // console.log("Is clicking");
}


function dragstarted(d) {
    d3.select(this).classed("fixed", d.fixed = true);
    console.log("Is dragging");
}

function dragged(d) {
    d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}

function dragended(d) {
    d3.select(this).classed("active", false);
}

在这个力导向图中有两件事导致了问题:

  1. d3v4 不使用d.fixed修复节点
  2. d3v4 没有与 d3v3 相同的默认 alpha 衰减

1:修复节点

虽然您没有明确指出这一点,但在 2-3 秒内图形更新 link 数据,节点在拖动后仍然移动。

这是因为,与 d3v3 不同,您需要使用 d.fxd.fy 手动固定坐标的 x 和 y 值; d.fixed 不再修复您的节点。对于您的代码,这看起来像:

function dragended(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dblclick(d) {
      d.fx = null;
      d.fy = null;
}

2:设置 alpha 衰减

这导致您的图表冻结。在图形由于 alpha 衰减而稳定后,不再调用 tick 函数。此时,您的拖动发生时没有对 link 进行任何更新,导致节点与 link.

之间的连接断开

在 d3v3 中,默认情况下 alpha 衰减是未定义的,d3 可能 退回到不计算 alpha 衰减或使用零作为 alpha 衰减因子(导致相同结果)。在 d3v4 中,alpha 衰减设置为非零值:

If decay is specified, sets the alpha decay rate to the specified number in the range [0,1] and returns this simulation. If decay is not specified, returns the current alpha decay rate, which defaults to 0.0228… = 1 - pow(0.001, 1 / 300) where 0.001 is the default minimum alpha.

The alpha decay rate determines how quickly the current alpha interpolates towards the desired target alpha; since the default target alpha is zero, by default this controls how quickly the simulation cools. Higher decay rates cause the simulation to stabilize more quickly, but risk getting stuck in a local minimum; lower values cause the simulation to take longer to run, but typically converge on a better layout. To have the simulation run forever at the current alpha, set the decay rate to zero; alternatively, set a target alpha greater than the minimum alpha.

因此,您可以使用:

var simulation = d3.forceSimulation()
    .force("charge", d3.forceManyBody().strength(-100))
    .alphaDecay(0);

(或者,将 alphaTarget 设置为适当的值,如上述文档引用中所述)。

总的来说,这看起来像 this