Count any colored cells in range - google-apps-script

I am trying to create a Gantt Chart in Google Sheets, therefore I'll need to count all the colored cells, of any color, within a specific range, in order to get the duration of such task.
However, from what I've gathered, there's no built-in function.
https://www.youtube.com/watch?v=IuMSytD9t38 is the closest thing that I've found online, especially since it solves the "auto-refresh" problem. But it's not quite it.
Can anyone point me in the right direction?

If you want to use Google Scripts, you'll use the getBackgrounds() function for the cell range.
The following code added to a sheet via Google Scripts allows you to put =colors("a1:a5") and get all the non-white cells counted. You do have to put the cell array in quotes for it to work.
You can see it working on this sheet.
function COLORS(input) {
var ss = SpreadsheetApp.getActiveSpreadsheet();//get this doc
var sheet = ss.getActiveSheet();//get the active sheet
var counter = 0;//no colors yet
var range = sheet.getRange(input);//get range of cells from the function
var bgColors = range.getBackgrounds();//get the array of background colors
bgColors.forEach(function(element){
var cleanColors = arrayRemove(element,'#ffffff');//kick out the white backgrounds
counter = counter + cleanColors.length;//count them up
})
return counter;//return the total count
}
//kick things out of arrays from https://love2dev.com/blog/javascript-remove-from-array/#create-remove-method
function arrayRemove(arr, value) {
return arr.filter(function(ele){
return ele != value;
});
}

to count background color you can use this addon:
https://chrome.google.com/webstore/detail/custom-count-and-sum/njiklelndjpdbdngfkdgeijcpfabfgkb
and then you can simply use this formula:
=COUNTBACKGROUNDCOLOR("A2:A20", "A3")
A2:A20 being the range and A3 being the example of color to count

Related

Is it possible to add formatting (shading) to rows being appended in Google Sheets (by Google Apps Script)

I've got a Google App Script which is copying rows from one sheet to another, performing various transformations. This logic ultimately gets rows onto the new sheet using sheet.appendRow(row detail). I would like these newly created rows to have a background colour (my intention is to hold a 'latestColour' so I can alternate the shading).
So, is there anyway to add shading within the appendRow method itself, or easily determine the range that the appendRow method processed, such that I can apply additional logic to add the shading.
You can use conditional formatting
=and(A1<>"",A2="")
Although I'm not sure whether I could correctly understand your situation, from your question, I thought that you might be using [Format] --> [Alternating colors] in Google Spreadsheet. And, when a new row is appended by putting the values, you might want to reflect "Alternating colors" in the appended row. If my guess is correct, how about the following sample script?
Sample script:
function myFunction() {
const addValues = ["sample1", "sample2", "sample3"]; // This is a sample appending value. Please replace this for your value.
const sheetName = "Sheet1"; // Please set the sheet name.
// Retrieve banding object from the data range.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const b = sheet.getDataRange().getBandings();
if (b.length == 0) {
console.log("Bandings are not used.");
return;
}
// Append the value.
sheet.appendRow(addValues);
// Expand the range of banding.
b[0].setRange(sheet.getDataRange());
}
When this script is run, the current banding is retrieved. And, after the value was appended, the banding is updated by including the appended row. In this sample, even when the multiple rows are appended, this script can be used.
Note:
From your question, I guessed that there is one banding in the data range in your sheet. Please be careful this.
References:
getBandings()
setRange(range)
Unfortunately the method appendRow() does not receive formatting settings as input, only an array of values.
However, here is a suggestion if you want to implement your own logic:
Sample code:
function applyColorLastRow() {
var ss = SpreadsheetApp.getActive(); //get active sheets file
var range = ss.getDataRange(); //get populated range, you may want to set a range manually if needed.
var lastRowNum = range.getLastRow(); //getting the last row index of the range.
var lastRowRange = ss.getRange(`${lastRowNum}:${lastRowNum}`); //narrowing the range (using A1 notation) to the last row only to apply color
var lastRowColor = lastRowRange.getCell(1,1).getBackgroundObject().asRgbColor().asHexString();
//Your row coloring logic here...
if (lastRowColor === '#ffffff'){ //toggling white/grey color as an example...
lastRowRange.setBackground('#cccccc'); //apply grey color to all cells in the last row range
} else {
lastRowRange.setBackground('#ffffff'); //apply white color to all cells in the last row range
};
}

