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

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.

Related

D3 Multiple Force Directed Graphs

I can't multiple D3 force directed graphs to appear on one page, as they all pile into the same svg for whatever reason. I am out of ideas, and I have tried the top ideas online, which generally center around making sure you have a unique div to place each in, which I do.
I have divs with ids occurrences5, occurrences5...etc. So the loop should correctly find each div. The names of the JSON files are the same as the ids for each div.
var color = d3.scale.category20();
var occurences = ["occurrences3", "occurrences5", "occurrences10","occurrences20"];
for (var i = 0; i < occurences.length; i++) {
var occurence = occurences[i];
var svg = d3.select("#" + occurence).append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "#" + occurence + "svg");
d3.json(occurence + ".json", function(error, graph) {
if (error) throw error;
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
}})
You need to set svg within the callback function(error, graph) you pass to d3.json and not outside it
Reason: the callback function you pass to d3.json is called asynchronously i.e. it won't run nicely once per iteration as you're looping through the occurrences array. It appears all 4 callbacks are returning after that loop has finished and thus svg is set to the last value it had in that loop, thus everything's getting added to that svg element.
If I separate the creation of each chart into their own function() blocks, it works. I got my answer from: D3 Dimple - How to show multiple dimple charts on same page?
However, you can separate into a function like so:
var occurences = ["occurrences3", "occurrences5", "occurrences10","occurrences20"];
occurences.forEach(function(entry) {
draw(entry);
});
function draw(occurence) {...

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

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.

D3Js updating wrong elements using call

I am new to D3JS and you can find the code at http://jsfiddle.net/3n8wD/
I am a facing an issue and any pointers would help
When I move the line it separates as expected, but as soon as I try to move the circles away it jumps back to the line.
On inspecting the Array, looks like the circle array is updating as I am moving the link, not sure what is causing that.
Any help on this would be highly appreciated. below is the code that i have on jsfiddle
var width = 960,
height = 500;
graph1 = {"nodes":[{"x":444,"y":275},{"x":378,"y":324}],"links":[{"source":1,"target":0}]}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var onDraggable = function(d1) {
console.log(d1);
d1.x = d3.event.x, d1.y = d3.event.y;
d3.select(this).attr("cx", d1.x).attr("cy", d1.y);
//console.log(d1);
link.filter(function(l) { return l.source === d1; }).attr("x1", d3.event.x).attr("y1", d3.event.y);
link.filter(function(l) { return l.target === d1; }).attr("x2", d3.event.x).attr("y2", d3.event.y);
}
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("drag", onDraggable)
var onDraggable1 = function(d) {
//d.x1 = d3.event.x1, d.y1 = d3.event.y1, d.x2=d3.event.x2, y2=d3.event.y2;
var mouseX = d3.mouse(this)[0];
var mouseY = d3.mouse(this)[1];
var relativeX = mouseX-d.source.x;
var relativeY = mouseY-d.source.y;
console.log(d);
//console.log(d);
// d3.select(this).attr("x1", d3.event.x).attr("y1", d3.event.y).attr("x2", d3.event.x).attr("y2", d3.event.y);
d.source.x= d.source.x+relativeX;
d.source.y = d.source.y+relativeY;
d.target.x= d.target.x+relativeX;
d.target.y = d.target.y+relativeY;
d3.select(this).attr("x1", d.source.x).attr("y1", d.source.y);
d3.select(this).attr("x2", d.target.x).attr("y2", d.target.y);
}
var drag1 = d3.behavior.drag()
.origin(function(d) { return d; })
.on("drag", onDraggable1);
graph1.links.forEach(function(d) {
d.source = graph1.nodes[d.source];
d.target = graph1.nodes[d.target];
});
var node = svg.append("g").attr("class","node")
.selectAll("circle")
.data(graph1.nodes)
.enter().append("circle")
.attr("r", 4)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.call(drag);
var link = svg.append("g").attr("class","link")
.selectAll("line")
.data(graph1.links)
.enter().append("line")
.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; })
.attr("stroke-width", 2)
.attr("stroke", "black")
.call(drag1);
The reason that your nodes are "jumping" to the end of the line, even after the line has been moved away, is because the data object for the node is the same object as the source/target data object for the line. The d value for the node and the d.source or d.target value for the link are just pointers (references) to the same object in the browser's memory. Everything you do to that data object is reflected in both the link and the node. That's what makes the node-drag function work: you change the data object directly, and then you update the position of the line and the circle to match their already-changed data value.
So, even though you don't update the circle's cx and cy position at the time you move the line, the statements d.source.x = etc. in the line drag method are setting the d.x and d.y values for the nodes. Then in the node drag method, when you access these values to determine the new position, the movement is determined relative to the end of the line, not to the position of the circle on screen.
So how do you get the behaviour you want, to separate the line from the node? You need to create a new data object for the line's source and target when you start to drag it, one that no longer references the nodes' data objects. You only need to do this once, at the start of the drag, so you use the dragstart event of the drag behaviour object:
var drag1 = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", separateNodes);
.on("drag", onDraggable1);
The separateNodes method is going to be quite short. All we have to do is, for both the target and the source of the link, create a new data object that is a copy of the existing target/source object (but which can be edited independently of the original object). Javascript doesn't have any default methods for copying an object (although various extensions do), but if the object in question just consists of x and y attributes, it is easy to do.
var separateNodes = function(d) {
//create new data objects that are copies of d.source and d.target
var newSource = {x:d.source.x, y:d.source.y};
var newTarget = {x:d.target.x, y:d.target.y};
//set the new objects as the target/source of this link
d.source = newSource;
d.target = newTarget;
}
You could even skip the variable declarations and just merge it all into one line for source and one for target, but I find it easier to understand like this. I should mention, however, that this approach works because x and y are both just simple data types (numbers), not pointers to objects. So when it says x:d.source.x, you are actually getting a new copy of that number which you can change without having it affect the original object that contained that number.
If, however, your nodes and links also have a value property that is a complex data object, then you have to decide what you want to do when the line separates from the node: do you want to create a copy of the entire value data object, or maintain the link to the original object, or replace it with a null (empty/undefined) object? The first case is complicated, and depends on the data structure of the value object; for the second you don't need to do anything (except remember that you've got multiple pointers to the same object); and for the last case you just add a value:null, attribute to the newSource and newTarget object definitions in your separateNodes function.
Hope that all makes sense,
--ABR

