Как изменить угол расположения узлов и ссылок в d3-force?

#javascript #svg #d3.js #d3-force-directed

#javascript #svg #d3.js #d3-принудительно направленный

Вопрос:

Я новичок d3-force , я нахожу, что трудно изменить угол наклона линий и узлов, вы можете запустить мой код здесь.

В любом случае, мой код очень прост:

 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.js

Но я хочу этого:
чего я хочу


Я копаю источник d3-force here, обнаруживаю, что он добавляет Math.PI * (3 - Math.sqrt(5)) каждый раз.

Вот что я хочу:

  1. что такое угол? —> равномерно распределяется по всем узлам, поэтому, если у нас есть n узлы, это будет Math.PI / (n-1)
  2. как насчет порядка? —> сохранить порядок по часовой стрелке
  3. где находится первый узел? —> начинается с Math.PI / (n-1) / 2
  4. а как насчет дочерних элементов дочерних элементов? —> следуйте тому же правилу, но на половину длины родительской ссылки.

Комментарии:

1. Но разве они не будут перекрывать родительские элементы?

2. @RubenHelsloot О, я забыл сказать, что ссылка должна быть короче родительской

Ответ №1:

Я бы не стал использовать 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>