D3 tree link arrows are weird, not in the right place. How to fix them?

Viewed 405

I have a D3 tree and I added arrow link markers with the code below, but they don't always match with the links, like if they are to the right or left of the parent. How can I fix it so that they look better?

 svg
    .append("svg:defs")
    .selectAll("marker")
    .data(["end"])
    .enter()
    .append("svg:marker")  
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 20)
    .attr("refY", function(d) {
        return 0
    })
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");

My code for the tree is below and you can see how they look

var margin = {
  top: 30,
  right: 20,
  bottom: 20,
  left: 20
},
    width = 400 - margin.right - margin.left,
    height = 400 - margin.top - margin.bottom;

var i = 0,
    duration = 400,
    root;

var tree = d3.layout.tree()
    .nodeSize([5, 5])
    .separation(function separation(a, b) {
        return (a.parent == b.parent ? 1 : 2) / a.depth;
    });

var diagonal = d3.svg.diagonal()
    .projection(function (d) {
        return [d.x, d.y];
});

var svg = d3.select("#render").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
    .call(zm = d3.behavior.zoom().scaleExtent([1,8]).on("zoom", redraw))
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

zm.translate([40, 40]);

svg.append("svg:defs").selectAll("marker")
    .data(["end"])
    .enter().append("svg:marker")  
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 20)
    .attr("refY", function(d) {
        return 0
    })
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");


//Redraw for zoom
function redraw() {
  svg.attr("transform",
      "translate(" + d3.event.translate + ")"
      + " scale(" + d3.event.scale + ")");
}

var node = {
  name: 'Root',
  type: 'root',
  children: [{
    name: 'A',
    type: 'child'
  },
  {
    name: 'B',
    type: 'child'
  },
  {
    name: 'C',
    type: 'child'
  }]
};

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

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

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

function update(source) {
  var newHeight = Math.max(tree.nodes(root).reverse().length * 30, height);

  d3.select("#render svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", newHeight + margin.top + margin.bottom);

  tree = d3.layout.tree().size([newHeight, width]);

  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

  nodes.forEach(function (d) {
    d.y = d.depth * 80;
  });

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

  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function (d) {
          return "translate(" + source.x0 + "," + source.y0 + ")";
      })
      .on("click", click);

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

  nodeEnter.append("text")
    .attr("y", function (d) {
    return -20;
  })
    .attr("dy", ".35em")
    .attr("text-anchor", function (d) {
    return "middle";
  })
    .text(function (d) {
    return d.name;
  })
    .style("fill-opacity", 1e-6);

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

  nodeUpdate.select("circle")
    .attr("r", 10)
    .style("fill", function (d) {
    return d.endNode ? "orange" : "lightsteelblue";
  });

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

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

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

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

  var link = svg.selectAll("path.link")
  .data(links, function (d) {
    return d.target.id;
  });

  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("d", function (d) {
    var o = {
      x: source.x0,
      y: source.y0
    };
    return diagonal({
      source: o,
      target: o
    });
  })
  .attr("marker-end", "url(#end)");

  link.transition()
    .duration(duration)
   .attr("d", diagonal);

  link.exit().transition()
    .duration(duration)
    .attr("d", function (d) {
    var o = {
      x: source.x,
      y: source.y
    };
    return diagonal({
      source: o,
      target: o
    });
  })
    .remove();

  nodes.forEach(function (d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

function click(d) {

      var children = d.children || [];
      for (var i = 0; i < 1; i++) {
        children.push({
          name: d.id + 1,
          type: 'child',
          endNode: true
        });
      }

      d.children = children;
      update(d);
}
#render {
  overflow: auto;
  text-align: center;
}

#render .node {
  cursor: pointer;
}

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

#render .node text {
  font: 16px "Hiragino Sans GB", "华文细黑", "STHeiti", "微软雅黑", "Microsoft YaHei", SimHei, "Helvetica Neue", Helvetica, Arial, sans-serif !important;
}

#render .link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.10/d3.min.js"></script>
<div id="render"></div>

1 Answers
Related