Executing an animation with for loop in callback function - javascript

Inside updateMapColor function, the world map changes colors based on the input year's value. I am trying to animate the color change over a sequence of years by calling updateMapColor, but it's not working.
Do I need to use setInterval, if so why? Could someone explain the underlying issue please?
d3.select('body').append('button').attr({
class: "button",
id: "animateMap"
})
.text("Animate the map")
.on("click", function (d) {
for (i = 0; i < yearArray.length; i++) {
updateMapColor[yearArray[i]]
}
})
var updateMapColor = function (y) {
year = y;
var rankingList = {}
coloredWorldMap.transition().duration(700).attr('fill', function (d) {
a = d;
x = d3.values(tennis).filter(function (d) {
return (d.DATE == year)
})
rankingList = x;
x = d3.values(x).filter(function (d) {
return (d.COUNTRY_ID == a.id)
})
if (x.length != 0) {
if (x[0].COUNT == 0) {
return "#fff"
}
else {
return colorScale(x[0].COUNT)
}
}
else {
return '#fff'
}
return colorScale(x[0].COUNT)
})

This is a classic problem in javascript: trying to call a function inside a for loop with some delay.
The reason your code doesn't work is simple: the for loop will run almost immediately (in just a few miliseconds for a hundred or so loops), and all the calls to updateMapColor will happen almost simultaneously.
There are different ways to fix this. One of my favourite solutions is using an IIFE:
(function loop(i) {
if (i++ >= (yearArray.length - 1)) return;
setTimeout(function() {
updateMapColor(i);
loop(i)
}, 500)
})(-1);
Explanation:
This IIFE will run for the first time with i = -1. Then, the if increases i (doing i++, which makes i effectively starting at 0, just like in your for loop) and checks if it is equal or bigger than yearArray.length - 1. If it is, the function loop returns. If it's not, a setTimeout is scheduled, which calls updateMapColor and the very function loop again.
Check the demo, with a setInterval of 0.5 seconds:
var yearArray = d3.range(20);
(function loop(i) {
if (i++ >= (yearArray.length - 1)) return;
setTimeout(function() {
updateMapColor(i);
loop(i)
}, 500)
})(-1);
function updateMapColor(index){
console.log("The function was called with i = " + index);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
PS: whatever you're trying to do, it'd be way easier just chaining a bunch of D3 transitions.

Related

Javascript decrement from 10 then do something when 0

I have a function that when called will decrease by 1. It is called when a user reports something. I want to be able to store this and then when it hits 0, to execute an action.
function userReported() {
console.log('user report ' + add());
var add = (function () {
var counter = 10;
return function () {
counter -= 1;
return counter;
}
})();
}
Now the problem is I can return the counter so it logs down from 10. But the issue I have is that I can seem to add an if/else before returning counter as it does not store the variable.
I attempted the following but it doesn't work and I don't know how to return something > store it, and at the same time check its value. I also tried a while loop but failed too.
function userReported() {
var limit = add;
if ( limit <= 0 ) {
console.log('Link does not work!');
}
else {
console.log('user report ' + limit);
}
var add = (function () {
var counter = 10;
return function () {
counter -= 1;
return counter;
}
})();
}
How do I go about creating a value, increment/decrement said value, then when it reaches a number -> do something?
You would typically do this with a function that returns a function that captures the counter in a closure. This allows the returned function to maintain state over several calls.
For example:
function createUserReport(limit, cb) {
console.log('user report initiated' );
return function () {
if (limit > 0) {
console.log("Report filed, current count: ", limit)
limit--
} else if (limit == 0) {
limit--
cb() // call callback when done
}
// do something below zero?
}
}
// createUserReport takes a limit and a function to call when finished
// and returns a counter function
let report = createUserReport(10, () => console.log("Reached limit, running done callback"))
// each call to report decrements the limit:
for (let i = 0; i <= 10; i++){
report()
}
You can of course hard-code the callback functionality and limit number into the function itself rather than passing in arguments.
Ok, if you need to get a report based on an external limit, you could do something like that:
var limit = 10;
function remove() {
limit -= 1;
}
function userReport() {
if (limit <= 0) {
console.log("Link does not work!");
} else {
remove();
console.log(`User report: ${limit}`);
}
}
userReport();
If that's what you want, removing the remove function from userReport and taking the limit variable out will make things work

Queue function call in javascript

I have functions that contain timed animation and i want these functions to run one after the other just when the previous one is done.
function a() {
var i = 0;
var x = setInterval(function () {
console.log('a' + i);
if (i == 3) {
console.log('Done #' + i);
clearInterval(x);
return true;
}
i++;
}, 1000);
}
function b() {
var c = 0;
var y = setInterval(function () {
console.log('b' + c);
if (c == 1) {
console.log('Done 2 #' + c);
clearInterval(y);
return true;
}
c++;
}, 1000);
}
a().then(b());
I tried one here but it's not working. Here's the fiddle.
What i want to achieve is like this:
a0
a1
a2
a3
Done #3
b0
b1
Done 2 #1
but it stops at Done #3. Im looking for any other ways to achieve this.
If you want to call then method – function must return promise, after then you don’t want to immediate call b() but pass reference to it a().then(b);
function a() {
var i = 0;
var defer = $.Deferred();
var x = setInterval(function () {
console.log('a' + i);
if (i == 3) {
console.log('Done #' + i);
clearInterval(x);
defer.resolve('asdasd');
}
i++;
}, 1000);
return defer;
}
function b() {
var c = 0;
var y = setInterval(function () {
console.log('b' + c);
if (c == 1) {
console.log('Done 2 #' + c);
clearInterval(y);
return true;
}
c++;
}, 1000);
}
a().then(b);
jsfiddle: http://jsfiddle.net/Lungx/3/
Two simpler approaches that should generally work, though maybe not in your case.
(1) jQuery. If you're using the jQuery animate() function, then you can set each animation in the callback for the animation previous. That way when animation n finishes, your callback function will start the animation for n+1, etc. There is a discussion and a couple examples here.
(2) If you're animating (or transitioning, really) CSS, you can attach a handler to the transitionend event. That way, similar to the first approach, you can have the next transition begin as soon as the previous transitionend event fires. See here, especially under the header "Detecting the completion of a transition."
I've used the first approach before, and I don't see any reason why the second shouldn't work. I hope this helps. Let me know if it doesn't work; I'm curious.

creating a timer using setInterval that can clean up after itself?

I want to use setInterval to animate a couple things. First I'd like to be able to specify a series of page elements, and have them set their background color, which will gradually fade out. Once the color returns to normal the timer is no longer necessary.
So I've got
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(itvlH);
// but itvlH isn't in scope...?
}
},50);
}
Further complicating the situation is I'd want to be able to have multiple instances of this going on. I'm thinking maybe I'll push the live interval handlers into an array and clean them up as they "go dead" but how will I know when they do? Only inside the interval closure do I actually know when it has finished.
What would help is if there was a way to get the handle to the interval from within the closure.
Or I could do something like this?
function intRun() {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// now I can access an array containing all handles to intervals
// but how do I know which one is ME?
clearInterval(itvlH);
}
}
var handlers = [];
function setFadeColor(nodes) {
var x = 256;
handlers.push(setInterval(intRun,50);
}
Your first example will work fine and dandy ^_^
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
clearInterval(itvlH);
// itvlH IS in scope!
}
},50);
}
Did you test it at all?
I've used code like your first block, and it works fine. Also this jsFiddle works as well.
I think you could use a little trick to store the handler. Make an object first. Then set the handler as a property, and later access the object's property. Like so:
function setFadeColor(nodes) {
var x = 256;
var obj = {};
// store the handler as a property of the object which will be captured in the closure scope
obj.itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(obj.itvlH);
// but itvlH isn't in scope...?
}
},50);
}
You can write helper function like so:
function createDisposableTimerInterval(closure, delay) {
var cancelToken = {};
var handler = setInterval(function() {
if (cancelToken.cancelled) {
clearInterval(handler);
} else {
closure(cancelToken);
}
}, delay);
return handler;
}
// Example:
var i = 0;
createDisposableTimerInterval(function(token) {
if (i < 10) {
console.log(i++);
} else {
// Don't need that timer anymore
token.cancelled = true;
}
}, 2000);

