@zukhruf/diagram 0.3.32 → 0.3.34

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 (122) hide show
  1. package/README.md +98 -52
  2. package/dist/index.d.ts +5 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/compiler/diagram-compile.d.ts +3 -4
  7. package/dist/lib/compiler/diagram-compile.d.ts.map +1 -1
  8. package/dist/lib/compiler/diagram-compile.js +85 -370
  9. package/dist/lib/compiler/diagram-compile.js.map +1 -1
  10. package/dist/lib/layouts/graph/attach.d.ts +25 -0
  11. package/dist/lib/layouts/graph/attach.d.ts.map +1 -0
  12. package/dist/lib/layouts/graph/attach.js +272 -0
  13. package/dist/lib/layouts/graph/attach.js.map +1 -0
  14. package/dist/lib/layouts/graph/connections.d.ts +45 -0
  15. package/dist/lib/layouts/graph/connections.d.ts.map +1 -0
  16. package/dist/lib/layouts/graph/connections.js +138 -0
  17. package/dist/lib/layouts/graph/connections.js.map +1 -0
  18. package/dist/lib/layouts/graph/graph-layout-result.d.ts +5 -3
  19. package/dist/lib/layouts/graph/graph-layout-result.d.ts.map +1 -1
  20. package/dist/lib/layouts/graph/graph-layout-result.js.map +1 -1
  21. package/dist/lib/layouts/graph/gutters.js +41 -22
  22. package/dist/lib/layouts/graph/gutters.js.map +1 -1
  23. package/dist/lib/layouts/graph/labels.d.ts +23 -0
  24. package/dist/lib/layouts/graph/labels.d.ts.map +1 -0
  25. package/dist/lib/layouts/graph/labels.js +120 -0
  26. package/dist/lib/layouts/graph/labels.js.map +1 -0
  27. package/dist/lib/layouts/graph/layout.d.ts +1 -1
  28. package/dist/lib/layouts/graph/layout.d.ts.map +1 -1
  29. package/dist/lib/layouts/graph/layout.js +37 -12
  30. package/dist/lib/layouts/graph/layout.js.map +1 -1
  31. package/dist/lib/layouts/graph/measure.d.ts +15 -11
  32. package/dist/lib/layouts/graph/measure.d.ts.map +1 -1
  33. package/dist/lib/layouts/graph/measure.js +82 -38
  34. package/dist/lib/layouts/graph/measure.js.map +1 -1
  35. package/dist/lib/layouts/graph/min-heap.d.ts +8 -0
  36. package/dist/lib/layouts/graph/min-heap.d.ts.map +1 -0
  37. package/dist/lib/layouts/graph/min-heap.js +56 -0
  38. package/dist/lib/layouts/graph/min-heap.js.map +1 -0
  39. package/dist/lib/layouts/graph/node-content.d.ts +2 -3
  40. package/dist/lib/layouts/graph/node-content.d.ts.map +1 -1
  41. package/dist/lib/layouts/graph/node-content.js +67 -47
  42. package/dist/lib/layouts/graph/node-content.js.map +1 -1
  43. package/dist/lib/layouts/graph/path.d.ts +6 -0
  44. package/dist/lib/layouts/graph/path.d.ts.map +1 -0
  45. package/dist/lib/layouts/graph/path.js +53 -0
  46. package/dist/lib/layouts/graph/path.js.map +1 -0
  47. package/dist/lib/layouts/graph/round.d.ts +11 -0
  48. package/dist/lib/layouts/graph/round.d.ts.map +1 -0
  49. package/dist/lib/layouts/graph/round.js +181 -0
  50. package/dist/lib/layouts/graph/round.js.map +1 -0
  51. package/dist/lib/layouts/graph/route-search.d.ts +34 -0
  52. package/dist/lib/layouts/graph/route-search.d.ts.map +1 -0
  53. package/dist/lib/layouts/graph/route-search.js +208 -0
  54. package/dist/lib/layouts/graph/route-search.js.map +1 -0
  55. package/dist/lib/layouts/graph/route.d.ts +20 -0
  56. package/dist/lib/layouts/graph/route.d.ts.map +1 -0
  57. package/dist/lib/layouts/graph/route.js +276 -0
  58. package/dist/lib/layouts/graph/route.js.map +1 -0
  59. package/dist/lib/layouts/graph/sides.d.ts +28 -0
  60. package/dist/lib/layouts/graph/sides.d.ts.map +1 -0
  61. package/dist/lib/layouts/graph/sides.js +98 -0
  62. package/dist/lib/layouts/graph/sides.js.map +1 -0
  63. package/dist/lib/layouts/timeline/timeline-layout.d.ts +2 -1
  64. package/dist/lib/layouts/timeline/timeline-layout.d.ts.map +1 -1
  65. package/dist/lib/layouts/timeline/timeline-layout.js +9 -4
  66. package/dist/lib/layouts/timeline/timeline-layout.js.map +1 -1
  67. package/dist/lib/model/diagram-ir.d.ts +19 -156
  68. package/dist/lib/model/diagram-ir.d.ts.map +1 -1
  69. package/dist/lib/model/diagram-ir.js.map +1 -1
  70. package/dist/lib/model/validate-graph.d.ts +7 -0
  71. package/dist/lib/model/validate-graph.d.ts.map +1 -0
  72. package/dist/lib/model/validate-graph.js +54 -0
  73. package/dist/lib/model/validate-graph.js.map +1 -0
  74. package/dist/lib/react/Diagram.d.ts +3 -9
  75. package/dist/lib/react/Diagram.d.ts.map +1 -1
  76. package/dist/lib/react/Diagram.js +1 -13
  77. package/dist/lib/react/Diagram.js.map +1 -1
  78. package/dist/lib/react/diagram-types.d.ts +17 -46
  79. package/dist/lib/react/diagram-types.d.ts.map +1 -1
  80. package/dist/lib/react/diagram-types.js.map +1 -1
  81. package/dist/lib/renderers/canvas/graph-canvas.d.ts +2 -5
  82. package/dist/lib/renderers/canvas/graph-canvas.d.ts.map +1 -1
  83. package/dist/lib/renderers/canvas/graph-canvas.js +68 -43
  84. package/dist/lib/renderers/canvas/graph-canvas.js.map +1 -1
  85. package/dist/lib/renderers/html/graph-html.d.ts +2 -3
  86. package/dist/lib/renderers/html/graph-html.d.ts.map +1 -1
  87. package/dist/lib/renderers/html/graph-html.js +298 -289
  88. package/dist/lib/renderers/html/graph-html.js.map +1 -1
  89. package/dist/lib/renderers/shared/graph-parts.d.ts +3 -1
  90. package/dist/lib/renderers/shared/graph-parts.d.ts.map +1 -1
  91. package/dist/lib/renderers/shared/graph-parts.js +350 -418
  92. package/dist/lib/renderers/shared/graph-parts.js.map +1 -1
  93. package/dist/lib/renderers/shared/node-parts.d.ts +5 -6
  94. package/dist/lib/renderers/shared/node-parts.d.ts.map +1 -1
  95. package/dist/lib/renderers/shared/node-parts.js +112 -190
  96. package/dist/lib/renderers/shared/node-parts.js.map +1 -1
  97. package/dist/lib/renderers/shared/run-spans.js +2 -2
  98. package/dist/lib/renderers/shared/run-spans.js.map +1 -1
  99. package/dist/lib/renderers/shared/text-runs.d.ts +0 -6
  100. package/dist/lib/renderers/shared/text-runs.d.ts.map +1 -1
  101. package/dist/lib/renderers/shared/text-runs.js +0 -12
  102. package/dist/lib/renderers/shared/text-runs.js.map +1 -1
  103. package/dist/lib/renderers/svg/graph-svg.d.ts +1 -3
  104. package/dist/lib/renderers/svg/graph-svg.d.ts.map +1 -1
  105. package/dist/lib/renderers/svg/graph-svg.js +66 -80
  106. package/dist/lib/renderers/svg/graph-svg.js.map +1 -1
  107. package/dist/lib/renderers/svg/timeline-svg.d.ts.map +1 -1
  108. package/dist/lib/renderers/svg/timeline-svg.js +7 -5
  109. package/dist/lib/renderers/svg/timeline-svg.js.map +1 -1
  110. package/package.json +1 -1
  111. package/dist/lib/layouts/graph/graph-layout.d.ts +0 -154
  112. package/dist/lib/layouts/graph/graph-layout.d.ts.map +0 -1
  113. package/dist/lib/layouts/graph/graph-layout.js +0 -2340
  114. package/dist/lib/layouts/graph/graph-layout.js.map +0 -1
  115. package/dist/lib/layouts/graph/legacy-graph-layout.d.ts +0 -6
  116. package/dist/lib/layouts/graph/legacy-graph-layout.d.ts.map +0 -1
  117. package/dist/lib/layouts/graph/legacy-graph-layout.js +0 -542
  118. package/dist/lib/layouts/graph/legacy-graph-layout.js.map +0 -1
  119. package/dist/lib/layouts/text-metrics.d.ts +0 -7
  120. package/dist/lib/layouts/text-metrics.d.ts.map +0 -1
  121. package/dist/lib/layouts/text-metrics.js +0 -40
  122. package/dist/lib/layouts/text-metrics.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connections.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/connections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,cAAc,EACd,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACX,MAAM,0BAA0B,CAAC;AAIlC,2DAA2D;AAC3D,MAAM,MAAM,KAAK,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC;IACpC,GAAG,EAAE,GAAG,CAAC;IACT,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,EAAE,GAAG,EAAE,CAAC;IACd,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,sEAAsE;IACtE,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;IAC7B,0GAA0G;IAC1G,IAAI,EAAE,GAAG,CAAC;CACX,CAAC;AAEF,sGAAsG;AACtG,MAAM,MAAM,UAAU,GAAG;IACvB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,aAAa,CAAC;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,SAAS,GACf,UAAU,EAAE,EAAE,CAiJhB"}
