admin管理员组

文章数量:1421066

If you play with the collapsible tree below you'll see that when you get to the end of the tree, and expand and collapse the nodes the lines are doing some wacky stuff and I am no entirely sure what drives the behavior or if my rewriting of enter link description here is pletely off base. I went off a flat data structure and used stratify to transform it into a tree layout. The only issue so far is the line transitions...any thoughts?

var data = [{
    "name": "Hazer 5000",
    "parent": "CFO",
    "img": ".cdpn.io/t-342/stephen.jpg"
  }, {
    "name": "Employee 1",
    "parent": "Hazer 5000",
    "img": ".cdpn.io/t-342/cory.jpg"
  }, {
    "name": "Analytics Area",
    "parent": "Hazer 5000",
    "img": ".cdpn.io/t-342/matt.jpg"
  }, {
    "name": "Employee 2",
    "parent": "Hazer 5000",
    "img": ".cdpn.io/t-342/XinheZhang.jpg"
  }, {
    "name": "Employee 3",
    "parent": "Hazer 5000",
    "img": ".cdpn.io/t-342/craig.jpg"
  }, {
    "name": "Employee 4",
    "parent": "Hazer 5000",
    "img": ".cdpn.io/t-342/youri.jpg"
  }, {
    "name": "Intern 1",
    "parent": "Analytics Area",
    "img": ""
  }, {
    "name": "Inter 2",
    "parent": "Analytics Area",
    "img": ""
  }, {
    "name": "CFO",
    "parent": null,
    "img": ".cdpn.io/t-342/Brett.jpg"
  }, {
    "name": "CPA",
    "parent": "CFO",
    "img": ".cdpn.io/t-342/Wes.jpg"
  }, {
    "name": "Matt's wife",
    "parent": "CPA",
    "img": ".cdpn.io/t-342/Amy_R.jpg"
  }, {
    "name": "Employee 5",
    "parent": "CPA",
    "img": ".cdpn.io/t-342/DavidBriley.jpg"
  }, {
    "name": "Employee 6",
    "parent": "CPA",
    "img": ".cdpn.io/t-342/BrittanyAllred_.jpg"
  }, {
    "name": "Employee 7",
    "parent": "CPA",
    "img": ".cdpn.io/t-342/Shea.jpg"
  }, {
    "name": "Employee 8",
    "parent": "Matt's wife",
    "img": ".cdpn.io/t-342/Mindy.jpg"
  }, {
    "name": "Employee 9",
    "parent": "Matt's wife",
    "img": ".cdpn.io/t-342/Jessica_Stacy.jpg"
  }, {
    "name": "Employee 10",
    "parent": "Matt's wife",
    "img": ".cdpn.io/t-342/FraleaneHudson.jpg"
  },{
    "name": "Employee 11",
    "parent": "Employee 9",
    "img": ".cdpn.io/t-342/MeganPierce_.jpg"
  },{
    "name": "Intern 3",
    "parent": "Employee 8",
    "img": ""
  }, {
    "name": "Intern 4",
    "parent": "Employee 8",
    "img": ""
  }

];

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

var i = 0,
    duration = 750,
    root;

var tree = d3.tree()
    .size([height, width]);

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


var stratify = d3.stratify()
  .id(function(d) {
    return d.name;//This position
  })
  .parentId(function(d) {
    return d.parent; //What position this position reports to
  });

var root = stratify(data);

root.each(function(d) {
  
    d.name = d.id; //transferring name to a name variable
    d.id = i; //Assigning numerical Ids
    i++;
  
  });

  root.x0 = height / 2;
  root.y0 = 0;

  function collapse(d) {
    if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
  }

  root.children.forEach(collapse);
  update(root);


d3.select(self.frameElement).style("height", "800px");

function update(source) {

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

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

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

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

  nodeEnter.append("circle")
      .attr("r", 1e-6)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeEnter.append("text")
      .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.merge(nodeEnter).transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

  nodeUpdate.select("circle")
      .attr("r", 4.5)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeUpdate.select("text")
      .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);


  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links);
  
  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", connector);

  // Enter any new links at the parent's previous position.
  var linkEnter = link.enter().insert("path", "g")
                        .attr("class", "link")
                        .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0, parent:{x: source.x0, y: source.y0}};
        return connector(o);
      });

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

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d",  function(d) {
        var o = {x: source.x, y: source.y, parent:{x: source.x, y: source.y}};
        return connector(o);
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}

