Note, I was going to close this as a duplicate because I think I understand the mistake of the OP. I created an answer, instead, because I'm just guessing at the problem and wanted to provide a bit of explanation.
Your contention that .on("end") call gets invoked before the duration(3000)
is incorrect. The end callback does in fact fire after 3 seconds. My guess is you are miss understanding this statement in the documentation:
When a specified transition event is dispatched on a selected node,
the specified listener will be invoked for the transitioning element,
being passed the current datum d and index i, with the this context as
the current DOM element. Listeners always see the latest datum for
their element, but the index is a property of the selection and is
fixed when the listener is assigned; to update the index, re-assign
the listener.
What this is saying is that the event will fire for each element in the transition, so 10 elements means 10 events. The first one will occur after 3 seconds, the rest 0 to 1 milliseconds after.
See this code snippet:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<svg>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
</svg>
<script>
dataForKey = {
0: d3.range(10),
1: d3.range(10),
2: d3.range(10),
3: d3.range(10)
};
var date = new Date();
next(1);
function next(keyIndex) {
if (keyIndex < 3) {
d3.selectAll(".dot")
.data(dataForKey[keyIndex])
.transition().duration(3000)
//.call(position)
.on("end", function() {
console.log('end=', new Date().getTime() - date.getTime());
date = new Date();
next(keyIndex + 1)
});
}
}
</script>
</body>
</html>
The console output you'll see will be something like:
end= 3008 //<-- first element after 3 seconds
end= 0 //<-- other 9 events after 0 to 1 milliseconds
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
Now I think your question becomes, how do I fire a callback after all transitions have ended. This has been covered many times here.
EDITS
Thanks for the fiddle, always helps to reproduce the issue. And the code is different in the fiddle then what you posted in the question.
Your problem is actually much simpler; .on('end', func)
expects a function as the second arguement and you aren't giving it a function, you are calling a function and giving it the return value of the function (nothing). Just wrap it in an anon function:
.on("end", function(d){
next(keyIndex + 1)
});
Updated fiddle.