Published
Edited
May 21, 2019
Importers
8 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
size = [128, 129] // width, height of the data arrays
Insert cell
diffuse_steps = 800
Insert cell
diffuse_amount_per_step = 0.08
Insert cell
hard_C_limit = 0.14
Insert cell
// data array of max in-gamut chroma for each lightness & hue
C = {
const
[m, n] = size,
C = new Float32Array(m*n);
for (let i = 1; i < n-1; i++) {
for (let j = 0; j < m; j++) {
const
k = i*m + j,
Jk = Jmax * i/(n-1),
hk = Math.PI*2 * j/m;
C[k] = Math.min(max_C(Jk, hk), hard_C_limit);
}
}
return C;
}
Insert cell
// Make a copy of C, then blur it `diffuse_steps` times
Cdiffuse = {
const Cdif = new C.constructor(C);
for (let i = 0; i < diffuse_steps; i++) {
constrained_diffuse(Cdif, C, size, diffuse_amount_per_step)
}
return Cdif;
}
Insert cell
C_for_Jh = function (J, h) { // takes J and h in [0, 1]
const
[m, n] = size, mE = m+3, nE = n + 2,
CE = Cdiffuse_extended,
Ji = J * (n-1),
hj = h * m,
i = Math.floor(Ji) + 1,
j = Math.floor(hj) + 1,
ty = mod(Ji, 1),
tx = mod(hj, 1);
// // Bilinear interpolation
// return lerp(
// lerp(CE[i*mE + j], CE[i*mE + j+1], tx),
// lerp(CE[(i+1)*mE + j], CE[(i+1)*mE + j+1], tx),
// ty)
// Bicubic interpolation
return cubic_interp(
cubic_interp(CE[(i-1)*mE + j-1], CE[(i-1)*mE + j], CE[(i-1)*mE + j+1], CE[(i-1)*mE + j+2], tx),
cubic_interp(CE[(i+0)*mE + j-1], CE[(i+0)*mE + j], CE[(i+0)*mE + j+1], CE[(i+0)*mE + j+2], tx),
cubic_interp(CE[(i+1)*mE + j-1], CE[(i+1)*mE + j], CE[(i+1)*mE + j+1], CE[(i+1)*mE + j+2], tx),
cubic_interp(CE[(i+2)*mE + j-1], CE[(i+2)*mE + j], CE[(i+2)*mE + j+1], CE[(i+2)*mE + j+2], tx),
ty);
}
Insert cell
hex_for_Jh = function (J, h) { // takes J and h in [0, 1]
const C = C_for_Jh(J, h);
J = Jmax * J;
h = Math.PI * 2 * h;
return to_hex(xyz_to_srgb(JzCzhz_inverse([J, C, h]).map(X=>X/10)).map(clip));
}
Insert cell
Cdiffuse_extended = {
const [m,n] = size, mE = m + 3, nE = n + 2;
const CdifE = new Float64Array(mE*nE);
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
const k = i*m + j, kE = (i+1)*mE + j+1;
CdifE[kE] = C[k];
}
}
// wrap columns to the sides
for (let i = 1; i < nE-1; i++) {
CdifE[i*mE] = CdifE[i*mE + m];
CdifE[i*mE + m+1] = CdifE[i*mE + 1];
CdifE[i*mE + m+2] = CdifE[i*mE + 2];
}
// linearly extrapolate top/bottom
for (let j = 0; j < mE; j++) {
CdifE[j] = 2*CdifE[j + mE] - CdifE[j + 2*mE];
CdifE[j + mE*(n+1)] = 2*CdifE[j + mE*n] - 2*CdifE[j + mE*(n-1)];
}
return CdifE
}
Insert cell
bitmap = {
const
[m, n] = size,
image = new ImageData(m, n),
data = image.data;
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
const
k = i*m + j,
Jk = Jmax * i/(n-1),
hk = Math.PI*2 * j/m;
const [Rf, Gf, Bf] = xyz_to_srgb(JzCzhz_inverse([Jk, C[k], hk]).map(X=>X/10));
data[4*k+0] = Rf*255;
data[4*k+1] = Gf*255;
data[4*k+2] = Bf*255;
data[4*k+3] = 255;
}
}
return createImageBitmap(image)
}
Insert cell
dif_bitmap = {
const
[m, n] = size,
image = new ImageData(m, n),
data = image.data;
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
const
k = i*m + j,
Jk = Jmax * i/(n-1),
hk = Math.PI*2 * j/m;
const [Rf, Gf, Bf] = xyz_to_srgb(JzCzhz_inverse([Jk, Cdiffuse[k], hk]).map(X=>X/10));
data[4*k+0] = Rf*255;
data[4*k+1] = Gf*255;
data[4*k+2] = Bf*255;
data[4*k+3] = 255;
}
}
return createImageBitmap(image)
}
Insert cell
Cbitmap = {
const
[m, n] = size,
image = new ImageData(m, n),
data = image.data;
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
const k = i*m + j;
data[4*k] = 255*C[k]/Cmax;
data[4*k+1] = 255*C[k]/Cmax;
data[4*k+2] = 255*C[k]/Cmax;
data[4*k+3] = 255;
}
}
return createImageBitmap(image)
}
Insert cell
Cdif_bitmap = {
const
[m, n] = size,
image = new ImageData(m, n),
data = image.data;
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
const k = i*m + j;
data[4*k] = 255*Cdiffuse[k]/Cmax;
data[4*k+1] = 255*Cdiffuse[k]/Cmax;
data[4*k+2] = 255*Cdiffuse[k]/Cmax;
data[4*k+3] = 255;
}
}
return createImageBitmap(image)
}
Insert cell
Insert cell
// Note: mutates X
constrained_diffuse = function constrained_diffuse(X, XConstraint, [m, n], amount) {
for (let i = 1; i < n-1; i++) {
for (let j = 0; j < m; j++) {
const k = i*m + j;
X[k] = Math.min(
X[k] + amount * (
-4 * X[k] +
X[i*m + mod(j-1, m)] +
X[i*m + mod(j+1, m)] +
X[k - m] +
X[k + m]),
XConstraint[k]);
}
}
}
Insert cell
Jmax = JzCzhz(rgb_to_xyz([1,1,1]).map(X=>X*10))[0]
Insert cell
Cmax = Math.max(...C)
Insert cell
// For a given lightness/hue, find the largest chroma in sRGB gamut.
max_C = (J, h) => Math.min(
bisect(C => rgb_R_distance(JzCzhz_inverse([J, C, h]).map(X=>X/10)), 0, 1, 1e-7),
bisect(C => rgb_G_distance(JzCzhz_inverse([J, C, h]).map(X=>X/10)), 0, 1, 1e-7),
bisect(C => rgb_B_distance(JzCzhz_inverse([J, C, h]).map(X=>X/10)), 0, 1, 1e-7))
Insert cell
Insert cell
// Use Horner’s method to evaluate the polynomial in t established by the data points
// Expects t in [0, 1]
cubic_interp = (y_1, y0, y1, y2, t) =>
y0 + .5 * (
t * (-y_1 + y1 +
t * (2*y_1 - 5*y0 + 4*y1 - y2 +
t * (-y_1 + 3*(y0 - y1) + y2 ))))
Insert cell
lerp = (y0, y1, t) =>
y0 + t*(y1 - y0)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
clip = (x) => (x <= 1) * (x >= 0) * x + (x > 1)
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more