Google Sheets - Add to Certain Value Based on Background Color

I've tried making a Google Apps Script, but I was having trouble trying to understand how to set it up. From this it seems like I can create a function that I can call inside the spreadsheet itself like the SUM function provided by Google Sheets. I've taken a look at the getBackground() function, but it seems like it needs some global variables included instead of just functions.
Here's my current spreadsheet:
I want to input a function where it takes in the ranges A2:A1000 and based on the background color of the cell, determine whether it goes into "Work" or "Life" and then adds it onto the cells E4 (Total Work) or F4 (Total Life) accordingly. The cells in column A will always be numbers.
Here's what I've tried, I think I may be off the path completely based off of my single cell approach:
function workTime(input) {
if (input.getBackground() == "#d9ead3") {
input.setFontColor(4285f4)
} else {
input.setFontColor(null)
}
}
//I get errors on line 3 for some reason though...
TL;DR Based on the background colors of the cells, how do I create a function that calculates the sum of the numbers in those specific colors and displays them in different cells under the "Total Work Time" and "Total Life Time" accordingly?
The "custom formula" approach is very limited
The only input you'll get into the custom formulae are the values, not the cell object. The function that is running the formula will never know about its location or formatting. It receives a value or an array of values, and returns a value or am array of values.
Apps Script version
function workTime2() {
let file = SpreadsheetApp.getActive();
let sheet = file.getSheetByName("Sheet1");
let range = sheet.getRange('A1:A16');
let targetColor = "#00ffff"
let values = range.getValues(); // [[1],[2],[3]...]
let colors = range.getBackgrounds(); // [[#ffffff],[#00ffff],[#ffffff]...]
let sum = 0
for (let i = 1; i != values.length; i++){ // starting at 1 to skip first row
let value = values[i][0]
let color = colors[i][0]
if (color == targetColor) {
sum += value
}
}
let resultCell = sheet.getRange('B2');
resultCell.setValue(sum);
}
This script will sum the values in A1:A16 if they are turquoise. Putting the sum in B2.
This is a way to get a sum based of a cell value. This should give you a good starting point to customize to your liking.
Reference
getRange(a1Notation)
getValues()
getBackgrounds()
setValue(value)

Change cell value based on the cell's background color in Google Sheets

