Published
Edited
Oct 1, 2022
Importers
26 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
density = (data, xBins, colorRamp, min, max) => {
// Start the timer. Faster with the console closed, by the way – at least in Chrome.
const timeLabel = `${comma(numPoints)} data points`;
console.time(timeLabel);

// Free all pre-existing WASM-side allocations
alloc.reset();

// Allocate a buffer for bins and initialize it to zero.
const bins = alloc(Float64Array, xBins).fill(0);

// Allocate a buffer for the data and initialize it.
const xs = alloc(Float64Array, data.length);
xs.set(data);

if (min !== undefined && max !== undefined) {
// Bin the points, counting the number of points per bin in the range (min, max).
// prettier-ignore
zig.density1dMinMax(bins.byteOffset, bins.length, xs.byteOffset, xs.length, min, max );
} else {
// Bin the points, computing the min and max from the data instead:
zig.density1d(bins.byteOffset, bins.length, xs.byteOffset, xs.length);
}

// Allocate a buffer for what will become the canvas image data.
const imgData = alloc(Uint8ClampedArray, 4 * bins.length);

// Allocate a buffer for the color ramp and initialize it.
const ramp = alloc(Uint8ClampedArray, colorRamp.length);
ramp.set(colorRamp);

// Compute the image data based on the bins and linear ramp.
zig.binsToImgData(
imgData.byteOffset,
bins.byteOffset,
bins.length,
ramp.byteOffset,
ramp.length / 4
);

// Transfer the data to the image and update the canvas.
image.data.set(imgData);
ctx.putImageData(image, 0, 0);

// Provide the raw bins as the value of this view.
// Note that these are in WASM memory.
ctx.canvas.value = bins;

console.timeEnd(timeLabel);
return ctx.canvas;
}
Insert cell
instance = {
const request = fetch(await FileAttachment("density@1.wasm").url());
const prom = WebAssembly.instantiateStreaming
? WebAssembly.instantiateStreaming(request)
: request
.then((res) => res.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes));
return prom.then((results) => {
// Ensure the WASM memory is large enough to store all of the data.
const instance = results.instance;
const memory = instance.exports.memory;
const bytesPerPage = 65536;
const numPages = memory.grow(0);
const initialBytes = numPages * bytesPerPage;
const desiredBytes =
Float64Array.BYTES_PER_ELEMENT * 1e6 * (maxMillions + 1);
const additionalPages = Math.ceil(
(desiredBytes - initialBytes) / bytesPerPage
);
memory.grow(additionalPages);
instance.initialBytes = initialBytes;
return instance;
});
}
Insert cell
zig = instance.exports
Insert cell
dataset = Float64Array.from({ length: maxMillions * 1e6 }, d3.randomNormal())
Insert cell
data = dataset.subarray(0, numPoints)
Insert cell
// canvas
Insert cell
ctx = {
let canvas = DOM.canvas(xBins, 1, 1);
let ctx = canvas.getContext("2d");
canvas.style.width = `${width}px`;
canvas.style.height = `${70}px`;
canvas.style.imageRendering = "pixelated";
ctx.imageSmoothingEnabled = false;
return ctx;
}
Insert cell
image = ctx.getImageData(0, 0, xBins, 1)
Insert cell
// utilities
Insert cell
Insert cell
colorRamp = {
const rampLen = 256;
const ramp = new Uint8Array(4 * rampLen);
const scale = interpolator;
for (let i = 0; i < rampLen; i++) {
const { r, g, b } = d3.rgb(scale(i / (rampLen - 1)));
ramp[4 * i] = r;
ramp[4 * i + 1] = g;
ramp[4 * i + 2] = b;
ramp[4 * i + 3] = 255;
}
return ramp;
}
Insert cell
// A simple memory allocator that allocates from the WebAssembly memory buffer.
alloc = {
// Pointer to the address of the next allocation.
// To prevent overwriting memory we don't control, we never allocate in
// the initial memory pages loaded by the runtime.
let ptr = instance.initialBytes;
return Object.assign(
(T, len) => {
// Align allocations to an 8-byte boundary.
if (ptr % 8 > 0) ptr = ptr + 8 - (ptr % 8);
const numBytes = T.BYTES_PER_ELEMENT * len;
const buffer = new T(instance.exports.memory.buffer, ptr, len);
ptr += numBytes;
return buffer;
},
{
reset: () => (ptr = instance.initialBytes)
}
);
}
Insert cell
comma = d3.format(",")
Insert cell
numPoints = 1e6 * millions
Insert cell
maxMillions = 10
Insert cell
wasmMemoryMb = zig.memory.buffer.byteLength / (1024 * 1024)
Insert cell
details = (summary, contents, isOpen) => {
return html`<details ${isOpen ? "open" : ""}>
${contents}
<summary>${summary}</summary>
</details>`;
}
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