@weasel-js/diagram 1.9.2 → 1.9.4
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.
- package/README.md +16 -0
- package/dist/{chunk-UBM2J3HJ.js → chunk-EDSBULQM.js} +368 -25
- package/dist/chunk-EDSBULQM.js.map +1 -0
- package/dist/index.d.ts +135 -9
- package/dist/index.js +312 -17
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +1 -1
- package/dist/{layout.entry-W-8PYfGM.d.ts → layout.entry-DyKFfHG5.d.ts} +349 -273
- package/dist/layout.js +1 -1
- package/package.json +4 -3
- package/dist/chunk-UBM2J3HJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -108,6 +108,22 @@ starting `at`, and a minimum `width`/`height`; `lines` is shorthand for one
|
|
|
108
108
|
label row each. An edge can name `fromPort`/`toPort`, its own `router`,
|
|
109
109
|
`waypoints` and `labelPlacement`.
|
|
110
110
|
|
|
111
|
+
`groups` draws a box around some nodes, with an optional `label` inside it:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
diagramScene({ nodes, edges, groups: [{ id: 'ingest', members: ['fetch', 'decode'], label: 'ingest' }] });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`layered` and `tree` keep a group's members together. Each group gets a column
|
|
118
|
+
on the cross axis that nothing else enters, in every rank from its first
|
|
119
|
+
member's to its last, and rank gaps widen where a box needs the room. The box
|
|
120
|
+
is an ordinary scene node that depends on its members and derives its pose from
|
|
121
|
+
theirs (`groupDerivePose`), so dragging a member carries the box along. An edge
|
|
122
|
+
whose `from` or `to` names a group meets the box, and ranks against the
|
|
123
|
+
group's first stage. Groups are flat, and `force` ignores them. A scene built
|
|
124
|
+
any other way joins in with a node whose trait carries `group` and whose
|
|
125
|
+
`dependsOn` lists its members.
|
|
126
|
+
|
|
111
127
|
Text is sized from character counts unless `measure` is given, so layout runs
|
|
112
128
|
without a canvas. The layout defaults to `layered`, picked by name or passed as
|
|
113
129
|
any `LayoutFn`; `layoutOptions` merge over `order: 'barycenter'`, and
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { rectPath, polygonFromPoints, PathBuilder, PATH_M, PATH_L, PATH_C, flattenCubic, PATH_Q, PATH_Z, AUTO_POSE_DESCRIPTOR, rotatePoint, createSimulation } from '@weasel-js/core/math';
|
|
2
|
+
import { cssFamilyName } from '@weasel-js/font';
|
|
2
3
|
|
|
3
4
|
// src/outline.ts
|
|
4
5
|
var LEAN = 0.2;
|
|
@@ -188,6 +189,7 @@ var dataKeyReader = (node) => {
|
|
|
188
189
|
const trait = data[DIAGRAM_TRAIT_KEY];
|
|
189
190
|
if (trait === null || typeof trait !== "object") return null;
|
|
190
191
|
if ("from" in trait && "to" in trait) return null;
|
|
192
|
+
if ("label" in trait || "anchor" in trait) return null;
|
|
191
193
|
return trait;
|
|
192
194
|
};
|
|
193
195
|
function createDiagramNodes(entries) {
|
|
@@ -272,15 +274,13 @@ function portOf(node, pose, portId, opts = {}) {
|
|
|
272
274
|
function typeOf(spec) {
|
|
273
275
|
return spec.type === void 0 ? {} : { type: spec.type };
|
|
274
276
|
}
|
|
275
|
-
|
|
276
|
-
// src/body.ts
|
|
277
277
|
var DEFAULT_PADDING = 8;
|
|
278
278
|
var DEFAULT_GAP = 4;
|
|
279
279
|
var FALLBACK_LINE = 16;
|
|
280
280
|
function canvasMeasure(ctx) {
|
|
281
281
|
return (text, style) => {
|
|
282
282
|
const size = style.fontSize ?? FALLBACK_LINE;
|
|
283
|
-
ctx.font = `${style.bold === true ? "bold " : ""}${size}px ${style.fontFamily ?? "sans-serif"}`;
|
|
283
|
+
ctx.font = `${style.bold === true ? "bold " : ""}${size}px ${cssFamilyName(style.fontFamily ?? "sans-serif")}`;
|
|
284
284
|
return { width: ctx.measureText(text).width, height: Math.ceil(size * 1.2) };
|
|
285
285
|
};
|
|
286
286
|
}
|
|
@@ -487,6 +487,30 @@ function settle(graph, slots, opts = {}) {
|
|
|
487
487
|
}
|
|
488
488
|
return translated(graph, placed, pinned, tolerance);
|
|
489
489
|
}
|
|
490
|
+
function untilSettled(once, graph, opts = {}, passes = 8) {
|
|
491
|
+
const first = once(graph, opts);
|
|
492
|
+
if (first.size === 0) return first;
|
|
493
|
+
const at = new Map(first);
|
|
494
|
+
for (let pass = 1; pass < passes; pass++) {
|
|
495
|
+
const nodes = graph.nodes.map((node) => {
|
|
496
|
+
const to = at.get(node.id);
|
|
497
|
+
return to === void 0 ? node : { ...node, bounds: { ...node.bounds, x: to.x, y: to.y } };
|
|
498
|
+
});
|
|
499
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
500
|
+
const next = once({ ...graph, nodes, node: (id) => byId.get(id) }, opts);
|
|
501
|
+
if (next.size === 0) break;
|
|
502
|
+
for (const [id, to] of next) at.set(id, to);
|
|
503
|
+
}
|
|
504
|
+
const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE;
|
|
505
|
+
const out = /* @__PURE__ */ new Map();
|
|
506
|
+
for (const node of graph.nodes) {
|
|
507
|
+
const to = at.get(node.id);
|
|
508
|
+
if (to === void 0) continue;
|
|
509
|
+
if (Math.abs(to.x - node.bounds.x) <= tolerance && Math.abs(to.y - node.bounds.y) <= tolerance) continue;
|
|
510
|
+
out.set(node.id, to);
|
|
511
|
+
}
|
|
512
|
+
return out;
|
|
513
|
+
}
|
|
490
514
|
function translated(graph, placed, pinned, tolerance = DEFAULT_TOLERANCE) {
|
|
491
515
|
const anchor = anchorDelta(graph, placed, pinned);
|
|
492
516
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -543,13 +567,313 @@ function graphOrder(graph) {
|
|
|
543
567
|
graph.nodes.forEach((node, i) => index.set(node.id, i));
|
|
544
568
|
return (id) => index.get(id) ?? Number.MAX_SAFE_INTEGER;
|
|
545
569
|
}
|
|
570
|
+
|
|
571
|
+
// src/cluster.ts
|
|
572
|
+
function insetOf(inset) {
|
|
573
|
+
if (typeof inset === "number") return { top: inset, right: inset, bottom: inset, left: inset };
|
|
574
|
+
return {
|
|
575
|
+
top: inset?.top ?? DEFAULT_GROUP_INSET,
|
|
576
|
+
right: inset?.right ?? DEFAULT_GROUP_INSET,
|
|
577
|
+
bottom: inset?.bottom ?? DEFAULT_GROUP_INSET,
|
|
578
|
+
left: inset?.left ?? DEFAULT_GROUP_INSET
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
var DEFAULT_GROUP_INSET = 12;
|
|
582
|
+
function canonical(inset, axes) {
|
|
583
|
+
if (axes.rank === "y") {
|
|
584
|
+
return axes.sign === 1 ? { crossLo: inset.left, crossHi: inset.right, rankLo: inset.top, rankHi: inset.bottom } : { crossLo: inset.left, crossHi: inset.right, rankLo: inset.bottom, rankHi: inset.top };
|
|
585
|
+
}
|
|
586
|
+
return axes.sign === 1 ? { crossLo: inset.top, crossHi: inset.bottom, rankLo: inset.left, rankHi: inset.right } : { crossLo: inset.top, crossHi: inset.bottom, rankLo: inset.right, rankHi: inset.left };
|
|
587
|
+
}
|
|
588
|
+
function rankGaps(graph, rows, axes, rankGap) {
|
|
589
|
+
const gaps = rows.slice(1).map(() => rankGap);
|
|
590
|
+
const ends = /* @__PURE__ */ new Map();
|
|
591
|
+
const starts = /* @__PURE__ */ new Map();
|
|
592
|
+
const reach = (span, side) => {
|
|
593
|
+
const own = canonical(span.group.inset, axes)[side === "lo" ? "rankLo" : "rankHi"];
|
|
594
|
+
const flush = span.children.filter((c) => c[side] === span[side]);
|
|
595
|
+
return own + Math.max(0, ...flush.map((c) => reach(c, side)));
|
|
596
|
+
};
|
|
597
|
+
for (const span of groupSpans(graph, rows)) {
|
|
598
|
+
ends.set(span.hi, Math.max(ends.get(span.hi) ?? 0, reach(span, "hi")));
|
|
599
|
+
starts.set(span.lo, Math.max(starts.get(span.lo) ?? 0, reach(span, "lo")));
|
|
600
|
+
}
|
|
601
|
+
for (let i = 0; i < gaps.length; i++) {
|
|
602
|
+
gaps[i] = Math.max(gaps[i], rankGap + (ends.get(i) ?? 0) + (starts.get(i + 1) ?? 0));
|
|
603
|
+
}
|
|
604
|
+
return gaps;
|
|
605
|
+
}
|
|
606
|
+
function groupSpans(graph, rows) {
|
|
607
|
+
const rankOf = /* @__PURE__ */ new Map();
|
|
608
|
+
rows.forEach((row, i) => row.forEach((n) => rankOf.set(n.id, i)));
|
|
609
|
+
const spans = [];
|
|
610
|
+
const byId = /* @__PURE__ */ new Map();
|
|
611
|
+
for (const group of graph.groups ?? []) {
|
|
612
|
+
const ranks = group.members.map((id) => rankOf.get(id)).filter((r) => r !== void 0);
|
|
613
|
+
if (ranks.length === 0) continue;
|
|
614
|
+
const span = { group, lo: Math.min(...ranks), hi: Math.max(...ranks), children: [] };
|
|
615
|
+
spans.push(span);
|
|
616
|
+
byId.set(group.id, span);
|
|
617
|
+
}
|
|
618
|
+
for (const span of spans) {
|
|
619
|
+
const parent = span.group.parent === void 0 ? void 0 : byId.get(span.group.parent);
|
|
620
|
+
if (parent === void 0) continue;
|
|
621
|
+
span.parent = parent;
|
|
622
|
+
parent.children.push(span);
|
|
623
|
+
}
|
|
624
|
+
return spans;
|
|
625
|
+
}
|
|
626
|
+
function packClusters(graph, rows, preferred, axes, nodeGap) {
|
|
627
|
+
const spans = groupSpans(graph, rows);
|
|
628
|
+
const spanById = new Map(spans.map((s) => [s.group.id, s]));
|
|
629
|
+
const spanOf = (n) => n.group === void 0 ? void 0 : spanById.get(n.group);
|
|
630
|
+
const width = (n) => extent(n.bounds, axes.cross);
|
|
631
|
+
const pref = (n) => preferred.get(n.id) ?? 0;
|
|
632
|
+
const insets = new Map(spans.map((s) => [s, canonical(s.group.inset, axes)]));
|
|
633
|
+
const centerOf = /* @__PURE__ */ new Map();
|
|
634
|
+
for (const span of spans) {
|
|
635
|
+
const inside = new Set(span.group.members);
|
|
636
|
+
let sum = 0;
|
|
637
|
+
let count = 0;
|
|
638
|
+
for (const row of rows) {
|
|
639
|
+
for (const n of row) {
|
|
640
|
+
if (!inside.has(n.id)) continue;
|
|
641
|
+
sum += pref(n) + width(n) / 2;
|
|
642
|
+
count++;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
centerOf.set(span, count === 0 ? 0 : sum / count);
|
|
646
|
+
}
|
|
647
|
+
const itemsByRow = (owner, among) => rows.map((row, r) => {
|
|
648
|
+
const items = [];
|
|
649
|
+
for (const node of row) {
|
|
650
|
+
if (spanOf(node) === owner) items.push({ kind: "node", node, center: pref(node) + width(node) / 2 });
|
|
651
|
+
}
|
|
652
|
+
for (const span of among) {
|
|
653
|
+
if (r >= span.lo && r <= span.hi) items.push({ kind: "group", span, center: centerOf.get(span) });
|
|
654
|
+
}
|
|
655
|
+
return items.map((item, i) => ({ item, i })).sort((a, b) => a.item.center - b.item.center || a.i - b.i).map(({ item }) => item);
|
|
656
|
+
});
|
|
657
|
+
const settle2 = (byRow, start2, nodeFloor, origin) => {
|
|
658
|
+
const nodeAt = /* @__PURE__ */ new Map();
|
|
659
|
+
let ends = [];
|
|
660
|
+
for (let pass = 0; pass <= start2.size; pass++) {
|
|
661
|
+
let raised = false;
|
|
662
|
+
ends = byRow.map((items) => {
|
|
663
|
+
let cursor = origin;
|
|
664
|
+
for (const item of items) {
|
|
665
|
+
if (item.kind === "node") {
|
|
666
|
+
const x2 = Math.max(nodeFloor(item.node), cursor);
|
|
667
|
+
nodeAt.set(item.node.id, x2);
|
|
668
|
+
cursor = x2 + width(item.node) + nodeGap;
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
const s = start2.get(item.span);
|
|
672
|
+
const x = Math.max(s, cursor);
|
|
673
|
+
if (x > s) {
|
|
674
|
+
start2.set(item.span, x);
|
|
675
|
+
raised = true;
|
|
676
|
+
}
|
|
677
|
+
cursor = x + outer(item.span) + nodeGap;
|
|
678
|
+
}
|
|
679
|
+
return items.length === 0 ? origin : cursor - nodeGap;
|
|
680
|
+
});
|
|
681
|
+
if (!raised) break;
|
|
682
|
+
}
|
|
683
|
+
return { nodeAt, ends };
|
|
684
|
+
};
|
|
685
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
686
|
+
const blockOf = (span) => {
|
|
687
|
+
const known = blocks.get(span);
|
|
688
|
+
if (known !== void 0) return known;
|
|
689
|
+
const byRow = itemsByRow(span, span.children);
|
|
690
|
+
const childAt = new Map(span.children.map((c) => [c, 0]));
|
|
691
|
+
const { nodeAt, ends } = settle2(byRow, childAt, () => 0, 0);
|
|
692
|
+
const block = { width: Math.max(0, ...ends), nodeAt, childAt };
|
|
693
|
+
byRow.forEach((items, r) => {
|
|
694
|
+
if (items.some((item) => item.kind === "group")) return;
|
|
695
|
+
const shift = (block.width - ends[r]) / 2;
|
|
696
|
+
for (const item of items) {
|
|
697
|
+
if (item.kind === "node") nodeAt.set(item.node.id, nodeAt.get(item.node.id) + shift);
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
blocks.set(span, block);
|
|
701
|
+
return block;
|
|
702
|
+
};
|
|
703
|
+
function outer(span) {
|
|
704
|
+
const inset = insets.get(span);
|
|
705
|
+
return blockOf(span).width + inset.crossLo + inset.crossHi;
|
|
706
|
+
}
|
|
707
|
+
const roots = spans.filter((s) => s.parent === void 0);
|
|
708
|
+
const start = new Map(roots.map((s) => [s, centerOf.get(s) - outer(s) / 2]));
|
|
709
|
+
const { nodeAt: at } = settle2(itemsByRow(void 0, roots), start, pref, -Infinity);
|
|
710
|
+
const place = (span, from) => {
|
|
711
|
+
const origin = from + insets.get(span).crossLo;
|
|
712
|
+
const block = blockOf(span);
|
|
713
|
+
for (const [id, local] of block.nodeAt) at.set(id, origin + local);
|
|
714
|
+
for (const [child, local] of block.childAt) place(child, origin + local);
|
|
715
|
+
};
|
|
716
|
+
for (const root of roots) place(root, start.get(root));
|
|
717
|
+
return at;
|
|
718
|
+
}
|
|
719
|
+
function expandGroupEdges(edges, groups) {
|
|
720
|
+
const byId = new Map(groups.map((g) => [g.id, g]));
|
|
721
|
+
if (byId.size === 0) return [...edges];
|
|
722
|
+
const within = (id, group) => {
|
|
723
|
+
if (!byId.has(id)) return group.members.includes(id);
|
|
724
|
+
for (let at = byId.get(id); at !== void 0; at = at.parent === void 0 ? void 0 : byId.get(at.parent)) {
|
|
725
|
+
if (at === group) return true;
|
|
726
|
+
}
|
|
727
|
+
return false;
|
|
728
|
+
};
|
|
729
|
+
const entries = /* @__PURE__ */ new Map();
|
|
730
|
+
const exits = /* @__PURE__ */ new Map();
|
|
731
|
+
for (const g of groups) {
|
|
732
|
+
const inside = new Set(g.members);
|
|
733
|
+
const pointedAt = /* @__PURE__ */ new Set();
|
|
734
|
+
const pointing = /* @__PURE__ */ new Set();
|
|
735
|
+
for (const e of edges) {
|
|
736
|
+
if (inside.has(e.from) && inside.has(e.to) && e.from !== e.to) {
|
|
737
|
+
pointedAt.add(e.to);
|
|
738
|
+
pointing.add(e.from);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const entry = g.members.filter((m) => !pointedAt.has(m));
|
|
742
|
+
const exit = g.members.filter((m) => !pointing.has(m));
|
|
743
|
+
entries.set(g.id, entry.length > 0 ? entry : g.members);
|
|
744
|
+
exits.set(g.id, exit.length > 0 ? exit : g.members);
|
|
745
|
+
}
|
|
746
|
+
const out = [];
|
|
747
|
+
for (const e of edges) {
|
|
748
|
+
const fromGroup = byId.has(e.from) ? e.from : void 0;
|
|
749
|
+
const toGroup = byId.has(e.to) ? e.to : void 0;
|
|
750
|
+
if (fromGroup === void 0 && toGroup === void 0) {
|
|
751
|
+
out.push(e);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (fromGroup !== void 0 && within(e.to, byId.get(fromGroup))) continue;
|
|
755
|
+
if (toGroup !== void 0 && within(e.from, byId.get(toGroup))) continue;
|
|
756
|
+
const froms = fromGroup !== void 0 ? exits.get(fromGroup) : [e.from];
|
|
757
|
+
const tos = toGroup !== void 0 ? entries.get(toGroup) : [e.to];
|
|
758
|
+
let k = 0;
|
|
759
|
+
for (const from of froms) {
|
|
760
|
+
for (const to of tos) out.push({ id: `${e.id}#${k++}`, from, to });
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return out;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/forceGroups.ts
|
|
767
|
+
function resolve(groups, byId) {
|
|
768
|
+
const out = [];
|
|
769
|
+
for (const group of groups) {
|
|
770
|
+
const members = group.members.map((id) => byId.get(id)).filter((b) => b !== void 0);
|
|
771
|
+
if (members.length > 0) out.push({ members, group, children: [] });
|
|
772
|
+
}
|
|
773
|
+
const byGroup = new Map(out.map((g) => [g.group.id, g]));
|
|
774
|
+
for (const g of out) {
|
|
775
|
+
const parent = g.group.parent === void 0 ? void 0 : byGroup.get(g.group.parent);
|
|
776
|
+
if (parent === void 0) continue;
|
|
777
|
+
g.parent = parent;
|
|
778
|
+
parent.children.push(g);
|
|
779
|
+
}
|
|
780
|
+
return out;
|
|
781
|
+
}
|
|
782
|
+
function groupCohesion(groups, byId, strength) {
|
|
783
|
+
const resolved = resolve(groups, byId);
|
|
784
|
+
return (alpha) => {
|
|
785
|
+
for (const { members } of resolved) {
|
|
786
|
+
let cx = 0;
|
|
787
|
+
let cy = 0;
|
|
788
|
+
for (const m of members) {
|
|
789
|
+
cx += m.x;
|
|
790
|
+
cy += m.y;
|
|
791
|
+
}
|
|
792
|
+
cx /= members.length;
|
|
793
|
+
cy /= members.length;
|
|
794
|
+
for (const m of members) {
|
|
795
|
+
m.vx += (cx - m.x) * strength * alpha;
|
|
796
|
+
m.vy += (cy - m.y) * strength * alpha;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function boxOf(g) {
|
|
802
|
+
let x0 = Infinity;
|
|
803
|
+
let y0 = Infinity;
|
|
804
|
+
let x1 = -Infinity;
|
|
805
|
+
let y1 = -Infinity;
|
|
806
|
+
for (const m of g.members) {
|
|
807
|
+
x0 = Math.min(x0, m.x - m.hw);
|
|
808
|
+
y0 = Math.min(y0, m.y - m.hh);
|
|
809
|
+
x1 = Math.max(x1, m.x + m.hw);
|
|
810
|
+
y1 = Math.max(y1, m.y + m.hh);
|
|
811
|
+
}
|
|
812
|
+
for (const child of g.children) {
|
|
813
|
+
const b = boxOf(child);
|
|
814
|
+
x0 = Math.min(x0, b.x0);
|
|
815
|
+
y0 = Math.min(y0, b.y0);
|
|
816
|
+
x1 = Math.max(x1, b.x1);
|
|
817
|
+
y1 = Math.max(y1, b.y1);
|
|
818
|
+
}
|
|
819
|
+
const { inset } = g.group;
|
|
820
|
+
return { x0: x0 - inset.left, y0: y0 - inset.top, x1: x1 + inset.right, y1: y1 + inset.bottom };
|
|
821
|
+
}
|
|
822
|
+
function clearance(a, b, padding) {
|
|
823
|
+
const gapX = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0) + padding;
|
|
824
|
+
const gapY = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0) + padding;
|
|
825
|
+
if (gapX <= 0 || gapY <= 0) return null;
|
|
826
|
+
const dx = b.x0 + b.x1 - (a.x0 + a.x1);
|
|
827
|
+
const dy = b.y0 + b.y1 - (a.y0 + a.y1);
|
|
828
|
+
return gapX < gapY ? { x: (Math.sign(dx) || 1) * gapX, y: 0 } : { x: 0, y: (Math.sign(dy) || 1) * gapY };
|
|
829
|
+
}
|
|
830
|
+
function holds(outer, inner) {
|
|
831
|
+
for (let at = inner.parent; at !== void 0; at = at.parent) {
|
|
832
|
+
if (at === outer) return true;
|
|
833
|
+
}
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
function shove(bodies, x, y) {
|
|
837
|
+
for (const body of bodies) {
|
|
838
|
+
body.vx += x;
|
|
839
|
+
body.vy += y;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function separateGroups(groups, bodies, byId, padding) {
|
|
843
|
+
const resolved = resolve(groups, byId);
|
|
844
|
+
const memberSets = resolved.map((g) => new Set(g.members));
|
|
845
|
+
return () => {
|
|
846
|
+
const boxes = resolved.map(boxOf);
|
|
847
|
+
for (let i = 0; i < resolved.length; i++) {
|
|
848
|
+
for (let j = i + 1; j < resolved.length; j++) {
|
|
849
|
+
if (holds(resolved[i], resolved[j]) || holds(resolved[j], resolved[i])) continue;
|
|
850
|
+
const push = clearance(boxes[i], boxes[j], padding);
|
|
851
|
+
if (push === null) continue;
|
|
852
|
+
shove(resolved[i].members, -push.x / 2, -push.y / 2);
|
|
853
|
+
shove(resolved[j].members, push.x / 2, push.y / 2);
|
|
854
|
+
}
|
|
855
|
+
for (const body of bodies) {
|
|
856
|
+
if (memberSets[i].has(body)) continue;
|
|
857
|
+
const box = { x0: body.x - body.hw, y0: body.y - body.hh, x1: body.x + body.hw, y1: body.y + body.hh };
|
|
858
|
+
const push = clearance(boxes[i], box, padding);
|
|
859
|
+
if (push === null) continue;
|
|
860
|
+
shove(resolved[i].members, -push.x / 2, -push.y / 2);
|
|
861
|
+
body.vx += push.x / 2;
|
|
862
|
+
body.vy += push.y / 2;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// src/force.ts
|
|
546
869
|
var DEFAULTS = {
|
|
547
870
|
iterations: 300,
|
|
548
871
|
linkDistance: 160,
|
|
549
872
|
linkStrength: 0.35,
|
|
550
873
|
charge: -3e3,
|
|
551
874
|
gravity: 0.02,
|
|
552
|
-
padding: 24
|
|
875
|
+
padding: 24,
|
|
876
|
+
groupStrength: 0.2
|
|
553
877
|
};
|
|
554
878
|
var GOLDEN = Math.PI * (3 - Math.sqrt(5));
|
|
555
879
|
var NUDGE = 0.5;
|
|
@@ -628,7 +952,8 @@ function forceRelaxation(graph, opts = {}) {
|
|
|
628
952
|
linkStrength = DEFAULTS.linkStrength,
|
|
629
953
|
charge: chargeStrength = DEFAULTS.charge,
|
|
630
954
|
gravity: gravityStrength = DEFAULTS.gravity,
|
|
631
|
-
padding = DEFAULTS.padding
|
|
955
|
+
padding = DEFAULTS.padding,
|
|
956
|
+
groupStrength = DEFAULTS.groupStrength
|
|
632
957
|
} = opts;
|
|
633
958
|
const pinned = pinnedSet(graph, opts);
|
|
634
959
|
const bodies = graph.nodes.map((node) => {
|
|
@@ -651,13 +976,15 @@ function forceRelaxation(graph, opts = {}) {
|
|
|
651
976
|
x: bodies.reduce((sum, b) => sum + b.x, 0) / bodies.length,
|
|
652
977
|
y: bodies.reduce((sum, b) => sum + b.y, 0) / bodies.length
|
|
653
978
|
};
|
|
979
|
+
const groups = graph.groups ?? [];
|
|
654
980
|
const sim = createSimulation({
|
|
655
981
|
nodes: bodies,
|
|
656
982
|
forces: [
|
|
657
983
|
charge(bodies, chargeStrength),
|
|
658
984
|
links(byId, graph, linkDistance, linkStrength),
|
|
659
985
|
gravity(bodies, centroid, gravityStrength),
|
|
660
|
-
separateBoxes(bodies, padding)
|
|
986
|
+
separateBoxes(bodies, padding),
|
|
987
|
+
...groups.length > 0 ? [groupCohesion(groups, byId, groupStrength), separateGroups(groups, bodies, byId, padding)] : []
|
|
661
988
|
]
|
|
662
989
|
});
|
|
663
990
|
return {
|
|
@@ -761,7 +1088,12 @@ function ranksOf(graph, back) {
|
|
|
761
1088
|
for (const node of graph.nodes) rankOf(node.id);
|
|
762
1089
|
return ranks;
|
|
763
1090
|
}
|
|
764
|
-
var layered = (graph, opts = {}) =>
|
|
1091
|
+
var layered = (graph, opts = {}) => (
|
|
1092
|
+
// Barycenter ordering reorders on every run by design, so it has no
|
|
1093
|
+
// arrangement to settle on.
|
|
1094
|
+
(graph.groups?.length ?? 0) > 0 && opts.order !== "barycenter" ? untilSettled(layeredOnce, graph, opts) : layeredOnce(graph, opts)
|
|
1095
|
+
);
|
|
1096
|
+
var layeredOnce = (graph, opts = {}) => {
|
|
765
1097
|
const axes = axesFor(opts.direction);
|
|
766
1098
|
const nodeGap = opts.nodeGap ?? DEFAULT_NODE_GAP;
|
|
767
1099
|
const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
|
|
@@ -778,29 +1110,29 @@ var layered = (graph, opts = {}) => {
|
|
|
778
1110
|
const rows = [...byRank.keys()].sort((a, b) => a - b);
|
|
779
1111
|
let ordered = rows.map((rank) => seededOrder(byRank.get(rank), axes.cross, order));
|
|
780
1112
|
if (opts.order === "barycenter") ordered = barycenterOrder(graph, ordered, back);
|
|
781
|
-
const packed =
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
packed.
|
|
786
|
-
|
|
1113
|
+
const packed = ordered.map((row) => packAcross(row, axes.cross, nodeGap));
|
|
1114
|
+
const widest = Math.max(0, ...packed.map((p) => p.span));
|
|
1115
|
+
const preferred = /* @__PURE__ */ new Map();
|
|
1116
|
+
for (const [i, row] of ordered.entries()) {
|
|
1117
|
+
const indent = (widest - packed[i].span) / 2;
|
|
1118
|
+
for (const node of row) preferred.set(node.id, indent + packed[i].at.get(node.id));
|
|
787
1119
|
}
|
|
1120
|
+
const grouped = (graph.groups?.length ?? 0) > 0;
|
|
1121
|
+
const crossAt = grouped ? packClusters(graph, ordered, preferred, axes, nodeGap) : preferred;
|
|
1122
|
+
const gaps = grouped ? rankGaps(graph, ordered, axes, rankGap) : ordered.slice(1).map(() => rankGap);
|
|
788
1123
|
const slots = /* @__PURE__ */ new Map();
|
|
789
1124
|
let along = 0;
|
|
790
|
-
for (const
|
|
791
|
-
const row = byRank.get(rank);
|
|
792
|
-
const p = packed.get(rank);
|
|
793
|
-
const indent = (widest - p.span) / 2;
|
|
1125
|
+
for (const [i, row] of ordered.entries()) {
|
|
794
1126
|
const depth = Math.max(0, ...row.map((n) => extent(n.bounds, axes.rank)));
|
|
795
1127
|
for (const node of row) {
|
|
796
1128
|
slots.set(node.id, {
|
|
797
|
-
cross:
|
|
1129
|
+
cross: crossAt.get(node.id),
|
|
798
1130
|
// Centered in its rank's band, so a tall node does not push its
|
|
799
1131
|
// neighbors' edges off the line they read along.
|
|
800
1132
|
rank: along + (depth - extent(node.bounds, axes.rank)) / 2
|
|
801
1133
|
});
|
|
802
1134
|
}
|
|
803
|
-
along += depth +
|
|
1135
|
+
along += depth + (gaps[i] ?? 0);
|
|
804
1136
|
}
|
|
805
1137
|
return settle(graph, slots, opts);
|
|
806
1138
|
};
|
|
@@ -838,7 +1170,8 @@ function forestOf(graph, crossAxis) {
|
|
|
838
1170
|
}
|
|
839
1171
|
return { roots, children, depth };
|
|
840
1172
|
}
|
|
841
|
-
var tree = (graph, opts = {}) =>
|
|
1173
|
+
var tree = (graph, opts = {}) => (graph.groups?.length ?? 0) > 0 ? untilSettled(treeOnce, graph, opts) : treeOnce(graph, opts);
|
|
1174
|
+
var treeOnce = (graph, opts = {}) => {
|
|
842
1175
|
const axes = axesFor(opts.direction);
|
|
843
1176
|
const nodeGap = opts.nodeGap ?? DEFAULT_NODE_GAP;
|
|
844
1177
|
const rankGap = opts.rankGap ?? DEFAULT_RANK_GAP;
|
|
@@ -848,11 +1181,15 @@ var tree = (graph, opts = {}) => {
|
|
|
848
1181
|
const at = depth.get(node.id) ?? 0;
|
|
849
1182
|
bands.set(at, Math.max(bands.get(at) ?? 0, extent(node.bounds, axes.rank)));
|
|
850
1183
|
}
|
|
1184
|
+
const depths = [...bands.keys()].sort((a, b) => a - b);
|
|
1185
|
+
const rowsAt = depths.map((at) => graph.nodes.filter((n) => (depth.get(n.id) ?? 0) === at));
|
|
1186
|
+
const grouped = (graph.groups?.length ?? 0) > 0;
|
|
1187
|
+
const gaps = grouped ? rankGaps(graph, rowsAt, axes, rankGap) : depths.slice(1).map(() => rankGap);
|
|
851
1188
|
const bandStart = /* @__PURE__ */ new Map();
|
|
852
1189
|
let along = 0;
|
|
853
|
-
for (const at of
|
|
1190
|
+
for (const [i, at] of depths.entries()) {
|
|
854
1191
|
bandStart.set(at, along);
|
|
855
|
-
along += bands.get(at) +
|
|
1192
|
+
along += bands.get(at) + (gaps[i] ?? 0);
|
|
856
1193
|
}
|
|
857
1194
|
const slots = /* @__PURE__ */ new Map();
|
|
858
1195
|
const measured = /* @__PURE__ */ new Map();
|
|
@@ -888,9 +1225,15 @@ var tree = (graph, opts = {}) => {
|
|
|
888
1225
|
place(root, cursor);
|
|
889
1226
|
cursor += measure(root) + nodeGap;
|
|
890
1227
|
}
|
|
1228
|
+
if (grouped) {
|
|
1229
|
+
const preferred = new Map([...slots].map(([id, slot]) => [id, slot.cross]));
|
|
1230
|
+
const ordered = rowsAt.map((row) => [...row].sort((a, b) => preferred.get(a.id) - preferred.get(b.id)));
|
|
1231
|
+
const crossAt = packClusters(graph, ordered, preferred, axes, nodeGap);
|
|
1232
|
+
for (const [id, slot] of slots) slots.set(id, { ...slot, cross: crossAt.get(id) ?? slot.cross });
|
|
1233
|
+
}
|
|
891
1234
|
return settle(graph, slots, opts);
|
|
892
1235
|
};
|
|
893
1236
|
|
|
894
|
-
export { COMPASS, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DIAGRAM_TRAIT_KEY, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, center, contentBox, createDiagramNodes, dataKeyReader, diagramNodeOf, extent, force, forceRelaxation, forestOf, graphOrder, isPinned, layered, layoutBody, layoutRowPorts, measureBody, outlinePath, outlinePolyline, packAcross, pinnedSet, portOf, portsOf, portsOnOutline, ranksOf, rayHit, seededOrder, settle, sizeToBody, translated, tree };
|
|
895
|
-
//# sourceMappingURL=chunk-
|
|
896
|
-
//# sourceMappingURL=chunk-
|
|
1237
|
+
export { COMPASS, DEFAULT_GROUP_INSET, DEFAULT_NODE_GAP, DEFAULT_PORTS, DEFAULT_RANK_GAP, DIAGRAM_TRAIT_KEY, axesFor, backEdges, bodyOutline, bodyTrait, boxForContent, buildBody, canvasMeasure, center, contentBox, createDiagramNodes, dataKeyReader, diagramNodeOf, expandGroupEdges, extent, force, forceRelaxation, forestOf, graphOrder, insetOf, isPinned, layered, layoutBody, layoutRowPorts, measureBody, outlinePath, outlinePolyline, packAcross, packClusters, pinnedSet, portOf, portsOf, portsOnOutline, rankGaps, ranksOf, rayHit, seededOrder, settle, sizeToBody, translated, tree, untilSettled };
|
|
1238
|
+
//# sourceMappingURL=chunk-EDSBULQM.js.map
|
|
1239
|
+
//# sourceMappingURL=chunk-EDSBULQM.js.map
|