Reputation: 540
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>
Upvotes: 2
Views: 465
Reputation: 102174
Your marker is 6px high and your circles have a radius of 10px. So, inside the diagonal
function, let's move the paths' end 16px up (that is, 6 + 10
):
var diagonal = d3.svg.diagonal()
.target(function(d) {
var o = d.target;
o.y = o.y - 16
return o;
})
.projection(function(d) {
return [d.x, d.y];
});
Of course, don't forget to change the markers' refX
attribute accordingly (that is, subtracting 16
).
Here is your code with that change, I'm also removing the texts to better see the path and the marker:
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()
.target(function(d){
var o = d.target;
o.y = o.y - 16
return o;
})
.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", 4)
.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";
});
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";
});
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);
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>
Upvotes: 1