@weasel-js/diagram 1.14.2 → 1.15.1

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 (34) hide show
  1. package/README.md +114 -5
  2. package/dist/{chunk-GR2DK4S2.js → chunk-EIHPPOAM.js} +1480 -229
  3. package/dist/chunk-EIHPPOAM.js.map +1 -0
  4. package/dist/{chunk-AXL4JQBM.js → chunk-EIT4BFDK.js} +3 -3
  5. package/dist/{chunk-AXL4JQBM.js.map → chunk-EIT4BFDK.js.map} +1 -1
  6. package/dist/{chunk-DE7UILJA.js → chunk-NEX4QKQQ.js} +183 -23
  7. package/dist/chunk-NEX4QKQQ.js.map +1 -0
  8. package/dist/{chunk-FDCODMFW.js → chunk-UOFQBGJY.js} +904 -599
  9. package/dist/chunk-UOFQBGJY.js.map +1 -0
  10. package/dist/{chunk-IYUU5KUY.js → chunk-UUMLQEMR.js} +8 -96
  11. package/dist/chunk-UUMLQEMR.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 +3 -3
  15. package/dist/effect.d.ts +5 -5
  16. package/dist/effect.js +7 -7
  17. package/dist/effect.js.map +1 -1
  18. package/dist/icons.table.js +44 -0
  19. package/dist/icons.table.js.map +1 -1
  20. package/dist/icons.table.json +44 -0
  21. package/dist/index.d.ts +938 -37
  22. package/dist/index.js +708 -34
  23. package/dist/index.js.map +1 -1
  24. package/dist/{labelTrait-BBjRgxbz.d.ts → labelTrait-moIciyXr.d.ts} +4 -0
  25. package/dist/layout.d.ts +3 -3
  26. package/dist/{layout.entry-CbmGuqE-.d.ts → layout.entry-zstuljAl.d.ts} +2 -2
  27. package/dist/layout.js +2 -2
  28. package/dist/{stageTypes-UiO25hcn.d.ts → stageTypes-B7oF8Lww.d.ts} +1 -1
  29. package/dist/{types-B0n2CNTW.d.ts → types-D5ybp8Xh.d.ts} +32 -1
  30. package/package.json +11 -11
  31. package/dist/chunk-DE7UILJA.js.map +0 -1
  32. package/dist/chunk-FDCODMFW.js.map +0 -1
  33. package/dist/chunk-GR2DK4S2.js.map +0 -1
  34. 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 ?? [];
@@ -1605,442 +1731,180 @@ function routeHolds(from, to, waypoints, route) {
1605
1731
  return true;
1606
1732
  }
1607
1733
 
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;
1734
+ // src/layered/cycles.ts
1735
+ function backEdges(graph) {
1736
+ const back = /* @__PURE__ */ new Set();
1737
+ const done = /* @__PURE__ */ new Set();
1738
+ const onStack = /* @__PURE__ */ new Set();
1739
+ const walk = (id) => {
1740
+ if (done.has(id)) return;
1741
+ onStack.add(id);
1742
+ for (const edge of graph.outgoing(id)) {
1743
+ if (edge.to === id) continue;
1744
+ if (onStack.has(edge.to)) back.add(edge.id);
1745
+ else walk(edge.to);
1746
+ }
1747
+ onStack.delete(id);
1748
+ done.add(id);
1749
+ };
1750
+ for (const node of graph.nodes) walk(node.id);
1751
+ return back;
1623
1752
  }
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
- }
1753
+ function ranksOf(graph, back) {
1754
+ const ranks = /* @__PURE__ */ new Map();
1755
+ const resolving = /* @__PURE__ */ new Set();
1756
+ const rankOf = (id) => {
1757
+ const known = ranks.get(id);
1758
+ if (known !== void 0) return known;
1759
+ if (resolving.has(id)) return 0;
1760
+ resolving.add(id);
1761
+ let rank = 0;
1762
+ for (const edge of graph.incoming(id)) {
1763
+ if (back.has(edge.id) || edge.from === id) continue;
1764
+ rank = Math.max(rank, rankOf(edge.from) + 1);
1640
1765
  }
1766
+ resolving.delete(id);
1767
+ ranks.set(id, rank);
1768
+ return rank;
1641
1769
  };
1770
+ for (const node of graph.nodes) rankOf(node.id);
1771
+ return ranks;
1642
1772
  }
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);
1773
+
1774
+ // src/route.ts
1775
+ var FACES = {
1776
+ down: { out: "s", in: "n" },
1777
+ up: { out: "n", in: "s" },
1778
+ right: { out: "e", in: "w" },
1779
+ left: { out: "w", in: "e" }
1780
+ };
1781
+ function layeredRoutes(input) {
1782
+ const { ranks, members, compass } = input;
1783
+ const faces = FACES[input.direction ?? "down"];
1784
+ const span = (id) => {
1785
+ const rs = (members.get(id) ?? [id]).map((m) => ranks.get(m)).filter((r) => r !== void 0);
1786
+ return rs.length === 0 ? null : [Math.min(...rs), Math.max(...rs)];
1787
+ };
1788
+ const routes = /* @__PURE__ */ new Map();
1789
+ for (const edge of input.edges) {
1790
+ const from = span(edge.from);
1791
+ const to = span(edge.to);
1792
+ const route = {};
1793
+ if (from !== null && to !== null && (from[1] < to[0] || to[1] < from[0])) {
1794
+ const ahead = from[1] < to[0];
1795
+ if (compass.has(edge.from)) route.fromPort = ahead ? faces.out : faces.in;
1796
+ if (compass.has(edge.to)) route.toPort = ahead ? faces.in : faces.out;
1797
+ }
1798
+ const waypoints = input.waypoints.get(edge.id);
1799
+ if (waypoints !== void 0 && waypoints.length > 0) {
1800
+ route.waypoints = waypoints;
1801
+ const label = input.labels?.get(edge.id);
1802
+ if (label !== void 0) route.labelAt = label;
1803
+ }
1804
+ if (route.fromPort !== void 0 || route.toPort !== void 0 || route.waypoints !== void 0) {
1805
+ routes.set(edge.id, route);
1806
+ }
1653
1807
  }
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);
1808
+ return routes;
1809
+ }
1810
+ function layeredPorts(graph, ranks, direction) {
1811
+ const faces = FACES[direction ?? "down"];
1812
+ const compass = new Set(graph.groups?.map((g) => g.id) ?? []);
1813
+ for (const node of graph.nodes) {
1814
+ if (hasCompassPort(node, faces.out) && hasCompassPort(node, faces.in)) compass.add(node.id);
1660
1815
  }
1661
- const { inset: inset2 } = g.group;
1662
- return { x0: x0 - inset2.left, y0: y0 - inset2.top, x1: x1 + inset2.right, y1: y1 + inset2.bottom };
1816
+ const edges = /* @__PURE__ */ new Map();
1817
+ for (const e of graph.edges) {
1818
+ const id = e.origin?.id ?? e.id;
1819
+ if (!edges.has(id)) edges.set(id, { id, from: e.origin?.from ?? e.from, to: e.origin?.to ?? e.to });
1820
+ }
1821
+ const routes = layeredRoutes({
1822
+ edges: [...edges.values()],
1823
+ ranks,
1824
+ waypoints: /* @__PURE__ */ new Map(),
1825
+ members: new Map((graph.groups ?? []).map((g) => [g.id, g.members])),
1826
+ compass,
1827
+ ...direction !== void 0 ? { direction } : {}
1828
+ });
1829
+ return new Map([...routes].map(([id, r]) => [id, {
1830
+ ...r.fromPort !== void 0 ? { from: r.fromPort } : {},
1831
+ ...r.toPort !== void 0 ? { to: r.toPort } : {}
1832
+ }]));
1663
1833
  }
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 };
1834
+ function hasCompassPort(node, face) {
1835
+ const point = COMPASS[face];
1836
+ return (node.ports ?? DEFAULT_PORTS).some((p) => p.id === face && p.at.u === point.u && p.at.v === point.v);
1671
1837
  }
