如何更改 d3-force 中节点和链接的角度?

How to change angle of nodes and links in d3-force?

我是新手d3-force,我发现线和节点的角度很难改变,you can run my code here

无论如何,我的代码很简单:

const width = 800;
const height = 400;
const centerX = width / 2;
const centerY = height / 2;

const graph = ({
  nodes: Array.from({length:8}, () => ({})),
  links: [
    {source: 1, target: 0},
    {source: 2, target: 0},
    {source: 3, target: 0},
    {source: 4, target: 0},
    {source: 5, target: 0},
    {source: 6, target: 0},
    {source: 7, target: 0},
  ]
});

const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]),
    link = svg
      .selectAll(".link")
      .data(graph.links)
      .join("line"),
    node = svg
      .selectAll(".node")
      .data(graph.nodes)
      .join("g");
  node.append("circle")
    .attr("r", 12)
    .attr("cursor", "move")
    .attr("fill", "#ccc")
    .attr("stroke", "#000")
    .attr("stroke-width", "1.5px");
  node.append("text").attr("dy", 25).text(function(d) {return d.index})

  const simulation = d3
    .forceSimulation()
    .nodes(graph.nodes)
    .force("link", d3.forceLink(graph.links).distance(100))
    .force("charge", d3.forceManyBody().strength(-400))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .stop();
  for (let i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {  
    simulation.tick();
  }
  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)
      .attr("stroke", "#000")
      .attr("stroke-width", "1.5px")
    node
      .attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>
它像这样生成 svg:

但我想要这个:


我挖了d3-forcehere的源码,发现每次都加上Math.PI * (3 - Math.sqrt(5))

这是我想要的:

  1. 角度是多少? --> 所有节点平均分布,所以如果我们有n个节点,它将是Math.PI / (n-1)
  2. 订单怎么样? --> 保持顺序顺时针
  3. 第一个节点在哪里? --> 以 Math.PI / (n-1) / 2
  4. 开头
  5. children 的 children 呢? --> 遵循相同的规则,但是 parent link.
  6. 的一半长度

我不会使用 d3-force,而是使用一些基本的三角函数自己计算位置:

const width = 600;
const height = 400;
const centerX = width / 2;
const centerY = height / 2;

const graph = ({
  nodes: d3.range(8).map(i => ({ id: i })),
  links: [{
      source: 0,
      target: 1
    },
    {
      source: 0,
      target: 2
    },
    {
      source: 0,
      target: 3
    },
    {
      source: 0,
      target: 4
    },
    {
      source: 1,
      target: 5
    },
    {
      source: 1,
      target: 6
    },
    {
      source: 1,
      target: 7
    },
  ]
});

graph.root = graph.nodes[0];
graph.nodes.forEach(n => {
  n.children = [];
});

// Replace ID's with references to the nodes themselves
graph.links.forEach(l => {
  l.source = graph.nodes.find(n => n.id === l.source);
  l.target = graph.nodes.find(n => n.id === l.target);
  
  // Register the target as a child of the source
  l.source.children.push(l.target);
  l.target.parent = l.source;
});

// Place the nodes
graph.nodes.forEach(n => {
  if(n.parent === undefined) {
    // root
    n.x = centerX;
    n.y = centerY;
    n.level = 0;
    return;
  }

  const parent = n.parent;
  n.level = parent.level + 1;
  const nSiblings = parent.children.length;
  const ithSibling = parent.children.indexOf(n);

  // Position the node
  const angle = 2 * Math.PI / nSiblings; // in radians
  const startAngle = - angle / 2;
  const radius = 200 - 60 * n.level;
  
  console.log(angle, startAngle);

  n.x = parent.x + radius * Math.cos((ithSibling + 1) * angle + startAngle);
  // Use a plus to keep the order clockwise, - for counterclockwise
  n.y = parent.y + radius * Math.sin((ithSibling + 1) * angle + startAngle);
});


const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]),
  link = svg
  .selectAll(".link")
  .data(graph.links)
  .join("line"),
  node = svg
  .selectAll(".node")
  .data(graph.nodes)
  .join("g");
node.append("circle")
  .attr("r", 12)
  .attr("cursor", "move")
  .attr("fill", "#ccc")
  .attr("stroke", "#000")
  .attr("stroke-width", "1.5px");
node.append("text").attr("dy", 25).text(function(d) {
  return d.id
});

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)
  .attr("stroke", "#000")
  .attr("stroke-width", "1.5px")
node
  .attr("transform", function(d) {
    return "translate(" + d.x + ", " + d.y + ")";
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>