Public
Edited
Apr 16, 2024
Paused
4 forks
Importers
16 stars
Insert cell
Insert cell
Insert cell
mixbox = require("mixbox@2.0.0/mixbox.js")
Insert cell
spectraljs = require("spectral.js@2")
Insert cell
Insert cell
mixed = {
const rgb1 = "rgb(0, 33, 133)"; // blue
const rgb2 = "rgb(252, 211, 0)"; // yellow
const t = 0.5; // mixing ratio

return mixbox.lerp(rgb1, rgb2, t);
}
Insert cell
Insert cell
rgbMix = {
const rgb1 = "rgb(0, 33, 133)"; // blue
const rgb2 = "rgb(252, 211, 0)"; // yellow
const rgb3 = "rgb(255, 39, 2)"; // red
const z1 = mixbox.rgbToLatent(rgb1);
const z2 = mixbox.rgbToLatent(rgb2);
const z3 = mixbox.rgbToLatent(rgb3);

const zMix = new Array(mixbox.LATENT_SIZE);

for (var i = 0; i < zMix.length; i++) {
// mix:
zMix[i] =
0.3 * z1[i] + // 30% of rgb1
0.6 * z2[i] + // 60% of rgb2
0.1 * z3[i]; // 10% of rgb3
}

return mixbox.latentToRgb(zMix);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
palettePigments = {
const extraPigmentEntries = d3
.range(0, extraPigments)
.map((i) => [`Custom pigment #${i}`, "rgb(0, 0, 0)"]);

return [...Object.entries(DEFAULT_PIGMENTS), ...extraPigmentEntries].filter(
([name, color]) => !removals.includes(name)
);
}
Insert cell
Insert cell
modifedPalletePigments = settle(viewof palette)
Insert cell
pigments = Object.fromEntries(modifedPalletePigments)
Insert cell
mutable removals = []
Insert cell
Insert cell
culori = require("culori@3.3.0/bundled/culori.umd.js")
Insert cell
rgbGamutInOklab = {
const ranges = culori.modeOklab.ranges;
const lRange = d3.range(...ranges.l, 0.02);
const aRange = d3.range(...ranges.a, 0.01);
const bRange = d3.range(...ranges.b, 0.01);

const acc = [];

for (const a of aRange) {
for (const b of bRange) {
for (const l of lRange) {
const oklab = { mode: "oklab", l, a, b };

if (!culori.displayable(oklab)) {
continue;
}

if (Math.random() < 0.9) {
continue;
}

const hex = culori.formatHex(oklab);

acc.push({ hex, ...oklab });
}
}
}
return acc;
}
Insert cell
STEP_SIZE = ({
a: 1 / 150,
b: 1 / 150,
h: 1,
l: 1 / 30
})
Insert cell
rgbMaxLInOklab = {
const ranges = culori.modeOklab.ranges;
const lRange = d3.range(...ranges.l, STEP_SIZE.l);
const aRange = d3.range(...ranges.a, STEP_SIZE.a);
const bRange = d3.range(...ranges.b, STEP_SIZE.b);

const getMaxLightness = (a, b) => {
return _.last(
lRange.filter((l) => {
return culori.displayable({ mode: "oklab", l, a, b });
})
);
};

return aRange
.flatMap((a) =>
bRange.map((b) => {
const l = getMaxLightness(a, b);
const oklab = { mode: "oklab", l, a, b };
const hex = culori.formatHex(oklab);

return { hex, ...oklab };
})
)
.filter((d) => d.l);
}
Insert cell
rgbMaxLInOklabMark = Plot.rect(rgbMaxLInOklab, {
x1: "a",
x2: (d) => d.a + STEP_SIZE.a,
y1: "b",
y2: (d) => d.b + STEP_SIZE.b,
fill: "hex",
fillOpacity: settings.oklabTransparency
})
Insert cell
rgbMaxCInOklch = {
const ranges = culori.modeOklch.ranges;
const lRange = d3.range(...ranges.l, STEP_SIZE.l);
const hRange = d3.range(...ranges.h, STEP_SIZE.h);

return lRange.flatMap((l) =>
hRange.map((h) => {
const c = ranges.c[1];
const oklch = culori.clampChroma({ mode: "oklch", l, c, h }, "oklch");
const hex = culori.formatHex(oklch);
return { hex, ...oklch };
})
);
}
Insert cell
rgbMaxCInOklchMark = Plot.rect(rgbMaxCInOklch, {
x1: (d) => d.h,
x2: (d) => d.h + STEP_SIZE.h,
y1: (d) => d.l,
y2: (d) => d.l + STEP_SIZE.l,
fill: "hex",
fillOpacity: settings.oklabTransparency
})
Insert cell
centerPointMark = Plot.dot([[0, 0]], {
x: () => 0,
y: () => 0,
fill: () => "#000000",
r: 3,
title: () => `#000000
oklch(0, 0, 0°)`
})
Insert cell
Insert cell
Insert cell
Insert cell
mix = (from, to, fromLabel, toLabel) =>
mixingCoefficients.flatMap((t) => {
const [r, g, b] = mixbox.lerp(from, to, t);
const rgb = { mode: "rgb", r: r / 255, g: g / 255, b: b / 255 };

return [
{
algorithm: "Mixbox",
coefficient: t,
hex: culori.formatHex(rgb),
...culori.oklab(rgb),
fromLabel,
toLabel
},
{
algorithm: "Spectral.js",
coefficient: t,
hex: spectraljs.mix(from, to, t),
...culori.oklab(spectraljs.mix(from, to, t)),
fromLabel,
toLabel
}
];
})
Insert cell
mixingPath = mix(
firstPigment,
secondPigment,
pigments[firstMixingPigmentName] === culori.formatHex(firstPigment)
? firstMixingPigmentName
: "Custom pigment",
pigments[secondMixingPigmentName] === culori.formatHex(secondPigment)
? secondMixingPigmentName
: "Custom pigment"
)
Insert cell
explorePaths = {
const length = Object.values(pigments).length;
const acc = [];

for (let j = 0; j < length; ++j) {
acc.push(
...mix(
exploringPigment,
Object.values(pigments)[j],
pigments[exploringPigmentName] === culori.formatHex(exploringPigment)
? exploringPigmentName
: "Custom pigment",
Object.keys(pigments)[j]
)
);
}

return acc;
}
Insert cell
allPaths = {
const length = Object.values(pigments).length;
const acc = [];

for (let i = 0; i < length; ++i) {
for (let j = i; j < length; ++j) {
const mixes = mix(
Object.values(pigments)[i],
Object.values(pigments)[j],
Object.keys(pigments)[i],
Object.keys(pigments)[j]
);

acc.push(...mixes);
}
}

return acc;
}
Insert cell
Insert cell
tooltip = (d) => {
let { l, c, h } = culori.oklch(d.hex);

h = h || 0;

return `Algorithm: ${d.algorithm}
${d.hex}
oklch(${(l * 100).toFixed(0)}, ${c.toFixed(2)}, ${h.toFixed(0)}°)
From: ${d.fromLabel}
To: ${d.toLabel}
Mixing coefficient: ${d.coefficient.toFixed(2)}`;
}
Insert cell
Insert cell
facetDomain = compareSpectralJs ? ["Mixbox", "Spectral.js"] : ["Mixbox"]
Insert cell
renderPlot = ({ points }) =>
Plot.plot({
width: facetDomain.length * 600,
caption: md`${caption}`,
aspectRatio: 1,
fx: {
domain: facetDomain
},
marks: [
rgbMaxLInOklabMark,
// must be rendered after diagram to get tooltip to work
Plot.dot(points, {
x: "a",
y: "b",
sort: "l",
fill: "hex",
fx: "algorithm",
r: settings.pathRadius,
tip: true,
title: tooltip
}),
centerPointMark
]
})
Insert cell
Insert cell
render3dPlot = ({ points }) => {
const data = [
...facetDomain.map((algorithmFacet) => {
// deliberately shadow
const facetPoints = points.filter((d) => d.algorithm === algorithmFacet);

return {
x: facetPoints.map((point) => point.a),
y: facetPoints.map((point) => point.b),
z: facetPoints.map((point) => point.l),
hovertext: facetPoints.map((point) => tooltip(point)),
name: algorithmFacet,
mode: "markers",
type: "scatter3d",
marker: {
size: settings.pathRadius,
color: facetPoints.map((point) => point.hex),
opacity: 1
}
};
}),
{
x: rgbGamutInOklab.map((point) => point.a),
y: rgbGamutInOklab.map((point) => point.b),
z: rgbGamutInOklab.map((point) => point.l),
mode: "markers",
name: "RGB gamut",
type: "scatter3d",
marker: {
size: 5,
color: rgbGamutInOklab.map((point) => point.hex),
opacity: settings.oklabTransparency
}
}
];
const layout = {
margin: {
l: 0,
r: 0,
b: 0,
t: 0
},
height: 600,
width: 800,
scene: {
camera: {
eye: {
x: 0,
y: -0.1,
z: 2
}
},
aspectmode: "manual",
aspectratio: {
x: 1,
y: 1,
z: 1
},
xaxis: {
title: {
text: "a"
}
},
yaxis: {
title: {
text: "b"
}
},
zaxis: {
title: {
text: "L"
}
}
}
};
const div = DOM.element("div");
return Plotly.newPlot(div, data, layout);
}
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