Created
January 22, 2016 11:24
-
-
Save rivo/8cd9dd1ec1c4bbb8de7d to your computer and use it in GitHub Desktop.
Generating Color Palettes for Charts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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