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

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.

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).

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.

angular.js $apply bottleneck

I am using realtime data to draw some lines using SVG on a webpage. To manage the data I am using Angular.js and to manage the visualization I use D3.js.
I set up a controller in angular that holds the data(lines). The data consists of some arrays of points (dictionary with x/y coordinates). Some lines are known at initialization, others are updated according to live data.
I set up an angular directive ('topView') which contains an SVG element. For each line at initialization, I add it as a path using:
var routeLeftLine = container.select("#routes").append("path");
var routeLeftLineData = scope.val.route.left; // -> 1000+ points in there
routeLeftLine
.attr("d", lineFunction(routeLeftLineData))
.attr("stroke", "black")
.attr("stroke-width", 1)
.attr("fill", "none");
For each line I want to keep updating (1), I set up an angular directive, for instance:
<surface-cable val="data.cable"></surface-cable>
where data is my data object on the controller and data.cable the array of points.
The directive looks like this:
OCMSWeb.directive('surfaceCable', function ( /* dependencies */ ) {
return {
restrict: 'AE',
scope: {
val: '='
},
templateNamespace: 'svg',
replace: true,
template: '<g/>',
link: function (scope, element, attrs) {
var cableLine = d3.select(element[0]).append("path");
scope.$watch('val', function () {
var cableLineData = simplify(scope.val, 1, false); // size grows in time
cableLine
.attr("d", lineFunction(cableLineData))
.attr("stroke", "rgb(240,144,32)")
.attr("stroke-width", 1)
.attr("fill", "none");
}, true);
}
};
});
The structure works fine when I am updating the data using a timer, the change is reflected in the SVG.
The problem arises when I increase the number of points(>1000... I'll need even more in the future) that are in a line (both the non-changing, and the updated line have this effect) the performance degrades. The updating of the line becomes terribly slow, even when the elements to redraw do not contain many elements yet.
I can't find the cause. Does SVG/d3/angular render all elements in a svg again?
Is my way of binding data inefficient? Should I skip d3 all together?
I have tried to profile the javascript performance, and about 80-90% of the CPU time seems to go towards calls of angular $apply (which, I think, scans the DOM for changes?). Why does $apply take so long if an element (the line is a <path> element) has many data-points?
With this architecture, 1000 lines means 1000 directives, 1000 watches, and 1000 value comparisons every time you change anything on your scope, whether or not those values have actually changed. I doubt the root problem here is your d3 code, though reseting attributes for stroke, stroke-width, and fill unnecessarily certainly doesn't help.
Generally speaking, the better way to do this would be to have a single directive that takes an array of lines and handles the layout of all of your cable paths in the SVG. If your are looking at 10s of thousands of paths, then you might want to look at rendering them on canvas instead of SVG.
Although #ethan-jewett did not fully answer my question, you did
point me in the right direction though.
Because I linked the 'data' dictionary of my controller (containing both static and dynamic data) to the directive, I presume that angular does check all values in there for change. By moving my static data out of this 'data' dictionary, it does not get checked and makes this setup considerably faster.
Profiling still yields that angular gets slow when I increase the size of the dynamic data, and I assume this is for the same reason (angular needing to check all data for change). I am not sure on how I'll tackle this: I'll investigate whether D3.js has a more efficient mechanism for detecting changes in the data, or I'll split my long arrays in a static and a dynamic part (since they represent paths/cables, only the end of the cable can actually change. At some point a large part of the cable can be considered static.).

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.

second d3.js triggered from first doesn't fully iterate over X axis

Folks -
I'm now trying to trigger a second chart based on which series in the first chart is clicked on.
Based on which is chosen, one of two data sets are sent to the function:
.on("mouseup", function(d) {return d.myCat == 0 ? updateData(yesXYZData) : updateData(nonXYZData)})
This part works, but I'm getting one big stack in the target div, not the iteration I am expecting.
function updateData(whichDataSet) {...
I've tried putting the updateData() function into the window.onload function, duping or reusing various elements (since the domain and range for the X axis are the same, I expect to reuse).
[Note- I have taken Lars Kothoff's advice regarding numbers in the data object. Also, I will create a better data structure later, using crossfilter.js and native d3.js data manipulation- for now I need a working prototype demonstrating functionality.]
here is the gist:
https://gist.github.com/RCL1/6906892
Thanks in advance!
-RL
line 242 of the gist. I needed to use a non-filtered version of the data to calculate x axis (totalAll.map).

Categories

Resources