add multiple charts d3, nvd3 using for loop - javascript

I'm trying to generate multiple charts using nvd3 and d3. I have the right amount of divs.
If I remove the forloop, then I get a chart in #chart1. If I put the for loop then I get a chart ONLY in #chart2.
Can anyone see why?
for (var j = 1; j <= 2; j += 1) {
var s = '#chart' + j.toString() + ' svg';
console.log(s);
nv.addGraph(function() {
var chart = nv.models.lineChart();
chart.xAxis.axisLabel('Time step').tickFormat(d3.format(',r'));
chart.yAxis.axisLabel('eig(' + j.toString() + ')').tickFormat(d3.format('.02f'));
d3.select(s).datum(function() {
var sin = [], cos = [];
for (var i = 0; i < 100; i++) {
sin.push({
x : i,
y : Math.sin(i / 10)
});
cos.push({
x : i,
y : .5 * Math.cos(i / 10)
});
}
result = [];
result.push({
values : sin,
key : 'sin',
});
return result;
}).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
}

Firstly it's not common to use a for loop like you have (Data Driven Documents). In d3 it is preferred to select all the elements you want and use .each() like so
d3.selectAll('.chart svg')
.each(function(data){
// Do what you would have done in the loop here
})
Secondly it looks like there is an issue with using an anonymous function the way you have (not sure why and not spent too much time looking). By calling it as an actual function it works.
nv.addGraph(addMyChart(this))
See this JSFiddle http://jsfiddle.net/a5BYP/

Stumbled upon this when I was facing the same issue. Hope this helps another beginner like myself.
nv.addGraph() takes a function as callback. This function you passed is not executed immediately but is instead pushed onto the event loop and executed some time later. Internally nv.addGraph is actually quite simple and it makes use of setTimeout.
The reason why for-loop didn't work is because of Javascript scoping. It's the same reason why this code prints 5 5 times instead of 0,1,2,3,4.
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 0)
}
In JS, the var keyword declares a variable to the enclosing function scope (it ignores block scope - for or if curly braces). If you put all the above code in $() then the i variable would be available everywhere inside the $().
When the callback function is executed, it has access to the parent environment where it was first declared.
Inside the callback function, it encounters i. Since i is not declared inside the callback function, it goes one level up to look for i. It finds i in the enclosing function scope but i variable has already been updated to 5 before the callback function was even run.

Related

How do I loop through an array of objects and fill the object in the mouseover function?

