Skip to content

Instantly share code, notes, and snippets.

@sxv
Last active December 11, 2015 14:48
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 sxv/4616231 to your computer and use it in GitHub Desktop.
Save sxv/4616231 to your computer and use it in GitHub Desktop.
Chroma + Phi (ϕ)

An exploration of color generation based on the golden ratio (phi). Generating colors according to this ratio allows any arbitrary number of new values to be added, while maximizing the separation between nearest values.

This example uses D3's HCL color space, designed for maximum (perceived) hue separation. Because HCL is based upon three distinct variables, this algorithm optimizes variation of the hue (H) variable first, then chroma (C) and luminance (L). In other words, hue values are generated strictly according to the golden ratio, while chroma and luminance are "staggered" to avoid multiple variables moving together along the Fibonacci spiral "period". I suspect there are more elegant solutions to this problem but have not found any yet.

Both chroma and luminance are restricted to values between 15 and 100.

Inspired by Vi Hart.

<!doctype html>
<head>
<title>chromaphi</title>
<style>
body {
width: 960px;
height: 500px;
text-align: center;
}
svg {
display: block;
width: 100%;
height: 100%;
}
button { margin: 24px 0 12px 12px; }
</style>
<script src='http://d3js.org/d3.v3.min.js'></script>
</head>
<body>
<script type='text/javascript'>
/* VARS */
var _ = {};
_.n = 34;
_.width = 160 / _.n;
_.innerRadius = 60;
_.outerRadius = 180;
_.hMin = 0, _.hMax = 360;
_.cMin = 15, _.cMax = 100;
_.lMin = 15, _.lMax = 100;
_.arc =
d3.svg.arc()
.startAngle(function(d) { return _.d2r( d.h - _.width / 2 ); })
.endAngle(function(d) { return _.d2r( d.h + _.width / 2 ); })
.innerRadius(function() { return _.innerRadius; })
.outerRadius(function() { return _.outerRadius; });
_.steps = [4, 6, 9, 14, 22, 35, 56, 90, 145, 224];
/* DOM */
var $body = d3.select('body');
var $input =
$body
.append('input')
.attr('type', 'range')
.attr('min', 2)
.attr('max', 89)
.attr('step', 1)
.attr('value', _.n)
.style('vertical-align', 'middle')
.on('change', function() { _.update(this.value); });
var $button =
$body
.append('button')
.on( 'click', function() { return _.draw(); });
var $g =
$body
.append('svg').attr('class', 'svg')
.append('g')
.attr( 'transform', 'translate(480, 200)' );
/* CONTROL */
_.init =
function() {
_.h = Math.random() * (_.hMax - _.hMin) + _.hMin;
_.c = Math.random() * (_.cMax - _.cMin) + _.cMin;
_.l = Math.random() * (_.lMax - _.lMin) + _.lMin;
};
_.add =
function(i) {
_.h = _.nacc('h');
_.c = _.nacc('c');
_.l = _.nacc('l');
var step = _.steps.indexOf(i);
if (step !== -1) {
_.c = _.nacc('c');
_.l = _.nacc('l');
_.l = _.nacc('l');
if ( _.steps.indexOf(step + 1) !== -1 ) {
_.c = _.nacc('c');
_.l = _.nacc('l');
_.l = _.nacc('l');
}
}
var $hcl = d3.hcl( _.h, _.c, _.l ).toString();
$g.selectAll('path:not(.ray)')
.data( [{ h: _.h, c: _.c, l: _.l}] ).enter()
.append('path').attr('class', 'ray')
.attr('d', _.arc)
.style('fill', function() { return $hcl; })
.append('title')
.text( $hcl );
};
_.draw =
function(i) {
clearTimeout(_.timeout);
i = i || 1;
if (i === 1) {
_.init();
d3.selectAll('.ray').remove();
}
$button.style( 'color', d3.hcl(0, 0, 100 - i / _.n * 80).toString() );
_.timeout = setTimeout( function() {
_.add(i);
if (i++ < _.n) {
_.draw(i);
}
}, 50);
};
_.update =
function(n) {
_.n = n || _.n || 34;
_.width = 160 / _.n;
$button.text('generate ' + _.n + ' colors');
};
_.update();
_.init();
_.draw();
/* TOOLBELT */
_.fib =
function(val, min, max) {
var range = max - min;
return ( ( (val - min) + (range * 1.618) ) % range ) + min;
};
_.nacc =
function(n) {
return _.fib( _[n], _[n + 'Min'], _[n + 'Max'] );
};
_.d2r =
function(d) {
return d * Math.PI / 180;
};
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment