Public
Edited
Jul 31, 2023
Importers
1 star
Insert cell
Insert cell
viewof color = picker("hcl", [
{ name: "h", domain: [0, 360] },
{ name: "c", domain: [0, 100] },
{ name: "l", domain: [0, 150] }
], this?.value)
Insert cell
function picker(model, channels, color) {
// Specify the picker’s dimensions.
// const width = 928; // set by Observable to the window’s width, for a responsive chart
const height = 70;

channels = channels.map(({ name, domain }) => ({
name,
scale: d3.scaleLinear().domain(domain).range([0, width]),
removeLastTick: domain[1]===100 || domain[1]===1
}));

// Start from the passed color (present after, e.g., a window resize event), or the midpoint.
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)
);

// Update the value of the cell and notify listeners (for example, this informs the “color” cell below).
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();
}
Insert cell
color
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