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.
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');
let data = d3.range(1000)
.map(d => [Math.random()*width, Math.random()*height, Math.random()]);
let voronoi, workervoronoi;
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){
polygons.forEach( (d) => {
var c = d.data[2] ? color(d.data[2]) : 'white';
context.beginPath();
context.fillStyle = c;
drawCell(d);
context.fill();
});
workervoronoi.busy = false;
fps[1]++;
});
}
}
createWorker('worker.js', (worker) => {
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;
}
// this is for blockbuilder
function createWorker(s, cb) {
d3.queue()
.defer(d3.text, s)
.awaitAll(function (err, scripts) {
const worker = new Worker(window.URL.createObjectURL(new Blob(scripts, {
type: "text/javascript"
})));
if (typeof cb == 'function') {
cb(worker);
}
});
}
</script>
</body>
https://d3js.org/d3.v4.min.js