function connector(d) {
  return "M" + d.y + "," + d.x +
    "C" + (d.y + d.parent.y) / 2 + "," + d.x +
    " " + (d.y + d.parent.y) / 2 + "," + d.parent.x +
    " " + d.parent.y + "," + d.parent.x;
}
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

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

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
<script src=".v4.min.js"></script>

If you play with the collapsible tree below you'll see that when you get to the end of the tree, and expand and collapse the nodes the lines are doing some wacky stuff and I am no entirely sure what drives the behavior or if my rewriting of enter link description here is pletely off base. I went off a flat data structure and used stratify to transform it into a tree layout. The only issue so far is the line transitions...any thoughts?

var data = [{
    "name": "Hazer 5000",
    "parent": "CFO",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/stephen.jpg"
  }, {
    "name": "Employee 1",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/cory.jpg"
  }, {
    "name": "Analytics Area",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/matt.jpg"
  }, {
    "name": "Employee 2",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/XinheZhang.jpg"
  }, {
    "name": "Employee 3",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/craig.jpg"
  }, {
    "name": "Employee 4",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/youri.jpg"
  }, {
    "name": "Intern 1",
    "parent": "Analytics Area",
    "img": ""
  }, {
    "name": "Inter 2",
    "parent": "Analytics Area",
    "img": ""
  }, {
    "name": "CFO",
    "parent": null,
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Brett.jpg"
  }, {
    "name": "CPA",
    "parent": "CFO",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Wes.jpg"
  }, {
    "name": "Matt's wife",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Amy_R.jpg"
  }, {
    "name": "Employee 5",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/DavidBriley.jpg"
  }, {
    "name": "Employee 6",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/BrittanyAllred_.jpg"
  }, {
    "name": "Employee 7",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Shea.jpg"
  }, {
    "name": "Employee 8",
    "parent": "Matt's wife",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Mindy.jpg"
  }, {
    "name": "Employee 9",
    "parent": "Matt's wife",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Jessica_Stacy.jpg"
  }, {
    "name": "Employee 10",
    "parent": "Matt's wife",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/FraleaneHudson.jpg"
  },{
    "name": "Employee 11",
    "parent": "Employee 9",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/MeganPierce_.jpg"
  },{
    "name": "Intern 3",
    "parent": "Employee 8",
    "img": ""
  }, {
    "name": "Intern 4",
    "parent": "Employee 8",
    "img": ""
  }

];

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

var i = 0,
    duration = 750,
    root;

var tree = d3.tree()
    .size([height, width]);

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


var stratify = d3.stratify()
  .id(function(d) {
    return d.name;//This position
  })
  .parentId(function(d) {
    return d.parent; //What position this position reports to
  });

var root = stratify(data);

root.each(function(d) {
  
    d.name = d.id; //transferring name to a name variable
    d.id = i; //Assigning numerical Ids
    i++;
  
  });

  root.x0 = height / 2;
  root.y0 = 0;

  function collapse(d) {
    if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
  }

  root.children.forEach(collapse);
  update(root);


d3.select(self.frameElement).style("height", "800px");

function update(source) {

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

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

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

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

  nodeEnter.append("circle")
      .attr("r", 1e-6)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeEnter.append("text")
      .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.merge(nodeEnter).transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

  nodeUpdate.select("circle")
      .attr("r", 4.5)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeUpdate.select("text")
      .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);


  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links);
  
  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", connector);

  // Enter any new links at the parent's previous position.
  var linkEnter = link.enter().insert("path", "g")
                        .attr("class", "link")
                        .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0, parent:{x: source.x0, y: source.y0}};
        return connector(o);
      });

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

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d",  function(d) {
        var o = {x: source.x, y: source.y, parent:{x: source.x, y: source.y}};
        return connector(o);
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}

function connector(d) {
  return "M" + d.y + "," + d.x +
    "C" + (d.y + d.parent.y) / 2 + "," + d.x +
    " " + (d.y + d.parent.y) / 2 + "," + d.parent.x +
    " " + d.parent.y + "," + d.parent.x;
}
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

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

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
<script src="https://d3js/d3.v4.min.js"></script>

Share Improve this question edited Jul 27, 2016 at 12:38 Tekill asked Jul 11, 2016 at 15:28 TekillTekill 1,2011 gold badge15 silver badges30 bronze badges 0
Add a ment  | 

