@weasel-js/diagram 1.15.0 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +114 -5
  2. package/dist/{chunk-GR2DK4S2.js → chunk-6YQSQXQT.js} +1485 -266
  3. package/dist/chunk-6YQSQXQT.js.map +1 -0
  4. package/dist/{chunk-FDCODMFW.js → chunk-7BV2MBED.js} +906 -606
  5. package/dist/chunk-7BV2MBED.js.map +1 -0
  6. package/dist/{chunk-IYUU5KUY.js → chunk-FQVZWS24.js} +11 -101
  7. package/dist/chunk-FQVZWS24.js.map +1 -0
  8. package/dist/{chunk-AXL4JQBM.js → chunk-LPTNAYER.js} +3 -3
  9. package/dist/{chunk-AXL4JQBM.js.map → chunk-LPTNAYER.js.map} +1 -1
  10. package/dist/{chunk-DE7UILJA.js → chunk-QGL6QJE4.js} +183 -23
  11. package/dist/chunk-QGL6QJE4.js.map +1 -0
  12. package/dist/{dataTypes-BIkhAOc4.d.ts → dataTypes-1ch6cMtq.d.ts} +12 -3
  13. package/dist/effect-audio.d.ts +2 -2
  14. package/dist/effect-audio.js +7 -11
  15. package/dist/effect-audio.js.map +1 -1
  16. package/dist/effect.d.ts +5 -5
  17. package/dist/effect.js +13 -20
  18. package/dist/effect.js.map +1 -1
  19. package/dist/icons.table.js +44 -0
  20. package/dist/icons.table.js.map +1 -1
  21. package/dist/icons.table.json +44 -0
  22. package/dist/index.d.ts +932 -37
  23. package/dist/index.js +707 -34
  24. package/dist/index.js.map +1 -1
  25. package/dist/{labelTrait-BBjRgxbz.d.ts → labelTrait-moIciyXr.d.ts} +4 -0
  26. package/dist/layout.d.ts +3 -3
  27. package/dist/{layout.entry-CbmGuqE-.d.ts → layout.entry-zstuljAl.d.ts} +2 -2
  28. package/dist/layout.js +2 -2
  29. package/dist/{stageTypes-UiO25hcn.d.ts → stageTypes-B7oF8Lww.d.ts} +1 -1
  30. package/dist/{types-B0n2CNTW.d.ts → types-D5ybp8Xh.d.ts} +32 -1
  31. package/package.json +12 -11
  32. package/dist/chunk-DE7UILJA.js.map +0 -1
  33. package/dist/chunk-FDCODMFW.js.map +0 -1
  34. package/dist/chunk-GR2DK4S2.js.map +0 -1
  35. package/dist/chunk-IYUU5KUY.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { boundsOfPath, transformPath, ellipsePath, polylineFromPoints, pathFromD, worldCoords, PATH_M as PATH_M$1, PATH_CMD_LENGTHS, measurePath, forEachSegment, PATH_L as PATH_L$1, nearestOnLine, PATH_Q as PATH_Q$1, nearestOnQuadratic, PATH_C as PATH_C$1, nearestOnCubic } from '@weasel-js/geom';
1
+ import { boundsOfPath, transformPath, ellipsePath, pathFromD, polylineFromPoints, worldCoords, PATH_M as PATH_M$1, PATH_CMD_LENGTHS, measurePath, forEachSegment, PATH_L as PATH_L$1, nearestOnLine, PATH_Q as PATH_Q$1, nearestOnQuadratic, PATH_C as PATH_C$1, nearestOnCubic } from '@weasel-js/geom';
2
2
  import { PathBuilder, rectPath, polygonFromPoints, AUTO_POSE_DESCRIPTOR, rotatePoint, PATH_M, PATH_L, PATH_C, flattenCubic, PATH_Q, PATH_Z, pointAlongPath } from '@weasel-js/core/math';
3
3
  import { cssFamilyName } from '@weasel-js/font/metrics';
4
4
  import { createSimulation } from '@weasel-js/simulation';
@@ -372,7 +372,34 @@ var doubleEllipse = {
372
372
  },
373
373
  boxForContent: (c) => ({ width: c.width * Math.SQRT2 + RING * 2, height: c.height * Math.SQRT2 + RING * 2 })
374
374
  };
375
- var FIGURE_OUTLINES = { roundRect, triangle, octagon, cloud, actor, doubleEllipse };
375
+ var HEAD = 0.24;
376
+ var NECK = 0.04;
377
+ var SHOULDER = 18;
378
+ var FOOT = 6;
379
+ function personParts({ y, width: w, height: h }) {
380
+ const d = Math.min(HEAD * h, 0.4 * w);
381
+ const top = y + d + NECK * h;
382
+ const body = y + h - top;
383
+ return { d, top, body, shoulder: Math.min(SHOULDER, w / 4, body / 2), foot: Math.min(FOOT, w / 6, body / 4) };
384
+ }
385
+ var person = {
386
+ path: (b) => {
387
+ const { x, y, width: w, height: h } = b;
388
+ const { d, top, shoulder: r, foot: f } = personParts(b);
389
+ const k = r * KAPPA;
390
+ const q = f * KAPPA;
391
+ const p = new PathBuilder().moveTo(x + r, top).lineTo(x + w - r, top).curveTo(x + w - r + k, top, x + w, top + r - k, x + w, top + r).lineTo(x + w, y + h - f).curveTo(x + w, y + h - f + q, x + w - f + q, y + h, x + w - f, y + h).lineTo(x + f, y + h).curveTo(x + f - q, y + h, x, y + h - f + q, x, y + h - f).lineTo(x, top + r).curveTo(x, top + r - k, x + r - k, top, x + r, top).close().moveTo(x + w / 2 + d / 2, y + d / 2);
392
+ arc(p, x + w / 2, y + d / 2, d / 2, 0, Math.PI * 2);
393
+ return p.close().build();
394
+ },
395
+ content: (b) => {
396
+ const { top, body, shoulder } = personParts(b);
397
+ const i = shoulder * BITE;
398
+ return { x: b.x + i, y: top + i, width: b.width - i * 2, height: body - i * 2 };
399
+ },
400
+ boxForContent: (c) => ({ width: c.width + SHOULDER * BITE * 2, height: (c.height + SHOULDER * BITE * 2) / (1 - HEAD - NECK) })
401
+ };
402
+ var FIGURE_OUTLINES = { roundRect, triangle, octagon, cloud, actor, doubleEllipse, person };
376
403
 
377
404
  // src/outlines/bpmn.ts
378
405
  var STRIP = 32;
@@ -474,6 +501,10 @@ var globe = device(
474
501
  ring2(0.5, 0.6, 0.4),
475
502
  "M0.1 0.6H0.9M0.1536 0.4H0.8464M0.1536 0.8H0.8464M0.5 0.2V1M0.5 0.2A0.17 0.4 0 0 0 0.5 1A0.17 0.4 0 0 0 0.5 0.2"
476
503
  );
