@unpunnyfuns/conduit 0.1.0
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/LICENSE +25 -0
- package/README.md +202 -0
- package/dist/assert.d.ts +6 -0
- package/dist/assert.d.ts.map +1 -0
- package/dist/assert.js +8 -0
- package/dist/assert.js.map +1 -0
- package/dist/cn.d.ts +3 -0
- package/dist/cn.d.ts.map +1 -0
- package/dist/cn.js +3 -0
- package/dist/cn.js.map +1 -0
- package/dist/components/Badge.d.ts +6 -0
- package/dist/components/Badge.d.ts.map +1 -0
- package/dist/components/Badge.js +11 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/Card.d.ts +20 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +24 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Diagram.d.ts +37 -0
- package/dist/components/Diagram.d.ts.map +1 -0
- package/dist/components/Diagram.js +92 -0
- package/dist/components/Diagram.js.map +1 -0
- package/dist/components/EdgeLayer.d.ts +23 -0
- package/dist/components/EdgeLayer.d.ts.map +1 -0
- package/dist/components/EdgeLayer.js +85 -0
- package/dist/components/EdgeLayer.js.map +1 -0
- package/dist/components/Lane.d.ts +11 -0
- package/dist/components/Lane.d.ts.map +1 -0
- package/dist/components/Lane.js +6 -0
- package/dist/components/Lane.js.map +1 -0
- package/dist/components/icons.d.ts +7 -0
- package/dist/components/icons.d.ts.map +1 -0
- package/dist/components/icons.js +47 -0
- package/dist/components/icons.js.map +1 -0
- package/dist/components/pulse.d.ts +15 -0
- package/dist/components/pulse.d.ts.map +1 -0
- package/dist/components/pulse.js +10 -0
- package/dist/components/pulse.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/architecture.d.ts +60 -0
- package/dist/layout/architecture.d.ts.map +1 -0
- package/dist/layout/architecture.js +88 -0
- package/dist/layout/architecture.js.map +1 -0
- package/dist/layout/bounds.d.ts +25 -0
- package/dist/layout/bounds.d.ts.map +1 -0
- package/dist/layout/bounds.js +32 -0
- package/dist/layout/bounds.js.map +1 -0
- package/dist/layout/congestion.d.ts +18 -0
- package/dist/layout/congestion.d.ts.map +1 -0
- package/dist/layout/congestion.js +49 -0
- package/dist/layout/congestion.js.map +1 -0
- package/dist/layout/design.d.ts +98 -0
- package/dist/layout/design.d.ts.map +1 -0
- package/dist/layout/design.js +92 -0
- package/dist/layout/design.js.map +1 -0
- package/dist/layout/edges.d.ts +53 -0
- package/dist/layout/edges.d.ts.map +1 -0
- package/dist/layout/edges.js +848 -0
- package/dist/layout/edges.js.map +1 -0
- package/dist/layout/errors.d.ts +6 -0
- package/dist/layout/errors.d.ts.map +1 -0
- package/dist/layout/errors.js +9 -0
- package/dist/layout/errors.js.map +1 -0
- package/dist/layout/frame.d.ts +35 -0
- package/dist/layout/frame.d.ts.map +1 -0
- package/dist/layout/frame.js +63 -0
- package/dist/layout/frame.js.map +1 -0
- package/dist/layout/geometry.d.ts +23 -0
- package/dist/layout/geometry.d.ts.map +1 -0
- package/dist/layout/geometry.js +22 -0
- package/dist/layout/geometry.js.map +1 -0
- package/dist/layout/labels.d.ts +12 -0
- package/dist/layout/labels.d.ts.map +1 -0
- package/dist/layout/labels.js +153 -0
- package/dist/layout/labels.js.map +1 -0
- package/dist/layout/lanes.d.ts +5 -0
- package/dist/layout/lanes.d.ts.map +1 -0
- package/dist/layout/lanes.js +13 -0
- package/dist/layout/lanes.js.map +1 -0
- package/dist/layout/layout.d.ts +42 -0
- package/dist/layout/layout.d.ts.map +1 -0
- package/dist/layout/layout.js +94 -0
- package/dist/layout/layout.js.map +1 -0
- package/dist/layout/rank.d.ts +14 -0
- package/dist/layout/rank.d.ts.map +1 -0
- package/dist/layout/rank.js +78 -0
- package/dist/layout/rank.js.map +1 -0
- package/dist/layout/scope.d.ts +20 -0
- package/dist/layout/scope.d.ts.map +1 -0
- package/dist/layout/scope.js +52 -0
- package/dist/layout/scope.js.map +1 -0
- package/dist/layout/seating.d.ts +27 -0
- package/dist/layout/seating.d.ts.map +1 -0
- package/dist/layout/seating.js +135 -0
- package/dist/layout/seating.js.map +1 -0
- package/dist/layout/text.d.ts +12 -0
- package/dist/layout/text.d.ts.map +1 -0
- package/dist/layout/text.js +148 -0
- package/dist/layout/text.js.map +1 -0
- package/dist/layout/trace.d.ts +8 -0
- package/dist/layout/trace.d.ts.map +1 -0
- package/dist/layout/trace.js +51 -0
- package/dist/layout/trace.js.map +1 -0
- package/dist/layout.d.ts +9 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +6 -0
- package/dist/layout.js.map +1 -0
- package/dist/schema/document.d.ts +239 -0
- package/dist/schema/document.d.ts.map +1 -0
- package/dist/schema/document.js +83 -0
- package/dist/schema/document.js.map +1 -0
- package/dist/schema/errors.d.ts +20 -0
- package/dist/schema/errors.d.ts.map +1 -0
- package/dist/schema/errors.js +12 -0
- package/dist/schema/errors.js.map +1 -0
- package/dist/schema/integrity.d.ts +8 -0
- package/dist/schema/integrity.d.ts.map +1 -0
- package/dist/schema/integrity.js +63 -0
- package/dist/schema/integrity.js.map +1 -0
- package/dist/schema/parse.d.ts +6 -0
- package/dist/schema/parse.d.ts.map +1 -0
- package/dist/schema/parse.js +53 -0
- package/dist/schema/parse.js.map +1 -0
- package/dist/schema/primitives.d.ts +58 -0
- package/dist/schema/primitives.d.ts.map +1 -0
- package/dist/schema/primitives.js +44 -0
- package/dist/schema/primitives.js.map +1 -0
- package/dist/theme.css +63 -0
- package/package.json +83 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { canvasFor, union } from "./bounds.js";
|
|
2
|
+
import { relieveCongestion } from "./congestion.js";
|
|
3
|
+
import { BADGE_HEIGHT, BADGE_RISE, DEFAULT_CARD_HEIGHTS, DIAGRAM_MARGIN, } from "./design.js";
|
|
4
|
+
import { curveBounds, pathOf, shiftCurve, transposeCurve } from "./edges.js";
|
|
5
|
+
import { ConduitLayoutError } from "./errors.js";
|
|
6
|
+
import { frameFor } from "./frame.js";
|
|
7
|
+
import { roundCoord, transposeBox } from "./geometry.js";
|
|
8
|
+
import { placeLabelPills } from "./labels.js";
|
|
9
|
+
import { findView, resolveScope } from "./scope.js";
|
|
10
|
+
const WHOLE_DOCUMENT = { kind: "all" };
|
|
11
|
+
const requireView = (views, id) => {
|
|
12
|
+
const view = findView(views, id);
|
|
13
|
+
if (view === undefined)
|
|
14
|
+
throw new ConduitLayoutError("UNKNOWN_VIEW", `this document has no view '${id}'`);
|
|
15
|
+
return view;
|
|
16
|
+
};
|
|
17
|
+
const shiftBox = (box, canvas) => ({
|
|
18
|
+
x: roundCoord(box.x + canvas.shiftX),
|
|
19
|
+
y: roundCoord(box.y + canvas.shiftY),
|
|
20
|
+
width: roundCoord(box.width),
|
|
21
|
+
height: roundCoord(box.height),
|
|
22
|
+
});
|
|
23
|
+
const badgeStrip = (placed) => placed.node.badges.length === 0
|
|
24
|
+
? undefined
|
|
25
|
+
: {
|
|
26
|
+
x: placed.box.x,
|
|
27
|
+
y: placed.box.y - BADGE_RISE,
|
|
28
|
+
width: placed.box.width,
|
|
29
|
+
height: BADGE_HEIGHT,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A document in, every box out. Nothing here reads a clock, a font or a
|
|
33
|
+
* random number: the same document and options produce the same geometry,
|
|
34
|
+
* so a diagram never rearranges itself between two renders that barely
|
|
35
|
+
* changed anything.
|
|
36
|
+
*/
|
|
37
|
+
export const layout = (doc, options = {}) => {
|
|
38
|
+
const heights = { ...DEFAULT_CARD_HEIGHTS, ...options.cardHeights };
|
|
39
|
+
const view = options.view === undefined ? undefined : requireView(doc.views, options.view);
|
|
40
|
+
const graph = resolveScope(doc, view?.scope ?? WHOLE_DOCUMENT);
|
|
41
|
+
if (graph.nodes.length === 0)
|
|
42
|
+
throw new ConduitLayoutError("NOTHING_TO_RENDER", "no nodes are in scope for this view");
|
|
43
|
+
const frame = frameFor(doc.direction, graph, heights);
|
|
44
|
+
const { layout: placed, routed } = relieveCongestion(graph, frame);
|
|
45
|
+
const swapped = frame.direction === "down";
|
|
46
|
+
const pills = placeLabelPills(routed, swapped);
|
|
47
|
+
const orient = (box) => (swapped ? transposeBox(box) : box);
|
|
48
|
+
const orientCurve = (curve) => (swapped ? transposeCurve(curve) : curve);
|
|
49
|
+
const lanesOnScreen = placed.lanes.map((lane) => ({ ...lane, box: orient(lane.box) }));
|
|
50
|
+
const nodesOnScreen = placed.nodes.map((node) => ({ ...node, box: orient(node.box) }));
|
|
51
|
+
const curves = new Map(routed.map(({ edge, curve }) => [edge.id, orientCurve(curve)]));
|
|
52
|
+
const pillBoxes = new Map([...pills].map(([id, box]) => [id, orient(box)]));
|
|
53
|
+
const extent = swapped ? { width: placed.height, height: placed.width } : placed;
|
|
54
|
+
const drawn = [
|
|
55
|
+
...lanesOnScreen.map(({ box }) => box),
|
|
56
|
+
...nodesOnScreen.flatMap((node) => {
|
|
57
|
+
const strip = badgeStrip(node);
|
|
58
|
+
return strip === undefined ? [node.box] : [node.box, strip];
|
|
59
|
+
}),
|
|
60
|
+
...pillBoxes.values(),
|
|
61
|
+
...[...curves.values()].map(curveBounds),
|
|
62
|
+
];
|
|
63
|
+
const canvas = canvasFor(extent, union(drawn), DIAGRAM_MARGIN);
|
|
64
|
+
const lanes = lanesOnScreen.map((lane) => ({ ...lane, box: shiftBox(lane.box, canvas) }));
|
|
65
|
+
const nodes = nodesOnScreen.map((node) => ({ ...node, box: shiftBox(node.box, canvas) }));
|
|
66
|
+
const edgeBoxes = {};
|
|
67
|
+
const edges = routed.map(({ edge }) => {
|
|
68
|
+
const curve = curves.get(edge.id) ?? { from: { x: 0, y: 0 }, segments: [] };
|
|
69
|
+
edgeBoxes[edge.id] = shiftBox(curveBounds(curve), canvas);
|
|
70
|
+
const pill = pillBoxes.get(edge.id);
|
|
71
|
+
return {
|
|
72
|
+
edge,
|
|
73
|
+
path: pathOf(shiftCurve(curve, canvas.shiftX, canvas.shiftY)),
|
|
74
|
+
tone: edge.status,
|
|
75
|
+
...(pill === undefined || edge.label === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: { label: { text: edge.label, box: shiftBox(pill, canvas) } }),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
width: canvas.width,
|
|
82
|
+
height: canvas.height,
|
|
83
|
+
direction: doc.direction,
|
|
84
|
+
lanes,
|
|
85
|
+
nodes,
|
|
86
|
+
edges,
|
|
87
|
+
atlas: {
|
|
88
|
+
lanes: Object.fromEntries(lanes.map(({ lane, box }) => [lane.id, box])),
|
|
89
|
+
nodes: Object.fromEntries(nodes.map(({ node, box }) => [node.id, box])),
|
|
90
|
+
edges: edgeBoxes,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/layout/layout.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAe,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,UAAU,EACV,oBAAoB,EACpB,cAAc,GAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAY,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAgCpD,MAAM,cAAc,GAAc,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAElD,MAAM,WAAW,GAAG,CAAC,KAAsB,EAAE,EAAU,EAAQ,EAAE;IAC/D,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACjC,IAAI,IAAI,KAAK,SAAS;QACpB,MAAM,IAAI,kBAAkB,CAAC,cAAc,EAAE,8BAA8B,EAAE,GAAG,CAAC,CAAC;IACpF,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,GAAQ,EAAE,MAAc,EAAO,EAAE,CAAC,CAAC;IACnD,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACpC,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACpC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;IAC5B,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;CAC/B,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CAAC,MAAkB,EAAmB,EAAE,CACzD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;IAC7B,CAAC,CAAC,SAAS;IACX,CAAC,CAAC;QACE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QACf,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU;QAC5B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK;QACvB,MAAM,EAAE,YAAY;KACrB,CAAC;AAER;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,GAAoB,EAAE,OAAO,GAAkB,EAAE,EAAU,EAAE;IAClF,MAAM,OAAO,GAAgB,EAAE,GAAG,oBAAoB,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACjF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3F,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,cAAc,CAAC,CAAC;IAC/D,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAC1B,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,EAAE,qCAAqC,CAAC,CAAC;IAE3F,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACtD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEnE,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC;IAC3C,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAG,CAAC,GAAQ,EAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtE,MAAM,WAAW,GAAG,CAAC,KAAY,EAAS,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAEvF,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACvF,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAEjF,MAAM,KAAK,GAAU;QACnB,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC;QACtC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAChC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC,CAAC;QACF,GAAG,SAAS,CAAC,MAAM,EAAE;QACrB,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC;KACzC,CAAC;IACF,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,CAAC;IAE/D,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1F,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAE1F,MAAM,SAAS,GAAwB,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAiB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAC5E,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpC,OAAO;YACL,IAAI;YACJ,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7D,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;gBAChD,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;SAClE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK,EAAE;YACL,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;YACvE,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;YACvE,KAAK,EAAE,SAAS;SACjB;KACF,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Edge, ConduitNode } from "../schema/document.js";
|
|
2
|
+
/**
|
|
3
|
+
* Layer index per node: how far down the diagram it sits.
|
|
4
|
+
*
|
|
5
|
+
* Longest path from a source, so an arrow always points at a node below the
|
|
6
|
+
* one it left. Real dependency graphs contain cycles, and a cycle has no
|
|
7
|
+
* such ordering, so the edges that close one are dropped first — the arrow
|
|
8
|
+
* still gets drawn, it just runs back up the page.
|
|
9
|
+
*
|
|
10
|
+
* `hints` are explicit rows, applied as a floor rather than an answer: a hint
|
|
11
|
+
* can push a node further down, never above something that feeds it.
|
|
12
|
+
*/
|
|
13
|
+
export declare const rankNodes: (nodes: readonly ConduitNode[], edges: readonly Edge[], hints: Readonly<Record<string, number>>) => Map<string, number>;
|
|
14
|
+
//# sourceMappingURL=rank.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rank.d.ts","sourceRoot":"","sources":["../../src/layout/rank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,UACb,SAAS,WAAW,EAAE,SACtB,SAAS,IAAI,EAAE,SACf,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KACtC,GAAG,CAAC,MAAM,EAAE,MAAM,CA6BpB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer index per node: how far down the diagram it sits.
|
|
3
|
+
*
|
|
4
|
+
* Longest path from a source, so an arrow always points at a node below the
|
|
5
|
+
* one it left. Real dependency graphs contain cycles, and a cycle has no
|
|
6
|
+
* such ordering, so the edges that close one are dropped first — the arrow
|
|
7
|
+
* still gets drawn, it just runs back up the page.
|
|
8
|
+
*
|
|
9
|
+
* `hints` are explicit rows, applied as a floor rather than an answer: a hint
|
|
10
|
+
* can push a node further down, never above something that feeds it.
|
|
11
|
+
*/
|
|
12
|
+
export const rankNodes = (nodes, edges, hints) => {
|
|
13
|
+
const forward = new Map();
|
|
14
|
+
const backward = new Map();
|
|
15
|
+
for (const node of nodes) {
|
|
16
|
+
forward.set(node.id, []);
|
|
17
|
+
backward.set(node.id, []);
|
|
18
|
+
}
|
|
19
|
+
const withinGraph = edges.filter((edge) => edge.from !== edge.to && forward.has(edge.from) && forward.has(edge.to));
|
|
20
|
+
for (const edge of withinGraph)
|
|
21
|
+
forward.get(edge.from)?.push(edge.to);
|
|
22
|
+
for (const [from, to] of forwardEdgesWithoutCycles(nodes, forward))
|
|
23
|
+
backward.get(to)?.push(from);
|
|
24
|
+
const ranks = new Map();
|
|
25
|
+
const settle = (id) => {
|
|
26
|
+
const known = ranks.get(id);
|
|
27
|
+
if (known !== undefined)
|
|
28
|
+
return known;
|
|
29
|
+
let rank = hints[id] ?? 0;
|
|
30
|
+
for (const predecessor of backward.get(id) ?? [])
|
|
31
|
+
rank = Math.max(rank, settle(predecessor) + 1);
|
|
32
|
+
ranks.set(id, rank);
|
|
33
|
+
return rank;
|
|
34
|
+
};
|
|
35
|
+
for (const node of nodes)
|
|
36
|
+
settle(node.id);
|
|
37
|
+
return ranks;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Depth-first from every node in document order, dropping any edge that
|
|
41
|
+
* reaches a node still open on the stack. Which edge closes a cycle depends
|
|
42
|
+
* on where the walk started, so the walk order is fixed by the document
|
|
43
|
+
* rather than by iteration order of a map.
|
|
44
|
+
*/
|
|
45
|
+
const forwardEdgesWithoutCycles = (nodes, forward) => {
|
|
46
|
+
const kept = [];
|
|
47
|
+
const open = new Set();
|
|
48
|
+
const closed = new Set();
|
|
49
|
+
for (const root of nodes) {
|
|
50
|
+
if (closed.has(root.id))
|
|
51
|
+
continue;
|
|
52
|
+
const stack = [{ id: root.id, next: 0 }];
|
|
53
|
+
open.add(root.id);
|
|
54
|
+
while (stack.length > 0) {
|
|
55
|
+
const frame = stack[stack.length - 1];
|
|
56
|
+
if (frame === undefined)
|
|
57
|
+
break;
|
|
58
|
+
const successors = forward.get(frame.id) ?? [];
|
|
59
|
+
if (frame.next >= successors.length) {
|
|
60
|
+
open.delete(frame.id);
|
|
61
|
+
closed.add(frame.id);
|
|
62
|
+
stack.pop();
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const successor = successors[frame.next];
|
|
66
|
+
frame.next += 1;
|
|
67
|
+
if (successor === undefined || open.has(successor))
|
|
68
|
+
continue;
|
|
69
|
+
kept.push([frame.id, successor]);
|
|
70
|
+
if (closed.has(successor))
|
|
71
|
+
continue;
|
|
72
|
+
open.add(successor);
|
|
73
|
+
stack.push({ id: successor, next: 0 });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return kept;
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=rank.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rank.js","sourceRoot":"","sources":["../../src/layout/rank.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,KAA6B,EAC7B,KAAsB,EACtB,KAAuC,EAClB,EAAE;IACvB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACzB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAClF,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,WAAW;QAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEtE,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,yBAAyB,CAAC,KAAK,EAAE,OAAO,CAAC;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEjG,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,MAAM,MAAM,GAAG,CAAC,EAAU,EAAU,EAAE;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAEtC,IAAI,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAC1B,KAAK,MAAM,WAAW,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;YAC9C,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,yBAAyB,GAAG,CAChC,KAA6B,EAC7B,OAA+C,EAC3B,EAAE;IACtB,MAAM,IAAI,GAAuB,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,SAAS;QAElC,MAAM,KAAK,GAAmC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAElB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACtC,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM;YAE/B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,KAAK,CAAC,IAAI,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;gBACpC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACtB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACrB,KAAK,CAAC,GAAG,EAAE,CAAC;gBACZ,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACzC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;YAChB,IAAI,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;gBAAE,SAAS;YAE7D,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;YACjC,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;gBAAE,SAAS;YAEpC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Edge, Lane, ConduitDocument, ConduitNode, View, ViewScope } from "../schema/document.js";
|
|
2
|
+
/** The slice of a document one SVG draws. */
|
|
3
|
+
export type ScopedGraph = {
|
|
4
|
+
lanes: Lane[];
|
|
5
|
+
nodes: ConduitNode[];
|
|
6
|
+
edges: Edge[];
|
|
7
|
+
};
|
|
8
|
+
export declare const findView: (views: readonly View[], id: string) => View | undefined;
|
|
9
|
+
export declare const flattenViews: (views: readonly View[]) => View[];
|
|
10
|
+
/**
|
|
11
|
+
* Turns a view's scope into the elements to draw.
|
|
12
|
+
*
|
|
13
|
+
* A selection pulls in what its members need to make sense: the lanes its
|
|
14
|
+
* nodes sit in, and both ends of every edge it names, since half an arrow is
|
|
15
|
+
* worse than none. Edges are the one place the author keeps full control —
|
|
16
|
+
* name any and exactly those are drawn, name none and the connections between
|
|
17
|
+
* the selected nodes come along.
|
|
18
|
+
*/
|
|
19
|
+
export declare const resolveScope: (doc: ConduitDocument, scope: ViewScope) => ScopedGraph;
|
|
20
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/layout/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,IAAI,EACJ,eAAe,EACf,WAAW,EACX,IAAI,EACJ,SAAS,EACV,MAAM,uBAAuB,CAAC;AAG/B,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,QAAQ,UAAW,SAAS,IAAI,EAAE,MAAM,MAAM,KAAG,IAAI,GAAG,SAOpE,CAAC;AAEF,eAAO,MAAM,YAAY,UAAW,SAAS,IAAI,EAAE,KAAG,IAAI,EACO,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,QAAS,eAAe,SAAS,SAAS,KAAG,WA8BrE,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { assertNever } from "../assert.js";
|
|
2
|
+
export const findView = (views, id) => {
|
|
3
|
+
for (const view of views) {
|
|
4
|
+
if (view.id === id)
|
|
5
|
+
return view;
|
|
6
|
+
const nested = findView(view.children, id);
|
|
7
|
+
if (nested)
|
|
8
|
+
return nested;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
};
|
|
12
|
+
export const flattenViews = (views) => views.flatMap((view) => [view, ...flattenViews(view.children)]);
|
|
13
|
+
/**
|
|
14
|
+
* Turns a view's scope into the elements to draw.
|
|
15
|
+
*
|
|
16
|
+
* A selection pulls in what its members need to make sense: the lanes its
|
|
17
|
+
* nodes sit in, and both ends of every edge it names, since half an arrow is
|
|
18
|
+
* worse than none. Edges are the one place the author keeps full control —
|
|
19
|
+
* name any and exactly those are drawn, name none and the connections between
|
|
20
|
+
* the selected nodes come along.
|
|
21
|
+
*/
|
|
22
|
+
export const resolveScope = (doc, scope) => {
|
|
23
|
+
switch (scope.kind) {
|
|
24
|
+
case "all":
|
|
25
|
+
return { lanes: [...doc.lanes], nodes: [...doc.nodes], edges: [...doc.edges] };
|
|
26
|
+
case "selection": {
|
|
27
|
+
const selectedLanes = new Set(scope.lanes);
|
|
28
|
+
const selectedEdges = new Set(scope.edges);
|
|
29
|
+
const nodeIds = new Set(scope.nodes);
|
|
30
|
+
for (const node of doc.nodes)
|
|
31
|
+
if (selectedLanes.has(node.lane))
|
|
32
|
+
nodeIds.add(node.id);
|
|
33
|
+
for (const edge of doc.edges) {
|
|
34
|
+
if (!selectedEdges.has(edge.id))
|
|
35
|
+
continue;
|
|
36
|
+
nodeIds.add(edge.from);
|
|
37
|
+
nodeIds.add(edge.to);
|
|
38
|
+
}
|
|
39
|
+
const nodes = doc.nodes.filter((node) => nodeIds.has(node.id));
|
|
40
|
+
const edges = scope.edges.length > 0
|
|
41
|
+
? doc.edges.filter((edge) => selectedEdges.has(edge.id))
|
|
42
|
+
: doc.edges.filter((edge) => nodeIds.has(edge.from) && nodeIds.has(edge.to));
|
|
43
|
+
const laneIds = new Set(scope.lanes);
|
|
44
|
+
for (const node of nodes)
|
|
45
|
+
laneIds.add(node.lane);
|
|
46
|
+
return { lanes: doc.lanes.filter((lane) => laneIds.has(lane.id)), nodes, edges };
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
return assertNever(scope, "Unhandled view scope");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/layout/scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Edge, Lane, ConduitNode } from "../schema/document.js";
|
|
2
|
+
/** One occupied row of one lane: a single card, or two side by side. */
|
|
3
|
+
export type SeatedRow = {
|
|
4
|
+
grid: number;
|
|
5
|
+
nodes: ConduitNode[];
|
|
6
|
+
};
|
|
7
|
+
export type Seating = {
|
|
8
|
+
rowsByLane: Map<string, SeatedRow[]>;
|
|
9
|
+
rowCount: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Where every card sits: a row grid shared across all lanes.
|
|
13
|
+
*
|
|
14
|
+
* A node with an explicit `row` claims that grid row outright. Everything
|
|
15
|
+
* else is ranked by its connections — longest path from a source — so an
|
|
16
|
+
* arrow always points at a node below the one it left, and then falls to
|
|
17
|
+
* the next row its lane has not already claimed. Seating reads connections,
|
|
18
|
+
* explicit rows and document order, never a label, so a rename moves nothing.
|
|
19
|
+
*
|
|
20
|
+
* When no row is explicit, the ranks in use are compressed onto contiguous
|
|
21
|
+
* rows, so a lane that enters the story late does not leave a hole above it.
|
|
22
|
+
* Once any row is explicit the grid is absolute — an automatic node sits at
|
|
23
|
+
* its rank — because a compressed row could land above the pinned card that
|
|
24
|
+
* feeds it, and an arrow running back up the page is worse than a gap.
|
|
25
|
+
*/
|
|
26
|
+
export declare const seatNodes: (orderedLanes: readonly Lane[], nodes: readonly ConduitNode[], edges: readonly Edge[]) => Seating;
|
|
27
|
+
//# sourceMappingURL=seating.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seating.d.ts","sourceRoot":"","sources":["../../src/layout/seating.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIrE,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,iBACN,SAAS,IAAI,EAAE,SACtB,SAAS,WAAW,EAAE,SACtB,SAAS,IAAI,EAAE,KACrB,OAoCF,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ConduitLayoutError } from "./errors.js";
|
|
2
|
+
import { rankNodes } from "./rank.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where every card sits: a row grid shared across all lanes.
|
|
5
|
+
*
|
|
6
|
+
* A node with an explicit `row` claims that grid row outright. Everything
|
|
7
|
+
* else is ranked by its connections — longest path from a source — so an
|
|
8
|
+
* arrow always points at a node below the one it left, and then falls to
|
|
9
|
+
* the next row its lane has not already claimed. Seating reads connections,
|
|
10
|
+
* explicit rows and document order, never a label, so a rename moves nothing.
|
|
11
|
+
*
|
|
12
|
+
* When no row is explicit, the ranks in use are compressed onto contiguous
|
|
13
|
+
* rows, so a lane that enters the story late does not leave a hole above it.
|
|
14
|
+
* Once any row is explicit the grid is absolute — an automatic node sits at
|
|
15
|
+
* its rank — because a compressed row could land above the pinned card that
|
|
16
|
+
* feeds it, and an arrow running back up the page is worse than a gap.
|
|
17
|
+
*/
|
|
18
|
+
export const seatNodes = (orderedLanes, nodes, edges) => {
|
|
19
|
+
const laneIndex = new Map(orderedLanes.map((lane, index) => [lane.id, index]));
|
|
20
|
+
const docIndex = new Map(nodes.map((node, index) => [node.id, index]));
|
|
21
|
+
const pinnedRows = {};
|
|
22
|
+
for (const node of nodes)
|
|
23
|
+
if (node.row !== undefined)
|
|
24
|
+
pinnedRows[node.id] = node.row;
|
|
25
|
+
const anyPinned = Object.keys(pinnedRows).length > 0;
|
|
26
|
+
const ranks = rankNodes(nodes, edges, pinnedRows);
|
|
27
|
+
const rowOfRank = anyPinned ? identityRows(ranks) : compressRanks(nodes, ranks);
|
|
28
|
+
const keys = barycenters(nodes, edges, ranks, laneIndex);
|
|
29
|
+
const rowsByLane = new Map();
|
|
30
|
+
let lastRow = -1;
|
|
31
|
+
for (const lane of orderedLanes) {
|
|
32
|
+
const members = nodes.filter((node) => node.lane === lane.id);
|
|
33
|
+
if (members.length === 0)
|
|
34
|
+
continue;
|
|
35
|
+
const pinned = pinRows(members.filter((node) => node.row !== undefined));
|
|
36
|
+
const claimed = new Set(pinned.map((row) => row.grid));
|
|
37
|
+
const automatic = seatLane(members.filter((node) => node.row === undefined), ranks, rowOfRank, keys, docIndex, claimed);
|
|
38
|
+
const rows = [...pinned, ...automatic].sort((a, b) => a.grid - b.grid);
|
|
39
|
+
rowsByLane.set(lane.id, rows);
|
|
40
|
+
lastRow = Math.max(lastRow, rows[rows.length - 1]?.grid ?? -1);
|
|
41
|
+
}
|
|
42
|
+
return { rowsByLane, rowCount: lastRow + 1 };
|
|
43
|
+
};
|
|
44
|
+
/** Rank values in use, in order, mapped onto contiguous rows. */
|
|
45
|
+
const compressRanks = (nodes, ranks) => {
|
|
46
|
+
const used = [...new Set(nodes.map((node) => ranks.get(node.id) ?? 0))].sort((a, b) => a - b);
|
|
47
|
+
return new Map(used.map((rank, index) => [rank, index]));
|
|
48
|
+
};
|
|
49
|
+
const identityRows = (ranks) => new Map([...new Set(ranks.values())].map((rank) => [rank, rank]));
|
|
50
|
+
/** Explicit rows, grouped: a row holds at most two cards, in array order. */
|
|
51
|
+
const pinRows = (pinned) => {
|
|
52
|
+
const byRow = new Map();
|
|
53
|
+
for (const node of pinned) {
|
|
54
|
+
const grid = node.row ?? 0;
|
|
55
|
+
const list = byRow.get(grid) ?? [];
|
|
56
|
+
list.push(node);
|
|
57
|
+
byRow.set(grid, list);
|
|
58
|
+
}
|
|
59
|
+
return [...byRow.entries()]
|
|
60
|
+
.sort(([a], [b]) => a - b)
|
|
61
|
+
.map(([grid, members]) => {
|
|
62
|
+
if (members.length > 2)
|
|
63
|
+
throw new ConduitLayoutError("ROW_OVERFULL", `lane '${members[0]?.lane}' row ${grid} holds ${members.length} nodes; a row holds at most two`);
|
|
64
|
+
return { grid, nodes: members };
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const barycenters = (nodes, edges, ranks, laneIndex) => {
|
|
68
|
+
const laneOf = new Map(nodes.map((node) => [node.id, laneIndex.get(node.lane) ?? 0]));
|
|
69
|
+
const partners = new Map(nodes.map((node) => [node.id, []]));
|
|
70
|
+
for (const edge of edges) {
|
|
71
|
+
if (edge.from === edge.to || !laneOf.has(edge.from) || !laneOf.has(edge.to))
|
|
72
|
+
continue;
|
|
73
|
+
partners.get(edge.from)?.push(edge.to);
|
|
74
|
+
partners.get(edge.to)?.push(edge.from);
|
|
75
|
+
}
|
|
76
|
+
const fall = new Map();
|
|
77
|
+
const side = new Map();
|
|
78
|
+
for (const node of nodes) {
|
|
79
|
+
const linked = partners.get(node.id) ?? [];
|
|
80
|
+
fall.set(node.id, mean(linked.map((id) => ranks.get(id) ?? 0)) ?? ranks.get(node.id) ?? 0);
|
|
81
|
+
side.set(node.id, mean(linked.map((id) => laneOf.get(id) ?? 0)) ?? laneOf.get(node.id) ?? 0);
|
|
82
|
+
}
|
|
83
|
+
return { fall, side };
|
|
84
|
+
};
|
|
85
|
+
const mean = (values) => values.length === 0 ? undefined : values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
86
|
+
/**
|
|
87
|
+
* One lane's automatic members onto the shared grid.
|
|
88
|
+
*
|
|
89
|
+
* A card sits at its rank's row unless an earlier card of the same lane
|
|
90
|
+
* already claimed it — by rank or by an explicit row — in which case it falls
|
|
91
|
+
* to the next free one. Two cards share a row only at the same rank in the
|
|
92
|
+
* same sub-group. Who falls, and who sits left in a pair, is decided by where
|
|
93
|
+
* each card's partners are, with document order as the stable tiebreak.
|
|
94
|
+
*/
|
|
95
|
+
const seatLane = (members, ranks, rowOfRank, keys, docIndex, claimed) => {
|
|
96
|
+
const doc = (node) => docIndex.get(node.id) ?? 0;
|
|
97
|
+
const sorted = [...members].sort((a, b) => {
|
|
98
|
+
const row = (rowOfRank.get(ranks.get(a.id) ?? 0) ?? 0) - (rowOfRank.get(ranks.get(b.id) ?? 0) ?? 0);
|
|
99
|
+
if (row !== 0)
|
|
100
|
+
return row;
|
|
101
|
+
const fall = (keys.fall.get(a.id) ?? 0) - (keys.fall.get(b.id) ?? 0);
|
|
102
|
+
if (fall !== 0)
|
|
103
|
+
return fall;
|
|
104
|
+
return doc(a) - doc(b);
|
|
105
|
+
});
|
|
106
|
+
const rows = [];
|
|
107
|
+
let previous = -1;
|
|
108
|
+
for (const node of sorted) {
|
|
109
|
+
const rank = ranks.get(node.id) ?? 0;
|
|
110
|
+
const open = rows[rows.length - 1];
|
|
111
|
+
if (open !== undefined &&
|
|
112
|
+
open.nodes.length === 1 &&
|
|
113
|
+
open.rank === rank &&
|
|
114
|
+
(open.nodes[0]?.group ?? "") === (node.group ?? "")) {
|
|
115
|
+
const first = open.nodes[0];
|
|
116
|
+
if (first !== undefined && leansLeft(node, first, keys, doc))
|
|
117
|
+
open.nodes.unshift(node);
|
|
118
|
+
else
|
|
119
|
+
open.nodes.push(node);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
previous = Math.max(previous + 1, rowOfRank.get(rank) ?? 0);
|
|
123
|
+
while (claimed.has(previous))
|
|
124
|
+
previous += 1;
|
|
125
|
+
rows.push({ grid: previous, rank, nodes: [node] });
|
|
126
|
+
}
|
|
127
|
+
return rows.map(({ grid, nodes }) => ({ grid, nodes }));
|
|
128
|
+
};
|
|
129
|
+
const leansLeft = (node, other, keys, doc) => {
|
|
130
|
+
const side = (keys.side.get(node.id) ?? 0) - (keys.side.get(other.id) ?? 0);
|
|
131
|
+
if (side !== 0)
|
|
132
|
+
return side < 0;
|
|
133
|
+
return doc(node) < doc(other);
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=seating.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seating.js","sourceRoot":"","sources":["../../src/layout/seating.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The renderer never asks a font engine how wide a string is: it must produce
|
|
3
|
+
* the same geometry on a CI runner with no fonts installed as on a laptop
|
|
4
|
+
* with all of them. Widths come from a table instead, and every box is sized
|
|
5
|
+
* with enough slack that the small error against a real face stays invisible.
|
|
6
|
+
*/
|
|
7
|
+
export type Face = "sans" | "sans-bold" | "mono";
|
|
8
|
+
export declare const SANS_STACK = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif";
|
|
9
|
+
export declare const MONO_STACK = "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace";
|
|
10
|
+
/** Width of `text` in user units at `fontSize`, to a hundredth of a unit. */
|
|
11
|
+
export declare const measure: (text: string, face: Face, fontSize: number) => number;
|
|
12
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/layout/text.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAEjD,eAAO,MAAM,UAAU,iGACqE,CAAC;AAC7F,eAAO,MAAM,UAAU,0EAAwE,CAAC;AAsJhG,6EAA6E;AAC7E,eAAO,MAAM,OAAO,SAAU,MAAM,QAAQ,IAAI,YAAY,MAAM,KAAG,MAIpE,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { assertNever } from "../assert.js";
|
|
2
|
+
export const SANS_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif';
|
|
3
|
+
export const MONO_STACK = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
|
|
4
|
+
/** Advance widths per 1000 units of em, for the regular sans face. */
|
|
5
|
+
const SANS_ADVANCE = {
|
|
6
|
+
" ": 278,
|
|
7
|
+
"!": 278,
|
|
8
|
+
'"': 355,
|
|
9
|
+
"#": 556,
|
|
10
|
+
$: 556,
|
|
11
|
+
"%": 889,
|
|
12
|
+
"&": 667,
|
|
13
|
+
"'": 191,
|
|
14
|
+
"(": 333,
|
|
15
|
+
")": 333,
|
|
16
|
+
"*": 389,
|
|
17
|
+
"+": 584,
|
|
18
|
+
",": 278,
|
|
19
|
+
"-": 333,
|
|
20
|
+
".": 278,
|
|
21
|
+
"/": 278,
|
|
22
|
+
"0": 556,
|
|
23
|
+
"1": 556,
|
|
24
|
+
"2": 556,
|
|
25
|
+
"3": 556,
|
|
26
|
+
"4": 556,
|
|
27
|
+
"5": 556,
|
|
28
|
+
"6": 556,
|
|
29
|
+
"7": 556,
|
|
30
|
+
"8": 556,
|
|
31
|
+
"9": 556,
|
|
32
|
+
":": 278,
|
|
33
|
+
";": 278,
|
|
34
|
+
"<": 584,
|
|
35
|
+
"=": 584,
|
|
36
|
+
">": 584,
|
|
37
|
+
"?": 556,
|
|
38
|
+
"@": 1015,
|
|
39
|
+
A: 667,
|
|
40
|
+
B: 667,
|
|
41
|
+
C: 722,
|
|
42
|
+
D: 722,
|
|
43
|
+
E: 667,
|
|
44
|
+
F: 611,
|
|
45
|
+
G: 778,
|
|
46
|
+
H: 722,
|
|
47
|
+
I: 278,
|
|
48
|
+
J: 500,
|
|
49
|
+
K: 667,
|
|
50
|
+
L: 556,
|
|
51
|
+
M: 833,
|
|
52
|
+
N: 722,
|
|
53
|
+
O: 778,
|
|
54
|
+
P: 667,
|
|
55
|
+
Q: 778,
|
|
56
|
+
R: 722,
|
|
57
|
+
S: 667,
|
|
58
|
+
T: 611,
|
|
59
|
+
U: 722,
|
|
60
|
+
V: 667,
|
|
61
|
+
W: 944,
|
|
62
|
+
X: 667,
|
|
63
|
+
Y: 667,
|
|
64
|
+
Z: 611,
|
|
65
|
+
"[": 278,
|
|
66
|
+
"\\": 278,
|
|
67
|
+
"]": 278,
|
|
68
|
+
"^": 469,
|
|
69
|
+
_: 556,
|
|
70
|
+
"`": 333,
|
|
71
|
+
a: 556,
|
|
72
|
+
b: 556,
|
|
73
|
+
c: 500,
|
|
74
|
+
d: 556,
|
|
75
|
+
e: 556,
|
|
76
|
+
f: 278,
|
|
77
|
+
g: 556,
|
|
78
|
+
h: 556,
|
|
79
|
+
i: 222,
|
|
80
|
+
j: 222,
|
|
81
|
+
k: 500,
|
|
82
|
+
l: 222,
|
|
83
|
+
m: 833,
|
|
84
|
+
n: 556,
|
|
85
|
+
o: 556,
|
|
86
|
+
p: 556,
|
|
87
|
+
q: 556,
|
|
88
|
+
r: 333,
|
|
89
|
+
s: 500,
|
|
90
|
+
t: 278,
|
|
91
|
+
u: 556,
|
|
92
|
+
v: 500,
|
|
93
|
+
w: 722,
|
|
94
|
+
x: 500,
|
|
95
|
+
y: 500,
|
|
96
|
+
z: 500,
|
|
97
|
+
"{": 334,
|
|
98
|
+
"|": 260,
|
|
99
|
+
"}": 334,
|
|
100
|
+
"~": 584,
|
|
101
|
+
};
|
|
102
|
+
/** What a narrow character outside the table is assumed to cost, in the same units. */
|
|
103
|
+
const FALLBACK_ADVANCE = 600;
|
|
104
|
+
/**
|
|
105
|
+
* East Asian Wide and Fullwidth glyphs occupy a full em. Measuring them at
|
|
106
|
+
* the narrow fallback puts every CJK label about 38% short of its real width,
|
|
107
|
+
* which is enough for an edge label's plate to stop covering its own text.
|
|
108
|
+
*/
|
|
109
|
+
const isWide = (character) => {
|
|
110
|
+
const point = character.codePointAt(0) ?? 0;
|
|
111
|
+
return ((point >= 0x1100 && point <= 0x115f) ||
|
|
112
|
+
(point >= 0x2e80 && point <= 0xa4cf) ||
|
|
113
|
+
(point >= 0xac00 && point <= 0xd7a3) ||
|
|
114
|
+
(point >= 0xf900 && point <= 0xfaff) ||
|
|
115
|
+
(point >= 0xfe30 && point <= 0xfe6f) ||
|
|
116
|
+
(point >= 0xff00 && point <= 0xff60) ||
|
|
117
|
+
(point >= 0xffe0 && point <= 0xffe6) ||
|
|
118
|
+
(point >= 0x20000 && point <= 0x3fffd));
|
|
119
|
+
};
|
|
120
|
+
const FULL_WIDTH_ADVANCE = 1000;
|
|
121
|
+
const fallbackAdvance = (character) => isWide(character) ? FULL_WIDTH_ADVANCE : FALLBACK_ADVANCE;
|
|
122
|
+
/**
|
|
123
|
+
* The bold face of the same family runs a little wider at every weight step.
|
|
124
|
+
* One factor over the regular table is close enough for boxes that already
|
|
125
|
+
* carry padding, and it keeps a second table from drifting out of sync.
|
|
126
|
+
*/
|
|
127
|
+
const BOLD_WIDENING = 1.06;
|
|
128
|
+
const MONO_ADVANCE = 600;
|
|
129
|
+
const advanceFor = (face, character) => {
|
|
130
|
+
switch (face) {
|
|
131
|
+
case "sans":
|
|
132
|
+
return SANS_ADVANCE[character] ?? fallbackAdvance(character);
|
|
133
|
+
case "sans-bold":
|
|
134
|
+
return (SANS_ADVANCE[character] ?? fallbackAdvance(character)) * BOLD_WIDENING;
|
|
135
|
+
case "mono":
|
|
136
|
+
return MONO_ADVANCE;
|
|
137
|
+
default:
|
|
138
|
+
return assertNever(face, "Unhandled font face");
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
/** Width of `text` in user units at `fontSize`, to a hundredth of a unit. */
|
|
142
|
+
export const measure = (text, face, fontSize) => {
|
|
143
|
+
let thousandths = 0;
|
|
144
|
+
for (const character of text)
|
|
145
|
+
thousandths += advanceFor(face, character);
|
|
146
|
+
return Math.round((thousandths / 1000) * fontSize * 100) / 100;
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=text.js.map
|