将 d3.layout.force v3 更新为 d3.forceSimulation v7

Kev*_*vin 7 javascript d3.js

我正在尝试将使用 d3js 版本 3 编写的力导向图更新为 d3js 版本 7。

以下代码片段是使用 d3js v3 的工作实现:

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

graph = {
  nodes: [],
  links: [],
}

var simulation = d3.layout.force()
  .size([width, height])
  .nodes(graph.nodes)
  .links(graph.links)
  .on("tick", function() {
    svg.selectAll('.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 })
      
    svg.selectAll('.node')
      .attr("cx", function (d) { return d.x })
      .attr("cy", function (d) { return d.y })
      .attr("transform", function (d) {
        return "translate(" + d.x + "," + d.y + ")";
      })
  });
  
function update() {
  // update links
  var link = svg.selectAll('.link').data(graph.links);
  link.enter()
    .insert('line', '.node')
    .attr('class', 'link')
    .style('stroke', '#d9d9d9');
  link
    .exit()
    .remove()
    
  // update nodes
  var node = svg.selectAll('.node').data(graph.nodes);
  var g = node.enter()
            .append('g')
            .attr('class', 'node');
  g.append('circle')
    .attr("r", 20)
    .style("fill", "#d9d9d9");
  g.append('text')
    .attr("class", "text")
    .text(function (d) { return d.name });
  node
    .exit()
    .remove();
    
  // update simulation
  simulation
    .linkDistance(100)
    .charge(-200)
    .start();
};

function addNode(node) {
  graph.nodes.push(node);
  update();
};

function connectNodes(source, target) {
  graph.links.push({
    source: source,
    target: target,
  });
  update();
};

addNode({
  id: "you",
  name: "you",
});

let index = 1;

// add a new node every three seconds and connect to 'you'
const interval = window.setInterval(() => {
  let id = Math.random().toString(36).replace('0.','');
  id = id.slice(0,4);
  addNode({
      id: id,
      name: id
  });
  
  connectNodes(0, index);
  index++;
}, 3000);

// no more than 8 nodes
setTimeout(() => {
  clearInterval(interval)
}, 3000 * 8);
Run Code Online (Sandbox Code Playgroud)
<html>
<head>
  <script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
  <svg width="400" height="200"></svg>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

以下代码片段是我尝试使用 d3js v7 实现上述代码片段:

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

graph = {
  nodes: [],
  links: [],
}

var simulation = d3.forceSimulation()
  .force("center", d3.forceCenter(width / 2, height / 2).strength(0.01))
  .nodes(graph.nodes)
  .force("link", d3.forceLink(graph.links).distance(100))
  .on("tick", function() {
    svg.selectAll('.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 })
      
    svg.selectAll('.node')
      .attr("cx", function (d) { return d.x })
      .attr("cy", function (d) { return d.y })
      .attr("transform", function (d) {
        return "translate(" + d.x + "," + d.y + ")";
      })
  });
  
function update() {
  // update links
  var link = svg.selectAll('.link').data(graph.links);
  link.enter()
    .insert('line', '.node')
    .attr('class', 'link')
    .style('stroke', '#d9d9d9');
  link
    .exit()
    .remove()
    
  // update nodes
  var node = svg.selectAll('.node').data(graph.nodes);
  var g = node.enter()
            .append('g')
            .attr('class', 'node');
  g.append('circle')
    .attr("r", 20)
    .style("fill", "#d9d9d9");
  g.append('text')
    .attr("class", "text")
    .text(function (d) { return d.name });
  node
    .exit()
    .remove();
    
  // update simulation
  simulation
    .nodes(graph.nodes)
    .force("link", d3.forceLink(graph.links).distance(100))
    .force("charge", d3.forceManyBody().strength(-200))
    .restart()
};

function addNode(node) {
  graph.nodes.push(node);
  update();
};

function connectNodes(source, target) {
  graph.links.push({
    source: source,
    target: target,
  });
  update();
};

addNode({
  id: "you",
  name: "you",
});

let index = 1;

// add a new node every three seconds and connect to 'you'
const interval = window.setInterval(() => {
  let id = Math.random().toString(36).replace('0.','');
  id = id.slice(0,4);
  addNode({
      id: id,
      name: id
  });
  
  connectNodes(0, index);
  index++;
}, 3000);

// no more than 8 nodes
setTimeout(() => {
  clearInterval(interval)
}, 3000 * 8);
Run Code Online (Sandbox Code Playgroud)
<html>
<head>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
  <svg width="400" height="200"></svg>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

d3js v7 代码片段不会产生与 d3js v3 相同的结果 - 这是为什么?我所做的确切更改可以在此差异中看到: https: //www.diffchecker.com/wdq7AFbU

即使不添加任何连接,两种实现之间也存在差异。v3 实现使“you”节点从随机方向飞入,而使用 v7 实现时“you”节点始终从同一方向飞入。

由于 v7 实现中的新节点卡在左上角,因此力的施加方式似乎也存在一些差异。

sun*_*eol 2

我注意到 DOM 的属性正确地反映了状态。只是模拟提前停止了。

\n

简而言之, 的默认值对于预期结果来说太短d3.force.alphaDecay了;指示模拟的结束。尝试稍微扩大该值。根据d3-force github readme ,最新的默认值为0.001 。在我的测试会话中,将值设置为alphaDecayalphaDecay1/5(0.0002)似乎足以获得相同的结果。

\n

尝试运行下面的代码。效果很好。

\n

尖端

\n

使用 DOM 和 SVG 时,尝试添加匹配的data-ooo标签以查看是否d3.selection正常工作。我已将节点数据的属性(例如.index和 ).target添加到.source属性(例如data-index,data-id,data-target,data-source...和 )中,注意到一切都已就位。

\n

\r\n
\r\n
var svg = d3.select("svg"),\n  width = +svg.attr("width"),\n  height = +svg.attr("height");\n\ngraph = {\n  nodes: [],\n  links: [],\n}\n\nvar simulation = d3.forceSimulation()\n  .force("center", d3.forceCenter(width / 2, height / 2).strength(0.01))\n  .nodes(graph.nodes)\n  .force("link", d3.forceLink(graph.links).distance(100))\n  .on("tick", function() {\n    svg.selectAll(\'.link\')\n      .attr("x1", function (d) { return d.source.x })\n      .attr("y1", function (d) { return d.source.y })\n      .attr("x2", function (d) { return d.target.x })\n      .attr("y2", function (d) { return d.target.y })\n      \n    svg.selectAll(\'.node\')\n      .attr("cx", function (d) { return d.x })\n      .attr("cy", function (d) { return d.y })\n      .attr("transform", function (d) {\n        return "translate(" + d.x + "," + d.y + ")";\n      })\n  }).alphaDecay(0.0002) // just added alpha decay to delay end of execution\n  \nfunction update() {\n  // update links\n  var link = svg.selectAll(\'.link\').data(graph.links);\n  link.enter()\n    .insert(\'line\', \'.node\')\n    .attr(\'class\', \'link\')\n    .style(\'stroke\', \'#d9d9d9\');\n  link\n    .exit()\n    .remove()\n    \n  // update nodes\n  var node = svg.selectAll(\'.node\').data(graph.nodes);\n  var g = node.enter()\n            .append(\'g\')\n            .attr(\'class\', \'node\');\n  g.append(\'circle\')\n    .attr("r", 20)\n    .style("fill", "#d9d9d9");\n  g.append(\'text\')\n    .attr("class", "text")\n    .text(function (d) { return d.name });\n  node\n    .exit()\n    .remove();\n    \n  // update simulation\n  simulation\n    .nodes(graph.nodes)\n    .force("link", d3.forceLink(graph.links).distance(100))\n    .force("charge", d3.forceManyBody().strength(-200))\n    .restart()\n};\n\nfunction addNode(node) {\n  graph.nodes.push(node);\n  update();\n};\n\nfunction connectNodes(source, target) {\n  graph.links.push({\n    source: source,\n    target: target,\n  });\n  update();\n};\n\naddNode({\n  id: "you",\n  name: "you",\n});\n\nlet index = 1;\n\n// add a new node every three seconds and connect to \'you\'\nconst interval = window.setInterval(() => {\n  let id = Math.random().toString(36).replace(\'0.\',\'\');\n  id = id.slice(0,4);\n  addNode({\n      id: id,\n      name: id\n  });\n  \n  connectNodes(0, index);\n  index++;\n}, 3000);\n\n// no more than 8 nodes\nsetTimeout(() => {\n  clearInterval(interval)\n}, 3000 * 8);
Run Code Online (Sandbox Code Playgroud)\r\n
<html>\n<head>\n  <script src="https://d3js.org/d3.v7.min.js"></script>\n</head>\n<body>\n  <svg width="400" height="200"></svg>\n</body>\n</html>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

编辑:什么是alphaalphaDecay

\n
\n

执行simulation.restart().alpha(0.3)似乎给出了与我之前的帖子的答案中提到的相同的效果。两者有什么区别吗?

\n
\n

d3-force github 自述文件说alpha代表熵。简单来说,alpha代表模拟的生命周期;alpha=1 代表开始,alpha=0 代表结束。

\n
\n

https://github.com/d3/d3-force#simulation_alpha

\n

α 大致类似于模拟退火中的温度。随着模拟 \xe2\x80\x9c 冷却\xe2\x80\x9d,它会随着时间的推移而减小。当alpha达到alphaMin时,模拟停止

\n
\n

这是一个简单的伪代码来说明这个想法。

\n
alpha = 1\nalphaDecay = 0.002\nfunction tick() {\n  alpha = alpha - alphaDecay\n}\n\nloop {\n  tick()\n  if alpha equals to 0 then end simulation\n}\n
Run Code Online (Sandbox Code Playgroud)\n

评论中提到的先前答案在重新启动时增加了alpha,因为他想在重置后给模拟更多的时间。

\n

在我的回答中,我将alphaDecay设置为较低的数字,以便模拟可以工作更长的时间。

\n
    \n
  • 增加 alphaDecay/减少 alpha = 模拟结束得更快
  • \n
  • 减少 alphaDecay/增加 alpha = 模拟稍后结束
  • \n
\n

编辑:自 D3 v4 以来 d3-force 有何变化?

\n
\n

另外,v3 和 v7 的实现仍然存在一些差异;1) v3 中的碰撞更有弹性,2) 添加的新节点来自随机方向。您知道在 v7 实现中可以修复哪些问题来获得 1) 和 2) 吗?

\n
\n

请阅读此d3-force v1 github 变更日志;自 d3 v4 以来,d3-force 成为一个单独的软件包,此更改日志解释了这些更改。

\n

1. d3-force变得更加准确。

\n

变更日志提到了许多改进:

\n
\n

力模拟现在使用速度 Verlet 积分而不是位置 Verlet,跟踪节点\xe2\x80\x99 位置(node.x、node.y)和速度(node.vx、node.vy)而不是它们之前的位置(node .px、node.py)。\n新的链接力取代了force.linkStrength,并采用更好的默认启发式方法来提高稳定性。

\n
\n

d3-force 的物理集成已得到改进,精度更高。这就是它看起来与 v3 实现不同的原因。

\n

虽然可以以特定方式调整模拟外观,但这more elastic意味着什么呢?是否意味着反作用力更强?或者这是否意味着更快的动画(但在相同的时间内)?只要请求更详细,它肯定可以调整。每个 d3 包都具有令人惊讶的简单结构和公式。可以查看内部并改变其内部功能。

\n

2. 操纵节点的位置

\n

https://github.com/d3/d3-force#simulation_nodes

\n

在模拟过程中操纵节点的.x和来改变它们的位置。.y

\n
addNode({ id: /* ... */, x: 0, y: 100}) // like this\n
Run Code Online (Sandbox Code Playgroud)\n

编辑:我的回答中有一些关于 alpha 和时间的增减关系的拼写错误。

\n