I just started getting involved in web visualizations, so I'm totally novice. My goal is to display a family tree where a root node would have both multiple parents and children. While looking for a solution I found this example: http://bl.ocks.org/jdarling/2503502 It's great because it seems to have the feature I need. However, I would like to alter the orientation (top-to-bottom). I tried to do so using this example: http://bl.ocks.org/mbostock/3184089 but failed.
My code:
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x, d.y];
});
var elbow = function (d, i){
var source = calcTop(d.source);
var target = calcTop(d.target);
var hx = (target.x-source.x)/2;
if(d.isRight)
hx = -hx;
return "M" + source.x + "," + source.y
+ "H" + (source.x+hx)
+ "V" + target.y + "H" + target.x;
};
var connector = elbow;
var calcTop = function(d){
var top = d.x;
if(!d.isRight){
top = d.x-halfHeight;
top = halfHeight - top;
}
return {x : top, y : d.y};
};
var vis = d3.select("#chart")
.append("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.right + margin.left)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("tree.json", function(json) {
root = json;
root.x0 = height / 2;
root.y0 = width / 2;
var t1 = d3.layout.tree()
.size([halfHeight, width])
.children(function(d){
return d.winners;
});
var t2 = d3.layout.tree()
.size([halfHeight, width])
.children(function(d){
return d.challengers;
});
t1.nodes(root);
t2.nodes(root);
var rebuildChildren = function(node){
node.children = getChildren(node);
if(node.children)
node.children.forEach(rebuildChildren);
}
rebuildChildren(root);
root.isRight = false;
update(root);
});
var toArray = function(item, arr){
arr = arr || [];
var i = 0, l = item.children?item.children.length:0;
arr.push(item);
for(; i < l; i++){
toArray(item.children[i], arr);
}
return arr;
};
function update(source) {
// Compute the new tree layout.
var nodes = toArray(source);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.x = d.depth * 180 + halfHeight; });
// Update the nodes…
var node = vis.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.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("dy", function(d) { return d.isRight?14:-8;})
.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) {
p = calcTop(d);
return "translate(" + p.x + "," + p.y + ")";
});
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) {
p = calcTop(d.parent||source);
return "translate(" + p.x + "," + p.y + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links...
var link = vis.selectAll("path.link")
.data(tree.links(nodes), 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 connector({source: o, target: o});
});
// Transition links to their new position.
link.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 = calcTop(d.source||source);
if(d.source.isRight)
o.x -= halfHeight - (d.target.x - d.source.x);
else
o.x += halfHeight - (d.target.x - d.source.x);
return connector({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
var p = calcTop(d);
d.x0 = p.x;
d.y0 = p.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(source);
} }
Would really appreciate the help!
Here is an example of bottom-top diagram that I made. I gather it might be useful for you, maybe you'll spot some idea etc.
Link to code on codepen. Feel free to ask any question.
The example you're looking at is actually already flipped - this might be causing you some confusion. Trees in D3 are naturally top-down trees, and the code does a lot of x-y flipping to make the tree sideways.
Changing
to
will get the nodes displaying in the right position. Doing a similar change with any instance of swapped x-y coordinates inside update() fixes most of the positioning issues. One last thing is the elbow function/variable/whatever you want to call it, where
should be changed to
This changes the connector shape from horizontal vertical horizontal to vertical horizontal vertical. Note that this is a raw SVG line, not d3 at all. The changes I made (plus swapping width and height, and changing the AJAX JSON request to hardcoding the data - AJAX is hard to get working in fiddle) are all at http://jsfiddle.net/Zj3th/2/.
If you have no experience with d3 and SVG, I would definitely take a look and fully understand a simple example like http://blog.pixelingene.com/2011/07/building-a-tree-diagram-in-d3-js/ before you go further in modifying the code.