MCPcopy Create free account
hub / github.com/ScaleML/AgentSPEX / applyCompactGridLayout

Function applyCompactGridLayout

yaml-flow-editor/src/utils/layout.ts:453–536  ·  view source on GitHub ↗

* Place nodes in columns using greedy bin-packing by height budget. * Normal nodes (~80px) pack ~5 per column; oversized container nodes (while, for_each) * naturally get fewer neighbors or their own column, preventing one huge node from * stretching an entire column far beyond the others. * *

(
  nodes: FlowNode[],
  edges: FlowEdge[],
  direction: 'TB' | 'LR',
  nodeDimensions: Map<string, { width: number; height: number }>,
  gap: number,
  margin: number
)

Source from the content-addressed store, hash-verified

451 * Order = topo order; columns filled left-to-right (TB) or top-to-bottom (LR).
452 */
453function applyCompactGridLayout(
454 nodes: FlowNode[],
455 edges: FlowEdge[],
456 direction: 'TB' | 'LR',
457 nodeDimensions: Map<string, { width: number; height: number }>,
458 gap: number,
459 margin: number
460): FlowNode[] {
461 const n = nodes.length;
462 if (n === 0) return nodes;
463 const orderedIds = topoOrderIds(nodes, edges);
464 const nodeById = new Map<string, FlowNode>();
465 nodes.forEach((node) => nodeById.set(node.id, node));
466 const orderedNodes = orderedIds.map((id) => nodeById.get(id)!).filter(Boolean);
467 if (orderedNodes.length !== n) return nodes;
468
469 // Stacking dimension helpers (TB: stack vertically, LR: stack horizontally)
470 const mainSize = (node: FlowNode) => {
471 const dim = nodeDimensions.get(node.id) || { width: DEFAULT_NODE_WIDTH, height: DEFAULT_NODE_HEIGHT };
472 return direction === 'TB' ? dim.height : dim.width;
473 };
474 const crossSize = (node: FlowNode) => {
475 const dim = nodeDimensions.get(node.id) || { width: DEFAULT_NODE_WIDTH, height: DEFAULT_NODE_HEIGHT };
476 return direction === 'TB' ? dim.width : dim.height;
477 };
478
479 // Height budget per column: roughly 5 normal-sized nodes.
480 // Oversized nodes consume more budget → fewer nodes in that column.
481 const maxGroupMain = 5 * (COMPACT_MAX_ROW_HEIGHT + gap);
482
483 // Greedy bin-packing: fill each column until the next node would exceed the budget
484 const groups: number[][] = [[]];
485 let currentMain = 0;
486
487 for (let i = 0; i < orderedNodes.length; i++) {
488 const nodeMain = mainSize(orderedNodes[i]);
489 const added = groups[groups.length - 1].length > 0 ? gap + nodeMain : nodeMain;
490
491 if (groups[groups.length - 1].length > 0 && currentMain + added > maxGroupMain) {
492 // Start a new column
493 groups.push([i]);
494 currentMain = nodeMain;
495 } else {
496 groups[groups.length - 1].push(i);
497 currentMain += added;
498 }
499 }
500
501 // Column cross-axis widths (actual dimensions, no cap)
502 const numGroups = groups.length;
503 const groupCrossWidths: number[] = new Array(numGroups).fill(0);
504 for (let g = 0; g < numGroups; g++) {
505 for (const i of groups[g]) {
506 groupCrossWidths[g] = Math.max(groupCrossWidths[g], crossSize(orderedNodes[i]));
507 }
508 }
509
510 // Cumulative cross-axis positions for each column

Callers 1

getLayoutedElementsFunction · 0.85

Calls 4

topoOrderIdsFunction · 0.85
mainSizeFunction · 0.85
crossSizeFunction · 0.85
getMethod · 0.80

Tested by

no test coverage detected