Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active December 17, 2016 23:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fil/2a2ee6fa246d125eb51ed65525682e55 to your computer and use it in GitHub Desktop.
Save Fil/2a2ee6fa246d125eb51ed65525682e55 to your computer and use it in GitHub Desktop.
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