Published
Edited
Dec 20, 2021
2 forks
1 star
Insert cell
Insert cell
Insert cell
viewof imageRaw = imageInput({
crossOrigin: "anonymous",
initialUrl: undefined
})
Insert cell
Insert cell
Insert cell
Insert cell
mutable contours_ = []
Insert cell
Insert cell
{
const context = DOM.context2d(imageWidth, imageHeight, 1);
context.drawImage(image, 0, 0, imageWidth, imageHeight);

context.canvas.style.width = "400px";
context.canvas.style.border = "1px solid black";

const grid = getCells(200).map((cell) => {
const { x, y, width, height } = cell;
const pixels = getPixels2({
context,
x,
y,
width,
height
});
const meanLightness = d3.max(pixels, (d) => d.lightness);
return {
...cell,
meanLightness
};
});

const columns = d3.max(grid, (d) => d.column) + 1;
const rows = d3.max(grid, (d) => d.row) + 1;

const xScale = d3.scaleLinear([0, columns], [0, imageWidth]);
const yScale = d3.scaleLinear([0, rows], [0, imageHeight]);

const contours = d3
.contours()
// .thresholds([0, 10, 50, 100, 150, 200, 240, 255])
// .thresholds(d3.range(0, 300, 20))
.thresholds(thresholds)
.size([columns, rows])(grid.map((d) => d.meanLightness));

// Re-scale in place
contours.forEach(({ coordinates }) => {
coordinates.forEach((lineStrings) => {
lineStrings.forEach((points) => {
points.forEach((point) => {
point[0] = xScale(point[0]);
point[1] = yScale(point[1]);
});
});
});
});

mutable contours_ = contours;

const contourPaths = contours
.map((geoJSON) => d3.geoPath()(geoJSON))
.filter((d) => d)
.map(
(pathDef) => htl.svg`<path fill="none" stroke="black" d=${pathDef} />`
);

return htl.html`
<svg viewBox="0 0 ${imageWidth} ${imageHeight}" style="width: 400px; border: 1px dashed red;">
${contourPaths}
</svg>
`;
}
Insert cell
{
const context = DOM.context2d(imageWidth, imageHeight, 1);
context.drawImage(image, 0, 0, imageWidth, imageHeight);

context.canvas.style.width = "400px";
context.canvas.style.border = "1px solid black";

const columns = 100;

const grid = getCells(columns).map((cell) => {
const { x, y, width, height } = cell;
const pixels = getPixels2({
context,
x,
y,
width,
height
});
const maxLightness = d3.max(pixels, (d) => d.lightness);
const meanLightness = d3.mean(pixels, (d) => d.lightness);
return {
...cell,
maxLightness,
meanLightness
};
});

// const maxDots = 1500 / columns;
const maxDots = 10;

// const maxDots = 20;
const numDotsScale = d3.scaleLinear([0, 255], [0, maxDots]);

const allDots = grid
.map((cell) => {
const numDots = Math.ceil(numDotsScale(cell.meanLightness));
const dots = d3.range(numDots).map(() => {
const x = cell.x + Math.random() * cell.width;
const y = cell.y + Math.random() * cell.height;
return { x, y };
});
return dots;
})
.flat(1)
.map((d) => htl.svg`<circle r="1" cx=${d.x} cy=${d.y} />`);

const children = allDots;

return htl.html`
<svg viewBox="0 0 ${imageWidth} ${imageHeight}" style="width: 400px; border: 1px dashed red;">
${children}
</svg>
`;
}
Insert cell
mutable voronoiPoints = []
Insert cell
Insert cell
Insert cell
Insert cell
{
const context = DOM.context2d(imageWidth, imageHeight, 1);
context.drawImage(image, 0, 0, imageWidth, imageHeight);

const img = await imageJS.Image.fromCanvas(context.canvas);

const grey = img.grey();

const defaultOptions = {
lowThreshold: 10,
highThreshold: 30,
gaussianBlur: 1.1
};

const edges = grey.cannyEdge({
...defaultOptions,
lowThreshold,
highThreshold,
gaussianBlur
});

const contextOut = DOM.context2d(imageWidth, imageHeight, 1);

edges.data.forEach((d, i) => {
const x = i % img.width;
const y = Math.floor(i / img.width);
contextOut.beginPath();
contextOut.fillStyle = d === 0 ? `white` : `red`;
contextOut.fillRect(x, y, 1, 1);
});

const voronoiPointsAll = getCells(100).map((cell) => {
const { x, y, width, height } = cell;
const pixels = getPixels2({
context: contextOut,
x,
y,
width,
height
});
const average = d3.mean(pixels, (d) => d.lightness);
return [x, y, average];
});

mutable voronoiPoints = voronoiPointsAll;

const voronoiPointsFiltered = voronoiPointsAll.filter((d) => d[2] < 255);

const voronoiCells = getVoronoiCells(voronoiPointsFiltered, {
width: imageWidth,
height: imageHeight
});

const voronoiPaths = voronoiCells.map(
(d) => htl.svg`<path fill="none" stroke="black" d=${d.pathDefinition} />`
);

return htl.html`
<svg viewBox=${`0 0 ${imageWidth} ${imageHeight}`} style="border: 1px dashed black; width: 400px">
${voronoiPaths}
</svg>
`;
}
Insert cell
{
const colorScale = d3.scaleLinear([0, 255], ["white", "black"]);
const bumpMax = 100;
const bumpScale = d3.scaleLinear([0, 255], [bumpMax, 0]);

const dots = voronoiPoints.map(
([x, y, average]) =>
htl.svg`<circle r="2" cx=${x} cy=${y} fill=${colorScale(average)} />`
);

const lines = d3
.groups(voronoiPoints, (d) => d[1])
.map(([rowY, values]) => {
const chunks = values
.map(([x, y, lightness]) => {
const bump = bumpScale(lightness);
return `L ${x} ${y + bump}`;
})
.join(" ");
return `M 0 ${rowY} ${chunks}`;
})
.map((pathDef) => {
return htl.svg`<path fill="none" stroke="black" d=${pathDef} />`;
});

const children = lines;
return htl.html`<svg viewBox=${`0 0 ${imageWidth} ${imageHeight}`} style="border: 1px dashed black; width: 400px">${children}</svg>`;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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