I'm a first year student I've been scrolling through Stack Overflow and have read a lot about the object problem (reference) but I can't figure out the solution to my problem.
I have made arrays of objects and looping over them to fill a div with all the info like img, name, value, so far no problem here.
The problem is with filling a mouseover function (attached to the image) with the object I'm looping through at the moment, so later when I hover over the image all the info of that particular object is shown on another div.
for (i = 0; i < arrgezelschap.lenght; i++) {
var x = arrgezelschap[i];
var element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
element.addEventListener('mouseover', function() {
showinfo(x)
});
inhoud.append(element);
}
In the function showinfo(object) the output is always the last object of the array.
Why is this and what do I need to do so that it saves or points to the object that it's looping through at the moment in my function?
TL;DR: change var x to let x
I can't really do a better job explaining than Creating closures in loops: A common mistake, but I'll take a shot at rephrasing it.
Compare the output of these two snippets (below). The only difference is var vs let. The example demonstrates creating 5 functions in a loop, but does not call them yet. Each function references variables declared inside the loop, outside the loop and in the for itself. Then, at the end of the loop, we call all the functions to see what we got.
In the first case, the variables outside, i (the loop variable) and inside (declared inside the loop) are all declared with var. They are the same variable on every iteration of the loop. The inside var is hoisted to the top of the scope (outside the loop).
When we call all the functions we created, we will see that they all refer to the one-and-only instance of each variable, and they all have the value that the variables have after completion of the loop.
let functions = [];
var outside = 0;
for (var i = 0; i < 5; ++i) {
outside = i * 10;
var inside = i * 100;
functions.push(() => { console.log(outside, i, inside); })
}
functions.map(f => f()); // call all the functions
Output:
40 5 400
40 5 400
40 5 400
40 5 400
40 5 400
In this second example, the variables are all declared with let. The variable i declared in the for and the variable inside declared inside the body of the loop are different variables on each iteration of the loop. But the outside variable is declared outside the loop, so there's still only one outside variable that is used in every iteration of the loop.
When we call all the functions we made this time, we see that each function is displaying a different variable i and inside and their values are the value they held during that particular iteration of the loop, because the variables only existed for that iteration of the loop and the function was bound to the instance of the variable that was used for that iteration. But the outside variable is the same variable every iteration and holds only one value: the value that it has at the end of the loop.
let functions = [];
let outside = 0;
for (let i = 0; i < 5; ++i) {
outside = i * 10;
let inside = i * 100;
functions.push(() => { console.log(outside, i, inside); })
}
functions.map(f => f()); // call all the functions
Output:
40 0 0
40 1 100
40 2 200
40 3 300
40 4 400
In your case, each function binds to the same (one and only) variable x. If you change your declaration of x from var x to let x then you will get a different variable x for each iteration of the loop, and the event listener function will be bound to a different x each time, which will have the value corresponding to that iteration of the loop.
Footnote: Hopefully functions.map(f => f()); is not confusing for you. It just calls all the functions in the array. It is the same as this:
for (var index = 0; index < functions.length; ++index) {
functions[index]();
}
This is because x is a reference here, not a value and it change while you loop. Have a look at this :
let x = 0;
let fcn = a => console.log(a);
function execAnotherFcn(fcn) {
fcn(x);
}
execAnotherFcn(fcn);
x++;
execAnotherFcn(fcn);
You could use the dataset attribute to store your information.
Here's my implementation:
const root = document.querySelector('#root');
function createImagePlaceholder(color, data) {
const el = document.createElement('div');
el.style.width = '50px';
el.style.height = '50px';
el.style.margin = '5px';
el.style.backgroundColor = color;
el.dataset = data;
root.appendChild(el);
el.addEventListener('mouseover', () => {
document.querySelector('pre').innerText = JSON.stringify(data);
});
el.addEventListener('mouseleave', () => {
document.querySelector('pre').innerText = '';
});
}
createImagePlaceholder('red', { text: 'I am a red block' });
createImagePlaceholder('blue', { text: 'I am a blue block' });
<div id="root"></div>
<pre><pre>
You can fix this by making the scope of element block level.
This happens because here the value of x is send as a closure and the var is defined as function level.The event listner function will get executed at a future time(not to the main thread), so at that time the value of x is changed by the loop to the last value.
This can be done using the let key word or using a IIFE.
1.
for (i = 0; i < arrgezelschap.length; i++) {
let x = arrgezelschap[i];
let element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
element.addEventListener('mouseover', function() {
showinfo(x)
});
inhoud.append(element);
}
2.
for (i = 0; i < arrgezelschap.lenght; i++) {
var x = arrgezelschap[i];
var element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
(function(x){element.addEventListener('mouseover', function() {
showinfo(x)
});})(x);
inhoud.append(element);
}
#PopHips answer explains the theory of what is going wrong. so here is a working example with your code so you can follow it.
for(i =0;i<arrgezelschap.lenght;i++){
var x = arrgezelschap[i];
var element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
element.dataset.identifyer = i;
element.addEventListener('mouseover', function(e) {
showinfo(arrgezelschap[e.target.dataset.identifyer])
});
inhoud.append(element);
}
So because we're using an event listener it will give the first param as an EventArgs object, this contains a property called target that is the HTMLElement effected. we can use the dataset (data-) system to save the identifier to the object's dataset so we can use it in the event handler.
Please note this answer should not be used as it is, there is some really bad practice in this answer, NEVER CREATE A FUNCTION INSIDE A LOOP in production code.

Strange behaviour of for loop in Heap's algorithm in JavaScript

I'm trying to implement Heap's algorithm to find different permutations of a string and found a strange behaviour with a for loop, here's the code
function permAlone(str) {
var strArr = str.split(''),
permutations = [];
function swap(strArr, x, y) {
var tmp = strArr[x];
strArr[x] = strArr[y];
strArr[y] = tmp;
}
function generate(n) {
if (n === 1) {
permutations.push(strArr.join());
} else {
for (var i = 0; i != n; i++) {
generate(n - 1);
swap(n % 2 ? 0 : i, n - 1);
}
}
}
generate(strArr.length);
return permutations;
}
console.log(permAlone('aab'));
In the for loop within the generate function, if I put i = 0 the output of the script is ['a,a,b', 'a,a,b'] but if I put var i = 0 the output is ['a,a,b', 'a,a,b', 'a,a,b', 'a,a,b', 'a,a,b', 'a,a,b'].
I understand that var i would create a local variable for the loop, but don't understand in this case why it would change how the loop functions as there is no i variable declared anywhere else in the script.
Thanks any help appreciated.
The reason the behaviour changes if you have a global i variable is that you have multiple recursive calls to generate() all trying to control their own partially complete for loops with the same variable, and all setting i back to 0 when they start.
Picture what happens on the second iteration of the for loop: i is 1 because it has just been incremented, but then immediately a recursive call to generate() starts its own loop and sets i back to 0 again.
If you create a local variable with var then each for loop in each recursive call is independent of all the others.
Try stepping through the code with the debugger, or try adding the following as the first line inside the for loop and watch what happens to the variables when the code runs:
console.log('n:' + n + '; i: '+i);

Javascript returns negative number for index

Getting the negative value when i perform the onclick function in javascript
function sun()
{
var d,i;
var t = document.getElementById("table");
var rows = t.getElementsByTagName("tr");
for (i = 0; i < rows.length; i++) {
console.log("inside............." + i);
rows[i].onclick = function() {
d = (this.rowIndex);
console.log(d);
};
}
}
Though I'm not sure it's what's causing the exact issue you're noticing, you've encountered a pretty common JavaScript pitfall here by using a closure (anonymous function) inside of a loop. JavaScript, like many other languages that support functional programming, has the convenient property that functions can "close scope" around any variables visible to them at the time of their creation. So, as you've done there, you can use the value of d (or i) inside your function so long as it can see them when your function is declared.
Something funny happens inside a loop, though: every function you create within the loop shares the same scope, meaning they all share the exact same copies of d and i. As a result, when you click on any of your rows, the values of d and i used will be their values at the end of the loop, not the particular iteration you're targeting.
This is ordinarily fixed using something known as the "generator pattern," where you create a separate function that returns new functions closed over your desired scope. For example, in your code, you might do something like
function generateClickHandler(i, d) {
return function() {
d = (this.rowIndex);
console.log(d);
};
}
function sun()
{
var d,i;
var t = document.getElementById("table");
var rows = t.getElementsByTagName("tr");
for (i = 0; i < rows.length; i++) {
console.log("inside............." + i);
rows[i].onclick = generateClickHandler(i, d);
}
}
The new function generateClickHandler returns a function itself, but the important thing to notice here is that the returned function closes over the local arguments i and d, not the shared i and d values used in the loop — their values get copied when you call generateClickHandler. In this way, your code won't be subject to strange closure effects.

Javascript: How to modify global variables within functions?

Below is the code I'm working on. I'm wondering why, when I change the variable ttt in the function, the changes do not stay outside of the function? I've declared it as var ttt = new Array; at the very top.
Also, why can't I use the variable i in the function?
code:
client.on('connection', function()
{
var sss;
var aaa;
console.log('Connected');
for (i = 0 ; i < 120 ; i++)
ttt[i] = 0;
for (i = 0 ; i < 9 ; i++)
{
client.getMatchHistory(434582, function(err, result)
{
sss = JSON.stringify(result);
var myObject = eval('(' + sss + ')');
console.log (myObject.object.data[i].object.value);
ttt[myObject.object.data[i].object.value]++;
});
}
for (i = 0 ; i < 120 ; i++)
console.log ("Record" + i + " Successes: " + ttt[i]);
});
As you pointed out, there are two separate problems with your code, and they're both somewhat related. First, ttt is being modified globally. The problem is that you're checking for the modifications before they happen. I suspect that client.getMatchHistory() is making an asynchronous call. There's no guarantee that all the asynchronous operations in the second for loop will be done executing by the time you execute the third for loop, where you read the array.
The second problem is one of scope, but it's not global scope that's your problem. Since client.getMatchHistory() is asynchronous, the callbacks will be called once the loop is done executing. Once the loop's done executing i will have a value of 10. Likely this is not what you intended. You need to create a callback generating function, that takes the value of i, and returns a function that can be used as a callback. Like this:
function make_callback(i) {
return function(err, result) {
// The body of your callback in here
};
}
And then you should use it like this in the body of the loop:
client.getMatchHistory(434582, make_callback(i))
This will capture the value of i in the current iteration and the generated callback will use that value when executed. That should fix your problem with i.
First of all, all globals variables are effectively 'window' object fields, so you can use window.ttt to be sure you are using global variables instead of local. This code should work, so did you try it in developer tool? what does debugger say about presence of such variable?
As for variable i: sure, you can use it, but it better to use it locally, defining 'var i;' on the top of the function to not spoil global namespace.
The client.getMatchHistory probably asynchronous request, you expect that after loop, you will have filled ttt array, to acheive this you have to make a handler which run after last loop step:
var afterloop=function() {
for (var i = 0 ; i < 120 ; i++)
console.log ("Record" + i + " Successes: " + ttt[i]);
}
for (var i = 0 ; i < 120 ; i++)
ttt[i] = 0;
var i_final=0;
for (var i = 0 ; i < 9 ; i++)
{
var i=i; //localise i
client.getMatchHistory(434582, function(err, result)
{
i_final++;
sss = JSON.stringify(result);
var myObject = eval('(' + sss + ')');
console.log (myObject.object.data[i].object.value);
ttt[myObject.object.data[i].object.value]++;
if (i_final>8) {afterloop();}
});
}
in the sample, i_final counts done requests, they can be done in random order, due to async, so you can't refer to i when deciding to run afterloop() , when i_final count to more than last executed request, you run function that should be executed after last request is done.
Note: please use global vars as less as possible, in your code you used global i without any reason

JS setTimeout Not Delaying

Alright guys, I'm trying to add numbers on my page every 1/4 second or so. So the change is visible to the user. I'm using setTimeout and all my calculations are occurring correctly but without any delay. Here's the code:
for(var i = 0; i < 10; i++)
{
setTimeout(addNum(i),250);
}
I've also tried capturing the return value:
for(var i = 0; i < 10; i++)
{
var t = setTimeout(addNum(i),250);
}
I've also tried using function syntax as part of the setTimeout params:
for(var i = 0; i < 10; i++)
{
var t = setTimeout(function(){array[j].innerHTML + 1},250);
}
I've also tried putting code in a string & the function call in a string. I can't ever get it to delay. Help Please!
How about:
var i=0;
function adder() {
if(i>=10) {return;}
addNum(i++);
setTimeout(adder,250);
}
adder();
When you did setTimeout(addNum(i),250); you executed the function straight away (function name followed by () will execute it right away and pass the return value to the timeout to be executed 1/4 second later). So in a loop that would just execute all 10 immediately. Which is what you saw.
Capturing the return value var t = setTimeout(...); is helpful, but not in your use case; the value is the timer id number, used for cancelling the timeout.
Not sure what your last attempt is, although presumably it's the function body of your addNum routine, so the same logic applies as above.
Perhaps instead, since you're running the same method multiple times, you should use the setInterval method instead? Here's an example of how you might do that.
Try setTimeout("addNum(" + i + ")", 250); the reason its not working is because its evaluating the parameter and executing it and changing it to something like setTimeout(result of addNum(i), 250);

Categories

Resources