1672
- function holds(outer, inner) {
1673
- for (let at2 = inner.parent; at2 !== void 0; at2 = at2.parent) {
1674
- if (at2 === outer) return true;
1838
+ function authoredOver(edges, plan) {
1839
+ return new Map(edges.map((e) => {
1840
+ const ports = plan.ports?.get(e.id);
1841
+ const planned = plan.waypoints?.get(e.id);
1842
+ const fromPort = e.fromPort ?? ports?.from;
1843
+ const toPort = e.toPort ?? ports?.to;
1844
+ const waypoints = e.waypoints ?? (planned !== void 0 && planned.length > 0 ? planned : void 0);
1845
+ const labelAt = e.waypoints === void 0 && waypoints !== void 0 ? plan.labels?.get(e.id) : void 0;
1846
+ return [e.id, {
1847
+ ...fromPort ? { fromPort } : {},
1848
+ ...toPort ? { toPort } : {},
1849
+ ...waypoints ? { waypoints } : {},
1850
+ ...labelAt !== void 0 ? { labelAt } : {}
1851
+ }];
1852
+ }));
1853
+ }
1854
+
1855
+ // src/layered/rank.ts
1856
+ var GROUP_WEIGHT = 4;
1857
+ function ranksByLength(graph, back) {
1858
+ const index = /* @__PURE__ */ new Map();
1859
+ graph.nodes.forEach((node, i) => index.set(node.id, i));
1860
+ const arcs = [];
1861
+ for (const edge of graph.edges) {
1862
+ const from = index.get(edge.from);
1863
+ const to = index.get(edge.to);
1864
+ if (from === void 0 || to === void 0 || from === to) continue;
1865
+ arcs.push(back.has(edge.id) ? { tail: to, head: from, weight: 1, minlen: 1 } : { tail: from, head: to, weight: 1, minlen: 1 });
1675
1866
  }
1676
- return false;
1867
+ let count = graph.nodes.length;
1868
+ for (const group of graph.groups ?? []) {
1869
+ const members = [...new Set(group.members.map((id) => index.get(id)).filter((i) => i !== void 0))];
1870
+ if (members.length < 2) continue;
1871
+ const [top, bottom] = [count, count + 1];
1872
+ count += 2;
1873
+ for (const m of members) {
1874
+ arcs.push({ tail: top, head: m, weight: GROUP_WEIGHT, minlen: 0 });
1875
+ arcs.push({ tail: m, head: bottom, weight: GROUP_WEIGHT, minlen: 0 });
1876
+ }
1877
+ }
1878
+ const real = graph.nodes.length;
1879
+ const start2 = longestPath(count, arcs);
1880
+ const ranks = /* @__PURE__ */ new Map();
1881
+ for (const part of components(count, arcs)) {
1882
+ const rank = part.nodes.map((g) => start2[g]);
1883
+ simplex(part.arcs, rank);
1884
+ const own = part.nodes.flatMap((g, i) => g < real ? [i] : []);
1885
+ const min = Math.min(...own.map((i) => rank[i]));
1886
+ for (const i of own) ranks.set(graph.nodes[part.nodes[i]].id, rank[i] - min);
1887
+ }
1888
+ return ranks;
1677
1889
  }
1678
- function shove(bodies, x, y) {
1679
- for (const body of bodies) {
1680
- body.vx += x;
1681
- body.vy += y;
1890
+ function longestPath(count, arcs) {
1891
+ const rank = new Array(count).fill(0);
1892
+ const pending = new Array(count).fill(0);
1893
+ const out = Array.from({ length: count }, () => []);
1894
+ for (const arc2 of arcs) {
1895
+ pending[arc2.head]++;
1896
+ out[arc2.tail].push(arc2);
1897
+ }
1898
+ const ready = [];
1899
+ for (let i = 0; i < count; i++) if (pending[i] === 0) ready.push(i);
1900
+ for (let k = 0; k < ready.length; k++) {
1901
+ const u = ready[k];
1902
+ for (const { head: v, minlen } of out[u]) {
1903
+ rank[v] = Math.max(rank[v], rank[u] + minlen);
1904
+ if (--pending[v] === 0) ready.push(v);
1905
+ }
1682
1906
  }
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;
1907
+ return rank;
2044
1908
  }
2045
1909
  function components(count, arcs) {
2046
1910
  const near = Array.from({ length: count }, () => []);
@@ -2194,16 +2058,16 @@ function tighten(rank, arcs, inTree) {
2194
2058
  const adj = treeAdjacency(rank.length, arcs, inTree);
2195
2059
  const seen = rank.map(() => false);
2196
2060
  seen[0] = true;
2197
- const queue = [0];
2198
- for (let k = 0; k < queue.length; k++) {
2199
- const v = queue[k];
2061
+ const queue2 = [0];
2062
+ for (let k = 0; k < queue2.length; k++) {
2063
+ const v = queue2[k];
2200
2064
  for (const a of adj[v]) {
2201
2065
  const arc2 = arcs[a];
2202
2066
  const w = arc2.tail === v ? arc2.head : arc2.tail;
2203
2067
  if (seen[w]) continue;
2204
2068
  seen[w] = true;
2205
2069
  rank[w] = arc2.tail === v ? rank[v] + arc2.minlen : rank[v] - arc2.minlen;
2206
- queue.push(w);
2070
+ queue2.push(w);
2207
2071
  }
2208
2072
  }
2209
2073
  }
@@ -2302,13 +2166,13 @@ function expand(graph, nodeRanks, back, opts) {
2302
2166
  if (a === null || b === null || a[1] >= b[0]) continue;
2303
2167
  if (d.authored && b[0] - a[1] > 1) continue;
2304
2168
  const group = sharedGroup(from, to);
2305
- const start = crossCenter(from);
2306
- const end = crossCenter(to);
2169
+ const start2 = crossCenter(from);
2170
+ const end2 = crossCenter(to);
2307
2171
  const crossings = b[0] - a[1] - 1;
2308
2172
  const now = d.waypoints?.length === crossings * 2 ? d.waypoints : void 0;
2309
2173
  const seed = (j) => {
2310
2174
  if (now !== void 0) return now[(d.back ? crossings - 1 - j : j) * 2][axes.cross];
2311
- return start + (end - start) * (j + 1) / (crossings + 1);
2175
+ return start2 + (end2 - start2) * (j + 1) / (crossings + 1);
2312
2176
  };
2313
2177
  const labelRow = labeled.labelRow.get(d.id);
2314
2178
  let prev = inRank(from, a[1]);
@@ -2409,22 +2273,22 @@ function settlePlaces(rows, hops) {
2409
2273
  if (!hops.some((h) => h.fromPlaces !== void 0 || h.toPlaces !== void 0)) return [...hops];
2410
2274
  const pos = /* @__PURE__ */ new Map();
2411
2275
  for (const row of rows) row.forEach((n, i) => pos.set(n.id, i));
2412
- const sides = sidesOf(hops);
2276
+ const sides2 = sidesOf(hops);
2413
2277
  return hops.map((h) => {
2414
2278
  if (h.fromPlaces === void 0 && h.toPlaces === void 0) return h;
2415
2279
  const { fromPlaces, toPlaces, ...rest } = h;
2416
2280
  return {
2417
2281
  ...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) } : {}
2282
+ ...fromPlaces !== void 0 ? { fromAt: placeAmong(fromPlaces, h.to, sides2.down.get(h.from) ?? [], pos) } : {},
2283
+ ...toPlaces !== void 0 ? { toAt: placeAmong(toPlaces, h.from, sides2.up.get(h.to) ?? [], pos) } : {}
2420
2284
  };
2421
2285
  });
2422
2286
  }
2423
2287
 
2424
2288
  // 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);
2289
+ function ends(p, pos, sides2) {
2290
+ const fa = p.fromPlaces === void 0 ? p.fromAt : placeAmong(p.fromPlaces, p.to, sides2.down.get(p.from) ?? [], pos);
2291
+ const fb = p.toPlaces === void 0 ? p.toAt : placeAmong(p.toPlaces, p.from, sides2.up.get(p.to) ?? [], pos);
2428
2292
  return [at(pos.get(p.from), fa), at(pos.get(p.to), fb)];
2429
2293
  }
2430
2294
  var at = (pos, frac) => pos + 0.25 + frac / 2;
@@ -2445,16 +2309,16 @@ function positions(rows) {
2445
2309
  for (const row of rows) row.forEach((n, i) => pos.set(n.id, i));
2446
2310
  return pos;
2447
2311
  }
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]);
2312
+ function pairCrossings(pairs, pos, sides2) {
2313
+ const met = pairs.map((p) => ends(p, pos, sides2)).sort((p, q) => p[0] - q[0] || p[1] - q[1]);
2450
2314
  const lows = [...new Set(met.map(([, b]) => b))].sort((p, q) => p - q);
2451
2315
  const slot = new Map(lows.map((b, i) => [b, i]));
2452
2316
  const width = lows.length;
2453
2317
  const tree2 = new Array(width + 1).fill(0);
2454
2318
  let seen = 0;
2455
2319
  let count = 0;
2456
- for (const [, end] of met) {
2457
- const b = slot.get(end);
2320
+ for (const [, end2] of met) {
2321
+ const b = slot.get(end2);
2458
2322
  let atMost = 0;
2459
2323
  for (let i = b + 1; i > 0; i -= i & -i) atMost += tree2[i];
2460
2324
  count += seen - atMost;
@@ -2463,9 +2327,9 @@ function pairCrossings(pairs, pos, sides) {
2463
2327
  }
2464
2328
  return count;
2465
2329
  }
2466
- function total(rows, byRank, pos, sides) {
2330
+ function total(rows, byRank, pos, sides2) {
2467
2331
  let sum = 0;
2468
- for (let r = 0; r + 1 < rows.length; r++) sum += pairCrossings(byRank[r], pos, sides);
2332
+ for (let r = 0; r + 1 < rows.length; r++) sum += pairCrossings(byRank[r], pos, sides2);
2469
2333
  return sum;
2470
2334
  }
2471
2335
  function chainsOf(groups) {
@@ -2517,10 +2381,10 @@ function sortRank(row, key, chainOf, places = /* @__PURE__ */ new Map()) {
2517
2381
  });
2518
2382
  const out = [];
2519
2383
  const flatten = (block) => {
2520
- const place = block.group === void 0 ? void 0 : places.get(block.group);
2384
+ const place2 = block.group === void 0 ? void 0 : places.get(block.group);
2521
2385
  const items = block.items.map((item) => {
2522
- if (place !== void 0) {
2523
- const k = place.get("id" in item ? item.id : item.group) ?? Infinity;
2386
+ if (place2 !== void 0) {
2387
+ const k = place2.get("id" in item ? item.id : item.group) ?? Infinity;
2524
2388
  return { item, k, first: "id" in item ? firstOf.get(item) : item.first };
2525
2389
  }
2526
2390
  if ("id" in item) return { item, k: keyOf.get(item), first: firstOf.get(item) };
@@ -2600,15 +2464,15 @@ function orderRows(rows, hops, opts = {}) {
2600
2464
  }
2601
2465
  }
2602
2466
  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 };
2467
+ const sides2 = sidesOf(hops);
2468
+ const work = { rows: seeded, pos: positions(seeded), ups, downs, sides: sides2, chainOf, places };
2605
2469
  let best = seeded.map((row) => [...row]);
2606
- let fewest = total(work.rows, byRank, work.pos, sides);
2470
+ let fewest = total(work.rows, byRank, work.pos, sides2);
2607
2471
  for (let sweep = 0, stale = 0; sweep < maxSweeps && stale < patience && fewest > 0; sweep++) {
2608
2472
  if (sweep % 2 === 0) for (let r = 1; r < work.rows.length; r++) reorder(work, r, ups);
2609
2473
  else for (let r = work.rows.length - 2; r >= 0; r--) reorder(work, r, downs);
2610
2474
  transpose(work);
2611
- const n = total(work.rows, byRank, work.pos, sides);
2475
+ const n = total(work.rows, byRank, work.pos, sides2);
2612
2476
  if (n < fewest) {
2613
2477
  fewest = n;
2614
2478
  best = work.rows.map((row) => [...row]);
@@ -2645,9 +2509,9 @@ function levelRoutes(input) {
2645
2509
  out.ports.set(id, named(graph, [from, face], [to, face]));
2646
2510
  out.waypoints.set(id, around(a, b, others, axes, input.rankGap));
2647
2511
  } else {
2648
- const sides = SIDES[axes.cross];
2512
+ const sides2 = SIDES[axes.cross];
2649
2513
  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]));
2514
+ out.ports.set(id, named(graph, [from, ahead ? sides2.high : sides2.low], [to, ahead ? sides2.low : sides2.high]));
2651
2515
  }
2652
2516
  }
2653
2517
  return out;
@@ -2916,11 +2780,11 @@ function holdPins(input) {
2916
2780
  if (pins.length < 2) return { crossAt: new Map(input.crossAt), gaps: [...input.gaps], depths: natural, rankAt, unheld };
2917
2781
  const anchor = pins[0];
2918
2782
  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 } };
2783
+ let start2 = 0;
2784
+ for (let r = 0; r < a; r++) start2 += natural[r] + input.gaps[r];
2785
+ const bandInput = { natural, gaps: input.gaps, minGaps: input.minGaps, home: { rank: a, lo: start2 } };
2922
2786
  const anchorExt = extent(anchor.bounds, axes.rank);
2923
- const anchorRank = start + (natural[a] - anchorExt) / 2;
2787
+ const anchorRank = start2 + (natural[a] - anchorExt) / 2;
2924
2788
  const anchorWorld = worldOf({ cross: input.crossAt.get(anchor.id), rank: anchorRank }, anchorExt, axes);
2925
2789
  const target = (id) => {
2926
2790
  const node = graph.node(id);
@@ -3284,107 +3148,369 @@ function layeredPlan(graph, opts = {}) {
3284
3148
  }, graph, opts);
3285
3149
  return { ...last, moves };
3286
3150
  }
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 };
3151
+ function routed(edge, plan) {
3152
+ const { waypoints: _, ...rest } = edge;
3153
+ const route = plan.waypoints.get(edge.origin?.id ?? edge.id);
3154
+ return route === void 0 ? rest : { ...rest, waypoints: route };
3155
+ }
3156
+ function planOnce(graph, opts = {}) {
3157
+ const axes = axesFor(opts.direction);
3158
+ const nodeGap = opts.nodeGap ?? DEFAULT_NODE_GAP;
3159
+ const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
3160
+ const finding = opts.order === "barycenter";
3161
+ const back = backEdges(graph);
3162
+ const ranks = ranksByLength(graph, back);
3163
+ const expanded = expand(graph, ranks, back, { axes, nodeGap });
3164
+ let rows = finding ? orderRows(expanded.rows, expanded.hops, { groups: graph.groups ?? [] }) : expanded.rows;
3165
+ const grouped = (graph.groups?.length ?? 0) > 0;
3166
+ const pinned = pinnedSet(graph, opts);
3167
+ const across = () => {
3168
+ const preferred2 = placeAcross(rows, settlePlaces(rows, expanded.hops), axes.cross, nodeGap);
3169
+ return grouped ? packClusters(graph, rows, preferred2, axes, nodeGap) : preferred2;
3170
+ };
3171
+ let packed = across();
3172
+ for (let turn = 0; turn < rows.length; turn++) {
3173
+ const turned = turnOutward({ graph, rows, crossAt: packed, axes, pinned, hops: expanded.hops });
3174
+ if (turned === void 0) break;
3175
+ rows = turned;
3176
+ packed = across();
3177
+ }
3178
+ const spaced = grouped ? rankGaps(graph, rows, axes, rankGap) : rows.slice(1).map(() => rankGap);
3179
+ const minGaps = spaced.map((gap) => gap - rankGap + rankGap * MIN_GAP);
3180
+ for (const r of expanded.labelRows) {
3181
+ spaced[r - 1] = spaced[r - 1] - rankGap * (1 - LABEL_GAP);
3182
+ spaced[r] = spaced[r] - rankGap * (1 - LABEL_GAP);
3183
+ }
3184
+ const { crossAt, gaps, depths: depths2, rankAt, unheld } = holdPins({ graph, rows, crossAt: packed, gaps: spaced, pinned, axes, nodeGap, minGaps });
3185
+ const slots = /* @__PURE__ */ new Map();
3186
+ const bands = [];
3187
+ let along = 0;
3188
+ for (const [i, row] of rows.entries()) {
3189
+ const depth = depths2[i];
3190
+ bands.push([along, along + depth]);
3191
+ for (const node of row) {
3192
+ slots.set(node.id, { cross: crossAt.get(node.id), rank: rankAt.get(node.id) ?? along + (depth - extent(node.bounds, axes.rank)) / 2 });
3193
+ }
3194
+ along += depth + (gaps[i] ?? 0);
3195
+ }
3196
+ const placed = /* @__PURE__ */ new Map();
3197
+ for (const node of graph.nodes) {
3198
+ const slot = slots.get(node.id);
3199
+ if (slot !== void 0) placed.set(node.id, worldOf(slot, extent(node.bounds, axes.rank), axes));
3200
+ }
3201
+ const moves = translated(graph, placed, pinned, opts.tolerance);
3202
+ const offset = anchorOffset(graph, placed, pinned);
3203
+ const displaced = /* @__PURE__ */ new Set();
3204
+ for (const id of pinned) {
3205
+ const at2 = placed.get(id);
3206
+ const node = graph.node(id);
3207
+ if (at2 === void 0 || node === void 0) continue;
3208
+ 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);
3209
+ }
3210
+ const waypoints = /* @__PURE__ */ new Map();
3211
+ const labels = /* @__PURE__ */ new Map();
3212
+ const rankOf = /* @__PURE__ */ new Map();
3213
+ rows.forEach((row, r) => row.forEach((n) => rankOf.set(n.id, r)));
3214
+ const reach = boxReach(graph, rows, axes);
3215
+ const widthOf = new Map(rows.flat().map((n) => [n.id, extent(n.bounds, axes.cross)]));
3216
+ for (const [edge, chain2] of expanded.chains) {
3217
+ if (chain2.ends.some((id) => displaced.has(id))) continue;
3218
+ const beside = chain2.ids.indexOf(expanded.labels.get(edge) ?? "");
3219
+ const points = [];
3220
+ for (const id of chain2.ids) {
3221
+ const cross = crossAt.get(id) + (expanded.wires.get(id) ?? widthOf.get(id) / 2);
3222
+ const r = rankOf.get(id);
3223
+ const [lo, hi] = [bands[r][0] - reach.lo[r], bands[r][1] + reach.hi[r]];
3224
+ for (const rank of chain2.back ? [hi, lo] : [lo, hi]) {
3225
+ const at2 = worldOf({ cross, rank }, 0, axes);
3226
+ points.push({ x: at2.x + offset.x, y: at2.y + offset.y });
3227
+ }
3228
+ }
3229
+ waypoints.set(edge, points);
3230
+ if (beside >= 0) {
3231
+ const [a, b] = [points[beside * 2], points[beside * 2 + 1]];
3232
+ labels.set(edge, { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
3233
+ }
3234
+ }
3235
+ const ports = layeredPorts(graph, ranks, opts.direction);
3236
+ const boxes = new Map(graph.nodes.map((n) => [n.id, { ...n.bounds, ...moves.get(n.id) }]));
3237
+ const level = levelRoutes({ graph, boxes, axes, rankGap, ...opts.direction !== void 0 ? { direction: opts.direction } : {} });
3238
+ for (const [edge, named2] of level.ports) {
3239
+ ports.set(edge, named2);
3240
+ waypoints.delete(edge);
3241
+ labels.delete(edge);
3242
+ }
3243
+ for (const [edge, points] of level.waypoints) waypoints.set(edge, points);
3244
+ return { moves, ranks, back, waypoints, labels, ports, unheld };
3245
+ }
3246
+
3247
+ // src/layered.ts
3248
+ var layered = Object.assign(
3249
+ (graph, opts = {}) => layeredPlan(graph, opts).moves,
3250
+ { plan: layeredPlan }
3251
+ );
3252
+
3253
+ // src/forceGroups.ts
3254
+ function resolve(groups, byId) {
3255
+ const out = [];
3256
+ for (const group of groups) {
3257
+ const members = group.members.map((id) => byId.get(id)).filter((b) => b !== void 0);
3258
+ if (members.length > 0) out.push({ members, group, children: [] });
3259
+ }
3260
+ const byGroup = new Map(out.map((g) => [g.group.id, g]));
3261
+ for (const g of out) {
3262
+ const parent = g.group.parent === void 0 ? void 0 : byGroup.get(g.group.parent);
3263
+ if (parent === void 0) continue;
3264
+ g.parent = parent;
3265
+ parent.children.push(g);
3266
+ }
3267
+ return out;
3268
+ }
3269
+ function groupCohesion(groups, byId, strength) {
3270
+ const resolved = resolve(groups, byId);
3271
+ return (alpha) => {
3272
+ for (const { members } of resolved) {
3273
+ let cx = 0;
3274
+ let cy = 0;
3275
+ for (const m of members) {
3276
+ cx += m.x;
3277
+ cy += m.y;
3278
+ }
3279
+ cx /= members.length;
3280
+ cy /= members.length;
3281
+ for (const m of members) {
3282
+ m.vx += (cx - m.x) * strength * alpha;
3283
+ m.vy += (cy - m.y) * strength * alpha;
3284
+ }
3285
+ }
3286
+ };
3287
+ }
3288
+ function boxOf(g) {
3289
+ let x0 = Infinity;
3290
+ let y0 = Infinity;
3291
+ let x1 = -Infinity;
3292
+ let y1 = -Infinity;
3293
+ for (const m of g.members) {
3294
+ x0 = Math.min(x0, m.x - m.hw);
3295
+ y0 = Math.min(y0, m.y - m.hh);
3296
+ x1 = Math.max(x1, m.x + m.hw);
3297
+ y1 = Math.max(y1, m.y + m.hh);
3298
+ }
3299
+ for (const child of g.children) {
3300
+ const b = boxOf(child);
3301
+ x0 = Math.min(x0, b.x0);
3302
+ y0 = Math.min(y0, b.y0);
3303
+ x1 = Math.max(x1, b.x1);
3304
+ y1 = Math.max(y1, b.y1);
3305
+ }
3306
+ const { inset: inset2 } = g.group;
3307
+ return { x0: x0 - inset2.left, y0: y0 - inset2.top, x1: x1 + inset2.right, y1: y1 + inset2.bottom };
3308
+ }
3309
+ function clearance(a, b, padding) {
3310
+ const gapX = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0) + padding;
3311
+ const gapY = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0) + padding;
3312
+ if (gapX <= 0 || gapY <= 0) return null;
3313
+ const dx = b.x0 + b.x1 - (a.x0 + a.x1);
3314
+ const dy = b.y0 + b.y1 - (a.y0 + a.y1);
3315
+ return gapX < gapY ? { x: (Math.sign(dx) || 1) * gapX, y: 0 } : { x: 0, y: (Math.sign(dy) || 1) * gapY };
3316
+ }
3317
+ function holds(outer, inner) {
3318
+ for (let at2 = inner.parent; at2 !== void 0; at2 = at2.parent) {
3319
+ if (at2 === outer) return true;
3320
+ }
3321
+ return false;
3322
+ }
3323
+ function shove(bodies, x, y) {
3324
+ for (const body of bodies) {
3325
+ body.vx += x;
3326
+ body.vy += y;
3327
+ }
3328
+ }
3329
+ function separateGroups(groups, bodies, byId, padding) {
3330
+ const resolved = resolve(groups, byId);
3331
+ const memberSets = resolved.map((g) => new Set(g.members));
3332
+ return () => {
3333
+ const boxes = resolved.map(boxOf);
3334
+ for (let i = 0; i < resolved.length; i++) {
3335
+ for (let j = i + 1; j < resolved.length; j++) {
3336
+ if (holds(resolved[i], resolved[j]) || holds(resolved[j], resolved[i])) continue;
3337
+ const push = clearance(boxes[i], boxes[j], padding);
3338
+ if (push === null) continue;
3339
+ shove(resolved[i].members, -push.x / 2, -push.y / 2);
3340
+ shove(resolved[j].members, push.x / 2, push.y / 2);
3341
+ }
3342
+ for (const body of bodies) {
3343
+ if (memberSets[i].has(body)) continue;
3344
+ const box = { x0: body.x - body.hw, y0: body.y - body.hh, x1: body.x + body.hw, y1: body.y + body.hh };
3345
+ const push = clearance(boxes[i], box, padding);
3346
+ if (push === null) continue;
3347
+ shove(resolved[i].members, -push.x / 2, -push.y / 2);
3348
+ body.vx += push.x / 2;
3349
+ body.vy += push.y / 2;
3350
+ }
3351
+ }
3352
+ };
3353
+ }
3354
+
3355
+ // src/force.ts
3356
+ var DEFAULTS = {
3357
+ iterations: 300,
3358
+ linkDistance: 160,
3359
+ linkStrength: 0.35,
3360
+ charge: -3e3,
3361
+ gravity: 0.02,
3362
+ padding: 24,
3363
+ groupStrength: 0.2
3364
+ };
3365
+ var GOLDEN = Math.PI * (3 - Math.sqrt(5));
3366
+ var NUDGE2 = 0.5;
3367
+ function charge(bodies, strength) {
3368
+ return (alpha) => {
3369
+ for (let i = 0; i < bodies.length; i++) {
3370
+ const a = bodies[i];
3371
+ for (let j = i + 1; j < bodies.length; j++) {
3372
+ const b = bodies[j];
3373
+ const dx = b.x - a.x;
3374
+ const dy = b.y - a.y;
3375
+ const d2 = dx * dx + dy * dy;
3376
+ if (d2 === 0) continue;
3377
+ const w = strength * alpha / d2;
3378
+ a.vx += dx * w;
3379
+ a.vy += dy * w;
3380
+ b.vx -= dx * w;
3381
+ b.vy -= dy * w;
3382
+ }
3383
+ }
3384
+ };
3385
+ }
3386
+ function links(bodies, graph, distance, strength) {
3387
+ return (alpha) => {
3388
+ for (const edge of graph.edges) {
3389
+ const a = bodies.get(edge.from);
3390
+ const b = bodies.get(edge.to);
3391
+ if (a === void 0 || b === void 0 || a === b) continue;
3392
+ const dx = b.x - a.x;
3393
+ const dy = b.y - a.y;
3394
+ const d = Math.hypot(dx, dy);
3395
+ if (d === 0) continue;
3396
+ const pull = (d - distance) / d * alpha * strength;
3397
+ a.vx += dx * pull;
3398
+ a.vy += dy * pull;
3399
+ b.vx -= dx * pull;
3400
+ b.vy -= dy * pull;
3401
+ }
3402
+ };
3403
+ }
3404
+ function separateBoxes(bodies, padding) {
3405
+ return () => {
3406
+ for (let i = 0; i < bodies.length; i++) {
3407
+ const a = bodies[i];
3408
+ for (let j = i + 1; j < bodies.length; j++) {
3409
+ const b = bodies[j];
3410
+ const dx = b.x - a.x;
3411
+ const dy = b.y - a.y;
3412
+ const gapX = a.hw + b.hw + padding - Math.abs(dx);
3413
+ const gapY = a.hh + b.hh + padding - Math.abs(dy);
3414
+ if (gapX <= 0 || gapY <= 0) continue;
3415
+ if (gapX < gapY) {
3416
+ const push = (Math.sign(dx) || 1) * gapX * 0.5;
3417
+ a.vx -= push;
3418
+ b.vx += push;
3419
+ } else {
3420
+ const push = (Math.sign(dy) || 1) * gapY * 0.5;
3421
+ a.vy -= push;
3422
+ b.vy += push;
3423
+ }
3424
+ }
3425
+ }
3426
+ };
3291
3427
  }
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;
3428
+ function gravity(bodies, at2, strength) {
3429
+ return (alpha) => {
3430
+ for (const body of bodies) {
3431
+ body.vx += (at2.x - body.x) * strength * alpha;
3432
+ body.vy += (at2.y - body.y) * strength * alpha;
3433
+ }
3306
3434
  };
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 });
3435
+ }
3436
+ function forceRelaxation(graph, opts = {}) {
3437
+ const {
3438
+ linkDistance = DEFAULTS.linkDistance,
3439
+ linkStrength = DEFAULTS.linkStrength,
3440
+ charge: chargeStrength = DEFAULTS.charge,
3441
+ gravity: gravityStrength = DEFAULTS.gravity,
3442
+ padding = DEFAULTS.padding,
3443
+ groupStrength = DEFAULTS.groupStrength
3444
+ } = opts;
3445
+ const pinned = pinnedSet(graph, opts);
3446
+ const bodies = graph.nodes.map((node) => {
3447
+ const body = {
3448
+ id: node.id,
3449
+ x: center(node.bounds, "x"),
3450
+ y: center(node.bounds, "y"),
3451
+ hw: node.bounds.width / 2,
3452
+ hh: node.bounds.height / 2
3453
+ };
3454
+ if (pinned.has(node.id)) {
3455
+ body.fx = body.x;
3456
+ body.fy = body.y;
3329
3457
  }
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 });
3458
+ return body;
3459
+ });
3460
+ separate(bodies);
3461
+ const byId = new Map(bodies.map((b) => [b.id, b]));
3462
+ const centroid = {
3463
+ x: bodies.reduce((sum, b) => sum + b.x, 0) / bodies.length,
3464
+ y: bodies.reduce((sum, b) => sum + b.y, 0) / bodies.length
3465
+ };
3466
+ const groups = graph.groups ?? [];
3467
+ const sim = createSimulation({
3468
+ nodes: bodies,
3469
+ forces: [
3470
+ charge(bodies, chargeStrength),
3471
+ links(byId, graph, linkDistance, linkStrength),
3472
+ gravity(bodies, centroid, gravityStrength),
3473
+ separateBoxes(bodies, padding),
3474
+ ...groups.length > 0 ? [groupCohesion(groups, byId, groupStrength), separateGroups(groups, bodies, byId, padding)] : []
3475
+ ]
3476
+ });
3477
+ return {
3478
+ bodies,
3479
+ byId,
3480
+ pinned,
3481
+ sim,
3482
+ placed() {
3483
+ const placed = /* @__PURE__ */ new Map();
3484
+ for (const node of graph.nodes) {
3485
+ const body = byId.get(node.id);
3486
+ placed.set(node.id, {
3487
+ x: body.x - node.bounds.width / 2,
3488
+ y: body.y - node.bounds.height / 2
3489
+ });
3363
3490
  }
3491
+ return placed;
3364
3492
  }
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 };
3493
+ };
3494
+ }
3495
+ var force = (graph, opts = {}) => {
3496
+ if (graph.nodes.length === 0) return /* @__PURE__ */ new Map();
3497
+ const relaxation = forceRelaxation(graph, opts);
3498
+ relaxation.sim.tick(opts.iterations ?? DEFAULTS.iterations);
3499
+ return translated(graph, relaxation.placed(), relaxation.pinned, opts.tolerance);
3500
+ };
3501
+ function separate(bodies) {
3502
+ const seen = /* @__PURE__ */ new Map();
3503
+ bodies.forEach((body, i) => {
3504
+ const key = `${body.x},${body.y}`;
3505
+ const nth = seen.get(key) ?? 0;
3506
+ seen.set(key, nth + 1);
3507
+ if (nth === 0 || body.fx != null || body.fy != null) return;
3508
+ const angle = GOLDEN * i;
3509
+ const radius = NUDGE2 * nth;
3510
+ body.x += Math.cos(angle) * radius;
3511
+ body.y += Math.sin(angle) * radius;
3512
+ });
3381
3513
  }
