D3 - make links flow along with nodes when tree loads

一曲冷凌霜 提交于 2021-01-05 11:46:18

问题


Below code represents a flow chart with connecting links that was built using D3.js Things work fine, but one issue I am seeing is with the links, right now the links are drawn first and then the nodes are transitioned/animated to their respective positions.

How can I have both links and nodes transition to their positions simultaneously ( smoothly ) ?

var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120,
  },
  width = 960 - margin.right - margin.left,
  height = 800 - margin.top - margin.bottom;

var treeData = [{
  "name": "Root",
  "type": "decision",
  "id": "root",
  "children": [{
      "name": "analytics",
      "condition": "False",
      "type": "decision",
      "value": "a+b",
      "id": "child1",
      "children": [{
        "name": "distinction",
        "type": "action",
        "id": "child2",
        "condition": "True",
        "value": "5",
      }, {
        "name": "nonDistinction",
        "type": "action",
        "id": "child3",
        "condition": "False",
        "value": "4",
        "children": [],
      }],
    },
    {
      "condition": "True",
      "name": "division",
      "type": "decision",
      "value": "a-b",
      "id": "child33",
      "children": [{
          "condition": "True",
          "name": "division1",
          "type": "decision",
          "value": "a-b",
          "id": "child44",
          "children": [{
            "condition": "True",
            "name": "division1.1",
            "type": "decision",
            "value": "a-b",
            "id": "child599",
            "children": [{
              "condition": "True",
              "name": "division1.1.34",
              "type": "decision",
              "value": "a-b",
              "id": "child234234",
              "children": [{
                "condition": "True",
                "name": "division1.1.434",
                "type": "decision",
                "value": "a-b",
                "id": "child35343",
                "children": [],
              }],
            }, {
              "condition": "True",
              "name": "division1.1.2",
              "type": "decision",
              "value": "a-b",
              "id": "child77",
              "children": [{
                "condition": "True",
                "name": "division1.1.1",
                "type": "decision",
                "value": "a-b",
                "id": "child1222",
                "children": [],
              }, {
                "condition": "True",
                "name": "division1.1.1",
                "type": "decision",
                "value": "a-b",
                "id": "child66",
                "children": [],
              }],
            }],
          }, {
            "condition": "True",
            "name": "NODE HAVING OVERLAP ISSUE",
            "type": "decision",
            "value": "a-b",
            "id": "child9090",
            "children": [{
                "condition": "True",
                "name": "division1.1.1",
                "type": "decision",
                "value": "a-b",
                "id": "child909090",
                "children": [],
              }],
          }],
        },
        {
          "condition": "True",
          "name": "division2",
          "type": "decision",
          "value": "a-b",
          "id": "child55",
          "children": [{
            "condition": "True",
            "name": "division2.1",
            "type": "decision",
            "value": "a-b",
            "id": "child88",
            "children": [{
              "condition": "True",
              "name": "division2.1.1",
              "type": "decision",
              "value": "a-b",
              "id": "child99",
              "children": [],
            }],
          }],
        },
      ],
    },
  ],
}];
var i = 0,
  duration = 1000,
  rectW = 120,
  rectH = 60;

var treeMap = d3.tree()
  .nodeSize([140, 120])
  .separation(function(a, b) {
    // If they have the same parent
    if(a.parent === b.parent) {
      // and are either both leaf nodes or both not leaf nodes
      // or have only one child (which results in a straight line down)
      if((a.children === undefined || a.children.length <= 1) ===
         (b.children === undefined || b.children.length <= 1)) {
        return 1;
      }
      // else, increase the size between them
      return 2;
    }
    
    // If they have the same depth, mark them as such so we can avoid them later
    if(a.depth === b.depth) {
      a.data.avoidRight = b;
      b.data.avoidLeft = a;
    }
    return 2;
  });

//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
  var source = {
    x: d.source.x + rectW / 2,
    y: d.source.y + (rectH / 2),
  };
  var target = {
    x: d.target.x + (rectW / 2),
    y: d.target.y + 3,
  };

  // This is where the line bends
  var inflection = {
    x: target.x,
    y: source.y,
  };
  var radius = 5;

  var result = "M" + source.x + ',' + source.y;

  if(!d.source.data.type) {
    return;
  }
  if(source.x < target.x) {
    // Child is to the right of the parent
    if(d.source.data.avoidRight !== undefined && inflection.x > d.source.data.avoidRight.x) {
      // There is some node that we should try to avoid first
      result += ' H' + (d.source.data.avoidRight.x - 2 * radius);
      result += ' V' + (d.source.data.avoidRight.y + rectH + radius);
      inflection.y = d.source.data.avoidLeft.y + rectH + radius;
    }

    result += ' H' + (inflection.x - radius);
  } else {
    // Child is to the left of parent
    if(d.source.data.avoidLeft !== undefined && inflection.x < d.source.data.avoidLeft.x + rectW) {
      result += ' H' + (d.source.data.avoidLeft.x + rectW + 2 * radius);
      result += ' V' + (d.source.data.avoidLeft.y + rectH + radius);
      inflection.y = d.source.data.avoidLeft.y + rectH + radius;
    }

    result += ' H' + (inflection.x + radius);
  }

  // Curve the line at the bend slightly
  result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);

  result += 'V' + target.y;
  return result;
};

