Published
Edited
Apr 1, 2022
1 fork
11 stars
Also listed in…
Plot
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.stereoHistogram(
penguins,
Plot.binX({ y: "count" }, { x: "body_mass", thresholds: 20 })
)
],
width,
height: 500
})
Insert cell
Insert cell
Insert cell
Insert cell
penguins
Insert cell
import {data as penguins} from "@observablehq/plot-exploration-penguins"
Insert cell
Plot = require("@observablehq/plot").then(d => (d.stereoHistogram = stereoHistogram(d), d))
Insert cell
function stereoHistogram(Plot) {
return function (data, options) {
const dot = Plot.rectY(data, options);
const { render } = dot;
dot.render = function (
I,
{},
{ x1: X1, x2: X2, y1: Y1, y2: Y2 },
{ marginLeft, marginTop, marginRight, marginBottom, width, height }
) {
width = (width - marginLeft - marginRight) | 0;
height = (height - marginTop - marginBottom) | 0;
const dpi = 1;
const context = DOM.context2d(width, height, dpi);
context.fillStyle = "#fff";
context.fillRect(0, 0, width, height); // background 0
context.fillStyle = "#000";
context.strokeStyle = "#333";
for (const i of I) {
context.fillRect(X1[i] + 5, Y2[i], X2[i] - X1[i] - 10, Y1[i] - Y2[i]);
context.strokeRect(X1[i] + 5, Y2[i], X2[i] - X1[i] - 10, Y1[i] - Y2[i]);
}
const imdata = context.getImageData(0, 0, dpi * width, dpi * height);
const pixels = context.getImageData(0, 0, dpi * width, dpi * height);
generatePixelData({
pixels,
imdata,
width: dpi * width,
height: dpi * height,
colors: Array.from(d3.schemeAccent.slice(0, 6), (c) => {
c = d3.rgb(c);
return [c.r, c.g, c.b, 255];
})
});
if (apply3d) context.putImageData(pixels, 0, 0);

return d3
.create("svg:image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("image-rendering", "pixelated")
.attr("width", width)
.attr("height", height)
.attr("href", context.canvas.toDataURL())
.node();
};
return dot;
};
}
Insert cell
mutable debug = 0
Insert cell
Insert cell
function generatePixelData(opts) {
/*
* This algorithm was published in a paper authored by by Harold W.
* Thimbleby, Stuart Inglis, and Ian H. Witten. The following code was
* translated from the C code that was featured in the article.
* http://www.cs.sfu.ca/CourseCentral/414/li/material/refs/SIRDS-Computer-94.pdf
*/

var x,
y,
i,
left,
right,
visible,
t,
zt,
k,
sep,
z,
pixelOffset,
rgba,
width = opts.width,
height = opts.height,
numColors = opts.colors.length,
same, // points to a pixel to the right
dpi = 72, // assuming output of 72 dots per inch
eyeSep = Math.round(2.5 * dpi), // eye separation assumed to be 2.5 inches
mu = 1 / 3, // depth of field (fraction of viewing distance)
imdata = opts.imdata, // 👈 input image
pixels = opts.pixels.data; // 👈 output image

// for each row
for (y = 0; y < height; y++) {
// max image width (for Uint16Array) is 65536
same = new Uint16Array(width); // points to a pixel to the right

for (x = 0; x < width; x++) {
same[x] = x; // each pixel is initially linked with itself
}

// for each column
for (x = 0; x < width; x++) {
z = imdata.data[4 * (x + width * y)] / 255; // (red channel)
if (reverse) z = 1 - z;

// stereo separation corresponding to z
sep = Math.round(((1 - mu * z) * eyeSep) / (2 - mu * z));

// x-values corresponding to left and right eyes
left = Math.round(x - (sep + (sep & y & 1)) / 2);
right = left + sep;

if (0 <= left && right < width) {
// remove hidden surfaces
t = 1;
do {
zt = z + (2 * (2 - mu * z) * t) / (mu * eyeSep);
visible =
imdata.data[4 * (x - t + width * y)] < zt &&
imdata.data[4 * (x + t + width * y)] < zt; // false if obscured
t++;
} while (visible && zt < 1);

if (visible) {
// record that left and right pixels are the same
for (k = same[left]; k !== left && k !== right; k = same[left]) {
if (k < right) {
left = k;
} else {
left = right;
right = k;
}
}
same[left] = right;
}
}
}

for (x = width - 1; x >= 0; x--) {
pixelOffset = y * width * 4 + x * 4;
if (same[x] === x) {
// set random color
rgba = opts.colors[Math.floor(Math.random() * numColors)];
for (i = 0; i < 4; i++) {
pixels[pixelOffset + i] = rgba[i];
}
} else {
// constrained pixel, obey constraint
pixelOffset = y * width * 4 + x * 4;
for (i = 0; i < 4; i++) {
pixels[pixelOffset + i] = pixels[y * width * 4 + same[x] * 4 + i];
}
}
}
}
}
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