Published
Edited
Feb 18, 2022
1 star
Insert cell
# Canvas infinite pan
Insert cell
voronoiAlgo = function(gens, nb) {3
let min = +Infinity;
let index = 0;
const { x, y } = this.thread;
const { x: dim1, y: dim2 } = this.output;
const [hd1, hd2] = [dim1 / 2, dim2 / 2];

for (let k = 0; k < nb; k++) {
const [gx, gy] = gens[k];
let [dx, dy] = [x - gx, y - gy];
// the following 2 lines ensure the sample
// cycles seamlessly through its borders
dx += dx > hd1 ? -dim1 : dx < -hd1 ? dim1 : 0;
dy += dy > hd2 ? -dim2 : dy < -hd2 ? dim2 : 0;
const sqdist = dx * dx + dy * dy;
if (sqdist < min) {
index = k;
min = sqdist;
}
}

return index;
}
Insert cell
voronoiKernel = {
const gpu = new GPU.GPU();
const options = { dynamicOutput: true, dynamicArguments: true };
return gpu.createKernel(voronoiAlgo, options);
}
Insert cell
output = [800, 400]
Insert cell
renderKernel = {
const gpu = new GPU.GPU();
// Old-style declaration necessary for kernel injection
function xs(coord, size) {
return Math.floor(coord - size * Math.floor(coord / size));
}
return sharedGraphicalKernel(gpu, renderAlgo, { output, functions: [xs] });
}
Insert cell
renderAlgo = {
let xs; // Allows injecting function in kernel while not pissing Observable's parser
return function(sample, dims, viewbox, colors) {
const [dim1, dim2] = dims;
const [xmin, ymin, scale] = viewbox;
const { x, y } = this.thread; // canvas space
const [X, Y] = [xmin + x * scale, ymin - y * scale]; // user space
const [I, J] = [xs(X, dim1), xs(Y, dim2)]; // sample space
const [r, g, b] = colors[sample[J][I]];
this.color(r, g, b);
};
}
Insert cell
vtess = function(dims, colors) {
let voronoi;
let viewbox = [[0, 0], [dims[0] * 3, dims[1] * 3]];
const [W, H] = output;
const div = html`<div style="background-color:red; width: ${W}px; height: ${H}px">`;
const render = renderKernel(div);
const vb = ([[xmin, ymin], [xmax, ymax]]) => [xmin, ymax, (xmax - xmin) / W];
const update = () => render(voronoi, dims, vb(viewbox), colors);
const updateView = vb => ((viewbox = vb), voronoi && update());
zoomAndPan2D(div, output, viewbox, updateView);
return generators => {
voronoiKernel.setOutput(dims);
update((voronoi = voronoiKernel(generators, generators.length)));
return div;
};
}
Insert cell
set2 = [[0, 0], [128, 221], [256, 0], [384, 221]]
Insert cell
greyScale = nb => {
const grey = val => [val, val, val];
return [...Array(nb)].map((_, i) => grey(i / (nb - 1)));
}
Insert cell
vtess([512, 512], greyScale(1))(set2)
Insert cell
Insert cell
import { SVG } from "@ehouais/svg-utility"
Insert cell
import { zoomAndPan2D } from "@ehouais/zoom-and-pan"
Insert cell
GPU = require("gpu.js")
Insert cell
import { sharedGraphicalKernel } from "@ehouais/shared-gpu-js-graphical-shader"
Insert cell
genView = dims => {
const { svg, rect, circle, viewbox } = SVG;
const r = rect(dims, { fill: 'none', stroke: 'grey' });
const radius = Math.max(...dims) / 50;
const toPoint = ([cx, cy]) => circle(radius, { cx, cy });
const viewBox = viewbox(-10, -10, dims[0] + 10, dims[1] + 10);
return gens => svg([200, 200], [r, ...gens.map(toPoint)], { viewBox });
}
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