I'm working on adding better visualization to data tables that I have so that the highest numbers in that column will have a green CSS background, and the lowest values in that column will have a red CSS background.
I've come pretty far, I am basically down to the last bit. I'm a native PHP dev, so I may be messing up the integer comparison as well as not doing the final jQuery selector correctly. The code selects all the elements in the same column, finds the min and max, calculates the step value, and calculates how many steps above the minimum the current element is. All I need to do now is apply a css class based on the steps. It will be something like values in the 0-5% range will have css group 0, 5-10 will have css group 1, 10-15 group 2, 95-100 group 20. All that css is on the fiddle. I am successfully applying a CSS class, but not to a single cell, it does it for the whole column
$(document).on('click', '#dvData td.color', function() {
var ndx = $(this).index() + 1;
//alert('Val of ndx: ' + ndx);
var thisCol = $('#dvData td:nth-child(' +ndx+ ')');
var arr = thisCol.slice(1, thisCol.length);
var columnDataArr = new Array();
alert("Number of rows: " + arr.length);
//alert("First Row: " + arr[0].innerHTML);
for(var i = 0, x = arr.length; i < x; i++){
columnDataArr[i] = arr[i].innerHTML;
}
var colorsArray = ["63BE7B","72C27B","82C77C","91CB7D","A1D07E","B1D47F","C0D980","D0DD81","DFE282","EFE683","FFEB84","FFDE82","FED280","FDC47D","FDB87B","FCAA78","FB9D75","FB9073","FA8370","F9776E","F8696B"];
var max = Math.max.apply(Math, columnDataArr),
min = Math.min.apply(Math, columnDataArr),
range = max - min,
step_val = range/100;
alert("Step Value:" + step_val);
for(var i = 0, x = arr.length; i < x; i++){
var thisPercentile = parseInt((columnDataArr[i] - min) / step_val);
alert("Percentile:" + thisPercentile);
switch ( thisPercentile ) {
// yes this looks terrible, but i can't seem to get the case to work
// with: case(thisPercentile) <= 5:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
alert("Below 10th Percentile");
break;
case parseInt(90):
alert("90th Percentile");
//arr[2].addClass('group10') // doesn't work
break;
}
}
arr.addClass('group20');
});
So the two issues are how to deal with passing a Range to a switch statement (or giving up and using ifs and else ifs), and what the correct selector is to target the current table cell. I have my code on jsfiddle.
Since your value range goes from 0-100 and your group names go from group0-group20, you can do a bit of math and forego the switch/if statement entirely.
If you get the floor of ( value / 5 ), you will end up with 0 for 0-4, 1 for 5-9, ... 19 for 95-99, 20 for 100.
After getting the floor value, you can concatenate the result with the group name and add the result clsas like below:
Note: You created a vanilla JS array with splice, so you'll need to wrap arr[i] with $( ) to turn it into a jQuery object.
jsfiddle: http://jsfiddle.net/7Luwyyxr/2/
for(var i = 0, x = arr.length; i < x; i++){
var thisPercentile = parseInt((columnDataArr[i] - min) / step_val);
alert("Percentile:" + thisPercentile);
// added this stuff
var gnum = Math.floor( thisPercentile/5 ); // returns 0 for 0-4, 1 for 5-9, ...
//alert("Group Num: " + gnum);
$(arr[i]).addClass('group'+gnum); // appends class to array index
}
This solution will distinguish between 20 different numbers before assigning 2 numbers the same colors. It works with percentile (e.g. the percent of items it is greater than or equal to) to assign a color. The top value would always get the same class, and the lowest would always get the same class. The numbers in between would depend on each other to get a class assigned.
for(var i = 0, x = columnDataArr.length; i < x; i++){
var greaterThan = 0;
var curNum = columnDataArr[i];
for(var j = 0, x = columnDataArr.length; j < x; j++){
if(curNum <= columnDataArr[j]){
greaterThan += 1;
}
}
var percentile = Math.round((greaterThan*100)/columnDataArr.length);
var group = Math.round(percentile/5);
$(arr[i]).addClass('group'+group);
}
And heres a fiddle : http://jsfiddle.net/7Luwyyxr/4/
Related
Basically I have a script that is in 4 blocks:
1. Copies within a range each row provided it meets a criteria
2. Removes all empty rows
3. Sets all numbers as percentage
4. Applies conditional cell formatting to one of the columns
The 4th part is the one that is causing me issues. The script runs without any error message AND block 4 works perfectly fine if it's in another script alone with the same variables defined but as soon as it is inside the same function as the others it simply doesn't run without any error message of any kind.
Tried changing the name of the variables to single use ones to ensure it wasn't because one of the "var" was modified above it, removing the "else if" to keep only an "if" in the loop, moving it around to other parts of the script but if the block 1 is in the script then block 4 won't apply (will apply if it is only with 2 & 3.
2 & 3 which follow the same structure work well with 1.
Does any one have any clue what's wrong with my script ? :)
Each block is commented with what it does
function copy() {
//Set variables & criterion to choose which rows to copy
var s = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1bEiLWsbFszcsz0tlQudMBgTk5uviyv_wDx7fFa8txFM/edit');
var ssSource = s.getSheetByName('Variations');
var ssDest = s.getSheetByName('Email');
var lastRowSource = ssSource.getLastRow();
var lastRowDest = ssDest.getLastRow();
var lastColSource = ssSource.getLastColumn()
var criteria = 0;
var titles = ssSource.getRange(1,1,1, lastColSource).getValues()
//Copies the range
ssDest.getRange(1,1,1, lastColSource).setValues(titles)
for (var i = 2; i < lastRowSource; i++ ) {
var test = ssSource.getRange(i ,1);
Logger.log(test.getValue()+ ' <? ' + criteria);
if (ssSource.getRange(i ,6).getValue() > criteria) {
ssSource.getRange(i ,1,1,ssSource.getLastColumn()).copyTo(ssDest.getRange(i ,1,1,ssSource.getLastColumn()), {contentsOnly:true}); // copy/paste content only
}
}
//Removes empty rows
var data = ssDest.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])};
Logger.log(data[n].join().replace(/,/g,''))
}
ssDest.getDataRange().clear();
ssDest.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);
//Formats numbers as percentages
var rangePercent = ssDest.getRange(1,1,ssDest.getLastRow(),ssDest.getLastColumn());
var rowsPercent = rangePercent.getNumRows();
var colsPercent = rangePercent.getNumColumns();
for(var rowPercent = 1; rowPercent <= rowsPercent; rowPercent++) {
for(var colPercent = 1; colPercent <= colsPercent; colPercent++) {
var cellPercent = rangePercent.getCell(rowPercent, colPercent);
var valuePercent = cellPercent.getValue();
if(typeof(valuePercent) == 'number') {
cellPercent.setNumberFormat("##.#%");
}
}
}
//Adds conditional background colours
for (var z = 2; z < lastRowDest+1;z++) {
var avgCpc = 4;
var rangeColour = ssDest.getRange(z,avgCpc);
var dataColour = rangeColour.getValue()
if (dataColour < 0) {
ssDest.getRange(z,avgCpc).setBackground('#d9ead3')
}
else if (dataColour > 0) {
ssDest.getRange(z,avgCpc).setBackground('#f4cccc')
}
}
//Centers Values
}
The problem you're having is your code has performance issues because you're calling too many times methods such as getRange() and getValue() inside various loops, therefore Apps Script can't keep up with all those calls. Please check Best Practices.
Having said that, I modified your code in order to make it more efficient. Besides your copy function, I added two more functions to make the code more readable.
function copy
As before this function sets the variables, but now it calls two other functions, which are setPositiveCostValues and formatCells
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues
This will take the values where the cost is positive and it will get rip off of the cells with empty values and "n/a" values.
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells
This will format the cells in the cost col as a percentage and will set the right color in your avgCpc col.
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}
Code
Your whole code now it will look like this:
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}
I have done a code where it will generate 7 random numbers from 0 to 49.
HTML
<button id="btn_generate" onClick="getMyLuckyNumbers()">GENERATE NUMBERS</button>
<div id="display"></div>
JS
function getMyLuckyNumbers() {
for (var allNumbers=[],i=0;i<50;++i) allNumbers[i]=i;
function shuffle(array) {
var tmp, current, top = array.length;
if(top) while(--top) {
current = Math.floor(Math.random() * (top + 1));
tmp = array[current];
array[current] = array[top];
array[top] = tmp;
}
return array;
}
allNumbers = shuffle(allNumbers);
var luckyNumbers = "";
var g;
for (g = 0; g < 7; g++) {
luckyNumbers += allNumbers[g] + "<br>";
}
document.getElementById("display").innerHTML = luckyNumbers;
}
I would like to know how I can omit 0?
I attempted two ways, but both failed.
Attempt 1:
Changed the i=0 to i=1.
for (var allNumbers=[],i=1;i<50;++i)
allNumbers[i]=i;
This did omit 0 but when 0 was randomly supposed to appear, it shows as undefined.
Attempt 2:
I tried to do an if statement.
if(allNumbers != 0) {
allNumbers = shuffle(allNumbers);
}
But this still displays 0 if it happens to be randomly generated.
So, how do I omit 0?
The issue with starting i from 1 is that the 0th index will be empty, and so when accessed gives undefined.
So, if you want to avoid the number 0, you can make i start at 1, but you would need to change the way you add numbers to your array. Instead of adding your numbers to your array by placing them at a specific index, you can .push() them to the end of your array each iteration like so:
// \/------ start at i = 1, the 1st number to be added to your array
for (var allNumbers=[],i=1;i<50;++i)
allNumbers.push(i);
This way, you will fill up your array with numbers from 1 to 49 which can them be shuffled.
I am attempting to write a page which has a table where the user can input a number in each cell, it would then display a total of that row with the lowest two values of that row subtracted. The code creates the array, finds the total of the array and the lowest 2 values (low and low2 in the code below). However when I try to take the two lowest values from the total, i get an error. I think i have found the error as testing the values using isNaN i find that the sum of the array is not a number, which doesn't seem to make sense to me. Here is where I got up to with the code:
table = document.getElementsByTagName("tbody")[0];
allrows = table.getElementsByTagName("tr");
for (i=0; i < allrows.length; i++) {
rowTotal = 0;
rowArray =[];
for (ii=0; ii < allrows[i].getElementsByTagName("input").length; ii++) {
rowTotal = rowTotal + Number(allrows[i].getElementsByTagName("input")[ii].value);
rowArray.push(Number(allrows[i].getElementsByTagName("input")[ii].value));
var tots=rowTotal;
}
rowArray.sort(function(a, b){return b-a});
var low = $(rowArray[rowArray.length-2]);
var low2 = $(rowArray[rowArray.length-1]);
rowTotaladj = rowTotal- low - low2;
$(".row-total:eq("+(i-1)+")").html(rowTotaladj);
}
Here is a link to a previous version of the page which correctly displays the total (rowTotal) but does not have the total minus the lowest two values in:
Any explaination as to why the sum is not a number and help with the code would be much appreciated.
Since you're already using jQuery, you can use the following code to achieve what you want:
// When an <input> is changed:
$('table input').on('change', function() {
var sum = 0,
vals = [ ];
$(this).parents('table').find('input').each(function() {
var val = parseInt( $(this).val() ) || 0;
vals.push( val );
sum+= val;
});
// Now sort the array:
vals.sort();
total = sum - vals[0] - vals[1];
$('tfoot td').html( total );
});
jsFiddle Demo
My title might be terrible, but I can explain exactly what I want here. First of all, I am challenging myself to something very specific. Even if you guess what I'm trying to accomplish, please keep your answers on topic to what I am asking here, and not the overall problem I am solving.
I have a string of 25 generated letters.
I get an index of a letter via str.indexOf(c) and let's say that index is 16.
Visualize that str is a linear representation of a 5x5 table, thus an index of 16 would be the 4th row, 2nd column of that table.
I'm trying to find a way to find the row and column using javascript without looping through like this:
var row = 1;
var index = str.indexOf(c) + 1;
while(index > 5) {
index = index - 5;
row++;
}
With the above code, if index starts as 16, my end result will be row 4, index 2 - which is what I want. It just feels like there should be a way to do this with an algorithm instead of a loop.
Any thoughts?
You are replicating division and modulus in this code sample, you can achieve the same thing by doing row = Math.floor(index / 5) and col = index % 5.
var index = parseInt(prompt('Numeric Index'));
var row = Math.floor(index / 5) + 1;
var col = index % 5;
alert('[ ' + col + ', ' + row + ' ]');
Why not just use maths?
var index = 16;
var columnsPerRow = 5;
row = parseInt(index / columnsPerRow) + 1;
column = index % columnsPerRow + 1;
Instead of using a loop, you can just create a function
function findPosition(index, cols) {
var rowIndex = parseInt(index/cols) + 1;
var colIndex = index % cols;
return {rowIndex: rowIndex, colIndex: colIndex}
}
So in your case string index is 17, and your matrix col is 5
you will be calling findPosition(17, 5) and it will return {rowIndex: 4, colIndex: 2}
try this, you can just use the remainder (or modulus) to find the column and the whole number to find the row.
function getPosition(str, character, gridWidth) {
var index = str.indexOf(character);
var row = Math.floor(index/gridWidth);
var col = index%gridWidth;
return {row:row, col:col};
}
Then use it like this
var str = "hello sir";
console.log(getPosition(str, "i", 3));
You can caluclate the position, this solution depend on Matrix start at 0, 0 not 1, 1 add 1 to row and column if you want to start from index 1:
array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
number = 16
per_row = 5
index = array.indexOf(number)
row = Math.floor(index/ per_row )
column = index % per_row
I have:
function getRandomInt(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}
But the problem is I want randomise the population of something with elements in an array (so they do not appear in the same order every time in the thing I am populating) so I need to ensure the number returned is unique compared to the other numbers so far.
So instead of:
for(var i = 0; i < myArray.length; i++) {
}
I have:
var i;
var count = 0;
while(count < myArray.length){
count++;
i = getRandomInt(0, myArray.length); // TODO ensure value is unique
// do stuff with myArray[i];
}
It looks like rather than independent uniform random numbers you rather want a random permutation of the set {1, 2, 3, ..., N}. I think there's a shuffle method for arrays that will do that for you.
As requested, here's the code example:
function shuffle(array) {
var top = array.length;
while (top--) {
var current = Math.floor(Math.random() * top);
var tmp = array[current];
array[current] = array[top - 1];
array[top - 1] = tmp;
}
return array;
}
Sometimes the best way to randomize something (say a card deck) is to not shuffle it before pulling it out, but to shuffle it as you pull it out.
Say you have:
var i,
endNum = 51,
array = new Array(52);
for(i = 0; i <= endNum; i++) {
array[i] = i;
}
Then you can write a function like this:
function drawNumber() {
// set index to draw from
var swap,
drawIndex = Math.floor(Math.random() * (endNum+ 1));
// swap the values at the drawn index and at the "end" of the deck
swap = array[drawIndex];
array[drawIndex] = array[endNum];
array[endNum] = swap;
endNum--;
}
Since I decrement the end counter the drawn items will be "discarded" at the end of the stack and the randomize function will only treat the items from 0 to end as viable.
This is a common pattern I've used, I may have adopted it into js incorrectly since the last time I used it was for writing a simple card game in c#. In fact I just looked at it and I had int ____ instead of var ____ lol
If i understand well, you want an array of integers but sorted randomly.
A way to do it is described here
First create a rand function :
function randOrd(){
return (Math.round(Math.random())-0.5); }
Then, randomize your array. The following example shows how:
anyArray = new Array('1','2','3','4','5');
anyArray.sort( randOrd );
document.write('Random : ' + anyArray + '<br />';);
Hope that will help,
Regards,
Max
You can pass in a function to the Array.Sort method. If this function returns a value that is randomly above or below zero then your array will be randomly sorted.
myarray.sort(function() {return 0.5 - Math.random()})
should do the trick for you without you having to worry about whether or not every random number is unique.
No loops and very simple.