Count rows by background color failed - javascript

Hey guys I am in google sheets excel and I want to count how many rows have the sheet based in background color.
I have the next code:
function countColoredCells(countRange,colorRef) {
var activeRg = SpreadsheetApp.getActiveRange();
var activeSht = SpreadsheetApp.getActiveSheet();
var activeformula = activeRg.getFormula();
var countRangeAddress = activeformula.match(/\((.*)\,/).pop().trim();
var backGrounds = activeSht.getRange(countRangeAddress).getBackgrounds();
var colorRefAddress = activeformula.match(/\,(.*)\)/).pop().trim();
var BackGround = activeSht.getRange(colorRefAddress).getBackground();
var countCells = 0;
for (var i = 0; i < backGrounds.length; i++)
for (var k = 0; k < backGrounds[i].length; k++)
if ( backGrounds[i][k] == BackGround )
countCells = countCells + 1;
return countCells;
};
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Contar",
functionName : "countColoredCells"
}];
spreadsheet.addMenu("Contar numero de casillas por color", entries);
}
But when i put the next code doesn't work? Shows error. What's happens?
=countcoloredcells(B1:B9,A1)

When you run activeformula.match(/\((.*)\,/) I think there are instances when .match() is returning null and therefore you can not call .pop().
You'll have to do something like this...
var match = activeformula.match(/\((.*)\,/);
if(match) var countRangeAddress = match.pop().trim(); //if match is not null...
But of course deal with not having that variable in the rest of your script.

The script you posted uses a unusual method of getting the range argument. It fails at regex because at your location, Sheets uses ; as delimiter rather than , making the match null. Regardless, Taking the argument directly should work. So, Try changing the original code
From
var countRangeAddress = activeformula.match(/\((.*)\,/).pop().trim();
var backGrounds = activeSht.getRange(countRangeAddress).getBackgrounds();
var colorRefAddress = activeformula.match(/\,(.*)\)/).pop().trim();
To
var countRangeAddress = countRange
var backGrounds = activeSht.getRange(countRangeAddress).getBackgrounds();
var colorRefAddress = colorRef
You could also try replacing the regex match , with ; ( like match(/\((.*)\;/).)
Usage should be like:
=countcoloredcells("B1:B9";"A1")

Use this code instead.
The arguments need to be passed as strings.
/**
* Usage countColoredCells("A1:B3";"C5")
* #param {range} countRange Range to be evaluated
* #param {colorRef} colorRef Cell with background color to be searched for in countRange
* #return {number}
* #customfunction
*/
function countColoredCells(countRange,colorRef) {
var activeRange = SpreadsheetApp.getActiveRange();
var activeSheet = activeRange.getSheet();
var formula = activeRange.getFormula();
var backgrounds = activeSheet.getRange(countRange).getBackgrounds();
var colorRefBackground = activeSheet.getRange(colorRef).getBackground();
var count = 0;
for(var i=0;i<backgrounds.length;i++)
for(var j=0;j<backgrounds[0].length;j++)
if( backgrounds[i][j] == colorRefBackground )
count=count+1;
return count;
};

Related

Google Apps Script - Improve Script to Execute Faster

I have a sheet with over 3000 points (lat, long). I am running the following code to calculate distance of each point from 9 reference points and writing the minimum distance next to each point in the sheet. The code runs fine but is slow and times out before running for all the points. How can I make the following code more efficient - so that it reads, computes and writes back the data faster?
/*
******************************
Reference Points
Point_A 41.166866 -76.151926
Point_B 41.087500 -76.204694
Point_C 40.540960 -75.704900
Point_D 40.401080 -75.589600
Point_E 40.326130 -75.642500
Point_F 40.167480 -75.921500
Point_G 40.093370 -76.084700
Point_H 39.974450 -76.063000
Point_I 39.722350 -76.156000
********************************
*/
var SpreadsheetID = "ABC";
var SheetName = "XYZ";
function DistanceCalculator() {
var ss = SpreadsheetApp.openById(SpreadsheetID)
var sheet = ss.getSheetByName(SheetName);
//Select the column
var columnToCheck = sheet.getRange("A:A").getValues();
// Get the last row in the column
var lastRow = getLastRowSpecial(columnToCheck);
// Ref point latitude
var Latitude_Point_A = 41.166866;
var Latitude_Point_B = 41.087500;
var Latitude_Point_C = 40.540960;
var Latitude_Point_D = 40.401080;
var Latitude_Point_E = 40.326130;
var Latitude_Point_F = 40.167480;
var Latitude_Point_G = 40.093370;
var Latitude_Point_H = 39.974450;
var Latitude_Point_I = 39.722350;
// Ref point longitude
var Longitude_Point_A = -76.151926;
var Longitude_Point_B = -76.204694;
var Longitude_Point_C = -75.704900;
var Longitude_Point_D = -75.589600;
var Longitude_Point_E = -75.642500;
var Longitude_Point_F = -75.921500;
var Longitude_Point_G = -76.084700;
var Longitude_Point_H = -76.084700;
var Longitude_Point_I = -76.156000;
for( var i=1; i<=lastRow;i++){
//Reading Lat_Long from the sheet
var Lat_Check = sheet.getRange(i,2).getValue();
var Long_Check = sheet.getRange(i,3).getValue();
//Calculating distance between each point and reference points
var distance01 = calculateDistance(Latitude_Point_A,Longitude_Point_A,Lat_Check,Long_Check);
var distance02 = calculateDistance(Latitude_Point_B,Longitude_Point_B,Lat_Check,Long_Check);
var distance03 = calculateDistance(Latitude_Point_C,Longitude_Point_C,Lat_Check,Long_Check);
var distance04 = calculateDistance(Latitude_Point_D,Longitude_Point_D,Lat_Check,Long_Check);
var distance05 = calculateDistance(Latitude_Point_E,Longitude_Point_E,Lat_Check,Long_Check);
var distance06 = calculateDistance(Latitude_Point_F,Longitude_Point_F,Lat_Check,Long_Check);
var distance07 = calculateDistance(Latitude_Point_G,Longitude_Point_G,Lat_Check,Long_Check);
var distance08 = calculateDistance(Latitude_Point_H,Longitude_Point_H,Lat_Check,Long_Check);
var distance09 = calculateDistance(Latitude_Point_I,Longitude_Point_I,Lat_Check,Long_Check);
//measuring minimum distance
var minimumDistance = Math.round(Math.min(distance01,distance02,distance03,distance04,distance05,distance06,distance07,distance08,distance09))
sheet.getRange(i,4).setValue(minimumDistance);
}
}
function calculateDistance(Ref_Lat, Ref_Long, Looped_Lat, Looped_Long){
var p = 0.017453292519943295; // Math.PI / 180
var c = Math.cos;
var a = 0.5 - c((Looped_Lat - Ref_Lat) * p)/2 +
c(Ref_Lat * p) * c(Looped_Lat * p) *
(1 - c((Looped_Long - Ref_Long) * p))/2;
var result = 7926 * Math.asin(Math.sqrt(a)); // 2 * R; R = 3963 miles
return result
};
function getLastRowSpecial(range){
var rowNum = 0;
var blank = false;
for(var row = 0; row < range.length; row++){
if(range[row][0] === "" && !blank){
rowNum = row;
blank = true;
}else if(range[row][0] !== ""){
blank = false;
};
};
return rowNum;
};
Sample Spreadsheet Screenshot
I believe your goal is as follows.
You want to reduce the process cost of your script.
In your script, getValue and setValue are used in a loop. I thought that this might be the reason for your issue. In this case, at first, the values are retrieved from the sheet and the values are processed, then, the values are put to the sheet. This flow is suitable for reducing the process cost. I think that "Best Practice" will be useful for understanding your issue. Ref
Also, I thought that when the process cost of your script is reduced, a little ingenuity might be required to be used for modifying your script. So I propose one of several modified scripts as a reference. Please modify your script as follows.
From:
for( var i=0; i<lastRow;i++){
//Reading Lat_Long from the sheet
var Lat_Check = sheet.getRange(i,2).getValue();
var Long_Check = sheet.getRange(i,3).getValue();
//Calculating distance between each point and reference points
var distance01 = calculateDistance(Latitude_Point_A,Longitude_Point_A,Lat_Check,Long_Check);
var distance02 = calculateDistance(Latitude_Point_B,Longitude_Point_B,Lat_Check,Long_Check);
var distance03 = calculateDistance(Latitude_Point_C,Longitude_Point_C,Lat_Check,Long_Check);
var distance04 = calculateDistance(Latitude_Point_D,Longitude_Point_D,Lat_Check,Long_Check);
var distance05 = calculateDistance(Latitude_Point_E,Longitude_Point_E,Lat_Check,Long_Check);
var distance06 = calculateDistance(Latitude_Point_F,Longitude_Point_F,Lat_Check,Long_Check);
var distance07 = calculateDistance(Latitude_Point_G,Longitude_Point_G,Lat_Check,Long_Check);
var distance08 = calculateDistance(Latitude_Point_H,Longitude_Point_H,Lat_Check,Long_Check);
var distance09 = calculateDistance(Latitude_Point_I,Longitude_Point_I,Lat_Check,Long_Check);
//measuring minimum distance
var minimumDistance = Math.round(Math.min(distance01,distance02,distance03,distance04,distance05,distance06,distance07,distance08,distance09))
sheet.getRange(i,4).setValue(minimumDistance);
}
To:
var values = sheet.getRange("B1:C" + lastRow).getValues();
var ar = [[Latitude_Point_A, Longitude_Point_A], [Latitude_Point_B, Longitude_Point_B], [Latitude_Point_C, Longitude_Point_C], [Latitude_Point_D, Longitude_Point_D], [Latitude_Point_E, Longitude_Point_E], [Latitude_Point_F, Longitude_Point_F], [Latitude_Point_G, Longitude_Point_G], [Latitude_Point_H, Longitude_Point_H], [Latitude_Point_I, Longitude_Point_I]];
var res = values.map(([b, c]) => [Math.round(Math.min(...ar.map(e => calculateDistance(...e, b, c))))]);
sheet.getRange(1, 4, res.length).setValues(res);
In this modification, from your script, it supposes that the 1st row is not the header row. Please be careful about this.
In this modification, the values are retrieved from the sheet by getValues, and then, the retrieved values are calculated and put to the sheet by setValues.
Note:
When I saw your script, I thought that an error occurs at var Lat_Check = sheet.getRange(i,2).getValue(); because the 1st value of i is 0. And in the function calculateDistance, Ref_Lat, Ref_Long are not used. So I'm worried that your showing script might be different from the script that you have the issue with. So, when you test the above modification, when an error occurs, can you confirm whether your showing script is the correct script for your situation. And, please provide the sample Spreadsheet for replicating the issue as the image. By this, I would like to confirm it.
References:
map()
getValues()
setValues(values)

Google scripts: How to put a range in setValue

I have used the example code from this link, to make the code below.
https://yagisanatode.com/2017/12/13/google-apps-script-iterating-through-ranges-in-sheets-the-right-and-wrong-way/
Most of the code is working as expected, except for the last row.
In Column E, I want to place the custom function =apiav() with the data from cell A.
However the code is returning =apiav(Range) in the Google sheet cells. (to be clear it should be something like =apiav(a1))
I tried everything i could think of and of course googled for hours, but i am really lost and can't find the right solution for this.
function energy(){
var sector = "Energy";
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var rangeData = sheet.getDataRange();
var lastColumn = 2;
var lastRow = 999 ;
var sheet = ss.getSheets()[0];
var searchRange = sheet.getRange(2,2, lastRow-1 ,1 );
var ouputrange = sheet.getRange(2,4, lastRow-1 ,1 );
//clear range first
ouputrange.clear("D:D");
ouputrange.clear("E:E");
/*
GOOD - Create a client-side array of the relevant data
*/
// Get array of values in the search Range
var rangeValues = searchRange.getValues();
// Loop through array and if condition met, add relevant
// background color.
for ( i = 0; i < lastColumn - 1; i++){
for ( j = 0 ; j < lastRow - 1; j++){
if(rangeValues[j][i] === sector){
sheet.getRange(j+2,i+4).setValue("yes");
var formularange = sheet.getRange (j+2,i+1);
sheet.getRange(j+2,i+5).setValue('=apiav(' + formularange + ')');
}
};
};
};
Replace:
var formularange = sheet.getRange(j+2,i+1);
with:
var formularange = sheet.getRange(j+2,i+1).getA1Notation();
So you will be able to pass the cell reference instead of the range object.

Javascript in google sheets: passing arguments that are ranges of cells to a function

I'm trying to run & test this function in Google Sheets:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('NN');
var range_p = sheet.getRange('B32:J32');
var range_q = sheet.getRange('B33:J33');
kl_divergence(range_p, range_q);
function kl_divergence(range_p, range_q) {
var norm = 0.00001;
var i;
var tot = 0.0;
for (i = 0; i < range_p.length; i++){
tot += range_p[i] * LOG((range_p[i] + norm) / (range_q[i] + norm));
}
return tot;
}
but I'm getting this error:
TypeError: Cannot read property 'length' of undefined (line 11, file "Code"
What's going on, and how do I fix it?
the getRange() function you are using returns a Range class https://developers.google.com/apps-script/reference/spreadsheet/range.
I think you need to change it to:
var range_p = sheet.getRange('B32:J32').getValues();
var range_q = sheet.getRange('B33:J33').getValues();

Only the first element of the array has been output to the google sheet, I need all elements (except null/undefined elements) to be output

My script needs to get values from one sheet and put them into another sheet. It picks up the values correctly. However, when the values are being output to the other sheet only the first element is being output. I need all elements to be output. This could be achieved (I think) in one of two ways; either skip over/ignore the empty elements of the array when the values are being output or ignore the empty cells in the source sheet. Any help is appreciated. The script is below:
Updated code:
function exportArds() {
var ards = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Newtownards");
var export = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Export");
var ardsLastRow = ards.getLastRow();
var exportLastRow = export.getLastRow();
var refValues = new Array(6);
var amountValues = new Array(6);
//get values for export
refValues = ards.getRange("B12:G12").getValues();
amountValues = ards.getRange("B13:G13").getValues();
for(var i = 2; i<=6; i++){
var a = refValues.join().split(',').filter(Boolean);
var b = amountValues.join().split(',').filter(Boolean);
//get values, if any, in export sheet (used to check where the empty cells are for input)
var checkAmount = export.getRange(i, 4).getValue().toString();
var checkDescription = export.getRange(i, 5).getValue().toString();
export.getRange(i, 4).setValue(b[i-2]);
export.getRange(i, 5).setValue(a[i-2]);
}//close for loop
Logger.log(a);
Logger.log(b);
}//close function
When the values are being set to a cell correctly the next sheet however, if the array has say 2 values in it, the undefined will be output 4 times. Is there a way to prevent this?
I have found a solution that works for this problem, I'm sure it could be a bit cleaner so feel free to comment any suggestions! The code is below:
function exportArds() {
var ards = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Newtownards");
var export = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Export");
//get values for export
var refValues = ards.getRange("B12:G12").getValues();
var amountValues = ards.getRange("B13:G13").getValues();
for(var i = 2; i<=6; i++){
var a = refValues.join().split(',').filter(Boolean);
var b = amountValues.join().split(',').filter(Boolean);
}//close for loop
var aLength = a.length;
var bLength = b.length;
for(i = 2; i<=aLength; i++){
export.getRange(i, 5).setValue(a[i-2]);
}
for(i = 2; i<=bLength; i++){
export.getRange(i, 4).setValue(b[i-2]);
}
}//close function

Get the spreadsheet row for a matched value

I'm new to Google Apps Script & I'm trying to make a script for a spreadsheet where I can get the range for the matched value. But there's no function for the cell value like .getRow() or .getRange() or something like this since it's a string value. Here are my codes,
function getInfo() {
var ss1 = sheet1.getSheetByName("Sheet1");
var ss2 = sheet2.getSheetByName("Rates");
var getCountry = ss1.getRange(ss1.getLastRow(), 11).getValue();
var countryRange = ss2.getRange(2, 1, ss2.getLastRow(), 1).getValues();
var getZone = 0;
for (var i = 0; i < countryRange.length; i++) {
if (countryRange[i] == getCountry) {
var getrow_cr = countryRange[i].getRow(); //.getRow() doesn't apply here for string value
getZone = ss2.getRange(getrow_cr + 1, 2).getValue();
}
}
Logger.log(getZone);
}
How can I get the row for the matched string so that I can get the cell value beside the matched cell value?
Your looping variable i starts at 0, which in your case is equivalent to Row 2. To help keep that clear, you could use a named variable to track the first row with data:
var firstrow = 2;
Then when you find a match, the spreadsheet row it's in is i + firstrow.
Updated code:
function getInfo() {
var ss1 = sheet1.getSheetByName("Sheet1");
var ss2 = sheet2.getSheetByName("Rates");
var getCountry = ss1.getRange(ss1.getLastRow(), 11).getValue();
var firstrow = 2;
var countryRange = ss2.getRange(firstrow, 1, ss2.getLastRow(), 1).getValues();
var getZone = 0;
for (var i = 0; i < countryRange.length; i++) {
if (countryRange[i][0] == getCountry) {
var getrow_cr = i + firstrow;
getZone = ss2.getRange(getrow_cr, 2).getValue();
break; // Found what we needed; exit loop
}
}
Logger.log(getZone);
}
Since countryRange is a two-dimensional array with one element in each row, the correct way to reference the value in row i is countryRange[i][0]. If you instead just compare countryRange[i], you are relying on the JavaScript interpreter to coerce the "row" into a String object.

Categories

Resources