3382
-
3383
- // src/layered.ts
3384
- var layered = Object.assign(
3385
- (graph, opts = {}) => layeredPlan(graph, opts).moves,
3386
- { plan: layeredPlan }
3387
- );
3388
3514
 
3389
3515
  // src/chainGroups.ts
3390
3516
  function depths(groups) {
@@ -3446,8 +3572,8 @@ function nearest(members, from, index) {
3446
3572
  }
3447
3573
  return best;
3448
3574
  }
3449
- function clearColumns(graph, ordered, index, owner, start) {
3450
- const level = new Map(start);
3575
+ function clearColumns(graph, ordered, index, owner, start2) {
3576
+ const level = new Map(start2);
3451
3577
  const columns = /* @__PURE__ */ new Map();
3452
3578
  for (const node of graph.nodes) {
3453
3579
  if (index.has(node.id)) continue;
@@ -3482,19 +3608,19 @@ function lineGaps(graph, line, hanging, rankGap, axes, packed) {
3482
3608
  return bandGaps(graph, columns, axes, columns.slice(1).map(() => rankGap), (id) => packed.get(id)?.rank);
3483
3609
  }
3484
3610
  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);
3611
+ const levels2 = Array.from(levelSize, () => []);
3612
+ for (const node of graph.nodes) (levels2[hanging.level.get(node.id)] ??= []).push(node);
3487
3613
  const outward = { rank: axes.cross, cross: axes.rank, sign: before ? -1 : 1 };
3488
3614
  const from = [0];
3489
3615
  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) => {
3616
+ return bandGaps(graph, levels2, outward, base, (id) => {
3491
3617
  const node = graph.node(id);
3492
3618
  const at2 = hanging.level.get(id);
3493
3619
  if (node === void 0 || at2 === void 0) return void 0;
3494
3620
  return at2 === 0 ? (levelSize[0] - extent(node.bounds, outward.rank)) / 2 : from[at2];
3495
3621
  });
3496
3622
  }
3497
- function bandGaps(graph, bands, axes, gaps, start) {
3623
+ function bandGaps(graph, bands, axes, gaps, start2) {
3498
3624
  const insets = rankGaps(graph, bands, axes, 0);
3499
3625
  const out = gaps.map((gap, i) => gap + (insets[i] ?? 0));
3500
3626
  const groups = graph.groups ?? [];
@@ -3507,7 +3633,7 @@ function bandGaps(graph, bands, axes, gaps, start) {
3507
3633
  const span = (id, now) => {
3508
3634
  let [lo, hi] = [Infinity, -Infinity];
3509
3635
  for (const m of byId.get(id).members) {
3510
- const at2 = start(m);
3636
+ const at2 = start2(m);
3511
3637
  const band = bandOf.get(m);
3512
3638
  const node = graph.node(m);
3513
3639
  if (at2 === void 0 || band === void 0 || node === void 0) continue;
@@ -3517,7 +3643,7 @@ function bandGaps(graph, bands, axes, gaps, start) {
3517
3643
  return { lo, hi };
3518
3644
  };
3519
3645
  widenForMinWidth(deepestFirst(groups).map((g) => {
3520
- const placed = g.members.flatMap((id) => bandOf.has(id) && start(id) !== void 0 ? [bandOf.get(id)] : []);
3646
+ const placed = g.members.flatMap((id) => bandOf.has(id) && start2(id) !== void 0 ? [bandOf.get(id)] : []);
3521
3647
  return {
3522
3648
  id: g.id,
3523
3649
  ...g.minWidth !== void 0 ? { minWidth: g.minWidth } : {},
@@ -3589,8 +3715,8 @@ function sidesOf2(graph, hanging, moves, axes, before) {
3589
3715
  return byLine;
3590
3716
  }
3591
3717
  var SIDE_TRACK_GAP = 12;
3592
- function turning(sides) {
3593
- const sideways = sides.filter((s) => Math.abs(s.hung - s.port) > 0.5);
3718
+ function turning(sides2) {
3719
+ const sideways = sides2.filter((s) => Math.abs(s.hung - s.port) > 0.5);
3594
3720
  return [
3595
3721
  sideways.filter((s) => s.hung < s.port).sort((a, b) => a.port - b.port),
3596
3722
  sideways.filter((s) => s.hung > s.port).sort((a, b) => b.port - a.port)
@@ -3598,18 +3724,18 @@ function turning(sides) {
3598
3724
  }
3599
3725
  function tracksNeeded(graph, hanging, moves, axes, before) {
3600
3726
  let most = 0;
3601
- for (const sides of sidesOf2(graph, hanging, moves, axes, before).values()) {
3602
- const [low, high] = turning(sides);
3727
+ for (const sides2 of sidesOf2(graph, hanging, moves, axes, before).values()) {
3728
+ const [low, high] = turning(sides2);
3603
3729
  most = Math.max(most, low.length, high.length);
3604
3730
  }
3605
3731
  return most;
3606
3732
  }
3607
3733
  function sideRoutes(graph, hanging, moves, axes, before) {
3608
3734
  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));
3735
+ for (const sides2 of sidesOf2(graph, hanging, moves, axes, before).values()) {
3736
+ const [low, high] = turning(sides2);
3737
+ const lineEdge = sides2[0].lineEdge;
3738
+ const hungEdge = before ? Math.max(...sides2.map((s) => s.hungEdge)) : Math.min(...sides2.map((s) => s.hungEdge));
3613
3739
  const step = (hungEdge - lineEdge) / (Math.max(low.length, high.length) + 1);
3614
3740
  for (const run of [low, high]) {
3615
3741
  run.forEach((s, k) => {
@@ -3661,12 +3787,12 @@ function lineOf(graph, ids) {
3661
3787
  if (given.length > 0) return given;
3662
3788
  const back = backEdges(graph);
3663
3789
  const ranks = ranksOf(graph, back);
3664
- let end;
3790
+ let end2;
3665
3791
  for (const node of graph.nodes) {
3666
- if (end === void 0 || ranks.get(node.id) > ranks.get(end.id)) end = node;
3792
+ if (end2 === void 0 || ranks.get(node.id) > ranks.get(end2.id)) end2 = node;
3667
3793
  }
3668
3794
  const line = [];
3669
- for (let at2 = end; at2 !== void 0; ) {
3795
+ for (let at2 = end2; at2 !== void 0; ) {
3670
3796
  line.unshift(at2);
3671
3797
  const rank = ranks.get(at2.id);
3672
3798
  const edge = graph.incoming(at2.id).find((e) => !back.has(e.id) && ranks.get(e.from) === rank - 1);
@@ -3728,10 +3854,10 @@ function chainPlan(graph, opts = {}) {
3728
3854
  };
3729
3855
  const slots = (rank, level2) => slotsFor(line, rows, levelSize, axes, { node: nodeGap, rank, level: level2 }, before);
3730
3856
  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);
3857
+ const place2 = (side2) => settle(graph, slots(gaps, across(side2)), opts);
3858
+ let moves = place2(nodeGap);
3733
3859
  const side = (tracksNeeded(graph, hanging, moves, axes, before) + 1) * SIDE_TRACK_GAP;
3734
- if (side > nodeGap) moves = place(side);
3860
+ if (side > nodeGap) moves = place2(side);
3735
3861
  return { moves, waypoints: sideRoutes(graph, hanging, moves, axes, before) };
3736
3862
  }
3737
3863
  function slotsFor(line, rows, levelSize, axes, gaps, before) {
@@ -3766,6 +3892,97 @@ function slotsFor(line, rows, levelSize, axes, gaps, before) {
3766
3892
  return slots;
3767
3893
  }
3768
3894
 
3895
+ // src/grid.ts
3896
+ function levels(graph) {
3897
+ const order = graphOrder(graph);
3898
+ const groups = graph.groups ?? [];
3899
+ const first = new Map(groups.map((g) => [g.id, Math.min(...g.members.map(order))]));
3900
+ const at2 = (id) => first.get(id) ?? order(id);
3901
+ const out = /* @__PURE__ */ new Map();
3902
+ const add = (level, id) => out.set(level, [...out.get(level) ?? [], id]);
3903
+ for (const n of graph.nodes) add(n.group ?? null, n.id);
3904
+ for (const g of groups) add(g.parent ?? null, g.id);
3905
+ for (const ids of out.values()) ids.sort((a, b) => at2(a) - at2(b));
3906
+ return out;
3907
+ }
3908
+ function deal(ids, sizeOf, columns, grid2) {
3909
+ const items = [];
3910
+ let row = 0;
3911
+ let col = 0;
3912
+ for (const id of ids) {
3913
+ col += Math.max(0, Math.floor(grid2.skips?.[id] ?? 0));
3914
+ row += Math.floor(col / columns);
3915
+ col %= columns;
3916
+ const span = Math.min(columns, Math.max(1, Math.floor(grid2.spans?.[id] ?? 1)));
3917
+ if (col + span > columns) {
3918
+ row++;
3919
+ col = 0;
3920
+ }
3921
+ items.push({ id, size: sizeOf(id), row, col, span });
3922
+ col += span;
3923
+ }
3924
+ return { items, columns };
3925
+ }
3926
+ function place(cells, gap, rowGap) {
3927
+ const width = Math.max(0, ...cells.items.map((i) => (i.size.width - (i.span - 1) * gap) / i.span));
3928
+ const rows = [];
3929
+ for (const i of cells.items) rows[i.row] = Math.max(rows[i.row] ?? 0, i.size.height);
3930
+ const tops = [];
3931
+ let y = 0;
3932
+ for (let r = 0; r < rows.length; r++) {
3933
+ tops[r] = y;
3934
+ y += (rows[r] ?? 0) + rowGap;
3935
+ }
3936
+ const at2 = /* @__PURE__ */ new Map();
3937
+ for (const i of cells.items) {
3938
+ const across = i.span * width + (i.span - 1) * gap;
3939
+ 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 });
3940
+ }
3941
+ const used = Math.max(0, ...cells.items.map((i) => i.col + i.span));
3942
+ return { at: at2, size: { width: used === 0 ? 0 : used * width + (used - 1) * gap, height: rows.length === 0 ? 0 : y - rowGap } };
3943
+ }
3944
+ var grid = (graph, opts = {}) => {
3945
+ const gap = opts.nodeGap ?? DEFAULT_NODE_GAP;
3946
+ const rowGap = opts.rankGap ?? gap;
3947
+ const spec = opts.grid ?? {};
3948
+ const byLevel = levels(graph);
3949
+ const groups = new Map((graph.groups ?? []).map((g) => [g.id, g]));
3950
+ const inner = /* @__PURE__ */ new Map();
3951
+ const sizeOf = (id) => {
3952
+ const g = groups.get(id);
3953
+ if (g === void 0) return graph.node(id).bounds;
3954
+ const laid = layOut(id);
3955
+ return {
3956
+ width: Math.max(g.minWidth ?? 0, laid.size.width + g.inset.left + g.inset.right),
3957
+ height: laid.size.height + g.inset.top + g.inset.bottom
3958
+ };
3959
+ };
3960
+ const layOut = (level) => {
3961
+ const done = level === null ? void 0 : inner.get(level);
3962
+ if (done !== void 0) return done;
3963
+ const ids = byLevel.get(level) ?? [];
3964
+ const columns = Math.max(1, Math.floor((level === null ? spec.columns : spec.groupColumns?.[level]) ?? ids.length));
3965
+ const laid = place(deal(ids, sizeOf, columns, spec), gap, rowGap);
3966
+ if (level !== null) inner.set(level, laid);
3967
+ return laid;
3968
+ };
3969
+ const placed = /* @__PURE__ */ new Map();
3970
+ const put = (level, origin) => {
3971
+ for (const [id, p] of layOut(level).at) {
3972
+ const corner = { x: origin.x + p.x, y: origin.y + p.y };
3973
+ const g = groups.get(id);
3974
+ if (g === void 0) {
3975
+ placed.set(id, corner);
3976
+ continue;
3977
+ }
3978
+ const slack = sizeOf(id).width - (inner.get(id).size.width + g.inset.left + g.inset.right);
3979
+ put(id, { x: corner.x + g.inset.left + slack / 2, y: corner.y + g.inset.top });
3980
+ }
3981
+ };
3982
+ put(null, { x: 0, y: 0 });
3983
+ return translated(graph, placed, pinnedSet(graph, opts), opts.tolerance);
3984
+ };
3985
+
3769
3986
  // src/tree.ts
3770
3987
  function forestOf(graph, crossAxis) {
3771
3988
  const order = graphOrder(graph);
@@ -3866,6 +4083,94 @@ var treeOnce = (graph, opts = {}) => {
3866
4083
  return settle(graph, slots, opts);
3867
4084
  };
3868
4085
 
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
4086
+ // src/radial.ts
4087
+ var radial = (graph, opts = {}) => {
4088
+ const nodeGap = opts.nodeGap ?? DEFAULT_NODE_GAP;
4089
+ const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
4090
+ const { roots, children } = forestOf(graph, "y");
4091
+ const blocks = subtreeBlocks(children, "y", nodeGap);
4092
+ const order = graphOrder(graph);
4093
+ const placed = /* @__PURE__ */ new Map();
4094
+ let top = 0;
4095
+ for (const root of roots) {
4096
+ const tree2 = mindMap(root, children, order, blocks, nodeGap, rankGap);
4097
+ for (const [id, p] of tree2.at) placed.set(id, { x: p.x, y: p.y - tree2.top + top });
4098
+ top += tree2.bottom - tree2.top + nodeGap;
4099
+ }
4100
+ return translated(graph, placed, pinnedSet(graph, opts), opts.tolerance);
4101
+ };
4102
+ function mindMap(root, children, order, blocks, nodeGap, rankGap) {
4103
+ const at2 = /* @__PURE__ */ new Map([[root.id, { x: -root.bounds.width / 2, y: -root.bounds.height / 2 }]]);
4104
+ let top = -root.bounds.height / 2;
4105
+ let bottom = root.bounds.height / 2;
4106
+ const [right, left] = sides(root, children, order);
4107
+ for (const [sign, branches] of [[1, right], [-1, left]]) {
4108
+ if (branches.length === 0) continue;
4109
+ const span = branches.reduce((sum, b) => sum + blocks.span(b), 0) + (branches.length - 1) * nodeGap;
4110
+ const ys = /* @__PURE__ */ new Map();
4111
+ let cursor = -span / 2;
4112
+ for (const branch of branches) {
4113
+ blocks.lay(branch, cursor, ys);
4114
+ cursor += blocks.span(branch) + nodeGap;
4115
+ }
4116
+ top = Math.min(top, -span / 2);
4117
+ bottom = Math.max(bottom, span / 2);
4118
+ for (const [id, x] of sideColumns(branches, children, root.bounds.width / 2 + rankGap, rankGap, sign)) {
4119
+ at2.set(id, { x, y: ys.get(id) });
4120
+ }
4121
+ }
4122
+ return { at: at2, top, bottom };
4123
+ }
4124
+ function sides(root, children, order) {
4125
+ const weight = (node) => 1 + (children.get(node.id) ?? []).reduce((sum, k) => sum + weight(k), 0);
4126
+ const kids = children.get(root.id) ?? [];
4127
+ const half = root.bounds.width / 2;
4128
+ const mid = root.bounds.x + half;
4129
+ const standing = (k) => {
4130
+ const c = k.bounds.x + k.bounds.width / 2 - mid;
4131
+ return c > half ? 1 : c < -half ? -1 : 0;
4132
+ };
4133
+ const stands = new Map(kids.map((k) => [k, standing(k)]));
4134
+ const arranged = [...stands.values()].includes(1) && [...stands.values()].includes(-1);
4135
+ const right = /* @__PURE__ */ new Set();
4136
+ let r = 0;
4137
+ let l = 0;
4138
+ const dealt = arranged ? kids.filter((k) => stands.get(k) !== 0) : [];
4139
+ for (const k of dealt) {
4140
+ if (stands.get(k) === 1) {
4141
+ right.add(k);
4142
+ r += weight(k);
4143
+ } else l += weight(k);
4144
+ }
4145
+ for (const child of kids.filter((k) => !dealt.includes(k)).sort((a, b) => order(a.id) - order(b.id))) {
4146
+ const w = weight(child);
4147
+ if (r <= l) {
4148
+ right.add(child);
4149
+ r += w;
4150
+ } else l += w;
4151
+ }
4152
+ return [kids.filter((k) => right.has(k)), kids.filter((k) => !right.has(k))];
4153
+ }
4154
+ function sideColumns(branches, children, from, rankGap, sign) {
4155
+ const rows = [];
4156
+ const visit = (node, depth) => {
4157
+ (rows[depth] ??= []).push(node);
4158
+ for (const kid of children.get(node.id) ?? []) visit(kid, depth + 1);
4159
+ };
4160
+ for (const branch of branches) visit(branch, 0);
4161
+ const xs = /* @__PURE__ */ new Map();
4162
+ let start2 = from;
4163
+ for (const row of rows) {
4164
+ const band = Math.max(...row.map((n) => n.bounds.width));
4165
+ for (const node of row) {
4166
+ const near = start2 + (band - node.bounds.width) / 2;
4167
+ xs.set(node.id, sign === 1 ? near : -near - node.bounds.width);
4168
+ }
4169
+ start2 += band + rankGap;
4170
+ }
4171
+ return xs;
4172
+ }
4173
+
4174
+ 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 };
4175
+ //# sourceMappingURL=chunk-UOFQBGJY.js.map
4176
+ //# sourceMappingURL=chunk-UOFQBGJY.js.map