1 Answer 1

Reset to default 7

Looks like an issue with the keys used for the link's data binding. I'm referring to the fact that if you call .data(links) without a 2nd argument, d3 uses i as the key when it putes the enter/update/exit sets, and so when you expand/collapse the same link can have different i values.

I believe I got it working, and all I did was add that 2nd param as a function that constructs a unique link id by bining the node's id with its parent node's id.

I.e. instead of this

var link = svg.selectAll("path.link")
  .data(links)

I made it this

var link = svg.selectAll("path.link")
  .data(links, function(link) { var id = link.id + '->' + link.parent.id; return id; });

Try it out:

var data = [{
    "name": "Hazer 5000",
    "parent": "CFO",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/stephen.jpg"
  }, {
    "name": "Employee 1",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/cory.jpg"
  }, {
    "name": "Analytics Area",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/matt.jpg"
  }, {
    "name": "Employee 2",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/XinheZhang.jpg"
  }, {
    "name": "Employee 3",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/craig.jpg"
  }, {
    "name": "Employee 4",
    "parent": "Hazer 5000",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/youri.jpg"
  }, {
    "name": "Intern 1",
    "parent": "Analytics Area",
    "img": ""
  }, {
    "name": "Inter 2",
    "parent": "Analytics Area",
    "img": ""
  }, {
    "name": "CFO",
    "parent": null,
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Brett.jpg"
  }, {
    "name": "CPA",
    "parent": "CFO",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Wes.jpg"
  }, {
    "name": "Matt's wife",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Amy_R.jpg"
  }, {
    "name": "Employee 5",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/DavidBriley.jpg"
  }, {
    "name": "Employee 6",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/BrittanyAllred_.jpg"
  }, {
    "name": "Employee 7",
    "parent": "CPA",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Shea.jpg"
  }, {
    "name": "Employee 8",
    "parent": "Matt's wife",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Mindy.jpg"
  }, {
    "name": "Employee 9",
    "parent": "Matt's wife",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/Jessica_Stacy.jpg"
  }, {
    "name": "Employee 10",
    "parent": "Matt's wife",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/FraleaneHudson.jpg"
  },{
    "name": "Employee 11",
    "parent": "Employee 9",
    "img": "https://s3-us-west-2.amazonaws./s.cdpn.io/t-342/MeganPierce_.jpg"
  },{
    "name": "Intern 3",
    "parent": "Employee 8",
    "img": ""
  }, {
    "name": "Intern 4",
    "parent": "Employee 8",
    "img": ""
  }

];

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

var i = 0,
    duration = 750,
    root;

var tree = d3.tree()
    .size([height, width]);

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


var stratify = d3.stratify()
  .id(function(d) {
    return d.name;//This position
  })
  .parentId(function(d) {
    return d.parent; //What position this position reports to
  });

var root = stratify(data);

root.each(function(d) {
  
    d.name = d.id; //transferring name to a name variable
    d.id = i; //Assigning numerical Ids
    i++;
  
  });

  root.x0 = height / 2;
  root.y0 = 0;

  function collapse(d) {
    if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
  }

  root.children.forEach(collapse);
  update(root);


d3.select(self.frameElement).style("height", "800px");

function update(source) {

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

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

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

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

  nodeEnter.append("circle")
      .attr("r", 1e-6)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeEnter.append("text")
      .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.merge(nodeEnter).transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

  nodeUpdate.select("circle")
      .attr("r", 4.5)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeUpdate.select("text")
      .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);


  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links, function(link) { var id = link.id + '->' + link.parent.id; return id; });
  
  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", connector);

  // Enter any new links at the parent's previous position.
  var linkEnter = link.enter().insert("path", "g")
                        .attr("class", "link")
                        .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0, parent:{x: source.x0, y: source.y0}};
        return connector(o);
      });

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

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d",  function(d) {
        var o = {x: source.x, y: source.y, parent:{x: source.x, y: source.y}};
        return connector(o);
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}

function connector(d) {
  return "M" + d.y + "," + d.x +
    "C" + (d.y + d.parent.y) / 2 + "," + d.x +
    " " + (d.y + d.parent.y) / 2 + "," + d.parent.x +
    " " + d.parent.y + "," + d.parent.x;
}
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

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

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
<script src="https://d3js/d3.v4.min.js"></script>

本文标签: javascriptd3js v4 wacky link transition in collapsible tree exampleStack Overflow