I'm looking for a way to conditionally format a cohort table in Google Sheets so that the colors will change from red (low values) through yellow (medium values) to green (high values) based on the values in each row. Anyone knows if this is possible?
Also, choosing the "Color scale" option in conditional formatting menu doesn't work because it colors the table based on the values of the full table, not each row individually.
I can use that option only if I apply it to each row individually, but my dataset has thousands of entries so that doesn't work for me.
Example table: https://docs.google.com/spreadsheets/d/1gpLMdgfs10Flt-VTtsc68E3Feju2H8UQhnai-R_9b3k/copy
Thanks in advance you guys are the greatest!
I wrote a simple script in Apps Script to apply the formatting to every row. It achieves the gradient per row that you want.
Example:
function applyColorGradientConditionalFormat(min = '#FF0000', mid = '#FF9900', max = '#00FF00') {
const sheet = SpreadsheetApp.getActiveSheet();
const lastCol = sheet.getLastColumn();
const conditionalFormatRules = sheet.getConditionalFormatRules();
// Build this conditional rule for all rows in sheet
for (let i = 2; i <= sheet.getLastRow(); i++) {
let range = sheet.getRange('R' + i + 'C2:R' + i + 'C' + lastCol);
let rule = SpreadsheetApp.newConditionalFormatRule()
.setRanges([range])
.setGradientMinpoint(min)
.setGradientMidpointWithValue(mid, SpreadsheetApp.InterpolationType.PERCENT, '50')
.setGradientMaxpoint(max)
.build()
conditionalFormatRules.push(rule);
}
// Apply all conditional rules built to the sheet
sheet.setConditionalFormatRules(conditionalFormatRules);
};
// Easy improvements: Create a menu to build all conditional formats manually
// or setup triggers to do it automatically
For your sample sheet this results in the following table:
Useful documentation:
Class ConditionalFormatRuleBuilder
getConditionalFormatRules()
non scripted:
=(B2:M2=MAX($B2:$M2))*($B2:$M2<>"")
=(B2:M2=MIN($B2:$M2))*($B2:$M2<>"")
Related
I'm making a sheet with details about a bunch of fictional characters, and one column I want to have is their height. I would also really like to use Conditional Formatting with a Color Scale to color-code the tallest and shortest characters, and everything in between.
Unfortunately, I live in the US, and am used to height expressed in feet and inches (e.g. 5'10''), which Google Sheets of course does not recognize as a number. Is there any way to remedy this, besides writing everything in terms of just inches (e.g. 60), such that I could apply conditional formatting directly to the column?
I've tried different formats (e.g. 5'10), and I considered having a hidden column with just the inch value and have conditional formatting work off of that row (doesn't work with Color Scale as far as I can tell, since you can't input a custom formula). One thought I had is somehow formatting things as an improper fraction with a denominator of 12, but hiding the denominator? But I have no idea how that would work. I've Googled as best I can, but I haven't found anything (everything's just about changing row height, which makes sense in hindsight).
I understand that you have two goals in mind. First of all, you should decide which unit length to use for managing heights. I have chosen inches, but you could work with feet if you need. This will simplify the scenario and will allow you to work easily with the data, but you could always create a function that translates inches to the foot/inches combo in order to show the data to a third party. This is the example table that I will use:
And this is my code, I will explain it at the bottom:
function main() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
data = sortTable(data);
sheet.getDataRange().setValues(data);
for (var i = 1; i < data.length; i++) {
data[i][2] = gradient(data.length, i);
}
for (var i = 1; i < data.length; i++) {
sheet.getRange(i, 2).setBackground("#" + data[i][2][0] + data[i][2][1] +
data[i][2][2]);
}
}
function sortTable(data) {
data.sort(function(a, b) {
return b[1] - a[1];
})
return data;
}
function gradient(arraySize, position) {
var relativePosition = position / arraySize;
var topColor = [parseInt("00", 16), parseInt("7A", 16), parseInt("33",
16)]; // Green
var bottomColor = [parseInt("FF", 16), parseInt("FF", 16), parseInt("FF",
16)]; // White
var positionColor = [0, 0, 0];
for (var i = 0; i < 3; i++) {
positionColor[i] = Math.floor(topColor[i] * (1 - relativePosition) +
bottomColor[i] * relativePosition).toString(16);
}
return positionColor;
}
First of all you have to read the data with a combination of getValues()/setValues(), and once you do that you can sort the table based on height so you can create the gradient later. Please notice how I separated the sorting function for better clarity.
After that you need the gradient color for setBackground(). To do so I developed a simple linear gradient function that calculates the RGB code from the top to the bottom. In my example the gradient fades from green to white, but you can change it. I also separated the gradient script into its own function. At this point you already have the sorted table and its gradient colors, so you only have to use setValues() and you are done. Feel free to leave any comment if you have doubts about this approach. This would be the final result:
UPDATE
Based in your comments I get that you need an imperial height format. For that case, you could use =INT(B2)&"' "&TRIM(TEXT(ROUND(MOD(B2,1)*12*16,0)/16,"# ??/??")&"""") (assuming that B2 contains the height). This approach will use Sheets Formulas to calculate the remainder part of the height, and its expression as an irreducible fraction. This is the final result:
Trying to ditch Excel for Google sheets.
I have this empty table with some colored cells that I need to fill with symbols. Presently I use this VBA script to do the job:
Sub mark()
Dim r As Range
Dim rCell As Range
Set r = Selection.Cells
For Each rCell In r
If rCell.Interior.ColorIndex = 10 Then rCell.Value = "×"
If rCell.Interior.ColorIndex = 3 Then rCell.Value = "×"
If rCell.Interior.ColorIndex = 45 Then rCell.Value = "×"
If rCell.Interior.ColorIndex = 1 Then rCell.Value = "×"
If rCell.Interior.ColorIndex = 15 Then rCell.Value = "×"
Next
End Sub
Is there a way to accomplish same thing using google sheets?
Solution
In order to achieve this you will have to use Google Apps Script. You can attach an Apps Script project to your Google Spreadsheet by navigating Tools > Script Editor.
You should find a template function called myFunction, a perfect starting point for your script.
Here you can start translating your VBA script to Apps Script which is very similar to Javascript.
First you should define some constants for your script:
// An array containing the color codes you want to check
const colors = ['#00ff00']; // Watch out, it's case sensitive
// A reference to the attached Spreadsheet
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('SheetName'); // Selecting the Worksheet we want to work with by name
// Here we retrieve the color codes of the backgrounds of the range we want to check
const range = ss.getDataRange().getBackgrounds(); // Here I select all the cells with data in them
Let's now loop through our range rows and columns to apply the logic:
The .getBackgrounds() method returns a multidimensional array in the form array[row][column] -> "background-color-code".
for (let i = 0; i<range.length; i++) {
let row = range[i];
// Let's loop through the row now
for (let j = 0; j< row.length; j++) {
let color = row[j];
// If the background color code is among the ones we are checking we set the cell value to "x"
if(colors.includes(color)) {
// Javascript index notation is 0 based, Spreadsheet one though, starts from 1
ss.getRange(i+1, j+1).setValue("x"); // Let's add 1 to our indexes to reference the correct cell with the .getRange(row, column) function
}
}
}
Reference
Please take a look at the documentation for further reading and method specifications
Google Apps Script Spreadsheet Service
Range Class
.getBackgrounds()
.getRange(row,column)
I am trying to create a select list in Google Sheet based on cells in another sheet. Those cells contains all the values that my list should display.
It works well but I also want to retrieve the style of those cells along with the values. So in my main sheet, depending on the selected value the style is copied from the "source" cells.
I know I can setup a conditional formatting so if the value is X or Y or Z I can apply a style but since my "source" cells are going to be updated, I'll have to also update those conditions which is a slow process.
I am wondering if there is a way to just dynamically copy the style of another cell.
Here is an example of my source cells:
Using Apps Script, you could create an onEdit trigger to do the following:
Track changes to the cells that contain data validations based on values in a range.
If the edited cell contains this data validation, look for the value in the source range that corresponds to the selected value.
Copy and paste the format of the cell in source range to the selected cell.
To do that, just create a bound script by selecting Tools > Script editor, copy the following code and save the project.
Code sample (check comments):
const onEdit = e => {
// Get information on edited cell (column, row, value):
const range = e.range;
const column = range.getColumn();
const row = range.getRow();
const value = range.getValue();
// Check that edited cell contains a validation rule and that its criteria type is "VALUE_IN_RANGE":
const validation = range.getDataValidation();
if (validation && validation.getCriteriaType() == "VALUE_IN_RANGE") {
const sourceRange = validation.getCriteriaValues()[0]; // Get range validation is based on
// In source range, get index of the cell that corresponds to selected value in edited cell:
const values = sourceRange.getValues();
let i, j;
for (i = 0; i < values.length; i++) {
j = values[i].indexOf(value);
if (j != -1) break;
}
const rangeToCopy = sourceRange.getSheet().getRange(sourceRange.getRow() + i, sourceRange.getColumn() + j); // Get cell in source range to copy format
rangeToCopy.copyFormatToRange(range.getSheet(), column, column, row, row); // Copy format to edited cell
}
}
Reference:
onEdit trigger
Class DataValidation
copyFormatToRange(sheet, column, columnEnd, row, rowEnd)
I am new at this so still trying to figure how everything works.
I have a sheet that collects responses from a Google Form. Based on the answer to one of those questions I would like the row from that sheet to move to a different sheet document all together (not a sheet in the same document).
I have it set on a time based trigger every minute so as new responses come in it would kick them over to the correct document and then delete the row from the original spreadsheet.
I have been able to get part of the way there. I would like for it to take the row, columns A through E, move those to the correct document, find where the next open row is and place the data in columns A through E on the new document.
Where my issue is coming in at the moment is when the row is moved to the new document. I have formulas saved in columns G - Z on the destination page. It is finding the last row with a formula and placing the row after that (which is at the very bottom of the page). I am pretty sure this has to do with using an array? But I may be wrong. Is there a way to just have that look at the destination page column A-E, find the next blank row, and copy A-E from the original file to the new page?
arr = [],
values = sheetOrg.getDataRange().getValues(),
i = values.length;
while (--i) {
if (value1ToWatch.indexOf(values[i][1]) > -1) {
arr.unshift(values[i])
sheetOrg.deleteRow(i + 1)
sheet1in.getRange(sheet1in.getLastRow()+1, 1, arr.length, arr[0].length).setValues(arr);
};
I have multiple If statements each with some changes to the "valueToWatch" and the "Sheet1in" for different values and destination pages. If that information helps at all.
You can find the last cell in a column with data in it like this:
function findLastValueInColumn() {
var column = "A"; // change to whatever column you want to check
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var lastDataRow = sheet.getDataRange().getLastRow();
for (var currentRow = lastDataRow; currentRow > 0; currentRow--) {
var cellName = column + currentRow;
var cellval = sheet.getRange( cellName ).getValue();
if (cellval != "") {
Logger.log("Last value in Column " + column + " is in cell " + currentRow);
break;
}
}
}
You can then use this function to figure out where to start your new data.
I am using Kendo Grid and using the methods suggested in this Article by Kendo. I am more interested towards the first approach as I find it faster than the 2nd approach.
The problem is if the number of records 65535, then it throws error
Invalid Row number (65536) outside allowable range (0..65535)
I am not able to find any solution to this. Done lot of research, tried the other method but that seems way too slow for my clients liking.
First you need to check if the row numbers are greater than 65535, if they are then you need to split the data in multiple sheets like so...
//create new workbook
var workbook = new HSSFWorkbook();
//create sheet
var sheet = workbook.CreateSheet();
//declare row number
int numberOfRow = 1;
//add value to sheet name inorder not to receive error that the sheet name already exists
int i = 0;
if(numberOfRow > 65535)
{
sheet = workbook.CreateSheet("(Name of sheet " + ++i + ")");
numberOfRow = 1;
//include your header row here
}