function picker(model, channels, color) {
const height = 70;
channels = channels.map(({ name, domain }) => ({
name,
scale: d3.scaleLinear().domain(domain).range([0, width]),
removeLastTick: domain[1]===100 || domain[1]===1
}));
for (const d of channels) d.x = Math.round(color ? d.scale(color[d.name]) : width / 2);
const wrapper = d3.create("div");
const white = d3.rgb("white");
const black = d3.rgb("black");
const channel = wrapper.selectAll("div").data(channels).join("div");
const ctx = d3.local();
const canvas = channel
.append("canvas")
.attr("width", width)
.attr("height", 1)
.style("max-width", "100%")
.style("width", `${width}px`)
.style("height", `${height}px`)
.each(function (d) {
const context = this.getContext("2d");
const image = context.createImageData(width, 1);
ctx.set(this, { context, image, data: image.data });
})
.each(render);
const svg = channel
.append("svg")
.attr("width", width)
.attr("height", 20)
.attr("viewBox", [0, 0, width, 20])
.style("max-width", "100%")
.style("overflow", "visible")
.append("g")
.each(function (d) {
d3.select(this).call(
d3.axisBottom(d.scale).ticks(Math.min(width / 80, 10))
);
if (d.removeLastTick) d3.select(this).select("g:last-of-type").remove();
})
.append("text")
.attr("x", width)
.attr("y", 9)
.attr("dy", ".72em")
.style("text-anchor", "middle")
.style("text-transform", "uppercase")
.attr("fill", "currentColor")
.text((d) => d.name);
canvas.call(
d3.drag()
.subject(({x}) => ({x}))
.on("start drag", ({x}, d) => {
d.x = Math.max(1, Math.min(width - 1, x));
canvas.each(render);
})
);
function render(d) {
const current = d3[model](
channels[0].scale.invert(channels[0].x),
channels[1].scale.invert(channels[1].x),
channels[2].scale.invert(channels[2].x)
);
if (d.name === "h") {
wrapper.node().value = current.copy();
wrapper.node().dispatchEvent(new Event("input"));
}
const { context, image, data } = ctx.get(this);
for (let x = 0, i = -1; x < width; ++x) {
const c = x === d.x ? white
: x === d.x - 1 ? black
: ((current[d.name] = d.scale.invert(x)), d3.rgb(current));
data[++i] = c.r;
data[++i] = c.g;
data[++i] = c.b;
data[++i] = 255;
}
context.putImageData(image, 0, 0);
}
return wrapper.node();
}