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.
xxxxxxxxxx
<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>
https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.4.0/lodash.min.js
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js