Using data(...) in D3 force layout to update the graph - d3.js

I am using the D3 force layout and adding code to allow the user to select individual nodes (here, select is not related to D3 select() but indicates the highlighted state of the node from the user's perspective). Only one node should be selected at one time. So, when the user clicks on a node which is not selected, I want to deselect any selected nodes.
I am using an attribute, selected, on the node and struggling to set that using the D3 data(...) or datum(...) method. In fact, I couldn't get it to work like that. What I have now seems kind of kludgy so I am hoping there is a cleaner way.
function deselectAll() {
var sc = d3.selectAll("circle")
.filter(function(d) {return (d.selected == "y")});
var circles = sc.data();
sc.transition()
.duration(50)
.style("stroke-width", "1px")
.style("stroke", "#3182bd");
if(null != circles && circles.length > 0) {
for(i=0; i<circles.length; i++) {
circles[i].selected = "n";
}
}
}
The problem is that adding .data("n") to the chain doesn't set 'selected' to "n". Is it possible to do that as part of the d3 chain?

Many selection (or transition) functions in d3 pass the current datum from each node to your defined functions. You can modify the contents of this datum at any time.
Using your example, if you want to set the nodes that have d.selected == "y" from "y" to "n" you can use your filtered selection like this:
sc.each(function(d) {
d.selected = "n";
});
This function will be invoked once for each node in the selection (each having "y" for selected as per your filter) so you can simply change the property value on the datum.

Related

Hide root node and edges in D3 v4 Tree Diagram

For the following Fiddle I'd like to hide the root node. Any help is appreciated, to view the code please view the fiddle.
I imagine I would do something like the following but I'm unsure of how/where to implement it:
if (d.depth > 0) {
...node is drawn
}
Image below:
Not drawing it is not "drawing it with zero opacity" or "hidden display". It's actually not appending the element.
Therefore, the simplest option is removing it from the data array. Just filter out the first node:
nodes = nodes.filter(function(d){
return d.depth != 0;
})
As 0 is falsy, this is the same of:
nodes = nodes.filter(function(d){
return d.depth;
})
And also filter out all links from it:
links = links.filter(function(d){
return d.depth != 1;
})
Here is your updated fiddle: https://jsfiddle.net/wa21csbc/
Also, since those elements are not painted anymore, you can move the dataviz to the left, thus occupying the empty SVG space. That space is there because we're filtering out the first node after d3.tree() calculated the positions.

D3.js Highlighting links on node selection

so first, I have data in the format the same as miserables.json below
https://bl.ocks.org/mbostock/4062045
I would like to be able to make the link connected to my node red when I click the node.
So the psuedo⁻code would be like
selectAll(.links)
if links.source=nodeID or links.target=nodeID
then links.color=red
But I have not be able to do it. My ultimate goal would be intergrate it with Arc diagram below
http://bl.ocks.org/sjengle/5431779
Your pseudocode is a good start. You can use filter to implement the if condition in a selection. Note that the .source and .target of links are edited by d3, they are no longer the id of the nodes, but the node themselves:
thisNode = nodeObject; // where nodeObject is the javascript object for the node, it's probably called "d" in your function.
d3.selectAll(".link")
.filter(function(d) {
return (d.source === thisNode) || (d.target === thisNode);
})
.style("stroke", "red")
Expanding on top of #laurent's answer, to "reset" color of links that potentially were painted red during a previous interaction:
thisNode = nodeObject; // where nodeObject is the javascript object for the node, it's probably called "d" in your function.
d3.selectAll(".link")
.style("stroke",function(d) {
return d.source === thisNode || d.target === thisNode ? "red" : "#888888";
})

How to create slow simultaneous transitions of multiple attributes in force directed graphs?

In a previous post called "D3: How to create slow transition of Circles for nodes in Force Directed Graphs FDG?", I got a great answer for how to transition a single element (e.g. the radius for "just circles") in D3.
My followup question is now about how to transition "multiple D3 attributes" at the same time...
As a reminder, I'm using D3 generated Radio Buttons to toggle the size of Nodes in a FDG Layout (on mouse click) from a default size to a scaled magnitude. You can find the Radio Buttons in the upper left hand of the Node Cluster Diagram (http://nounz.if4it.com/Nouns/Applications/A__Application_1.NodeCluster.html)
The code that toggles the node circles between a default number and a scaled magnitude (now using transitions) looks as follows...
var densityControlClick = function() {
var thisObject = d3.select(this);
var typeValue = thisObject.attr("density_type");
var oppositeTypeValue = (function() {
if(typeValue=="On") {
return "Off";
} else {
return "On";
}
})();
var densityBulletSelector = "." + "densityControlBullet-" + typeValue;
var selectedBullet = d3.selectAll(densityBulletSelector);
selectedBullet.style("fill", "Black")
var oppositeDensityBulletSelector = "." + "densityControlBullet-" + oppositeTypeValue;
var selectedOppositeBullet = d3.selectAll(oppositeDensityBulletSelector);
selectedOppositeBullet.style("fill", "White")
if(typeValue=="On") {
var selectedNodeCircles = d3.selectAll("#NODE");
selectedNodeCircles.transition().duration(500).attr("r", function(d){ return rRange(d.rSize); });
}
else {
var selectedNodeCircles = d3.selectAll("#NODE"); selectedNodeCircles.transition().duration(500).attr("r", function(d) { if (d.id==focalNodeID) { return centerNodeSize; } else { return defaultNodeSize; } } );
}
}
Everything works great and you can see the slower node transitions when you select the radio buttons. However, I'd now like to learn how to transition multiple elements, such as the the radius and the edge lengths simultaneously, along with the theory behind doing so, in order to show off D3's dynamic nature.
My question is: Given that I already can successfully transition the radius of circles, how would I also transition other elements like the edge lengths based on attributes like "alpha", "friction", etc., and... what's the theory behind transitioning multiple elements (in other words, what does the code mean, in English)? The D3 API doesn't appear to clearly get into the theory behind transitioning multiple attributes, simultaneously.
So transitioning multiple attributes is the simple part of this question. Just like a regular selection you can set multiple attributes at a time on your transition:
selectedNodeCircles.transition().duration(500)
.attr("r", function(d){ return rRange(d.rSize); })
.attr("stroke", 'red');
This will transition your radius and your line colour. The transition is a property of the DOM element (in this case the circle) and it will transition as many DOM attributes as you like. The thing to remember is that there is only only one transition object on each DOM element. So if you create another you will overwrite the old one.
// This will NOT work
circles.transition().duration(1000).attr('r', 50);
// The radius transition will be overridden by the fill
// transition and so will not complete
circles.transition().duration(1000).attr('fill', 'red');
This can actually be quite useful because you don't have to worry about interrupting animations that are in progress and figure out how far along they are and then starting a new animation - this will generally be handled automatically.
In your case you want to transition edge lengths in your graph. These are determined by the positional attributes of the nodes. Judging by your finished product, these attributes are already being animated because you are updating the DOM on every iteration of the layout algorithm (not through transitions) probably in the tick() callback.
So you could use transitions inside your tick callback, which might look odd and may be a hassle to keep in synch with the radius transitions (you will have to set both attributes in the transition). But it might be just what you need.
Alternatively, if you can wait, don't update the DOM in the tick callback. Let the layout complete - it runs a lot faster when it is not rendering on each tick - and once it is complete you can animate the radius and x and y attributes to their final positions. Of course this means you'll want good starting positions.

D3: Highlight connected/adjacent nodes [duplicate]

I am working on a force directed graph in D3. I want to highlight the mouseover'd node, its links, and its child nodes by setting all of the other nodes and links to a lower opacity.
In this example, http://jsfiddle.net/xReHA/, I am able to fade out all of the links and nodes then fade in the connected links, but, so far, I haven't been able to elegantly fade in the connected nodes that are children of the currently mouseover'd node.
This is the key function from the code:
function fade(opacity) {
return function(d, i) {
//fade all elements
svg.selectAll("circle, line").style("opacity", opacity);
var associated_links = svg.selectAll("line").filter(function(d) {
return d.source.index == i || d.target.index == i;
}).each(function(dLink, iLink) {
//unfade links and nodes connected to the current node
d3.select(this).style("opacity", 1);
//THE FOLLOWING CAUSES: Uncaught TypeError: Cannot call method 'setProperty' of undefined
d3.select(dLink.source).style("opacity", 1);
d3.select(dLink.target).style("opacity", 1);
});
};
}
I am getting a Uncaught TypeError: Cannot call method 'setProperty' of undefined error when I try to set the opacity on an element I loaded from the source.target. I suspect this is not the right way to load that node as a d3 object, but I can't find another way to load it without iterating over all of the nodes again to find the ones that match the link's target or source. To keep the performance reasonable, I don't want to iterate over all the nodes more than necessary.
I took the example of fading the links from https://bl.ocks.org/mbostock/4062006:
However, that doesn't show how to alter the connected child nodes.
Any good suggestions on how to solve or improve this will be furiously upvoted :)
The error is because you are selecting the data objects (d.source and d.target) rather than the DOM elements associated with those data objects.
You've got the line highlighting working, but I would probably combine your code into a single iteration, like this:
link.style("opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
Highlighting the neighboring nodes is harder because what you need to know the neighbors for each node. This information isn't that easy to determine with your current data structures, since all you have as an array of nodes and an array of links. Forget the DOM for a second, and ask yourself how you would determine whether two nodes a and b are neighbors?
function neighboring(a, b) {
// ???
}
An expensive way to do that is to iterate over all of the links and see if there is a link that connects a and b:
function neighboring(a, b) {
return links.some(function(d) {
return (d.source === a && d.target === b)
|| (d.source === b && d.target === a);
});
}
(This assumes that links are undirected. If you only want to highlight forward-connected neighbors, then eliminate the second half of the OR.)
A more efficient way of computing this, if you have to do it frequently, is to have a map or a matrix which allows constant-time lookup to test whether a and b are neighbors. For example:
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
Now you can say:
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
And thus, you can now iterate over the nodes and update their opacity correctly:
node.style("opacity", function(o) {
return neighboring(d, o) ? 1 : opacity;
});
(You may also want to special-case the mouseovered link itself, either by setting a self-link for every node in linkedByIndex, or by testing for d directly when computing the style, or by using a !important css :hover style.)
The last thing I would change in your code is to use fill-opacity and stroke-opacity rather than opacity, because these offer much better performance.

select only updating elements with d3.js

With a d3.js join Is there a way to select only the 'updating' elements separately from the 'entering' elements?
updateAndEnter = d3.selectAll('element').data(data);
entering = updateAndEnter.enter();
exiting = updateAndEnter.exit();
updatingOnly = ??;
Yes, the selection just after the data join contains the 'update only' elements. After appending to the enter() selection, it will be expanded to include the entering elements as well.
See General Update Pattern:
// DATA JOIN
// Join new data with old elements, if any.
var text = svg.selectAll("text")
.data(data);
// UPDATE
// Update old elements as needed.
text.attr("class", "update");
// ENTER
// Create new elements as needed.
text.enter().append("text")
.attr("class", "enter")
.attr("x", function(d, i) { return i * 32; })
.attr("dy", ".35em");
// ENTER + UPDATE
// Appending to the enter selection expands the update selection to include
// entering elements; so, operations on the update selection after appending to
// the enter selection will apply to both entering and updating nodes.
text.text(function(d) { return d; });
// EXIT
// Remove old elements as needed.
text.exit().remove();
it's my pleasure
For me ( too ) this is a little bit confusing : it seems that the only available set is actually ENTER+UPDATE ( blended together ) and EXIT.
But what if i want to work or at least identify only updated elements?
I wrote a very simple function ( that follows, simply put wrap it in a script tag at the end of a basic html page ) showing this simple dilemma : how do I highlight updated elements ? Only ENTER and EXIT seem to react "correctly"
To test it, just type in chrome console :
manage_p(['append','a few','paragraph'])
manage_p(['append','a few','new','paragraph'])
manage_p(['append','paragraphs'])
I can get green or red highlighting, i can't get white
Maybe we're missing D3Js specs?
Best regards,
Fabrizio
function join_p(dataSet) {
var el = d3.select('body');
var join = el
.selectAll('p')
.data(dataSet);
join.enter().append('p').style('background-color','white');
join.style('background-color','green').text(function(d) { return d; });
join.exit().text('eliminato').style('background-color','red');
}

Resources