Here's how to fix your links:
First, change the rectangle positioning so that they are centred over where the circle would be in the standard link-node diagram. Rather than hard-coding values in the update
function, I've configured the rectangle dimensions in an object:
var rect = {l: 60, w: 20};
Edit the rectangle attributes to use the values in rect
, and centre the rectangle over the node by offsetting the x
and y
values by half the length and half the width of the rectangle respectively.
nodeEnter.append("rect") .attr("width", rect.l) // long dimension .attr("height", rect.w) // short dimension .attr("x", -rect.l/2) // offset by half the long dimension .attr("y", -rect.w/2) // offset by half the short dimension .style("fill", function(d) { return d._children ? "lightsteelblue" : "red"; });
To fix the links, we need to change their start and end position by the same offset. Since both exiting and entering links use the position of the source node, the only links we need to worry about are the update selection:
link.transition() .duration(duration) .attr("d", diagonal);
diagonal
takes an object of the form
{ source: { x: 123, y: 456 }, target: { x: 234, y: 567 } }
In the case of the update selection, the data items are already in this configuration, so calling diagonal(d)
works correctly. Since we're altering the coordinates to include the offset for the rectangle, we have to break out the diagonal
call. Note that the projection switches the x
and y
coordinates, so we have to alter the y
values:
link.transition() .duration(duration) .attr("d", function(d){ return diagonal({ source: {x: d.source.x, y: d.source.y + rect.l/2}, // add half the rectangle length target: {x: d.target.x, y: d.target.y - rect.l/2} // minus half the rectangle length }) });
I also took the liberty of centering the text in the rectangles by removing the offset you'd added and setting the text anchor to 'middle'.
Here's the result:
var pubs ={"name": "AUT-1","children": [ {"name": "PUB-1","children": [ {"name": "AUT-11","children": [ {"name": "AFF-111"}, {"name": "AFF-112"} ]}, {"name": "AUT-12","children": [ {"name": "AFF-121"} ]}, {"name": "AUT-13","children": [ {"name": "AFF-131"}, {"name": "AFF-132"} ]}, {"name": "AUT-14","children": [ {"name": "AFF-141"} ]} ] }, {"name": "PUB-2","children": [ {"name": "AUT-21"}, {"name": "AUT-22"}, {"name": "AUT-23"}, {"name": "AUT-24"}, {"name": "AUT-25"}, {"name": "AUT-26"}, {"name": "AUT-27"}, {"name": "AUT-28","children":[ {"name": "AFF-281"}, {"name": "AFF-282"}, {"name": "AFF-283"}, {"name": "AFF-284"}, {"name": "AFF-285"}, {"name": "AFF-286"} ]} ] }, {"name": "PUB-3"}, {"name": "PUB-4","children": [ {"name": "AUT-41"}, {"name": "AUT-42"}, {"name": "AUT-43","children": [ {"name": "AFF-431"}, {"name": "AFF-432"}, {"name": "AFF-433"}, {"name": "AFF-434","children":[ {"name": "ADD-4341"}, {"name": "ADD-4342"}, ]} ]}, {"name": "AUT-44"} ] }, {"name": "PUB-5","children": [ {"name": "AUT-51","children":[ {"name": "AFF-511"}, {"name": "AFF-512"}, {"name": "AFF-513"}, {"name": "AFF-514"}, {"name": "AFF-515"}, {"name": "AFF-516"} ]}, {"name": "AUT-52"}, {"name": "AUT-53"}, {"name": "AUT-54"}, {"name": "AUT-55","children":[ {"name": "AFF-551"}, {"name": "AFF-552"}, {"name": "AFF-553"}, {"name": "AFF-554"} ]}, {"name": "AUT-56"}, {"name": "AUT-57"}, {"name": "AUT-58"}, {"name": "AUT-59"}, {"name": "AUT-591"}, {"name": "AUT-592"}, {"name": "AUT-593"}, {"name": "AUT-594"}, {"name": "AUT-595"}, {"name": "AUT-596"} ] }, {"name": "PUB-6","children": [ {"name": "AUT-61","children":[ {"name": "AFF-611"}, {"name": "AFF-612"}, {"name": "AFF-613"}, {"name": "AFF-614","children":[ {"name": "ADD-6141"}, {"name": "ADD-6142"}, ]} ]}, {"name": "AUT-62"}, {"name": "AUT-63"}, {"name": "AUT-64"}, {"name": "AUT-65"}, {"name": "AUT-66"}, {"name": "AUT-67"}, {"name": "AUT-68"}, {"name": "AUT-69"} ] } ]};var diameter = 800;var margin = {top: 20, right: 120, bottom: 20, left: 120}, width = diameter, height = diameter;var i = 0, duration = 350, root;var tree = d3.layout.tree() .size([360, diameter / 2 - 80]) .separation(function(a, b) { return (a.parent == b.parent ? 1 : 1) / a.depth; });var diagonal = d3.svg.diagonal.radial() .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });var svg = d3.select("body").append("svg") .attr("width", width ) .attr("height", height ) .append("g") .attr("transform", "translate("+ diameter / 2 +","+ diameter / 2 +")");var rect = {l: 60, w: 20}root = pubs;root.x0 = height / 2;root.y0 = 0;update(root);d3.select(self.frameElement).style("height", "800px");function update(source) { // Compute the new tree layout. var nodes = tree.nodes(root), links = tree.links(nodes), offset = nodes[0].x // 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") .on("click", click); nodeEnter.append("rect") .attr("width", rect.l) .attr("height", rect.w) .attr("x", -rect.l/2) .attr("y", -rect.w/2) .style("fill", function(d) { return d._children ? "lightsteelblue" : "red"; }); nodeEnter.append("text") .attr("x", 0) .attr("dy", ".35em") .attr("text-anchor", "middle") //.attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)translate(-"+ (d.name.length * 8.5) +")"; }) .text(function(d) { return d.name; }) .style("fill-opacity", 1e-6); // Transition nodes to their new position. var nodeUpdate = node.transition() .duration(duration) .attr("transform", function(d) { return "rotate("+ (d.x - 90) +")translate("+ d.y +")"; }) // nodeUpdate.select("circle") // .attr("r", 4.5) // .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeUpdate.select("text") .style("fill-opacity", 1) .attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)"; }); // TODO: appropriate transform var nodeExit = node.exit().transition() .duration(duration) //.attr("transform", function(d) { return "diagonal("+ 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(d) { return d.target.id; }); // Enter any new links at the parent's previous position. 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}); }); // Transition links to their new position. link.transition() .duration(duration) .attr("d", function(d){ return diagonal({ source: {x: d.source.x, y: d.source.y+rect.l/2}, target: {x: d.target.x, y: d.target.y-rect.l/2} }) }); // 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}; return diagonal({source: o, target: 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);}// Collapse nodesfunction collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; }}
.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 type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
I think that straight lines would look better on this visualisation - e.g.:
var pubs ={"name": "AUT-1","children": [ {"name": "PUB-1","children": [ {"name": "AUT-11","children": [ {"name": "AFF-111"}, {"name": "AFF-112"} ]}, {"name": "AUT-12","children": [ {"name": "AFF-121"} ]}, {"name": "AUT-13","children": [ {"name": "AFF-131"}, {"name": "AFF-132"} ]}, {"name": "AUT-14","children": [ {"name": "AFF-141"} ]} ] }, {"name": "PUB-2","children": [ {"name": "AUT-21"}, {"name": "AUT-22"}, {"name": "AUT-23"}, {"name": "AUT-24"}, {"name": "AUT-25"}, {"name": "AUT-26"}, {"name": "AUT-27"}, {"name": "AUT-28","children":[ {"name": "AFF-281"}, {"name": "AFF-282"}, {"name": "AFF-283"}, {"name": "AFF-284"}, {"name": "AFF-285"}, {"name": "AFF-286"} ]} ] }, {"name": "PUB-3"}, {"name": "PUB-4","children": [ {"name": "AUT-41"}, {"name": "AUT-42"}, {"name": "AUT-43","children": [ {"name": "AFF-431"}, {"name": "AFF-432"}, {"name": "AFF-433"}, {"name": "AFF-434","children":[ {"name": "ADD-4341"}, {"name": "ADD-4342"}, ]} ]}, {"name": "AUT-44"} ] }, {"name": "PUB-5","children": [ {"name": "AUT-51","children":[ {"name": "AFF-511"}, {"name": "AFF-512"}, {"name": "AFF-513"}, {"name": "AFF-514"}, {"name": "AFF-515"}, {"name": "AFF-516"} ]}, {"name": "AUT-52"}, {"name": "AUT-53"}, {"name": "AUT-54"}, {"name": "AUT-55","children":[ {"name": "AFF-551"}, {"name": "AFF-552"}, {"name": "AFF-553"}, {"name": "AFF-554"} ]}, {"name": "AUT-56"}, {"name": "AUT-57"}, {"name": "AUT-58"}, {"name": "AUT-59"}, {"name": "AUT-591"}, {"name": "AUT-592"}, {"name": "AUT-593"}, {"name": "AUT-594"}, {"name": "AUT-595"}, {"name": "AUT-596"} ] }, {"name": "PUB-6","children": [ {"name": "AUT-61","children":[ {"name": "AFF-611"}, {"name": "AFF-612"}, {"name": "AFF-613"}, {"name": "AFF-614","children":[ {"name": "ADD-6141"}, {"name": "ADD-6142"}, ]} ]}, {"name": "AUT-62"}, {"name": "AUT-63"}, {"name": "AUT-64"}, {"name": "AUT-65"}, {"name": "AUT-66"}, {"name": "AUT-67"}, {"name": "AUT-68"}, {"name": "AUT-69"} ] } ]};var diameter = 800;var margin = {top: 20, right: 120, bottom: 20, left: 120}, width = diameter, height = diameter;var i = 0, duration = 350, root;var tree = d3.layout.tree() .size([360, diameter / 2 - 80]) .separation(function(a, b) { return (a.parent == b.parent ? 1 : 1) / a.depth; });var diagonal_extras = { path: { // diagonal line direct: function(p){ return [ p.source, p.target ]; } // this is also the default path in radial trees , l_shape: function(p){ return [ p.source, { x: p.target.x, y: p.source.y }, p.target ]; } , l_shape_2: function(p){ return [ p.source, { x: p.source.x, y: p.target.y }, p.target ]; } , dogleg: function(p){ return [ p.source, { x: p.source.x, y: (p.source.y + p.target.y) / 2 }, { x: (p.source.x + p.target.x) / 2, y: (p.source.y + p.target.y) / 2 }, { x: p.target.x, y: (p.source.y + p.target.y) / 2 }, p.target]; } , dogleg_2: function(p){ return [ p.source, { x: (p.source.x + p.target.x) / 2, y: p.source.y }, { x: (p.source.x + p.target.x) / 2, y: (p.source.y + p.target.y) / 2 }, { x: (p.source.x + p.target.x) / 2, y: p.target.y }, p.target]; } } , polar_obj_to_cart: function(pt){ var angle = pt.x / 180 * Math.PI; return [pt.y * Math.cos(angle), pt.y * Math.sin(angle)]; } , polar_coords_to_cart: function(xy){ var angle = xy[0] / 180 * Math.PI; return [ xy[1] * Math.cos(angle), xy[1] * Math.sin(angle)]; }}diagonal_extras.right_angle = function() { var projection = d3.svg.diagonal().projection() , path_type = 'dogleg' ; function diagonal(d) { return diagonal.path_maker( diagonal_extras.path[ diagonal.path_type() ](d) ); } diagonal.path_maker = function( pathData ) { return "M"+ pathData.map( projection ).join(''); }; diagonal.valid_path_types = function() { return Object.keys( diagonal_extras.path ); }; diagonal.path_type = function(x) { if (! arguments.length) { return path_type; } if ( diagonal_extras.path[ x ] ) { path_type = x; return diagonal; } throw new Error( x +' is not a valid path type' ); }; diagonal.projection = function(x) { if (!arguments.length) { return projection; } projection = x; return diagonal; }; diagonal.path = function(x) { if (!arguments.length) { return path; } path = x; return diagonal; }; diagonal.draw = function(d) { return diagonal(d); }; return diagonal;}diagonal_extras.radial = function() { var diagonal = diagonal_extras.right_angle() , projection = function(pt){ return [ pt.x, pt.y ]; }; diagonal.path_type('direct'); diagonal.projection = function(x) { if (!arguments.length) { return projection; } projection = x; return diagonal; }; diagonal.path_maker = function( pathData ) { var projected = pathData.map( function(x){ return projection(x); }) , pl = projected.length , points , prev_angle ; // direct link: if ( 2 === pl ) { return 'M'+ projected.map( function(x){ return diagonal_extras.polar_coords_to_cart(x); }).join(''); } points = projected.map( function(obj){ return { angle: obj[0] / 180 * Math.PI, radius: obj[1] }; }); return "M"+ points.map( function(pt){ var str = ''; if ( prev_angle ) { if ( prev_angle === pt.angle ) { // draw a straight line str = 'L'; } else { // draw an arc to the new radius and angle str = 'A'+ pt.radius +','+ pt.radius // x axis rotation+" 0 " // large arc flag+" 0," // sweep+ ( pt.angle > prev_angle ? 1 : 0) +""; } } prev_angle = pt.angle; return str + pt.radius * Math.cos(pt.angle) +","+ pt.radius * Math.sin(pt.angle); }).join(''); }; return diagonal;}var diagonal = diagonal_extras.radial() .path_type('dogleg') .projection(function(d){ return [ d.x - 90, d.y ]; });var svg = d3.select("body").append("svg") .attr("width", width ) .attr("height", height ) .append("g") .attr("transform", "translate("+ diameter / 2 +","+ diameter / 2 +")rotate(90)"); // note the altered rotationvar rect = {l: 60, w: 20}root = pubs;root.x0 = height / 2;root.y0 = 0;//root.children.forEach(collapse); // start with all children collapsedupdate(root);d3.select(self.frameElement).style("height", "800px");function update(source) { // Compute the new tree layout. var nodes = tree.nodes(root), links = tree.links(nodes), offset = nodes[0].x // Normalise angles so that the root is horizontal nodes.forEach(function(d) { d.x -= offset; if (d.x < 180) { d.x += 360 } }); // 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") .on("click", click); nodeEnter.append("rect") .attr("width", rect.l) .attr("height", rect.w) .attr("x", -rect.l/2) .attr("y", -rect.w/2) .style("fill", function(d) { return d._children ? "lightsteelblue" : "red"; }); nodeEnter.append("text") .attr("x", 0) .attr("dy", ".35em") .attr("text-anchor", "middle") .text(function(d) { return d.name; }) .style("fill-opacity", 1e-6); // Transition nodes to their new position. var nodeUpdate = node.transition() .duration(duration) .attr("transform", function(d) { return "rotate("+ (d.x - 90) +")translate("+ d.y +")"; }) nodeUpdate.select("text") .style("fill-opacity", 1) .attr("transform", function(d) { return ( d.x < 270 || d.x > 450 ) ? 'rotate(180)' : '' }); // TODO: appropriate transform var nodeExit = node.exit().transition() .duration(duration) //.attr("transform", function(d) { return "diagonal("+ source.y +","+ source.x +")"; }) .remove(); nodeExit.select("text") .style("fill-opacity", 1e-6); // Update the links… var link = svg.selectAll("path.link") .data(links, function(d) { return d.target.id; }); // Enter any new links at the parent's previous position. 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}); }); // Transition links to their new position. link.transition() .duration(duration) .attr("d", function(d){ return diagonal({ source: {x: d.source.x, y: d.source.y+rect.l/2}, target: {x: d.target.x, y: d.target.y-rect.l/2} }) }); // 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}; return diagonal({source: o, target: 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);}// Collapse nodesfunction collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; }}
.node { cursor: pointer;}.node text { font: 10px sans-serif;}.link { fill: none; stroke: #ccc; stroke-width: 1.5px;}
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>