@@ -0,0 +1,138 @@
1
+ import { parseEndpointRef } from "../../model/endpoint-ref.js";
2
+ import { sideLine, sidePairs } from "./sides.js";
3
+ import { LAYOUT_TOKENS } from "./tokens.js";
4
+
5
+ /** A node or group that a connection starts or ends at. */
6
+
7
+ /** An edge, or the leader from a note to its target: anything the router draws between two shapes. */
8
+
9
+ /**
10
+ * Resolves every edge and every note leader to the shapes and sides it may
11
+ * join, in order of preference. A side hint wins over the cells; a port
12
+ * fixes the side; a side that declares ports takes no edge without one. A
13
+ * connection between two shapes with no hint and no port on either end may
14
+ * also take the other side pairs the cells allow, whose sides declare no
15
+ * ports; any other connection has one choice.
16
+ */
17
+ export function graphConnections(ir, nodes, groups, areas) {
18
+ const shapes = new Map();
19
+ for (const {
20
+ node,
21
+ box,
22
+ frames,
23
+ radius,
24
+ ports,
25
+ caption
26
+ } of nodes) {
27
+ shapes.set(node.id, {
28
+ id: node.id,
29
+ kind: node.kind === 'junction' ? 'junction' : 'node',
30
+ box,
31
+ radius,
32
+ frames,
33
+ ports,
34
+ clear: caption ? [caption.box] : [],
35
+ area: areas.nodes.get(node.id)
36
+ });
37
+ }
38
+ for (const [index, {
39
+ group,
40
+ rect,
41
+ ports
42
+ }] of groups.entries()) {
43
+ shapes.set(group.id, {
44
+ id: group.id,
45
+ kind: 'group',
46
+ box: rect,
47
+ radius: LAYOUT_TOKENS.RADIUS,
48
+ frames: [],
49
+ ports,
50
+ clear: [],
51
+ area: areas.groups[index].area
52
+ });
53
+ }
54
+ const connect = (name, edge, fromRef, toRef, hints) => {
55
+ const resolve = ref => {
56
+ const {
57
+ nodeId,
58
+ portId
59
+ } = parseEndpointRef(ref);
60
+ const shape = shapes.get(nodeId);
61
+ if (!shape) {
62
+ throw new Error(`Diagram ${name}: "${nodeId}" is not a node or a group.`);
63
+ }
64
+ if (portId === undefined) return {
65
+ shape,
66
+ port: undefined
67
+ };
68
+ const port = shape.ports.find(candidate => candidate.id === portId);
69
+ if (!port) {
70
+ throw new Error(`Diagram ${name}: "${nodeId}" has no port "${portId}".`);
71
+ }
72
+ return {
73
+ shape,
74
+ port
75
+ };
76
+ };
77
+ const from = resolve(fromRef);
78
+ const to = resolve(toRef);
79
+ const a = from.shape.area;
80
+ const b = to.shape.area;
81
+ const [[exitByCells, entryByCells], ...others] = from.shape === to.shape ?
82
+ // A loop has no cell delta to read; it leaves and re-enters the right side.
83
+ [['right', 'right']] : sidePairs(a, b);
84
+ const declaresPorts = (shape, side) => shape.ports.some(port => port.side === side);
85
+ const sideEnd = (shape, side) => ({
86
+ shape,
87
+ side,
88
+ port: undefined,
89
+ face: outermostFace(shape, side)
90
+ });
91
+ const end = ({
92
+ shape,
93
+ port
94
+ }, hint, byCells, role, ref) => {
95
+ if (port) {
96
+ if (hint !== undefined && hint !== port.side) {
97
+ throw new Error(`Diagram ${name}: ${role} ${hint} contradicts port "${ref}" on side ${port.side}.`);
98
+ }
99
+ return {
100
+ shape,
101
+ side: port.side,
102
+ port,
103
+ face: shape.box
104
+ };
105
+ }
106
+ const side = hint ?? byCells;
107
+ if (declaresPorts(shape, side)) {
108
+ throw new Error(`Diagram ${shape.kind === 'group' ? 'group' : 'node'} "${shape.id}" side ${side} declares ports; ${name} must use "${shape.id}:<port>".`);
109
+ }
110
+ return sideEnd(shape, side);
111
+ };
112
+ const cellDistance = Math.abs(b.row + b.rowSpan / 2 - (a.row + a.rowSpan / 2)) + Math.abs(b.col + b.colSpan / 2 - (a.col + a.colSpan / 2));
113
+ const chosen = {
114
+ name,
115
+ edge,
116
+ from: end(from, hints.exitSide, exitByCells, 'exitSide', fromRef),
117
+ to: end(to, hints.entrySide, entryByCells, 'entrySide', toRef),
118
+ cellDistance
119
+ };
120
+ const free = hints.exitSide === undefined && hints.entrySide === undefined && !from.port && !to.port;
121
+ if (!free) return [chosen];
122
+ return [chosen, ...others.filter(([exit, entry]) => !declaresPorts(from.shape, exit) && !declaresPorts(to.shape, entry)).map(([exit, entry]) => ({
123
+ name,
124
+ edge,
125
+ from: sideEnd(from.shape, exit),
126
+ to: sideEnd(to.shape, entry),
127
+ cellDistance
128
+ }))];
129
+ };
130
+ return [...ir.edges.map((edge, index) => connect(`edge ${edge.from}→${edge.to}`, index, edge.from, edge.to, edge)), ...ir.nodes.flatMap(node => node.kind === 'note' && node.target !== undefined ? [connect(`note leader "${node.id}"→"${node.target}"`, undefined, node.id, node.target, {})] : [])];
131
+ }
132
+
133
+ /** The frame of `shape` whose `side` lies farthest out: the front frame, or a stacked back frame. */
134
+ function outermostFace(shape, side) {
135
+ const outward = side === 'bottom' || side === 'right' ? 1 : -1;
136
+ return shape.frames.reduce((best, frame) => sideLine(frame, side) * outward > sideLine(best, side) * outward ? frame : best, shape.box);
137
+ }
138
+ //# sourceMappingURL=connections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connections.js","names":["parseEndpointRef","sideLine","sidePairs","LAYOUT_TOKENS","graphConnections","ir","nodes","groups","areas","shapes","Map","node","box","frames","radius","ports","caption","set","id","kind","clear","area","get","index","group","rect","entries","RADIUS","connect","name","edge","fromRef","toRef","hints","resolve","ref","nodeId","portId","shape","Error","undefined","port","find","candidate","from","to","a","b","exitByCells","entryByCells","others","declaresPorts","side","some","sideEnd","face","outermostFace","end","hint","byCells","role","cellDistance","Math","abs","row","rowSpan","col","colSpan","chosen","exitSide","entrySide","free","filter","exit","entry","map","edges","flatMap","target","outward","reduce","best","frame"],"sources":["../../../../src/lib/layouts/graph/connections.ts"],"sourcesContent":["import type {\n Box,\n DiagramGraphIR,\n DiagramSide,\n} from '../../model/diagram-ir.ts';\nimport { parseEndpointRef } from '../../model/endpoint-ref.ts';\nimport type { CellArea, GridAreas } from './areas.ts';\nimport type {\n GroupLayout,\n NodeLayout,\n PortLayout,\n} from './graph-layout-result.ts';\nimport { type SidePair, sideLine, sidePairs } from './sides.ts';\nimport { LAYOUT_TOKENS } from './tokens.ts';\n\n/** A node or group that a connection starts or ends at. */\nexport type Shape = {\n id: string;\n kind: 'node' | 'junction' | 'group';\n box: Box;\n /** The corner radius, which slots keep clear of. */\n radius: number;\n /** A stack's back frames, which draw as one unit with `box`. */\n frames: Box[];\n ports: PortLayout[];\n /** What a connection may not touch the shape through: its caption. */\n clear: Box[];\n area: CellArea;\n};\n\nexport type ConnectionEnd = {\n shape: Shape;\n side: DiagramSide;\n port: PortLayout | undefined;\n /** The box whose `side` the end touches: of a stack, the frame that reaches farthest out on that side. */\n face: Box;\n};\n\n/** An edge, or the leader from a note to its target: anything the router draws between two shapes. */\nexport type Connection = {\n /** How errors name it: `edge a→b`, or `note leader \"n\"→\"t\"`. */\n name: string;\n /** The index of the edge in the graph, or undefined for a leader. */\n edge: number | undefined;\n from: ConnectionEnd;\n to: ConnectionEnd;\n /** The Manhattan distance between the two cell areas' centres. */\n cellDistance: number;\n};\n\n/**\n * Resolves every edge and every note leader to the shapes and sides it may\n * join, in order of preference. A side hint wins over the cells; a port\n * fixes the side; a side that declares ports takes no edge without one. A\n * connection between two shapes with no hint and no port on either end may\n * also take the other side pairs the cells allow, whose sides declare no\n * ports; any other connection has one choice.\n */\nexport function graphConnections(\n ir: DiagramGraphIR,\n nodes: NodeLayout[],\n groups: GroupLayout[],\n areas: GridAreas,\n): Connection[][] {\n const shapes = new Map<string, Shape>();\n for (const { node, box, frames, radius, ports, caption } of nodes) {\n shapes.set(node.id, {\n id: node.id,\n kind: node.kind === 'junction' ? 'junction' : 'node',\n box,\n radius,\n frames,\n ports,\n clear: caption ? [caption.box] : [],\n area: areas.nodes.get(node.id)!,\n });\n }\n for (const [index, { group, rect, ports }] of groups.entries()) {\n shapes.set(group.id, {\n id: group.id,\n kind: 'group',\n box: rect,\n radius: LAYOUT_TOKENS.RADIUS,\n frames: [],\n ports,\n clear: [],\n area: areas.groups[index]!.area,\n });\n }\n\n const connect = (\n name: string,\n edge: number | undefined,\n fromRef: string,\n toRef: string,\n hints: {\n exitSide?: DiagramSide | undefined;\n entrySide?: DiagramSide | undefined;\n },\n ): Connection[] => {\n const resolve = (ref: string) => {\n const { nodeId, portId } = parseEndpointRef(ref);\n const shape = shapes.get(nodeId);\n if (!shape) {\n throw new Error(\n `Diagram ${name}: \"${nodeId}\" is not a node or a group.`,\n );\n }\n if (portId === undefined) return { shape, port: undefined };\n const port = shape.ports.find((candidate) => candidate.id === portId);\n if (!port) {\n throw new Error(\n `Diagram ${name}: \"${nodeId}\" has no port \"${portId}\".`,\n );\n }\n return { shape, port };\n };\n const from = resolve(fromRef);\n const to = resolve(toRef);\n const a = from.shape.area;\n const b = to.shape.area;\n const [[exitByCells, entryByCells], ...others]: [SidePair, ...SidePair[]] =\n from.shape === to.shape\n ? // A loop has no cell delta to read; it leaves and re-enters the right side.\n [['right', 'right']]\n : sidePairs(a, b);\n const declaresPorts = (shape: Shape, side: DiagramSide) =>\n shape.ports.some((port) => port.side === side);\n const sideEnd = (shape: Shape, side: DiagramSide): ConnectionEnd => ({\n shape,\n side,\n port: undefined,\n face: outermostFace(shape, side),\n });\n const end = (\n { shape, port }: { shape: Shape; port: PortLayout | undefined },\n hint: DiagramSide | undefined,\n byCells: DiagramSide,\n role: 'exitSide' | 'entrySide',\n ref: string,\n ): ConnectionEnd => {\n if (port) {\n if (hint !== undefined && hint !== port.side) {\n throw new Error(\n `Diagram ${name}: ${role} ${hint} contradicts port \"${ref}\" on side ${port.side}.`,\n );\n }\n return { shape, side: port.side, port, face: shape.box };\n }\n const side = hint ?? byCells;\n if (declaresPorts(shape, side)) {\n throw new Error(\n `Diagram ${shape.kind === 'group' ? 'group' : 'node'} \"${shape.id}\" side ${side} declares ports; ${name} must use \"${shape.id}:<port>\".`,\n );\n }\n return sideEnd(shape, side);\n };\n const cellDistance =\n Math.abs(b.row + b.rowSpan / 2 - (a.row + a.rowSpan / 2)) +\n Math.abs(b.col + b.colSpan / 2 - (a.col + a.colSpan / 2));\n const chosen = {\n name,\n edge,\n from: end(from, hints.exitSide, exitByCells, 'exitSide', fromRef),\n to: end(to, hints.entrySide, entryByCells, 'entrySide', toRef),\n cellDistance,\n };\n const free =\n hints.exitSide === undefined &&\n hints.entrySide === undefined &&\n !from.port &&\n !to.port;\n if (!free) return [chosen];\n return [\n chosen,\n ...others\n .filter(\n ([exit, entry]) =>\n !declaresPorts(from.shape, exit) && !declaresPorts(to.shape, entry),\n )\n .map(([exit, entry]) => ({\n name,\n edge,\n from: sideEnd(from.shape, exit),\n to: sideEnd(to.shape, entry),\n cellDistance,\n })),\n ];\n };\n\n return [\n ...ir.edges.map((edge, index) =>\n connect(`edge ${edge.from}→${edge.to}`, index, edge.from, edge.to, edge),\n ),\n ...ir.nodes.flatMap((node) =>\n node.kind === 'note' && node.target !== undefined\n ? [\n connect(\n `note leader \"${node.id}\"→\"${node.target}\"`,\n undefined,\n node.id,\n node.target,\n {},\n ),\n ]\n : [],\n ),\n ];\n}\n\n/** The frame of `shape` whose `side` lies farthest out: the front frame, or a stacked back frame. */\nfunction outermostFace(shape: Shape, side: DiagramSide): Box {\n const outward = side === 'bottom' || side === 'right' ? 1 : -1;\n return shape.frames.reduce(\n (best, frame) =>\n sideLine(frame, side) * outward > sideLine(best, side) * outward\n ? frame\n : best,\n shape.box,\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -34,6 +34,8 @@ export type NodeLayout = {
34
34
  };
