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
}
Related
I have a chart in Google Sheets, the "Data Range" is as follows...
MIR!J1:K4547,DIR!K1:K4547
What I am trying to do is reference a cell value for sheet "MIR" and "DIR" values.
Let's say I edit cell A1 to ZIR...
then the data range changes to
ZIR!J1:K4547,DIR!K1:K4547
I searched intensively in Google but maybe I am not using right keywords, will appriciate some feedback on this...
I'm not sure what type of chart you are trying to get, but you could create a dynamic range, however, this would only work for a line graph.
Choose any cell and enter this formula: =A1&"!K1:J4547"
Go to Data > Named Ranges and make the cell with the formula the named range. (This step is optional, but will make things easier)
You can now use this range for other formulas, for example, if you want a line graph: =SPARKLINE(INDIRECT(dynamicRange))
Reference: https://www.benlcollins.com/formula-examples/dynamic-named-ranges/
While writing this, I also realized a different work around, but using the same idea. Using the QUERY function, you can grab cell values, without needing to change the chart range. For example, placing the formula =QUERY(INDIRECT(A3)) in a cell, will grab all the values from DIR!K1:J4547. Changing the value of A1 in the sheet will change the sheet that it grabs from. From there, set the chart range to wherever you place the QUERY formula.
You could also just do =QUERY(INDIRECT(A3)), the named range is not necessary.
Hope this was helpful!
What I had done is the following
I created an empty spreadheet and used 3 columns on that sheet to construc the chart. Then I have created a script (I know very dirty code but it works).
The script replaces the data on the last sheet I created as follows:
function replace() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('CHART'); // or whatever is the name of the sheet
var range = sheet.getRange(1,1);
var range2 = sheet.getRange(1,2);
var data = range.getValue();
var data2 = range2.getValue();
var sheetFrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(data);
var sheetTo = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ChartData");
var sheet2From = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(data2);
var sheet2To = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ChartData");
// Copy from 17th row, 4th column, all rows for one column
var valuesToCopy = sheetFrom.getRange(1, 10, sheetFrom.getLastRow(), 1).getValues();
var valuesToCopy2 = sheetFrom.getRange(1, 11, sheetFrom.getLastRow(), 1).getValues();
var valuesToCopy3 = sheet2From.getRange(1, 11, sheet2From.getLastRow(), 1).getValues();
//Paste to another sheet from first cell onwards
sheetTo.clearContents();
sheetTo.getRange(1,sheetTo.getLastColumn()+1,valuesToCopy.length,1).setValues(valuesToCopy);
sheetTo.getRange(1,sheetTo.getLastColumn()+1,valuesToCopy2.length,1).setValues(valuesToCopy2);
sheetTo.getRange(1,sheet2To.getLastColumn()+1,valuesToCopy3.length,1).setValues(valuesToCopy3);}
For changing the Data Range I pulled data from the cells.
I have a Google Sheet with many (more than 50) tabs with different formula calculations. In some cells there are N/A as an output. The sheet is old an managed by different team members, so i can't manage it to edit every formula.
How can i remove N/A from all tabs, from the whole sheet?
I've seen many similar topics here, but all of them deal with a single formula on the level of editing of it with IFERROR or IFNA or similar - i will just not get it manually in the whole sheet. And i can't use search and replace - all formulas are different, so i can't just add =IFNA(.
Its easier to do with Apps Script
Here is a working snippet which replaces all N/A found in formulas in every sheet to ""
function replaceFormulaInSheets() {
var textToReplace = "N/A";
var sheets = SpreadsheetApp.getActive().getSheets();
for(var k in sheets){
var sheet = sheets[k];
var range = sheet.getDataRange();
var formulas = range.getFormulas();
for (var i in formulas) {
for (var j in formulas[i]){
formulas[i][j] = formulas[i][j].replace(textToReplace, "");
}
}
range.setFormulas(formulas);
}
}
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
}
}
}
}
}
}
}
I'm currently using a modified script that allows me to copy an entire line from sheet#1, create a new line at the top of sheet #2, paste the copied line to that sheet, and delete the old line from sheet#1.
This is done often throughout the day by many users. The function is onEdit.
This is the script :
function onEdit(e) {
var ss = e.source;
var activatedSheetName = ss.getActiveSheet().getName();
var activatedCell = ss.getActiveSelection();
var activatedCellRow = activatedCell.getRow();
var activatedCellColumn = activatedCell.getColumn();
var activatedCellValue = activatedCell.getValue();
var URGENCE = ss.getSheetByName("List"); // source sheet
var COMPLET = ss.getSheetByName("Comp"); // target sheet
// if the value in column K is "x", move the row to target sheet
if (activatedSheetName == URGENCE.getName() && activatedCellColumn == 11 && activatedCellValue == "x")
{
COMPLET.insertRows(2,1);// insert a new row at the second row of the target sheet
var rangeToMove = URGENCE.getRange(/*startRow*/ activatedCellRow, /*startColumn*/ 1, /*numRows*/ 1, /*numColumns*/ URGENCE.getMaxColumns());
rangeToMove.moveTo(COMPLET.getRange("A2"));
URGENCE.deleteRows(activatedCellRow,1); // delete row from source sheet
}
}
Recently this has been crashing my sheet. Everytime someone puts an "x" in Column K, the sheet will stall and most of the time, it will crash and chrome will kill the page.
I could be wrong, but the problem I'm guessing is that most of the rows in sheet#1 have conditional formatting. When the line is copied, it also copies the conditional formatting. This results in my sheet#2 having hundreds of repeating conditional formatting: this sheet is VERY slow to open. IT could also be because this document is shared with about 30 people who view it and edit it very often: perhaps onEdit isn't the right function here?
Is there a simple script I could add to my function which would strip the conditional formatting on the pasted line? I don't need the conditional formatting in my sheet#2 and for some odd reason I can't find an answer to this anywhere.
Found this function from here.
clearFormats()
Clears the sheet of formatting, while preserving contents. Formatting
refers to how data is formatted as allowed by choices under the
"Format" menu (ex: bold, italics, conditional formatting) and not
width or height of cells.
Sample code:
function testKillFormatting (nameOfSheet) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(nameOfSheet);
sheet.clearFormats();
}
I am trying to get a Google spreadsheet to automatically highlight all rows in a spreadsheet where the value in a particular column is above 0. I have looked for a few solutions but haven't got it to work.
I have got various metrics in the columns, so say I want to highlight all rows, in which column "I" has a value of more than zero.
Can someone help me with this?
On the conditional formatting page choose "Custom formula is" from the list of options available and then in the text field type:
=$I:$I>0
Select your formatting options and then in the range field type the range. For example:
A2:Z100
Because only to be applied to a single column there is a simpler version (that I have complicated so that text is not highlighted):
Clear formatting, select ColumnI and Format, Conditional formatting..., Format cells if... Custom formula is and:
=and(isnumber(I1),I1>0)
with fill of choice and Done.
If to format not just the relevant cell but the entire row then change the Apply to Range (say to A1:Z1000) and add anchors ($s) as below:
=and(isnumber($I1),$I1>0)
The only solution I'm aware of would be to write a script.
Link: Google Apps Scripts
The following is not pretty, but it works:
function myFunction() {
var I_INDEX = 1;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Highlight rows");
var dataRange = sheet.getDataRange();
var dataValues = dataRange.getValues();
for (var i=1; i<=dataRange.getNumRows(); i++) {
var row = sheet.getRange(i, 1, 1, 2);
if (dataValues[i-1][I_INDEX] > 0) {
row.setBackground("red");
}
}
}
See example, use "Tools" --> "Script Editor..." to view/run the script.