Create a JavaScript loop without while

I am trying to create a page which needs to preform lots of loops. Using a while/for loops cause the page to hang until the loop completes and it is possible in this case that the loop could be running for hours. I have also tried using setTimeout, but that hits a recursion limit. How do I prevent the page from reaching a recursion limit?
var looper = {
characters: 'abcdefghijklmnopqrstuvwxyz',
current: [0],
target: '',
max: 25,
setHash: function(hash) {
this.target = hash;
this.max = this.characters.length;
},
getString: function() {
string = '';
for (letter in this.current) {
string += this.characters[this.current[letter]];
}
return string;
},
hash: function() {
return Sha1.hash(this.getString());
},
increment: function() {
this.current[0] += 1;
if (this.current[0] > this.max) {
if (this.current.length == 1) {
this.current = [0, 0];
} else {
this.current[1] += 1;
this.current[0] = 0;
}
}
if (this.current[1] > this.max) {
if (this.current.length == 2) {
this.current[2] == 0;
} else {
this.current[3] += 1;
this.current[2] = 0;
}
}
},
loop: function() {
if (this.hash() == this.target) {
alert(this.getString());
} else {
this.increment();
setTimeout(this.loop(), 1);
}
}
}
setInterval is the usual way, but you could also try web workers, which would be a more straightforward refactoring of your code than setInterval but would only work on HTML5 browsers.
http://dev.w3.org/html5/workers/
Your setTimeout is not doing what you think it's doing. Here's what it's doing:
It encounters this statement:
setTimeout(this.loop(), 1);
It evaluates the first argument to setTimeout, this.loop(). It calls loop right there; it does not wait for a millisecond as you likely expected.
It calls setTimeout like this:
setTimeout(undefined, 1);
In theory, anyway. In reality, the second step never completes; it recurses indefinitely. What you need to do is pass a reference to the function rather than the returned value of the function:
setTimeout(this.loop, 1);
However, then this will be window on the next loop, not looper. Bind it, instead:
setTimeout(this.loop.bind(this), 1);
setInterval might work. It calls a function every certain amount of milliseconds.
For Example
myInterval = setInterval(myFunction,5000);
That will call your function (myFunction) every 5 seconds.
why not have a loop checker using setInterval?
var loopWorking = false;
function looper(){
loopWorking = true;
//Do stuff
loopWorking = false;
}
function checkLooper()
{
if(loopWorking == false)
looper();
}
setInterval(checkLooper, 100); //every 100ms or lower. Can reduce down to 1ms
If you want to avoid recursion then don't call this.loop() from inside of this.loop(). Instead use window.setInterval() to call the loop repeatedly.
I had to hand-code continuation passing style in google-code prettify.
Basically I turned
for (var i = 0, n = arr.length; i < n; ++i) {
processItem(i);
}
done();
into
var i = 0, n = arr.length;
function work() {
var t0 = +new Date;
while (i < n) {
processItem(i);
++i;
if (new Date - t0 > 100) {
setTimeout(work, 250);
return;
}
}
done();
}
work();
which doesn't hit any recursion limit since there are no recursive function calls.