35
35
  text: TextRun[];
36
36
  bullets: Point[];
37
+ /** The box reserved for the node's React content, under its text. */
38
+ contentBox?: Box;
37
39
  caption?: {
38
40
  box: Box;
39
41
  framed: boolean;
@@ -54,15 +56,15 @@ export type GroupLayout = {
54
56
  rect: Box;
55
57
  /** The title band, inside `rect`. */
56
58
  band?: Box;
59
+ /** The line on the band edge that faces the content; present exactly when `band` is. */
60
+ separator?: [Point, Point];
57
61
  label: TextRun[];
58
62
  dividers: [Point, Point][];
59
- ports?: PortLayout[];
63
+ ports: PortLayout[];
60
64
  };
61
65
  export type EdgeLabelLayout = {
62
66
  box: Box;
63
67
  text: TextRun[];
64
- /** The index of the route segment that the label sits beside. */
65
- segment?: number;
66
68
  };
67
69
  export type EdgeLayout = {
68
70
  edge: DiagramEdge;
@@ -1 +1 @@
1
- {"version":3,"file":"graph-layout-result.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/graph-layout-result.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"graph-layout-result.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/graph-layout-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,WAAW,EACX,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,KAAK,EACN,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEpD,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,oBAAoB;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,uCAAuC;IACvC,MAAM,EAAE,GAAG,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,iBAAiB;IACjB,GAAG,EAAE,GAAG,CAAC;IACT,mEAAmE;IACnE,MAAM,EAAE,GAAG,CAAC;IACZ,+BAA+B;IAC/B,MAAM,EAAE,GAAG,EAAE,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,qEAAqE;IACrE,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,GAAG,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IACzD,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE;QACV,IAAI,EAAE,KAAK,EAAE,CAAC;QACd,iDAAiD;QACjD,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACrB,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,EAAE,GAAG,EAAE,CAAC;KACd,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,GAAG,CAAC;IACV,qCAAqC;IACrC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,wFAAwF;IACxF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC3B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;IAC3B,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,EAAE,WAAW,CAAC;IACvB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,GAAG,CAAC;CACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"graph-layout-result.js","names":[],"sources":["../../../../src/lib/layouts/graph/graph-layout-result.ts"],"sourcesContent":["import type {\n Box,\n DiagramEdge,\n DiagramGroup,\n DiagramNode,\n DiagramSide,\n DiagramTextColor,\n Point,\n} from '../../model/diagram-ir.ts';\nimport type { Font } from '../text/font.ts';\n\nexport type TextAnchor = 'start' | 'middle' | 'end';\n\nexport type TextRun = {\n text: string;\n x: number;\n /** The baseline. */\n y: number;\n anchor: TextAnchor;\n font: Font;\n color?: DiagramTextColor;\n /** The ink box the engine measured. */\n bounds: Box;\n};\n\nexport type PortLayout = {\n id: string;\n side: DiagramSide;\n point: Point;\n label?: TextRun;\n};\n\nexport type NodeLayout = {\n node: DiagramNode;\n /** The frame. */\n box: Box;\n /** The frame, the stacked back frames and the caption together. */\n visual: Box;\n /** The stacked back frames. */\n frames: Box[];\n radius: number;\n icon?: { x: number; y: number; size: number };\n text: TextRun[];\n bullets: Point[];\n caption?: { box: Box; framed: boolean; text: TextRun[] };\n ports: PortLayout[];\n appliance?: {\n body: Point[];\n /** The line under the front edge of the body. */\n face: [Point, Point];\n antenna: [Point, Point][];\n jacks: Box[];\n };\n};\n\nexport type GroupLayout = {\n group: DiagramGroup;\n depth: number;\n rect: Box;\n /** The title band, inside `rect`. */\n band?: Box;\n label: TextRun[];\n dividers: [Point, Point][];\n ports?: PortLayout[];\n};\n\nexport type EdgeLabelLayout = {\n box: Box;\n text: TextRun[];\n /** The index of the route segment that the label sits beside. */\n segment?: number;\n};\n\nexport type EdgeLayout = {\n edge: DiagramEdge;\n exitSide: DiagramSide;\n entrySide: DiagramSide;\n points: Point[];\n d: string;\n label?: EdgeLabelLayout;\n badge?: { box: Box; icon?: Box; text: TextRun[] };\n};\n\nexport type LeaderLayout = {\n /** The id of the note node. */\n note: string;\n /** The id of the node or group that the note explains. */\n target: string;\n points: Point[];\n d: string;\n};\n\nexport type GraphLayout = {\n nodes: NodeLayout[];\n groups: GroupLayout[];\n edges: EdgeLayout[];\n leaders: LeaderLayout[];\n viewBox: Box;\n};\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"graph-layout-result.js","names":[],"sources":["../../../../src/lib/layouts/graph/graph-layout-result.ts"],"sourcesContent":["import type {\n Box,\n DiagramEdge,\n DiagramGroup,\n DiagramNode,\n DiagramSide,\n DiagramTextColor,\n Point,\n} from '../../model/diagram-ir.ts';\nimport type { Font } from '../text/font.ts';\n\nexport type TextAnchor = 'start' | 'middle' | 'end';\n\nexport type TextRun = {\n text: string;\n x: number;\n /** The baseline. */\n y: number;\n anchor: TextAnchor;\n font: Font;\n color?: DiagramTextColor;\n /** The ink box the engine measured. */\n bounds: Box;\n};\n\nexport type PortLayout = {\n id: string;\n side: DiagramSide;\n point: Point;\n label?: TextRun;\n};\n\nexport type NodeLayout = {\n node: DiagramNode;\n /** The frame. */\n box: Box;\n /** The frame, the stacked back frames and the caption together. */\n visual: Box;\n /** The stacked back frames. */\n frames: Box[];\n radius: number;\n icon?: { x: number; y: number; size: number };\n text: TextRun[];\n bullets: Point[];\n /** The box reserved for the node's React content, under its text. */\n contentBox?: Box;\n caption?: { box: Box; framed: boolean; text: TextRun[] };\n ports: PortLayout[];\n appliance?: {\n body: Point[];\n /** The line under the front edge of the body. */\n face: [Point, Point];\n antenna: [Point, Point][];\n jacks: Box[];\n };\n};\n\nexport type GroupLayout = {\n group: DiagramGroup;\n depth: number;\n rect: Box;\n /** The title band, inside `rect`. */\n band?: Box;\n /** The line on the band edge that faces the content; present exactly when `band` is. */\n separator?: [Point, Point];\n label: TextRun[];\n dividers: [Point, Point][];\n ports: PortLayout[];\n};\n\nexport type EdgeLabelLayout = {\n box: Box;\n text: TextRun[];\n};\n\nexport type EdgeLayout = {\n edge: DiagramEdge;\n exitSide: DiagramSide;\n entrySide: DiagramSide;\n points: Point[];\n d: string;\n label?: EdgeLabelLayout;\n badge?: { box: Box; icon?: Box; text: TextRun[] };\n};\n\nexport type LeaderLayout = {\n /** The id of the note node. */\n note: string;\n /** The id of the node or group that the note explains. */\n target: string;\n points: Point[];\n d: string;\n};\n\nexport type GraphLayout = {\n nodes: NodeLayout[];\n groups: GroupLayout[];\n edges: EdgeLayout[];\n leaders: LeaderLayout[];\n viewBox: Box;\n};\n"],"mappings":"","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import { parseEndpointRef } from "../../model/endpoint-ref.js";
2
- import { measureEdgeLabel } from "./measure.js";
2
+ import { measureEdgeTags } from "./measure.js";
3
3
  import { LAYOUT_TOKENS } from "./tokens.js";
4
4
 
5
5
  /**
@@ -18,7 +18,7 @@ const STROKE = 1;
18
18
  * boundary, so every group border stays a straight line.
19
19
  */
20
20
  export function gridGutters(areas, titleHeights, edges) {
21
- const minimums = labelMinimums(edges, areas.nodes);
21
+ const minimums = tagMinimums(edges, areas.nodes);
22
22
  const chrome = LAYOUT_TOKENS.GROUP_PAD + STROKE;
23
23
  const spans = axis => areas.groups.map(({
24
24
  group,
@@ -47,9 +47,8 @@ function axisGutters(count, spans, minimums) {
47
47
  }, (_, boundary) => {
48
48
  const closing = slotWidths(spans.filter(span => span.end === boundary), span => span.closingChrome);
49
49
  const opening = slotWidths(spans.filter(span => span.start === boundary), span => span.openingChrome);
50
- const slots = [...closing.values(), ...opening.values()].reduce((total, width) => total + width, 0);
51
50
  const outer = boundary === 0 || boundary === count;
52
- const middle = Math.max(outer ? 0 : LAYOUT_TOKENS.GAP, (minimums.get(boundary) ?? 0) - slots);
51
+ const middle = Math.max(outer ? 0 : LAYOUT_TOKENS.GAP, minimums.get(boundary) ?? 0);
53
52
  const closingEdges = new Map();
54
53
  let offset = 0;
55
54
  for (const [depth, width] of [...closing].sort(([a], [b]) => b - a)) {
@@ -81,37 +80,57 @@ function slotWidths(spans, chromeOf) {
81
80
  }
82
81
 
83
82
  /**
84
- * A labelled edge between two neighbouring cells has only the gutter between
85
- * them for its label, so that gutter must hold the label and a stub on each
86
- * side of it.
83
+ * A tagged edge between two facing cells, with no node between them, runs
84
+ * straight across the gutters between them, and the tracks between them may
85
+ * be empty. Its label and its badge sit in a row along it, LABEL_GAP apart.
86
+ * The tags must not straddle the border of a group that opens or closes
87
+ * there, so the middles of those gutters, outside all their chrome, share
88
+ * the row and a stub on each side of it.
87
89
  */
88
- function labelMinimums(edges, nodes) {
90
+ function tagMinimums(edges, nodes) {
89
91
  const minimums = {
90
92
  row: new Map(),
91
93
  col: new Map()
92
94
  };
93
- const raise = (axis, boundary, need) => minimums[axis].set(boundary, Math.max(minimums[axis].get(boundary) ?? 0, need));
95
+ const raise = (axis, boundaries, need) => {
96
+ for (const boundary of boundaries) {
97
+ minimums[axis].set(boundary, Math.max(minimums[axis].get(boundary) ?? 0, need / boundaries.length));
98
+ }
99
+ };
100
+ const areas = [...nodes.values()];
94
101
  for (const edge of edges) {
95
- if (!edge.label?.length) continue;
102
+ const {
103
+ label,
104
+ badge
105
+ } = measureEdgeTags(edge);
106
+ const tags = [label, badge].filter(tag => tag !== undefined);
107
+ if (tags.length === 0) continue;
96
108
  const from = nodes.get(parseEndpointRef(edge.from).nodeId);
97
109
  const to = nodes.get(parseEndpointRef(edge.to).nodeId);
98
110
  if (!from || !to) continue;
99
- const label = measureEdgeLabel(edge, LAYOUT_TOKENS);
100
- const side = sideBySide(from, to);
101
- if (side) raise('col', side, label.w + 2 * LAYOUT_TOKENS.STUB);
102
- const stacked = sideBySide(transpose(from), transpose(to));
103
- if (stacked) raise('row', stacked, label.h + 2 * LAYOUT_TOKENS.STUB);
111
+ const rowLength = along => tags.reduce((sum, tag) => sum + tag[along], 0) + (tags.length - 1) * LAYOUT_TOKENS.LABEL_GAP + 2 * LAYOUT_TOKENS.STUB;
112
+ raise('col', facingBoundaries(from, to, areas), rowLength('w'));
113
+ raise('row', facingBoundaries(transpose(from), transpose(to), areas.map(transpose)), rowLength('h'));
104
114
  }
105
115
  return minimums;
106
116
  }
107
117
 
108
- /** The column boundary between two areas that share rows and touch across it. */
109
- function sideBySide(a, b) {
110
- const shareRows = Math.max(a.row, b.row) < Math.min(a.row + a.rowSpan, b.row + b.rowSpan);
111
- if (!shareRows) return undefined;
112
- if (a.col + a.colSpan === b.col) return b.col;
113
- if (b.col + b.colSpan === a.col) return a.col;
114
- return undefined;
118
+ /**
119
+ * The column boundaries between two areas that share rows and face each
120
+ * other across them, with no other area between them in the rows they share.
121
+ */
122
+ function facingBoundaries(a, b, areas) {
123
+ const top = Math.max(a.row, b.row);
124
+ const bottom = Math.min(a.row + a.rowSpan, b.row + b.rowSpan);
125
+ const [left, right] = a.col < b.col ? [a, b] : [b, a];
126
+ const start = left.col + left.colSpan;
127
+ const end = right.col;
128
+ if (top >= bottom || start > end) return [];
129
+ const blocked = areas.some(area => area.col < end && area.col + area.colSpan > start && area.row < bottom && area.row + area.rowSpan > top);
130
+ if (blocked) return [];
131
+ return Array.from({
132
+ length: end - start + 1
133
+ }, (_, index) => start + index);
115
134
  }
116
135
  function transpose(area) {
117
136
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"gutters.js","names":["parseEndpointRef","measureEdgeLabel","LAYOUT_TOKENS","STROKE","gridGutters","areas","titleHeights","edges","minimums","labelMinimums","nodes","chrome","GROUP_PAD","spans","axis","groups","map","group","depth","area","start","span","row","rowSpan","col","colSpan","title","get","id","titleAtEnd","labelSide","end","openingChrome","closingChrome","axisGutters","rows","cols","count","Array","from","length","_","boundary","closing","slotWidths","filter","opening","slots","values","reduce","total","width","outer","middle","Math","max","GAP","closingEdges","Map","offset","sort","a","b","set","openingEdges","size","chromeOf","widths","CHANNEL","raise","need","edge","label","nodeId","to","side","sideBySide","w","STUB","stacked","transpose","h","shareRows","min","undefined"],"sources":["../../../../src/lib/layouts/graph/gutters.ts"],"sourcesContent":["import type { DiagramEdge } from '../../model/diagram-ir.ts';\nimport { parseEndpointRef } from '../../model/endpoint-ref.ts';\nimport type { CellArea, GridAreas } from './areas.ts';\nimport { measureEdgeLabel } from './measure.ts';\nimport { LAYOUT_TOKENS } from './tokens.ts';\n\nexport type Axis = 'row' | 'col';\n\n/**\n * The space at one track boundary. Boundary b lies before track b, so\n * boundaries 0 and N are the outer edges of the grid.\n */\nexport type Gutter = {\n size: number;\n /** For each depth, how far from the gutter's start the border of a group closing here lies. */\n closing: Map<number, number>;\n /** For each depth, how far from the gutter's start the border of a group opening here lies. */\n opening: Map<number, number>;\n};\n\nexport type GridGutters = Record<Axis, Gutter[]>;\n\n/** The border stroke a group draws, which its chrome must hold. */\nconst STROKE = 1;\n\ntype GroupSpan = {\n depth: number;\n start: number;\n end: number;\n openingChrome: number;\n closingChrome: number;\n};\n\n/**\n * Sizes every gutter before the tracks, because no gutter depends on a\n * track's size. A gutter holds, in order: the chrome of the groups closing\n * there (deepest first, next to the track before), a middle gap, and the\n * chrome of the groups opening there (deepest last, next to the track after).\n * Each depth's slot is as wide as its widest group anywhere along the\n * boundary, so every group border stays a straight line.\n */\nexport function gridGutters(\n areas: GridAreas,\n titleHeights: Map<string, number>,\n edges: DiagramEdge[],\n): GridGutters {\n const minimums = labelMinimums(edges, areas.nodes);\n const chrome = LAYOUT_TOKENS.GROUP_PAD + STROKE;\n const spans = (axis: Axis): GroupSpan[] =>\n areas.groups.map(({ group, depth, area }) => {\n const [start, span] =\n axis === 'row' ? [area.row, area.rowSpan] : [area.col, area.colSpan];\n const title = axis === 'row' ? titleHeights.get(group.id)! : 0;\n const titleAtEnd = group.labelSide === 'bottom';\n return {\n depth,\n start,\n end: start + span,\n openingChrome: chrome + (titleAtEnd ? 0 : title),\n closingChrome: chrome + (titleAtEnd ? title : 0),\n };\n });\n return {\n row: axisGutters(areas.rows, spans('row'), minimums.row),\n col: axisGutters(areas.cols, spans('col'), minimums.col),\n };\n}\n\nfunction axisGutters(\n count: number,\n spans: GroupSpan[],\n minimums: Map<number, number>,\n): Gutter[] {\n return Array.from({ length: count + 1 }, (_, boundary) => {\n const closing = slotWidths(\n spans.filter((span) => span.end === boundary),\n (span) => span.closingChrome,\n );\n const opening = slotWidths(\n spans.filter((span) => span.start === boundary),\n (span) => span.openingChrome,\n );\n const slots = [...closing.values(), ...opening.values()].reduce(\n (total, width) => total + width,\n 0,\n );\n const outer = boundary === 0 || boundary === count;\n const middle = Math.max(\n outer ? 0 : LAYOUT_TOKENS.GAP,\n (minimums.get(boundary) ?? 0) - slots,\n );\n\n const closingEdges = new Map<number, number>();\n let offset = 0;\n for (const [depth, width] of [...closing].sort(([a], [b]) => b - a)) {\n offset += width;\n closingEdges.set(depth, offset);\n }\n offset += middle;\n const openingEdges = new Map<number, number>();\n for (const [depth, width] of [...opening].sort(([a], [b]) => a - b)) {\n openingEdges.set(depth, offset);\n offset += width;\n }\n return { size: offset, closing: closingEdges, opening: openingEdges };\n });\n}\n\n/** The width of each depth's slot: its widest chrome plus a channel for edges. */\nfunction slotWidths(\n spans: GroupSpan[],\n chromeOf: (span: GroupSpan) => number,\n): Map<number, number> {\n const widths = new Map<number, number>();\n for (const span of spans) {\n const width = chromeOf(span) + LAYOUT_TOKENS.CHANNEL;\n widths.set(span.depth, Math.max(widths.get(span.depth) ?? 0, width));\n }\n return widths;\n}\n\n/**\n * A labelled edge between two neighbouring cells has only the gutter between\n * them for its label, so that gutter must hold the label and a stub on each\n * side of it.\n */\nfunction labelMinimums(\n edges: DiagramEdge[],\n nodes: Map<string, CellArea>,\n): Record<Axis, Map<number, number>> {\n const minimums: Record<Axis, Map<number, number>> = {\n row: new Map(),\n col: new Map(),\n };\n const raise = (axis: Axis, boundary: number, need: number) =>\n minimums[axis].set(\n boundary,\n Math.max(minimums[axis].get(boundary) ?? 0, need),\n );\n for (const edge of edges) {\n if (!edge.label?.length) continue;\n const from = nodes.get(parseEndpointRef(edge.from).nodeId);\n const to = nodes.get(parseEndpointRef(edge.to).nodeId);\n if (!from || !to) continue;\n const label = measureEdgeLabel(edge, LAYOUT_TOKENS);\n const side = sideBySide(from, to);\n if (side) raise('col', side, label.w + 2 * LAYOUT_TOKENS.STUB);\n const stacked = sideBySide(transpose(from), transpose(to));\n if (stacked) raise('row', stacked, label.h + 2 * LAYOUT_TOKENS.STUB);\n }\n return minimums;\n}\n\n/** The column boundary between two areas that share rows and touch across it. */\nfunction sideBySide(a: CellArea, b: CellArea): number | undefined {\n const shareRows =\n Math.max(a.row, b.row) < Math.min(a.row + a.rowSpan, b.row + b.rowSpan);\n if (!shareRows) return undefined;\n if (a.col + a.colSpan === b.col) return b.col;\n if (b.col + b.colSpan === a.col) return a.col;\n return undefined;\n}\n\nfunction transpose(area: CellArea): CellArea {\n return {\n row: area.col,\n col: area.row,\n rowSpan: area.colSpan,\n colSpan: area.rowSpan,\n };\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"gutters.js","names":["parseEndpointRef","measureEdgeTags","LAYOUT_TOKENS","STROKE","gridGutters","areas","titleHeights","edges","minimums","tagMinimums","nodes","chrome","GROUP_PAD","spans","axis","groups","map","group","depth","area","start","span","row","rowSpan","col","colSpan","title","get","id","titleAtEnd","labelSide","end","openingChrome","closingChrome","axisGutters","rows","cols","count","Array","from","length","_","boundary","closing","slotWidths","filter","opening","outer","middle","Math","max","GAP","closingEdges","Map","offset","width","sort","a","b","set","openingEdges","size","chromeOf","widths","CHANNEL","raise","boundaries","need","values","edge","label","badge","tags","tag","undefined","nodeId","to","rowLength","along","reduce","sum","LABEL_GAP","STUB","facingBoundaries","transpose","top","bottom","min","left","right","blocked","some","index"],"sources":["../../../../src/lib/layouts/graph/gutters.ts"],"sourcesContent":["import type { DiagramEdge } from '../../model/diagram-ir.ts';\nimport { parseEndpointRef } from '../../model/endpoint-ref.ts';\nimport type { CellArea, GridAreas } from './areas.ts';\nimport { measureEdgeTags } from './measure.ts';\nimport { LAYOUT_TOKENS } from './tokens.ts';\n\nexport type Axis = 'row' | 'col';\n\n/**\n * The space at one track boundary. Boundary b lies before track b, so\n * boundaries 0 and N are the outer edges of the grid.\n */\nexport type Gutter = {\n size: number;\n /** For each depth, how far from the gutter's start the border of a group closing here lies. */\n closing: Map<number, number>;\n /** For each depth, how far from the gutter's start the border of a group opening here lies. */\n opening: Map<number, number>;\n};\n\nexport type GridGutters = Record<Axis, Gutter[]>;\n\n/** The border stroke a group draws, which its chrome must hold. */\nconst STROKE = 1;\n\ntype GroupSpan = {\n depth: number;\n start: number;\n end: number;\n openingChrome: number;\n closingChrome: number;\n};\n\n/**\n * Sizes every gutter before the tracks, because no gutter depends on a\n * track's size. A gutter holds, in order: the chrome of the groups closing\n * there (deepest first, next to the track before), a middle gap, and the\n * chrome of the groups opening there (deepest last, next to the track after).\n * Each depth's slot is as wide as its widest group anywhere along the\n * boundary, so every group border stays a straight line.\n */\nexport function gridGutters(\n areas: GridAreas,\n titleHeights: Map<string, number>,\n edges: DiagramEdge[],\n): GridGutters {\n const minimums = tagMinimums(edges, areas.nodes);\n const chrome = LAYOUT_TOKENS.GROUP_PAD + STROKE;\n const spans = (axis: Axis): GroupSpan[] =>\n areas.groups.map(({ group, depth, area }) => {\n const [start, span] =\n axis === 'row' ? [area.row, area.rowSpan] : [area.col, area.colSpan];\n const title = axis === 'row' ? titleHeights.get(group.id)! : 0;\n const titleAtEnd = group.labelSide === 'bottom';\n return {\n depth,\n start,\n end: start + span,\n openingChrome: chrome + (titleAtEnd ? 0 : title),\n closingChrome: chrome + (titleAtEnd ? title : 0),\n };\n });\n return {\n row: axisGutters(areas.rows, spans('row'), minimums.row),\n col: axisGutters(areas.cols, spans('col'), minimums.col),\n };\n}\n\nfunction axisGutters(\n count: number,\n spans: GroupSpan[],\n minimums: Map<number, number>,\n): Gutter[] {\n return Array.from({ length: count + 1 }, (_, boundary) => {\n const closing = slotWidths(\n spans.filter((span) => span.end === boundary),\n (span) => span.closingChrome,\n );\n const opening = slotWidths(\n spans.filter((span) => span.start === boundary),\n (span) => span.openingChrome,\n );\n const outer = boundary === 0 || boundary === count;\n const middle = Math.max(\n outer ? 0 : LAYOUT_TOKENS.GAP,\n minimums.get(boundary) ?? 0,\n );\n\n const closingEdges = new Map<number, number>();\n let offset = 0;\n for (const [depth, width] of [...closing].sort(([a], [b]) => b - a)) {\n offset += width;\n closingEdges.set(depth, offset);\n }\n offset += middle;\n const openingEdges = new Map<number, number>();\n for (const [depth, width] of [...opening].sort(([a], [b]) => a - b)) {\n openingEdges.set(depth, offset);\n offset += width;\n }\n return { size: offset, closing: closingEdges, opening: openingEdges };\n });\n}\n\n/** The width of each depth's slot: its widest chrome plus a channel for edges. */\nfunction slotWidths(\n spans: GroupSpan[],\n chromeOf: (span: GroupSpan) => number,\n): Map<number, number> {\n const widths = new Map<number, number>();\n for (const span of spans) {\n const width = chromeOf(span) + LAYOUT_TOKENS.CHANNEL;\n widths.set(span.depth, Math.max(widths.get(span.depth) ?? 0, width));\n }\n return widths;\n}\n\n/**\n * A tagged edge between two facing cells, with no node between them, runs\n * straight across the gutters between them, and the tracks between them may\n * be empty. Its label and its badge sit in a row along it, LABEL_GAP apart.\n * The tags must not straddle the border of a group that opens or closes\n * there, so the middles of those gutters, outside all their chrome, share\n * the row and a stub on each side of it.\n */\nfunction tagMinimums(\n edges: DiagramEdge[],\n nodes: Map<string, CellArea>,\n): Record<Axis, Map<number, number>> {\n const minimums: Record<Axis, Map<number, number>> = {\n row: new Map(),\n col: new Map(),\n };\n const raise = (axis: Axis, boundaries: number[], need: number) => {\n for (const boundary of boundaries) {\n minimums[axis].set(\n boundary,\n Math.max(minimums[axis].get(boundary) ?? 0, need / boundaries.length),\n );\n }\n };\n const areas = [...nodes.values()];\n for (const edge of edges) {\n const { label, badge } = measureEdgeTags(edge);\n const tags = [label, badge].filter((tag) => tag !== undefined);\n if (tags.length === 0) continue;\n const from = nodes.get(parseEndpointRef(edge.from).nodeId);\n const to = nodes.get(parseEndpointRef(edge.to).nodeId);\n if (!from || !to) continue;\n const rowLength = (along: 'w' | 'h') =>\n tags.reduce((sum, tag) => sum + tag[along], 0) +\n (tags.length - 1) * LAYOUT_TOKENS.LABEL_GAP +\n 2 * LAYOUT_TOKENS.STUB;\n raise('col', facingBoundaries(from, to, areas), rowLength('w'));\n raise(\n 'row',\n facingBoundaries(transpose(from), transpose(to), areas.map(transpose)),\n rowLength('h'),\n );\n }\n return minimums;\n}\n\n/**\n * The column boundaries between two areas that share rows and face each\n * other across them, with no other area between them in the rows they share.\n */\nfunction facingBoundaries(\n a: CellArea,\n b: CellArea,\n areas: CellArea[],\n): number[] {\n const top = Math.max(a.row, b.row);\n const bottom = Math.min(a.row + a.rowSpan, b.row + b.rowSpan);\n const [left, right] = a.col < b.col ? [a, b] : [b, a];\n const start = left.col + left.colSpan;\n const end = right.col;\n if (top >= bottom || start > end) return [];\n const blocked = areas.some(\n (area) =>\n area.col < end &&\n area.col + area.colSpan > start &&\n area.row < bottom &&\n area.row + area.rowSpan > top,\n );\n if (blocked) return [];\n return Array.from({ length: end - start + 1 }, (_, index) => start + index);\n}\n\nfunction transpose(area: CellArea): CellArea {\n return {\n row: area.col,\n col: area.row,\n rowSpan: area.colSpan,\n colSpan: area.rowSpan,\n };\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,23 @@
1
+ import type { Box, Point } from '../../model/diagram-ir.ts';
2
+ type Size = {
3
+ w: number;
4
+ h: number;
5
+ };
6
+ export type TagKind = 'label' | 'badge';
7
+ /**
8
+ * The first free position for an edge's tags on its route: the longest
9
+ * segment first, and on each segment the fractions in FRACTIONS order. A
10
+ * label sits LABEL_GAP beside the segment (above, then below a horizontal
11
+ * one; right, then left of a vertical one). A badge sits centred on it. An
12
+ * edge with both puts them in a row along the segment, LABEL_GAP apart and
13
+ * centred on the fraction: the label first, then the badge first.
14
+ */
15
+ export declare function tagPosition(tags: {
16
+ label?: Size;
17
+ badge?: Size;
18
+ }, points: Point[], isFree: (box: Box, kind: TagKind) => boolean): {
19
+ label?: Box;
20
+ badge?: Box;
21
+ } | undefined;
22
+ export {};
23
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAM5D,KAAK,IAAI,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAExC;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,IAAI,CAAA;CAAE,EACpC,MAAM,EAAE,KAAK,EAAE,EACf,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,GAC3C;IAAE,KAAK,CAAC,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAA;CAAE,GAAG,SAAS,CAgD1C"}
@@ -0,0 +1,120 @@
1
+ import { LAYOUT_TOKENS } from "./tokens.js";
2
+
3
+ /** Where along a segment the tags are tried, in order. */
4
+ const FRACTIONS = [0.5, 0.3, 0.7, 0.15, 0.85];
5
+ /**
6
+ * The first free position for an edge's tags on its route: the longest
7
+ * segment first, and on each segment the fractions in FRACTIONS order. A
8
+ * label sits LABEL_GAP beside the segment (above, then below a horizontal
9
+ * one; right, then left of a vertical one). A badge sits centred on it. An
10
+ * edge with both puts them in a row along the segment, LABEL_GAP apart and
11
+ * centred on the fraction: the label first, then the badge first.
12
+ */
13
+ export function tagPosition(tags, points, isFree) {
14
+ const {
15
+ label,
16
+ badge
17
+ } = tags;
18
+ const freeLabel = (size, at, horizontal) => beside(size, at, horizontal).find(box => isFree(box, 'label'));
19
+ const freeBadge = (size, at) => {
20
+ const box = on(size, at);
21
+ return isFree(box, 'badge') ? box : undefined;
22
+ };
23
+ const segments = points.slice(1).map((to, segment) => ({
24
+ from: points[segment],
25
+ to,
26
+ segment
27
+ })).sort((a, b) => length(b.from, b.to) - length(a.from, a.to) || a.segment - b.segment);
28
+ for (const {
29
+ from,
30
+ to
31
+ } of segments) {
32
+ const horizontal = from.y === to.y;
33
+ for (const t of FRACTIONS) {
34
+ const at = {
35
+ x: from.x + (to.x - from.x) * t,
36
+ y: from.y + (to.y - from.y) * t
37
+ };
38
+ if (label && badge) {
39
+ const along = ({
40
+ w,
41
+ h
42
+ }) => horizontal ? w : h;
43
+ const half = (along(label) + LAYOUT_TOKENS.LABEL_GAP + along(badge)) / 2;
44
+ for (const first of [-1, 1]) {
45
+ const labelBox = freeLabel(label, shift(at, horizontal, first * (half - along(label) / 2)), horizontal);
46
+ const badgeBox = freeBadge(badge, shift(at, horizontal, -first * (half - along(badge) / 2)));
47
+ if (labelBox && badgeBox) return {
48
+ label: labelBox,
49
+ badge: badgeBox
50
+ };
51
+ }
52
+ } else if (label) {
53
+ const labelBox = freeLabel(label, at, horizontal);
54
+ if (labelBox) return {
55
+ label: labelBox
56
+ };
57
+ } else if (badge) {
58
+ const badgeBox = freeBadge(badge, at);
59
+ if (badgeBox) return {
60
+ badge: badgeBox
61
+ };
62
+ }
63
+ }
64
+ }
65
+ return undefined;
66
+ }
67
+ function beside({
68
+ w,
69
+ h
70
+ }, at, horizontal) {
71
+ const gap = LAYOUT_TOKENS.LABEL_GAP;
72
+ if (horizontal) {
73
+ return [{
74
+ x: at.x - w / 2,
75
+ y: at.y - gap - h,
76
+ w,
77
+ h
78
+ }, {
79
+ x: at.x - w / 2,
80
+ y: at.y + gap,
81
+ w,
82
+ h
83
+ }];
84
+ }
85
+ return [{
86
+ x: at.x + gap,
87
+ y: at.y - h / 2,
88
+ w,
89
+ h
90
+ }, {
91
+ x: at.x - gap - w,
92
+ y: at.y - h / 2,
93
+ w,
94
+ h
95
+ }];
96
+ }
97
+ function on({
98
+ w,
99
+ h
100
+ }, at) {
101
+ return {
102
+ x: at.x - w / 2,
103
+ y: at.y - h / 2,
104
+ w,
105
+ h
106
+ };
107
+ }
108
+ function shift(at, horizontal, by) {
109
+ return horizontal ? {
110
+ x: at.x + by,
111
+ y: at.y
112
+ } : {
113
+ x: at.x,
114
+ y: at.y + by
115
+ };
116
+ }
117
+ function length(a, b) {
118
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
119
+ }
120
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","names":["LAYOUT_TOKENS","FRACTIONS","tagPosition","tags","points","isFree","label","badge","freeLabel","size","at","horizontal","beside","find","box","freeBadge","on","undefined","segments","slice","map","to","segment","from","sort","a","b","length","y","t","x","along","w","h","half","LABEL_GAP","first","labelBox","shift","badgeBox","gap","by","Math","abs"],"sources":["../../../../src/lib/layouts/graph/labels.ts"],"sourcesContent":["import type { Box, Point } from '../../model/diagram-ir.ts';\nimport { LAYOUT_TOKENS } from './tokens.ts';\n\n/** Where along a segment the tags are tried, in order. */\nconst FRACTIONS = [0.5, 0.3, 0.7, 0.15, 0.85];\n\ntype Size = { w: number; h: number };\n\nexport type TagKind = 'label' | 'badge';\n\n/**\n * The first free position for an edge's tags on its route: the longest\n * segment first, and on each segment the fractions in FRACTIONS order. A\n * label sits LABEL_GAP beside the segment (above, then below a horizontal\n * one; right, then left of a vertical one). A badge sits centred on it. An\n * edge with both puts them in a row along the segment, LABEL_GAP apart and\n * centred on the fraction: the label first, then the badge first.\n */\nexport function tagPosition(\n tags: { label?: Size; badge?: Size },\n points: Point[],\n isFree: (box: Box, kind: TagKind) => boolean,\n): { label?: Box; badge?: Box } | undefined {\n const { label, badge } = tags;\n const freeLabel = (size: Size, at: Point, horizontal: boolean) =>\n beside(size, at, horizontal).find((box) => isFree(box, 'label'));\n const freeBadge = (size: Size, at: Point) => {\n const box = on(size, at);\n return isFree(box, 'badge') ? box : undefined;\n };\n const segments = points\n .slice(1)\n .map((to, segment) => ({ from: points[segment]!, to, segment }))\n .sort(\n (a, b) =>\n length(b.from, b.to) - length(a.from, a.to) || a.segment - b.segment,\n );\n for (const { from, to } of segments) {\n const horizontal = from.y === to.y;\n for (const t of FRACTIONS) {\n const at = {\n x: from.x + (to.x - from.x) * t,\n y: from.y + (to.y - from.y) * t,\n };\n if (label && badge) {\n const along = ({ w, h }: Size) => (horizontal ? w : h);\n const half =\n (along(label) + LAYOUT_TOKENS.LABEL_GAP + along(badge)) / 2;\n for (const first of [-1, 1]) {\n const labelBox = freeLabel(\n label,\n shift(at, horizontal, first * (half - along(label) / 2)),\n horizontal,\n );\n const badgeBox = freeBadge(\n badge,\n shift(at, horizontal, -first * (half - along(badge) / 2)),\n );\n if (labelBox && badgeBox) return { label: labelBox, badge: badgeBox };\n }\n } else if (label) {\n const labelBox = freeLabel(label, at, horizontal);\n if (labelBox) return { label: labelBox };\n } else if (badge) {\n const badgeBox = freeBadge(badge, at);\n if (badgeBox) return { badge: badgeBox };\n }\n }\n }\n return undefined;\n}\n\nfunction beside({ w, h }: Size, at: Point, horizontal: boolean): Box[] {\n const gap = LAYOUT_TOKENS.LABEL_GAP;\n if (horizontal) {\n return [\n { x: at.x - w / 2, y: at.y - gap - h, w, h },\n { x: at.x - w / 2, y: at.y + gap, w, h },\n ];\n }\n return [\n { x: at.x + gap, y: at.y - h / 2, w, h },\n { x: at.x - gap - w, y: at.y - h / 2, w, h },\n ];\n}\n\nfunction on({ w, h }: Size, at: Point): Box {\n return { x: at.x - w / 2, y: at.y - h / 2, w, h };\n}\n\nfunction shift(at: Point, horizontal: boolean, by: number): Point {\n return horizontal ? { x: at.x + by, y: at.y } : { x: at.x, y: at.y + by };\n}\n\nfunction length(a: Point, b: Point): number {\n return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);\n}\n"],"mappings":"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","ignoreList":[]}
@@ -4,7 +4,7 @@ import type { GraphLayout } from './graph-layout-result.ts';
4
4
  * Lays out a graph on a content-sized grid: it measures every node, sizes
5
5
  * each row and column to the content in it, and reserves the chrome of the
6
6
  * groups in the gutters between them. It throws when the grid cannot be
7
- * drawn without overlap.
7
+ * drawn without overlap, or when an id or an edge end is wrong.
8
8
  */
9
9
  export declare function layoutGraph(ir: DiagramGraphIR): GraphLayout;
10
10
  //# sourceMappingURL=layout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,cAAc,EAAS,MAAM,2BAA2B,CAAC;AAE5E,OAAO,KAAK,EACV,WAAW,EAGZ,MAAM,0BAA0B,CAAC;AAWlC;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,cAAc,GAAG,WAAW,CA6D3D"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,cAAc,EAAS,MAAM,2BAA2B,CAAC;AAG5E,OAAO,KAAK,EACV,WAAW,EAGZ,MAAM,0BAA0B,CAAC;AAclC;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,cAAc,GAAG,WAAW,CAqE3D"}