@zukhruf/diagram 0.3.32 → 0.3.33

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 (49) hide show
  1. package/dist/lib/layouts/graph/attach.d.ts +25 -0
  2. package/dist/lib/layouts/graph/attach.d.ts.map +1 -0
  3. package/dist/lib/layouts/graph/attach.js +216 -0
  4. package/dist/lib/layouts/graph/attach.js.map +1 -0
  5. package/dist/lib/layouts/graph/connections.d.ts +38 -0
  6. package/dist/lib/layouts/graph/connections.d.ts.map +1 -0
  7. package/dist/lib/layouts/graph/connections.js +112 -0
  8. package/dist/lib/layouts/graph/connections.js.map +1 -0
  9. package/dist/lib/layouts/graph/graph-layout-result.d.ts +1 -1
  10. package/dist/lib/layouts/graph/graph-layout-result.d.ts.map +1 -1
  11. package/dist/lib/layouts/graph/graph-layout-result.js.map +1 -1
  12. package/dist/lib/layouts/graph/gutters.js +5 -5
  13. package/dist/lib/layouts/graph/gutters.js.map +1 -1
  14. package/dist/lib/layouts/graph/labels.d.ts +16 -0
  15. package/dist/lib/layouts/graph/labels.d.ts.map +1 -0
  16. package/dist/lib/layouts/graph/labels.js +76 -0
  17. package/dist/lib/layouts/graph/labels.js.map +1 -0
  18. package/dist/lib/layouts/graph/layout.d.ts.map +1 -1
  19. package/dist/lib/layouts/graph/layout.js +31 -9
  20. package/dist/lib/layouts/graph/layout.js.map +1 -1
  21. package/dist/lib/layouts/graph/measure.d.ts +7 -6
  22. package/dist/lib/layouts/graph/measure.d.ts.map +1 -1
  23. package/dist/lib/layouts/graph/measure.js +35 -29
  24. package/dist/lib/layouts/graph/measure.js.map +1 -1
  25. package/dist/lib/layouts/graph/min-heap.d.ts +12 -0
  26. package/dist/lib/layouts/graph/min-heap.d.ts.map +1 -0
  27. package/dist/lib/layouts/graph/min-heap.js +60 -0
  28. package/dist/lib/layouts/graph/min-heap.js.map +1 -0
  29. package/dist/lib/layouts/graph/node-content.d.ts +1 -2
  30. package/dist/lib/layouts/graph/node-content.d.ts.map +1 -1
  31. package/dist/lib/layouts/graph/node-content.js +43 -42
  32. package/dist/lib/layouts/graph/node-content.js.map +1 -1
  33. package/dist/lib/layouts/graph/path.d.ts +14 -0
  34. package/dist/lib/layouts/graph/path.d.ts.map +1 -0
  35. package/dist/lib/layouts/graph/path.js +55 -0
  36. package/dist/lib/layouts/graph/path.js.map +1 -0
  37. package/dist/lib/layouts/graph/route-search.d.ts +34 -0
  38. package/dist/lib/layouts/graph/route-search.d.ts.map +1 -0
  39. package/dist/lib/layouts/graph/route-search.js +208 -0
  40. package/dist/lib/layouts/graph/route-search.js.map +1 -0
  41. package/dist/lib/layouts/graph/route.d.ts +16 -0
  42. package/dist/lib/layouts/graph/route.d.ts.map +1 -0
  43. package/dist/lib/layouts/graph/route.js +241 -0
  44. package/dist/lib/layouts/graph/route.js.map +1 -0
  45. package/dist/lib/layouts/graph/sides.d.ts +23 -0
  46. package/dist/lib/layouts/graph/sides.d.ts.map +1 -0
  47. package/dist/lib/layouts/graph/sides.js +91 -0
  48. package/dist/lib/layouts/graph/sides.js.map +1 -0
  49. package/package.json +1 -1
