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
xxxxxxxxxx
<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>
https://d3js.org/d3.v4.min.js