Skip to content

Instantly share code, notes, and snippets.

@brucemcpherson
Created April 4, 2019 11:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brucemcpherson/a397a56c26cff8690dc881210a6d481f to your computer and use it in GitHub Desktop.
Save brucemcpherson/a397a56c26cff8690dc881210a6d481f to your computer and use it in GitHub Desktop.
colorschemer - needs parse.com and apps script so now deprecated
<!DOCTYPE HTML>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="http://xliberation.com/parse/colortable/css/schemer.css">
<script src="http://www.parsecdn.com/js/parse-1.2.2.min.js"></script>
<script src="http://xliberation.com/parse/colortable/js/colortable.js"></script>
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
// globals
var schemeFromParse;
</script>
<script type="text/javascript">
google.load('visualization', '1');
google.load("jquery", '1');
</script>
<script type="text/javascript">
google.setOnLoadCallback(function () {
// get the useful code from google apps script
getKnownSchemes();
addListeners();
$('#status').text("...getting google apps script modules");
$.when.apply($,loadModules (["vEquivalents","hacks","usefulColors"],"mcpher")).then (
function () {
var randomColor = randomBar();
// get the currently selected scheme, and if nothing entered yet, generate a random scheme
getSelectedScheme($('#schemeselector').val())
.done( function() {
if (!$('#hexinput').val()) calculateColors (rgbToHTMLHex(randomColor),"random color");
});
},
function (error) {
console.log(JSON.stringify(error));
});
});
function loadModules(modules, sourceLibrary) {
var promises = [];
// now does this in one call - returns promise array for backward compat.
var url = "https://script.google.com/macros/s/AKfycbzhzIDmgY9BNeBu87puxMVUlMkJ4UkD_Yvjdt5MhOxR1R6RG88/exec";
var u = url + "?source=script&type=javascript&module=" + modules.join() + "&library=" + sourceLibrary;
promises.push ($.getScript(u));
return promises;
}
function fillBar(rgbColor) {
// create random palette - different colored headers each time
var swatchSize = 200, elem = $('#randombar'), w = 100/swatchSize, h= 20;
elem.empty();
var pBase = makeColorProps(rgbColor);
var a = makeAPalette(pBase.rgb, "hsl", "hue", swatchSize, false);
var title =
"<div><h2>This exercises algorithms for comparing colors " +
"<a href='http://ramblings.mcpher.com/Home/excelquirks/colorfiesta/nearest'>" +
"Excel Liberation</a><h2></div>";
elem.append("<div>"
+makeStyleSpan (pBase.htmlHex,rgbToHTMLHex(pBase.textColor),
100,h*4,'<h1>color scheme explorer</h1>'+title)+"</div>");
var spans = elem.append("<div></div>")
for (var i=0;i<a.length;i++) {
spans.append(makeStyleSpan (a[i].htmlHex,rgbToHTMLHex(a[i].textColor),w,h,''));
}
elem.append("</div>");
return rgbColor;
}
function randomBar() {
// create random palette - different colored headers each time
return fillBar (Math.floor((VBCOLORS.vbWhite - VBCOLORS.vbBlack + 1)
* Math.random() + VBCOLORS.vbBlack));
}
// we can do this asynch as soon as it's known
function getSelectedScheme (scheme) {
//get current parse data for scheme - async
$('#status').text("...getting selected scheme " + scheme);
var parseKeys = getParseKeys();
Parse.initialize(parseKeys.appId, parseKeys.jsKey);
var ColorTable = Parse.Object.extend("ColorTable");
schemeFromParse = schemePromise(ColorTable, scheme);
schemeFromParse.fail(function (error) {
$('#status').text("error getting scheme " + scheme + JSON.stringify(error));
});
schemeFromParse.done(function (results) {
$('#status').text("found " + results.length + " colors in scheme " + scheme );
});
return schemeFromParse;
}
// get the data for chosen scheme from parse
function schemePromise(model, scheme, allResults, allPromise) {
// find a scheme at a time
var schemeArray = [];
var promise = allPromise || $.Deferred();
findChunk(model, scheme, allResults || [])
.done(function (results, allOver) {
if (allOver) {
// we are done
// normalize to an array from parse ob
for (var i =0;i<results.length;i++) {
schemeArray.push({label: results[i].get("label"),
hex: results[i].get("hex"),
scheme: results[i].get("scheme"),
code: results[i].get("code"),
key: results[i].get("key")});
}
promise.resolve(schemeArray);
} else {
// may be more
schemePromise(model, scheme, results, promise);
}
})
.fail(function (error) {
promise.reject(error);
});
return promise.promise();
}
// return in chunks to the parse.com limit
function findChunk(model, scheme, allData) {
// we have to find in chunks since there is a limit on query size
// will return a promise
var limit = 1000;
var skip = allData.length;
var findPromise = $.Deferred();
var query = new Parse.Query(model);
if (scheme) query.equalTo("scheme", scheme);
query
.limit(limit)
.skip(skip)
.find()
.then(function (results) {
findPromise.resolve(allData.concat(results), !results.length);
}, function (error) {
findPromise.reject(error);
});
return findPromise.promise();
}
function makeStyleSpan (back,color,w,h,text) {
return "<span class='spanBox' style=" + "background-color:" + back +
";color:" + color +
";width:" + w +
"%;height:"+h+"px;" +
">" + text + "</span>"
}
function calculateColors(basedOn,label) {
var elem = $('#show').empty();
var swatchSize=5,w= 100/(swatchSize+2),h=60;
schemeFromParse.done ( function (schemeArray) {
var nearest = nearestColors(schemeArray,basedOn,swatchSize,elem,w,h,label);
getSomePalettes(schemeArray,basedOn,swatchSize,elem,w,h,label);
// change the headers to match current color
fillBar(htmlHexToRgb(basedOn));
// add details
fillWithDetails(elem,nearest);
});
}
function nearestColors(schemeArray,basedOn,swatchSize,elem,w,h,label) {
// closest colors...
var p = makeColorProps(htmlHexToRgb(basedOn));
var results = sortClosestColors(p.rgb,schemeArray);
elem.append(makeStyleSpan ( p.htmlHex , rgbToHTMLHex(p.textColor),
w,h,"nearest colors") );
elem.append( makeStyleSpan ( p.htmlHex , rgbToHTMLHex(p.textColor),w,h,(label ? label + '<br>': '') + p.htmlHex) );
for (var j=0; j < swatchSize ; j++){
spanElem (elem,results[j],w,h);
}
elem.append("<div></div>");
return results.length ? results[0] : null;
}
function getSomePalettes(schemeArray,basedOn,swatchSize,elem,w,h,label) {
// these are the models& properties we are going to process
var models = ["lch", "hsl"];
var prop = ["hue", "lightness", "saturation"];
var pBase = makeColorProps(htmlHexToRgb(basedOn));
// create various palettes
for (var j = 0; j < arrayLength(prop); j++) {
for (var k = 0; k < arrayLength(models); k++) {
var a = makeAPalette(pBase.rgb, models[k],
prop[j], swatchSize, prop[j] == "lightness");
elem.append( makeStyleSpan ( pBase.htmlHex , rgbToHTMLHex(pBase.textColor),
w,h,"palette<br>" + models[k]+"-"+prop[j]) );
elem.append( makeStyleSpan ( pBase.htmlHex , rgbToHTMLHex(pBase.textColor),w,h,
(label ? label + '<br>': '') + pBase.htmlHex) );
for (var i = 0; i < arrayLength(a); i++) {
spanElem (elem,sortClosestColors(a[i].rgb,schemeArray)[0],w,h);
}
elem.append("<div></div>");
}
}
}
function spanElem (elem,nearest,w,h) {
var p = makeColorProps(htmlHexToRgb(nearest.hex));
var span = $(makeStyleSpan ( p.htmlHex , rgbToHTMLHex(p.textColor),w,h,
nearest.label+"<br>"+p.htmlHex ));
span.data('nearest',nearest)
.appendTo(elem)
.click(function() {
var data = $(this).data('nearest');
calculateColors (data.hex,data.label );
return false;
});
return span;
}
function sortClosestColors (targetRgb, schemeArray) {
var targetProps = makeColorProps (targetRgb);
for (var i= 0; i < schemeArray.length ; i++) {
schemeArray[i].comparison = compareColors (targetRgb, htmlHexToRgb(schemeArray[i].hex));
}
return schemeArray.sort (comparisonSort);
}
function comparisonSort(a,b) {
if (a.comparison < b.comparison)
return -1;
if (a.comparison > b.comparison)
return 1;
return 0;
}
function getKnownSchemes() {
// there isnt a unique values type of structure, so we'll need to do something better later
selectValues = ["pfh","pms","dulux","htm","raf"];
for (var i = 0; i < selectValues.length; i++ ) {
$('#schemeselector')
.append($("<option/>", {
value: selectValues[i],
text: selectValues[i]
}));
}
}
function addListeners () {
$('#go').click(function(){
calculateColors ($('#hexinput').val() );
});
$('#schemeselector').change(function() {
// todo deal with in the middle of getting
getSelectedScheme($(this).val());
});
}
function fillWithDetails(elem,nearest) {
elem.append("<div class='detail'>Nearest Color details</div>");
elem.append(
fillAdd (nearest, "label"),
fillAdd (nearest, "hex"),
fillAdd (nearest, "scheme"));
elem.append('<br><div class="detail"></div>',
fillAdd (nearest, "code"),
fillAdd (nearest, "key"),
fillAdd (nearest, "comparison"));
var p = makeColorProps(htmlHexToRgb(nearest.hex));
elem.append('<br><div class="detail">RGB</div>',
fillAdd (p, "rgb"),
fillAdd (p, "red"),
fillAdd (p, "green"),
fillAdd (p, "blue"));
elem.append('<br><div class="detail">CIE L*C*h Color Scale</div>',
fillAdd (p, "LStar"),
fillAdd (p, "cStar"),
fillAdd (p, "hStar"));
elem.append('<br><div class="detail">CIE L*a*b Color Scale</div>',
fillAdd (p, "LStar"),
fillAdd (p, "aStar"),
fillAdd (p, "bStar"));
elem.append('<br><div class="detail">HSL/HSV Color Scale</div>',
fillAdd (p, "hue"),
fillAdd (p, "lightness"),
fillAdd (p, "saturation"),
fillAdd (p, "value")
);
elem.append('<br><div class="detail">CMYK Color Scale</div>',
fillAdd (p, "cyan"),
fillAdd (p, "magenta"),
fillAdd (p, "yellow"),
fillAdd (p, "black")
);
elem.append('<br><div class="detail">XYZ Color Scale</div>',
fillAdd (p, "x"),
fillAdd (p, "y"),
fillAdd (p, "z")
);
elem.append('<br><div class="detail">other info</div>',
fillAdd (p, "textColor"),
fillAdd (p, "contrastRatio"),
fillAdd (p, "luminance")
);
return elem;
}
function fillAdd ( o,k) {
var n;
try {
n = o[k].toFixed(2);
}
catch(err) {
n = o[k];
}
return $("<span class='labelDetail'>"+k+"</span><span class='detail'>" + n + "</span>");
}
</script>
</head>
<body>
<div id="randombar"></div>
<div>
<div class="label">Target hex value
<input type="text"" class = "text" id ="hexinput"></input>
<div class = "button" id ="go">create details</div>
</div>
<div class="label">Match within scheme
<select name="schemeselector" id="schemeselector"></select></div>
<div class="text" id="status"></div>
</div>
<div id="show" style="width:100%;"></div>
</body>
</html>
function getParseKeys() {
var keys = {
appId :"parse now deprecated",
jsKey :"parse now deprecated" };
return keys;
}
// get color data from colortable stored in parse.com
// will return json or jsonp.
// typical usage
// ?key|rgb|hex=something&closest=n&scheme=s will return the 'n' closest colors in scheme x to the given target
// ?scheme|key|name|label|code|rgb|hex=s&limit=n will return up to n colors as per the filter
// ??key|rgb|hex=something&palette=n
// &scheme=|s&type=hue|saturation|lightness&model=hsl|lch
// will return a palette (within scheme if specified)
// in each case, various color model properties are returned for each one
function doGet(e) {
return ContentService
.createTextOutput(pit(e,getColorStuff(e)))
.setMimeType(ContentService.MimeType.JSON);
}
function pit(e,s) {
// jsonp?
return e.parameter.callback ? e.parameter.callback + "(" + s + ");" : s;
}
function printStuff(){
Logger.log(getColorStuff());
}
// get results and convert to json
function getColorStuff(e) {
var ea = argAlign(e);
return JSON.stringify({ p: ea,
c: processColorRequest(ea) } );
}
function getTargetRgb(e) {
var rgb,error,results;
if (e.parameter.access == "key") {
results = getSomehow (e,e.parameter.access);
if (!results.results.length) {
error = "could not find key " + e.parameter.key;
}
else {
rgb = mcpher.htmlHexToRgb(results.results[0].hex);
}
}
else {
if(e.parameter.access == "rgb")
rgb = e.parameter.rgb ;
else if (e.parameter.access == "hex")
rgb = mcpher.htmlHexToRgb(e.parameter.hex);
else
error = "access type invalid for closest match " + e.parameter.access ;
if (!error) {
results = {};
results.results = [];
results.results.push ( { hex: mcpher.rgbToHTMLHex(rgb)} );
}
}
return { rgb: rgb, error: error , results: results } ;
}
function getFromSomewhere(e) {
if (e.parameter.access == "hex")
return {results:[{hex:e.parameter.hex}]};
else
if (e.parameter.access == "rgb")
return {results:[{ hex: mcpher.rgbToHTMLHex(e.parameter.rgb)}]};
else
return e.parameter.provider == "parse" ? getFromParse(e) : getFromScriptDb(e) ;
}
function limitArray(a,n) {
var results =[];
for (var i=0;i<n && i < a.length ; i++) results.push(a[i]);
return results;
}
function processColorRequest(e) {
var results,limited;
if (e.parameter.action == "closest") {
var rgb = getTargetRgb(e);
if (!rgb.error) {
// take the first 'n' sorted colors plus the original query
var scheme = getSomehow (e,"scheme").results;
var a = sortClosestColors ( rgb.rgb, scheme);
limited = [rgb.results.results[0]].concat(limitArray(a,e.parameter.closest));
}
results = { results : limited , error:rgb.error };
}
else {
results = getFromSomewhere(e);
}
return formatColorRequest(e, results);
}
// put all results to array
function formatColorRequest(e, results) {
var propArray = [];
// return the original
if (results.results) {
for (var i = 0; i < results.results.length; i++ ) {
var r = results.results[i];
try {
propArray.push( {colortable:r,
properties: mcpher.makeColorProps (mcpher.htmlHexToRgb(r.hex))});
}
catch(err) {
propArray.push( {colortable:r,properties:null, error: "invalid color " + err} );
}
}
}
else
propArray.push( {error:"no results",properties:null,colortable:null} );
return propArray;
}
function makeQuery(e) {
var eArgs = e.parameter;
var model = ["key","scheme","code","label","name"];
// build query
var query;
for (var k=0; k < model.length ; k++ ) {
if (eArgs[model[k]]) {
query = query || {};
query[model[k]] = eArgs[model[k]];
}
}
return query;
}
function getSomehow (e,k) {
var result, ob = {};
ob[k] = e.parameter[k];
// get a single record based on a single property
if (e.parameter.provider == "parse") {
ob[k] = e.parameter[k];
result = new cParseCom(getKeys()).queryArray("ColorTable",
ob,0,e.parameter.useCache);
}
else {
result = mcpher.scriptDbSilo("colorSchemes",publicStuffDb())
.queryArray(ob) ;
}
return { results: result };
}
function timeIt() {
var scheme = "dulux";
var provider = "parse";
var e = {parameter:{provider:provider,scheme:scheme,useCache:false}};
mcpher.useTimer("scheme").start("Using GAS to getting scheme " +
e.parameter.scheme + " from " + e.parameter.provider);
var r = getSomehow(e,"scheme");
mcpher.useTimer("scheme").stop();
Logger.log("found "+r.results.length+" rows in scheme " +
e.parameter.scheme + " using provider " + e.parameter.provider);
Logger.log(mcpher.useTimer().report(false));
}
function sortClosestColors (targetRgb, schemeArray) {
var targetProps = mcpher.makeColorProps (targetRgb);
for (var i= 0; i < schemeArray.length ; i++) {
schemeArray[i].comparison = mcpher.compareColors (targetRgb, mcpher.htmlHexToRgb(schemeArray[i].hex));
}
return schemeArray.sort (comparisonSort);
}
function comparisonSort(a,b) {
if (a.comparison < b.comparison)
return -1;
if (a.comparison > b.comparison)
return 1;
return 0;
}
// set the arg defaults
function argAlign(e) {
eArgs = e || {};
eArgs.parameter = eArgs.parameter || {};
// testing
//eArgs.parameter.hex = "#282821";
//eArgs.parameter.rgb = 1234;
//eArgs.parameter.closest = 5;
//eArgs.parameter.scheme = "htm";
//eArgs.parameter.key = "dulux-crushed raspberry";
//eArgs.parameter.provider = "parse";
//eArgs.parameter.limit = 0;
eArgs.parameter.action = eArgs.parameter.action ||
(eArgs.parameter.hasOwnProperty("closest") ?
"closest" : (eArgs.parameter.hasOwnProperty("palette") ? "palette" : "get" ));
eArgs.parameter.access = eArgs.parameter.access ||
(eArgs.parameter.hasOwnProperty("key") ?
"key" : (eArgs.parameter.hasOwnProperty("hex") ?
"hex" : (eArgs.parameter.hasOwnProperty("rgb") ? "rgb" : "multi" )));
eArgs.parameter.cache = mcpher.LCase(eArgs.parameter.cache) || "yes";
eArgs.parameter.useCache = eArgs.parameter.cache == "yes";
eArgs.parameter.limit = eArgs.parameter.limit || "0";
eArgs.parameter.provider = eArgs.parameter.provider || "scriptdb";
eArgs.parameter.type = eArgs.parameter.type || "hue";
eArgs.parameter.closest = eArgs.parameter.closest || 5;
eArgs.parameter.palette = eArgs.parameter.palette || 5;
eArgs.parameter.model = eArgs.parameter.model || "lch";
return eArgs;
}
// -- parse specific
/**
* PARSE has been deprecated
* creates a new object to interact with parse.com
* @param {object} keys the parse.com .applicationId & . restKey
* @return {cParseCom} The object
*/
function cParseCom(keys) {
this.xKeys = keys;
this.keys = function () {
return this.xKeys;
}
return this;
}
/**
* creates headers needed to authorize parse.com
* @return {string} The headers
*/
cParseCom.prototype.restHeaders = function () {
return { headers: {
"X-Parse-Application-Id" : this.keys().applicationId,
"X-Parse-REST-API-Key" : this.keys().restKey }
};
}
function getFromParse(e) {
var results;
results= { results: new cParseCom(getKeys()).queryArray("ColorTable",makeQuery(e),
e.parameter.limit,e.parameter.useCache) };
return results;
}
/**
* gets a JSON string response from parser.com
* @param {string} class the parse.com class name
* @param {object=} optQuery the object with the query constraint key/value pairs
* @param {number} optLim limit
* @param {boolean} optCache useCache
* @return {Array} the results
*/
cParseCom.prototype.queryArray = function(class,optQuery,optLim,optCache) {
// this is the max I'll take in one go to be compatible with parse.com
var maxLimit = 1000;
var q ,limit = mcpher.fixOptional(optLim,0), useCache = mcpher.fixOptional (optCache, true);
var results =[];
var options = this.restHeaders();
while(true) {
q = "?limit=" + (limit > 0 ? limit-results.length : maxLimit);
q+= "&skip=" + results.length;
q+= "&where="+mcpher.URLEncode(JSON.stringify(optQuery));
qr = new mcpher.cBrowser().get("https://api.parse.com/1/classes/" + class + q, options, useCache);
qo = JSON.parse(qr);
if (qo.results.length <= 0 ) return results;
for (var i=0; i < qo.results.length && (limit==0 || results.length < limit);i++ ) {
results.push(qo.results[i]);
}
}
};
body{
font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif;
font-size:12px;
}
p, h1, form, button{border:0; margin:0; padding:0;}
.spacer{clear:both; height:1px;}
.form {
margin:0 auto;
width:400px;
padding:14px;
}
.label{
display:inline;
font-weight:bold;
text-align:right;
width:140px;
}
.text{
color:#666666;
display:inline;
font-size:11px;
font-weight:normal;
text-align:left;
width:140px;
}
.button{
-moz-border-radius: 15px;
border-radius: 5px;
border:1px solid LightSlateGray ;
width: 120px;
background: DarkSlateGray ;
display:inline;
font-weight:normal;
color:white;
padding:4px;
}
.detail{
color:#666666;
display:inline-block;
font-size:10px;
font-weight:normal;
text-align:left;
width:90px;
margin: 2px;
padding:2px;
-moz-border-radius: 3px;
border-radius: 3px;
border:1px solid LightGray ;
}
.labelDetail{
display:inline-block;
font-weight:bold;
text-align:right;
width:80px;
font-size:10px;
}
.spanBox {
text-align:center;vertical-align:middle;padding:0px;margin:0px;
border-bottom:1px solid white ;
border-top:1px solid white ;
border-left:none;
border-right:none;
display:inline-block;
}
//scriptdb specific
//scriptdb deprecated
function getFromScriptDb(e) {
return { results :
mcpher
.scriptDbSilo("colorSchemes",publicStuffDb())
.queryArray(makeQuery(e),e.parameter.limit) };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment