d3.js v4 force diagram update - javascript

Trying to update a force diagram with nodes and links that already exist in the current diagram.
So for example, we have a simple diagram with two nodes A and B and a link between them "A" --> "B".
I was wondering what would be the correct way of handling if another node, exactly the same, is added.
In the fiddle I've put together and in my personal development I end up with duplicates, which I thought would be handled in
.data(nodes, function(d) { return d.id; })
and the links appropriate data function
(for example .data(links, function(d) { return d.target; }))
Ideally, I'm looking for "nothing to happen" when the same node/ link is added.
Hopefully, this makes sense, please see my fiddle example here

Related

d3.js combining Hierarchical Edge Bundling and Radial Reingold–Tilford Tree + data

I would like to (sort of) combine the Hierarchical Edge Bundling and the Radial Reingold–Tilford Tree
It would look a little like this (pardon my horrible paint.net skills)*:
*the children are supposed to be in an arc, like in the Tree.
I have setup this fiddle that shows simple data in HEB: https://fiddle.jshell.net/d1766z4r/2/
I have already combined the two types of data (in comments at the begining, that will replace the current variable "classes"):
var classes = [
{"name":"test.cluster1.item1","children": [
{"name": "test.cluster1.item4","imports":["test.cluster1.item2","test.cluster1.item3"]},
{"name": "test.cluster1.item5"}
]},
{"name":"test.cluster1.item2","imports":["test.cluster1.item3"]},
{"name":"test.cluster1.item3"}
];
If there is a better way to combine the data, feel free to change it accordingly.
Other than changing the data, I am not sure how to proceed, as I am a d3.js and javascript novice, especially when it'll come to linking the right subitem (item 4), but not the other (item 5). However, I will consider an answer that will show a link to all the children of item 1 as a way to start developing this.
Please update this fiddle or make a new one to back your code up if possible.
Any advice on how to proceed is welcomed of course.
The next step will be to make those children show or hide on click, using a method similar to the Collapsible Tree (feel free to give advice on that aswell, but it will probably be a new question later if I can't find out how to do it), as I will have to work with big amounts of data, justifying children in the first place.
Finally, every child could have children of its own, but 1 line of children will suffice for now. I'll get to that later I suppose.
UPDATE: The answers don't have to be complete solutions, every bit (see what I did there?) helps!
I might be completely off track to what you are asking, please clarify if this is the case.
TL;DR: Here is how it looks, based on what I interpreted from the question. Have added some more data for testing.
Hierarchical Edge Bundling, provides a way of visualizing non-hierarchical edges in the graph. Here is the link to the paper which is implemented in d3, if you have not found.
In the example only leaf nodes are shown by filtering the other nodes:
node = node
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("text")
.attr("class", "node")...
Hierarchical relations in the example are represented by dots in names, so cluster1 is the parent of item1, item2 and item3.
Here is how it look if we remove the filter while appending nodes.
Now, my interpretation of your question is you want to use Hierarchical Edge Bundling for visualizing the non-hierarchical relations (represented by imports in the example) and 'Radial Reingold–Tilford' for hierarchical relations.
Here is how this can be done:
Data format need not change to what you have proposed. Below should be okay:
var classes = [
{
"name": "test.cluster1.item1.item4",
"imports": ["test.cluster1.item2", "test.cluster1.item3"]
},
{
"name": "test.cluster1.item1.item5",
"imports": []
}
]
Adopt packageImports function to get hierarchical edges from the nodes:
nodes.forEach(function (d) {
if(d.children && d.name !== "") d.children.forEach(function (i) {
imports.push({
source: map[d.name],
target: i
});
});
});
Add the radial diagonal generator from Radial Reingold–Tilford example :
var diagonal = d3.svg.diagonal.radial()
.projection(
function(d) {
debugger;
return [d.y, d.x / 180 * Math.PI];
}
);
Append hierarchical edges path:
treeLink = treeLink
.data(getHeirarchialEdges(nodes))
.enter().append("path")
.attr("class", "tree-link")
.attr("d", diagonal);
I have also added to the mouseover and mouseout functions to highlight the hierarchical nodes, try hovering over any parent node.

Basic d3: why can you select things that don't exist yet?

I've been learning about d3, and I'm a bit confused about selecting. Consider the following example:
http://bl.ocks.org/mbostock/1021841
Specifically, let's look at this line:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8)
.style("fill", function(d, i) { return fill(i & 3); })
.style("stroke", function(d, i) { return d3.rgb(fill(i & 3)).darker(2); })
.call(force.drag)
.on("mousedown", function() { d3.event.stopPropagation(); });
In the documentation it says, "A selection is an array of elements pulled from the current document." I interpret this to mean that svg.selectAll(.node) creates an array of elements of class .node pulled from the current document, but as far as I can tell there are no such elements! Unless I'm confused - and I'm almost certain that I am - the only place in the document where something is given the class "node" is after the selection has already occurred (when we write .attr("class", "node")).
So what is going on here? What does svg.selectAll(".node") actually select?
Although, at first sight, this may look like a simple and silly question, the answer to it is probably the most important one for everyone trying to do some serious work with D3.js. Always keep in mind, that D3.js is all about binding data to some DOM structure and providing the means of keeping your data and the document in sync.
Your statement does exactly that:
Select all elements having class node. This may very well return an empty selection, as it is in your case, but it will still be a d3.selection.
Bind data to this selection. Based on the above mentioned selection this will, on a per-element basis, compute a join checking if the new data is a) not yet bound to this selection, b) has been bound before, or c) was bound before but is not included in the new data any more. Depending on the result of this check the selection will be divided into an enter, an update, or an exit selection, respectively.
Because your selection was empty in the first place. All data will end up in the enter selection which is retrieved by calling selection.enter().
You are now able to append your new elements corresponding to the newly bound data by calling selection.append() on the enter selection.
Have a look at the excellent article Thinking with Joins by Mike Bostock for a more in-depth explanation of what is going on.