504
+ var disk = device(
505
+ "M0.1 0.42H0.9Q0.94 0.42 0.94 0.46V0.96Q0.94 1 0.9 1H0.1Q0.06 1 0.06 0.96V0.46Q0.06 0.42 0.1 0.42Z",
506
+ `${ring2(0.42, 0.71, 0.22)}${ring2(0.42, 0.71, 0.03)}${ring2(0.8, 0.53, 0.03)}M0.78 0.55L0.5 0.8`
507
+ );
477
508
  var NETWORK_OUTLINES = {
478
509
  server,
479
510
  router,
@@ -484,7 +515,8 @@ var NETWORK_OUTLINES = {
484
515
  phone,
485
516
  printer,
486
517
  user,
487
- globe
518
+ globe,
519
+ disk
488
520
  };
489
521
  var ICON = { width: 14, height: 12, tab: { width: 6, height: 3 }, margin: 6 };
490
522
  var ICON_SIDE = ICON.margin * 2 + ICON.width + ICON.tab.width / 2;
@@ -520,6 +552,88 @@ var doubleRect = {
520
552
  boxForContent: (c) => ({ width: c.width + GAP * 2, height: c.height + GAP * 2 })
521
553
  };
522
554
  var MODELING_OUTLINES = { component, doubleRect };
555
+ function square({ x, y, width: w, height: h }) {
556
+ const side = Math.min(w, h);
557
+ return { x: x + (w - side) / 2, y: y + (h - side) / 2, width: side, height: side };
558
+ }
559
+ function scaled(b, k) {
560
+ const w = b.width * k;
561
+ const h = b.height * k;
562
+ return { x: b.x + (b.width - w) / 2, y: b.y + (b.height - h) / 2, width: w, height: h };
563
+ }
564
+ var BULLSEYE = 0.55;
565
+ var BAR = 6;
566
+ var circle = (b) => ellipsePath(square(b));
567
+ var start = {
568
+ path: circle,
569
+ solid: circle
570
+ };
571
+ var end = {
572
+ path: circle,
573
+ solid: (b) => ellipsePath(scaled(square(b), BULLSEYE))
574
+ };
575
+ function barOf({ x, y, width: w, height: h }) {
576
+ if (w >= h) {
577
+ const t2 = Math.min(BAR, h);
578
+ return { x, y: y + (h - t2) / 2, width: w, height: t2 };
579
+ }
580
+ const t = Math.min(BAR, w);
581
+ return { x: x + (w - t) / 2, y, width: t, height: h };
582
+ }
583
+ var bar = {
584
+ path: (b) => {
585
+ const r = barOf(b);
586
+ return rectPath(r.x, r.y, r.width, r.height);
587
+ },
588
+ solid: (b) => {
589
+ const r = barOf(b);
590
+ return rectPath(r.x, r.y, r.width, r.height);
591
+ }
592
+ };
593
+ var STATE_OUTLINES = { start, end, bar };
594
+ var SHARE2 = 0.75;
595
+ var ring3 = (cx, cy, rx, ry) => `M${cx + rx} ${cy}A${rx} ${ry} 0 1 1 ${cx - rx} ${cy}A${rx} ${ry} 0 1 1 ${cx + rx} ${cy}Z`;
596
+ var boundary = figureOver(outlineFromD(`${ring3(0.7, 0.5, 0.4 / 1.5, 0.4)}M0.04 0.1V0.9M0.04 0.5H${0.7 - 0.4 / 1.5}`), 1.5, SHARE2);
597
+ var control = figureOver(outlineFromD(ring3(0.5, 0.55, 0.42, 0.42), { detail: "M0.6 0.02L0.46 0.13L0.6 0.24" }), 1, SHARE2);
598
+ var entity = figureOver(outlineFromD(`${ring3(0.5, 0.45, 0.42, 0.42)}M0.08 0.97H0.92`), 1, SHARE2);
599
+ var offsetOf = (w, h) => Math.min(6, w / 8, h / 8);
600
+ var collections = {
601
+ path: ({ x, y, width: w, height: h }) => {
602
+ const o = offsetOf(w, h);
603
+ return new PathBuilder().moveTo(x + o, y).lineTo(x + w, y).lineTo(x + w, y + h - o).lineTo(x + w - o, y + h - o).lineTo(x + w - o, y + h).lineTo(x, y + h).lineTo(x, y + o).lineTo(x + o, y + o).close().build();
604
+ },
605
+ detail: ({ x, y, width: w, height: h }) => {
606
+ const o = offsetOf(w, h);
607
+ return new PathBuilder().moveTo(x + o, y + o).lineTo(x + w - o, y + o).lineTo(x + w - o, y + h - o).build();
608
+ },
609
+ content: ({ x, y, width: w, height: h }) => {
610
+ const o = offsetOf(w, h);
611
+ return { x, y: y + o, width: w - o, height: h - o };
612
+ },
613
+ boxForContent: (c) => ({ width: c.width + 6, height: c.height + 6 })
614
+ };
615
+ var capOf2 = (w, h) => Math.min(w * 0.12, h * 0.3);
616
+ var queue = {
617
+ path: ({ x, y, width: w, height: h }) => {
618
+ const r = capOf2(w, h);
619
+ const cy = y + h / 2;
620
+ const ry = h / 2;
621
+ return new PathBuilder().moveTo(x + r, y).lineTo(x + w - r, y).curveTo(x + w - r + r * KAPPA, y, x + w, cy - ry * KAPPA, x + w, cy).curveTo(x + w, cy + ry * KAPPA, x + w - r + r * KAPPA, y + h, x + w - r, y + h).lineTo(x + r, y + h).curveTo(x + r - r * KAPPA, y + h, x, cy + ry * KAPPA, x, cy).curveTo(x, cy - ry * KAPPA, x + r - r * KAPPA, y, x + r, y).close().build();
622
+ },
623
+ detail: ({ x, y, width: w, height: h }) => {
624
+ const r = capOf2(w, h);
625
+ const cy = y + h / 2;
626
+ const ry = h / 2;
627
+ const rim = x + w - r;
628
+ return new PathBuilder().moveTo(rim, y).curveTo(rim - r * KAPPA, y, rim - r, cy - ry * KAPPA, rim - r, cy).curveTo(rim - r, cy + ry * KAPPA, rim - r * KAPPA, y + h, rim, y + h).build();
629
+ },
630
+ content: ({ x, y, width: w, height: h }) => {
631
+ const r = capOf2(w, h);
632
+ return { x: x + r, y, width: Math.max(w - r * 3, 0), height: h };
633
+ },
634
+ boxForContent: (c) => ({ width: c.width / 0.64, height: c.height })
635
+ };
636
+ var PARTICIPANT_OUTLINES = { boundary, control, entity, collections, queue };
523
637
 
524
638
  // src/outlines/table.ts
525
639
  var BUILTINS = {
@@ -528,7 +642,9 @@ var BUILTINS = {
528
642
  ...STORAGE_OUTLINES,
529
643
  ...NETWORK_OUTLINES,
530
644
  ...BPMN_OUTLINES,
531
- ...MODELING_OUTLINES
645
+ ...MODELING_OUTLINES,
646
+ ...STATE_OUTLINES,
647
+ ...PARTICIPANT_OUTLINES
532
648
  };
533
649
  var table = new Map(
534
650
  Object.entries(BUILTINS).map(([name, def]) => [name, [def]])
@@ -563,6 +679,10 @@ function outlineDetail(outline, bounds) {
563
679
  if (typeof outline !== "string" || bounds.width <= 0 || bounds.height <= 0) return null;
564
680
  return resolveOutline(outline).detail?.(bounds) ?? null;
565
681
  }
682
+ function outlineSolid(outline, bounds) {
683
+ if (typeof outline !== "string" || bounds.width <= 0 || bounds.height <= 0) return null;
684
+ return resolveOutline(outline).solid?.(bounds) ?? null;
685
+ }
566
686
  function contentBox(outline, bounds) {
567
687
  if (typeof outline !== "string") return bounds;
568
688
  return resolveOutline(outline).content?.(bounds) ?? bounds;
@@ -681,6 +801,7 @@ var dataKeyReader = (node) => {
681
801
  if (trait === null || typeof trait !== "object") return null;
682
802
  if ("from" in trait && "to" in trait) return null;
683
803
  if ("label" in trait || "anchor" in trait || "mark" in trait) return null;
804
+ if ("seq" in trait || "sequence" in trait) return null;
684
805
  if (trait.container?.role === "lane") return null;
685
806
  return trait;
686
807
  };
@@ -753,7 +874,7 @@ function placerOf(node, pose, opts) {
753
874
  const cx = bounds.x + bounds.width / 2;
754
875
  const cy = bounds.y + bounds.height / 2;
755
876
  const poly = trait.outline === void 0 || bounds.width <= 0 || bounds.height <= 0 ? null : outlinePolyline(trait.outline, bounds);
756
- const place = (spec, at2, given) => {
877
+ const place2 = (spec, at2, given) => {
757
878
  const anchored = {
758
879
  x: bounds.x + at2.u * bounds.width,
759
880
  y: bounds.y + at2.v * bounds.height
@@ -772,7 +893,7 @@ function placerOf(node, pose, opts) {
772
893
  ...extrasOf(spec)
773
894
  };
774
895
  };
775
- return { specs: trait.ports ?? DEFAULT_PORTS, place };
896
+ return { specs: trait.ports ?? DEFAULT_PORTS, place: place2 };
776
897
  }
777
898
  function extrasOf(spec) {
778
899
  return {
@@ -1271,10 +1392,10 @@ function packClusters(graph, rows, preferred2, axes, nodeGap) {
1271
1392
  }
1272
1393
  return items.map((item, i) => ({ item, i })).sort((a, b) => a.item.center - b.item.center || a.i - b.i).map(({ item }) => item);
1273
1394
  });
1274
- const settle2 = (byRow, start2, nodeFloor, origin) => {
1395
+ const settle2 = (byRow, start3, nodeFloor, origin) => {
1275
1396
  const nodeAt = /* @__PURE__ */ new Map();
1276
1397
  let ends2 = [];
1277
- for (let pass = 0; pass <= start2.size; pass++) {
1398
+ for (let pass = 0; pass <= start3.size; pass++) {
1278
1399
  let raised = false;
1279
1400
  ends2 = byRow.map((items) => {
1280
1401
  let cursor = origin;
@@ -1285,10 +1406,10 @@ function packClusters(graph, rows, preferred2, axes, nodeGap) {
1285
1406
  cursor = x2 + width(item.node) + nodeGap;
1286
1407
  continue;
1287
1408
  }
1288
- const s = start2.get(item.span);
1409
+ const s = start3.get(item.span);
1289
1410
  const x = Math.max(s, cursor);
1290
1411
  if (x > s) {
1291
- start2.set(item.span, x);
1412
+ start3.set(item.span, x);
1292
1413
  raised = true;
1293
1414
  }
1294
1415
  cursor = x + outer(item.span) + nodeGap;
@@ -1324,15 +1445,15 @@ function packClusters(graph, rows, preferred2, axes, nodeGap) {
1324
1445
  return blockOf(span).width + inset2.crossLo + inset2.crossHi;
1325
1446
  }
1326
1447
  const roots = spans.filter((s) => s.parent === void 0);
1327
- const start = new Map(roots.map((s) => [s, centerOf.get(s) - outer(s) / 2]));
1328
- const { nodeAt: at2 } = settle2(itemsByRow(void 0, roots), start, pref, -Infinity);
1329
- const place = (span, from) => {
1448
+ const start2 = new Map(roots.map((s) => [s, centerOf.get(s) - outer(s) / 2]));
1449
+ const { nodeAt: at2 } = settle2(itemsByRow(void 0, roots), start2, pref, -Infinity);
1450
+ const place2 = (span, from) => {
1330
1451
  const origin = from + insets.get(span).crossLo;
1331
1452
  const block = blockOf(span);
1332
1453
  for (const [id, local] of block.nodeAt) at2.set(id, origin + local);
1333
- for (const [child, local] of block.childAt) place(child, origin + local);
1454
+ for (const [child, local] of block.childAt) place2(child, origin + local);
1334
1455
  };
1335
- for (const root of roots) place(root, start.get(root));
1456
+ for (const root of roots) place2(root, start2.get(root));
1336
1457
  return at2;
1337
1458
  }
1338
1459
  function centersOf(spans, rows, center2) {
@@ -1426,24 +1547,24 @@ function subpathOf(path, index) {
1426
1547
  if (path.kind !== "polygon") return index === 0 ? path : null;
1427
1548
  const { commands, coords } = path;
1428
1549
  let seen = -1;
1429
- let start = -1;
1550
+ let start2 = -1;
1430
1551
  let startCoord = 0;
1431
1552
  let coord2 = 0;
1432
1553
  for (let i = 0; i <= commands.length; i++) {
1433
1554
  const cmd = commands[i];
1434
1555
  if (i === commands.length || cmd === PATH_M$1) {
1435
1556
  if (seen === index) {
1436
- if (start === 0 && i === commands.length) return path;
1557
+ if (start2 === 0 && i === commands.length) return path;
1437
1558
  return {
1438
1559
  kind: "polygon",
1439
1560
  fillRule: path.fillRule,
1440
- commands: commands.slice(start, i),
1561
+ commands: commands.slice(start2, i),
1441
1562
  coords: coords.slice(startCoord, coord2),
1442
1563
  ...path.origin ? { origin: path.origin } : {}
1443
1564
  };
1444
1565
  }
1445
1566
  seen++;
1446
- start = i;
1567
+ start2 = i;
1447
1568
  startCoord = coord2;
1448
1569
  }
1449
1570
  if (cmd !== void 0) coord2 += PATH_CMD_LENGTHS[cmd] ?? 0;
@@ -1503,7 +1624,12 @@ function labelCenter(whole, label, size) {
1503
1624
  const at2 = label.placed ?? label;
1504
1625
  const station = typeof at2.at === "object" ? nearestStation(path, at2.at.near) ?? pointAlongPath(path, 0.5) : pointAlongPath(path, typeof at2.at === "number" ? at2.at : NAMED[at2.at] ?? 0.5);
1505
1626
  if (station === null) return null;
1506
- return besideStation(station.point, station.tangent, at2.offset ?? 0, size);
1627
+ const beside = besideStation(station.point, station.tangent, at2.offset ?? 0, size);
1628
+ const end2 = label.placed !== void 0 || label.inset === void 0 ? 0 : label.at === "start" ? 1 : label.at === "end" ? -1 : 0;
1629
+ if (end2 === 0) return beside;
1630
+ const { x: tx, y: ty } = station.tangent;
1631
+ const reach = end2 * (label.inset + (Math.abs(tx) * size.width + Math.abs(ty) * size.height) / 2);
1632
+ return { x: beside.x + tx * reach, y: beside.y + ty * reach };
1507
1633
  }
1508
1634
  function halfDepth(tangent, size) {
1509
1635
  return (Math.abs(tangent.y) * size.width + Math.abs(tangent.x) * size.height) / 2;
@@ -1546,8 +1672,8 @@ function labelSpotAt(whole, center2, size) {
1546
1672
  }
1547
1673
 
1548
1674
  // src/edgeTrait.ts
1549
- function layoutOwnsPort(end) {
1550
- return end.port === void 0 || end.port === end.laidOut;
1675
+ function layoutOwnsPort(end2) {
1676
+ return end2.port === void 0 || end2.port === end2.laidOut;
1551
1677
  }
1552
1678
  function layoutOwnsRoute(edge) {
1553
1679
  const points = edge.waypoints ?? [];
@@ -1567,8 +1693,6 @@ function diagramEdgeOf(node) {
1567
1693
  if (edge === null || typeof edge !== "object") return null;
1568
1694
  return "from" in edge && "to" in edge ? edge : null;
1569
1695
  }
1570
-
1571
- // src/avoid/holds.ts
1572
1696
  var EPS2 = 1e-6;
1573
1697
  function runsAlong(d, normal, sign) {
1574
1698
  const len = Math.hypot(d.x, d.y) * Math.hypot(normal.x, normal.y);
@@ -1577,11 +1701,8 @@ function runsAlong(d, normal, sign) {
1577
1701
  return Math.abs(cross) <= EPS2 * len && sign * (d.x * normal.x + d.y * normal.y) > 0;
1578
1702
  }
1579
1703
  function onSegment(p, a, b) {
1580
- const dx = b.x - a.x;
1581
- const dy = b.y - a.y;
1582
- const len2 = dx * dx + dy * dy;
1583
- const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2));
1584
- return Math.hypot(a.x + t * dx - p.x, a.y + t * dy - p.y) <= EPS2 * Math.max(1, Math.sqrt(len2)) ? t : null;
1704
+ const { t, dist } = nearestOnLine(p.x, p.y, a.x, a.y, b.x, b.y);
1705
+ return dist <= EPS2 * Math.max(1, Math.hypot(b.x - a.x, b.y - a.y)) ? t : null;
1585
1706
  }
1586
1707
  function routeHolds(from, to, waypoints, route) {
1587
1708
  const points = [from.point, ...route, to.point];
@@ -1605,442 +1726,180 @@ function routeHolds(from, to, waypoints, route) {
1605
1726
  return true;
1606
1727
  }
1607
1728
 
1608
- // src/forceGroups.ts
1609
- function resolve(groups, byId) {
1610
- const out = [];
1611
- for (const group of groups) {
1612
- const members = group.members.map((id) => byId.get(id)).filter((b) => b !== void 0);
1613
- if (members.length > 0) out.push({ members, group, children: [] });
1614
- }
1615
- const byGroup = new Map(out.map((g) => [g.group.id, g]));
1616
- for (const g of out) {
1617
- const parent = g.group.parent === void 0 ? void 0 : byGroup.get(g.group.parent);
1618
- if (parent === void 0) continue;
1619
- g.parent = parent;
1620
- parent.children.push(g);
1621
- }
1622
- return out;
1729
+ // src/layered/cycles.ts
1730
+ function backEdges(graph) {
1731
+ const back = /* @__PURE__ */ new Set();
1732
+ const done = /* @__PURE__ */ new Set();
1733
+ const onStack = /* @__PURE__ */ new Set();
1734
+ const walk = (id) => {
1735
+ if (done.has(id)) return;
1736
+ onStack.add(id);
1737
+ for (const edge of graph.outgoing(id)) {
1738
+ if (edge.to === id) continue;
1739
+ if (onStack.has(edge.to)) back.add(edge.id);
1740
+ else walk(edge.to);
1741
+ }
1742
+ onStack.delete(id);
1743
+ done.add(id);
1744
+ };
1745
+ for (const node of graph.nodes) walk(node.id);
1746
+ return back;
1623
1747
  }
1624
- function groupCohesion(groups, byId, strength) {
1625
- const resolved = resolve(groups, byId);
1626
- return (alpha) => {
1627
- for (const { members } of resolved) {
1628
- let cx = 0;
1629
- let cy = 0;
1630
- for (const m of members) {
1631
- cx += m.x;
1632
- cy += m.y;
1633
- }
1634
- cx /= members.length;
1635
- cy /= members.length;
1636
- for (const m of members) {
1637
- m.vx += (cx - m.x) * strength * alpha;
1638
- m.vy += (cy - m.y) * strength * alpha;
1639
- }
1748
+ function ranksOf(graph, back) {
1749
+ const ranks = /* @__PURE__ */ new Map();
1750
+ const resolving = /* @__PURE__ */ new Set();
1751
+ const rankOf = (id) => {
1752
+ const known = ranks.get(id);
1753
+ if (known !== void 0) return known;
1754
+ if (resolving.has(id)) return 0;
1755
+ resolving.add(id);
1756
+ let rank = 0;
1757
+ for (const edge of graph.incoming(id)) {
1758
+ if (back.has(edge.id) || edge.from === id) continue;
1759
+ rank = Math.max(rank, rankOf(edge.from) + 1);
1640
1760
  }
1761
+ resolving.delete(id);
1762
+ ranks.set(id, rank);
1763
+ return rank;
1641
1764
  };
1765
+ for (const node of graph.nodes) rankOf(node.id);
1766
+ return ranks;
1642
1767
  }
1643
- function boxOf(g) {
1644
- let x0 = Infinity;
1645
- let y0 = Infinity;
1646
- let x1 = -Infinity;
1647
- let y1 = -Infinity;
1648
- for (const m of g.members) {
1649
- x0 = Math.min(x0, m.x - m.hw);
1650
- y0 = Math.min(y0, m.y - m.hh);
1651
- x1 = Math.max(x1, m.x + m.hw);
1652
- y1 = Math.max(y1, m.y + m.hh);
1768
+
1769
+ // src/route.ts
1770
+ var FACES = {
1771
+ down: { out: "s", in: "n" },
1772
+ up: { out: "n", in: "s" },
1773
+ right: { out: "e", in: "w" },
1774
+ left: { out: "w", in: "e" }
1775
+ };
1776
+ function layeredRoutes(input) {
1777
+ const { ranks, members, compass } = input;
1778
+ const faces = FACES[input.direction ?? "down"];
1779
+ const span = (id) => {
1780
+ const rs = (members.get(id) ?? [id]).map((m) => ranks.get(m)).filter((r) => r !== void 0);
1781
+ return rs.length === 0 ? null : [Math.min(...rs), Math.max(...rs)];
1782
+ };
1783
+ const routes = /* @__PURE__ */ new Map();
1784
+ for (const edge of input.edges) {
1785
+ const from = span(edge.from);
1786
+ const to = span(edge.to);
1787
+ const route = {};
1788
+ if (from !== null && to !== null && (from[1] < to[0] || to[1] < from[0])) {
1789
+ const ahead = from[1] < to[0];
1790
+ if (compass.has(edge.from)) route.fromPort = ahead ? faces.out : faces.in;
1791
+ if (compass.has(edge.to)) route.toPort = ahead ? faces.in : faces.out;
1792
+ }
1793
+ const waypoints = input.waypoints.get(edge.id);
1794
+ if (waypoints !== void 0 && waypoints.length > 0) {
1795
+ route.waypoints = waypoints;
1796
+ const label = input.labels?.get(edge.id);
1797
+ if (label !== void 0) route.labelAt = label;
1798
+ }
1799
+ if (route.fromPort !== void 0 || route.toPort !== void 0 || route.waypoints !== void 0) {
1800
+ routes.set(edge.id, route);
1801
+ }
1653
1802
  }
1654
- for (const child of g.children) {
1655
- const b = boxOf(child);
1656
- x0 = Math.min(x0, b.x0);
1657
- y0 = Math.min(y0, b.y0);
1658
- x1 = Math.max(x1, b.x1);
1659
- y1 = Math.max(y1, b.y1);
1803
+ return routes;
1804
+ }
1805
+ function layeredPorts(graph, ranks, direction) {
1806
+ const faces = FACES[direction ?? "down"];
1807
+ const compass = new Set(graph.groups?.map((g) => g.id) ?? []);
1808
+ for (const node of graph.nodes) {
1809
+ if (hasCompassPort(node, faces.out) && hasCompassPort(node, faces.in)) compass.add(node.id);
1660
1810
  }
1661
- const { inset: inset2 } = g.group;
1662
- return { x0: x0 - inset2.left, y0: y0 - inset2.top, x1: x1 + inset2.right, y1: y1 + inset2.bottom };
1811
+ const edges = /* @__PURE__ */ new Map();
1812
+ for (const e of graph.edges) {
1813
+ const id = e.origin?.id ?? e.id;
1814
+ if (!edges.has(id)) edges.set(id, { id, from: e.origin?.from ?? e.from, to: e.origin?.to ?? e.to });
1815
+ }
1816
+ const routes = layeredRoutes({
1817
+ edges: [...edges.values()],
1818
+ ranks,
1819
+ waypoints: /* @__PURE__ */ new Map(),
1820
+ members: new Map((graph.groups ?? []).map((g) => [g.id, g.members])),
1821
+ compass,
1822
+ ...direction !== void 0 ? { direction } : {}
1823
+ });
1824
+ return new Map([...routes].map(([id, r]) => [id, {
1825
+ ...r.fromPort !== void 0 ? { from: r.fromPort } : {},
1826
+ ...r.toPort !== void 0 ? { to: r.toPort } : {}
1827
+ }]));
1663
1828
  }
1664
- function clearance(a, b, padding) {
1665
- const gapX = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0) + padding;
1666
- const gapY = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0) + padding;
1667
- if (gapX <= 0 || gapY <= 0) return null;
1668
- const dx = b.x0 + b.x1 - (a.x0 + a.x1);
1669
- const dy = b.y0 + b.y1 - (a.y0 + a.y1);
1670
- return gapX < gapY ? { x: (Math.sign(dx) || 1) * gapX, y: 0 } : { x: 0, y: (Math.sign(dy) || 1) * gapY };
1829
+ function hasCompassPort(node, face) {
1830
+ const point = COMPASS[face];
1831
+ return (node.ports ?? DEFAULT_PORTS).some((p) => p.id === face && p.at.u === point.u && p.at.v === point.v);
1671
1832
  }
1672
- function holds(outer, inner) {
1673
- for (let at2 = inner.parent; at2 !== void 0; at2 = at2.parent) {
1674
- if (at2 === outer) return true;
1833
+ function authoredOver(edges, plan) {
1834
+ return new Map(edges.map((e) => {
1835
+ const ports = plan.ports?.get(e.id);
1836
+ const planned = plan.waypoints?.get(e.id);
1837
+ const fromPort = e.fromPort ?? ports?.from;
1838
+ const toPort = e.toPort ?? ports?.to;
1839
+ const waypoints = e.waypoints ?? (planned !== void 0 && planned.length > 0 ? planned : void 0);
1840
+ const labelAt = e.waypoints === void 0 && waypoints !== void 0 ? plan.labels?.get(e.id) : void 0;
1841
+ return [e.id, {
1842
+ ...fromPort ? { fromPort } : {},
1843
+ ...toPort ? { toPort } : {},
1844
+ ...waypoints ? { waypoints } : {},
1845
+ ...labelAt !== void 0 ? { labelAt } : {}
1846
+ }];
1847
+ }));
1848
+ }
1849
+
1850
+ // src/layered/rank.ts
1851
+ var GROUP_WEIGHT = 4;
1852
+ function ranksByLength(graph, back) {
1853
+ const index = /* @__PURE__ */ new Map();
1854
+ graph.nodes.forEach((node, i) => index.set(node.id, i));
1855
+ const arcs = [];
1856
+ for (const edge of graph.edges) {
1857
+ const from = index.get(edge.from);
1858
+ const to = index.get(edge.to);
1859
+ if (from === void 0 || to === void 0 || from === to) continue;
1860
+ arcs.push(back.has(edge.id) ? { tail: to, head: from, weight: 1, minlen: 1 } : { tail: from, head: to, weight: 1, minlen: 1 });
1675
1861
  }
1676
- return false;
1862
+ let count = graph.nodes.length;
1863
+ for (const group of graph.groups ?? []) {
1864
+ const members = [...new Set(group.members.map((id) => index.get(id)).filter((i) => i !== void 0))];
1865
+ if (members.length < 2) continue;
1866
+ const [top, bottom] = [count, count + 1];
1867
+ count += 2;
1868
+ for (const m of members) {
1869
+ arcs.push({ tail: top, head: m, weight: GROUP_WEIGHT, minlen: 0 });
1870
+ arcs.push({ tail: m, head: bottom, weight: GROUP_WEIGHT, minlen: 0 });
1871
+ }
1872
+ }
1873
+ const real = graph.nodes.length;
1874
+ const start2 = longestPath(count, arcs);
1875
+ const ranks = /* @__PURE__ */ new Map();
1876
+ for (const part of components(count, arcs)) {
1877
+ const rank = part.nodes.map((g) => start2[g]);
1878
+ simplex(part.arcs, rank);
1879
+ const own = part.nodes.flatMap((g, i) => g < real ? [i] : []);
1880
+ const min = Math.min(...own.map((i) => rank[i]));
1881
+ for (const i of own) ranks.set(graph.nodes[part.nodes[i]].id, rank[i] - min);
1882
+ }
1883
+ return ranks;
1677
1884
  }
1678
- function shove(bodies, x, y) {
1679
- for (const body of bodies) {
1680
- body.vx += x;
1681
- body.vy += y;
1885
+ function longestPath(count, arcs) {
1886
+ const rank = new Array(count).fill(0);
1887
+ const pending = new Array(count).fill(0);
1888
+ const out = Array.from({ length: count }, () => []);
1889
+ for (const arc2 of arcs) {
1890
+ pending[arc2.head]++;
1891
+ out[arc2.tail].push(arc2);
1892
+ }
1893
+ const ready = [];
1894
+ for (let i = 0; i < count; i++) if (pending[i] === 0) ready.push(i);
1895
+ for (let k = 0; k < ready.length; k++) {
1896
+ const u = ready[k];
1897
+ for (const { head: v, minlen } of out[u]) {
1898
+ rank[v] = Math.max(rank[v], rank[u] + minlen);
1899
+ if (--pending[v] === 0) ready.push(v);
1900
+ }
1682
1901
  }
1683
- }
1684
- function separateGroups(groups, bodies, byId, padding) {
1685
- const resolved = resolve(groups, byId);
1686
- const memberSets = resolved.map((g) => new Set(g.members));
1687
- return () => {
1688
- const boxes = resolved.map(boxOf);
1689
- for (let i = 0; i < resolved.length; i++) {
1690
- for (let j = i + 1; j < resolved.length; j++) {
1691
- if (holds(resolved[i], resolved[j]) || holds(resolved[j], resolved[i])) continue;
1692
- const push = clearance(boxes[i], boxes[j], padding);
1693
- if (push === null) continue;
1694
- shove(resolved[i].members, -push.x / 2, -push.y / 2);
1695
- shove(resolved[j].members, push.x / 2, push.y / 2);
1696
- }
1697
- for (const body of bodies) {
1698
- if (memberSets[i].has(body)) continue;
1699
- const box = { x0: body.x - body.hw, y0: body.y - body.hh, x1: body.x + body.hw, y1: body.y + body.hh };
1700
- const push = clearance(boxes[i], box, padding);
1701
- if (push === null) continue;
1702
- shove(resolved[i].members, -push.x / 2, -push.y / 2);
1703
- body.vx += push.x / 2;
1704
- body.vy += push.y / 2;
1705
- }
1706
- }
1707
- };
1708
- }
1709
-
1710
- // src/force.ts
1711
- var DEFAULTS = {
1712
- iterations: 300,
1713
- linkDistance: 160,
1714
- linkStrength: 0.35,
1715
- charge: -3e3,
1716
- gravity: 0.02,
1717
- padding: 24,
1718
- groupStrength: 0.2
1719
- };
1720
- var GOLDEN = Math.PI * (3 - Math.sqrt(5));
1721
- var NUDGE2 = 0.5;
1722
- function charge(bodies, strength) {
1723
- return (alpha) => {
1724
- for (let i = 0; i < bodies.length; i++) {
1725
- const a = bodies[i];
1726
- for (let j = i + 1; j < bodies.length; j++) {
1727
- const b = bodies[j];
1728
- const dx = b.x - a.x;
1729
- const dy = b.y - a.y;
1730
- const d2 = dx * dx + dy * dy;
1731
- if (d2 === 0) continue;
1732
- const w = strength * alpha / d2;
1733
- a.vx += dx * w;
1734
- a.vy += dy * w;
1735
- b.vx -= dx * w;
1736
- b.vy -= dy * w;
1737
- }
1738
- }
1739
- };
1740
- }
1741
- function links(bodies, graph, distance, strength) {
1742
- return (alpha) => {
1743
- for (const edge of graph.edges) {
1744
- const a = bodies.get(edge.from);
1745
- const b = bodies.get(edge.to);
1746
- if (a === void 0 || b === void 0 || a === b) continue;
1747
- const dx = b.x - a.x;
1748
- const dy = b.y - a.y;
1749
- const d = Math.hypot(dx, dy);
1750
- if (d === 0) continue;
1751
- const pull = (d - distance) / d * alpha * strength;
1752
- a.vx += dx * pull;
1753
- a.vy += dy * pull;
1754
- b.vx -= dx * pull;
1755
- b.vy -= dy * pull;
1756
- }
1757
- };
1758
- }
1759
- function separateBoxes(bodies, padding) {
1760
- return () => {
1761
- for (let i = 0; i < bodies.length; i++) {
1762
- const a = bodies[i];
1763
- for (let j = i + 1; j < bodies.length; j++) {
1764
- const b = bodies[j];
1765
- const dx = b.x - a.x;
1766
- const dy = b.y - a.y;
1767
- const gapX = a.hw + b.hw + padding - Math.abs(dx);
1768
- const gapY = a.hh + b.hh + padding - Math.abs(dy);
1769
- if (gapX <= 0 || gapY <= 0) continue;
1770
- if (gapX < gapY) {
1771
- const push = (Math.sign(dx) || 1) * gapX * 0.5;
1772
- a.vx -= push;
1773
- b.vx += push;
1774
- } else {
1775
- const push = (Math.sign(dy) || 1) * gapY * 0.5;
1776
- a.vy -= push;
1777
- b.vy += push;
1778
- }
1779
- }
1780
- }
1781
- };
1782
- }
1783
- function gravity(bodies, at2, strength) {
1784
- return (alpha) => {
1785
- for (const body of bodies) {
1786
- body.vx += (at2.x - body.x) * strength * alpha;
1787
- body.vy += (at2.y - body.y) * strength * alpha;
1788
- }
1789
- };
1790
- }
1791
- function forceRelaxation(graph, opts = {}) {
1792
- const {
1793
- linkDistance = DEFAULTS.linkDistance,
1794
- linkStrength = DEFAULTS.linkStrength,
1795
- charge: chargeStrength = DEFAULTS.charge,
1796
- gravity: gravityStrength = DEFAULTS.gravity,
1797
- padding = DEFAULTS.padding,
1798
- groupStrength = DEFAULTS.groupStrength
1799
- } = opts;
1800
- const pinned = pinnedSet(graph, opts);
1801
- const bodies = graph.nodes.map((node) => {
1802
- const body = {
1803
- id: node.id,
1804
- x: center(node.bounds, "x"),
1805
- y: center(node.bounds, "y"),
1806
- hw: node.bounds.width / 2,
1807
- hh: node.bounds.height / 2
1808
- };
1809
- if (pinned.has(node.id)) {
1810
- body.fx = body.x;
1811
- body.fy = body.y;
1812
- }
1813
- return body;
1814
- });
1815
- separate(bodies);
1816
- const byId = new Map(bodies.map((b) => [b.id, b]));
1817
- const centroid = {
1818
- x: bodies.reduce((sum, b) => sum + b.x, 0) / bodies.length,
1819
- y: bodies.reduce((sum, b) => sum + b.y, 0) / bodies.length
1820
- };
1821
- const groups = graph.groups ?? [];
1822
- const sim = createSimulation({
1823
- nodes: bodies,
1824
- forces: [
1825
- charge(bodies, chargeStrength),
1826
- links(byId, graph, linkDistance, linkStrength),
1827
- gravity(bodies, centroid, gravityStrength),
1828
- separateBoxes(bodies, padding),
1829
- ...groups.length > 0 ? [groupCohesion(groups, byId, groupStrength), separateGroups(groups, bodies, byId, padding)] : []
1830
- ]
1831
- });
1832
- return {
1833
- bodies,
1834
- byId,
1835
- pinned,
1836
- sim,
1837
- placed() {
1838
- const placed = /* @__PURE__ */ new Map();
1839
- for (const node of graph.nodes) {
1840
- const body = byId.get(node.id);
1841
- placed.set(node.id, {
1842
- x: body.x - node.bounds.width / 2,
1843
- y: body.y - node.bounds.height / 2
1844
- });
1845
- }
1846
- return placed;
1847
- }
1848
- };
1849
- }
1850
- var force = (graph, opts = {}) => {
1851
- if (graph.nodes.length === 0) return /* @__PURE__ */ new Map();
1852
- const relaxation = forceRelaxation(graph, opts);
1853
- relaxation.sim.tick(opts.iterations ?? DEFAULTS.iterations);
1854
- return translated(graph, relaxation.placed(), relaxation.pinned, opts.tolerance);
1855
- };
1856
- function separate(bodies) {
1857
- const seen = /* @__PURE__ */ new Map();
1858
- bodies.forEach((body, i) => {
1859
- const key = `${body.x},${body.y}`;
1860
- const nth = seen.get(key) ?? 0;
1861
- seen.set(key, nth + 1);
1862
- if (nth === 0 || body.fx != null || body.fy != null) return;
1863
- const angle = GOLDEN * i;
1864
- const radius = NUDGE2 * nth;
1865
- body.x += Math.cos(angle) * radius;
1866
- body.y += Math.sin(angle) * radius;
1867
- });
1868
- }
1869
-
1870
- // src/layered/cycles.ts
1871
- function backEdges(graph) {
1872
- const back = /* @__PURE__ */ new Set();
1873
- const done = /* @__PURE__ */ new Set();
1874
- const onStack = /* @__PURE__ */ new Set();
1875
- const walk = (id) => {
1876
- if (done.has(id)) return;
1877
- onStack.add(id);
1878
- for (const edge of graph.outgoing(id)) {
1879
- if (edge.to === id) continue;
1880
- if (onStack.has(edge.to)) back.add(edge.id);
1881
- else walk(edge.to);
1882
- }
1883
- onStack.delete(id);
1884
- done.add(id);
1885
- };
1886
- for (const node of graph.nodes) walk(node.id);
1887
- return back;
1888
- }
1889
- function ranksOf(graph, back) {
1890
- const ranks = /* @__PURE__ */ new Map();
1891
- const resolving = /* @__PURE__ */ new Set();
1892
- const rankOf = (id) => {
1893
- const known = ranks.get(id);
1894
- if (known !== void 0) return known;
1895
- if (resolving.has(id)) return 0;
1896
- resolving.add(id);
1897
- let rank = 0;
1898
- for (const edge of graph.incoming(id)) {
1899
- if (back.has(edge.id) || edge.from === id) continue;
1900
- rank = Math.max(rank, rankOf(edge.from) + 1);
1901
- }
1902
- resolving.delete(id);
1903
- ranks.set(id, rank);
1904
- return rank;
1905
- };
1906
- for (const node of graph.nodes) rankOf(node.id);
1907
- return ranks;
1908
- }
1909
-
1910
- // src/route.ts
1911
- var FACES = {
1912
- down: { out: "s", in: "n" },
1913
- up: { out: "n", in: "s" },
1914
- right: { out: "e", in: "w" },
1915
- left: { out: "w", in: "e" }
1916
- };
1917
- function layeredRoutes(input) {
1918
- const { ranks, members, compass } = input;
1919
- const faces = FACES[input.direction ?? "down"];
1920
- const span = (id) => {
1921
- const rs = (members.get(id) ?? [id]).map((m) => ranks.get(m)).filter((r) => r !== void 0);
1922
- return rs.length === 0 ? null : [Math.min(...rs), Math.max(...rs)];
1923
- };
1924
- const routes = /* @__PURE__ */ new Map();
1925
- for (const edge of input.edges) {
1926
- const from = span(edge.from);
1927
- const to = span(edge.to);
1928
- const route = {};
1929
- if (from !== null && to !== null && (from[1] < to[0] || to[1] < from[0])) {
1930
- const ahead = from[1] < to[0];
1931
- if (compass.has(edge.from)) route.fromPort = ahead ? faces.out : faces.in;
1932
- if (compass.has(edge.to)) route.toPort = ahead ? faces.in : faces.out;
1933
- }
1934
- const waypoints = input.waypoints.get(edge.id);
1935
- if (waypoints !== void 0 && waypoints.length > 0) {
1936
- route.waypoints = waypoints;
1937
- const label = input.labels?.get(edge.id);
1938
- if (label !== void 0) route.labelAt = label;
1939
- }
1940
- if (route.fromPort !== void 0 || route.toPort !== void 0 || route.waypoints !== void 0) {
1941
- routes.set(edge.id, route);
1942
- }
1943
- }
1944
- return routes;
1945
- }
1946
- function layeredPorts(graph, ranks, direction) {
1947
- const faces = FACES[direction ?? "down"];
1948
- const compass = new Set(graph.groups?.map((g) => g.id) ?? []);
1949
- for (const node of graph.nodes) {
1950
- if (hasCompassPort(node, faces.out) && hasCompassPort(node, faces.in)) compass.add(node.id);
1951
- }
1952
- const edges = /* @__PURE__ */ new Map();
1953
- for (const e of graph.edges) {
1954
- const id = e.origin?.id ?? e.id;
1955
- if (!edges.has(id)) edges.set(id, { id, from: e.origin?.from ?? e.from, to: e.origin?.to ?? e.to });
1956
- }
1957
- const routes = layeredRoutes({
1958
- edges: [...edges.values()],
1959
- ranks,
1960
- waypoints: /* @__PURE__ */ new Map(),
1961
- members: new Map((graph.groups ?? []).map((g) => [g.id, g.members])),
1962
- compass,
1963
- ...direction !== void 0 ? { direction } : {}
1964
- });
1965
- return new Map([...routes].map(([id, r]) => [id, {
1966
- ...r.fromPort !== void 0 ? { from: r.fromPort } : {},
1967
- ...r.toPort !== void 0 ? { to: r.toPort } : {}
1968
- }]));
1969
- }
1970
- function hasCompassPort(node, face) {
1971
- const point = COMPASS[face];
1972
- return (node.ports ?? DEFAULT_PORTS).some((p) => p.id === face && p.at.u === point.u && p.at.v === point.v);
1973
- }
1974
- function authoredOver(edges, plan) {
1975
- return new Map(edges.map((e) => {
1976
- const ports = plan.ports?.get(e.id);
1977
- const planned = plan.waypoints?.get(e.id);
1978
- const fromPort = e.fromPort ?? ports?.from;
1979
- const toPort = e.toPort ?? ports?.to;
1980
- const waypoints = e.waypoints ?? (planned !== void 0 && planned.length > 0 ? planned : void 0);
1981
- const labelAt = e.waypoints === void 0 && waypoints !== void 0 ? plan.labels?.get(e.id) : void 0;
1982
- return [e.id, {
1983
- ...fromPort ? { fromPort } : {},
1984
- ...toPort ? { toPort } : {},
1985
- ...waypoints ? { waypoints } : {},
1986
- ...labelAt !== void 0 ? { labelAt } : {}
1987
- }];
1988
- }));
1989
- }
1990
-
1991
- // src/layered/rank.ts
1992
- var GROUP_WEIGHT = 4;
1993
- function ranksByLength(graph, back) {
1994
- const index = /* @__PURE__ */ new Map();
1995
- graph.nodes.forEach((node, i) => index.set(node.id, i));
1996
- const arcs = [];
1997
- for (const edge of graph.edges) {
1998
- const from = index.get(edge.from);
1999
- const to = index.get(edge.to);
2000
- if (from === void 0 || to === void 0 || from === to) continue;
2001
- arcs.push(back.has(edge.id) ? { tail: to, head: from, weight: 1, minlen: 1 } : { tail: from, head: to, weight: 1, minlen: 1 });
2002
- }
2003
- let count = graph.nodes.length;
2004
- for (const group of graph.groups ?? []) {
2005
- const members = [...new Set(group.members.map((id) => index.get(id)).filter((i) => i !== void 0))];
2006
- if (members.length < 2) continue;
2007
- const [top, bottom] = [count, count + 1];
2008
- count += 2;
2009
- for (const m of members) {
2010
- arcs.push({ tail: top, head: m, weight: GROUP_WEIGHT, minlen: 0 });
2011
- arcs.push({ tail: m, head: bottom, weight: GROUP_WEIGHT, minlen: 0 });
2012
- }
2013
- }
2014
- const real = graph.nodes.length;
2015
- const start = longestPath(count, arcs);
2016
- const ranks = /* @__PURE__ */ new Map();
2017
- for (const part of components(count, arcs)) {
2018
- const rank = part.nodes.map((g) => start[g]);
2019
- simplex(part.arcs, rank);
2020
- const own = part.nodes.flatMap((g, i) => g < real ? [i] : []);
2021
- const min = Math.min(...own.map((i) => rank[i]));
2022
- for (const i of own) ranks.set(graph.nodes[part.nodes[i]].id, rank[i] - min);
2023
- }
2024
- return ranks;
2025
- }
2026
- function longestPath(count, arcs) {
2027
- const rank = new Array(count).fill(0);
2028
- const pending = new Array(count).fill(0);
2029
- const out = Array.from({ length: count }, () => []);
2030
- for (const arc2 of arcs) {
2031
- pending[arc2.head]++;
2032
- out[arc2.tail].push(arc2);
2033
- }
2034
- const ready = [];
2035
- for (let i = 0; i < count; i++) if (pending[i] === 0) ready.push(i);
2036
- for (let k = 0; k < ready.length; k++) {
2037
- const u = ready[k];
2038
- for (const { head: v, minlen } of out[u]) {
2039
- rank[v] = Math.max(rank[v], rank[u] + minlen);
2040
- if (--pending[v] === 0) ready.push(v);
2041
- }
2042
- }
2043
- return rank;
1902
+ return rank;
2044
1903
  }
2045
1904
  function components(count, arcs) {
2046
1905
  const near = Array.from({ length: count }, () => []);
@@ -2194,16 +2053,16 @@ function tighten(rank, arcs, inTree) {
2194
2053
  const adj = treeAdjacency(rank.length, arcs, inTree);
2195
2054
  const seen = rank.map(() => false);
2196
2055
  seen[0] = true;
2197
- const queue = [0];
2198
- for (let k = 0; k < queue.length; k++) {
2199
- const v = queue[k];
2056
+ const queue2 = [0];
2057
+ for (let k = 0; k < queue2.length; k++) {
2058
+ const v = queue2[k];
2200
2059
  for (const a of adj[v]) {
2201
2060
  const arc2 = arcs[a];
2202
2061
  const w = arc2.tail === v ? arc2.head : arc2.tail;
2203
2062
  if (seen[w]) continue;
2204
2063
  seen[w] = true;
2205
2064
  rank[w] = arc2.tail === v ? rank[v] + arc2.minlen : rank[v] - arc2.minlen;
2206
- queue.push(w);
2065
+ queue2.push(w);
2207
2066
  }
2208
2067
  }
2209
2068
  }
@@ -2302,13 +2161,13 @@ function expand(graph, nodeRanks, back, opts) {
2302
2161
  if (a === null || b === null || a[1] >= b[0]) continue;
2303
2162
  if (d.authored && b[0] - a[1] > 1) continue;
2304
2163
  const group = sharedGroup(from, to);
2305
- const start = crossCenter(from);
2306
- const end = crossCenter(to);
2164
+ const start2 = crossCenter(from);
2165
+ const end2 = crossCenter(to);
2307
2166
  const crossings = b[0] - a[1] - 1;
2308
2167
  const now = d.waypoints?.length === crossings * 2 ? d.waypoints : void 0;
2309
2168
  const seed = (j) => {
2310
2169
  if (now !== void 0) return now[(d.back ? crossings - 1 - j : j) * 2][axes.cross];
2311
- return start + (end - start) * (j + 1) / (crossings + 1);
2170
+ return start2 + (end2 - start2) * (j + 1) / (crossings + 1);
2312
2171
  };
2313
2172
  const labelRow = labeled.labelRow.get(d.id);
2314
2173
  let prev = inRank(from, a[1]);
@@ -2409,22 +2268,22 @@ function settlePlaces(rows, hops) {
2409
2268
  if (!hops.some((h) => h.fromPlaces !== void 0 || h.toPlaces !== void 0)) return [...hops];
2410
2269
  const pos = /* @__PURE__ */ new Map();
2411
2270
  for (const row of rows) row.forEach((n, i) => pos.set(n.id, i));
2412
- const sides = sidesOf(hops);
2271
+ const sides2 = sidesOf(hops);
2413
2272
  return hops.map((h) => {
2414
2273
  if (h.fromPlaces === void 0 && h.toPlaces === void 0) return h;
2415
2274
  const { fromPlaces, toPlaces, ...rest } = h;
2416
2275
  return {
2417
2276
  ...rest,
2418
- ...fromPlaces !== void 0 ? { fromAt: placeAmong(fromPlaces, h.to, sides.down.get(h.from) ?? [], pos) } : {},
2419
- ...toPlaces !== void 0 ? { toAt: placeAmong(toPlaces, h.from, sides.up.get(h.to) ?? [], pos) } : {}
2277
+ ...fromPlaces !== void 0 ? { fromAt: placeAmong(fromPlaces, h.to, sides2.down.get(h.from) ?? [], pos) } : {},
2278
+ ...toPlaces !== void 0 ? { toAt: placeAmong(toPlaces, h.from, sides2.up.get(h.to) ?? [], pos) } : {}
2420
2279
  };
2421
2280
  });
2422
2281
  }
2423
2282
 
2424
2283
  // src/layered/order.ts
2425
- function ends(p, pos, sides) {
2426
- const fa = p.fromPlaces === void 0 ? p.fromAt : placeAmong(p.fromPlaces, p.to, sides.down.get(p.from) ?? [], pos);
2427
- const fb = p.toPlaces === void 0 ? p.toAt : placeAmong(p.toPlaces, p.from, sides.up.get(p.to) ?? [], pos);
2284
+ function ends(p, pos, sides2) {
2285
+ const fa = p.fromPlaces === void 0 ? p.fromAt : placeAmong(p.fromPlaces, p.to, sides2.down.get(p.from) ?? [], pos);
2286
+ const fb = p.toPlaces === void 0 ? p.toAt : placeAmong(p.toPlaces, p.from, sides2.up.get(p.to) ?? [], pos);
2428
2287
  return [at(pos.get(p.from), fa), at(pos.get(p.to), fb)];
2429
2288
  }
2430
2289
  var at = (pos, frac) => pos + 0.25 + frac / 2;
@@ -2445,16 +2304,16 @@ function positions(rows) {
2445
2304
  for (const row of rows) row.forEach((n, i) => pos.set(n.id, i));
2446
2305
  return pos;
2447
2306
  }
2448
- function pairCrossings(pairs, pos, sides) {
2449
- const met = pairs.map((p) => ends(p, pos, sides)).sort((p, q) => p[0] - q[0] || p[1] - q[1]);
2307
+ function pairCrossings(pairs, pos, sides2) {
2308
+ const met = pairs.map((p) => ends(p, pos, sides2)).sort((p, q) => p[0] - q[0] || p[1] - q[1]);
2450
2309
  const lows = [...new Set(met.map(([, b]) => b))].sort((p, q) => p - q);
2451
2310
  const slot = new Map(lows.map((b, i) => [b, i]));
2452
2311
  const width = lows.length;
2453
2312
  const tree2 = new Array(width + 1).fill(0);
2454
2313
  let seen = 0;
2455
2314
  let count = 0;
2456
- for (const [, end] of met) {
2457
- const b = slot.get(end);
2315
+ for (const [, end2] of met) {
2316
+ const b = slot.get(end2);
2458
2317
  let atMost = 0;
2459
2318
  for (let i = b + 1; i > 0; i -= i & -i) atMost += tree2[i];
2460
2319
  count += seen - atMost;
@@ -2463,9 +2322,9 @@ function pairCrossings(pairs, pos, sides) {
2463
2322
  }
2464
2323
  return count;
2465
2324
  }
2466
- function total(rows, byRank, pos, sides) {
2325
+ function total(rows, byRank, pos, sides2) {
2467
2326
  let sum = 0;
2468
- for (let r = 0; r + 1 < rows.length; r++) sum += pairCrossings(byRank[r], pos, sides);
2327
+ for (let r = 0; r + 1 < rows.length; r++) sum += pairCrossings(byRank[r], pos, sides2);
2469
2328
  return sum;
2470
2329
  }
2471
2330
  function chainsOf(groups) {
@@ -2517,10 +2376,10 @@ function sortRank(row, key, chainOf, places = /* @__PURE__ */ new Map()) {
2517
2376
  });
2518
2377
  const out = [];
2519
2378
  const flatten = (block) => {
2520
- const place = block.group === void 0 ? void 0 : places.get(block.group);
2379
+ const place2 = block.group === void 0 ? void 0 : places.get(block.group);
2521
2380
  const items = block.items.map((item) => {
2522
- if (place !== void 0) {
2523
- const k = place.get("id" in item ? item.id : item.group) ?? Infinity;
2381
+ if (place2 !== void 0) {
2382
+ const k = place2.get("id" in item ? item.id : item.group) ?? Infinity;
2524
2383
  return { item, k, first: "id" in item ? firstOf.get(item) : item.first };
2525
2384
  }
2526
2385
  if ("id" in item) return { item, k: keyOf.get(item), first: firstOf.get(item) };
@@ -2600,15 +2459,15 @@ function orderRows(rows, hops, opts = {}) {
2600
2459
  }
2601
2460
  }
2602
2461
  const seeded = rows.map((row) => sortRank(row, (_, i) => i, chainOf, places));
2603
- const sides = sidesOf(hops);
2604
- const work = { rows: seeded, pos: positions(seeded), ups, downs, sides, chainOf, places };
2462
+ const sides2 = sidesOf(hops);
2463
+ const work = { rows: seeded, pos: positions(seeded), ups, downs, sides: sides2, chainOf, places };
2605
2464
  let best = seeded.map((row) => [...row]);
2606
- let fewest = total(work.rows, byRank, work.pos, sides);
2465
+ let fewest = total(work.rows, byRank, work.pos, sides2);
2607
2466
  for (let sweep = 0, stale = 0; sweep < maxSweeps && stale < patience && fewest > 0; sweep++) {
2608
2467
  if (sweep % 2 === 0) for (let r = 1; r < work.rows.length; r++) reorder(work, r, ups);
2609
2468
  else for (let r = work.rows.length - 2; r >= 0; r--) reorder(work, r, downs);
2610
2469
  transpose(work);
2611
- const n = total(work.rows, byRank, work.pos, sides);
2470
+ const n = total(work.rows, byRank, work.pos, sides2);
2612
2471
  if (n < fewest) {
2613
2472
  fewest = n;
2614
2473
  best = work.rows.map((row) => [...row]);
@@ -2645,9 +2504,9 @@ function levelRoutes(input) {
2645
2504
  out.ports.set(id, named(graph, [from, face], [to, face]));
2646
2505
  out.waypoints.set(id, around(a, b, others, axes, input.rankGap));
2647
2506
  } else {
2648
- const sides = SIDES[axes.cross];
2507
+ const sides2 = SIDES[axes.cross];
2649
2508
  const ahead = center(a, axes.cross) < center(b, axes.cross);
2650
- out.ports.set(id, named(graph, [from, ahead ? sides.high : sides.low], [to, ahead ? sides.low : sides.high]));
2509
+ out.ports.set(id, named(graph, [from, ahead ? sides2.high : sides2.low], [to, ahead ? sides2.low : sides2.high]));
2651
2510
  }
2652
2511
  }
2653
2512
  return out;
@@ -2916,11 +2775,11 @@ function holdPins(input) {
2916
2775
  if (pins.length < 2) return { crossAt: new Map(input.crossAt), gaps: [...input.gaps], depths: natural, rankAt, unheld };
2917
2776
  const anchor = pins[0];
2918
2777
  const a = rowOf.get(anchor.id);
2919
- let start = 0;
2920
- for (let r = 0; r < a; r++) start += natural[r] + input.gaps[r];
2921
- const bandInput = { natural, gaps: input.gaps, minGaps: input.minGaps, home: { rank: a, lo: start } };
2778
+ let start2 = 0;
2779
+ for (let r = 0; r < a; r++) start2 += natural[r] + input.gaps[r];
2780
+ const bandInput = { natural, gaps: input.gaps, minGaps: input.minGaps, home: { rank: a, lo: start2 } };
2922
2781
  const anchorExt = extent(anchor.bounds, axes.rank);
2923
- const anchorRank = start + (natural[a] - anchorExt) / 2;
2782
+ const anchorRank = start2 + (natural[a] - anchorExt) / 2;
2924
2783
  const anchorWorld = worldOf({ cross: input.crossAt.get(anchor.id), rank: anchorRank }, anchorExt, axes);
2925
2784
  const target = (id) => {
2926
2785
  const node = graph.node(id);
@@ -3284,107 +3143,369 @@ function layeredPlan(graph, opts = {}) {
3284
3143
  }, graph, opts);
3285
3144
  return { ...last, moves };
3286
3145
  }
3287
- function routed(edge, plan) {
3288
- const { waypoints: _, ...rest } = edge;
3289
- const route = plan.waypoints.get(edge.origin?.id ?? edge.id);
3290
- return route === void 0 ? rest : { ...rest, waypoints: route };
3146
+ function routed(edge, plan) {
3147
+ const { waypoints: _, ...rest } = edge;
3148
+ const route = plan.waypoints.get(edge.origin?.id ?? edge.id);
3149
+ return route === void 0 ? rest : { ...rest, waypoints: route };
3150
+ }
3151
+ function planOnce(graph, opts = {}) {
3152
+ const axes = axesFor(opts.direction);
3153
+ const nodeGap = opts.nodeGap ?? DEFAULT_NODE_GAP;
3154
+ const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
3155
+ const finding = opts.order === "barycenter";
3156
+ const back = backEdges(graph);
3157
+ const ranks = ranksByLength(graph, back);
3158
+ const expanded = expand(graph, ranks, back, { axes, nodeGap });
3159
+ let rows = finding ? orderRows(expanded.rows, expanded.hops, { groups: graph.groups ?? [] }) : expanded.rows;
3160
+ const grouped = (graph.groups?.length ?? 0) > 0;
3161
+ const pinned = pinnedSet(graph, opts);
3162
+ const across = () => {
3163
+ const preferred2 = placeAcross(rows, settlePlaces(rows, expanded.hops), axes.cross, nodeGap);
3164
+ return grouped ? packClusters(graph, rows, preferred2, axes, nodeGap) : preferred2;
3165
+ };
3166
+ let packed = across();
3167
+ for (let turn = 0; turn < rows.length; turn++) {
3168
+ const turned = turnOutward({ graph, rows, crossAt: packed, axes, pinned, hops: expanded.hops });
3169
+ if (turned === void 0) break;
3170
+ rows = turned;
3171
+ packed = across();
3172
+ }
3173
+ const spaced = grouped ? rankGaps(graph, rows, axes, rankGap) : rows.slice(1).map(() => rankGap);
3174
+ const minGaps = spaced.map((gap) => gap - rankGap + rankGap * MIN_GAP);
3175
+ for (const r of expanded.labelRows) {
3176
+ spaced[r - 1] = spaced[r - 1] - rankGap * (1 - LABEL_GAP);
3177
+ spaced[r] = spaced[r] - rankGap * (1 - LABEL_GAP);
3178
+ }
3179
+ const { crossAt, gaps, depths: depths2, rankAt, unheld } = holdPins({ graph, rows, crossAt: packed, gaps: spaced, pinned, axes, nodeGap, minGaps });
3180
+ const slots = /* @__PURE__ */ new Map();
3181
+ const bands = [];
3182
+ let along = 0;
3183
+ for (const [i, row] of rows.entries()) {
3184
+ const depth = depths2[i];
3185
+ bands.push([along, along + depth]);
3186
+ for (const node of row) {
3187
+ slots.set(node.id, { cross: crossAt.get(node.id), rank: rankAt.get(node.id) ?? along + (depth - extent(node.bounds, axes.rank)) / 2 });
3188
+ }
3189
+ along += depth + (gaps[i] ?? 0);
3190
+ }
3191
+ const placed = /* @__PURE__ */ new Map();
3192
+ for (const node of graph.nodes) {
3193
+ const slot = slots.get(node.id);
3194
+ if (slot !== void 0) placed.set(node.id, worldOf(slot, extent(node.bounds, axes.rank), axes));
3195
+ }
3196
+ const moves = translated(graph, placed, pinned, opts.tolerance);
3197
+ const offset = anchorOffset(graph, placed, pinned);
3198
+ const displaced = /* @__PURE__ */ new Set();
3199
+ for (const id of pinned) {
3200
+ const at2 = placed.get(id);
3201
+ const node = graph.node(id);
3202
+ if (at2 === void 0 || node === void 0) continue;
3203
+ if (Math.abs(at2.x + offset.x - node.bounds.x) > 0.5 || Math.abs(at2.y + offset.y - node.bounds.y) > 0.5) displaced.add(id);
3204
+ }
3205
+ const waypoints = /* @__PURE__ */ new Map();
3206
+ const labels = /* @__PURE__ */ new Map();
3207
+ const rankOf = /* @__PURE__ */ new Map();
3208
+ rows.forEach((row, r) => row.forEach((n) => rankOf.set(n.id, r)));
3209
+ const reach = boxReach(graph, rows, axes);
3210
+ const widthOf = new Map(rows.flat().map((n) => [n.id, extent(n.bounds, axes.cross)]));
3211
+ for (const [edge, chain2] of expanded.chains) {
3212
+ if (chain2.ends.some((id) => displaced.has(id))) continue;
3213
+ const beside = chain2.ids.indexOf(expanded.labels.get(edge) ?? "");
3214
+ const points = [];
3215
+ for (const id of chain2.ids) {
3216
+ const cross = crossAt.get(id) + (expanded.wires.get(id) ?? widthOf.get(id) / 2);
3217
+ const r = rankOf.get(id);
3218
+ const [lo, hi] = [bands[r][0] - reach.lo[r], bands[r][1] + reach.hi[r]];
3219
+ for (const rank of chain2.back ? [hi, lo] : [lo, hi]) {
3220
+ const at2 = worldOf({ cross, rank }, 0, axes);
3221
+ points.push({ x: at2.x + offset.x, y: at2.y + offset.y });
3222
+ }
3223
+ }
3224
+ waypoints.set(edge, points);
3225
+ if (beside >= 0) {
3226
+ const [a, b] = [points[beside * 2], points[beside * 2 + 1]];
3227
+ labels.set(edge, { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
3228
+ }
3229
+ }
3230
+ const ports = layeredPorts(graph, ranks, opts.direction);
3231
+ const boxes = new Map(graph.nodes.map((n) => [n.id, { ...n.bounds, ...moves.get(n.id) }]));
3232
+ const level = levelRoutes({ graph, boxes, axes, rankGap, ...opts.direction !== void 0 ? { direction: opts.direction } : {} });
3233
+ for (const [edge, named2] of level.ports) {
3234
+ ports.set(edge, named2);
3235
+ waypoints.delete(edge);
3236
+ labels.delete(edge);
3237
+ }
3238
+ for (const [edge, points] of level.waypoints) waypoints.set(edge, points);
3239
+ return { moves, ranks, back, waypoints, labels, ports, unheld };
3240
+ }
3241
+
3242
+ // src/layered.ts
3243
+ var layered = Object.assign(
3244
+ (graph, opts = {}) => layeredPlan(graph, opts).moves,
3245
+ { plan: layeredPlan }
3246
+ );
3247
+
3248
+ // src/forceGroups.ts
3249
+ function resolve(groups, byId) {
3250
+ const out = [];
3251
+ for (const group of groups) {
3252
+ const members = group.members.map((id) => byId.get(id)).filter((b) => b !== void 0);
3253
+ if (members.length > 0) out.push({ members, group, children: [] });
3254
+ }
3255
+ const byGroup = new Map(out.map((g) => [g.group.id, g]));
3256
+ for (const g of out) {
3257
+ const parent = g.group.parent === void 0 ? void 0 : byGroup.get(g.group.parent);
3258
+ if (parent === void 0) continue;
3259
+ g.parent = parent;
3260
+ parent.children.push(g);
3261
+ }
3262
+ return out;
3263
+ }
3264
+ function groupCohesion(groups, byId, strength) {
3265
+ const resolved = resolve(groups, byId);
3266
+ return (alpha) => {
3267
+ for (const { members } of resolved) {
3268
+ let cx = 0;
3269
+ let cy = 0;
3270
+ for (const m of members) {
3271
+ cx += m.x;
3272
+ cy += m.y;
3273
+ }
3274
+ cx /= members.length;
3275
+ cy /= members.length;
3276
+ for (const m of members) {
3277
+ m.vx += (cx - m.x) * strength * alpha;
3278
+ m.vy += (cy - m.y) * strength * alpha;
3279
+ }
3280
+ }
3281
+ };
3282
+ }
3283
+ function boxOf(g) {
3284
+ let x0 = Infinity;
3285
+ let y0 = Infinity;
3286
+ let x1 = -Infinity;
3287
+ let y1 = -Infinity;
3288
+ for (const m of g.members) {
3289
+ x0 = Math.min(x0, m.x - m.hw);
3290
+ y0 = Math.min(y0, m.y - m.hh);
3291
+ x1 = Math.max(x1, m.x + m.hw);
3292
+ y1 = Math.max(y1, m.y + m.hh);
3293
+ }
3294
+ for (const child of g.children) {
3295
+ const b = boxOf(child);
3296
+ x0 = Math.min(x0, b.x0);
3297
+ y0 = Math.min(y0, b.y0);
3298
+ x1 = Math.max(x1, b.x1);
3299
+ y1 = Math.max(y1, b.y1);
3300
+ }
3301
+ const { inset: inset2 } = g.group;
3302
+ return { x0: x0 - inset2.left, y0: y0 - inset2.top, x1: x1 + inset2.right, y1: y1 + inset2.bottom };
3303
+ }
3304
+ function clearance(a, b, padding) {
3305
+ const gapX = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0) + padding;
3306
+ const gapY = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0) + padding;
3307
+ if (gapX <= 0 || gapY <= 0) return null;
3308
+ const dx = b.x0 + b.x1 - (a.x0 + a.x1);
3309
+ const dy = b.y0 + b.y1 - (a.y0 + a.y1);
3310
+ return gapX < gapY ? { x: (Math.sign(dx) || 1) * gapX, y: 0 } : { x: 0, y: (Math.sign(dy) || 1) * gapY };
3311
+ }
3312
+ function holds(outer, inner) {
3313
+ for (let at2 = inner.parent; at2 !== void 0; at2 = at2.parent) {
3314
+ if (at2 === outer) return true;
3315
+ }
3316
+ return false;
3317
+ }
3318
+ function shove(bodies, x, y) {
3319
+ for (const body of bodies) {
3320
+ body.vx += x;
3321
+ body.vy += y;
3322
+ }
3323
+ }
3324
+ function separateGroups(groups, bodies, byId, padding) {
3325
+ const resolved = resolve(groups, byId);
3326
+ const memberSets = resolved.map((g) => new Set(g.members));
3327
+ return () => {
3328
+ const boxes = resolved.map(boxOf);
3329
+ for (let i = 0; i < resolved.length; i++) {
3330
+ for (let j = i + 1; j < resolved.length; j++) {
3331
+ if (holds(resolved[i], resolved[j]) || holds(resolved[j], resolved[i])) continue;
3332
+ const push = clearance(boxes[i], boxes[j], padding);
3333
+ if (push === null) continue;
3334
+ shove(resolved[i].members, -push.x / 2, -push.y / 2);
3335
+ shove(resolved[j].members, push.x / 2, push.y / 2);
3336
+ }
3337
+ for (const body of bodies) {
3338
+ if (memberSets[i].has(body)) continue;
3339
+ const box = { x0: body.x - body.hw, y0: body.y - body.hh, x1: body.x + body.hw, y1: body.y + body.hh };
3340
+ const push = clearance(boxes[i], box, padding);
3341
+ if (push === null) continue;
3342
+ shove(resolved[i].members, -push.x / 2, -push.y / 2);
3343
+ body.vx += push.x / 2;
3344
+ body.vy += push.y / 2;
3345
+ }
3346
+ }
3347
+ };
3348
+ }
3349
+
3350
+ // src/force.ts
3351
+ var DEFAULTS = {
3352
+ iterations: 300,
3353
+ linkDistance: 160,
3354
+ linkStrength: 0.35,
3355
+ charge: -3e3,
3356
+ gravity: 0.02,
3357
+ padding: 24,
3358
+ groupStrength: 0.2
3359
+ };
3360
+ var GOLDEN = Math.PI * (3 - Math.sqrt(5));
3361
+ var NUDGE2 = 0.5;
3362
+ function charge(bodies, strength) {
3363
+ return (alpha) => {
3364
+ for (let i = 0; i < bodies.length; i++) {
3365
+ const a = bodies[i];
3366
+ for (let j = i + 1; j < bodies.length; j++) {
3367
+ const b = bodies[j];
3368
+ const dx = b.x - a.x;
3369
+ const dy = b.y - a.y;
3370
+ const d2 = dx * dx + dy * dy;
3371
+ if (d2 === 0) continue;
3372
+ const w = strength * alpha / d2;
3373
+ a.vx += dx * w;
3374
+ a.vy += dy * w;
3375
+ b.vx -= dx * w;
3376
+ b.vy -= dy * w;
3377
+ }
3378
+ }
3379
+ };
3380
+ }
3381
+ function links(bodies, graph, distance, strength) {
3382
+ return (alpha) => {
3383
+ for (const edge of graph.edges) {
3384
+ const a = bodies.get(edge.from);
3385
+ const b = bodies.get(edge.to);
3386
+ if (a === void 0 || b === void 0 || a === b) continue;
3387
+ const dx = b.x - a.x;
3388
+ const dy = b.y - a.y;
3389
+ const d = Math.hypot(dx, dy);
3390
+ if (d === 0) continue;
3391
+ const pull = (d - distance) / d * alpha * strength;
3392
+ a.vx += dx * pull;
3393
+ a.vy += dy * pull;
3394
+ b.vx -= dx * pull;
3395
+ b.vy -= dy * pull;
3396
+ }
3397
+ };
3398
+ }
3399
+ function separateBoxes(bodies, padding) {
3400
+ return () => {
3401
+ for (let i = 0; i < bodies.length; i++) {
3402
+ const a = bodies[i];
3403
+ for (let j = i + 1; j < bodies.length; j++) {
3404
+ const b = bodies[j];
3405
+ const dx = b.x - a.x;
3406
+ const dy = b.y - a.y;
3407
+ const gapX = a.hw + b.hw + padding - Math.abs(dx);
3408
+ const gapY = a.hh + b.hh + padding - Math.abs(dy);
3409
+ if (gapX <= 0 || gapY <= 0) continue;
3410
+ if (gapX < gapY) {
3411
+ const push = (Math.sign(dx) || 1) * gapX * 0.5;
3412
+ a.vx -= push;
3413
+ b.vx += push;
3414
+ } else {
3415
+ const push = (Math.sign(dy) || 1) * gapY * 0.5;
3416
+ a.vy -= push;
3417
+ b.vy += push;
3418
+ }
3419
+ }
3420
+ }
3421
+ };
3291
3422
  }
3292
- function planOnce(graph, opts = {}) {
3293
- const axes = axesFor(opts.direction);
3294
- const nodeGap = opts.nodeGap ?? DEFAULT_NODE_GAP;
3295
- const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
3296
- const finding = opts.order === "barycenter";
3297
- const back = backEdges(graph);
3298
- const ranks = ranksByLength(graph, back);
3299
- const expanded = expand(graph, ranks, back, { axes, nodeGap });
3300
- let rows = finding ? orderRows(expanded.rows, expanded.hops, { groups: graph.groups ?? [] }) : expanded.rows;
3301
- const grouped = (graph.groups?.length ?? 0) > 0;
3302
- const pinned = pinnedSet(graph, opts);
3303
- const across = () => {
3304
- const preferred2 = placeAcross(rows, settlePlaces(rows, expanded.hops), axes.cross, nodeGap);
3305
- return grouped ? packClusters(graph, rows, preferred2, axes, nodeGap) : preferred2;
3423
+ function gravity(bodies, at2, strength) {
3424
+ return (alpha) => {
3425
+ for (const body of bodies) {
3426
+ body.vx += (at2.x - body.x) * strength * alpha;
3427
+ body.vy += (at2.y - body.y) * strength * alpha;
3428
+ }
3306
3429
  };
3307
- let packed = across();
3308
- for (let turn = 0; turn < rows.length; turn++) {
3309
- const turned = turnOutward({ graph, rows, crossAt: packed, axes, pinned, hops: expanded.hops });
3310
- if (turned === void 0) break;
3311
- rows = turned;
3312
- packed = across();
3313
- }
3314
- const spaced = grouped ? rankGaps(graph, rows, axes, rankGap) : rows.slice(1).map(() => rankGap);
3315
- const minGaps = spaced.map((gap) => gap - rankGap + rankGap * MIN_GAP);
3316
- for (const r of expanded.labelRows) {
3317
- spaced[r - 1] = spaced[r - 1] - rankGap * (1 - LABEL_GAP);
3318
- spaced[r] = spaced[r] - rankGap * (1 - LABEL_GAP);
3319
- }
3320
- const { crossAt, gaps, depths: depths2, rankAt, unheld } = holdPins({ graph, rows, crossAt: packed, gaps: spaced, pinned, axes, nodeGap, minGaps });
3321
- const slots = /* @__PURE__ */ new Map();
3322
- const bands = [];
3323
- let along = 0;
3324
- for (const [i, row] of rows.entries()) {
3325
- const depth = depths2[i];
3326
- bands.push([along, along + depth]);
3327
- for (const node of row) {
3328
- slots.set(node.id, { cross: crossAt.get(node.id), rank: rankAt.get(node.id) ?? along + (depth - extent(node.bounds, axes.rank)) / 2 });
3430
+ }
3431
+ function forceRelaxation(graph, opts = {}) {
3432
+ const {
3433
+ linkDistance = DEFAULTS.linkDistance,
3434
+ linkStrength = DEFAULTS.linkStrength,
3435
+ charge: chargeStrength = DEFAULTS.charge,
3436
+ gravity: gravityStrength = DEFAULTS.gravity,
3437
+ padding = DEFAULTS.padding,
3438
+ groupStrength = DEFAULTS.groupStrength
3439
+ } = opts;
3440
+ const pinned = pinnedSet(graph, opts);
3441
+ const bodies = graph.nodes.map((node) => {
3442
+ const body = {
3443
+ id: node.id,
3444
+ x: center(node.bounds, "x"),
3445
+ y: center(node.bounds, "y"),
3446
+ hw: node.bounds.width / 2,
3447
+ hh: node.bounds.height / 2
3448
+ };
3449
+ if (pinned.has(node.id)) {
3450
+ body.fx = body.x;
3451
+ body.fy = body.y;
3329
3452
  }
3330
- along += depth + (gaps[i] ?? 0);
3331
- }
3332
- const placed = /* @__PURE__ */ new Map();
3333
- for (const node of graph.nodes) {
3334
- const slot = slots.get(node.id);
3335
- if (slot !== void 0) placed.set(node.id, worldOf(slot, extent(node.bounds, axes.rank), axes));
3336
- }
3337
- const moves = translated(graph, placed, pinned, opts.tolerance);
3338
- const offset = anchorOffset(graph, placed, pinned);
3339
- const displaced = /* @__PURE__ */ new Set();
3340
- for (const id of pinned) {
3341
- const at2 = placed.get(id);
3342
- const node = graph.node(id);
3343
- if (at2 === void 0 || node === void 0) continue;
3344
- if (Math.abs(at2.x + offset.x - node.bounds.x) > 0.5 || Math.abs(at2.y + offset.y - node.bounds.y) > 0.5) displaced.add(id);
3345
- }
3346
- const waypoints = /* @__PURE__ */ new Map();
3347
- const labels = /* @__PURE__ */ new Map();
3348
- const rankOf = /* @__PURE__ */ new Map();
3349
- rows.forEach((row, r) => row.forEach((n) => rankOf.set(n.id, r)));
3350
- const reach = boxReach(graph, rows, axes);
3351
- const widthOf = new Map(rows.flat().map((n) => [n.id, extent(n.bounds, axes.cross)]));
3352
- for (const [edge, chain2] of expanded.chains) {
3353
- if (chain2.ends.some((id) => displaced.has(id))) continue;
3354
- const beside = chain2.ids.indexOf(expanded.labels.get(edge) ?? "");
3355
- const points = [];
3356
- for (const id of chain2.ids) {
3357
- const cross = crossAt.get(id) + (expanded.wires.get(id) ?? widthOf.get(id) / 2);
3358
- const r = rankOf.get(id);
3359
- const [lo, hi] = [bands[r][0] - reach.lo[r], bands[r][1] + reach.hi[r]];
3360
- for (const rank of chain2.back ? [hi, lo] : [lo, hi]) {
3361
- const at2 = worldOf({ cross, rank }, 0, axes);
3362
- points.push({ x: at2.x + offset.x, y: at2.y + offset.y });
3453
+ return body;
3454
+ });
3455
+ separate(bodies);
3456
+ const byId = new Map(bodies.map((b) => [b.id, b]));
3457
+ const centroid = {
3458
+ x: bodies.reduce((sum, b) => sum + b.x, 0) / bodies.length,
3459
+ y: bodies.reduce((sum, b) => sum + b.y, 0) / bodies.length
3460
+ };
3461
+ const groups = graph.groups ?? [];
3462
+ const sim = createSimulation({
3463
+ nodes: bodies,
3464
+ forces: [
3465
+ charge(bodies, chargeStrength),
3466
+ links(byId, graph, linkDistance, linkStrength),
3467
+ gravity(bodies, centroid, gravityStrength),
3468
+ separateBoxes(bodies, padding),
3469
+ ...groups.length > 0 ? [groupCohesion(groups, byId, groupStrength), separateGroups(groups, bodies, byId, padding)] : []
3470
+ ]
3471
+ });
3472
+ return {
3473
+ bodies,
3474
+ byId,
3475
+ pinned,
3476
+ sim,
3477
+ placed() {
3478
+ const placed = /* @__PURE__ */ new Map();
3479
+ for (const node of graph.nodes) {
3480
+ const body = byId.get(node.id);
3481
+ placed.set(node.id, {
3482
+ x: body.x - node.bounds.width / 2,
3483
+ y: body.y - node.bounds.height / 2
3484
+ });
3363
3485
  }
3486
+ return placed;
3364
3487
  }
3365
- waypoints.set(edge, points);
3366
- if (beside >= 0) {
3367
- const [a, b] = [points[beside * 2], points[beside * 2 + 1]];
3368
- labels.set(edge, { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
3369
- }
3370
- }
3371
- const ports = layeredPorts(graph, ranks, opts.direction);
3372
- const boxes = new Map(graph.nodes.map((n) => [n.id, { ...n.bounds, ...moves.get(n.id) }]));
3373
- const level = levelRoutes({ graph, boxes, axes, rankGap, ...opts.direction !== void 0 ? { direction: opts.direction } : {} });
3374
- for (const [edge, named2] of level.ports) {
3375
- ports.set(edge, named2);
3376
- waypoints.delete(edge);
3377
- labels.delete(edge);
3378
- }
3379
- for (const [edge, points] of level.waypoints) waypoints.set(edge, points);
3380
- return { moves, ranks, back, waypoints, labels, ports, unheld };
3488
+ };
3489
+ }
3490
+ var force = (graph, opts = {}) => {
3491
+ if (graph.nodes.length === 0) return /* @__PURE__ */ new Map();
3492
+ const relaxation = forceRelaxation(graph, opts);
3493
+ relaxation.sim.tick(opts.iterations ?? DEFAULTS.iterations);
3494
+ return translated(graph, relaxation.placed(), relaxation.pinned, opts.tolerance);
3495
+ };
3496
+ function separate(bodies) {
3497
+ const seen = /* @__PURE__ */ new Map();
3498
+ bodies.forEach((body, i) => {
3499
+ const key = `${body.x},${body.y}`;
3500
+ const nth = seen.get(key) ?? 0;
3501
+ seen.set(key, nth + 1);
3502
+ if (nth === 0 || body.fx != null || body.fy != null) return;
3503
+ const angle = GOLDEN * i;
3504
+ const radius = NUDGE2 * nth;
3505
+ body.x += Math.cos(angle) * radius;
3506
+ body.y += Math.sin(angle) * radius;
3507
+ });
3381
3508
  }
3382
-
3383
- // src/layered.ts
3384
- var layered = Object.assign(
3385
- (graph, opts = {}) => layeredPlan(graph, opts).moves,
3386
- { plan: layeredPlan }
3387
- );
3388
3509
 
3389
3510
  // src/chainGroups.ts
3390
3511
  function depths(groups) {
@@ -3446,8 +3567,8 @@ function nearest(members, from, index) {
3446
3567
  }
3447
3568
  return best;
3448
3569
  }
3449
- function clearColumns(graph, ordered, index, owner, start) {
3450
- const level = new Map(start);
3570
+ function clearColumns(graph, ordered, index, owner, start2) {
3571
+ const level = new Map(start2);
3451
3572
  const columns = /* @__PURE__ */ new Map();
3452
3573
  for (const node of graph.nodes) {
3453
3574
  if (index.has(node.id)) continue;
@@ -3482,19 +3603,19 @@ function lineGaps(graph, line, hanging, rankGap, axes, packed) {
3482
3603
  return bandGaps(graph, columns, axes, columns.slice(1).map(() => rankGap), (id) => packed.get(id)?.rank);
3483
3604
  }
3484
3605
  function levelGaps(graph, hanging, levelSize, axes, before, base) {
3485
- const levels = Array.from(levelSize, () => []);
3486
- for (const node of graph.nodes) (levels[hanging.level.get(node.id)] ??= []).push(node);
3606
+ const levels2 = Array.from(levelSize, () => []);
3607
+ for (const node of graph.nodes) (levels2[hanging.level.get(node.id)] ??= []).push(node);
3487
3608
  const outward = { rank: axes.cross, cross: axes.rank, sign: before ? -1 : 1 };
3488
3609
  const from = [0];
3489
3610
  for (let k = 1; k < levelSize.length; k++) from[k] = from[k - 1] + (levelSize[k - 1] ?? 0);
3490
- return bandGaps(graph, levels, outward, base, (id) => {
3611
+ return bandGaps(graph, levels2, outward, base, (id) => {
3491
3612
  const node = graph.node(id);
3492
3613
  const at2 = hanging.level.get(id);
3493
3614
  if (node === void 0 || at2 === void 0) return void 0;
3494
3615
  return at2 === 0 ? (levelSize[0] - extent(node.bounds, outward.rank)) / 2 : from[at2];
3495
3616
  });
3496
3617
  }
3497
- function bandGaps(graph, bands, axes, gaps, start) {
3618
+ function bandGaps(graph, bands, axes, gaps, start2) {
3498
3619
  const insets = rankGaps(graph, bands, axes, 0);
3499
3620
  const out = gaps.map((gap, i) => gap + (insets[i] ?? 0));
3500
3621
  const groups = graph.groups ?? [];
@@ -3507,7 +3628,7 @@ function bandGaps(graph, bands, axes, gaps, start) {
3507
3628
  const span = (id, now) => {
3508
3629
  let [lo, hi] = [Infinity, -Infinity];
3509
3630
  for (const m of byId.get(id).members) {
3510
- const at2 = start(m);
3631
+ const at2 = start2(m);
3511
3632
  const band = bandOf.get(m);
3512
3633
  const node = graph.node(m);
3513
3634
  if (at2 === void 0 || band === void 0 || node === void 0) continue;
@@ -3517,7 +3638,7 @@ function bandGaps(graph, bands, axes, gaps, start) {
3517
3638
  return { lo, hi };
3518
3639
  };
3519
3640
  widenForMinWidth(deepestFirst(groups).map((g) => {
3520
- const placed = g.members.flatMap((id) => bandOf.has(id) && start(id) !== void 0 ? [bandOf.get(id)] : []);
3641
+ const placed = g.members.flatMap((id) => bandOf.has(id) && start2(id) !== void 0 ? [bandOf.get(id)] : []);
3521
3642
  return {
3522
3643
  id: g.id,
3523
3644
  ...g.minWidth !== void 0 ? { minWidth: g.minWidth } : {},
@@ -3589,8 +3710,8 @@ function sidesOf2(graph, hanging, moves, axes, before) {
3589
3710
  return byLine;
3590
3711
  }
3591
3712
  var SIDE_TRACK_GAP = 12;
3592
- function turning(sides) {
3593
- const sideways = sides.filter((s) => Math.abs(s.hung - s.port) > 0.5);
3713
+ function turning(sides2) {
3714
+ const sideways = sides2.filter((s) => Math.abs(s.hung - s.port) > 0.5);
3594
3715
  return [
3595
3716
  sideways.filter((s) => s.hung < s.port).sort((a, b) => a.port - b.port),
3596
3717
  sideways.filter((s) => s.hung > s.port).sort((a, b) => b.port - a.port)
@@ -3598,18 +3719,18 @@ function turning(sides) {
3598
3719
  }
3599
3720
  function tracksNeeded(graph, hanging, moves, axes, before) {
3600
3721
  let most = 0;
3601
- for (const sides of sidesOf2(graph, hanging, moves, axes, before).values()) {
3602
- const [low, high] = turning(sides);
3722
+ for (const sides2 of sidesOf2(graph, hanging, moves, axes, before).values()) {
3723
+ const [low, high] = turning(sides2);
3603
3724
  most = Math.max(most, low.length, high.length);
3604
3725
  }
3605
3726
  return most;
3606
3727
  }
3607
3728
  function sideRoutes(graph, hanging, moves, axes, before) {
3608
3729
  const out = /* @__PURE__ */ new Map();
3609
- for (const sides of sidesOf2(graph, hanging, moves, axes, before).values()) {
3610
- const [low, high] = turning(sides);
3611
- const lineEdge = sides[0].lineEdge;
3612
- const hungEdge = before ? Math.max(...sides.map((s) => s.hungEdge)) : Math.min(...sides.map((s) => s.hungEdge));
3730
+ for (const sides2 of sidesOf2(graph, hanging, moves, axes, before).values()) {
3731
+ const [low, high] = turning(sides2);
3732
+ const lineEdge = sides2[0].lineEdge;
3733
+ const hungEdge = before ? Math.max(...sides2.map((s) => s.hungEdge)) : Math.min(...sides2.map((s) => s.hungEdge));
3613
3734
  const step = (hungEdge - lineEdge) / (Math.max(low.length, high.length) + 1);
3614
3735
  for (const run of [low, high]) {
3615
3736
  run.forEach((s, k) => {
@@ -3661,12 +3782,12 @@ function lineOf(graph, ids) {
3661
3782
  if (given.length > 0) return given;
3662
3783
  const back = backEdges(graph);
3663
3784
  const ranks = ranksOf(graph, back);
3664
- let end;
3785
+ let end2;
3665
3786
  for (const node of graph.nodes) {
3666
- if (end === void 0 || ranks.get(node.id) > ranks.get(end.id)) end = node;
3787
+ if (end2 === void 0 || ranks.get(node.id) > ranks.get(end2.id)) end2 = node;
3667
3788
  }
3668
3789
  const line = [];
3669
- for (let at2 = end; at2 !== void 0; ) {
3790
+ for (let at2 = end2; at2 !== void 0; ) {
3670
3791
  line.unshift(at2);
3671
3792
  const rank = ranks.get(at2.id);
3672
3793
  const edge = graph.incoming(at2.id).find((e) => !back.has(e.id) && ranks.get(e.from) === rank - 1);
@@ -3728,10 +3849,10 @@ function chainPlan(graph, opts = {}) {
3728
3849
  };
3729
3850
  const slots = (rank, level2) => slotsFor(line, rows, levelSize, axes, { node: nodeGap, rank, level: level2 }, before);
3730
3851
  const gaps = grouped ? lineGaps(graph, line, hanging, rankGap, axes, slots(line.map(() => 0), [])) : line.map(() => rankGap);
3731
- const place = (side2) => settle(graph, slots(gaps, across(side2)), opts);
3732
- let moves = place(nodeGap);
3852
+ const place2 = (side2) => settle(graph, slots(gaps, across(side2)), opts);
3853
+ let moves = place2(nodeGap);
3733
3854
  const side = (tracksNeeded(graph, hanging, moves, axes, before) + 1) * SIDE_TRACK_GAP;
3734
- if (side > nodeGap) moves = place(side);
3855
+ if (side > nodeGap) moves = place2(side);
3735
3856
  return { moves, waypoints: sideRoutes(graph, hanging, moves, axes, before) };
3736
3857
  }
3737
3858
  function slotsFor(line, rows, levelSize, axes, gaps, before) {
@@ -3766,6 +3887,97 @@ function slotsFor(line, rows, levelSize, axes, gaps, before) {
3766
3887
  return slots;
3767
3888
  }
3768
3889
 
3890
+ // src/grid.ts
3891
+ function levels(graph) {
3892
+ const order = graphOrder(graph);
3893
+ const groups = graph.groups ?? [];
3894
+ const first = new Map(groups.map((g) => [g.id, Math.min(...g.members.map(order))]));
3895
+ const at2 = (id) => first.get(id) ?? order(id);
3896
+ const out = /* @__PURE__ */ new Map();
3897
+ const add = (level, id) => out.set(level, [...out.get(level) ?? [], id]);
3898
+ for (const n of graph.nodes) add(n.group ?? null, n.id);
3899
+ for (const g of groups) add(g.parent ?? null, g.id);
3900
+ for (const ids of out.values()) ids.sort((a, b) => at2(a) - at2(b));
3901
+ return out;
3902
+ }
3903
+ function deal(ids, sizeOf, columns, grid2) {
3904
+ const items = [];
3905
+ let row = 0;
3906
+ let col = 0;
3907
+ for (const id of ids) {
3908
+ col += Math.max(0, Math.floor(grid2.skips?.[id] ?? 0));
3909
+ row += Math.floor(col / columns);
3910
+ col %= columns;
3911
+ const span = Math.min(columns, Math.max(1, Math.floor(grid2.spans?.[id] ?? 1)));
3912
+ if (col + span > columns) {
3913
+ row++;
3914
+ col = 0;
3915
+ }
3916
+ items.push({ id, size: sizeOf(id), row, col, span });
3917
+ col += span;
3918
+ }
3919
+ return { items, columns };
3920
+ }
3921
+ function place(cells, gap, rowGap) {
3922
+ const width = Math.max(0, ...cells.items.map((i) => (i.size.width - (i.span - 1) * gap) / i.span));
3923
+ const rows = [];
3924
+ for (const i of cells.items) rows[i.row] = Math.max(rows[i.row] ?? 0, i.size.height);
3925
+ const tops = [];
3926
+ let y = 0;
3927
+ for (let r = 0; r < rows.length; r++) {
3928
+ tops[r] = y;
3929
+ y += (rows[r] ?? 0) + rowGap;
3930
+ }
3931
+ const at2 = /* @__PURE__ */ new Map();
3932
+ for (const i of cells.items) {
3933
+ const across = i.span * width + (i.span - 1) * gap;
3934
+ at2.set(i.id, { x: i.col * (width + gap) + (across - i.size.width) / 2, y: tops[i.row] + ((rows[i.row] ?? 0) - i.size.height) / 2 });
3935
+ }
3936
+ const used = Math.max(0, ...cells.items.map((i) => i.col + i.span));
3937
+ return { at: at2, size: { width: used === 0 ? 0 : used * width + (used - 1) * gap, height: rows.length === 0 ? 0 : y - rowGap } };
3938
+ }
3939
+ var grid = (graph, opts = {}) => {
3940
+ const gap = opts.nodeGap ?? DEFAULT_NODE_GAP;
3941
+ const rowGap = opts.rankGap ?? gap;
3942
+ const spec = opts.grid ?? {};
3943
+ const byLevel = levels(graph);
3944
+ const groups = new Map((graph.groups ?? []).map((g) => [g.id, g]));
3945
+ const inner = /* @__PURE__ */ new Map();
3946
+ const sizeOf = (id) => {
3947
+ const g = groups.get(id);
3948
+ if (g === void 0) return graph.node(id).bounds;
3949
+ const laid = layOut(id);
3950
+ return {
3951
+ width: Math.max(g.minWidth ?? 0, laid.size.width + g.inset.left + g.inset.right),
3952
+ height: laid.size.height + g.inset.top + g.inset.bottom
3953
+ };
3954
+ };
3955
+ const layOut = (level) => {
3956
+ const done = level === null ? void 0 : inner.get(level);
3957
+ if (done !== void 0) return done;
3958
+ const ids = byLevel.get(level) ?? [];
3959
+ const columns = Math.max(1, Math.floor((level === null ? spec.columns : spec.groupColumns?.[level]) ?? ids.length));
3960
+ const laid = place(deal(ids, sizeOf, columns, spec), gap, rowGap);
3961
+ if (level !== null) inner.set(level, laid);
3962
+ return laid;
3963
+ };
3964
+ const placed = /* @__PURE__ */ new Map();
3965
+ const put = (level, origin) => {
3966
+ for (const [id, p] of layOut(level).at) {
3967
+ const corner = { x: origin.x + p.x, y: origin.y + p.y };
3968
+ const g = groups.get(id);
3969
+ if (g === void 0) {
3970
+ placed.set(id, corner);
3971
+ continue;
3972
+ }
3973
+ const slack = sizeOf(id).width - (inner.get(id).size.width + g.inset.left + g.inset.right);
3974
+ put(id, { x: corner.x + g.inset.left + slack / 2, y: corner.y + g.inset.top });
3975
+ }
3976
+ };
3977
+ put(null, { x: 0, y: 0 });
3978
+ return translated(graph, placed, pinnedSet(graph, opts), opts.tolerance);
3979
+ };
3980
+
3769
3981
  // src/tree.ts
3770
3982
  function forestOf(graph, crossAxis) {
3771
3983
  const order = graphOrder(graph);
@@ -3866,6 +4078,94 @@ var treeOnce = (graph, opts = {}) => {
3866
4078
  return settle(graph, slots, opts);
3867
4079
  };
3868
4080
 
3869
- export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DEVICE_SIZE, DIAGRAM_TRAIT_KEY, DIVIDER_HEIGHT, EVENT_SIZE, GATEWAY_SIZE, PORT_LABEL_GAP, SIDE_TRACK_GAP, anchorOffset, authoredOver, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, center, chain, contentBox, createDiagramNodes, dataKeyReader, diagramEdgeOf, diagramNodeOf, expandGroupEdges, extent, footprintNode, force, forceRelaxation, forestOf, graphOrder, grownBy, insetOf, isPinned, isPromise, labelCenter, labelSpotAt, layered, layeredPlan, layeredRoutes, layoutBody, layoutOwnsPort, layoutOwnsRoute, layoutRowPorts, layoutWritesRoute, marginOf, measureBody, outlineDetail, outlineFromD, outlinePath, outlinePolyline, packAcross, packClusters, pinnedSet, planOf, plannerPlan, portLabelBox, portLabelsOf, portOf, portSitesOf, portsOf, portsOnOutline, rankGaps, ranksByLength, ranksOf, rayHit, registerOutline, resolveOutline, routeHolds, rowHeights, samePoints, seededOrder, settle, sizeToBody, subpathOf, subtreeBlocks, translated, tree, untilSettled, widenForMinWidth };
3870
- //# sourceMappingURL=chunk-FDCODMFW.js.map
3871
- //# sourceMappingURL=chunk-FDCODMFW.js.map
4081
+ // src/radial.ts
4082
+ var radial = (graph, opts = {}) => {
4083
+ const nodeGap = opts.nodeGap ?? DEFAULT_NODE_GAP;
4084
+ const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
4085
+ const { roots, children } = forestOf(graph, "y");
4086
+ const blocks = subtreeBlocks(children, "y", nodeGap);
4087
+ const order = graphOrder(graph);
4088
+ const placed = /* @__PURE__ */ new Map();
4089
+ let top = 0;
4090
+ for (const root of roots) {
4091
+ const tree2 = mindMap(root, children, order, blocks, nodeGap, rankGap);
4092
+ for (const [id, p] of tree2.at) placed.set(id, { x: p.x, y: p.y - tree2.top + top });
4093
+ top += tree2.bottom - tree2.top + nodeGap;
4094
+ }
4095
+ return translated(graph, placed, pinnedSet(graph, opts), opts.tolerance);
4096
+ };
4097
+ function mindMap(root, children, order, blocks, nodeGap, rankGap) {
4098
+ const at2 = /* @__PURE__ */ new Map([[root.id, { x: -root.bounds.width / 2, y: -root.bounds.height / 2 }]]);
4099
+ let top = -root.bounds.height / 2;
4100
+ let bottom = root.bounds.height / 2;
4101
+ const [right, left] = sides(root, children, order);
4102
+ for (const [sign, branches] of [[1, right], [-1, left]]) {
4103
+ if (branches.length === 0) continue;
4104
+ const span = branches.reduce((sum, b) => sum + blocks.span(b), 0) + (branches.length - 1) * nodeGap;
4105
+ const ys = /* @__PURE__ */ new Map();
4106
+ let cursor = -span / 2;
4107
+ for (const branch of branches) {
4108
+ blocks.lay(branch, cursor, ys);
4109
+ cursor += blocks.span(branch) + nodeGap;
4110
+ }
4111
+ top = Math.min(top, -span / 2);
4112
+ bottom = Math.max(bottom, span / 2);
4113
+ for (const [id, x] of sideColumns(branches, children, root.bounds.width / 2 + rankGap, rankGap, sign)) {
4114
+ at2.set(id, { x, y: ys.get(id) });
4115
+ }
4116
+ }
4117
+ return { at: at2, top, bottom };
4118
+ }
4119
+ function sides(root, children, order) {
4120
+ const weight = (node) => 1 + (children.get(node.id) ?? []).reduce((sum, k) => sum + weight(k), 0);
4121
+ const kids = children.get(root.id) ?? [];
4122
+ const half = root.bounds.width / 2;
4123
+ const mid = root.bounds.x + half;
4124
+ const standing = (k) => {
4125
+ const c = k.bounds.x + k.bounds.width / 2 - mid;
4126
+ return c > half ? 1 : c < -half ? -1 : 0;
4127
+ };
4128
+ const stands = new Map(kids.map((k) => [k, standing(k)]));
4129
+ const arranged = [...stands.values()].includes(1) && [...stands.values()].includes(-1);
4130
+ const right = /* @__PURE__ */ new Set();
4131
+ let r = 0;
4132
+ let l = 0;
4133
+ const dealt = arranged ? kids.filter((k) => stands.get(k) !== 0) : [];
4134
+ for (const k of dealt) {
4135
+ if (stands.get(k) === 1) {
4136
+ right.add(k);
4137
+ r += weight(k);
4138
+ } else l += weight(k);
4139
+ }
4140
+ for (const child of kids.filter((k) => !dealt.includes(k)).sort((a, b) => order(a.id) - order(b.id))) {
4141
+ const w = weight(child);
4142
+ if (r <= l) {
4143
+ right.add(child);
4144
+ r += w;
4145
+ } else l += w;
4146
+ }
4147
+ return [kids.filter((k) => right.has(k)), kids.filter((k) => !right.has(k))];
4148
+ }
4149
+ function sideColumns(branches, children, from, rankGap, sign) {
4150
+ const rows = [];
4151
+ const visit = (node, depth) => {
4152
+ (rows[depth] ??= []).push(node);
4153
+ for (const kid of children.get(node.id) ?? []) visit(kid, depth + 1);
4154
+ };
4155
+ for (const branch of branches) visit(branch, 0);
4156
+ const xs = /* @__PURE__ */ new Map();
4157
+ let start2 = from;
4158
+ for (const row of rows) {
4159
+ const band = Math.max(...row.map((n) => n.bounds.width));
4160
+ for (const node of row) {
4161
+ const near = start2 + (band - node.bounds.width) / 2;
4162
+ xs.set(node.id, sign === 1 ? near : -near - node.bounds.width);
4163
+ }
4164
+ start2 += band + rankGap;
4165
+ }
4166
+ return xs;
4167
+ }
4168
+
4169
+ export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DEVICE_SIZE, DIAGRAM_TRAIT_KEY, DIVIDER_HEIGHT, EVENT_SIZE, GATEWAY_SIZE, PORT_LABEL_GAP, SIDE_TRACK_GAP, anchorOffset, authoredOver, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, center, chain, contentBox, createDiagramNodes, dataKeyReader, diagramEdgeOf, diagramNodeOf, expandGroupEdges, extent, footprintNode, force, forceRelaxation, forestOf, graphOrder, grid, grownBy, insetOf, isPinned, isPromise, labelCenter, labelSpotAt, layered, layeredPlan, layeredRoutes, layoutBody, layoutOwnsPort, layoutOwnsRoute, layoutRowPorts, layoutWritesRoute, marginOf, measureBody, outlineDetail, outlineFromD, outlinePath, outlinePolyline, outlineSolid, packAcross, packClusters, pinnedSet, planOf, plannerPlan, portLabelBox, portLabelsOf, portOf, portSitesOf, portsOf, portsOnOutline, radial, rankGaps, ranksByLength, ranksOf, rayHit, registerOutline, resolveOutline, routeHolds, rowHeights, samePoints, seededOrder, settle, sizeToBody, subpathOf, subtreeBlocks, translated, tree, untilSettled, widenForMinWidth };
4170
+ //# sourceMappingURL=chunk-7BV2MBED.js.map
4171
+ //# sourceMappingURL=chunk-7BV2MBED.js.map