Color Schemes and palettes

Many applications have their own names for colors in their palettes. This example features an Apps Script library to retrieve and manipulate colors according to their names in their home palettes.

The known palettes currently are Material colors, HTML named colors and the Google Apps Palette.

The Google Apps Palette looks like this in the GUI.


I've generated the material palette using this library, along with the closest matching colors in the Apps and html palette. 


Here's the html  palette, showing the closest matches in the other palettes.

and the google palette


The idea  is to be able to get a color by its palette and name, or to find its name from its color codes. 

You can use the library by including this in your project.

MBo5MKxo06_g-0n5MOckL0ai_d-phDA33

It's also available on Github if you prefer, and the test spreadsheet is here if you'd like to make a copy.

Some examples (if you are using the library you'll need to prefix ColorList with the library identifier eg cColorList.ColorList.getColor())
function test() {
  
  // get a color properties
  Logger.log(JSON.stringify(cColorList.ColorList.getColor('google','light red berry 1')));
  
  // get a color properties by hex code
  Logger.log(JSON.stringify(cColorList.ColorList.getColorName('google','#f3f3f3')));
  
  // get a color properties by rgb number
  Logger.log(JSON.stringify(cColorList.ColorList.getColorName('html',255)));
  
  // get closest
  Logger.log(JSON.stringify(cColorList.ColorList.getClosest ('html' , '#ff9900')));
  Logger.log(JSON.stringify(cColorList.ColorList.getClosest ('material' , '#ff9900')));
  
  // check grey/gray
  Logger.log(JSON.stringify(cColorList.ColorList.getColor('material','grey 600')));
  Logger.log(JSON.stringify(cColorList.ColorList.getColor('google','light gray 2')));
  
  // get closest
  var lister = cColorList.ColorList;
  Logger.log(JSON.stringify(lister.getClosest ('material' , lister.getColor('google','red berry').value)));
}


The result is not only the code matching the name and palette, but loads of additional color related information (see Color Arranger for more information on these properties).
{
    "name": "light red berry 1",
    "value": "#ca4126",
    "palette": "google",
    "status": "ok",
    "properties": {
        "rgb": 2507210,
        "red": 202,
        "green": 65,
        "blue": 38,
        "htmlHex": "#ca4126",
        "luminance": 0.16378548548538505,
        "textColor": "#ffffff",
        "contrastRatio": 4.91146532991259,
        "black": 0.207843137254902,
        "yellow": 0.8118811881188118,
        "magenta": 0.6782178217821783,
        "cyan": 0,
        "hue": 9.878048780487813,
        "saturation": 68.33333333333333,
        "lightness": 47.05882352941176,
        "value": 0.792156862745098,
        "x": 26.597269352335072,
        "y": 16.47709067915203,
        "z": 3.612286684447369,
        "LStar": 47.594296368851595,
        "aStar": 52.928166803487386,
        "bStar": 45.38090169447055,
        "cStar": 69.71956023800627,
        "hStar": 40.609968669570854
    }
}

You can then use this in your Apps Script code to set background colors and avoid worrying about hex codes. (spacing and case are ignored in the color names) - for example.
ColorList.getColor('google','cornflower blue').value

Text Color

A very useful property is the the textColor, which will be set to either black or white, depending on which is the best font color to use with the given background color. 

This property can be used to set the font color appropriately if you change the background color of a cell.
ColorList.getColor('google','light red berry 1').properties.textColor

Matching Colors

This library also provides a useful way to get the closest match in a palette for some desired color. 

This snip shows the beginning of the result of finding the nearest match between the html palette and the google palette.


and visa versa

The material palette versus google apps.


and so on .. the full palette matches and details can be found in this sheet.

The Code for matching colors.

Finding the nearest match for a given color code in a palette is simple. Here's we're looking for the closest color in the html palette to #ff9900 (orange in the google palette).
Logger.log(JSON.stringify(ColorList.getClosest ('html' , '#ff9900')));

The query could also be written like this.
Logger.log(JSON.stringify(ColorList.getClosest ('html' , ColorList.getColor('google' , 'orange' ))));


It gives a result like this, where diff is a value between 0-100. The lower, the better the match.
{
    "diff": 4.2351903633318795,
    "member": {
        "name": "Orange",
        "value": "#ffa500"
    }
}

Code for comparing to complete color schemes and dumping the result

// get the best match between html and google and material palettes

function match () {
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var lister = cColorList.ColorList; 
  
  // compare combinations of palettes
  comparePalettes_ ('google','html');
  comparePalettes_ ('google','material');
  
  comparePalettes_ ('html','google');
  comparePalettes_ ('html','material');
  
  comparePalettes_ ('material','html');
  comparePalettes_ ('material','google');
  
  /** 
  * @param {string} p1 palette1
  * @param {string} p2 palette2
  */
  
  function comparePalettes_ (p1, p2) {
    
    // do comparison and write to sheet
    var sheet = ss.getSheetByName(p1+'_'+ p2) || ss.insertSheet(p1+'_'+ p2);
    
    // clear the sheet 
    sheet.clearContents();
    
    // get each of the palettes
    var list1 = lister.getList(p1);

    // add the properties to the sheet
    var heads = {};
    heads[p1+'Name'] = Object.keys(heads).length;
    heads[p1+'Value'] = Object.keys(heads).length;
    heads[p2+'Name'] = Object.keys(heads).length;
    heads[p2+'Value'] = Object.keys(heads).length; 
    heads.difference =  Object.keys(heads).length; 
    
    // find the nearest matches
    var values = Object.keys(list1).map(function(d) {
      
      // get the nearest match in the other list
      var best = lister.getClosest (p2 , list1[d].value);
      
      // make this row     
      var row = [];
      row[heads[p1+'Name']] = list1[d].name;
      row[heads[p1+'Value']] = list1[d].value;
      row[heads[p2+'Name']] = best.member.name;
      row[heads[p2+'Value']] = best.member.value;
      row[heads.difference] =  best.diff; 
      return row;
    });
    
    // now write the whole thing out
    sheet.getRange (1,1,values.length+1 , Object.keys(heads).length)
    .setValues([Object.keys(heads)].concat(values));
    
    // colorize the columns
    sheet.getRange (2,1+heads[p1+'Name'],values.length , 2)
    .setBackgrounds(values.map (function(row) {
      return row.slice(0,2).map(function(col) {
        return row[heads[p1+'Value']];
      });  
    }));
    
    sheet.getRange (2,1+heads[p2+'Name'],values.length , 2)
    .setBackgrounds(values.map (function(row) {
      return row.slice(0,2).map(function(col) {
        return row[heads[p2+'Value']];
      });  
    }));
    
    // choose best font color
    sheet.getRange (2,1+heads[p1+'Name'],values.length , 2)
    .setFontColors(values.map (function(row) {
      return row.slice(0,2).map(function(col) {
        return lister.getColor(p2,row[heads[p2+'Name']]).properties.textColor;
      });  
    }));
    
    sheet.getRange (2,1+heads[p2+'Name'],values.length , 2)
    .setFontColors(values.map (function(row) {
      return row.slice(0,2).map(function(col) {
        return lister.getColor(p2,row[heads[p2+'Name']]).properties.textColor;
      });  
    }));

  }
}

The gadget spec URL could not be found
The gadget spec URL could not be found


The gadget spec URL could not be found

Adding palettes

It is possible to add your own palettes with a simple pattern. To demonstrate, I'll use the material color design principle as demonstrated in the material palette site, where you can create something like this given a primary and an accent color.


By providing a primary and an accent color, here's the same thing applied to a sheet. In this case using Amber and Blue.

Abstracting the actual color selection means that colors can be referred to as dark, light, primary, icons, accent, secondaryText and primaryText and changing the color is just a matter of picking a new primary and accent color, with no changes of code required. This library can be used for client side color manipulation too of course.

A new Material palette combination is created like this
var theCombination = new cColorList.MaterialPalette('pink','blue');

and components accessed like this, which allows complete abstraction from the color scheme used.
var accent = lister.getColor (theCombination.getName(), 'accent').value;


Here's the code for creating a selection of combinations, each on its own sheet. 
// generate various material palette combinations
function generateMaterial() {
  
  var lister = cColorList.ColorList;
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  // here's a list of palette combinations to try
  [['pink','lime'],['amber','blue'],['purple','deeporange'],['deeporange','purple']].forEach(function (combination) {
    
    // create the material palette
    var mp = new cColorList.MaterialPalette(combination[0],combination[1]);
    
    // a sheet for each known combination or create one
    var sheet = ss.getSheetByName(mp.getName()) || ss.insertSheet(mp.getName());
    
    // clear the sheet 
    sheet.clear();
    
    //now we can use that with the normal lister stuff show all the props on a sheet
    var members = lister.getList(mp.getName());
    sheet.getRange(1,1,1,1).setValues ([['combination:' + mp.getName()]]);
    Object.keys(members).forEach(function(d,i) {
      
      // get the details on the material palette
      var color = lister.getColorName ('material', members[d].value);

      // write the details
      sheet.getRange(i+2,1,1,3).setValues ([[members[d].name,color.name,members[d].value]]);
      
      // set the background to show the color
      sheet.getRange(i+2,1,1,3).setBackgrounds ([[members[d].value,members[d].value,members[d].value]]);
      
      // use a contrasting text color if it exists in the material list, otherwise use the material text color
      var textColor = color.properties ? color.properties.textColor : members.primaryText;
      sheet.getRange(i+2,1,1,3).setFontColors ([[textColor,textColor,textColor]]);
      
      // specific colors can now be called out abstractly like this - allowing easy refactoring.
      Logger.log( lister.getColor(mp.getName() , 'primary').value);
      Logger.log( lister.getColor(mp.getName() , 'dark').value);
      Logger.log( lister.getColor(mp.getName() , 'light').value);
      Logger.log( lister.getColor(mp.getName() , 'accent').value);
      Logger.log( lister.getColor(mp.getName() , 'primaryText').value);
      Logger.log( lister.getColor(mp.getName() , 'secondaryText').value);
      
    });

  });

}

Creating your own.

The material combination code is in the ColorList library but it's straightforward to create your own by adapting this pattern.  In this case, the colors are picked up from the material palette, but they can be anything you want them to be, as can the properties like dark, light and so on. 
'use strict';

var MaterialPalette = function (primary, accent) {
  var self = this;
  var primary_ = (primary || 'Indigo').toLowerCase();
  var accent_ = (accent || 'Amber').toLowerCase();
  
  self.getName = function () {
    return 'material' + primary_ + accent_;
  };
  
  
  var gc = ColorList.getColor;
  
  // add to known palettes
  KnownPalettes.palettes[self.getName()] = {
    "dark":gc('material ' , primary_ + " 700").value,
    "light":gc('material ' ,primary_ + " 100").value,
    "primary":gc('material ' ,primary_ + " 500").value,
    "icons":gc('material ' ,"white").value,
    "accent":gc('material ' ,accent_ + (["pink","blue"].indexOf(accent_) === -1 ? " 500" : "a200")).value,   
    "primaryText":gc('material ' ,"gray 900").value,  
    "secondaryText":"#727272",
    "divider":"#B6B6B6"  
  };
  
  // update the keys.
  ColorList.reset();


}



Dumping the color properties. 

Here's an snip of dumping all the color properties for each of these palettes to a Sheet. 

The google palette.



the html palette


the material palette


The code for dumping the palettes

// show all palettes, 1 sheet per palette
function showPalettes() {
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var lister = cColorList.ColorList; 
 
  lister.getPalettes().forEach(function(pal) {
    
    // a sheet for each known palette or create one
    var sheet = ss.getSheetByName(pal) || ss.insertSheet(pal);
    
    // clear the sheet 
    sheet.clearContents();
    
    // get the colors in each palette
    var palette = lister.getList(pal);
    
    // add the properties to the sheet
    var heads,values = Object.keys(palette).map (function (color) {
      var data = lister.getColor(pal, color);
      
      if (!heads) {
        heads = {};
        heads.name = 0, heads.value = 1;
        // and the color properties
        Object.keys(data.properties).reduce(function(p,c) {
          p[c] = Object.keys(p).length;
          return p;
        },heads);
      }
      
      // add the data
      return Object.keys(data.properties).reduce(function(p,c) {
        p[heads[c]] = data.properties[c];
        return p;
      },[data.name,data.value]);
      
    });
    
    // now write the whole thing out
    sheet.getRange (1,1,values.length+1 , Object.keys(heads).length)
         .setValues([Object.keys(heads)].concat(values));
    
    // now to color the lines
    sheet.getRange (2,1,values.length , Object.keys(heads).length)
         .setBackgrounds(values.map (function(row) {
           return row.map(function(col) {
             return row[heads.htmlHex];
           });  
         }));
    
    // choose best font color
    sheet.getRange (2,1,values.length , Object.keys(heads).length)
         .setFontColors(values.map (function(row) {
           return row.map(function(col) {
             return row[heads.textColor];
           });  
         }))
    
  });

}

Code to generate the palettes with comparison notes. 



function generatePalette() {

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var lister = cColorList.ColorList; 
  var SIZE = 16, SEP=SIZE/6;
  
  // layout of palette
  var layout = {
    "material":{
      "rows":15
    },
    "google": {
      "rows":8,
    },
    "html": {
      "rows":12
    }
  };
  


  // get all the palettes
  lister.getPalettes().forEach (function (pal) {
  
    // open a palette sheet
    var sheet = ss.getSheetByName(pal+'Palette') || ss.insertSheet(pal+'Palette');
  
    // clear the sheet 
    sheet.clear();
    sheet.clearNotes();
    
    // these are the ones to compare against
    var compareAgainst = lister.getPalettes().filter(function(d) {
      return d !== pal;
    });
    
    // get the palette contents
    var palette = lister.getList(pal);
    
    // make the shape
    for (var i = 0, values = [], height = layout[pal].rows , 
         width = Math.ceil((Object.keys(palette).length)/height) ; i < height*2 ;i++ ) {
      values.push([]);
      for (var j =0 ; j < width *2 ; j++, values[i].push(''));
    }
    
    // fill up the values
    Object.keys(palette).forEach(function(d,idx) {
      values[1+2*(idx % height)][1+ 2*Math.floor(idx / height)] = palette[d].value;

      // set the note
      var note = palette[d].name + ' (' + palette[d].value +')\n' + 
        compareAgainst.map(function(e) {
          var result = lister.getClosest (e, palette[d].value);
          return e + ':' + result.member.name  + ' (' + result.member.value +')'
        }).join('\n'); 

      sheet.getRange(1+2*(idx % height)+1,1+ 2*Math.floor(idx / height)+1,1,1).setNote(note);
          
      // output the picture
      sheet.getRange (1,1,height*2, width*2).setBackgrounds(values);

      // set the width and height
      for (var i=0; i < height *2 ; sheet.setRowHeight (++i , i % 2 ? SEP : SIZE ));
      for (var i=0; i < width *2; sheet.setColumnWidth (++i , i % 2 ? SEP : SIZE ));
      
    });
    
  });


}



You can use the library by including this in your project.

MBo5MKxo06_g-0n5MOckL0ai_d-phDA33

It's also available on Github if you prefer, and the test spreadsheet is here if you'd like to make a copy.

Material color list adapted from https://github.com/danlevan/google-material-color

For more like this, see Color Arranger Why not join our forumfollow the blog or follow me on twitter to ensure you get updates when they are available. 





Comments