Reputation: 175
I am trying to modify code that updates a line graph every second.
The function takes a variable named path and uses it to make an animation on screen as though the line graph is moving.
Here is a code snippet. (The entire code, working example, and citation is linked at the end of this question.)
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
tick();
function tick() {
// push a new data point onto the back
data.push(random());
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
What I am trying to do is change the function so that it takes the path variable as an argument, so that I can use the same function (tick) for modifying various different paths.
However, when I change the above code to something like this:
tick(path);
function tick(path) { /*same as before*/ }
I get the TypeError path.attr is not a function.
And, if instead, I try to do something like this:
tick(d3.select(path));
function tick(path) { /*same as before*/ }
I get the error: cannot read property 'length' of undefined.
Can someone help me out?
Working example: Scroll down to second graph: http://bost.ocks.org/mike/path/
Working code: https://gist.githubusercontent.com/mbostock/1642874/raw/692ec5980f12f4b0cb38c43b41045fe2fe8e3b9e/index.html
Citation for code, example: mbostock on Github
Upvotes: 1
Views: 2782
Reputation: 21578
The error is not caused by the first call to tick(path)
but by subsequent calls when tick()
gets called as a callback you registered by .each("end", tick);
. These calls will not pass the required parameter path
to your function. And, looking at D3's source I don't see a way of passing this parameter to the callback.
You can circumvent these issues by parameterising your callback function with the path you want it to act on:
tick(path)();
function tick(path) {
var paramTick = function() {
// push a new data point onto the back
path.datum().push(random());
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", paramTick);
// pop the old data point off the front
path.datum().shift();
};
return paramTick;
}
This one uses a closure to save the reference to the path for future calls. The function tick(path)
defines an inner function paramTick()
which is basically the function you used previously. It does, however, save the reference to the parameter path
by means of a closure. Notice, that it passes the reference to itself as the callback parameter for the end
event instead of tick()
. This way you'll end up with a callback which gets parameterised by the call
tick(path)();
The call to tick(path)
returns the parameterised version of your rendering logic which is immediately executed by the second set of parentheses.
I set up a working JSFiddle moving two paths to demonstrate this.
Although this does answer your question, it doesn't seem to be a perfect solution for the problem. Looking at the example moving two paths you will notice that they will get slightly out of sync every now and then. This is caused by utilizing different transitions for each path, which will not get perfectly synchronized.
In my opinion the better approch would be to group multiple paths into one g
and apply the transition to this group. With only slight modifications to your original code (JSFiddle v2) this will give a smooth, synchronized movement of all paths.
var data = [
d3.range(n).map(random),
d3.range(n).map(random)
];
var paths = group.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("class", function(d,i) {
return "line" + i;
})
.attr("d", line);
tick();
function tick() {
// push a new data point onto the back
paths.each(function(d) {d.push(random());});
// redraw the line
paths
.attr("d", line);
// Apply the transition to the group
group
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", tick);
// pop the old data point off the front
paths.each(function(d) {d.shift();});
}
Upvotes: 3