At the bottom of each column I'm trying to display how many cells in that column have a particular background colour. This is what I'd like it to do:
Picture of intended result
The problem is it's a Gantt chart and I'll be inserting rows constantly. I can do it with a fixed number of rows using:
=countCellsWithBackgroundColor("#cfe2f3", "B1:B22")
which uses this function I found:
function countCellsWithBackgroundColor(color, rangeSpecification) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var range = sheet.getRange(rangeSpecification);
var x = 0;
for (var i = 1; i <= range.getNumRows(); i++) {
for (var j = 1; j <= range.getNumColumns(); j++) {
var cell = range.getCell(i, j);
if(cell.getBackgroundColor() == color)
x++;
}
}
return x;
}
I just can't figure out how to give that function every cell in the column. I get errors like "must be a range".
You could use a more efficient way to achieve the same result by using arrays in your function like below
function countCellsWithBackgroundColor(color, rangeSpecification) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var backGroundColors = sheet.getRange(rangeSpecification).getBackgrounds();
var x = 0;
for (var i = 0; i < backGroundColors.length; i++) {
for (var j = 0; j < backGroundColors[0].length; j++) {
if(backGroundColors[i][j] == color){ x++ }
}
}
return x;
}
WORKING EXAMPLE WITH AUTO UPDATE
function countCellsWithBackgroundColor(color, rangeSpecification) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var backGroundColors = sheet.getRange(rangeSpecification).getBackgrounds();
var x = 0;
for (var i = 0; i < backGroundColors.length; i++) {
for (var j = 0; j < backGroundColors[0].length; j++) {
if(backGroundColors[i][j] == color){ x++ }
}
}
return x;
}
function onEdit(e)
{
SpreadsheetApp.getActiveSheet().getRange('H26').setValue(Math.random());
}
Now Insert the dummy cell into spreadsheet function for auto update
=countCellsWithBackgroundColor("#0f9d58","A5:A26",H26)
Related
This is my code. I want to make tic toc toe game
I'm stuck at check value in cell. now I want to use for loop to check cell by cell. I don't know how to write it. Pls help me. TT
function onEdit(e) {
var activeSheet = e.source.getActiveSheet();
var cell = activeSheet.getRange("B2:D4");
var data = cell.getValues();
var data1 = data[2][2];
if(cell.getValues !== [["","",""],["","",""],["","",""]]){
Logger.log(cell.getValues());
Logger.log(data1)
for (var i = 2; i < 5; i++)
{
Logger.log("for1");
for (var j = 2; j < 5; j++)
{
Logger.log("for2");
if(data[i][j] == "X")
{
Logger.log("D")
activeSheet.getRange("H2").setValue("Playing");
}else
{
activeSheet.getRange("H2").setValues("");
Logger.log("F")
Logger.log(data1)
}
}
}
}
}
I tried to replicate your code and found some issues.
Array comparison - Comparing array with another array will not work, you have to either compare each element or convert the array into string by using JSON.stringify(array). See example 1 below.
In your for (var i = 2; i < 5; i++) and for (var j = 2; j < 5; j++), this will prompt index out of bounds once it reached 3, 4. The reason for this is the array in var data = cell.getValues() always start at zero. Instead you could use the array size to prevent error. See example 2 below.
setValues can only accept 2 dimensional arrays. See documentation.
if else statement - The else statement will override the value of H2 if the next element in your array is not X.
example 1:
if(JSON.stringify(data) != "[[\"\",\"\",\"\"],[\"\",\"\",\"\"],[\"\",\"\",\"\"]]"){
}
example 2:
for (var i = 0; i < data.length; i++)
{
for (var j = 0; j < data[0].length; j++)
{
//some statement
}
}
This will print every value in tictactoe Range:
Code:
function tictactoe(e) {
var activeSheet = e.source.getActiveSheet();
var cell = activeSheet.getRange("B2:D4");
var data = cell.getValues();
var a1 = ['B', 'C', 'D'];
if(JSON.stringify(data) != "[[\"\",\"\",\"\"],[\"\",\"\",\"\"],[\"\",\"\",\"\"]]"){
for (var i = 0; i < data.length; i++)
{
var column = i+2;
for (var j = 0; j < data[0].length; j++)
{
Logger.log("Value in " + a1[j] + (column) +" : "+ data[i][j]);
}
}
}
}
Output:
Additional info: You can get the A1 notation of the range being populated/edited by using e.range.getA1Notation();
I recive an email with table many times in one day it can be 10x2 or 40x2 or something like this. Every time I need to paste this table in sheet start from cell A1/ Now I have this code, but it paste whole table to cell A1:
var SEARCH_QUERY = "label:inbox is:unread to:me subject:importnumbers";
// Credit: https://gist.github.com/oshliaer/70e04a67f1f5fd96a708
function getEmails_(q) {
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
emails.push([msgs[j].getBody().replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '').replace(/^\s*/gm, '').replace(/\s*\n/gm, '\n')
]);
}
}
return emails;
}
function appendData_(sheet, array2d) {
sheet.getRange(1, 1).setValues(array2d);
}
function saveEmails() {
var array2d = getEmails_(SEARCH_QUERY);
if (array2d) {
appendData_(SpreadsheetApp.getActiveSpreadsheet().getSheetByName('numberlist'), array2d);
}
markArchivedAsRead ();
}
function markArchivedAsRead() {
var threads = GmailApp.search('label:inbox is:unread to:me subject:importnumberlist');
GmailApp.markThreadsRead(threads);
};
Please, help me to paste table with right dimensions.
Try this one.
Replace getEmails_(q) function
Code#1:
function getEmails_(q) {
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
var arrStr = msgs[j].getBody()
.replace(/<\/tr>/gm, '[/tr]')
.replace(/<\/td>/gm, '[/td]')
.replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '')
.replace(/^\s*/gm, '')
.replace(/\s*\n/gm, '\n')
.split("[/tr]");
var line = [];
for (var i = 0; i < arrStr.length - 1; i++) {
line = arrStr[i].split("[/td]");
line.length -= 1;
emails.push(line);
}
}
}
return emails;
}
Replace appendData_ function
Code#2:
function appendData_(sheet, array2d) {
var h = array2d.length;
var l = array2d[0].length;
sheet.getRange(1, 1, h, l).setValues(array2d);
}
This code converts the html text into 2d array and then paste it into Spreadsheet.
Note 2020-06
Your email may (1) contain several tables or (2) a table with merged cells. In this case, the proposed script may cause an error:
The number of columns in the data does not match the number of columns
in the range
In this situation, you'll need to create another function to parse the message body. I suggest you see the HTML-code of your message and inspect it in Gmail:
Right Click > Instect
or [Ctrl]+[Shift]+[I]
The other approach in this situation is to paste data "as is", use sample function to convert any 2d array into rectangular:
function convert2ArrayToRectangular_(array2d)
{
// get max width
var res = [];
var w = 0;
for (var i = 0; i < array2d.length; i++)
{
if (array2d[i].length > w) {w = array2d[i].length;}
}
var row = [];
for (var i = 0; i < array2d.length; i++)
{
row = array2d[i];
if(array2d[i].length < w)
{
for (var ii = array2d[i].length; ii < w; ii++)
{
row.push('');
}
}
res.push(row);
}
return res;
}
Sample usage:
function test_get_email() {
var q = 'Test_table_to_sheets';
var array2d = getEmails_(q);
var sheet = SpreadsheetApp.openById('1BKkd5LwBYyGoi2um-S3pTCBKrUEko34m9vJu94K8uOQ').getSheetByName('Test_table_to_sheets2');
appendData_(sheet, convert2ArrayToRectangular_(array2d));
}
I have a google spreadsheet with two columns corresponding to lessons: the first with names of the porfessors (occasionally repeating themselves) and the second with numbers (number of hours). I would like to have as output two columns, the first with the names of the porfessors and the second with the sum of all the hours
I tried with the following code, but it seems to give me back two arrays with the initial colums, as if the condition if (names[names.length-1] == namesColumn[i]) is never met.
What am I doing wrong?
function resumeProfessors() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var namesColumn = sheet.getRange("C4:C31").getValues();
var lessonsColumn = sheet.getRange("G4:G31").getValues();
var names = [];
names.length = 0;
var lessons = [];
lessons.length = 0;
namesColumn.sort();
for (var i = 0; i < namesColumn.length; i++) {
if (names[names.length-1] == namesColumn[i]){
lessons[lessons.length-1] = lessons[lessons.length-1] + lessonsColumn[i];}
else{
sheet.getRange(i+4, 9).setValue(names[names.length-1] + namesColumn[i]);
names[names.length] = namesColumn[i];
lessons[lessons.length] = lessonsColumn[i];
};}
writeResume(names, lessons);
}
Ty
Given your use-case, I'd recommend using a Pivot table or the =QUERY formula.
However, assuming your input sheet looks something like this -
And the expected output is something like this -
You can try the below code -
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName('Sheet1');
var output = ss.getSheetByName('Sheet2');
var inputValues = input.getDataRange().getValues();
Logger.log(inputValues)
for (var i = 1; i < inputValues.length; i++) {
var name = inputValues[i][0];
var totalHours = [];
for (var j = 0; j < inputValues.length; j++) {
var hours = inputValues[j][1];
if (name == inputValues[j][0]) {
totalHours.push(inputValues[j][1]);
}
}
var outputValues = output.getDataRange().getValues();
var newEntry = true;
for (var k = 0; k < outputValues.length; k++) {
if (name == outputValues[k][0]) {
newEntry = false;
}
}
if (newEntry) {
output.appendRow([name,totalHours.reduce(function(a, b) {return a + b})]);
}
}
}
Hope this helps.
A gs I have been using in the past to check for duplicates, is no longer working. The script would check all cells in the spread sheet if any were identical it would highlight all their occurrences green. I also had another function that would revert all the cells back to white.
setBackgroundColors() has been deprecated; people have been recommended to now use setBackground(). The script still doesn't work...
Here is my gs, make a copy and fiddle with it. Many Thanks...
https://docs.google.com/spreadsheets/d/1UELTxZRZPKQKU9NsQwNefvdxJDM0xDt8904sZy3etoY/edit#gid=0
Here is the script.
/**
* Retrieves all the rows in the active spreadsheet that contain data and logs the
* values for each row.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
Logger.log(row);
}
};
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Check Directory",
functionName : "CheckDirectory"
}];
spreadsheet.addMenu("Script Center Menu", entries);
};
function CheckDirectory() {
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getDataRange();
var data = dataRange.getValues();
var numRows = data.length;
var numColumns = data[0].length;
var formats = [];
var values = [];
for (var i = 0; i < numRows; i++) {
formats[i] = [];
for (var j = 0; j < numColumns; j++) {
formats[i][j] = 'white';
if (data[i][j] != '') {
values.push([data[i][j], i, j]);
}
}
}
var numValues = values.length;
for (var k = 0 ; k < numValues - 1; k++) {
if (formats[values[k][1]][values[k][2]] == 'white') {
for (var l = k + 1; l < numValues; l++) {
if (values[k][0] == values[l][0]) {
formats[values[k][1]][values[k][2]] = 'green';
formats[values[l][1]][values[l][2]] = 'green';
}
}
}
}
dataRange.setBackground(formats);
};
function resetCheckDirectory() {
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getDataRange();
var data = dataRange.getValues();
var numRows = data.length;
var numColumns = data[0].length;
var formats = [];
var values = [];
for (var i = 0; i < numRows; i++) {
formats[i] = [];
for (var j = 0; j < numColumns; j++) {
formats[i][j] = 'white';
if (data[i][j] != '') {
values.push([data[i][j], i, j]);
}
}
}
var numValues = values.length;
for (var k = 0 ; k < numValues - 1; k++) {
if (formats[values[k][1]][values[k][2]] == 'white') {
for (var l = k + 1; l < numValues; l++) {
if (values[k][0] == values[l][0]) {
formats[values[k][1]][values[k][2]] = 'white';
formats[values[l][1]][values[l][2]] = 'white';
}
}
}
}
dataRange.setBackground(formats);
};
Use setBackgrounds(). With an s since it's a method that applies multiple background colors to multiple cells
I am writing a function in Google Apps Script and it seems the last error I need to get around is a "reference does not exist" error in Google Sheets when I call my function. I don't know what to do about this because it doesn't seem to be a problem with my code.
This is what my code looks like now. It isn't complete because I need to change it for user input, but this is a test.
In a google sheets cell I type in =sortingtesting()
function sortingtesting()
{
var pInfo1 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','o','p','q','r','s']
var pInfo2 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','o','p','q','r','s']
var pInfo3 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','o','p','q','r','s']
var pWO = ['1','','','2','','','3','4','5','6','7','','','8','','','9','10']
var pSearch = ['c', 'b', 'a']
var WO = [];
var Info1 = [];
var Info2 = [];
var Info3 = [];
var Search = [];
for(var i = 0; i < 18; i++)
WO[i] = pWO[i];
for(var i = 0; i < 18; i++)
{
Info1[i] = pInfo1[i];
}
for(var i = 0; i < 18; i++)
{
Info2[i] = pInfo2[i];
}
for(var i = 0; i < 18; i++)
{
Info3[i] = pInfo3[i];
}
for(var i = 0; i < 1; i++)
Search[i] = pSearch[i];
// Declares secondary storage arrays and their counters
var FinalArray1 = [];
var FinalArray2 = [];
var FinalArray3 = [];
var LastArray = [];
var a = 0;
var b = 0;
var c = 0;
var d = 0;
// loop to run and make all of the cells in the work order row relevant to the work order number
for(var row = 0; row < WO.length; row ++)
{
var counter = row - 1;
while(WO[row] == "")
{
WO[row] = WO[counter];
counter--;
}
}
// loop that goes through saving which work orders meet certain search criteria, each search criteria has its own separate secondary array
for(var row = 0; row < Info1.length; row++)
{
if(Info1[row] == Search[0])
{
FinalArray1[a] = WO[row];
a++;
}
}
for(var row = 0; row < Info1.length; row++)
{
if(Info2[row] == Search[1])
{
FinalArray2[b] = WO[row];
b++;
}
}
for(var row = 0; row < Info1.length; row++)
{
if(Info3[row] == Search[2])
{
FinalArray3[c] = WO[row];
c++;
}
}
// loop to run through and get all the work orders that meet all of the criteria
for(var i = 0; i < FinalArray1.length; i++)
{
for(var j = 0; j < FinalArray2.length; j++)
{
for(var k = 0; k < FinalArray3.length; k++)
{
if(FinalArray3[k] == FinalArray2[j] && FinalArray2[j] == FinalArray1[i])
{
LastArray[d] = FinalArray1[i];
d++;
}
}
}
}
return LastArray;
}
Solution Found:
This is my working code with arrays coming in from google sheets as parameters and I just thought it would be nice to put the working prototype out there:
function sortingtesting(WO, Info, Search)
{
// Declares secondary storage arrays and their counters
var FinalArray1 = [];
var FinalArray2 = [];
var FinalArray3 = [];
var LastArray = [];
var a = 0;
var b = 0;
var c = 0;
var d = 0;
// loop to run and make all of the cells in the work order row relevant to the work order number instead of being blank
for(var row = 0; row < WO.length; row ++)
{
var counter = row - 1;
while(WO[row] == "")
{
WO[row] = WO[counter];
counter--;
}
}
// loop that goes through saving which work orders meet certain search criteria, each search criteria has its own separate secondary array to store the work orders that meet the criteria
for(var col = 0; col < Info[0].length; col++)
{
for(var row = 0; row < Info.length; row++)
{
if(Info[row][col] == Search[0])
{
FinalArray1[a] = WO[row];
a++;
}
else if(Info[row][col] == Search[1])
{
FinalArray2[b] = WO[row];
b++;
}
else if(Info[row][col] == Search[2])
{
FinalArray3[c] = WO[row];
c++;
}
}
}
LastArray[0] = 'N/A';
// loop to run through and get all the work orders that meet all of the criteria
for(var i = 0; i < FinalArray1.length; i++)
{
for(var j = 0; j < FinalArray2.length; j++)
{
for(var k = 0; k < FinalArray3.length; k++)
{
if(FinalArray3[k] == FinalArray2[j] && FinalArray2[j] == FinalArray1[i])
{
LastArray[d] = FinalArray1[i];
d++;
}
}
}
}
return LastArray;
}
TL;DR The function should not return an empty array.
By placing return "a valid string"; in various positions in the script (bisecting the code), you will see that return LastArray;` is causing the error.
By running the code in the debugger, LastArray is an empty array.
From experiments, an empty array is not a valid return value for a function called in a formula, neither is an array containing multiple values. An array of containing one integer is valid.
Changing var LastArray = []; to var LastArray = [1]; demonstrates this.