Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active Dec 17, 2016
Embed
What would you like to do?
d3 Voronoi Worker, quantized [UNLISTED]
license: mit

This block implements two independent processes:

  • one that computes the new values of data (by moving them randomly around)

  • one that asks a javascript worker to compute the voronoi tesselation of the data, and draws the polygons on callback

A SVG overlay tracks the mouse: it stays responsive even if the voronoi calculation is sluggish.

We watch the performance of both processes and display their fps rates. Tweak the data range and time intervals to see how they relate.

forked from Fil's block: d3 Voronoi Worker

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
div#fps,svg { position: fixed; top:0; left:0; color: white; }
</style>
</head>
<body>
<canvas width=960 height=500 />
<script>
const canvas = d3.select("canvas"),
width = canvas.attr('width'),
height = canvas.attr('height'),
context = canvas.node().getContext("2d"),
color = d3.scaleLinear().range(["brown", "steelblue"]);
const tracker = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "none")
.append('circle')
.attr("r", 10)
.attr("stroke", "white")
.attr("fill", "none");
const fpsdiv = d3.select('body').append('div').attr('id','fps');
const quant = d3.scaleQuantize().range(d3.range(21).map(d => 0.5/21+d/21));
let data = d3.range(4000)
.map(d => [Math.random()*width, Math.random()*height, quant(Math.random())]);
data = data.sort((a,b) => d3.ascending(a[2], b[2]));
let voronoi;
let fps = [0,0,0],
last = performance.now();
setInterval(() => {
fps[2]++;
let now = performance.now();
fps = fps.map(d => d * Math.pow(0.9, (now-last)/1000));
//fpsdiv.html('fps: ' + fps.map(d => Math.round(d * 10 * 5 / fps[2])/10));
fpsdiv.html('FPS<br>data: ' + Math.round(fps[0] * 10 * 5 / fps[2])/10 + '<br>' + 'image: ' + Math.round(fps[1] * 10 * 5 / fps[2])/10);
last = now;
}, 200);
d3.interval(() => {
data.forEach((d,i) => {
if (i===0) return;
data[i][0] += Math.random() - Math.random();
data[i][1] += Math.random() - Math.random();
});
fps[0]++;
}, 100);
canvas.on('mousemove', function() {
data[0] = d3.mouse(this);
tracker
.attr('cx', data[0][0])
.attr('cy', data[0][1]);
});
d3.interval(draw, 100);
let radius = 10;
function draw() {
if (typeof workervoronoi == 'function' && !workervoronoi.busy) {
workervoronoi.busy = true;
workervoronoi(data, function(polygons){
context.beginPath();
var nc = null;
polygons.forEach( (d) => {
var c = d.data[2] ? color(d.data[2]) : 'white';
if (c != nc) {
context.fill();
context.beginPath();
context.fillStyle = c;
}
drawCell(d);
});
context.fill();
workervoronoi.busy = false;
fps[1]++;
});
}
}
d3.queue()
.defer(d3.text, 'https://d3js.org/d3.v4.min.js')
.defer(d3.text, 'worker.js')
.await(function (err, t, w) {
const worker = new Worker(window.URL.createObjectURL(new Blob([t + w], {
type: "text/javascript"
})));
workervoronoi = function(data, callback){
worker.postMessage({
data: data,
extent: [[-1,-1], [width+1, height+1]],
});
worker.onmessage = function (e) {
if (e.data.polygons) {
callback(e.data.polygons);
}
}
}
});
function drawCell(cell) {
if (!cell) return false;
context.moveTo(cell[0][0], cell[0][1]);
for (var j = 1, m = cell.length; j < m; ++j) {
context.lineTo(cell[j][0], cell[j][1]);
}
context.closePath();
return true;
}
</script>
</body>
self.onmessage = function(e){
let msg = e.data;
let voronoi = d3.voronoi().extent(msg.extent);
self.postMessage({
polygons: voronoi.polygons(msg.data)
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment