Skip to content

Instantly share code, notes, and snippets.

@rivo
Created January 22, 2016 11:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rivo/8cd9dd1ec1c4bbb8de7d to your computer and use it in GitHub Desktop.
Save rivo/8cd9dd1ec1c4bbb8de7d to your computer and use it in GitHub Desktop.
Generating Color Palettes for Charts
var MIN_L = 40, // Don't generate too dark colors.
MAX_L = 85, // Don't generate too light colors.
index,
length,
offset,
emptySlots;
// Make a list of base colors. Start with blue if nothing is defined.
var baseColors = BASE_COLORS.length ? BASE_COLORS : ["RoyalBlue"];
emptySlots = FINAL_PALETTE_SIZE - baseColors.length - 2; // 2 gray values.
// Add more hues (via the hue of HSL so we cover a large range of colors).
length = Math.min(16,Math.max(8,emptySlots / 3)); // Min. 8, max. 16 more hues.
offset = d3.hsl(baseColors[0]).h;
for(index = 0; index < length; index++) {
baseColors.push(d3.hsl(offset + index * 360 / length,1,.5));
}
// Correct chroma.
var minChroma = d3.min(this.colors(),function(c) { return d3.hcl(c).c; }),
maxChroma = d3.max(this.colors(),function(c) { return d3.hcl(c).c; });
baseColors = baseColors.map(function(hsl) {
var hcl = d3.hcl(hsl);
if(hcl.c < minChroma)
hcl.c = minChroma;
if(hcl.c > maxChroma)
hcl.c = maxChroma;
return hcl;
});
// Turn base colors into scales.
var scales = baseColors.map(function(color) {
return d3.scale.linear()
.domain([0, d3.lab(color).l, 100])
.range(["black",color,"white"])
.interpolate(d3.interpolateLab); // Interpolate in L*a*b* space.
});
// Flatten scales into color wheels (arrays). Colors are in L*a*b* space.
var wheels = scales.map(function(scale) {
return d3.range(MIN_L,MAX_L,(MAX_L - MIN_L) / (emptySlots + 1)).map(function(l) {
return d3.lab(scale(l));
});
});
// Complete the palette by finding the most distant color one by one.
var labColors = BASE_COLORS.map(function(c) { return d3.lab(c); }),
farthest,
farthestDistance;
if(!labColors.length)
labColors = [d3.lab(baseColors[0])];
for(index = 0; index < emptySlots; index++) {
farthest = false;
// Find the most distant color in the wheels.
wheels.forEach(function(wheel) {
wheel.forEach(function(candidate,i) {
// Find the minimum distance of this candidate to all colors in the palette.
var minDistance = d3.min(labColors,function(c) { return distance(c,candidate); });
// Update farthest color if this candidate is closer.
if(!farthest || minDistance > farthestDistance) {
farthest = candidate;
farthestDistance = minDistance;
}
});
});
// Add the new color.
labColors.push(farthest);
}
// Make final colors.
var colors = labColors.map(function(c) { return c.toString(); });
colors.push("#d0d0d0","#606060"); // Include two grays.
// The distance function.
function distance(c1,c2) {
// Lindbloom, Bruce Justin. "Delta E (CIE 1994)"
// See also https://en.wikipedia.org/wiki/Color_difference#CIE94
var dL = c1.l - c2.l,
C1 = Math.sqrt(c1.a*c1.a + c1.b*c1.b),
C2 = Math.sqrt(c2.a*c2.a + c2.b*c2.b),
dCab = C1 - C2,
dCab2 = dCab * dCab,
da = c1.a - c2.a,
db = c1.b - c2.b,
dHab2 = da*da + db*db - dCab2,
SC = 1 + .045*C1, // "graphic arts"
SH = 1 + .015*C1,
t1 = dL*dL,
t2 = dCab2/(SC*SC),
t3 = dHab2/(SH*SH);
// No square root because we only use it for comparisons.
return t1 + t2 + t3;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment