64 lines
2.4 KiB
JavaScript
64 lines
2.4 KiB
JavaScript
/**
|
|
* 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);
|
|
}
|
|
});
|
|
}
|