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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more