@@ -0,0 +1,208 @@
1
+ import { MinHeap } from "./min-heap.js";
2
+
3
+ /** A direction of travel: right, left, down, up. */
4
+
5
+ /** The routing grid's pitch. */
6
+ export const GRID = 4;
7
+ const TURN_COST = 14;
8
+ const EDGE_CROSSING_COST = 20;
9
+ const BORDER_CROSSING_COST = 6;
10
+ /** Charged for every grid step that runs parallel to a group border within HUG_DISTANCE of it. */
11
+ const BORDER_HUG_COST = 10;
12
+ const HUG_DISTANCE = 4;
13
+ /** A run of another edge closer than this to a grid line lies on that line. */
14
+ const RUN_TOLERANCE = GRID / 2;
15
+ const STEPS = {
16
+ 0: [1, 0],
17
+ 1: [-1, 0],
18
+ 2: [0, 1],
19
+ 3: [0, -1]
20
+ };
21
+ const HEADINGS = [0, 1, 2, 3];
22
+ const REVERSE = {
23
+ 0: 1,
24
+ 1: 0,
25
+ 2: 3,
26
+ 3: 2
27
+ };
28
+ function reverse(heading) {
29
+ return REVERSE[heading];
30
+ }
31
+ function isHorizontal(heading) {
32
+ return heading < 2;
33
+ }
34
+ /**
35
+ * The cheapest orthogonal path from `start` to `goal` on a grid of GRID
36
+ * pitch, or undefined when the walls leave none. The search state is a grid
37
+ * point together with the heading that reached it, so a turn costs the same
38
+ * whichever way a point was reached.
39
+ */
40
+ export function searchRoute(search) {
41
+ const grid = new SearchGrid(search);
42
+ const start = grid.indexOf(search.start);
43
+ const goal = grid.indexOf(search.goal);
44
+ if (start === undefined || goal === undefined) return undefined;
45
+ if (grid.blocked[start] || grid.blocked[goal]) return undefined;
46
+ const states = grid.cells * 4;
47
+ const virtualGoal = states;
48
+ const cost = new Float64Array(states + 1).fill(Infinity);
49
+ const cameFrom = new Int32Array(states + 1).fill(-1);
50
+ const open = new MinHeap();
51
+ const startState = start * 4 + search.startHeading;
52
+ cost[startState] = 0;
53
+ open.push(startState, grid.estimate(start, goal));
54
+ while (open.size > 0) {
55
+ const state = open.pop();
56
+ if (state === virtualGoal) return grid.trace(cameFrom, cameFrom[state]);
57
+ const cell = state >> 2;
58
+ const heading = HEADINGS[state & 3];
59
+ const reached = cost[state];
60
+ if (cell === goal) {
61
+ if (heading === reverse(search.goalHeading)) continue;
62
+ const total = reached + (heading === search.goalHeading ? 0 : TURN_COST);
63
+ if (total < cost[virtualGoal]) {
64
+ cost[virtualGoal] = total;
65
+ cameFrom[virtualGoal] = state;
66
+ open.push(virtualGoal, total);
67
+ }
68
+ continue;
69
+ }
70
+ for (const next of HEADINGS) {
71
+ if (next === reverse(heading)) continue;
72
+ const step = grid.step(cell, next);
73
+ if (step === undefined) continue;
74
+ const nextState = step.cell * 4 + next;
75
+ const total = reached + step.cost + (next === heading ? 0 : TURN_COST);
76
+ if (total >= cost[nextState]) continue;
77
+ cost[nextState] = total;
78
+ cameFrom[nextState] = state;
79
+ open.push(nextState, total + grid.estimate(step.cell, goal));
80
+ }
81
+ }
82
+ return undefined;
83
+ }
84
+
85
+ /** The grid points of one search, with what each one costs to enter. */
86
+ class SearchGrid {
87
+ #alongRunH;
88
+ #alongRunV;
89
+ /** Group borders between grid column i − 1 and column i, at each row. */
90
+ #bordersBeforeX;
91
+ #bordersBeforeY;
92
+ #hugH;
93
+ #hugV;
94
+ constructor(search) {
95
+ const {
96
+ window
97
+ } = search;
98
+ this.xs = axis(window.x, window.x + window.w, search.lines.x);
99
+ this.ys = axis(window.y, window.y + window.h, search.lines.y);
100
+ this.cells = this.xs.length * this.ys.length;
101
+ this.blocked = new Uint8Array(this.cells);
102
+ this.#alongRunH = new Uint8Array(this.cells);
103
+ this.#alongRunV = new Uint8Array(this.cells);
104
+ this.#bordersBeforeX = new Uint8Array(this.cells);
105
+ this.#bordersBeforeY = new Uint8Array(this.cells);
106
+ this.#hugH = new Uint8Array(this.cells);
107
+ this.#hugV = new Uint8Array(this.cells);
108
+ for (const wall of search.walls) {
109
+ this.#fill(this.blocked, strictlyInside(this.xs, wall.x, wall.x + wall.w), strictlyInside(this.ys, wall.y, wall.y + wall.h));
110
+ }
111
+ for (const [a, b] of search.runs) {
112
+ if (a.y === b.y) {
113
+ this.#fill(this.#alongRunH, within(this.xs, Math.min(a.x, b.x), Math.max(a.x, b.x)), near(this.ys, a.y, RUN_TOLERANCE));
114
+ } else {
115
+ this.#fill(this.#alongRunV, near(this.xs, a.x, RUN_TOLERANCE), within(this.ys, Math.min(a.y, b.y), Math.max(a.y, b.y)));
116
+ }
117
+ }
118
+ for (const rect of search.borders) {
119
+ const rows = within(this.ys, rect.y, rect.y + rect.h);
120
+ const cols = within(this.xs, rect.x, rect.x + rect.w);
121
+ for (const x of [rect.x, rect.x + rect.w]) {
122
+ this.#fill(this.#bordersBeforeX, crossedBefore(this.xs, x), rows);
123
+ this.#fill(this.#hugV, near(this.xs, x, HUG_DISTANCE + 1e-6), rows);
124
+ }
125
+ for (const y of [rect.y, rect.y + rect.h]) {
126
+ this.#fill(this.#bordersBeforeY, cols, crossedBefore(this.ys, y));
127
+ this.#fill(this.#hugH, cols, near(this.ys, y, HUG_DISTANCE + 1e-6));
128
+ }
129
+ }
130
+ }
131
+ indexOf(point) {
132
+ const i = this.xs.indexOf(point.x);
133
+ const j = this.ys.indexOf(point.y);
134
+ return i === -1 || j === -1 ? undefined : j * this.xs.length + i;
135
+ }
136
+
137
+ /** A lower bound on the cost from `cell` to `goal`: one unit per GRID of distance. */
138
+ estimate(cell, goal) {
139
+ const width = this.xs.length;
140
+ return (Math.abs(this.xs[cell % width] - this.xs[goal % width]) + Math.abs(this.ys[Math.floor(cell / width)] - this.ys[Math.floor(goal / width)])) / GRID;
141
+ }
142
+ step(cell, heading) {
143
+ const width = this.xs.length;
144
+ const i = cell % width;
145
+ const j = Math.floor(cell / width);
146
+ const [di, dj] = STEPS[heading];
147
+ const ni = i + di;
148
+ const nj = j + dj;
149
+ if (ni < 0 || nj < 0 || ni >= width || nj >= this.ys.length) return undefined;
150
+ const next = nj * width + ni;
151
+ if (this.blocked[next]) return undefined;
152
+ const horizontal = isHorizontal(heading);
153
+ if ((horizontal ? this.#alongRunH : this.#alongRunV)[next]) return undefined;
154
+ const length = horizontal ? Math.abs(this.xs[ni] - this.xs[i]) : Math.abs(this.ys[nj] - this.ys[j]);
155
+ const crossed = horizontal ? this.#bordersBeforeX[j * width + Math.max(i, ni)] : this.#bordersBeforeY[Math.max(j, nj) * width + i];
156
+ const hugs = (horizontal ? this.#hugH : this.#hugV)[next] > 0;
157
+ const crossesRun = (horizontal ? this.#alongRunV : this.#alongRunH)[next] > 0;
158
+ return {
159
+ cell: next,
160
+ cost: length / GRID + crossed * BORDER_CROSSING_COST + (hugs ? BORDER_HUG_COST * length / GRID : 0) + (crossesRun ? EDGE_CROSSING_COST : 0)
161
+ };
162
+ }
163
+ trace(cameFrom, last) {
164
+ const width = this.xs.length;
165
+ const points = [];
166
+ for (let state = last; state !== -1; state = cameFrom[state]) {
167
+ const cell = state >> 2;
168
+ points.push({
169
+ x: this.xs[cell % width],
170
+ y: this.ys[Math.floor(cell / width)]
171
+ });
172
+ }
173
+ return points.reverse();
174
+ }
175
+ #fill(target, cols, rows) {
176
+ const width = this.xs.length;
177
+ for (const j of rows) for (const i of cols) target[j * width + i] += 1;
178
+ }
179
+ }
180
+
181
+ /** Every multiple of GRID in [from, to], with the extra lines inside it, sorted and distinct. */
182
+ function axis(from, to, extra) {
183
+ const values = new Set();
184
+ for (let v = Math.ceil(from / GRID) * GRID; v <= to; v += GRID) values.add(v);
185
+ for (const v of extra) if (v >= from && v <= to) values.add(v);
186
+ return [...values].sort((a, b) => a - b);
187
+ }
188
+ function indicesWhere(values, keep) {
189
+ const indices = [];
190
+ for (const [index, value] of values.entries()) if (keep(value)) indices.push(index);
191
+ return indices;
192
+ }
193
+ function strictlyInside(values, from, to) {
194
+ return indicesWhere(values, v => v > from && v < to);
195
+ }
196
+ function within(values, from, to) {
197
+ return indicesWhere(values, v => v >= from && v <= to);
198
+ }
199
+ function near(values, at, distance) {
200
+ return indicesWhere(values, v => Math.abs(v - at) < distance);
201
+ }
202
+
203
+ /** The index i whose step from i − 1 crosses `at`. */
204
+ function crossedBefore(values, at) {
205
+ const i = values.findIndex(v => v >= at);
206
+ return i > 0 && values[i - 1] < at ? [i] : [];
207
+ }
208
+ //# sourceMappingURL=route-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-search.js","names":["MinHeap","GRID","TURN_COST","EDGE_CROSSING_COST","BORDER_CROSSING_COST","BORDER_HUG_COST","HUG_DISTANCE","RUN_TOLERANCE","STEPS","HEADINGS","REVERSE","reverse","heading","isHorizontal","searchRoute","search","grid","SearchGrid","start","indexOf","goal","undefined","blocked","states","cells","virtualGoal","cost","Float64Array","fill","Infinity","cameFrom","Int32Array","open","startState","startHeading","push","estimate","size","state","pop","trace","cell","reached","goalHeading","total","next","step","nextState","alongRunH","alongRunV","bordersBeforeX","bordersBeforeY","hugH","hugV","constructor","window","xs","axis","x","w","lines","ys","y","h","length","Uint8Array","wall","walls","strictlyInside","a","b","runs","within","Math","min","max","near","rect","borders","rows","cols","crossedBefore","point","i","j","width","abs","floor","di","dj","ni","nj","horizontal","crossed","hugs","crossesRun","last","points","#fill","target","from","to","extra","values","Set","v","ceil","add","sort","indicesWhere","keep","indices","index","value","entries","at","distance","findIndex"],"sources":["../../../../src/lib/layouts/graph/route-search.ts"],"sourcesContent":["import type { Box, Point } from '../../model/diagram-ir.ts';\nimport { MinHeap } from './min-heap.ts';\n\n/** A direction of travel: right, left, down, up. */\nexport type Heading = 0 | 1 | 2 | 3;\n\n/** The routing grid's pitch. */\nexport const GRID = 4;\nconst TURN_COST = 14;\nconst EDGE_CROSSING_COST = 20;\nconst BORDER_CROSSING_COST = 6;\n/** Charged for every grid step that runs parallel to a group border within HUG_DISTANCE of it. */\nconst BORDER_HUG_COST = 10;\nconst HUG_DISTANCE = 4;\n/** A run of another edge closer than this to a grid line lies on that line. */\nconst RUN_TOLERANCE = GRID / 2;\n\nconst STEPS: Record<Heading, [number, number]> = {\n 0: [1, 0],\n 1: [-1, 0],\n 2: [0, 1],\n 3: [0, -1],\n};\nconst HEADINGS: Heading[] = [0, 1, 2, 3];\n\nconst REVERSE: Record<Heading, Heading> = { 0: 1, 1: 0, 2: 3, 3: 2 };\n\nfunction reverse(heading: Heading): Heading {\n return REVERSE[heading];\n}\n\nfunction isHorizontal(heading: Heading): boolean {\n return heading < 2;\n}\n\nexport type RouteSearch = {\n /** Only grid points inside this box are searched. */\n window: Box;\n /** Coordinates the grid holds besides every multiple of GRID: the route's own ends, slots and stubs. */\n lines: { x: number[]; y: number[] };\n /** No route passes a grid point strictly inside one of these. */\n walls: Box[];\n /** Segments of routes already drawn: a route may cross them at a cost, never run along them. */\n runs: [Point, Point][];\n /** Group rects and title bands: crossing or hugging their borders costs extra. */\n borders: Box[];\n start: Point;\n /** The heading of the stub that reaches `start`. */\n startHeading: Heading;\n goal: Point;\n /** The heading of the stub that leaves `goal` for the entry side. */\n goalHeading: Heading;\n};\n\n/**\n * The cheapest orthogonal path from `start` to `goal` on a grid of GRID\n * pitch, or undefined when the walls leave none. The search state is a grid\n * point together with the heading that reached it, so a turn costs the same\n * whichever way a point was reached.\n */\nexport function searchRoute(search: RouteSearch): Point[] | undefined {\n const grid = new SearchGrid(search);\n const start = grid.indexOf(search.start);\n const goal = grid.indexOf(search.goal);\n if (start === undefined || goal === undefined) return undefined;\n if (grid.blocked[start] || grid.blocked[goal]) return undefined;\n\n const states = grid.cells * 4;\n const virtualGoal = states;\n const cost = new Float64Array(states + 1).fill(Infinity);\n const cameFrom = new Int32Array(states + 1).fill(-1);\n const open = new MinHeap<number>();\n const startState = start * 4 + search.startHeading;\n cost[startState] = 0;\n open.push(startState, grid.estimate(start, goal));\n\n while (open.size > 0) {\n const state = open.pop()!;\n if (state === virtualGoal) return grid.trace(cameFrom, cameFrom[state]!);\n const cell = state >> 2;\n const heading = HEADINGS[state & 3]!;\n const reached = cost[state]!;\n if (cell === goal) {\n if (heading === reverse(search.goalHeading)) continue;\n const total = reached + (heading === search.goalHeading ? 0 : TURN_COST);\n if (total < cost[virtualGoal]!) {\n cost[virtualGoal] = total;\n cameFrom[virtualGoal] = state;\n open.push(virtualGoal, total);\n }\n continue;\n }\n for (const next of HEADINGS) {\n if (next === reverse(heading)) continue;\n const step = grid.step(cell, next);\n if (step === undefined) continue;\n const nextState = step.cell * 4 + next;\n const total = reached + step.cost + (next === heading ? 0 : TURN_COST);\n if (total >= cost[nextState]!) continue;\n cost[nextState] = total;\n cameFrom[nextState] = state;\n open.push(nextState, total + grid.estimate(step.cell, goal));\n }\n }\n return undefined;\n}\n\n/** The grid points of one search, with what each one costs to enter. */\nclass SearchGrid {\n readonly xs: number[];\n readonly ys: number[];\n readonly cells: number;\n readonly blocked: Uint8Array;\n readonly #alongRunH: Uint8Array;\n readonly #alongRunV: Uint8Array;\n /** Group borders between grid column i − 1 and column i, at each row. */\n readonly #bordersBeforeX: Uint8Array;\n readonly #bordersBeforeY: Uint8Array;\n readonly #hugH: Uint8Array;\n readonly #hugV: Uint8Array;\n\n constructor(search: RouteSearch) {\n const { window } = search;\n this.xs = axis(window.x, window.x + window.w, search.lines.x);\n this.ys = axis(window.y, window.y + window.h, search.lines.y);\n this.cells = this.xs.length * this.ys.length;\n this.blocked = new Uint8Array(this.cells);\n this.#alongRunH = new Uint8Array(this.cells);\n this.#alongRunV = new Uint8Array(this.cells);\n this.#bordersBeforeX = new Uint8Array(this.cells);\n this.#bordersBeforeY = new Uint8Array(this.cells);\n this.#hugH = new Uint8Array(this.cells);\n this.#hugV = new Uint8Array(this.cells);\n\n for (const wall of search.walls) {\n this.#fill(\n this.blocked,\n strictlyInside(this.xs, wall.x, wall.x + wall.w),\n strictlyInside(this.ys, wall.y, wall.y + wall.h),\n );\n }\n for (const [a, b] of search.runs) {\n if (a.y === b.y) {\n this.#fill(\n this.#alongRunH,\n within(this.xs, Math.min(a.x, b.x), Math.max(a.x, b.x)),\n near(this.ys, a.y, RUN_TOLERANCE),\n );\n } else {\n this.#fill(\n this.#alongRunV,\n near(this.xs, a.x, RUN_TOLERANCE),\n within(this.ys, Math.min(a.y, b.y), Math.max(a.y, b.y)),\n );\n }\n }\n for (const rect of search.borders) {\n const rows = within(this.ys, rect.y, rect.y + rect.h);\n const cols = within(this.xs, rect.x, rect.x + rect.w);\n for (const x of [rect.x, rect.x + rect.w]) {\n this.#fill(this.#bordersBeforeX, crossedBefore(this.xs, x), rows);\n this.#fill(this.#hugV, near(this.xs, x, HUG_DISTANCE + 1e-6), rows);\n }\n for (const y of [rect.y, rect.y + rect.h]) {\n this.#fill(this.#bordersBeforeY, cols, crossedBefore(this.ys, y));\n this.#fill(this.#hugH, cols, near(this.ys, y, HUG_DISTANCE + 1e-6));\n }\n }\n }\n\n indexOf(point: Point): number | undefined {\n const i = this.xs.indexOf(point.x);\n const j = this.ys.indexOf(point.y);\n return i === -1 || j === -1 ? undefined : j * this.xs.length + i;\n }\n\n /** A lower bound on the cost from `cell` to `goal`: one unit per GRID of distance. */\n estimate(cell: number, goal: number): number {\n const width = this.xs.length;\n return (\n (Math.abs(this.xs[cell % width]! - this.xs[goal % width]!) +\n Math.abs(\n this.ys[Math.floor(cell / width)]! -\n this.ys[Math.floor(goal / width)]!,\n )) /\n GRID\n );\n }\n\n step(\n cell: number,\n heading: Heading,\n ): { cell: number; cost: number } | undefined {\n const width = this.xs.length;\n const i = cell % width;\n const j = Math.floor(cell / width);\n const [di, dj] = STEPS[heading];\n const ni = i + di;\n const nj = j + dj;\n if (ni < 0 || nj < 0 || ni >= width || nj >= this.ys.length)\n return undefined;\n const next = nj * width + ni;\n if (this.blocked[next]) return undefined;\n const horizontal = isHorizontal(heading);\n if ((horizontal ? this.#alongRunH : this.#alongRunV)[next])\n return undefined;\n const length = horizontal\n ? Math.abs(this.xs[ni]! - this.xs[i]!)\n : Math.abs(this.ys[nj]! - this.ys[j]!);\n const crossed = horizontal\n ? this.#bordersBeforeX[j * width + Math.max(i, ni)]!\n : this.#bordersBeforeY[Math.max(j, nj) * width + i]!;\n const hugs = (horizontal ? this.#hugH : this.#hugV)[next]! > 0;\n const crossesRun =\n (horizontal ? this.#alongRunV : this.#alongRunH)[next]! > 0;\n return {\n cell: next,\n cost:\n length / GRID +\n crossed * BORDER_CROSSING_COST +\n (hugs ? (BORDER_HUG_COST * length) / GRID : 0) +\n (crossesRun ? EDGE_CROSSING_COST : 0),\n };\n }\n\n trace(cameFrom: Int32Array, last: number): Point[] {\n const width = this.xs.length;\n const points: Point[] = [];\n for (let state = last; state !== -1; state = cameFrom[state]!) {\n const cell = state >> 2;\n points.push({\n x: this.xs[cell % width]!,\n y: this.ys[Math.floor(cell / width)]!,\n });\n }\n return points.reverse();\n }\n\n #fill(target: Uint8Array, cols: number[], rows: number[]): void {\n const width = this.xs.length;\n for (const j of rows) for (const i of cols) target[j * width + i]! += 1;\n }\n}\n\n/** Every multiple of GRID in [from, to], with the extra lines inside it, sorted and distinct. */\nfunction axis(from: number, to: number, extra: number[]): number[] {\n const values = new Set<number>();\n for (let v = Math.ceil(from / GRID) * GRID; v <= to; v += GRID) values.add(v);\n for (const v of extra) if (v >= from && v <= to) values.add(v);\n return [...values].sort((a, b) => a - b);\n}\n\nfunction indicesWhere(\n values: number[],\n keep: (value: number) => boolean,\n): number[] {\n const indices: number[] = [];\n for (const [index, value] of values.entries())\n if (keep(value)) indices.push(index);\n return indices;\n}\n\nfunction strictlyInside(values: number[], from: number, to: number): number[] {\n return indicesWhere(values, (v) => v > from && v < to);\n}\n\nfunction within(values: number[], from: number, to: number): number[] {\n return indicesWhere(values, (v) => v >= from && v <= to);\n}\n\nfunction near(values: number[], at: number, distance: number): number[] {\n return indicesWhere(values, (v) => Math.abs(v - at) < distance);\n}\n\n/** The index i whose step from i − 1 crosses `at`. */\nfunction crossedBefore(values: number[], at: number): number[] {\n const i = values.findIndex((v) => v >= at);\n return i > 0 && values[i - 1]! < at ? [i] : [];\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,16 @@
1
+ import type { DiagramGraphIR } from '../../model/diagram-ir.ts';
2
+ import type { GridAreas } from './areas.ts';
3
+ import type { EdgeLayout, GroupLayout, LeaderLayout, NodeLayout } from './graph-layout-result.ts';
4
+ /**
5
+ * Routes every edge and note leader orthogonally, nearest cells first, and
6
+ * places each edge's label and badge before the next edge routes around
7
+ * them. It throws when a route or a label has no room; it never draws a
8
+ * diagonal or straight line through what is in the way.
9
+ */
10
+ export declare function routeGraph(ir: DiagramGraphIR, nodes: NodeLayout[], groups: GroupLayout[], areas: GridAreas,
11
+ /** How far beyond the bounding box of its two shapes a route first searches. */
12
+ searchMargin: number): {
13
+ edges: EdgeLayout[];
14
+ leaders: LeaderLayout[];
15
+ };
16
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,cAAc,EAAS,MAAM,2BAA2B,CAAC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAO5C,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,EACX,MAAM,0BAA0B,CAAC;AAmBlC;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,SAAS;AAChB,gFAAgF;AAChF,YAAY,EAAE,MAAM,GACnB;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,CA2KlD"}
@@ -0,0 +1,241 @@
1
+ import { attachConnections } from "./attach.js";
2
+ import { graphConnections } from "./connections.js";
3
+ import { tagPosition } from "./labels.js";
4
+ import { measureEdgeBadge, measureEdgeLabel } from "./measure.js";
5
+ import { pathToSvg, simplifyPath } from "./path.js";
6
+ import { searchRoute } from "./route-search.js";
7
+ import { OPPOSITE, OUTWARD } from "./sides.js";
8
+ import { LAYOUT_TOKENS } from "./tokens.js";
9
+
10
+ /** How far around the content a route may detour. */
11
+ const ROUTING_MARGIN = 64;
12
+ /** How far a route keeps from a node that it does not connect. */
13
+ const NODE_CLEARANCE = 8;
14
+ /**
15
+ * Routes every edge and note leader orthogonally, nearest cells first, and
16
+ * places each edge's label and badge before the next edge routes around
17
+ * them. It throws when a route or a label has no room; it never draws a
18
+ * diagonal or straight line through what is in the way.
19
+ */
20
+ export function routeGraph(ir, nodes, groups, areas, /** How far beyond the bounding box of its two shapes a route first searches. */
21
+ searchMargin) {
22
+ const connections = graphConnections(ir, nodes, groups, areas);
23
+ if (connections.length === 0) return {
24
+ edges: [],
25
+ leaders: []
26
+ };
27
+ const groupLabels = groups.flatMap(({
28
+ group,
29
+ label
30
+ }) => label.map(run => ({
31
+ group: group.id,
32
+ box: inflate(run.bounds, LAYOUT_TOKENS.LABEL_GAP)
33
+ })));
34
+ const attachments = attachConnections(connections, groupLabels);
35
+ const content = union([...nodes.map(nodeLayout => nodeLayout.visual), ...groups.map(groupLayout => groupLayout.rect)]);
36
+ const bounds = inflate(content, ROUTING_MARGIN);
37
+ const solid = nodes.filter(({
38
+ box
39
+ }) => box.w > 0 && box.h > 0);
40
+ const routed = [];
41
+ const tags = [];
42
+ const paths = new Map();
43
+ const labels = new Map();
44
+ const order = connections.map((connection, index) => ({
45
+ connection,
46
+ index
47
+ })).sort((a, b) => Number(a.connection.edge === undefined) - Number(b.connection.edge === undefined) || a.connection.cellDistance - b.connection.cellDistance || a.index - b.index);
48
+ for (const {
49
+ connection,
50
+ index
51
+ } of order) {
52
+ const {
53
+ from,
54
+ to
55
+ } = connection;
56
+ const ends = new Set([from.shape.id, to.shape.id]);
57
+ const junctions = new Set([from, to].filter(end => end.shape.kind === 'junction').map(end => end.shape.id));
58
+ const walls = [...solid.flatMap(({
59
+ node,
60
+ box,
61
+ visual,
62
+ frames,
63
+ caption
64
+ }) => {
65
+ if (!ends.has(node.id)) return [inflate(visual, NODE_CLEARANCE)];
66
+ if (node.kind === 'junction') return [];
67
+ // Its own parts, not their bounding box: a caption wider than the frame leaves room beside the frame.
68
+ return [box, ...frames, ...(caption ? [caption.box] : [])];
69
+ }), ...groupLabels.flatMap(({
70
+ group,
71
+ box
72
+ }) => ends.has(group) ? [] : [box]), ...tags.map(tag => inflate(tag, LAYOUT_TOKENS.LABEL_GAP))];
73
+ const runs = routed.filter(route => !overlaps(route.junctions, junctions)).flatMap(route => segments(route.points));
74
+ const {
75
+ start,
76
+ startStub,
77
+ end,
78
+ endStub
79
+ } = attachments[index];
80
+ const stubs = [[start, startStub], [endStub, end]];
81
+ const stubsClear = stubs.every(([a, b]) => walls.every(wall => !crosses(a, b, wall)));
82
+ const search = window => searchRoute({
83
+ window,
84
+ lines: {
85
+ x: [startStub.x, endStub.x],
86
+ y: [startStub.y, endStub.y]
87
+ },
88
+ walls,
89
+ runs,
90
+ borders: groups.flatMap(({
91
+ rect,
92
+ band
93
+ }) => band ? [rect, band] : [rect]),
94
+ start: startStub,
95
+ startHeading: OUTWARD[from.side],
96
+ goal: endStub,
97
+ goalHeading: OUTWARD[OPPOSITE[to.side]]
98
+ });
99
+ const near = intersection(inflate(union([from.shape.box, to.shape.box, spanOf(startStub, endStub)]), searchMargin), bounds);
100
+ const found = stubsClear ? search(near) ?? search(bounds) : undefined;
101
+ if (!found) {
102
+ const advice = connection.edge === undefined ? 'Move the note or its target.' : 'Change exitSide/entrySide or move a node.';
103
+ throw new Error(`Diagram ${connection.name}: no orthogonal route from ${endName(from)} to ${endName(to)} avoids the nodes and labels. ${advice}`);
104
+ }
105
+ const points = simplifyPath([start, ...found, end]);
106
+ paths.set(connection, points);
107
+ if (connection.edge !== undefined) {
108
+ const edge = ir.edges[connection.edge];
109
+ const isFree = (box, ownSegments) => solid.every(({
110
+ visual
111
+ }) => !intersects(box, visual)) && groups.every(({
112
+ rect,
113
+ band,
114
+ label
115
+ }) => !(band && intersects(box, band)) && label.every(run => !intersects(box, run.bounds)) && !(intersects(box, rect) && !contains(rect, box))) && tags.every(tag => !intersects(box, tag)) && [...routed.flatMap(route => segments(route.points)), ...(ownSegments ? segments(points) : [])].every(([a, b]) => !crosses(a, b, box));
116
+ const placed = {};
117
+ if (edge.label?.length) {
118
+ const measured = measureEdgeLabel(edge);
119
+ const position = tagPosition(measured, points, 'beside', box => isFree(box, true));
120
+ if (!position) {
121
+ throw new Error(`Diagram ${connection.name}: label "${edge.label.join(' / ')}" (${Math.ceil(measured.w)}×${Math.ceil(measured.h)}) has no free position beside its route.`);
122
+ }
123
+ placed.label = {
124
+ ...position,
125
+ text: measured.place(position.box)
126
+ };
127
+ tags.push(position.box);
128
+ }
129
+ if (edge.badgeIcon || edge.badgeLabel?.length) {
130
+ const measured = measureEdgeBadge(edge);
131
+ const position = tagPosition(measured, points, 'on', box => isFree(box, false));
132
+ if (!position) {
133
+ throw new Error(`Diagram ${connection.name}: badge "${(edge.badgeLabel ?? []).join(' / ')}" (${Math.ceil(measured.w)}×${Math.ceil(measured.h)}) has no free position on its route.`);
134
+ }
135
+ placed.badge = {
136
+ box: position.box,
137
+ ...measured.place(position.box)
138
+ };
139
+ tags.push(position.box);
140
+ }
141
+ labels.set(connection, placed);
142
+ }
143
+ routed.push({
144
+ points,
145
+ junctions
146
+ });
147
+ }
148
+ const edges = [];
149
+ const leaders = [];
150
+ for (const connection of connections) {
151
+ const points = paths.get(connection);
152
+ if (connection.edge === undefined) {
153
+ leaders.push({
154
+ note: connection.from.shape.id,
155
+ target: connection.to.shape.id,
156
+ points,
157
+ d: pathToSvg(points)
158
+ });
159
+ continue;
160
+ }
161
+ edges.push({
162
+ edge: ir.edges[connection.edge],
163
+ exitSide: connection.from.side,
164
+ entrySide: connection.to.side,
165
+ points,
166
+ d: pathToSvg(points),
167
+ ...labels.get(connection)
168
+ });
169
+ }
170
+ return {
171
+ edges,
172
+ leaders
173
+ };
174
+ }
175
+ function endName({
176
+ shape,
177
+ side,
178
+ port
179
+ }) {
180
+ if (port) return `port "${shape.id}:${port.id}"`;
181
+ if (shape.kind === 'junction') return `the centre of "${shape.id}"`;
182
+ return `${side} of "${shape.id}"`;
183
+ }
184
+ function segments(points) {
185
+ return points.slice(1).map((point, index) => [points[index], point]);
186
+ }
187
+ function overlaps(a, b) {
188
+ for (const id of a) if (b.has(id)) return true;
189
+ return false;
190
+ }
191
+ function spanOf(a, b) {
192
+ return {
193
+ x: Math.min(a.x, b.x),
194
+ y: Math.min(a.y, b.y),
195
+ w: Math.abs(a.x - b.x),
196
+ h: Math.abs(a.y - b.y)
197
+ };
198
+ }
199
+
200
+ /** Whether the segment enters the box's interior, not only its border. */
201
+ function crosses(a, b, box) {
202
+ const span = spanOf(a, b);
203
+ return span.x + span.w > box.x && span.x < box.x + box.w && span.y + span.h > box.y && span.y < box.y + box.h;
204
+ }
205
+ function intersects(a, b) {
206
+ return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
207
+ }
208
+ function contains(outer, inner) {
209
+ return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.w <= outer.x + outer.w && inner.y + inner.h <= outer.y + outer.h;
210
+ }
211
+ function inflate(box, by) {
212
+ return {
213
+ x: box.x - by,
214
+ y: box.y - by,
215
+ w: box.w + 2 * by,
216
+ h: box.h + 2 * by
217
+ };
218
+ }
219
+ function intersection(a, b) {
220
+ const x = Math.max(a.x, b.x);
221
+ const y = Math.max(a.y, b.y);
222
+ return {
223
+ x,
224
+ y,
225
+ w: Math.min(a.x + a.w, b.x + b.w) - x,
226
+ h: Math.min(a.y + a.h, b.y + b.h) - y
227
+ };
228
+ }
229
+ function union(boxes) {
230
+ const left = Math.min(...boxes.map(box => box.x));
231
+ const top = Math.min(...boxes.map(box => box.y));
232
+ const right = Math.max(...boxes.map(box => box.x + box.w));
233
+ const bottom = Math.max(...boxes.map(box => box.y + box.h));
234
+ return {
235
+ x: left,
236
+ y: top,
237
+ w: right - left,
238
+ h: bottom - top
239
+ };
240
+ }
241
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","names":["attachConnections","graphConnections","tagPosition","measureEdgeBadge","measureEdgeLabel","pathToSvg","simplifyPath","searchRoute","OPPOSITE","OUTWARD","LAYOUT_TOKENS","ROUTING_MARGIN","NODE_CLEARANCE","routeGraph","ir","nodes","groups","areas","searchMargin","connections","length","edges","leaders","groupLabels","flatMap","group","label","map","run","id","box","inflate","bounds","LABEL_GAP","attachments","content","union","nodeLayout","visual","groupLayout","rect","solid","filter","w","h","routed","tags","paths","Map","labels","order","connection","index","sort","a","b","Number","edge","undefined","cellDistance","from","to","ends","Set","shape","junctions","end","kind","walls","node","frames","caption","has","tag","runs","route","overlaps","segments","points","start","startStub","endStub","stubs","stubsClear","every","wall","crosses","search","window","lines","x","y","borders","band","startHeading","side","goal","goalHeading","near","intersection","spanOf","found","advice","Error","name","endName","set","isFree","ownSegments","intersects","contains","placed","measured","position","join","Math","ceil","text","place","push","badgeIcon","badgeLabel","badge","get","note","target","d","exitSide","entrySide","port","slice","point","min","abs","span","outer","inner","by","max","boxes","left","top","right","bottom"],"sources":["../../../../src/lib/layouts/graph/route.ts"],"sourcesContent":["import type { Box, DiagramGraphIR, Point } from '../../model/diagram-ir.ts';\nimport type { GridAreas } from './areas.ts';\nimport { attachConnections } from './attach.ts';\nimport {\n type Connection,\n type ConnectionEnd,\n graphConnections,\n} from './connections.ts';\nimport type {\n EdgeLayout,\n GroupLayout,\n LeaderLayout,\n NodeLayout,\n} from './graph-layout-result.ts';\nimport { tagPosition } from './labels.ts';\nimport { measureEdgeBadge, measureEdgeLabel } from './measure.ts';\nimport { pathToSvg, simplifyPath } from './path.ts';\nimport { searchRoute } from './route-search.ts';\nimport { OPPOSITE, OUTWARD } from './sides.ts';\nimport { LAYOUT_TOKENS } from './tokens.ts';\n\n/** How far around the content a route may detour. */\nconst ROUTING_MARGIN = 64;\n/** How far a route keeps from a node that it does not connect. */\nconst NODE_CLEARANCE = 8;\n\ntype Routed = {\n points: Point[];\n /** The junctions the route ends at: routes that share one may run together into it. */\n junctions: Set<string>;\n};\n\n/**\n * Routes every edge and note leader orthogonally, nearest cells first, and\n * places each edge's label and badge before the next edge routes around\n * them. It throws when a route or a label has no room; it never draws a\n * diagonal or straight line through what is in the way.\n */\nexport function routeGraph(\n ir: DiagramGraphIR,\n nodes: NodeLayout[],\n groups: GroupLayout[],\n areas: GridAreas,\n /** How far beyond the bounding box of its two shapes a route first searches. */\n searchMargin: number,\n): { edges: EdgeLayout[]; leaders: LeaderLayout[] } {\n const connections = graphConnections(ir, nodes, groups, areas);\n if (connections.length === 0) return { edges: [], leaders: [] };\n const groupLabels = groups.flatMap(({ group, label }) =>\n label.map((run) => ({\n group: group.id,\n box: inflate(run.bounds, LAYOUT_TOKENS.LABEL_GAP),\n })),\n );\n const attachments = attachConnections(connections, groupLabels);\n const content = union([\n ...nodes.map((nodeLayout) => nodeLayout.visual),\n ...groups.map((groupLayout) => groupLayout.rect),\n ]);\n const bounds = inflate(content, ROUTING_MARGIN);\n const solid = nodes.filter(({ box }) => box.w > 0 && box.h > 0);\n\n const routed: Routed[] = [];\n const tags: Box[] = [];\n const paths = new Map<Connection, Point[]>();\n const labels = new Map<Connection, Pick<EdgeLayout, 'label' | 'badge'>>();\n\n const order = connections\n .map((connection, index) => ({ connection, index }))\n .sort(\n (a, b) =>\n Number(a.connection.edge === undefined) -\n Number(b.connection.edge === undefined) ||\n a.connection.cellDistance - b.connection.cellDistance ||\n a.index - b.index,\n );\n\n for (const { connection, index } of order) {\n const { from, to } = connection;\n const ends = new Set([from.shape.id, to.shape.id]);\n const junctions = new Set(\n [from, to]\n .filter((end) => end.shape.kind === 'junction')\n .map((end) => end.shape.id),\n );\n const walls = [\n ...solid.flatMap(({ node, box, visual, frames, caption }) => {\n if (!ends.has(node.id)) return [inflate(visual, NODE_CLEARANCE)];\n if (node.kind === 'junction') return [];\n // Its own parts, not their bounding box: a caption wider than the frame leaves room beside the frame.\n return [box, ...frames, ...(caption ? [caption.box] : [])];\n }),\n ...groupLabels.flatMap(({ group, box }) =>\n ends.has(group) ? [] : [box],\n ),\n ...tags.map((tag) => inflate(tag, LAYOUT_TOKENS.LABEL_GAP)),\n ];\n const runs = routed\n .filter((route) => !overlaps(route.junctions, junctions))\n .flatMap((route) => segments(route.points));\n const { start, startStub, end, endStub } = attachments[index]!;\n const stubs: [Point, Point][] = [\n [start, startStub],\n [endStub, end],\n ];\n const stubsClear = stubs.every(([a, b]) =>\n walls.every((wall) => !crosses(a, b, wall)),\n );\n\n const search = (window: Box): Point[] | undefined =>\n searchRoute({\n window,\n lines: {\n x: [startStub.x, endStub.x],\n y: [startStub.y, endStub.y],\n },\n walls,\n runs,\n borders: groups.flatMap(({ rect, band }) =>\n band ? [rect, band] : [rect],\n ),\n start: startStub,\n startHeading: OUTWARD[from.side],\n goal: endStub,\n goalHeading: OUTWARD[OPPOSITE[to.side]],\n });\n const near = intersection(\n inflate(\n union([from.shape.box, to.shape.box, spanOf(startStub, endStub)]),\n searchMargin,\n ),\n bounds,\n );\n const found = stubsClear ? (search(near) ?? search(bounds)) : undefined;\n if (!found) {\n const advice =\n connection.edge === undefined\n ? 'Move the note or its target.'\n : 'Change exitSide/entrySide or move a node.';\n throw new Error(\n `Diagram ${connection.name}: no orthogonal route from ${endName(from)} to ${endName(to)} avoids the nodes and labels. ${advice}`,\n );\n }\n const points = simplifyPath([start, ...found, end]);\n paths.set(connection, points);\n\n if (connection.edge !== undefined) {\n const edge = ir.edges[connection.edge]!;\n const isFree = (box: Box, ownSegments: boolean) =>\n solid.every(({ visual }) => !intersects(box, visual)) &&\n groups.every(\n ({ rect, band, label }) =>\n !(band && intersects(box, band)) &&\n label.every((run) => !intersects(box, run.bounds)) &&\n !(intersects(box, rect) && !contains(rect, box)),\n ) &&\n tags.every((tag) => !intersects(box, tag)) &&\n [\n ...routed.flatMap((route) => segments(route.points)),\n ...(ownSegments ? segments(points) : []),\n ].every(([a, b]) => !crosses(a, b, box));\n const placed: Pick<EdgeLayout, 'label' | 'badge'> = {};\n if (edge.label?.length) {\n const measured = measureEdgeLabel(edge);\n const position = tagPosition(measured, points, 'beside', (box) =>\n isFree(box, true),\n );\n if (!position) {\n throw new Error(\n `Diagram ${connection.name}: label \"${edge.label.join(' / ')}\" (${Math.ceil(measured.w)}×${Math.ceil(measured.h)}) has no free position beside its route.`,\n );\n }\n placed.label = { ...position, text: measured.place(position.box) };\n tags.push(position.box);\n }\n if (edge.badgeIcon || edge.badgeLabel?.length) {\n const measured = measureEdgeBadge(edge);\n const position = tagPosition(measured, points, 'on', (box) =>\n isFree(box, false),\n );\n if (!position) {\n throw new Error(\n `Diagram ${connection.name}: badge \"${(edge.badgeLabel ?? []).join(' / ')}\" (${Math.ceil(measured.w)}×${Math.ceil(measured.h)}) has no free position on its route.`,\n );\n }\n placed.badge = { box: position.box, ...measured.place(position.box) };\n tags.push(position.box);\n }\n labels.set(connection, placed);\n }\n routed.push({ points, junctions });\n }\n\n const edges: EdgeLayout[] = [];\n const leaders: LeaderLayout[] = [];\n for (const connection of connections) {\n const points = paths.get(connection)!;\n if (connection.edge === undefined) {\n leaders.push({\n note: connection.from.shape.id,\n target: connection.to.shape.id,\n points,\n d: pathToSvg(points),\n });\n continue;\n }\n edges.push({\n edge: ir.edges[connection.edge]!,\n exitSide: connection.from.side,\n entrySide: connection.to.side,\n points,\n d: pathToSvg(points),\n ...labels.get(connection),\n });\n }\n return { edges, leaders };\n}\n\nfunction endName({ shape, side, port }: ConnectionEnd): string {\n if (port) return `port \"${shape.id}:${port.id}\"`;\n if (shape.kind === 'junction') return `the centre of \"${shape.id}\"`;\n return `${side} of \"${shape.id}\"`;\n}\n\nfunction segments(points: Point[]): [Point, Point][] {\n return points.slice(1).map((point, index) => [points[index]!, point]);\n}\n\nfunction overlaps(a: Set<string>, b: Set<string>): boolean {\n for (const id of a) if (b.has(id)) return true;\n return false;\n}\n\nfunction spanOf(a: Point, b: Point): Box {\n return {\n x: Math.min(a.x, b.x),\n y: Math.min(a.y, b.y),\n w: Math.abs(a.x - b.x),\n h: Math.abs(a.y - b.y),\n };\n}\n\n/** Whether the segment enters the box's interior, not only its border. */\nfunction crosses(a: Point, b: Point, box: Box): boolean {\n const span = spanOf(a, b);\n return (\n span.x + span.w > box.x &&\n span.x < box.x + box.w &&\n span.y + span.h > box.y &&\n span.y < box.y + box.h\n );\n}\n\nfunction intersects(a: Box, b: Box): boolean {\n return (\n a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h\n );\n}\n\nfunction contains(outer: Box, inner: Box): boolean {\n return (\n inner.x >= outer.x &&\n inner.y >= outer.y &&\n inner.x + inner.w <= outer.x + outer.w &&\n inner.y + inner.h <= outer.y + outer.h\n );\n}\n\nfunction inflate(box: Box, by: number): Box {\n return { x: box.x - by, y: box.y - by, w: box.w + 2 * by, h: box.h + 2 * by };\n}\n\nfunction intersection(a: Box, b: Box): Box {\n const x = Math.max(a.x, b.x);\n const y = Math.max(a.y, b.y);\n return {\n x,\n y,\n w: Math.min(a.x + a.w, b.x + b.w) - x,\n h: Math.min(a.y + a.h, b.y + b.h) - y,\n };\n}\n\nfunction union(boxes: Box[]): Box {\n const left = Math.min(...boxes.map((box) => box.x));\n const top = Math.min(...boxes.map((box) => box.y));\n const right = Math.max(...boxes.map((box) => box.x + box.w));\n const bottom = Math.max(...boxes.map((box) => box.y + box.h));\n return { x: left, y: top, w: right - left, h: bottom - top };\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,23 @@
1
+ import type { Box, DiagramSide, Point } from '../../model/diagram-ir.ts';
2
+ import type { CellArea } from './areas.ts';
3
+ import type { Heading } from './route-search.ts';
4
+ export declare const OPPOSITE: Record<DiagramSide, DiagramSide>;
5
+ /** The heading of a line that leaves a box through `side`. */
6
+ export declare const OUTWARD: Record<DiagramSide, Heading>;
7
+ export declare function isVertical(side: DiagramSide): boolean;
8
+ /**
9
+ * The sides a connection leaves and enters by, read from the cells: areas
10
+ * that share rows face left and right, areas that share columns face top
11
+ * and bottom, and otherwise the axis with the larger cell delta wins, the
12
+ * vertical one on a tie.
13
+ */
14
+ export declare function facingSides(from: CellArea, to: CellArea): [DiagramSide, DiagramSide];
15
+ /** The coordinate of the side's line: x for left and right, y for top and bottom. */
16
+ export declare function sideLine(box: Box, side: DiagramSide): number;
17
+ /** Where the side runs along its line: [start, end] in x for top and bottom, in y for left and right. */
18
+ export declare function sideSpan(box: Box, side: DiagramSide): [number, number];
19
+ /** The point on `side` of `box` at `along` on the side's span. */
20
+ export declare function pointOnSide(box: Box, side: DiagramSide, along: number): Point;
21
+ /** The point `distance` beyond `point` out of `side`. */
22
+ export declare function beyond(point: Point, side: DiagramSide, distance: number): Point;
23
+ //# sourceMappingURL=sides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sides.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/sides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,WAAW,CAKrD,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,CAKhD,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,QAAQ,EACd,EAAE,EAAE,QAAQ,GACX,CAAC,WAAW,EAAE,WAAW,CAAC,CAa5B;AAED,qFAAqF;AACrF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAW5D;AAED,yGAAyG;AACzG,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAEtE;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAG7E;AAED,yDAAyD;AACzD,wBAAgB,MAAM,CACpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,MAAM,GACf,KAAK,CAWP"}