Conditional formatting for alternating groups of duplicated cells - google-sheets

I am trying to get a conditional formatting formula that highlights groups of duplicated cells in a column, but that also differentiates among them.
When I use the formula =COUNTIF($J:$J,J1)>1, I get:
123 (green)
123 (green)
345
567
765 (green)
765 (green)
812 (green)
812 (green)
876
But I want something more like:
123 (green)
123 (green)
345
567
765 (yellow)
765 (yellow)
812 (red) (or green again)
812 (red) (or green again)
876
I don’t necessarily need different colors for each group (although that would be really nice), but at least two colors that alternate between the groups, so I can't easily visually differentiate between two adjacent group of duplicated cells.
For that last part, I am working with the formula =isodd(match($J2,unique($J$2:$J))) (plus one with iseven to use it with another color), the problem with this one is that it also highlights unique cells (which I don’t want).
Is there is a way to combine the two or another formula altogether that accomplish this?

Add this script and your sheet will highlight duplicated rows.
It automatically updates every time you edit the sheet.
So you can insert rows, or add rows or change rows and it will still update correctly.
You can look up how to install the script, but its easy. Let me know if stuck.
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(1,1,lastRow);
sheet.getRange("A1:A").setBackground("white");
var rowValues = range.getValues();
var color1 = "#DBAB30"; // light yellow
var color2= "#3DA843"; // light green
var color = color1;
if (lastRow < 2) return; // do nothing if only 1 row.
var dupCount=0;
var row;
for (row=1; row<=lastRow; row++)
{
if (row == lastRow){ // check if past the last row.
applyBackground(row-1,dupCount);
}
else{
if (rowValues[row][0] == rowValues[row-1][0])
{
dupCount++;
}
else
{
applyBackground(row-1,dupCount);
dupCount=0;
}
} // end if not last row
} // end loop
function applyBackground(row,dupCount){
if (dupCount > 0)
{
var colorRange = sheet.getRange(row-dupCount+1,1,dupCount+1);
colorRange.setBackground(color);
if (color == color1) {color = color2} else {color=color1};
}
}
}
Produces this output:
Which I think is what you asked for?

This isn't quite what you're looking for, but you can apply this formula starting in row 2 to identify the odd groups:
=and(or(A2=A1,A2=A3),isodd(sumproduct((A$1:A1<>A$2:A2)*(A$2:A2=A$3:A3))))
and similarly for the even groups:
=and(or(A2=A1,A2=A3),iseven(sumproduct((A$1:A1<>A$2:A2)*(A$2:A2=A$3:A3))))
If you tried to apply this to row 1, you would get a #REF! error. The only way I could come up with to identify a repeated value starting in row 1 was to add a third rule applied to the whole range:
=and(A$1=A$2,countif(A$1:A1,"<>"&A1)=0)
and to modify the original formulas to
=and(or(A2=A1,A2=A3),isodd(sumproduct((A$1:A1<>A$2:A2)*(A$2:A2=A$3:A3))+(A$1=A$2)))
and
=and(or(A2=A1,A2=A3),iseven(sumproduct((A$1:A1<>A$2:A2)*(A$2:A2=A$3:A3))+(A$1=A$2)))

Related

Google Sheets Script To Dynamically Set Row Height

I have a Google Form that is dumping data into this Google Sheet. The data from the form can be very long, with paragraph breaks which causes the row height to be very very tall.
So I found the following script and it works (mostly), except that if I set the second number (35 in this case, aka 'num_rows') to 999 it says it's out of bounds.
function ResizeRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// Sets the rows to height of 21 pixels
sheet.setRowHeightsForced(2, 35, 21);
}
Is there a way I can have the num_rows portion be dynamic so it'll run no matter if the sheet has 50 or 100 rows?
You get the error because you can only set height of existing rows.
Besides, it seems the row height set in advance does not apply to rows appended upon new responses.
You may consider to setRowHeightsForced with a onSubmit function.
I figured it out, I added a maxrows function and subtracted one since I'm ignoring the top (header) row.
function ResizeRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// This example assumes there is a sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var form = ss.getSheetByName("Form Responses 1");
var total = form.getMaxRows()-1;
// Sets the rows to height of 21 pixels
sheet.setRowHeightsForced(2, total, 21);
}

How can I make a Google Sheets macro to search all rows for a particular fill color, copy the rows and paste them at the bottom of the sheet?