// END OF LINK FUNC //

const zoomSvg = d3.select('.tree-diagram')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .append('g');

const svg = zoomSvg.append('g')
  .attr('transform', 'translate(' + 300 + ',' + 20 + ')');

const attachZoom = d3.select('svg');
attachZoom.call(d3.zoom().on('zoom', () => {
  zoomSvg.attr('transform', d3.event.transform)
}))


// ADD ARROW TO THE BOTTOM POINTING TO THE NEXT DECISION.

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", String)
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 15)
  .attr("refY", 0.5)
  .attr("markerWidth", 4)
  .attr("markerHeight", 4)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

root = d3.hierarchy(treeData[0], function(d) {
  return d.children;
});
root.x0 = 0;
root.y0 = 0;

update(root);

d3.select(".tree-diagram")
  .style("height", "1000px");

// END OF DRAW TREEE //

function update(source) {
  const treeData = treeMap(root);

  // Compute the new tree layout.
  var nodes = treeData.descendants(),
    links = treeData.links();

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 90;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes, function(d) {
      return d.data.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter()
    .append("g")
    .attr('transform', 'translate(' + source.x0 + ', ' + source.y0 + ')')
    .attr("class", "node")

  nodeEnter.append('path')
    .attr('d', function(d) {
      if (d.data.type === 'decision') {
        return 'M 60 0 120 30 60 60 0 30 Z';
      } else if (d.data.type === 'action') {
        return 'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
      }
    })
    .attr("stroke-width", 1)
    .attr('class', 'myPaths')
    .style("fill", function(d) {
      return "lightsteelblue";
    });

  nodeEnter.append("text")
    .attr("x", rectW / 2)
    .attr("y", rectH / 2)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d.data.name;
    });

  // UPDATE
  var nodeUpdate = nodeEnter.merge(node);

  // Transition to the proper position for the node
  nodeUpdate.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + (d.x) + "," + (d.y) + ")";
    });


  nodeUpdate.select('path.myPaths')
    .attr("d", function(d) {
      if (d.data.type === 'decision') {
        return 'M 60 0 120 30 60 60 0 30 Z';
      } else if (d.data.type === 'action') {
        return 'M 0 0 120 0 120 60 0 60 Z';
      } else {
        return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
      }
    });


  var nodeExit = node.exit()
    .transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.x + "," + source.y + ")";
    })
    .remove();

  // Update the links…
  var link = svg.selectAll(".link")
    .data(links, function(d) {
      return d.source.data.id + " " + d.target.data.id;
    })
    .classed('link1', true);


  // Enter any new links at the parent's previous position.
  var linkEnter = link.enter()
    .insert("g", "g")
    .attr("class", "link");

  linkEnter.append('path')
    .attr("marker-end", "url(#end)");
   
  // Add Link Texts.
  linkEnter.append('text');

  // Merge the new and the existing links before setting `d` and `text` on all of them
  link = linkEnter.merge(link);

  
  link.select('path')
    .attr("d", linkFunc);
  
  link.select('text')
    .text(function(d, i) {
      if (d.source.x < d.target.x) {
        return 'True';
      } else {
        return 'False';
      }
    })
    .attr('transform', function(d) {
      if (d.source.x < d.target.x && d.target.data.type) {
        return 'translate(' + (d.target.x + rectW / 2) + ',' + (d.source.y + rectH) + ')';
      } else {
        return null;
      }
    });


  //LinkUpdate
  var linkUpdate = linkEnter.merge(link);

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", linkFunc);

  // Transition links to their new position.

  // Transition exiting nodes to the parent's new position.
  link.exit()
    .transition()
    .duration(duration)
    .attr("d", linkFunc)
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}
.node {
  cursor: pointer;
  outline: none !important;
}

.node text {
  font: 10px sans-serif;
}

.button>path {
  stroke: blue;
  stroke-width: 1.5;
  /*   outline: none;  */
}

.button>rect {
  fill: #ddd;
  stroke: grey;
  stroke-width: 1px;
}

.conditionalSvg {
  /*   outline: none; */
  display: none;
}

.hide {
  /*    display: none;  */
  opacity: 0 !important;
  /*  pointer-events: none;  */
}

.link:hover {
  outline: none !important;
  cursor: pointer;
  stroke-width: 3px;
}

.link path {
  /*   outline: none !important;  */
  fill: none;
  stroke: darkgray;
  stroke-width: 2px;
}

.link path:hover {
  cursor: pointer;
  stroke-width: 4px;
}

.link text {
  font: 10px sans-serif;
}

.colorBlue {
  background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div class="tree-diagram">
</div>

来源:https://stackoverflow.com/questions/65210665/d3-make-links-flow-along-with-nodes-when-tree-loads

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!