Skip to content

Instantly share code, notes, and snippets.

@Sphinxxxx
Created October 28, 2017 23:47
Show Gist options
  • Save Sphinxxxx/08f4a5caaa1a575645c52844c365cb12 to your computer and use it in GitHub Desktop.
Save Sphinxxxx/08f4a5caaa1a575645c52844c365cb12 to your computer and use it in GitHub Desktop.
Chroma.js Color Scale Helper
<script>
//window.onerror = function(msg, url, line) { alert('Error: '+msg+'\nURL: '+url+'\nLine: '+line); };
</script>
<script type='text/javascript' src="//cdnjs.cloudflare.com/ajax/libs/chroma-js/0.7.4/chroma.min.js"></script>
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap-responsive.min.css">
<div id="main" class="container">
<header>
<h2>Chroma.js Color Scale Helper</h2>
<p>This <a href="https://github.com/gka/chroma.js" target="_blank">chroma.js</a>-powered tool is here to help us <a target="_blank" href="http://vis4.net/blog/posts/mastering-multi-hued-color-scales/">mastering multi-hued, multi-stops color scales</a>.</p>
<p><a href="https://www.handprint.com/HP/WCL/color2.html#uniquehues" target="_blank">More color theory</a></p>
</header>
<!-- template id="ui-template"-->
<div class="color-scale span10" style="display:none;">
<div class="form well span10">
<div class="row">
<div class="span3">
<label>Color names or hex codes:</label>
<input class="colors" checked="checked" type="text" value='black, orange, "#f0f", "darkred", yellow' />
</div>
<div class="span3">
<label>Step count</label>
<input class="steps" type="number" value="9" />
</div>
<div class="gradient-wrapper span4"></div>
</div>
<div class="row">
<div class="span6">
<label class="checkbox">
<input class="bez" type="checkbox" checked /> Bezier interpolation
</label>
<br />
<label class="checkbox">
<input class="coL" type="checkbox" checked /> Correct lightness gradient
</label>
</div>
<div class="exports-wrapper span4"></div>
</div>
<div class="row">
<div class="lightness-wrapper span4"></div>
</div>
</div>
</div>
<!-- /template -->
</div>
Array.from = Array.from || function(list) { return Array.prototype.slice.call(list); };
function $$(selector, context) {
context = context || document;
return context.querySelector(selector);
}
function $$$(selector, context) {
context = context || document;
return Array.from(context.querySelectorAll(selector));
}
function colorScale(ui, is2d) {
"use strict";
const _2d = is2d,
_colors = $$('.colors', ui),
_steps = $$('.steps', ui),
_bez = $$('.bez', ui),
_coL = $$('.coL', ui),
_gradient = $$('.gradient-wrapper', ui),
_lightness = $$('.lightness-wrapper', ui),
_exports = $$('.exports-wrapper', ui);
$$$('input', ui).forEach(input => {
input.onchange = update;
});
update();
function update() {
const colors = clean(_colors.value),
steps = _steps.value,
bez = _bez.checked,
coL = _coL.checked;
_gradient.innerHTML = '';
_lightness.innerHTML = '';
_exports.innerHTML = '';
if(_2d) {
const csTop = createScale([colors[0], colors[1]], bez, coL),
csLeft = createScale([colors[0], colors[2]], bez, coL),
csBottom = createScale([colors[2], colors[3]], bez, coL),
csRight = createScale([colors[1], colors[3]], bez, coL);
showTable(csTop, csLeft, csBottom, csRight, steps);
}
else {
// initialize chroma.scale
const cs = createScale(colors, bez, coL);
// visualize scale
showScale(cs, steps);
}
}
function clean(s) {
return s.match(/[#|\w]+/g);
}
function createScale(colors, bez, coL) {
// initialize chroma.scale
if(bez) colors = chroma.interpolate.bezier(colors);
const cs = chroma.scale(colors).mode('lab').correctLightness(coL);
return cs;
}
function sampleScale(cs, steps) {
var cols = [];
loop(steps, function(i) {
var t = i/(steps-1);
cols.push(cs(t).hex());
});
return cols;
}
function showTable(csTop, csLeft, csBottom, csRight, steps) {
var table = createElement('table', _gradient, { class: 'gradient' });
//Saturation/lightness:
const color = sampleScale(csLeft, 2)[0],
rowCount = Number(steps); //+ 1;
csLeft = createScale([color, color, 'black'], true, false);
csRight = createScale(['white', 'white', '#bbb'], true, false);
var columnFirst = sampleScale(csLeft, rowCount),
columnLast = sampleScale(csRight, rowCount);
for(let row=0; row < rowCount; row++) {
const tr = createElement('tr', table),
rowColors = sampleScale(createScale([columnFirst[row], columnLast[row],columnLast[row]], true, false), steps);
//for(let col=0; col < steps; col++) {
rowColors.forEach(color => {
const td = createElement('td', tr, createAttrs(color));
//createElement('div', td, { class: 'color', style: 'background:' + color });
});
}
}
function showScale(cs, steps) {
var c = createElement('div', _gradient, { class: 'gradient' });
var cols = sampleScale(cs, steps);
cols.forEach(color => createElement('div', c, createAttrs(color)));
//showLightnessCurve(cs, steps);
var list = '\'' + cols.join('\',\'') + '\'',
colors = cols.join(' '),
hexlist = cols.map(function (c) { return c.replace('#','0x'); }).join(',');
var link = location.href;
//var range = [];
//loop(steps, function(s) {
// range.push('min+'+(s+1)+'*d');
//});
//var d3_syntax = 'd3.scale.threshold()\n .range(['+list+']);',
// d3_syntax_full = 'function palette(min, max) {\n var d = (max-min)/'+steps+';\n' + ' return d3.scale.threshold()\n .range(['+list+'])\n .domain(['+range.join(',')+']);\n}';
export_palette(colors);
export_palette(list);
//export_palette(d3_syntax);
//export_palette(d3_syntax_full);
//export_palette(hexlist);
}
function createAttrs(color) {
return {
class: 'color',
'data-color': color,
title: color,
style: 'background:' + color,
}
}
function showLightnessCurve(cs, steps) {
const svg = document.createElementNS('http://www.w3.org/2000/svg','svg'),
path = document.createElementNS('http://www.w3.org/2000/svg','path'),
w = 200, margin = 5, dy = 100;
svg.setAttribute('width', w);
svg.setAttribute('height', dy + 2*margin);
const l_steps = [];
loop(steps, function(i) {
var t = i/(steps-1);
l_steps.push( cs(t).lab()[0] );
});
const sx = linearScale({ domain: [0,steps], range: [5, w-5] }),
//sy = d3.scale.linear().domain(d3.extent(l_steps)).range([h-5, 5]);
//sy = (y) => y + margin;
sy = (y) => (dy - y) + margin;
let d = '';
l_steps.forEach(function(l, i) {
var x0 = sx(i),
x1 = sx(i+1),
y = sy(l);
if (d == '') d = 'M'+[x0,y];
d += ' V'+y;
d += ' H'+x1;
});
path.setAttribute('d', d);
svg.appendChild(path);
_lightness.appendChild(svg);
}
function export_palette(str) {
createElement('pre', _exports).textContent = str;
}
/* Utils */
//linearScale.js
//https://gist.github.com/vectorsize/7031902
function linearScale(opts) {
var istart = opts.domain[0], istop = opts.domain[1], ostart = opts.range[0], ostop = opts.range[1];
return function scale(value) { return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); }
}
function loop(iterations, callback) {
for(let i=0; i<iterations; i++) { callback(i); }
}
function createElement(tag, parent, attributes) {
const elm = document.createElement(tag);
if(attributes) {
for(var key in attributes) { elm.setAttribute(key, attributes[key]); }
}
if(parent) { parent.appendChild(elm); }
return elm;
}
}
(function() {
//
const main = $$('#main'),
templ = $$('.color-scale'); //$$('#ui-template');
function createScaler(colors, steps, adjustLightness, is2d) {
//IE support..
// //Create color scalers from the template:
// //var ui = document.importNode(templ.content, true);
var ui = templ.cloneNode(true);
ui.style.display = '';
//https://stackoverflow.com/questions/31855108/get-element-from-document-importnode
// "You must query the contents of the document fragment *before* it's appended to the DOM"
$$('.colors', ui).value = colors;
$$('.steps', ui).value = steps;
$$('.coL', ui).checked = adjustLightness || false;
colorScale(ui, is2d);
main.appendChild(ui);
return ui;
}
//Persepted hues separation:
//createScaler('red, #fd0, yellow', 6/*8*/, true);
createScaler('red, #ff6500, #ffea00, yellow', 6/*8*/);
createScaler('yellow, #ef0, lime', 4);
//createScaler('lime, #0fc, cyan', 3, true);
createScaler('lime, cyan', 4);
createScaler('cyan, #0cf, blue', 6/*8*/);
//createScaler('blue, #c0f, #f0f', 6, true);
createScaler('blue, #5e00ff, #e500ff, #f0f', 5);
createScaler('#f0f, #ff00bb, red', 4/*3*/);
/* Saturation/lightness
createScaler('black, white, white', 8);
createScaler('lime,white,white', 6);
createScaler('lime, black', 6);
createScaler('lime, #bbb, #bbb', 7);
//*/
//Saturation/lightness tables
//createScaler('yellow,white,black,#bbb', 6, false, true);
const allColors = $$$('.gradient .color:not(:last-child)').map(x => x.dataset.color);
//console.log('hues', allColors);
allColors.forEach(c => createScaler([c,c,c,c].join(','), 6, false, true));
var huesList = $$$('table.gradient')
.map(t => $$$('tr:not(:last-child)', t)
.map(tr => $$$('td:not(:last-child)', tr).map(td => td.title))
);
const grays = createScaler('#bbb,white,black,#bbb', 5, false, true),
graysArr = $$$('tr', grays)
.map(tr => $$$('td', tr).map(td => td.title));
huesList.push(graysArr);
//console.log('hsv ', huesList);
const huesObj = {};
huesList.forEach(h => huesObj[h[0][0]] = h);
let output = JSON.stringify(huesObj, null, 4);
output = output.replace( /\[[^[{]*?]/g, (match => match.replace(/\s+/g, ' ')) );
console.log(`const _hsl = ${output};`);
})();
body {
padding: 20px;
}
header {
text-align: center;
padding-bottom: 1em;
}
h2 {
font-weight: 300;
}
input {
max-width: 100%;
}
label {
white-space: nowrap;
}
label.checkbox { display: inline-block; }
.color-scale {
.well {
padding: 0 1em;
padding-top: 5px;
margin-bottom: .5em;
}
.steps {
text-align: right;
}
.colors {
width: 98%;
}
.gradient {
display: inline-block;
padding: 10px 10px 6px;
border: 1px solid gainsboro;
border-radius: 10px;
background: white;
text-align: center;
white-space: nowrap;
.color {
display: inline-block;
width: 50px;
height: 50px;
color: rgba(0,0,0,0);
}
td.color {
padding: 0;
}
}
svg {
display: block;
margin: 10px 0;
background: #ffa;
path {
stroke: #000;
stroke-width: 2px;
fill: none;
shape-rendering: crispEdges;
}
}
pre {
border: 0;
padding: 0;
margin: 0;
margin-top: .25em;
color: #777;
white-space: nowrap;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment