Public
Edited
Oct 5, 2022
17 forks
32 stars
Insert cell
Insert cell
chart = {
const r = new Uint16Array(257);
const g = new Uint16Array(257);
const b = new Uint16Array(257);

const brush = d3.brush()
.on("start brush", brushed);

const context = DOM.context2d(width, height, 1);
context.willReadFrequently = true;

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const histogram = svg.append("g")
.style("isolation", "isolate");

const histoarea = histogram
.selectAll("path")
.data([r, g, b])
.join("path")
.style("mix-blend-mode", "screen")
.attr("fill", (d, i) => d3.hsl(i * 120, 1, 0.5));

const histoline = histoarea.clone()
.attr("fill", "none")
.attr("stroke", "black")
.attr("shape-rendering", "crispEdges")
.style("mix-blend-mode", null)
.raise();

context.drawImage(image, 0, 0, width, height);

svg.append("g")
.call(brush)
.call(brush.move, [[width * 0.3, height * 0.1], [width * 0.7, height * 0.4]]);

function brushed({selection: [[x0, y0], [x1, y1]]}) {
x0 = Math.round(x0), y0 = Math.round(y0);
x1 = Math.round(x1), y1 = Math.round(y1);
const dx = x1 - x0, dy = y1 - y0;

r.fill(0);
g.fill(0);
b.fill(0);

if (x1 > x0 && y1 > y0) {
const data = context.getImageData(x0, y0, dx, dy).data;
let max = 0;
for (let i = 0, k = -1; i < dx; ++i) {
for (let j = 0; j < dy; ++j, ++k) {
max = Math.max(max, ++r[data[++k]], ++g[data[++k]], ++b[data[++k]]);
}
}
y.domain([0, max]);
}

histoarea.attr("d", area);
histoline.attr("d", line);
}

return html`
${Object.assign(context.canvas, {style: "position: absolute;"})}
${Object.assign(svg.node(), {style: "position: relative;"})}
`;
}
Insert cell
x = d3.scaleLinear()
.domain([0, 256])
.rangeRound([0, width])
Insert cell
y = d3.scaleLinear()
.rangeRound([0, height / 4])
Insert cell
area = d3.area()
.curve(d3.curveStepAfter)
.x((d, i) => x(i))
.y0(y(0))
.y1(y)
Insert cell
line = d3.line()
.curve(curveStepBelow)
.x((d, i) => x(i))
.y(y)
Insert cell
Insert cell
function curveStepBelow(context) {
let y0, i;
return {
lineStart: () => { y0 = NaN, i = 0; },
lineEnd: () => {},
point: (x, y) => {
x -= y0 < y ? -0.5 : +0.5;
y += 0.5;
if (++i === 1) {
context.moveTo(x, y0 = y);
} else {
context.lineTo(x, y0);
context.lineTo(x, y0 = y);
}
}
};
}
Insert cell
image = FileAttachment("mona-lisa.jpeg").image({width: 60})
Insert cell
height = Math.round(width / image.naturalWidth * image.naturalHeight)
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