d3.js replace circle with a foreignObject

i played with this example of a force directed graph layout.
www.bl.ocks.org/GerHobbelt/3071239
or to manipulate directly, here with fiddle,
http://jsfiddle.net/BeSAb/
what i want was to replace the circle element
node = nodeg.selectAll("circle.node").data(net.nodes, nodeid);
node.exit().remove();
node.enter().append("circle")
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
.attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.style("fill", function(d) { return fill(d.group); })
.on("click", function(d) {
console.log("node click", d, arguments, this, expand[d.group]);
expand[d.group] = !expand[d.group];
init();
});
with a group (g) element that contains a svg foreignObject
node = nodeg.selectAll("g.node").data(net.nodes, nodeid);
node.exit().remove();
var nodeEnter = node.enter().append("foreignObject")
//simplified for this demo
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
.attr('width', '22px')
.attr('height', '22px')
.attr('x', -11)
.attr('y', -11)
.append('xhtml:div')
.style("background",function(d){return fill(d.group)})
.style("width","20px")
.style("height","20px")
.style("padding","2px")
.on("click", function(d) {
console.log("node click", d, arguments, this, expand[d.group]);
expand[d.group] = !expand[d.group];
init();
});
The Graph is build correct but if i try to expand a node by clicking it, it seems that the graph isn't updated. So that all old nodes are duplicated.
i make an other Fiddle where you can show this problem by clicking a node.
http://jsfiddle.net/xkV4b/
does anyone know what i forgot, or what the issue is?
Thank you very much!
Your enter append should probably match your selection on nodeg. But even then it appears that d3 has some trouble selecting 'foreignObject' things. That may be a question/issue to bring up on the d3 google group - it may be a bug.
However you can get around it by just selecting on the class. I updated the code to read:
node = nodeg.selectAll(".fo-node").data(net.nodes, nodeid);
node.exit().remove();
var nodeEnter = node.enter().append("foreignObject")
.attr("class", function(d) { return "fo-node node" + (d.size?"":" leaf"); })
.attr('width', '22px')
...
Which seems to work.

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