function paletteBuilder(options = {}) {
let {
startColor = "#aa3333",
size = 300,
ncolors = 5,
tension = 0.3,
slSpacing = 0.2,
hueSpacing = 20
} = options;
let palDisplay = html`<div>`;
let controls = paged(
{
hueSpacing: Inputs.range([1, 120], {
label: "Hue separation",
step: 1,
value: hueSpacing
}),
slSpacing: Inputs.range([0.01, 0.4], {
label: "Sat Lum spacing",
step: 0.01,
value: slSpacing
}),
tension: Inputs.range([0.1, 0.5], {
label: "SL curve tension",
value: tension,
step: 0.05
}),
nColors: Inputs.range([2, 9], {
label: "Num colors",
step: 1,
value: ncolors
})
},
undefined,
palDisplay
);
Object.assign(controls.style, {
display: "inline-block",
verticalAlign: "top",
marginRight: "10px"
});
// The sat lum plane
let { h, s, l } = d3.hsl(startColor);
let sz = size;
let slchart = SLChart(h, [s, l], sz);
Object.assign(slchart.style, {
display: "inline-block",
marginRight: "10px"
});
let npoints = ncolors - 1;
let sls;
// Compute sl points and draw them on the sl chart
const updateSlChart = () => {
let ctx = slchart.getContext("2d");
let sz = slchart.width;
let { slToXy, xyToSl } = colorplane(sz, sz);
let q = Vec(...slToXy(s, l));
const options = {
spacing: controls.value.slSpacing,
tension: controls.value.tension,
sizeX: sz,
sizeY: sz
};
let points = colorPlaneCurve(q, npoints, options);
sls = points.map((p) => xyToSl(p.x, p.y));
// Draw points
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
for (let { x, y } of points) {
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.stroke();
}
};
updateSlChart();
// The hue bar
let hueselector = hueSelector(h, 20, sz);
Object.assign(hueselector.style, {
display: "inline-block",
marginRight: "10px"
});
let hues;
// Compute and draw the interpolated hues;
const updateHue = () => {
let ctx = hueselector.getContext("2d");
let hSpacing = controls.value.hueSpacing; // degrees
hues = d3.range(1, (npoints >> 1) + 1).map((i) => (h + i * hSpacing) % 360);
hues = hues.concat(
d3
.range(1, npoints - (npoints >> 1) + 1)
.map((i) => (h - i * hSpacing + 360) % 360)
);
ctx.lineWidth = 1;
for (let hue of hues) {
let y = (hue / 360) * sz;
ctx.beginPath();
ctx.arc(10, y, 4, 0, Math.PI * 2);
ctx.stroke();
}
};
updateHue();
// Container for sl and hue
let div = html`<div>${slchart}${hueselector}${controls}</div>`;
function setValue() {
div.value = d3
.zip(sls, hues)
.map(([[s, l], h]) => d3.hsl(h, s, l).formatHsl());
div.value.splice(npoints >> 1, 0, d3.hsl(h, s, l).formatHsl());
palDisplay.innerHTML = "";
palDisplay.append(displayPalette(div.value));
div.dispatchEvent(new CustomEvent("input"));
}
function handleSlInput(e) {
[s, l] = slchart.value;
updateHue();
updateSlChart();
setValue();
e.preventDefault();
}
function handleHueInput(e) {
h = hueselector.value;
slchart.setHue(h);
updateHue();
updateSlChart();
setValue();
e.preventDefault();
}
function updateOnControl() {
npoints = controls.value.nColors - 1;
slchart.draw();
hueselector.draw();
updateSlChart();
updateHue();
setValue();
}
controls.addEventListener("input", updateOnControl);
slchart.addEventListener("input", handleSlInput);
hueselector.addEventListener("input", handleHueInput);
setValue();
return div;
}