Children with n>1 parents in d3.js tree layout - javascript

d3.js tree layout is a great tool, but it only allows children to have a single parent by default. I would like to be able to let children have more than one parent. I am happy with the position of the nodes provided by the tree's default behaviour. All I want is to draw extra diagonal links between childless parents and existing children after the default tree has been calculated.
My script currently looks like this :
<script>
var h = 870,
w = 1200;
var dimcirc = [60,30,10,8];
var offsetL = 60;
var couleurs = [
"#a2a2ff",
"#87ff87",
"#ffc55c",
"#ff844d",
"#ffe452"];
var tree = d3.layout.tree()
.size([h-100, w-400])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 1.2); });
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y + offsetL, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.append("g");
d3.json("donnees.json", function(error, root) {
if (error) throw error;
var nodes = tree.nodes(root),
links = tree.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", function(d){return "niv_" + d.depth.toString();})
.classed("node", true)
.attr("transform", function(d) { return "translate(" + (d.y + offsetL).toString() + "," + d.x +")"; })
// Draw node circles
node.append("circle")
.attr("fill", function(d){
console.log(d.couleur);
if(d.couleur!=null){
return couleurs[d.couleur];
}
else {return couleurs[4];}
})
.attr("r", function(d){
return dimcirc[d.depth];
});
node.append("text")
.attr("dy", "0.31em")
//.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d){
return "translate(" + (dimcirc[d.depth] + 10).toString() + ")"
})
.text(function(d) { return d.name; })
.call(wrap, 230);
});
// Wrap text
function wrap(text, width) {
// A function to help wrap svg text.
}
</script>
I have tried to copy the children of a parent node to a childless node using the root object, to no avail (the structure of the root object is quite obscure to me).
Any help appreciated.
PS : I am aware of a similar question (d3.js tree nodes with same parents), but the lack of answers didn't help me much or course.
EDIT
I've managed to get what I want by :
1) Identifying the nodes of interest : those without children that are to be connected to existing children, and the parent of the later.
2) Extracting the coordinates of the source (node without children) and target (children to be connected to this node) from the root object.
3) Creating the extra paths using the same diagonal as for the "standard" paths created by the tree.
Here's the code I've added to get the source and target for each path :
var link2 = [];
var loopLink2 = [{"a":0,"b":1, "c":0,"d":0},{"a":1,"b":1, "c":1,"d":0},{"a":3,"b":0, "c":2,"d":1}];
loopLink2.forEach(function(d){
var sourX = root.children[d.a].children[d.b].x;
var sourY = root.children[d.a].children[d.b].y;
root.children[d.c].children[d.d].children.forEach(function(d){
link2.push({"source":{"x":sourX,"y":sourY}, "target":{"x":d.x,"y":d.y}});
console.log(link2);
});
});
And here's where the actual creation of the paths takes place :
svg.selectAll(".link2")
.data(link2)
.enter().append("path")
.attr("class", "link2")
.attr("d", diagonal)
.attr("transform", function(d) { return "translate(0," + offsetT +")"; });
Someone has a nicer solution ?

I don't know whether this is exactly what you are looking for. But this might help.
Dagre
Dagre is a library that uses d3 to plot acyclic graphs. You can try modifying this. Here
is the github wiki.
Family Tree
Here you can find an excellent answer for creating a family tree , that can have multiple parents for one child. This might fulfill your requirement if the structure doesn't matter.

Related

Javascript: var is equal to function which same var is passed into

Question: Can someone explain what is happening here?
The var root is set where it calls a function (classes), but when that function is defined it is passing in the root object. This is what I don't understand.
Some extra details:
Because of the words I would enter into a search engine, I can't find exactly what I'm looking for. Just giving me the name of what this is called is what I'm looking for, so I can further research it.
I have just learned about recursion.
This code is not mine, it's from here. It is running fine on my local server.
<center></center>
<script>
var diameter = 960,
format = d3.format(",d"),
color = d3.scaleOrdinal(d3.schemeCategory20c);
var bubble = d3.pack()
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("center").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("flare.json", function(error, data) {
if (error) throw error;
var root = d3.hierarchy(classes(data))
.sum(function(d) { return d.value; })
.sort(function(a, b) { return b.value - a.value; });
bubble(root);
var node = svg.selectAll(".node")
.data(root.children)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.data.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return color(d.data.packageName);
});
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.className.substring(0, d.r / 3); });
});
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
console.log([classes]);
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
The var root is set where it calls a function (classes), but when that function is defined it is passing in the root object.
No, it simply has a parameter that is named root. This has nothing to do with the root variable outside of the function.
You could rename the function parameter for example foobar (function classes(foobar), and inside recurse(null, foobar);), and things would still work exactly the same.
Your confusion is completely understandable, considering there are two uses of root, and they don't refer to the same thing.
Note that classes is invoked by classes(data), so the root in the classes function refers to the data loaded in flare.json, while the root in your data loading function is a d3 object, returned by d3.hierarchy.
I would recommend renaming the root parameter in classes so there's less confusion.

Cannot read property error using d3.js

I've been trying to visualize a Parse Tree generated by a Python Script via HTML and d3.js. The Python Script generates an HMTL document that look like this:
<!DOCTYPE html>
<meta charset="utf-8">
<head><title> Tree Visualization </title></head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script type="text/javascript">js</script>
<body onLoad="drawTree({divID: \'viz\', width: 600, height: 400, padding: 50, treeData: treeData})">
<div id="viz"></div>
</body>
</html>
Where js is the Javascript code doing the d3 stuff
function drawTree(o) {
d3.select("#"+o.divID).select("svg").remove()
var viz = d3.select("#"+o.divID)
.append("svg")
.attr("width", o.width)
.attr("height", o.height)
var vis = viz
.append("g")
.attr("id","treeg")
.attr("transform", "translate("+ o.padding +","+ o.padding +")");
var tree = d3.layout.tree()
.size([o.width - (2 * o.padding), o.height - (2 * o.padding)]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
var nodes = tree.nodes(o.treeData);
var link = vis.selectAll("pathlink")
.data(tree.links(nodes))
.enter()
.append("path")
.attr("class", "link")
.attr("d", diagonal)
var node = vis.selectAll("g.node")
.data(nodes)
.enter()
.append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
node.append("circle")
.attr("r", 10)
.style("fill", function(d) { return (d.children) ? "#E14B3B" : "#1C8B98" });
node.append("svg:text")
.attr("dx", function(d) { return d.children ? 0 : 0; })
.attr("dy", function(d) { return d.children ? 5 : 5; })
.attr("text-anchor", function(d) { return d.children ? "middle" : "middle"; })
.style("fill", "white")
.text(function(d) { return d.name; });
}
and treeData is a JSON like representation of the tree.
What I now get is the error Cannot read property 'tree' of undefinedin the js part, in the var tree = ... line. Most likely I'm just blind and you'll instantly see what I did wrong, but I've been staring at this for a while now...
Thanks in advance for any help!
d3js v4 doesn't use d3.layout.tree any more. Instead use like following
var tree = d3.layout.tree(); //v3
var tree = d3.tree(); //v4
Check the changelog for the differences:
d3.layout.tree ↦ d3.tree
It may be the version of d3 you are using, I see you are loading:
<script src="https://d3js.org/d3.v4.min.js"></script>
It may be the case that in v4 there is no d3.layout.tree() object. Try switching to different version, may be this one:
<script src="http://d3js.org/d3.v3.min.js"></script>
As the error says, it can be that d3.layout is undefined or d3.layout.tree() is undefined.

d3.js force-directed issues, significance of "d"?

I'm having a bit of trouble getting a something to work with D3.js. Namely, I'm trying to make a tree of nodes, using the basic code from http://bl.ocks.org/mbostock/1021953.
I switched it to load the data inline, as opposed to loading from file, because I'm using it with a Rails application and don't want to have repetitive information. I switched the line so that you could see the format of my data.
Anyways, here's the bulk of my code:
<%= javascript_tag do %>
var nodes = [{"title":"Duncan's Death","id":"265"},{"title":"Nature Thrown Off","id":"266"},{"title":"Cows Dead","id":"267"},{"title":"Weather Bad","id":"268"},{"title":"Lighting kills man","id":"269"},{"title":"Macbeth's Rise","id":"270"}];
var links = [{"source":"265","target":"266","weight":"1"},{"source":"266","target":"267","weight":"1"},{"source":"266","target":"268","weight":"1"},{"source":"268","target":"269","weight":"1"}];
var firstelement = +links[0].source;
links.forEach(function(l) {
l.source = +l.source;
l.source = l.source-firstelement;
l.target = +l.target
l.target = l.target-firstelement;
});
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-1000)
.linkDistance(300)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
force
.nodes(nodes)
.links(links)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.weight); });
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("class", "circle_node")
.attr("r", 50)
.style("fill", function(d) { return color(d.id); })
node.append("title")
.text(function(d) { return d.title; });
node.append("text")
.attr("x", function(d) { return d.x; } )
.attr("y", function(d) { return d.y; })
.text(function(d) { return d.title; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("x", function(a) { return a.x; })
.attr("y", function(a) { return a.y; });
});
<% end %>
This seems like it should work to me, but I can seem to manage it. The links work, but the nodes all remain in the top left corner. I've tried just entering the circles directly and appending the text to them (staying close to the source code I listed above,) but while the circles behave properly, it doesn't display the text. I'd like the title to be centered in the nodes.
More generally, I'm kind of confused by how this is working. What does "d" refer to within lines like
function(d) { return d.source.x; }
It seems to be declaring a function and calling it simultaneously. I know that it doesn't have to be specifically the character "d," (for instance, switching the "d" to an "a" seems to make no difference as long as it's done both in the declaration and within the function.) But what is it referring to? The data entered into the object that's being modified? For instance, if I wanted to print that out, (outside of the attribute,) how would I do it?
Sorry, I'm new to D3 (and fairly new to JavaScript in general,) so I have a feeling the answer is obvious, but I've been looking it up and through tutorials and I'm still lost. Thanks in advance.
First, there's a simple problem with your code that is causing all your nodes to stay in the top left corner. You are trying to position each node using the code:
node.attr("x", function(a) { return a.x; })
.attr("y", function(a) { return a.y; });
However, node is a selection of gs which do not take x and y attributes. Instead, you can move each node using translate transform, e.g.
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
Making this change should allow the nodes to move around.
Next, moving to your question about "d", I think the first thing you need to understand is what you can do with a selection of elements in D3. From the docs: a selection (such as nodes) "is an array of elements pulled from the current document." Once you have a selection of elements, you can apply operators to change the attributes or style of the elements. You can also bind data to each element.
In your case, you are binding data to a selection of gs (nodes):
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
You are then using attr to change the position of each node. However, instead of setting the x and y attributes of each element to the same value, you are passing attr an anonymous function that will return a (presumably different) position for each node:
node.attr("x", function(a) { return a.x; })
.attr("y", function(a) { return a.y; });
This behavior is also explained in the docs for attr:
Attribute values and such are specified as either constants or
functions; the latter are evaluated for each element.
Thus, d represents an individual element (Object) in nodes.
So going back to your code, on each tick two things are happening:
The position of each node (data) is being recalculated by force.
Each corresponding element is then being moved to its new location by the anonymous function you pass to force.on.

How to show d3.js circle pack graph in a Rails application?

I have been trying for long to make this : http://bl.ocks.org/mbostock/4063269#flare.json example work in my sample Rails app to learn about d3.js. But, I am getting hard time to make it work.
I put the following code in my index.html.erb file :
<script type="text/javascript">
var diameter = 960,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("assets/data/flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
I put the flare.json file inside my app/assets/data directory. But, it seems like the javascript can not load the flare.json file from that location. I am just not sure how to make this work :( How to specify the location of the json file so that the javascript can load it and the code works? Any suggestion would be much much helpful.
Instead of loading the json file via d3.json, I rendered its contents on the javascript file and used a variable to store it.
var root = <%= render partial: 'controller_view/flare.json', formats: [:json] %>;

Updating a layout.pack in d3.js

I am trying to wrap my mind around d3's pack layout (http://bl.ocks.org/4063530).
I have the basic layout working but I would like to update it with new data. i.e. collect new data, bind it to the current layout.pack and update accordingly (update/exit/enter).
My attempts are here (http://jsfiddle.net/emepyc/n4xk8/14/):
var bPack = function(vis) {
var pack = d3.layout.pack()
.size([400,400])
.value(function(d) {return d.time});
var node = vis.data([data])
.selectAll("g.node")
.data(pack.nodes)
.enter()
.append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("circle")
.attr("r", function(d) { return d.r });
node.filter(function(d) { return !d.children; }).append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.analysis_id });
bPack.update = function(new_data) {
console.log("UPDATE");
node
.data([new_data])
.selectAll("g.node")
.data(pack.nodes);
node
.transition()
.duration(1000)
.attr("class", function(d) { return d.children ? "node" : "leaf node" })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" });
node.selectAll("circle")
.data(new_data)
.transition()
.duration(1000)
.attr("r", function(d) { return d.r; });
};
Specific questions...
How do I bind the data? (since the data is not complex structure and not an array of data)
How can new nodes/leafs be added to the layout? And old ones removed?
Pointers to a working example would be greatly appreciated.
Working example is here.
Basically, there is code for initial load, where all circles, tooltips, etc. are created and positioned in initial places. As well, the layout (pack) is created.
Than, on each button press, new data is loaded into pack, and the pack is recalculated. That crucial code is here:
Here you bind (load) now data into pack layout: (in my example its random data, of course you'll have your data from json or code or similar):
pack.value(function(d) { return 1 +
Math.floor(Math.random()*501); });
Here the new layout is calculated:
pack.nodes(data);
After that, elements are transitioned to new positions, and its attributes are changed as you determine.
I just want to stress that I don't use enter/update/exit pattern or transformations (that you might see in others solutions), since I believe this introduces unnecessary complexity for examples like this.
Here are some pics with transition in action:
Start:
Transition:
End:
I had the same problem recently, and came across the General Update Pattern tutorials as well. These did not serve my purpose. I had a few hundred DOM elements in a graph (ForceLayout), and I was receiving REST data back with properties for each individual node. Refreshing by rebinding data led to reconstruction of the entire graph, as you said in response to mg1075's suggestion. It tooks minutes to finish updating the DOM in my case.
I ended up assign unique ids to elements that need updating later, and I cherry picked them with JQuery. My entire graph setup uses D3, but then my updates don't. This feels bad, but it works just fine. Instead of taking minutes from destroying and recreating most of my DOM, it takes something like 3 seconds (leaving out timing for REST calls). I don't see a reason that something like property updates could not be made possible in D3.
Perhaps if Mike Bostock added a remove() or proper subselection function to the enter() selection, we could follow a pure D3 pattern to do updates. While figuring this out I was trying to bind a subset of data, the data with the new properties added, and then subselecting to get at elements that need updating, but it didn't work, due to the limited and specific nature of the enter() selection.
Of relevance, if you have not already reviewed:
http://bl.ocks.org/3808218 - General Update Pattern, I
http://bl.ocks.org/3808221 - General Update Pattern, II
http://bl.ocks.org/3808234 - General Update Pattern, III
This sample fiddle has no transitions, but here is at least one approach for updating the data.
http://jsfiddle.net/jmKH6/
// VISUALIZATION
var svg = d3.select("#kk")
.append("svg")
.attr("width", 500)
.attr("height", 600)
.attr("class", "pack");
var g = svg.append("g")
.attr("transform", "translate(2,2)");
var pack = d3.layout.pack()
.size([400,400])
.value(function(d) {return d.time});
function update(data) {
var nodeStringLenth = d3.selectAll("g.node").toString().length;
if ( nodeStringLenth > 0) {
d3.selectAll("g.node")
.remove();
}
var node = g.data([data]).selectAll("g.node")
.data(pack.nodes);
node.enter()
.append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("circle")
.attr("r", function(d) { return d.r });
node.filter(function(d) { return !d.children; }).append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.analysis_id });
node
.exit()
.remove();
}
var myData = [data1, data2, data3];
update(data1);
setInterval(function() {
update( myData[Math.floor(Math.random() * myData.length)] ); // http://stackoverflow.com/questions/4550505/getting-random-value-from-an-array?lq=1
}, 1500);

Categories

Resources