Skip to content

Instantly share code, notes, and snippets.

@dgerber
Forked from mbostock/.block
Last active December 23, 2015 21:39
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 dgerber/6697473 to your computer and use it in GitHub Desktop.
Save dgerber/6697473 to your computer and use it in GitHub Desktop.
L*a*b* and L*u*v* color spaces

This is Mike Bostock's block 3014589 plus the CIE L*u*v* and XYZ color spaces from this plugin. Both L*a*b* and L*u*v* are intended as "uniform color spaces" -- matching perceptual color difference with euclidean distance. d3.hcl is L*a*b* in cylindrical (Hue-Chroma-Lightness) coordinates.

(function(d3){
// since v3.4.9 https://github.com/mbostock/d3/pull/1922
var d3_Color = d3.color || d3.rgb(0,0,0).constructor/*.prototype.constructor*/;
d3.color = d3.color || {};
if (typeof module === 'object' && module.exports){
module.exports = d3.color;
}
d3.color.xyz = xyz;
d3.color.luv = luv;
d3.color.interpolateXYZ = interpolateXYZ;
d3.color.interpolateLuv = interpolateLuv;
function xyz(x, y, z){
if (arguments.length !== 1) return new XYZ(+x, +y, +z);
else if (x instanceof XYZ) return new XYZ(x.X, x.Y, x.Z);
else return rgb_XYZ((x = d3.rgb(x)).r, x.g, x.b);
}
function luv(l, u, v){
if (arguments.length !== 1) return new Luv(+l, +u, +v);
else if (l instanceof Luv) return new Luv(l.l, l.u, l.v);
else return rgb_XYZ((l = d3.rgb(l)).r, l.g, l.b).luv();
}
// Corresponds roughly to RGB brighter/darker
var d3_lab_K = 18;
// tristimulus values
function XYZ(X, Y, Z){ this.X = X; this.Y = Y; this.Z = Z; }
(XYZ.prototype = new d3_Color).constructor = XYZ;
// D65 standard referent // d3_lab_X d3_lab_Y d3_lab_Z
var D65 = new XYZ(0.950470, 1, 1.088830);
// CIE 1976 uniform chromaticity scale
XYZ.prototype.ucs_u = function(){
return (this.X == 0) ? 0 : 4 * this.X / (this.X + 15 * this.Y + 3 * this.Z);
};
XYZ.prototype.ucs_v = function(){
return (this.Y == 0) ? 0 : 9 * this.Y / (this.X + 15 * this.Y + 3 * this.Z);
};
XYZ.prototype.luv = function(){
var l = 116 * d3_xyz_lab(this.Y) - 16;
return new Luv(l,
13 * l * (this.ucs_u() - D65.ucs_u()),
13 * l * (this.ucs_v() - D65.ucs_v()));
};
XYZ.prototype.rgb = function(){
return d3.rgb(
d3_xyz_rgb( 3.2404542 * this.X - 1.5371385 * this.Y - 0.4985314 * this.Z),
d3_xyz_rgb(-0.9692660 * this.X + 1.8760108 * this.Y + 0.0415560 * this.Z),
d3_xyz_rgb( 0.0556434 * this.X - 0.2040259 * this.Y + 1.0572252 * this.Z)
);
};
function Luv(l, u, v){ this.l = l; this.u = u; this.v = v; }
(Luv.prototype = new d3_Color).constructor = Luv;
Luv.prototype.brighter = function(k){
return new Luv(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.u, this.v);
};
Luv.prototype.darker = function(k){
return new Luv(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.u, this.v);
};
Luv.prototype.rgb = function(){
return this.XYZ().rgb();
};
Luv.prototype.XYZ = function(){
var Y = d3_lab_xyz((this.l + 16) / 116) * D65.Y,
ucs_u = this.u / (13 * this.l) + D65.ucs_u(),
ucs_v = this.v / (13 * this.l) + D65.ucs_v(),
s = 9 * Y / ucs_v,
X = ucs_u / 4 * s,
Z = (s - X - 15 * Y) / 3;
return new XYZ(X, Y, Z);
};
function rgb_XYZ(r, g, b){
r = d3_rgb_xyz(r);
g = d3_rgb_xyz(g);
b = d3_rgb_xyz(b);
return new XYZ(0.4124564 * r + 0.3575761 * g + 0.1804375 * b,
0.2126729 * r + 0.7151522 * g + 0.0721750 * b,
0.0193339 * r + 0.1191920 * g + 0.9503041 * b);
}
function interpolateLuv(a, b) {
a = luv(a);
b = luv(b);
var ul = a.l,
uu = a.u,
uv = a.v,
dl = b.l - ul,
du = b.u - uu,
dv = b.v - uv;
return function(t) {
a.l = ul + dl * t;
a.u = uu + du * t;
a.v = uv + dv * t;
return a;
};
}
function interpolateXYZ(a, b){
a = xyz(a);
b = xyz(b);
var aX = a.X,
aY = a.Y,
aZ = a.Z,
dX = b.X - aX,
dY = b.Y - aY,
dZ = b.Z - aZ;
return function(t) {
a.X = aX + dX * t;
a.Y = aY + dY * t;
a.Z = aZ + dZ * t;
return a;
};
}
function d3_xyz_lab(x) { // L* companding
return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
}
function d3_lab_xyz(x) { // inverse L* companding
return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
}
function d3_xyz_rgb(r) { // sRGB companding
return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055));
}
function d3_rgb_xyz(r) { // inverse sRGB companding
return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
}
})((typeof module === 'object' && module.exports) ? require('d3') : d3);
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
form {
width: 960px; position: absolute;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="d3-color-cie-luv-xyz.js"></script>
<form>
<input type="color" value="#00ffff"/>
<input type="color" value="#ff0000" style="float:right"/>
</form>
<script src="vis.js"></script>
var interpolators = {
"Lab": d3.interpolateLab,
"Luv": d3.color.interpolateLuv,
"XYZ": d3.color.interpolateXYZ,
"RGB": d3.interpolateRgb,
"(Lab)HCL": d3.interpolateHcl,
"HSL": d3.interpolateHsl
};
var width = 960,
height = 500;
var y = d3.scale.ordinal()
.domain(d3.keys(interpolators))
.rangeRoundBands([0, height], .1);
var values = d3.range(960 - 28);
var x = d3.scale.ordinal()
.domain(values)
.rangeRoundBands([14, width - 14]);
var bounds = [d3.hsl(62,1,.9),
d3.hsl(228,.3,.2)],
color = d3.scale.linear()
.domain([0, values.length - 1]),
inputs = d3.selectAll('form input')
.on('change', paint);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.selectAll("g")
.data(d3.entries(interpolators))
.enter().append("g")
.attr("transform", function(d) { return "translate(0," + y(d.key) + ")"; });
paint();
g.append("text")
.attr("x", 28)
.attr("y", y.rangeBand() / 2)
.attr("dy", ".35em")
.text(function(d) { return d.key; });
function paint(){
inputs.each(function (d,i){
bounds[i] = d3.rgb(this.value);
});
g.each(function(d) {
color
.interpolate(d.value)
.range(bounds);
var r = d3.select(this).selectAll("rect")
.data(values);
r.enter().append("rect")
.attr("x", x)
.attr("width", x.rangeBand())
.attr("height", y.rangeBand);
r.style("fill", color);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment