Color manipulation library scripts
First off for this project we are going to need to figure out how to manipulate colors in Google Apps Script. As per the approach in Roadmapper migration my objective is to make the structure as much like the VBA version as possible so as to make dual maintenance a possibility.
Color Ramp Library
The approach will be the same as Create a heatmap in Excel, with an extensible library of named colorramps calling a general color generator.
Here is the result of applying various color ramps to a few lines of a Google Spreadsheet.
Copy of heatMap
Code for test Example
This code produces the example above. To test it, go to the spreadsheet and press the button.
The main points here are
- the use of a predefined library along with maximum and minimum values for the ramp against the current value, which returns a composute RGB code as used by VBA.
- the use of
rgbToHTMLHex
which converts an RGB code to an HTML code as used by Google Script. - The use of caching to improve speed – otherwise it takes much longer to set the cells one by one.
This is all that’s needed. The color ramp and caching utilities are provided in the mcpher script library.
Heatmap with random numbers
function testHeatMapRandomRamp() { var rampList = [ "heatmap", "heatmaptowhite", "blacktowhite", "whitetoblack", "hotinthemiddle", "candylime", "heatcolorblind", "gethotquick", "greensweep", "terrain", "terrainnosea"]; var nPoints = 10; var nRows = 20; var wr = mcpher.wholeSheet("heatmaprandom"); mcpher.WorkSheet(wr).clearFormats(); mcpher.WorkSheet(wr).clear(); // since we know the dimensions // extending the cache at beginning will save a few cycles var cacheData = mcpher.sheetCache(wr).extend(nRows,nPoints); var cacheColor = mcpher.sheetCache(wr,"getBackgroundColors").extend(nRows,nPoints); var cacheFontColor = mcpher.sheetCache(wr,"getFontColors").extend(nRows,nPoints); // create some random data for (var r = 0 ; r < nRows ; r++) { for (var c = 0; c < nPoints ; c++) { cacheData.setValue(mcpher.randBetween(0,100),r+1,c+1); } } // extremes of heatrange var mx = cacheData.max(); var mn = cacheData.min(); cacheData.commit(); // now apply the colors for (var l=0 ; l < rampList.length ; l++) { cacheData.forEach( function(v,r,c) { // the heatmap color and its properties var fillColor = mcpher.makeColorProps(mcpher.rampLibraryRGB(rampList[l], mn, mx, v)) ; // set the fill cacheColor.setValue(fillColor.htmlHex,r,c); // get a friendly font color for this fill cacheFontColor.setValue(mcpher.rgbToHTMLHex(fillColor.textColor),r,c); } ); cacheColor.commit(); cacheFontColor.commit(); mcpher.tryToast("painted color ramp "+rampList[l]) mcpher.sleep(5000); } // we're done cacheData.close(); cacheFontColor.close(); cacheColor.close(); }
Picking a good contrast font color
var fillColor = new mcpher.colorProps(mcpher.rampLibraryRGB(rampList[l], mn, mx, v)) ; // set the fill cacheColor.setValue(fillColor.htmlHex,r,c); // get a friendly font color for this fill cacheFontColor.setValue(mcpher.rgbToHTMLHex(fillColor.textColor),r,c);
The code for that is here.
function w3Luminance (rgbColor) { // this is based on // http://en.wikipedia.org/wiki/Luma_(video) return (0.2126 * Math.pow((rgbRed(rgbColor)/255),2.2)) + (0.7152 * Math.pow((rgbGreen(rgbColor)/255),2.2)) + (0.0722 * Math.pow((rgbBlue(rgbColor)/255),2.2)) ; }
function makeColorProps (rgbColor) { return new colorProps().populate(rgbColor); } function colorProps() { return this; }
The colorRamp library
function rampLibraryRGB(ramp, min , max, value , optBrighten) { var brighten = fixOptional (optBrighten, 1); if (IsArray(ramp)) { // ramp colors have been passed here return colorRamp(min, max, value, ramp,undefined , brighten); } else { switch(Trim(LCase(ramp))) { case "heatmaptowhite": return colorRamp(min, max, value, [VBCOLORS.vbBlue, VBCOLORS.vbGreen, VBCOLORS.vbYellow, VBCOLORS.vbRed, VBCOLORS.vbWhite],undefined , brighten); case "heatmap": return colorRamp(min, max, value, [VBCOLORS.vbBlue, VBCOLORS.vbGreen, VBCOLORS.vbYellow, VBCOLORS.vbRed], undefined, brighten); case "blacktowhite": return colorRamp(min, max, value, [VBCOLORS.vbBlack, VBCOLORS.vbWhite], undefined, brighten); case "whitetoblack": return colorRamp(min, max, value, [VBCOLORS.vbWhite, VBCOLORS.vbBlack],undefined , brighten); case "hotinthemiddle": return colorRamp(min, max, value, [VBCOLORS.vbBlue, VBCOLORS.vbGreen, VBCOLORS.vbYellow,VBCOLORS.vbRed,VBCOLORS.vbGreen, VBCOLORS.vbBlue], undefined, brighten); case "candylime": return colorRamp(min, max, value, [RGB(255, 77, 121), RGB(255, 121, 77), RGB(255, 210, 77), RGB(210, 255, 77)], undefined, brighten); case "heatcolorblind": return colorRamp(min, max, value, [VBCOLORS.vbBlack,VBCOLORS.vbBlue, VBCOLORS.vbRed, VBCOLORS.vbWhite], undefined, brighten); case "gethotquick": return colorRamp(min, max, value, [VBCOLORS.vbBlue,VBCOLORS.vbGreen, VBCOLORS.vbYellow, VBCOLORS.vbRed],[0, 0.1, 0.25, 1] , brighten); case "greensweep": return colorRamp(min, max, value, [RGB(153, 204, 51), RGB(51, 204, 179)] ,undefined , brighten); case "terrain": return colorRamp(min, max, value, [VBCOLORS.vbBlack, RGB(0, 46, 184), RGB(0, 138, 184), RGB(0, 184, 138), RGB(138, 184, 0), RGB(184, 138, 0), RGB(138, 0, 184), VBCOLORS.vbWhite] ,undefined , brighten); case "terrainnosea": return colorRamp(min, max, value, [VBCOLORS.vbGreen, RGB(0, 184, 138), RGB(138, 184, 0), RGB(184, 138, 0), RGB(138, 0, 184), VBCOLORS.vbWhite] , undefined, brighten); case "greendollar": return colorRamp(min, max, value, [RGB(225, 255, 235), RGB(2, 202, 69)], undefined, brighten); case "lightblue": return colorRamp(min, max, value, [RGB(230, 237, 246), RGB(163, 189, 271)], undefined, brighten); case "lightorange": return colorRamp(min, max, value, [rgb(253, 233, 217), rgb(244, 132, 40)], undefined, brighten); default: DebugAssert(false,"Unknown library entry " + ramp) ; } } }
The colorRamp function.
This is called by the library to calculate the color for the given value and its place in its scale. If the color ramp you need is not predefined, you can call this directly to create your own. Arguments are as follows
- min – the minimum value of the scale
- max – the maximum value of the scale
- value – the value being assessed
- mileStones – these are a list of RGB codes for the main colors that make up the scales.
- fractionStones – by default, these are evenly spaced – for example if there are 3 milestones – lets say blue, green, and yellow – then the fractionSones used would be 0, .33 and .66 – meaning that any value up to .33 * (max-min) , would fall somewhere between blue and green. This can be changed – let’s say you wanted to spend longer on shades of yellow, you could set say , 0 , .2 and .4. That way, any value above .4 would fall somewhere between green and yellow.
- optBrighten – this can be used to darken ( < 1 – for example .5 ) or brighten (>1 fpor example 1.5) the color tones. The default is 1.
function colorRamp(min, max , value , mileStones , fractionStones, optBrighten) { // color ramp given or default var brighten = fixOptional ( optBrighten,1); var ms = IsMissing(mileStones) ? [VBCOLORS.vbBlue,VBCOLORS.vbGreen,VBCOLORS.vbYellow, VBCOLORS.vbRed,VBCOLORS.vbRed,VBCOLORS.vbWhite] : mileStones; DebugAssert( ms.length , "No milestone colors specified"); // only 1 milestone - thats the color if (ms.length == 1) return ms[lb]; // fractions of range at which to apply these colors var fs = []; if (!IsMissing(fractionStones)) { DebugAssert( fractionStones.length == ms.length, "no of fractions must equal number of steps" ); fs = fractionStones; } else { // equal proportions fs[0]=0; for (var i = 1 ; i < ms.length ; i++ ) fs[i] = i/(ms.length-1) ; } // now calculate the color var spread = max - min; DebugAssert (spread >= 0 , "min is greater than max for color spread"); var ratio = (value - min) / spread; DebugAssert (ratio >= 0 && ratio <= 1, "couldnt calculate ratio for color spread"); //find which slot this value belongs in for (var i = 1; i < ms.length;i++) { if (ratio <= fs[i]) { var r = (ratio - fs[i - 1]) / (fs[i] - fs[i - 1]); var red = rgbRed(ms[i - 1]) + (rgbRed(ms[i]) - rgbRed(ms[i - 1])) * r; var blue = rgbBlue(ms[i - 1]) + (rgbBlue(ms[i]) - rgbBlue(ms[i - 1])) * r; var green = rgbGreen(ms[i - 1]) + (rgbGreen(ms[i]) - rgbGreen(ms[i - 1])) * r; return RGB(lumRGB(red, brighten), lumRGB(green, brighten), lumRGB(blue, brighten)); } } DebugAssert (false,"ColorRamp failed to work - dont know why"); }
Utilities and full code.
Here is the full code of the usefulColors Module in the mcpher library
Next Steps
For more like this, see From VBA to Google Apps Script .
For help and more information join our forum, follow the blog or follow me on Twitter .