Skip to content

Instantly share code, notes, and snippets.

@curran
Last active August 29, 2015 14:16
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 curran/dd73d3d8925cdf50df86 to your computer and use it in GitHub Desktop.
Save curran/dd73d3d8925cdf50df86 to your computer and use it in GitHub Desktop.
Picking N Colors Automatically

An experiment in automatically picking N distinct colors for visualizing N distinct categories.

The LAB Color Space was designed with perception in mind. This program has a background that has a fixed L (lightness), and varies the A and B opposing color components along X and Y.

LAB is supposed to be perceptually uniform, meaning that distances in LAB space should correspond to perceptual distances. The idea with this experiment is that in order to pick a set of colors that are distinct and perceptually equidistant, we can pick colors that are equidistant in LAB space. A simple approach to doing this is to select colors along a circle in (A, B) space with a fixed L. This is what the program is doing, varying the number of colors from 1 to 20.

If you want to try this out in your visualizations, copy and paste the generateColors function.

<!DOCTYPE html>
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.4.0/lodash.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<meta charset="utf-8">
<title>LAB Color Space</title>
</head>
<body>
<script>
var width = 960,
height = 500,
n = 100,
rectWidth = Math.ceil(width / n) + 1,
rectHeight = Math.ceil(height / n) + 1,
bExtent = 130
aExtent = bExtent * width / height,
lDefault = 50,
rDefault = 100,
circleSize = 40,
data = _.flatten(_.map(_.range(n), function(i){
return _.map(_.range(n), function(j){
return {
a: (i / (n - 1) - 0.5) * 2 * aExtent,
b: (j / (n - 1) - 0.5) * 2 * bExtent,
x: Math.round(i / n * width),
y: Math.round(j / n * height)
};
});
}));
var svg = d3.select("body").append("svg")
.attr("width", width )
.attr("height", height );
svg.selectAll("rect").data(data).enter().append("rect")
.attr("x", function(d){ return d.x; } )
.attr("y", function(d){ return d.y; } )
.attr("width", rectWidth )
.attr("height", rectHeight )
.style("fill", function (d){
return d3.lab(50, d.a, d.b);
});
svg.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", (width / 2) * (rDefault / aExtent))
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("fill", "none");
var smallCircles = svg.append("g");
// Generates n distinct colors.
// l is the fixed lightness, the L in LAB color space.
// r is the radius of the circle in AB space along which points are taken.
function generateColors(n, l, r){
var colors = [], a, b, θ;
for(var i = 0; i < n; i++){
θ = (i / n) * Math.PI * 2;
a = Math.sin(θ) * r;
b = Math.cos(θ) * r;
colors.push(d3.lab(l, a, b));
}
return colors;
}
function updateColors(numColors){
var colors = generateColors(numColors, lDefault, rDefault);
var circles = smallCircles.selectAll("circle").data(colors);
circles.enter().append("circle")
.attr("r", circleSize)
.attr("stroke", "black")
.attr("stroke-width", 2);
circles
.attr("cx", function(d, i){
var θ = (i / colors.length) * Math.PI * 2;
return width / 2 + (width / 2) * (rDefault / aExtent) * Math.sin(θ);
})
.attr("cy", function(d, i){
var θ = (i / colors.length) * Math.PI * 2;
return height / 2 + (height / 2) * (rDefault / bExtent) * Math.cos(θ);
})
.attr("fill", function (d){ return d; });
circles.exit().remove();
}
var numColors = 1;
setInterval(function(){
updateColors(numColors);
numColors = numColors % 20 + 1;
}, 1000);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment