I'm implementing a map visualization using D3.js. I have a number of x csv files (matrices). I want to load them and sum all values from all files.
I implemented a for loop over the array of names and load and parse the data using d3.text, but due to async behavior, I can't find a way to do this (I get undefined from console.log first and then the result of the for loop).
I've tried to use async.js but I cant figure out how to allow the flow of the for loop. Here is my code:
var total=[];
var matrix=[];
for(var i=0; i<filenames.length; i++){
d3.text(filenames[i], function(text){
matrix = d3.csv.parseRows(text).map(function (row){
return row.map(function(value){
return +value;
});
});
});
//sum the matrix pseudocode
for(...){
total = total + matrix;
}
}
//here I need the sum of all files and then do other stuffs with the total:
console.log(total);
...
...
How can I archieve this? Thanks.
I would suggest you do it with a recursive function.
In the following example you can use loadFilesAndCalculateSum() like the d3.text() but instead of one string it takes an array of strings and the callback gets the calculated sum instead of the text of a file.
/**
* load all files and calculate the sum of the values
* #param {Array} filenames Array with filenames as string
* #param {Function} cb Callback function, gets the sum as param
* #param {number} sum The initial sum
* #param {number} i Starting index for filenames
* #return {void}
*/
function loadFilesAndCalculateSum(filenames, cb, sum, i) {
sum = sum || 0;
i = i || 0;
d3.text(filenames[i], function(error, text) {
//parse the rows and reduce them
sum += d3.csv.parseRows(text).reduce(function(prev, curr) {
//return previous sum + this rows sum
return prev + d3.sum(curr, function(d){return +d;})
},0);
if(i < filenames.length - 1){
//load next file
loadFilesAndCalculateSum(filenames, cb, sum, i+1);
} else {
//call the callback with the final sum
cb(sum);
}
});
}
var filenames = ["file1.txt", "file2.txt", "file3.txt"];
loadFilesAndCalculateSum(filenames, function(sum){
//do something with the total sum
console.log(sum);
});
To clarify this. you have to do the processing of sum inside of the callback function where I put the comment do something with the total sum. This function is still executing async. That means, that everything you write after the loadFilesAndCalculateSum() function will possibly execute before the code inside the callback. You can find a little longer introduction to async javascript here
//this is executed first
//....
loadFilesAndCalculateSum(filenames, function(sum){
//do something with the total sum
//this is executed third, when all files are loaded and the sum is calculated
console.log(sum);
});
//code after this point is executed second, while the files are being loaded.
If you already have a function that does something with the sum, you can pass this function to the loadFilesAndCalculateSum as second parameter. This is possible because functions are so called first class citizens:
var addthis = 5;
function doSomethingWithTheSum(sum) {
//everything you want to do with the sum goes inside this function.
//from here you could call other functions and pass the sum.
soSomethingDifferentWithTheSum(sum);
//or you use the sum inside this function
console.log(sum);
var newsum = sum + addthis;
console.log(sum);
d3.select("whatever")
.data([sum])
.enter()
.append("text")
.text(function(d){ return d;});
}
loadFilesAndCalculateSum(filenames, doSomethingWithTheSum);
You can pass functions just like any other variable. in the first example I called the second parameter of the loadFiles... function cb which is the usual abbreviation for callback. As stated in the doc comment, this param should be of type Function.
In the end of the loadFiles... function I the callback function gets called by
....
//call the callback with the final sum
cb(sum);
....
Here the sum is given to the callback function as first parameter. Therefore if you pass a function, it should take a single param, like the anonymous function in the first example or the doSomethingWithTheSum function in the example above.
Related
In Javascript, I wanted to calculate total of two variables received from textbox, let us say txtBoxA and txtBoxB. The value gets calculated in an anonymous function and is stored in a variable total. Please take a look at the following code to see how it is calculated:
var total = function () {
var total = parseFloat(txtbox[1].value) + parseFloat(txtbox[2].value);
if (total == NaN) return 0;
else return total;
};
alert(total);
but unfortunately, the anonymous function itself is printed as it is as shown in the image below.
alert(total);
You are just printing the function variable. If you want to execute it, you have to add (), so that it calls.
alert(total());
And that is not an anonymous function. Just a function declaration and assigning to a variable.
I am trying to write a JS function which calculates the sum of all visible rows in a column in a table.
The way the values in the column is filled is by making multiple ajax calls (50 rows at a time).
I am trying to keep track of no of requests sent and when I receive all responses back calculate the sum.
function onClickofCalculateButton() {
var noOfRequestsSent = 0;
var sum = 0;
var sucCallback = function(response) {
updateColumn(response, noOfRequestsSent, sum);
};
//Some logic to send requests 50 rows a time and I increment the value of noOfRequestsSent;
}
in my updateColumn function()
function updateColumn(response, noOfRequestsSent, sum) {
noOfRequestsSent--;
//Do some logic to retrieve value of each row and add it to sum
if(noOfRequestsSent == 0) {
alert(sum);
}
}
However what is happening is the value of noOfRequestsSent is always equal to the actual number of requestssent even after subtracting it in updateColumn function. So it never reaches the condition where noOfRequestsSent == 0 and neither does the sum get added onto the previous value.
I guess I have to pass some object reference or something like pointers in C but I am unable to figure out how to do in JS.
You could try like this. Since you want to send variable as reference. In this way you can avoid global variables.
function onClickofCalculateButton() {
var noOfRequests = {
sent:0
};
var sum = 0;
var sucCallback = function(response) {
updateColumn(response, noOfRequests, sum);
};
//Some logic to send requests 50 rows a time and I increment the value of noOfRequestsSent;
}
function updateColumn(response, noOfRequestsSent, sum) {
noOfRequests.sent--;
//Do some logic to retrieve value of each row and add it to sum
if(noOfRequests.sent == 0) {
alert(sum);
}
}
I am grappling to understand functions as values, and closure in Javascript.
Consider the code;
function multiplier(factor) {
return function (number) {return number * factor; }
}
var i = multiplier(10);
What is i actually holding at this point? I know I could use i next by doing something like
console.log(i(5));
which would produce the value 50. But I'd still like to know what Javascript actually gives to i before this.
Consider the following two examples. The first you have given and the second one I provided.
In the first one might think that the returned function is
function (number) {return number * 10; }
But this isn't really the case even if it is easy to think of it that way. factor is saved as a reference to the parameter factor. So the function multiplier returns a function with a reference to the value you provided.
If the value of factor changes then the function result is different.
function multiplier(factor) {
return function (number) {return number * factor; }
}
var i = multiplier(10);
console.log('Example1',i(5))
function multiplier2(factor) {
setTimeout(() => {factor = 100}, 1000)
return function (number) {return number * factor; }
}
var i2 = multiplier2(10);
console.log('Example2',i2(5))
setTimeout(() => {console.log('Example2',i2(5))}, 1100)
var i = multiplier(10);
is actually var i = function(number){return number * 10;}
then when calling console.log(i(5));
you are calling
console.log(function(5){return 5* 10;})
i is actually a brand new function: function (number) {return number * 10; }
So if you have j = multiplier(10); actually i is not j, because they are 2 different functions
One more note is that, after i = multiplier(10); you no longer have factor as reference or as value anymore, factor already disappear from your i
I have this function called numFunc(), which produces a numeric output.
I want to run this function 5 times and get the sum of all 5 outputs.
Here's what I've tried
function total(){
for (itr=1; itr<=5; itr++) //run the function 5 times
{
var numF = numFunc(); //store the output from the function in a variable
var sum = sum+numF; //Get the sum of all 5 variables
}
console.log(sum);
}
total();
But, what I get is the following output
3
3
3
3
3
NaN
What I want is the sum as a single value. How do I achieve this?
You need to declare your variabble outside of the loop and set it to a number value otherwise you are adding an undefined to an integer. Thanks #jfriend00 for the clarification
function total(){
var sum = 0;
for (itr=1; itr<=5; itr++) //run the function 5 times
{
var numF = numFunc(); //store the output from the function in a variable
sum = sum + numF; //Get the sum of all 5 variables
}
console.log(sum);
}
total();
In the statement:
var sum = sum + numF;
You are trying to add something that does not yet exist, because you just declared it there with the var
With your statement, you should be declaring the vars above the loop:
function total(){
// declaring the variables before you use them.
// I will set them to 0 so I know they're supposed to be numbers, and 0 wont affect the outcome of the function
var numF = 0;
var sum = 0;
for (itr=1; itr<=5; itr++) //run the function 5 times
{
numF = numFunc(); //store the output from the function in a variable
sum = sum+numF; //Get the sum of all 5 variables
}
console.log(sum);
}
total();
What about trying an array, and then adding that array values externally after the function fires like this?
EDIT: Used the repl.it code editor to test this theory out. I had to change the code a bit but remained under the same premise. Used separate the functions and then fed the array builder function into the calculator function to properly calculate the array variables. Repl link here: (https://repl.it/B128/1)
function numFunc(){
return 3;
};
function total(){
var toBeSummed = []; //set up an array to put your sum variables into as a more versatile variable type to perform this in.
for (var itr=0; itr<5; itr++){ //runs the function 5 times
var numF = numFunc(); //store the output from the function
var arrayItr = itr; //uses "itr" variable to iterate a 0 based index 5 times
toBeSummed[arrayItr] = numF; //for each arrayItr 0-5 makes the array value equal to the numF value.
};
return toBeSummed;
};
var sum = 0;
function counter(totals){
for(sums in totals){ //iterates the x array elements(5 in this case)
sum = sum + totals[sums]; // Adds the current value of sum, plus the values at each existing array point(at this point values in array index 0-5)
//console.log(sum); //Should now log the correct set of sums "browser"
};
console.log(sum);
};
counter(total());
Thank you all for your help.
I think I found the issue. The original numFunc() function seemed to have a console.log() statement that's why I kept on getting the list of 3's all the time, replacing that with a return resolved that.
And as you guys suggested declaring the sum variable outside the loop prevented the display of 'NaN'
This on e is a doozey.
I have while loop to generate a random number that is not the same as any other random number produced before. The random number is used to select a text value from an object.
for example:
quoteArray[1] = "some text"
quoteArray[2] = "some different text"
quoteArray[3] = "text again"
quoteArray[4] = "completely different text"
quoteArray[5] = "ham sandwich"
This is part of a larger function and after that function has cycled through = quoteArray.length it resets and starts the cycle over again. The issue I am hitting is that the following code is SOMETIMES producing an infinite loop:
//Note: at this point in the function I have generated a random number once already and stored it in 'randomnumber'
//I use this while statement to evaluate 'randomnumber' until the condition of it NOT being a number that has already been used and NOT being the last number is met.
while(randomnumber === rotationArray[randomnumber] || randomnumber === lastnumber){
randomnumber = Math.floor(Math.random() * (quoteArray.length));
}
When I console.log(randomnumber) - when I am stuck in the loop - I am just getting '0' as a result. When stuck in the loop it doesn't appear as though Math.floor(Math.random() * (quoteArray.length)) is producing a random number but rather just '0' infinitely.
can anyone tell me why I am running into this problem?
EDIT: Here is the complete pertinent code with function + variable declarations
// Function to initialize the quoteObj
function quoteObj(text,cname,ccompany,url,height) {
this.text=text;
this.cname=cname;
this.ccompany=ccompany;
this.url=url;
this.height=height;
}
// Populate my quotes Object with the quotation information from the XML sheet.
var qObj = new quoteObj('','','','');
var quoteArray = new Array();
var counter = 0;
//cycles through each XML item and loads the data into an object which is then stored in an array
$.ajax({
type: "GET",
url: "quotes.xml",
dataType: "xml",
success: function(xml) {
$(xml).find('quote').each(function(){
quoteArray[counter] = new quoteObj('','','','');
console.log(quoteArray[counter]);
quoteArray[counter].text = $(this).find('text').text();
quoteArray[counter].cname = $(this).find('customer_name').text();
quoteArray[counter].ccompany = $(this).find('customer_company').text();
quoteArray[counter].url = $(this).find('project').text();
++counter;
});
}
});
// This is the setion that is generating my infinite loop issue.
// I've included all of the other code in case people are wondering specific things about how an item was initialized, etc.
// Generate a random first quote then randomly progress through the entire set and start all over.
var randomnumber = Math.floor(Math.random() * (quoteArray.length));
var rotationArray = new Array(quoteArray.length);
var v = 0;
var lastnumber = -1;
bHeight = $('#rightbox').height() + 50;
var cHeight = 0;
var divtoanim = $('#customerquotes').parent();
//NOT RELATED//
// Give the innershadow a height so that overflow hidden works with the quotations.
$(divtoanim).css({'height' : bHeight});
// Rotate the Quotations Randomly function.
setInterval(function(){
randomnumber = Math.floor(Math.random() * (quoteArray.length));
//checks to see if the function loop needs to start at the beginning.
if(v == (quoteArray.length)){
rotationArray.length = 0;
v = 0;
}
//determines if the random number is both different than any other random number generated before and that is is not the same as the last random number
while(randomnumber === rotationArray[randomnumber] || randomnumber === lastnumber){
randomnumber = Math.floor(Math.random() * (quoteArray.length));
}
lastnumber = randomnumber;
rotationArray[randomnumber] = randomnumber;
++v;
//NOT RELATED//
//animation sequence
$('#ctext, #cname').animate({'opacity':'0'},2000, function(){
$('#ctext').html(quoteArray[randomnumber].text);
$('#cname').html('- ' + quoteArray[randomnumber].cname);
cHeight = $('#customerquotes').height() + 50;
adjustHeight(bHeight,cHeight,divtoanim);
$('#ctext').delay(500).animate({'opacity':'1'},500);
$('#cname').delay(1500).animate({'opacity':'1'},500);
});
},15000);
This is an asynchronous problem: the array quoteArray is empty when the code runs, because it fires off the ajax request, and moves on. Anything that depends on quoteArray should be inside the success function of $.ajax.
The array has a length when you type quoteArray.length in the console, only because by that time the Ajax request has completed.
have you tried something like
Math.floor(Math.random() * (5));
To make sure the array length is being found properly?
First, since you updated your question, be sure that you are handling asynchronous data properly. Since an ajax call is asynchronous, you will need to be sure to only run the randomizer once the call is successful and data has been returned.
Second, assuming you are handling the asyc data properly, the size of your result set is likely it is too small. Thus, you are probably randomly getting the same number too often. Then, you can't use this number because you have already done so.
What you need to do is pop off the parts that are already used from the results array each time. Recalculate the array length, then pull a random from that. However, the likelihood of this feeling random is very slim.
There is probably a more efficient way to do this, but here's my go:
var results = ['some text','some text2','some text3','some text4','some text5', /* ...etc */ ],
randomable = results;
function getRandomOffset( arr )
{
var offset,
len;
if( arr.length < 1 )
return false;
else if( arr.length > 1 )
offset = Math.floor(Math.random() * arr.length );
else
offset = 0;
arr.splice( offset, 1 );
return [
offset,
arr
];
}
while( res = getRandomOffset( randomable ) )
{
// Set the randomable for next time
randomable = res[1];
// Do something with your resulting index
results[ res[0] ];
}
The arrgument sent to the function should be the array that is returned form it (except the first time). Then call that function as you need until it returns false.