D3 Сворачиваемый граф, ориентированный на силу, с данными, отличными от дерева, — выравнивание ссылок

Если вы видите существующий код, https://jsfiddle.net/sheilak/9wvmL8q8, когда график загруженные в первый раз ссылки, соединяющие родительский и дочерний узлы, находятся от границы родительского узла, но после его свертывания и расширения вы можете видеть, что те же ссылки находятся в центре родительского узла. я не хочу, чтобы ссылка была из центра родительского узла.

код

var width = 960,
height = 500;

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

var force = d3.layout.force()
  .size([width, height])
  //gravity(0.2)
  .linkDistance(height / 6)
  .charge(function(node) {
    if (node.type !== 'ORG') return -2000;
    return -30;
  });

// build the arrow.
svg.append("svg:defs").selectAll("marker")
  .data(["end"]) // Different link/path types can be defined here
  .enter().append("svg:marker") // This section adds in the arrows
  .attr("id", function(d) {
    return d;
  })
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 12)
  .attr("refY", 0)
  .attr("markerWidth", 9)
  .attr("markerHeight", 5)
  .attr("orient", "auto")
  .attr("class", "arrow")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

  var json = dataset;

  var edges = [];
  json.edges.forEach(function(e) {
    var sourceNode = json.nodes.filter(function(n) {
        return n.id === e.from;
      })[0],
      targetNode = json.nodes.filter(function(n) {
        return n.id === e.to;
      })[0];

    edges.push({
      source: sourceNode,
      target: targetNode,
      value: e.Value
    });
  });

  for(var i = 0; i < json.nodes.length; i++) {
    json.nodes[i].collapsing = 0;
    json.nodes[i].collapsed = false;
  }

  var link = svg.selectAll(".link");
  var node = svg.selectAll(".node");

  force.on("tick", function() {
    // make sure the nodes do not overlap the arrows
    link.attr("d", function(d) {
      // Total difference in x and y from source to target
      diffX = d.target.x - d.source.x;
      diffY = d.target.y - d.source.y;

      // Length of path from center of source node to center of target node
      pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));

      // x and y distances from center to outside edge of target node
      offsetX = (diffX * d.target.radius) / pathLength;
      offsetY = (diffY * d.target.radius) / pathLength;

      return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
    });

    node.attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });
  });

update();

function update(){
  var nodes = json.nodes.filter(function(d) {
    return d.collapsing == 0;
  });

  var links = edges.filter(function(d) {
    return d.source.collapsing == 0 && d.target.collapsing == 0;
  });

  force
    .nodes(nodes)
    .links(links)
    .start();

  link = link.data(links)

  link.exit().remove();

  link.enter().append("path")
    .attr("class", "link")
    .attr("marker-end", "url(#end)");

  node = node.data(nodes);

  node.exit().remove();

  node.enter().append("g")
    .attr("class", function(d) {
      return "node " + d.type
    });

  node.append("circle")
    .attr("class", "circle")
    .attr("r", function(d) {
      d.radius = 30;
      return d.radius
    }); // return a radius for path to use 

  node.append("text")
    .attr("x", 0)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .attr("class", "text")
    .text(function(d) {
      return d.type
    });

  // On node hover, examine the links to see if their
  // source or target properties match the hovered node.
  node.on('mouseover', function(d) {
    link.attr('class', function(l) {
      if (d === l.source || d === l.target)
        return "link active";
      else
        return "link inactive";
    });
  });

  // Set the stroke width back to normal when mouse leaves the node.
  node.on('mouseout', function() {
    link.attr('class', "link");
  })
  .on('click', click);

  function click(d) {
    if (!d3.event.defaultPrevented) {
      var inc = d.collapsed ? -1 : 1;
      recurse(d);

      function recurse(sourceNode){
        //check if link is from this node, and if so, collapse
        edges.forEach(function(l) {
          if (l.source.id === sourceNode.id){
            l.target.collapsing += inc;
            recurse(l.target);
          }
        });
      }
      d.collapsed = !d.collapsed;
    }      
    update();
  }
}

person Buvanesh Kumar    schedule 25.06.2020    source источник


Ответы (1)


Есть два простых способа решить эту проблему, которые требуют небольшой модификации существующего кода.

Первый наполовину сделан, так как цель каждой ссылки уже смещена, когда вы определяете данные своего пути:

  return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
});

Вы можете легко расширить это, чтобы сместиться от исходного узла, просто добавьте смещения к sourceX и sourceY как здесь . Таким образом, не имеет значения, находятся ли узлы над ссылками или под ними, потому что они не перекрываются. (Может быть небольшое перекрытие, поэтому вы можете добавить один или два пикселя к смещениям, чтобы учесть ширину ссылки).

Второй вариант, возможно, проще в d3v4+, так как он содержит selection.raise() (документы). Этот метод поднимает выбранный элемент наверх SVG (как последний дочерний элемент родительского элемента). Эквивалентно:

 this.parentNode.appendChild(this);

В вашей функции щелчка после обновления графика мы можем использовать эту строку, чтобы гарантировать, что узел, на который был нажат, поднимается вверх (по ссылкам). Вот пример этого.

person Andrew Reid    schedule 25.06.2020