Not able to get the Zoom/Pan and States name appear in India Map - D3?

I am new to D3.js. However after being practiced the examples of this site, I tried to play ahead with Mr.John Coogan's map given here. The output that I found in his site is as under
But when I am trying to do the same thing by placing his .js,css,.json and index.html in plunker it is coming as
Problems
a) No States are getting displayed
b) Zoom and Pan is not working
In another word, at this point of time I am looking only for the Indian map to work exactly as the example shown by Mr. Coogan.
What needs to be done for this?
Here's the working plunk: http://plnkr.co/1EqpIFecwJmkbvypTyQD?p=preview
You needed to uncomment this line:
.call(d3.behavior.zoom().on("zoom", redraw))
on line 40 of the index.html in your plunk, and then zoom and pan will work.
The state colors (based on wealth) are not showing because of various, more complex errors. The error shown in the console (svg is not defined referencing line 78) is just the start (you need to replace svg with india, which is defined).
In fact the whole original gist your example is based on is really just a work in progress, but most of the answers for how to fix it can be found in this thread from the google group, from Mike Bostock himself.
Essentially, the json data loads asynchronously, so need to be loaded in series.
// load the two JSON files in series
d3.json("states.json", function(states) {
d3.json("wealth.json", function(wealthdata) {
...
});
});
Then you can just apend the relevant colorbrewer CSS class when you first create each path:
india.selectAll("path")
.data(states.features)
.enter().append("path")
.attr("d", path)
.attr("class", function(d) {
return "q" + quantize(wealthdata[d.id]) + "-9";
})
.attr("d", path);
But you also need to define the quantize scale, range...:
var quantize = d3.scale.quantize()
.range(d3.range(9));
... and domain (which you can only do once the data has been loaded:
quantize.domain([0, d3.max(d3.values(wealthdata))]);

Adding on-exit transition to D3 circle pack-based reusable module

I have a reusable module based on the d3.layout.pack graph example.
I added a transition on exit on the node elements but it seems like the transition works only for one data set and it's not working for the other.
Basically, to simulate the data update I am calling a function with setInterval this way:
function test(){
d3.select('#vis')
.datum(data2)
.call(cluster);
}
setInterval(test, 1500);
...and I added the transition this way:
c.exit().transition()
.duration(700)
.attr("r", function(d){ return 0; })
.remove();
You can find the data update section in the bottom of the file and find the exit transition handling on line 431.
Could you please check what's wrong?
The behaviour you're seeing is caused by the way data is matched in D3. For one of the data sets, all the data elements are matched by existing DOM elements and hence the exit selection is empty -- you're simply updating position, dimensions, etc. of the existing elements.
The way to "fix" this is to tell D3 explicitly how you want data to be matched -- for example by changing line 424 to
.data(nodes, function(d) { return d.name; });
which will compare the name to determine whether a data element is represented by a DOM element. Modified jsfiddle here.

Adding and Removing Nodes in D3js Force Graph

I am loading json from database and creating a json file which loads fine. Now I don't know which steps to take for making the nodes responsive in a Force-Directed Graph. I need to remove and add new nodes and their links.
force.nodes(json.nodes)
.links(json.links)
.start();
initNodes(json);
How can I make this more dynamic or update it without resetting the whole visualization?
I have seen this question a couple of times not being answered so I hope someone can post and give a guide.
Adding nodes/links to my D3 force graph was very confusing until I better understood the way I was adding the initial set of nodes.
Assuming a <g> is what you'd like to use for your nodes:
// Select the element where you'd like to create the force layout
var el = d3.select("#svg");
// This should not select anything
el.selectAll("g")
// Because it's being compared to the data in force.nodes()
.data(force.nodes())
// Calling `.enter()` below returns the difference in nodes between
// the current selection and force.nodes(). At this point, there are
// no nodes in the selection, so `.enter()` should return
// all of the nodes in force.nodes()
.enter()
// Create the nodes
.append("g")
.attr("id", d.name)
.classed("manipulateYourNewNode", true);
Now let's make that function that will add a node to the layout once the graph has been initialized!
newNodeData is an object with the data you'd like to use for your new node.
connectToMe is a string containing the unique id of a node you'd like to connect your new node to.
function createNode (newNodeData, connectToMe) {
force.nodes().push(newNodeData);
el.selectAll("g")
.data(force.nodes(), function(datum, index) { return index })
The function given as the optional second argument in .data() is run once for each node in the selection and again for each node in force.nodes(), matching them up based on the returned value. If no function is supplied, a fallback function is invoked, which returns the index (as above).
However, there's most likely going to be a dispute between the index of your new selection (I believe the order is random) and the order in force.nodes(). Instead you'll most likely need the function to return a property that is unique to each node.
This time, .enter() will only return the node you're trying to add as newData because no key was found for it by the second argument of .data().
.enter()
.insert("g", "#svg")
.attr("id", d.name)
.classed("manipulatYourNewNode", true);
createLink(connectToMe, newNodeData.name);
force.start();
}
The function createLink (defined below) creates a link between your new node and your node of choice.
Additionally, the d3js API states that force.start() should be called after updating the layout.
Note: Calling force.stop() at the very beginning of my function was a huge help for me when I was first trying to figure out how to add nodes and links to my graph.
function createLink (from, to) {
var source = d3.select( "g#" + from ).datum(),
target = d3.select( "g#" + to ).datum(),
newLink = {
source: source,
target: target,
value: 1
};
force.links().push(newLink);
The code below works under the assumptions that:
#links is the wrapper element that contains all of your link elements
Your links are represented as <line> elements:
d3.select("#links")
.selectAll("line")
.data(force.links())
.enter()
.append("line");
You can see an example of how to append new nodes and relationships here:
http://bl.ocks.org/2432083
Getting rid of nodes and relationships is slightly trickier, but you can see the process here:
http://bl.ocks.org/1095795

Categories

Resources