The rows in my sheet have a fill color in column A of that particular row. I want to be able to use a macro or maybe for loop to search for these rows with the color identifier, copy and paste them below, return to the row where I was and continue the search until the I've hit the bottom of the original list.
Update -
Basically I want to start with a sheet like this.
Google Sheet before macro
and have the end result look like this
Google sheet post macro
If I understand you correctly, you want to loop through each row in your sheet and copy/paste the ones which have a certain background color in column A after the last row. If that's the case, then you could use something along the following lines using Google Apps Script (you would have to create a script bound to your spreadsheet):
function appendRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var color = "#582323"; // Please change accordingly
var firstRow = 1;
var numRows = sheet.getLastRow() - firstRow + 1;
var column = 1;
var numCols = sheet.getLastColumn() - column + 1;
var range = sheet.getRange(firstRow, column, numRows); // Get column A
var backgrounds = range.getBackgrounds(); // Get backgrounds of each cell in column A
for (var i = 0; i < backgrounds.length; i++) { // Iterate through each cell in column A
if (backgrounds[i][0] == color) { // Check background color
var rowToCopy = sheet.getRange(i + 1, column, 1, numCols); // Row to be copied
var lastRow = sheet.getLastRow(); // Row index to copy to
rowToCopy.copyTo(sheet.getRange(lastRow + 1, column, 1, numCols)); // Copy row to the bottom
}
}
}
Notes:
Please change the background color you want to look for.
Check inline comments for more information on what the script is doing, line by line.
Reference:
getBackgrounds
getRange
copyTo
I hope this is of any help.

Need help adapting script to multiple days of the week

I found this code in another post. For Google Sheets, basically it copies the color formatting in the "Status" tab and colors in the matching cells in "Monday".
function colorCodeRevised() {
var ss=SpreadsheetApp.getActiveSpreadsheet()
var lr=ss.getSheetByName("Monday").getLastRow() // get last row of sheet1
var lc=ss.getSheetByName("Monday").getLastColumn() //get last column of sheet1
var lr1=ss.getSheetByName("Status").getLastRow() // get last row of sheet2
var lc1=ss.getSheetByName("Status").getLastColumn() ////get last column of sheet2
var sv=ss.getSheetByName("Monday").getRange(5,2,1,lc-15).getValues() // get vehicles. startrow,startcolumn,numrows to return,numcolumns to return
var sn=ss.getSheetByName("Monday").getRange(6,1,lr-5,1).getValues() // get names
var s1=ss.getSheetByName("Status").getRange(2,1,lr1-2,lc1)//exclude legend. squarebackets is sheet numbermm 0=1, 1=2
var rng1=s1.getValues() // get sheet2 data
var rng2=s1.getBackgrounds() // get background colors of dheet2 data
var test= sn.length
var test1= sv.length
var test2=rng1[0].length
var col=1 //column for vehicles on sheet1
for(var m=0;m<sv[0].length;m++){ //for each vehicle
col=col+1 //add one to vehicle column
for(var n=0;n<sn.length;n++){ //for each name
for(var i=0;i<rng1.length;i++){ //loop sheet2 data
for(var j=0;j<rng1[0].length;j++){
if(rng1[i][j].indexOf(sv[0][m])>-1 && rng1[i][j].indexOf(sn[n][0])>-1){ //if sheet2 data cell contains vehicle and name
var c=ss.getSheetByName("Monday").getRange(n+6, col).setBackground(rng2[i][j]) //set color of vehicle and name on sheet1
}}}}}
}
I have a very similar spreadsheet to this but I am struggling to make it run not only for Monday but for all the days of the week. I could make 6 identical scripts but I feel like it would be too taxing having the sheet run 7 scripts every time a change needs to be made. The cells that I want to be colored are in the exact same pattern/order as Monday. For example if C6 on Monday is red, C6 on Tuesday should also be red. Can anyone help me make this script color the same cells from Tuesday-Sunday as it does Monday, without it taking 7 times as long to run?
To your specific question; I think all you really need to do is add a loop with a list of the sheets you want to modify. From there, you can just make everything a variable. That solution is shown last.
Before I just let you off with that though, I'd like to suggest a few changes ^_^
Getting that code-sample and making it work for your sheet was great! I liked the concept and I get where you are with organizing all of that data. I made some changes that (I think) might help you out.
Most of these changes were made with the intention of bringing the logic out of the script and into the sheet. The phenomenal thing about using the formulas in the sheets is that they are optimized by Google very well. If you could break down what you're looking to do into code-snippets that Google has implemented, things get much faster.
Suggested changes:
Status page
Changed to matrix of users and roles. Each role that someone plays has an x in it. In the future, you can populate this matrix based on a form input if that's what you're going for.
Set A1 to contain all of the conditional formatting needed to properly color the table on the day pages.
Manually colored the role headers since I didn't want to re-do all the conditional formatting in A1 and hierarchical conditional formatting isn't a thing.
Day pages
Each cell in the color table now has a formula in it that will set the value to the role based on whether the user in that row has an x in that role on the status page.
Based on the text in those cells, they are colored to have the class' selected color for the text and background.
The script
Can be configured at the top with a list of pages to change and the location of the cell with the formatting.
I wish I could have created the formatting rules in the script, but that functionality isn't available yet. Hopefully it will be implemented when Google gets to this issue
Grabs the cell with the formatting
Loops through each page
Gets the color table
Copies the formula (in the script) and the formatting to the table
Finally, it adds a custom menu at the top that can be used to call the function named "Script tools".
Advantages
You only need to copy the formulas and formatting to each sheet once
When changes are made in status, they are immediately reflected on the proper page
Alternate solution
link to my sheet
link to make an editable copy in your drive
/* Loops through all whitelisted sheets and applies a pre-defined format to the table on each.
*
* Michael Kenworthy 12/21/16
*/
//Set-up variables
var whitelist = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]
var colorKeySheet = "Status"
var colorKeyFormatCell = "A1"
var colorTableRange = "B6:M"
var doc = SpreadsheetApp.getActive();
var ui = SpreadsheetApp.getUi();
function formatting() {
statusSheet = doc.getSheetByName(colorKeySheet);
formatCell = statusSheet.getRange(colorKeyFormatCell);
for(var i=0; i<whitelist.length; i++){
sheet = doc.getSheetByName(whitelist[i])
colorTable = sheet.getRange(colorTableRange);
cell = colorTable.getCell(1, 1)
cell.setFormula('if(not(isblank(INDIRECT("Status!R"&MATCH($A6,Status!$A:$A,0)&"C"&MATCH(B$5,Status!$1:$1,0),false))),B$5,)')
formatCell.copyTo(cell, {formatOnly: true});
cell.copyTo(colorTable)
}
}
function onLoad(){
// Or DocumentApp or FormApp.
ui.createMenu('Sheet tools')
.addItem('Apply formatting to day sheets', 'formatting')
.addToUi();
}
Solution to original question
var whitelist = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]
function colorCodeRevised() {
var ss=SpreadsheetApp.getActiveSpreadsheet()
var lr1=ss.getSheetByName("Status").getLastRow() // get last row of sheet2
var lc1=ss.getSheetByName("Status").getLastColumn() ////get last column of sheet2
var s1=ss.getSheetByName("Status").getRange(2,1,lr1-2,lc1)//exclude legend. squarebackets is sheet numbermm 0=1, 1=2
var rng1=s1.getValues() // get sheet2 data
var rng2=s1.getBackgrounds() // get background colors of dheet2 data
for(var sheetNum=0;sheetNum<whitelist.length;sheetNum++){
var lr=ss.getSheetByName(whiltelist[sheetNum]).getLastRow() // get last row of sheet1
var lc=ss.getSheetByName(whiltelist[sheetNum]).getLastColumn() //get last column of sheet1
var sv=ss.getSheetByName(whiltelist[sheetNum]).getRange(5,2,1,lc-15).getValues() // get vehicles. startrow,startcolumn,numrows to return,numcolumns to return
var sn=ss.getSheetByName(whiltelist[sheetNum]).getRange(6,1,lr-5,1).getValues() // get names
var col=1 //column for vehicles on sheet1
for(var m=0;m<sv[0].length;m++){ //for each vehicle
col=col+1 //add one to vehicle column
for(var n=0;n<sn.length;n++){ //for each name
for(var i=0;i<rng1.length;i++){ //loop sheet2 data
for(var j=0;j<rng1[0].length;j++){
if(rng1[i][j].indexOf(sv[0][m])>-1 && rng1[i][j].indexOf(sn[n][0])>-1){ //if sheet2 data cell contains vehicle and name
var c=ss.getSheetByName(whiltelist[sheetNum]).getRange(n+6, col).setBackground(rng2[i][j]) //set color of vehicle and name on sheet1
}
}
}
}
}
}
}

Looping through subviews swift / ios

var replycount = replies.count
var startingTag = 10
for subview in self.personView.subviews {
if replycount > 0 {
subview.viewWithTag(startingTag)?.backgroundColor = .green
replycount = replycount - 1
startingTag = startingTag + 1
}
}
}
I'm pulling a number from a server (replycount) and trying to represent the number by coloring some views on the screen. I've got 10 bubbles across the bottom, and if replycount was 4, starting from the left I'd want 4 of the bubbles to have a green background color, and the rest to remain their default black.
What I'm trying to do with the above code is to grab the reply count which I"m doing successfully, my first bubble starts at a tag of 10 and goes up to 19, and if the reply count is more than 0, meaning there is a reply, I'm wanting to take the first tag of 10, make it green, then move on to the next tag of 11, minus from the reply count, and keep going until there are no more replies.
The only time the code below works is if I comment out
replycount = replycount - 1
and change viewWithTag(startingTag) to viewWithTag(10) and hardcode in the number. If either of those two things aren't done the view's color is not changed.
Is there a better way to do this, or any ideas why I'm running into this issue?
Skip looping through subviews and just do self.view.viewWithTag? Although I'm just assuming all of the bubbles are in the same view, and not each in a different subview.
Although I would probably have written it something like this for clarity:
var replycount = replies.count
for tag in 10 ..< (10 + replycount) {
self.view.viewWithTag(tag)?.backgroundColor = .green
}

Can you count links within a range?

I have many Google sheets that require manually hyperlinking (to specific unique documents) after other users have inserted values, but it is time-consuming to search through many sheets for cells yet to be linked. A basic solution would be to use COUNTA to get the number of cells within a range containing text, and a second function to count the number of links, showing the difference. I've tried many permutations of COUNTA and COUNTIF using wildcards but nothing seems to be able to recognise formulas. Is there a function within Google Sheets of getting the number of hyperlinked cells within a range?
If you are referring to urls in cells (like: www.google.com), you can try:
=SUM(ArrayFormula(N(ISURL(A3:A))))
Change range to suit.
This will not work if you are using =HYPERLINK() function.
EDIT: If you want to count the cells with text, but exclude the =Hyperlink() formulas (AND empty cells) you can try this custom function:
function countF(range) {
var r = SpreadsheetApp.getActive().getRange(range),
formulas = r.getFormulas(),
count = 0;
r.getValues()
.forEach(function (r, i) {
r.forEach(function (c, j) {
if (c && formulas[i][j].substring(1, 10) !== "HYPERLINK") count += 1;
})
})
return count;
}
This custom function can be used in your spreadsheet by entering
=COUNTCELLS("Sheet1!A1:A2")
If you want to exclude all formulas from your count, change the if-statement to:
if (c && !formulas[i][j]) count +=1
Make sure you always mention the sheet name.
EDIT2: to count the number of formulas, you can try something like this:
function countFormulas(range) {
var count = 0;
SpreadsheetApp.getActive()
.getRange(range).getFormulas()
.forEach(function(r) {
r.forEach(function(c) {
if (c.charAt(0) == '=') count += 1;
})
})
return count;
}
I suggest the following. The formula assumes the cells to check for Hyperlinks are in column A with header (adjust formula to your need). Select A2. Right click and select Conditional Formatting. Enter the following:
Apply to range
A2:A
Format cells if
Choose 'Custom formula is' from dropdown list and enter:
=AND(NOT(ISFORMULA(A2)),NOT(ISBLANK(A2)))
Choose the formatting style you want.
This will format and non blank cell not containing a formula. (No Hyperlink)
Two other options to get just a count:
In a column, lets say B enter the following formula(Note that ISFORMULA does not work in array formulas like ISBLANK does.) Copy the formula down. It will return FALSE if there is a Hyperlink and TRUE if there is not.
=NOT(ISFORMULA(A2))
Then to count use:
=countifs(B2:B,"TRUE",B2:B,"<>''")
The other option is script:
function noFormula() {
var ss=SpreadsheetApp.getActiveSpreadsheet()
var s = ss.getSheets()[0];// [0] is Sheet1
var lr=s.getLastRow()
var rng =s.getRange(2, 1, lr-1, 1) //get column A data. Assumes header row.
var data=rng.getFormulas()
count=0
for(i=0;i<data.length;i++){
var hasFormula=data[i][0]
var eqSign=hasFormula.substring(0,1) //looks for first character "=". If there are formulas other than Hyperlinks change to substring(0,2) to look for "=H".
if(eqSign !="="){ //Not equal to"="
count = count+1
}}
var c=s.getRange(1,2).setValue(count)//Sheet1 B1
}

Resources