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

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.

Related

D3 exit selecting wrong data

I'm trying to build kind of real time graph using D3.js. Code is available at https://plnkr.co/edit/hrawv8CTBIsJf2QWTBMb?p=preview.
The source data represent user authentication results from different organizations. For each organization there is a name, ok count and fail count. The graph should be dynamically (getting the data in loop) updated based on data.
The code is based on https://bl.ocks.org/mbostock/3808234.
There are few problems and few things i'm not sure about.
exit function only selects the red bars based on data update:
// JOIN new data with old elements
// specify function for data matching - correct?
var boxes = svg.selectAll(".box").data(data, function(d) {
return d.inst_name;
});
// EXIT old elements not present in new data
// this works somehow strange
// it does select all red boxes
boxes.exit().transition(t).remove();
Why does exit() select only red bars and not all? From my point of understanding the d3 documentation exit() should only select such elements that do not have any new data. Shouldn't that be all bars in case of infinite loop and constant data file?
This obviously breaks the graph quite a lot (see plunker). I need the exit to select only bars, which are not available in data file anymore. See example below.
initial state of data file:
inst_name,ok,fail
inst1,24,-1
inst2,23,-3
...
updated state of data file:
inst_name,ok,fail
inst1,26,-1
inst14,22,-4
...
The bars (both blue and red) for inst2 from intial state should be removed (and replaced by data of inst14) when the data is updated. Why is this not working?
I've read, that new data are matched against older using index. I've specified that inst_name should be used:
var boxes = svg.selectAll(".box").data(data, function(d) {
return d.inst_name;
});
Is this necessary (I've used it everywhere when inserting data)?
Also the transition for removing the elements does not work. What is the problem?
I'm also not sure if specifying data is necessary when adding new bars:
var boxes = svg.selectAll(".box").data(data, function(d) {
return d.inst_name;
});
.....
// add new element in new data
svg.selectAll(".blue")
.data(data, function(d) { // is this necessary ?
return d.inst_name;
}) // use function for new data matching against inst_name, necessary?
.enter().append("rect")
.transition(t)
.attr("class", function(d) {
return "blue box "
})
.attr("x", function(d) {
return x(d.inst_name);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.ok);
})
.attr("height", function(d) {
return height - y(d.ok + min);
})
Thanks for help.
EDIT
The underlying data get changed by script (this was not written clearly in the original post), so it can change independently of the graph state. The data should be only growing.
You've asked a lot of questions.
Why does exit() select only red bars and not all? From my point of understanding the d3 documentation exit() should only select such elements that do not have any new data. Shouldn't that be all bars in case of infinite loop and constant data file?
First, you build two sets of bars (blue [ok] and red [fail]). When you data bind these you give them the same key function, which identifies them by inst_name. You then do your data update, which now selects all the bars at once with:
svg.selectAll(".box")
You again data-bind with the same key function. Your data has 10 values in the array but you just selected 20 bars. The second 10 bars exit (the red ones) because to d3 they are not in your 10 data-points
The bars (both blue and red) for inst2 from intial state should be removed (and replaced by data of inst14) when the data is updated. Why is this not working?
I don't see that in your plunker, you are giving it the same data over and over.
Also the transition for removing the elements does not work. What is the problem?
You haven't given the transition anything to do. It'll run it, then at the end remove the rects. What you need is something for it to transition, like "height":
boxes.exit().transition(t).attr('height', 0).remove();
This will shrink them to 0 height.
So how do we clean up your code?
First, I would operate on g elements each one paired to an item in your data array. You then place both bars in the g that belong to that data point. Take a look here, I've started to clean-up your code (incomplete, though, hopefully it gets you going).

How to bind d3 generated HTML elements to scope?

I am generating nodes in d3 in an angular directive, and I would like the classes of the nodes to be bound dynamically to an element in my scope. Below is a general outline of what I would like to do:
app.directive('myDirective',function(){
return {
restrict: 'EA',
link: function(scope,element,attrs){
var node = d3.selectAll('.node')
.data(nodes)
.enter().append('circle')
.classed('selected',function(d){return d.id=scope.selected.id})
}
}
})
This is pseudocode, but essentially d3 is generating these nodes/circles, and I want the class of these circles to depend on the value of an element within the scope. So, if at any point in time I modify the scope.selected.id, it should affect the node's class. Using the approach shown above, however, does not work. I have tried modifying the scope.selected.id, but the classes of the nodes are unaffected.
How can I dynamically bind a d3 generated element to scope? I don't want to redraw nodes whenever the scope is modified. I simply want their classes to be binded to the scope.
Add a watch around scope.selected in your link function then wrap your d3 drawing code in a function and call that method when the watch fires.
(BTW a fiddle is always easier to deal with when asking questions)
You might want to read up on the General update pattern for D3 too http://bl.ocks.org/mbostock/3808218
It will both make life easier and your updates faster if you get to grips with the various stages a D3 documents lifecycle has.
function update(data) {
// 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();
}

Circles aren't updating in a multiline graph

I have a D3.js multiline graph with circles added on every path peak. When I update my graph, the paths update just fine with the new data but the circles don't seem to get updated at all. Here's my code: http://jsbin.com/eMuQOHoV/3/edit
Does anyone know what I'm doing wrong?
You need to update the data point circles in the same way that you've created them. In particular, you're using a nested selection when creating them, but not when updating. This means that the data won't get matched correctly on update and nothing happens.
The code for the update should look like this.
var sel = svg.selectAll('.series')
.data(sources);
sel.select('path')
.transition()
// etc
// update circles
sel.selectAll('.datapoint')
.data(function (d) {
return d.values;
})
// etc
Complete jsbin here.

Chained animations/transitions over each graph node - D3.js

I want to be able to change the radius of each node in my graph that i am creating using d3.js. However, i want to change the radius of each node, one at a time, and i want to able to control the delay between each change along with the sequence of the nodes.
For now this is what i have in terms of code:
var nodes = svg.selectAll(".node");
nodes.each(function() {
d3.select(this).
transition().
delay(100).
attr("r", "5")
});
You can replicate this simply by using the code at this link: http://bl.ocks.org/mbostock/4062045. The code that i have pasted above is simply an addition to the code at the aforementioned link.
When i run this, all the nodes in my graph transition simultaneously, i.e. grow in size (radius) simultaneously. I however want them to transition i.e. grow in size (radius), one at a time. I repeat that i want to be able to control:
the delay between the transition of each node and
the order of nodes that undergo the transitions.
Any pointers, tutorials, or even other stackoverflow answers would be great. I would ideally want some code examples.
The closest i have come to in terms of online references is this subsection of a tutorial on d3.js transitions: http://bost.ocks.org/mike/transition/#per-element. However, it lacks a concrete code example. I, being new to d3.js and javascript in general, am not able to pick it up without concrete code examples.
You can do this quite easily by calculating a delay based on each node's index. Mike Bostock has an example of such an implementation here. This is the relevant code:
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); }); // this would be .attr('r', ... ) in your case
To control the order of the transition, all you would then have to do is sort the array so that the elements' indices reflect the animation flow you want. To see how to sort an array, refer to the documentation on JavaScript's array.sort method and also see the Arrays > Ordering section of the D3 API reference.

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