Snapshot of main at 9f39a6e1e (2026-11-02)

This commit is contained in:
Ronald Huynen committed 2026-11-02 10:41:47 +01:00
commit 6ccf1792d7
2580 files changed
+1040202

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
/**
* The trading-network layout, as a pure function of nodes and edges.
*
* Kept apart from the page so it can run in a Web Worker (see
* network-layout-worker.js). fcose runs synchronously: on the ~1200-profile
* production graph it blocks ~4.5s on a desktop and several times that on a
* phone, where iOS stops a script that runs too long in one go - the graph
* then stayed on "Arranging N profiles..." for good. In a worker the page's
* own thread never blocks, whatever the device.
*
* Profiled on the real graph: ~70% of the time is fcose's repulsion and
* overlap maths, and numIter makes no difference (250 or 2500, same time,
* same quality) - so moving it off the main thread is the fix, not tuning.
*/
/**
* @param {Function} cytoscape The cytoscape factory, with fcose registered.
* @param {object} input {nodes: [{id, size}], edges: [{id, source, target}], options}
* @returns {Promise<Object<string, {x: number, y: number}>>} positions by node id
*/
export function layoutPositions(cytoscape, { nodes, edges, options }) {
return new Promise((resolve, reject) => {
let cy = null;
try {
cy = cytoscape({
headless: true,
// Needed headless for node sizes to count: fcose keeps nodes
// apart by their real dimensions, so a layout computed on
// default-sized nodes would not match the drawing.
styleEnabled: true,
style: [{ selector: 'node', style: { width: 'data(size)', height: 'data(size)' } }],
elements: [
...nodes.map((n) => ({ data: { id: n.id, size: n.size || 8 } })),
...edges.map((e) => ({ data: { id: e.id, source: e.source, target: e.target } })),
],
});
const layout = cy.layout({ ...options, animate: false });
layout.one('layoutstop', () => {
const positions = {};
cy.nodes().forEach((node) => {
const p = node.position();
positions[node.id()] = { x: p.x, y: p.y };
});
cy.destroy();
resolve(positions);
});
layout.run();
} catch (error) {
if (cy && !cy.destroyed()) {
cy.destroy();
}
reject(error);
}
});
}