In Google Sheets, I would like to change the cell value (in this case to add text) based on the cell's background color. For example, if the cell's background color is green (#62a84f) as in the picture attached, I would like to automatically insert text "g" in the cell.
Please see the picture
I found the following script but cannot figure out how to get it working:
function myFunction() {
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Prosinec+Leden");
var data1 = sheet1.getDataRange().getValues(); //2D array of sheet1 cell values
var bg;
for(var i=1; i<data1.length; i++)
{
bg = sheet1.getRange(?).getBackground();
if(bg == "#62a84f")
{
sheet1.getRange(?).setValue("x"); //Set value in corresponding row of sheet1
}
}
}
*Little background info - I work in an elementary school where we evaluate the progress of our pupils every week and it would save us a lot of work to have this script work.
Explanation:
While that's might not be an issue, the color "#62a84f" you selected is not one of the default colors in the color picker menu. In order to test the following code, I used the default #34a853:
But if you are sure that your color is indeed "#62a84f" then go ahead and use that and change the relevant part of my code to your case.
I used double map to iterate over every cell in the rng. The bigger the range the slower the performance of the code. I would highly advice you to use some specific range and not the whole data range.
Namely, you might want to replace:
const rng = sheet1.getDataRange(); with const rng = sheet1.getRange('B2:D10');
assuming that you want to check for green cells in the range 'B2:D10'.
Another reason you would want to use this approach is that you may have some green cells with no content outside of the DataRange and therefore your range won't contain these cells. So think carefully what is your scenario and choose the method that suits you the best.
Solution:
function myFunction() {
const sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Prosinec+Leden");
const rng = sheet1.getDataRange();
const colors = rng.getBackgrounds();
const values = rng.getValues();
colors.map((r,i)=>r.map((c,j)=>{
if(c=='#34a853'){
values[i][j]="g";
}}));
rng.clearContent();
rng.setValues(values);
}

How to get background color of all cells in a range and set background to a different range of the same size using apps script?

I am trying to copy the background color from a range of cells and paste it to a different range. In my case, my desired color is the range between rows 3 to 53 and every other column from E to BC. In A1 format, it would be (['E3:E53','G3:G53','I3:I53', ... 'BA3:BA53','BC3:BC53']). I want to get the background color of this range and then paste it to my target range, being between rows 3 to 53 and every other column from D to BB. This range in A1 notation would be (['D3:D53','F3:F53','H3:H53' ... 'AZ3:AZ53','BB3:BB53']).
In other words, I want every cell in my target range to be the same color as the adjacent cell to its right.
This is what I currently have.
This is my desired outcome using apps script.
I know I can change color manually, but the values I have in my sheet change frequently and the color of the cells I want to copy is based on conditional formatting rules, meaning I would have to manually change all the cell colors on a regular basis. That is why I want to use apps script, so all I have to do is run a function and it will create my desired effect for me.
I am very new to the world of coding, but this is what I have tried.
setColor(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Coach");
var targetArea = ss.getRangeList(['D3:D53','F3:F53','H3:H53','J3:J53','L3:L53','N3:N53','P3:P53','R3:R53','T3:T53','V3:V53','X3:X53','Z3:Z53',
'AB3:AB53','AD3:AD53','AF3:AF53','AH3:AH53','AJ3:AJ53','AL3:AL53','AN3:AN53','AP3:AP53','AR3:AR53','AT3:AT53','AV3:AV53','AX3:AX53','AZ3:AZ53',
'BB3:BB53']);
var desiredColor = ss.getRangeList(['E3:E53','G3:G53','I3:I53','K3:K53','M3:M53','O3:O53','Q3:Q53','S3:S53','U3:U53','W3:W53','Y3:Y53',
'AA3:AA53','AC3:AC53','AE3:AE53','AG3:AG53','AI3:AI53','AK3:AK53','AM3:AM53','AO3:AO53','AQ3:AQ53','AS3:AS53','AU3:AU53','AW3:AW53','AY3:AY53',
'BA3:BA53','BC3:BC53']);
var background = desiredColor.getBackgrounds;
targetArea.setBackgrounds(background)
}
Iv'e ran the code, but it tells me that "targetArea.setBackgrounds is not a function". If I remove the "s" from .setBackground, it does not do anything at all. No error or anything.
Any help would be greatly appreciated! Thank you!
The setBackground() method on the RangeList object allows you to only provide one color. Instead, you want to be calling setBackgrounds() (or setBackgroundObjects()) on the individual ranges. You can get those by calling getRanges() and then iterating through them to apply whatever changes you want.
function setColor() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Coach");
var targetRangeList = ss.getRangeList(['D3:D53','F3:F53','H3:H53','J3:J53','L3:L53','N3:N53','P3:P53','R3:R53','T3:T53','V3:V53','X3:X53','Z3:Z53',
'AB3:AB53','AD3:AD53','AF3:AF53','AH3:AH53','AJ3:AJ53','AL3:AL53','AN3:AN53','AP3:AP53','AR3:AR53','AT3:AT53','AV3:AV53','AX3:AX53','AZ3:AZ53',
'BB3:BB53']);
var sourceRangeList = ss.getRangeList(['E3:E53','G3:G53','I3:I53','K3:K53','M3:M53','O3:O53','Q3:Q53','S3:S53','U3:U53','W3:W53','Y3:Y53',
'AA3:AA53','AC3:AC53','AE3:AE53','AG3:AG53','AI3:AI53','AK3:AK53','AM3:AM53','AO3:AO53','AQ3:AQ53','AS3:AS53','AU3:AU53','AW3:AW53','AY3:AY53',
'BA3:BA53','BC3:BC53']);
var sourceRanges = sourceRangeList.getRanges();
var targetRanges = targetRangeList.getRanges();
for (var i = 0; i < sourceRanges.length; i++) {
targetRanges[i].setBackgrounds(sourceRanges[i].getBackgrounds());
}
}
By the way, you can probably get those ranges in a more programmatic way since the target ranges seems to be odd-numbered columns while the source ranges are even-numbered and they're all of the same height.
Issues:
The line var background = desiredColor.getBackgrounds; does not
actually execute getBackgrounds() because you forgot the parenthesis.
However, even if you didn't forget it, both getBackgrounds() and
setBackgrounds() are methods of a range object, not a
rangeList object as you use in your approach.
Solution:
Since the ranges are non-Contiguous you can't just use getRange(). Instead you can create a list of ranges and iterate over them to get the desired result.
function setColor(){
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Coach");
const targetArea = ['D3:D53','F3:F53','H3:H53','J3:J53','L3:L53','N3:N53','P3:P53','R3:R53','T3:T53','V3:V53','X3:X53','Z3:Z53',
'AB3:AB53','AD3:AD53','AF3:AF53','AH3:AH53','AJ3:AJ53','AL3:AL53','AN3:AN53','AP3:AP53','AR3:AR53','AT3:AT53','AV3:AV53','AX3:AX53','AZ3:AZ53',
'BB3:BB53'];
const desiredColor = ['E3:E53','G3:G53','I3:I53','K3:K53','M3:M53','O3:O53','Q3:Q53','S3:S53','U3:U53','W3:W53','Y3:Y53',
'AA3:AA53','AC3:AC53','AE3:AE53','AG3:AG53','AI3:AI53','AK3:AK53','AM3:AM53','AO3:AO53','AQ3:AQ53','AS3:AS53','AU3:AU53','AW3:AW53','AY3:AY53',
'BA3:BA53','BC3:BC53'];
const targetArea_Ranges = targetArea.map(ta=>ss.getRange(ta))
const desiredColor_Ranges = desiredColor.map(dr=>ss.getRange(dr))
targetArea_Ranges.forEach((tr,index)=>{
tr.setBackground(desiredColor_Ranges[index].getBackground())
});
}
The previous answers are right. You're calling methods that are not available on class Rangelist. In this answer, I would like to add a alternative approach, based on the fact that all your ranges are contiguous and follow a 1 to 1 pattern:
get all of the ranges in 1 swing: D3:BC53
getBackgrounds and copy paste colors in memory in array
setBackgrounds the modified array.
function setColors() {
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Coach');
const allArea = sh.getRange('D3:BC53');
const backgrounds = allArea.getBackgrounds();
for (const row of backgrounds) {
for (let i = 0; i < row.length - 1; row[i++] = row[i++]);
}
allArea.setBackgrounds(backgrounds);
}

How to get values from unspecified number of sheets

I have a spreadsheet that may have any number of sheets on it at any given time. These "Side Sheets" have a total value added and placed in a specified cell. We'll say this total is in cell "A1" on every side sheet. I want to total all of these side sheet totals, and place the total in-cell on another sheet.
I've coded a solution I think should work, but it displays "loading" forever. I'm certain there's an easier way to do this, I just can't see it.
function GETSIDESHEETTOTALS(){
var totalCell = "A1"
var total = 0;
var cur_ss = SpreadsheetApp.getActive();
cur_ss.getSheets().forEach(function(sheet){
total += sheet.getRange(totalCell).getValue();
});
return total;
}
I'm expecting the totals from each sheet to add together and display in the cell I've specified on the main sheet. I've placed the function "=GETSIDESHEETTOTALS()" into a cell on the main page of my spreadsheet. I would prefer it to be a cell-called function if possible.
Does anyone have an alternate solution, or can tell me what I'm doing wrong?
For those familiar with Excel, this could be rephrased as, "How do I use Google App Script to sum using 3D cell references?".
Briefly looking at yours, you do not exclude the sheet on which you aggregate the total. Perhaps you're recursively adding the values together?
My very quick example from scratch:
function sum3D(cellRef) {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var cumTotal = 0;
for (var i=0;i<sheets.length;i++){
if (sheets[i].getIndex()!==1){ // specifically omit the first sheet
cumTotal += sheets[i].getRange(cellRef).getValue();
}
}
return cumTotal;
}
This is implemented in the first sheet in my Google Sheet as "=sum3d('A1')".
However, I would recommend designing this more generally to simply return an array upon which you can perform any function (average, multiplications, etc.).
E.g.
function get3D(cellRef) {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var arr = [];
for (var i=0;i<sheets.length;i++){
if (sheets[i].getIndex()!==1){
arr.push( sheets[i].getRange(cellRef).getValue());
}
}
return arr;
}
and implemented as, e.g., "=sum(get3d('A1'))".
EDIT
Some parts unnecessarily separated in the code have been consolidated (but the overall function remains the same)
EDIT 2
There are obvious improvements regarding how you designate the aggregator sheet. For example, you could simply pass in the sheet name in the formula and omit that based on the return value of "sheets[i].getName()".