worker = {
const workerData = {
nodes: [],
links: []
};
const worker = createWorker('force-layout', workerData, function onInit(self, data) {
self.importScripts('https://d3js.org/d3.v5.min.js');
const sim = d3.forceSimulation()
.force('link', d3.forceLink().id(d => d.id))
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(0, 0));
sim.stop();
let positions, index;
const updateData = (nodes, links) => {
let i = -1;
while(data.nodes[++i] && nodes[i]) {
nodes[i].x = data.nodes[i].x;
nodes[i].y = data.nodes[i].y;
nodes[i].fx = data.nodes[i].fx;
nodes[i].fy = data.nodes[i].fy;
}
sim.nodes(data.nodes = nodes);
sim.force('link').links(data.links = links);
positions = new Float32Array(data.nodes.length * 2);
index = {};
data.nodes.forEach((node, i) => { index[node.id] = node; });
};
updateData(data.nodes, data.links);
const setFixed = (id, fx, fy) => {
index[id].fx = fx;
index[id].fy = fy;
};
const processMessage = (msg, ctx) => {
const command = msg[0];
const args = msg.slice(1);
switch(command) {
case 'update': {
for(let i = 0; i < data.nodes.length; i++) {
positions[i + i] = data.nodes[i].x;
positions[i + i + 1] = data.nodes[i].y;
}
return self.postMessage(['update', positions]);
};
case 'pin': return setFixed(args[0], args[1], args[2]);
case 'release': return setFixed(args[0], null, null);
case 'get': return self.postMessage([ 'get', args[0], ctx[args[0]]() ]);
case 'data': return updateData(args[0], args[1]);
case 'reheat': {
sim.alpha(Math.max(sim.alpha(), args[0]));
return sim.restart();
}
}
return ctx[command].apply(ctx, args);
}
self.onmessage = function(e) {
let ctx = sim, data = e.data;
if(!Array.isArray(data[0])) {
data = [data];
}
while(data.length) {
ctx = processMessage.call(null, data.shift(), ctx);
}
}
});
return worker;
}