Javascript - making 2 setInterval() be synchronous

I have 2 functions calling setInterval but I need them to be synchronous. Here is my code(yes, they are really simple).
var number1 = 0;
function caller(){
go();
come();
}
function go() {
anim1 = setInterval("doActionGo()", 20);
}
function come() {
anim2 = setInterval("doActionCome()", 20);
}
function doActionGo(){
if(number1 < 1023) {
number1++;
} else {
clearInterval(anim1);
}
}
function doActionCome() {
if (number1 > 0) {
number1 = number1 - 1
} else {
clearInterval(anim2);
}
functions doActionGo() and doActionCome() would be any code. Does anybody know how to solve it?
Regards!
To execute two animations in sequence just start the second one at the end of the first... for example:
var anim1, anim2;
var number = 0;
function animation1()
{
number = number + 1;
if (number > 1000)
{
clearInterval(anim1);
anim2 = setInterval(animation2, 20);
}
}
function animation2()
{
number = number - 1;
if (number < 0)
{
clearInterval(anim2);
}
}
function start()
{
anim1 = setInterval(animation1, 20);
}
Here is way to call them in sequence:
var number1 = 0;
function caller(){
go( come );
}
function go(comeFn) {
var anim = setInterval(function(){
if(number1 < 100) {
number1++;
} else {
clearInterval(anim);
comeFn();
}
}, 20);
}
function come() {
var anim = setInterval(function(){
if (number1 > 0) {
number1--;
} else {
clearInterval(anim);
}
}, 20);
}
And two comments:
you shouldn't use strings in setInterval('functionName', 30) but directly functions
if you don't use anim1 instead of var anim1 it is considered as a global variables
See demo of the following →
It wasn't really clear what you were asking, but it inspired me to write this mini animation queue. Maybe you or someone else will benefit from it:
var number1 = 0,
number2 = 1023,
queue = [];
// execute every XX miliseconds
function tick(delay) {
// iterate through queue
for (var i = 0, il = queue.length; i < il; i++) {
if (queue[i]) {
// execute each tick function
queue[i].tick();
}
}
// recall tick
setTimeout(function() {
tick(delay);
}, delay);
}
// kill tick function of matching name
function qKill(name) {
for (var i = 0, il = queue.length; i < il; i++) {
if (queue[i]) {
if (queue[i].name === name) {
queue.splice(i, 1);
}
}
}
}
var go = {
name: 'go',
tick: function() {
if (number1 < number2) {
number1++;
// do whatever here
} else {
qKill('go');
}
}
};
var come = {
name: 'come',
tick: function() {
if (number2 > number1) {
number2 = number2 - 1;
// do whatever here
} else {
qKill('come');
}
}
};
queue.push(go);
queue.push(come);
tick(20);
Demo →
As people already commented, JavaScript is single threaded. So running two functions simultaneously is impossible. However a couple of strategies can be used to simulate this.
For GUI effects, interpolation can be used. Basically, a single function is responsible of transitioning things step by step by interpolating the start and end value(be it an opacity, a position, a variable, a state or whatever can be interpolated). A javascript library can use this simple concept to create effects for example.
A second approach is to use co-routines available in javascript since version 1.7. This article (although a little old) describes how to simulate threading in javascript and can be a good starting point to understand the mechanism involved in a co-routine powered design.
Hope this will help :)

Categories