@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.
- package/dist/lib/layouts/graph/attach.d.ts +25 -0
- package/dist/lib/layouts/graph/attach.d.ts.map +1 -0
- package/dist/lib/layouts/graph/attach.js +216 -0
- package/dist/lib/layouts/graph/attach.js.map +1 -0
- package/dist/lib/layouts/graph/connections.d.ts +38 -0
- package/dist/lib/layouts/graph/connections.d.ts.map +1 -0
- package/dist/lib/layouts/graph/connections.js +112 -0
- package/dist/lib/layouts/graph/connections.js.map +1 -0
- package/dist/lib/layouts/graph/graph-layout-result.d.ts +1 -1
- package/dist/lib/layouts/graph/graph-layout-result.d.ts.map +1 -1
- package/dist/lib/layouts/graph/graph-layout-result.js.map +1 -1
- package/dist/lib/layouts/graph/gutters.js +5 -5
- package/dist/lib/layouts/graph/gutters.js.map +1 -1
- package/dist/lib/layouts/graph/labels.d.ts +16 -0
- package/dist/lib/layouts/graph/labels.d.ts.map +1 -0
- package/dist/lib/layouts/graph/labels.js +76 -0
- package/dist/lib/layouts/graph/labels.js.map +1 -0
- package/dist/lib/layouts/graph/layout.d.ts.map +1 -1
- package/dist/lib/layouts/graph/layout.js +31 -9
- package/dist/lib/layouts/graph/layout.js.map +1 -1
- package/dist/lib/layouts/graph/measure.d.ts +7 -6
- package/dist/lib/layouts/graph/measure.d.ts.map +1 -1
- package/dist/lib/layouts/graph/measure.js +35 -29
- package/dist/lib/layouts/graph/measure.js.map +1 -1
- package/dist/lib/layouts/graph/min-heap.d.ts +12 -0
- package/dist/lib/layouts/graph/min-heap.d.ts.map +1 -0
- package/dist/lib/layouts/graph/min-heap.js +60 -0
- package/dist/lib/layouts/graph/min-heap.js.map +1 -0
- package/dist/lib/layouts/graph/node-content.d.ts +1 -2
- package/dist/lib/layouts/graph/node-content.d.ts.map +1 -1
- package/dist/lib/layouts/graph/node-content.js +43 -42
- package/dist/lib/layouts/graph/node-content.js.map +1 -1
- package/dist/lib/layouts/graph/path.d.ts +14 -0
- package/dist/lib/layouts/graph/path.d.ts.map +1 -0
- package/dist/lib/layouts/graph/path.js +55 -0
- package/dist/lib/layouts/graph/path.js.map +1 -0
- package/dist/lib/layouts/graph/route-search.d.ts +34 -0
- package/dist/lib/layouts/graph/route-search.d.ts.map +1 -0
- package/dist/lib/layouts/graph/route-search.js +208 -0
- package/dist/lib/layouts/graph/route-search.js.map +1 -0
- package/dist/lib/layouts/graph/route.d.ts +16 -0
- package/dist/lib/layouts/graph/route.d.ts.map +1 -0
- package/dist/lib/layouts/graph/route.js +241 -0
- package/dist/lib/layouts/graph/route.js.map +1 -0
- package/dist/lib/layouts/graph/sides.d.ts +23 -0
- package/dist/lib/layouts/graph/sides.d.ts.map +1 -0
- package/dist/lib/layouts/graph/sides.js +91 -0
- package/dist/lib/layouts/graph/sides.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Box, Point } from '../../model/diagram-ir.ts';
|
|
2
|
+
import type { Connection } from './connections.ts';
|
|
3
|
+
/** Where a connection touches its two shapes, and where its stubs leave them. */
|
|
4
|
+
export type Attachment = {
|
|
5
|
+
start: Point;
|
|
6
|
+
startStub: Point;
|
|
7
|
+
end: Point;
|
|
8
|
+
endStub: Point;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Picks where every connection touches its shapes. A junction end is its
|
|
12
|
+
* centre and a port end is its port. The other ends that share a side of a
|
|
13
|
+
* shape split the free part of it, clear of the corners and of a caption on
|
|
14
|
+
* that side, into even slots, ordered by where their other ends lie, so they
|
|
15
|
+
* do not cross. A connection alone on two facing sides that overlap runs
|
|
16
|
+
* straight across the middle of the widest part of the overlap that no
|
|
17
|
+
* group label text lies on.
|
|
18
|
+
*/
|
|
19
|
+
export declare function attachConnections(connections: Connection[],
|
|
20
|
+
/** Group label text, by group id: a straight line between two facing sides passes beside it. */
|
|
21
|
+
groupLabels: {
|
|
22
|
+
group: string;
|
|
23
|
+
box: Box;
|
|
24
|
+
}[]): Attachment[];
|
|
25
|
+
//# sourceMappingURL=attach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/graph/attach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,kBAAkB,CAAC;AAYlE,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,GAAG,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,KAAK,CAAC;CAChB,CAAC;AAQF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,UAAU,EAAE;AACzB,gGAAgG;AAChG,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,EAAE,GACzC,UAAU,EAAE,CA+Ed"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { GRID } from "./route-search.js";
|
|
2
|
+
import { OPPOSITE, beyond, isVertical, pointOnSide, sideLine, sideSpan } from "./sides.js";
|
|
3
|
+
import { LAYOUT_TOKENS } from "./tokens.js";
|
|
4
|
+
|
|
5
|
+
/** Where a connection touches its two shapes, and where its stubs leave them. */
|
|
6
|
+
|
|
7
|
+
/** The closest two slots on one side may lie: two grid lines, so their routes keep a line between them. */
|
|
8
|
+
const SLOT_PITCH = 2 * GRID;
|
|
9
|
+
/**
|
|
10
|
+
* Picks where every connection touches its shapes. A junction end is its
|
|
11
|
+
* centre and a port end is its port. The other ends that share a side of a
|
|
12
|
+
* shape split the free part of it, clear of the corners and of a caption on
|
|
13
|
+
* that side, into even slots, ordered by where their other ends lie, so they
|
|
14
|
+
* do not cross. A connection alone on two facing sides that overlap runs
|
|
15
|
+
* straight across the middle of the widest part of the overlap that no
|
|
16
|
+
* group label text lies on.
|
|
17
|
+
*/
|
|
18
|
+
export function attachConnections(connections, /** Group label text, by group id: a straight line between two facing sides passes beside it. */
|
|
19
|
+
groupLabels) {
|
|
20
|
+
const fixed = end => {
|
|
21
|
+
if (end.port) return end.port.point;
|
|
22
|
+
if (end.shape.kind === 'junction') return centre(end.shape.box);
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
25
|
+
const bySide = new Map();
|
|
26
|
+
for (const [index, connection] of connections.entries()) {
|
|
27
|
+
for (const role of ['from', 'to']) {
|
|
28
|
+
const end = connection[role];
|
|
29
|
+
if (fixed(end)) continue;
|
|
30
|
+
const key = sideKey(end);
|
|
31
|
+
bySide.set(key, [...(bySide.get(key) ?? []), {
|
|
32
|
+
connection: index,
|
|
33
|
+
role,
|
|
34
|
+
end
|
|
35
|
+
}]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const crowd = end => bySide.get(sideKey(end)).length;
|
|
39
|
+
|
|
40
|
+
/** Where each slot end touches its side, by `${connection}:${role}`. */
|
|
41
|
+
const touches = new Map();
|
|
42
|
+
const touchKey = (connection, role) => `${connection}:${role}`;
|
|
43
|
+
for (const [index, {
|
|
44
|
+
from,
|
|
45
|
+
to
|
|
46
|
+
}] of connections.entries()) {
|
|
47
|
+
const between = groupLabels.flatMap(({
|
|
48
|
+
group,
|
|
49
|
+
box
|
|
50
|
+
}) => group === from.shape.id || group === to.shape.id ? [] : [box]);
|
|
51
|
+
const aligned = facingAlignment(from, to, fixed(from), fixed(to), crowd, between);
|
|
52
|
+
if (aligned) {
|
|
53
|
+
touches.set(touchKey(index, 'from'), aligned.from);
|
|
54
|
+
touches.set(touchKey(index, 'to'), aligned.to);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const slots of bySide.values()) {
|
|
58
|
+
const open = slots.filter(({
|
|
59
|
+
connection,
|
|
60
|
+
role
|
|
61
|
+
}) => !touches.has(touchKey(connection, role)));
|
|
62
|
+
if (open.length === 0) continue;
|
|
63
|
+
const {
|
|
64
|
+
end
|
|
65
|
+
} = slots[0];
|
|
66
|
+
const vertical = isVertical(end.side);
|
|
67
|
+
const otherAt = ({
|
|
68
|
+
connection,
|
|
69
|
+
role
|
|
70
|
+
}) => {
|
|
71
|
+
const other = connections[connection][role === 'from' ? 'to' : 'from'];
|
|
72
|
+
const at = fixed(other) ?? centre(other.shape.box);
|
|
73
|
+
return vertical ? at.x : at.y;
|
|
74
|
+
};
|
|
75
|
+
const ordered = open.map((slot, order) => ({
|
|
76
|
+
slot,
|
|
77
|
+
order,
|
|
78
|
+
at: otherAt(slot)
|
|
79
|
+
})).sort((a, b) => a.at - b.at || a.order - b.order);
|
|
80
|
+
const alongs = spread(end, ordered.length);
|
|
81
|
+
for (const [index, {
|
|
82
|
+
slot
|
|
83
|
+
}] of ordered.entries()) {
|
|
84
|
+
touches.set(touchKey(slot.connection, slot.role), pointOnSide(end.shape.box, end.side, alongs[index]));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return connections.map((connection, index) => {
|
|
88
|
+
const start = fixed(connection.from) ?? touches.get(touchKey(index, 'from'));
|
|
89
|
+
const end = fixed(connection.to) ?? touches.get(touchKey(index, 'to'));
|
|
90
|
+
const stub = stubLength(connection, start, end);
|
|
91
|
+
return {
|
|
92
|
+
start,
|
|
93
|
+
startStub: beyond(start, connection.from.side, stub),
|
|
94
|
+
end,
|
|
95
|
+
endStub: beyond(end, connection.to.side, stub)
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The parts of a side a connection may touch: the side less its corner
|
|
102
|
+
* inset, less what lies behind a caption on that side. A short side gives up
|
|
103
|
+
* its corner inset before its slots come closer than SLOT_PITCH.
|
|
104
|
+
*/
|
|
105
|
+
function freeIntervals(end, count) {
|
|
106
|
+
const {
|
|
107
|
+
shape,
|
|
108
|
+
side
|
|
109
|
+
} = end;
|
|
110
|
+
const [from, to] = sideSpan(shape.box, side);
|
|
111
|
+
const inset = Math.max(0, Math.min(shape.radius, (to - from - (count + 1) * SLOT_PITCH) / 2));
|
|
112
|
+
const vertical = isVertical(side);
|
|
113
|
+
const line = sideLine(shape.box, side);
|
|
114
|
+
const outward = side === 'bottom' || side === 'right';
|
|
115
|
+
const behind = shape.clear.flatMap(box => {
|
|
116
|
+
const [near, far] = vertical ? [box.y, box.y + box.h] : [box.x, box.x + box.w];
|
|
117
|
+
if (outward ? near < line : far > line) return [];
|
|
118
|
+
return vertical ? [[box.x - SLOT_PITCH, box.x + box.w + SLOT_PITCH]] : [[box.y - SLOT_PITCH, box.y + box.h + SLOT_PITCH]];
|
|
119
|
+
});
|
|
120
|
+
return cut([[from + inset, to - inset]], behind);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The parts of `intervals` outside every one of `cuts`. */
|
|
124
|
+
function cut(intervals, cuts) {
|
|
125
|
+
let free = intervals;
|
|
126
|
+
for (const [cutStart, cutEnd] of cuts) {
|
|
127
|
+
free = free.flatMap(([a, b]) => [[a, Math.min(b, cutStart)], [Math.max(a, cutEnd), b]]).filter(([low, high]) => high > low);
|
|
128
|
+
}
|
|
129
|
+
return free;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Positions for `count` slots on the free parts of a side, in order along
|
|
134
|
+
* it: each next slot goes to the part where slots would lie farthest apart,
|
|
135
|
+
* and each part spaces its slots evenly.
|
|
136
|
+
*/
|
|
137
|
+
function spread(end, count) {
|
|
138
|
+
const intervals = freeIntervals(end, count);
|
|
139
|
+
const counts = intervals.map(() => 0);
|
|
140
|
+
const pitchWithOneMore = index => (intervals[index][1] - intervals[index][0]) / (counts[index] + 2);
|
|
141
|
+
for (let placed = 0; placed < count && intervals.length > 0; placed++) {
|
|
142
|
+
const roomiest = counts.reduce((best, _, index) => pitchWithOneMore(index) > pitchWithOneMore(best) ? index : best, 0);
|
|
143
|
+
counts[roomiest] = counts[roomiest] + 1;
|
|
144
|
+
}
|
|
145
|
+
const alongs = intervals.flatMap(([a, b], index) => Array.from({
|
|
146
|
+
length: counts[index]
|
|
147
|
+
}, (_, slot) => a + (slot + 1) / (counts[index] + 1) * (b - a)));
|
|
148
|
+
const closest = Math.min(...intervals.flatMap(([a, b], index) => counts[index] > 1 ? [(b - a) / (counts[index] + 1)] : []));
|
|
149
|
+
if (alongs.length < count || closest < SLOT_PITCH) {
|
|
150
|
+
const {
|
|
151
|
+
shape,
|
|
152
|
+
side
|
|
153
|
+
} = end;
|
|
154
|
+
throw new Error(`Diagram ${shape.kind === 'group' ? 'group' : 'node'} "${shape.id}" side ${side} has no room for ${count} connections ${SLOT_PITCH}px apart beside its corners and caption.`);
|
|
155
|
+
}
|
|
156
|
+
return alongs;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Both ends on the middle of the widest part of the overlap of two facing
|
|
161
|
+
* sides that no label in `labels` between the sides lies on, when the
|
|
162
|
+
* connection is alone on each.
|
|
163
|
+
*/
|
|
164
|
+
function facingAlignment(from, to, fromFixed, toFixed, crowd, labels) {
|
|
165
|
+
if (to.side !== OPPOSITE[from.side] || fromFixed && toFixed) return;
|
|
166
|
+
if (!fromFixed && crowd(from) !== 1) return;
|
|
167
|
+
if (!toFixed && crowd(to) !== 1) return;
|
|
168
|
+
const vertical = isVertical(from.side);
|
|
169
|
+
const free = (end, at) => {
|
|
170
|
+
if (!at) return freeIntervals(end, 1);
|
|
171
|
+
const along = vertical ? at.x : at.y;
|
|
172
|
+
return [[along, along]];
|
|
173
|
+
};
|
|
174
|
+
const lines = [sideLine(from.shape.box, from.side), sideLine(to.shape.box, to.side)];
|
|
175
|
+
const [gapStart, gapEnd] = [Math.min(...lines), Math.max(...lines)];
|
|
176
|
+
const inGap = labels.flatMap(({
|
|
177
|
+
x,
|
|
178
|
+
y,
|
|
179
|
+
w,
|
|
180
|
+
h
|
|
181
|
+
}) => {
|
|
182
|
+
const [across, acrossEnd, along, alongEnd] = vertical ? [y, y + h, x, x + w] : [x, x + w, y, y + h];
|
|
183
|
+
return across < gapEnd && acrossEnd > gapStart ? [[along, alongEnd]] : [];
|
|
184
|
+
});
|
|
185
|
+
const widest = free(from, fromFixed).flatMap(([a0, a1]) => free(to, toFixed).flatMap(([b0, b1]) => cut([[Math.max(a0, b0), Math.min(a1, b1)]], inGap))).filter(([low, high]) => low <= high).sort((a, b) => b[1] - b[0] - (a[1] - a[0]))[0];
|
|
186
|
+
if (!widest) return;
|
|
187
|
+
const along = (widest[0] + widest[1]) / 2;
|
|
188
|
+
return {
|
|
189
|
+
from: fromFixed ?? pointOnSide(from.shape.box, from.side, along),
|
|
190
|
+
to: toFixed ?? pointOnSide(to.shape.box, to.side, along)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A stub is STUB long, except between two ends that face each other closer
|
|
196
|
+
* than two stubs: then each stub reaches half way, so they meet in the gap.
|
|
197
|
+
*/
|
|
198
|
+
function stubLength(connection, start, end) {
|
|
199
|
+
const {
|
|
200
|
+
from,
|
|
201
|
+
to
|
|
202
|
+
} = connection;
|
|
203
|
+
if (to.side !== OPPOSITE[from.side]) return LAYOUT_TOKENS.STUB;
|
|
204
|
+
const gap = (isVertical(from.side) ? end.y - start.y : end.x - start.x) * (from.side === 'bottom' || from.side === 'right' ? 1 : -1);
|
|
205
|
+
return gap > 0 && gap < 2 * LAYOUT_TOKENS.STUB ? gap / 2 : LAYOUT_TOKENS.STUB;
|
|
206
|
+
}
|
|
207
|
+
function sideKey(end) {
|
|
208
|
+
return `${end.shape.id}\u0000${end.side}`;
|
|
209
|
+
}
|
|
210
|
+
function centre(box) {
|
|
211
|
+
return {
|
|
212
|
+
x: box.x + box.w / 2,
|
|
213
|
+
y: box.y + box.h / 2
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=attach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach.js","names":["GRID","OPPOSITE","beyond","isVertical","pointOnSide","sideLine","sideSpan","LAYOUT_TOKENS","SLOT_PITCH","attachConnections","connections","groupLabels","fixed","end","port","point","shape","kind","centre","box","undefined","bySide","Map","index","connection","entries","role","key","sideKey","set","get","crowd","length","touches","touchKey","from","to","between","flatMap","group","id","aligned","facingAlignment","slots","values","open","filter","has","vertical","side","otherAt","other","at","x","y","ordered","map","slot","order","sort","a","b","alongs","spread","start","stub","stubLength","startStub","endStub","freeIntervals","count","inset","Math","max","min","radius","line","outward","behind","clear","near","far","h","w","cut","intervals","cuts","free","cutStart","cutEnd","low","high","counts","pitchWithOneMore","placed","roomiest","reduce","best","_","Array","closest","Error","fromFixed","toFixed","labels","along","lines","gapStart","gapEnd","inGap","across","acrossEnd","alongEnd","widest","a0","a1","b0","b1","STUB","gap"],"sources":["../../../../src/lib/layouts/graph/attach.ts"],"sourcesContent":["import type { Box, Point } from '../../model/diagram-ir.ts';\nimport type { Connection, ConnectionEnd } from './connections.ts';\nimport { GRID } from './route-search.ts';\nimport {\n OPPOSITE,\n beyond,\n isVertical,\n pointOnSide,\n sideLine,\n sideSpan,\n} from './sides.ts';\nimport { LAYOUT_TOKENS } from './tokens.ts';\n\n/** Where a connection touches its two shapes, and where its stubs leave them. */\nexport type Attachment = {\n start: Point;\n startStub: Point;\n end: Point;\n endStub: Point;\n};\n\n/** The closest two slots on one side may lie: two grid lines, so their routes keep a line between them. */\nconst SLOT_PITCH = 2 * GRID;\n\ntype Slot = { connection: number; role: 'from' | 'to'; end: ConnectionEnd };\ntype Interval = [number, number];\n\n/**\n * Picks where every connection touches its shapes. A junction end is its\n * centre and a port end is its port. The other ends that share a side of a\n * shape split the free part of it, clear of the corners and of a caption on\n * that side, into even slots, ordered by where their other ends lie, so they\n * do not cross. A connection alone on two facing sides that overlap runs\n * straight across the middle of the widest part of the overlap that no\n * group label text lies on.\n */\nexport function attachConnections(\n connections: Connection[],\n /** Group label text, by group id: a straight line between two facing sides passes beside it. */\n groupLabels: { group: string; box: Box }[],\n): Attachment[] {\n const fixed = (end: ConnectionEnd): Point | undefined => {\n if (end.port) return end.port.point;\n if (end.shape.kind === 'junction') return centre(end.shape.box);\n return undefined;\n };\n\n const bySide = new Map<string, Slot[]>();\n for (const [index, connection] of connections.entries()) {\n for (const role of ['from', 'to'] as const) {\n const end = connection[role];\n if (fixed(end)) continue;\n const key = sideKey(end);\n bySide.set(key, [\n ...(bySide.get(key) ?? []),\n { connection: index, role, end },\n ]);\n }\n }\n const crowd = (end: ConnectionEnd) => bySide.get(sideKey(end))!.length;\n\n /** Where each slot end touches its side, by `${connection}:${role}`. */\n const touches = new Map<string, Point>();\n const touchKey = (connection: number, role: 'from' | 'to') =>\n `${connection}:${role}`;\n for (const [index, { from, to }] of connections.entries()) {\n const between = groupLabels.flatMap(({ group, box }) =>\n group === from.shape.id || group === to.shape.id ? [] : [box],\n );\n const aligned = facingAlignment(\n from,\n to,\n fixed(from),\n fixed(to),\n crowd,\n between,\n );\n if (aligned) {\n touches.set(touchKey(index, 'from'), aligned.from);\n touches.set(touchKey(index, 'to'), aligned.to);\n }\n }\n\n for (const slots of bySide.values()) {\n const open = slots.filter(\n ({ connection, role }) => !touches.has(touchKey(connection, role)),\n );\n if (open.length === 0) continue;\n const { end } = slots[0]!;\n const vertical = isVertical(end.side);\n const otherAt = ({ connection, role }: Slot) => {\n const other = connections[connection]![role === 'from' ? 'to' : 'from'];\n const at = fixed(other) ?? centre(other.shape.box);\n return vertical ? at.x : at.y;\n };\n const ordered = open\n .map((slot, order) => ({ slot, order, at: otherAt(slot) }))\n .sort((a, b) => a.at - b.at || a.order - b.order);\n const alongs = spread(end, ordered.length);\n for (const [index, { slot }] of ordered.entries()) {\n touches.set(\n touchKey(slot.connection, slot.role),\n pointOnSide(end.shape.box, end.side, alongs[index]!),\n );\n }\n }\n\n return connections.map((connection, index) => {\n const start =\n fixed(connection.from) ?? touches.get(touchKey(index, 'from'))!;\n const end = fixed(connection.to) ?? touches.get(touchKey(index, 'to'))!;\n const stub = stubLength(connection, start, end);\n return {\n start,\n startStub: beyond(start, connection.from.side, stub),\n end,\n endStub: beyond(end, connection.to.side, stub),\n };\n });\n}\n\n/**\n * The parts of a side a connection may touch: the side less its corner\n * inset, less what lies behind a caption on that side. A short side gives up\n * its corner inset before its slots come closer than SLOT_PITCH.\n */\nfunction freeIntervals(end: ConnectionEnd, count: number): Interval[] {\n const { shape, side } = end;\n const [from, to] = sideSpan(shape.box, side);\n const inset = Math.max(\n 0,\n Math.min(shape.radius, (to - from - (count + 1) * SLOT_PITCH) / 2),\n );\n const vertical = isVertical(side);\n const line = sideLine(shape.box, side);\n const outward = side === 'bottom' || side === 'right';\n const behind = shape.clear.flatMap((box): Interval[] => {\n const [near, far] = vertical\n ? [box.y, box.y + box.h]\n : [box.x, box.x + box.w];\n if (outward ? near < line : far > line) return [];\n return vertical\n ? [[box.x - SLOT_PITCH, box.x + box.w + SLOT_PITCH]]\n : [[box.y - SLOT_PITCH, box.y + box.h + SLOT_PITCH]];\n });\n return cut([[from + inset, to - inset]], behind);\n}\n\n/** The parts of `intervals` outside every one of `cuts`. */\nfunction cut(intervals: Interval[], cuts: Interval[]): Interval[] {\n let free = intervals;\n for (const [cutStart, cutEnd] of cuts) {\n free = free\n .flatMap(([a, b]): Interval[] => [\n [a, Math.min(b, cutStart)],\n [Math.max(a, cutEnd), b],\n ])\n .filter(([low, high]) => high > low);\n }\n return free;\n}\n\n/**\n * Positions for `count` slots on the free parts of a side, in order along\n * it: each next slot goes to the part where slots would lie farthest apart,\n * and each part spaces its slots evenly.\n */\nfunction spread(end: ConnectionEnd, count: number): number[] {\n const intervals = freeIntervals(end, count);\n const counts = intervals.map(() => 0);\n const pitchWithOneMore = (index: number) =>\n (intervals[index]![1] - intervals[index]![0]) / (counts[index]! + 2);\n for (let placed = 0; placed < count && intervals.length > 0; placed++) {\n const roomiest = counts.reduce(\n (best, _, index) =>\n pitchWithOneMore(index) > pitchWithOneMore(best) ? index : best,\n 0,\n );\n counts[roomiest] = counts[roomiest]! + 1;\n }\n const alongs = intervals.flatMap(([a, b], index) =>\n Array.from(\n { length: counts[index]! },\n (_, slot) => a + ((slot + 1) / (counts[index]! + 1)) * (b - a),\n ),\n );\n const closest = Math.min(\n ...intervals.flatMap(([a, b], index) =>\n counts[index]! > 1 ? [(b - a) / (counts[index]! + 1)] : [],\n ),\n );\n if (alongs.length < count || closest < SLOT_PITCH) {\n const { shape, side } = end;\n throw new Error(\n `Diagram ${shape.kind === 'group' ? 'group' : 'node'} \"${shape.id}\" side ${side} has no room for ${count} connections ${SLOT_PITCH}px apart beside its corners and caption.`,\n );\n }\n return alongs;\n}\n\n/**\n * Both ends on the middle of the widest part of the overlap of two facing\n * sides that no label in `labels` between the sides lies on, when the\n * connection is alone on each.\n */\nfunction facingAlignment(\n from: ConnectionEnd,\n to: ConnectionEnd,\n fromFixed: Point | undefined,\n toFixed: Point | undefined,\n crowd: (end: ConnectionEnd) => number,\n labels: Box[],\n): { from: Point; to: Point } | undefined {\n if (to.side !== OPPOSITE[from.side] || (fromFixed && toFixed)) return;\n if (!fromFixed && crowd(from) !== 1) return;\n if (!toFixed && crowd(to) !== 1) return;\n const vertical = isVertical(from.side);\n const free = (end: ConnectionEnd, at: Point | undefined): Interval[] => {\n if (!at) return freeIntervals(end, 1);\n const along = vertical ? at.x : at.y;\n return [[along, along]];\n };\n const lines = [\n sideLine(from.shape.box, from.side),\n sideLine(to.shape.box, to.side),\n ];\n const [gapStart, gapEnd] = [Math.min(...lines), Math.max(...lines)];\n const inGap = labels.flatMap(({ x, y, w, h }): Interval[] => {\n const [across, acrossEnd, along, alongEnd] = vertical\n ? [y, y + h, x, x + w]\n : [x, x + w, y, y + h];\n return across < gapEnd && acrossEnd > gapStart ? [[along, alongEnd]] : [];\n });\n const widest = free(from, fromFixed)\n .flatMap(([a0, a1]) =>\n free(to, toFixed).flatMap(([b0, b1]) =>\n cut([[Math.max(a0, b0), Math.min(a1, b1)]], inGap),\n ),\n )\n .filter(([low, high]) => low <= high)\n .sort((a, b) => b[1] - b[0] - (a[1] - a[0]))[0];\n if (!widest) return;\n const along = (widest[0] + widest[1]) / 2;\n return {\n from: fromFixed ?? pointOnSide(from.shape.box, from.side, along),\n to: toFixed ?? pointOnSide(to.shape.box, to.side, along),\n };\n}\n\n/**\n * A stub is STUB long, except between two ends that face each other closer\n * than two stubs: then each stub reaches half way, so they meet in the gap.\n */\nfunction stubLength(connection: Connection, start: Point, end: Point): number {\n const { from, to } = connection;\n if (to.side !== OPPOSITE[from.side]) return LAYOUT_TOKENS.STUB;\n const gap =\n (isVertical(from.side) ? end.y - start.y : end.x - start.x) *\n (from.side === 'bottom' || from.side === 'right' ? 1 : -1);\n return gap > 0 && gap < 2 * LAYOUT_TOKENS.STUB ? gap / 2 : LAYOUT_TOKENS.STUB;\n}\n\nfunction sideKey(end: ConnectionEnd): string {\n return `${end.shape.id}\\u0000${end.side}`;\n}\n\nfunction centre(box: Box): Point {\n return { x: box.x + box.w / 2, y: box.y + box.h / 2 };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Box, DiagramGraphIR, DiagramSide } from '../../model/diagram-ir.ts';
|
|
2
|
+
import type { CellArea, GridAreas } from './areas.ts';
|
|
3
|
+
import type { GroupLayout, NodeLayout, PortLayout } from './graph-layout-result.ts';
|
|
4
|
+
/** A node or group that a connection starts or ends at. */
|
|
5
|
+
export type Shape = {
|
|
6
|
+
id: string;
|
|
7
|
+
kind: 'node' | 'junction' | 'group';
|
|
8
|
+
box: Box;
|
|
9
|
+
/** The corner radius, which slots keep clear of. */
|
|
10
|
+
radius: number;
|
|
11
|
+
ports: PortLayout[];
|
|
12
|
+
/** What a connection may not touch the shape through: its caption. */
|
|
13
|
+
clear: Box[];
|
|
14
|
+
area: CellArea;
|
|
15
|
+
};
|
|
16
|
+
export type ConnectionEnd = {
|
|
17
|
+
shape: Shape;
|
|
18
|
+
side: DiagramSide;
|
|
19
|
+
port: PortLayout | undefined;
|
|
20
|
+
};
|
|
21
|
+
/** An edge, or the leader from a note to its target: anything the router draws between two shapes. */
|
|
22
|
+
export type Connection = {
|
|
23
|
+
/** How errors name it: `edge a→b`, or `note leader "n"→"t"`. */
|
|
24
|
+
name: string;
|
|
25
|
+
/** The index of the edge in the graph, or undefined for a leader. */
|
|
26
|
+
edge: number | undefined;
|
|
27
|
+
from: ConnectionEnd;
|
|
28
|
+
to: ConnectionEnd;
|
|
29
|
+
/** The Manhattan distance between the two cell areas' centres. */
|
|
30
|
+
cellDistance: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Resolves every edge and every note leader to the shapes and sides it
|
|
34
|
+
* joins. A side hint wins over the cells; a port fixes the side; a side that
|
|
35
|
+
* declares ports takes no edge without one.
|
|
36
|
+
*/
|
|
37
|
+
export declare function graphConnections(ir: DiagramGraphIR, nodes: NodeLayout[], groups: GroupLayout[], areas: GridAreas): Connection[];
|
|
38
|
+
//# sourceMappingURL=connections.d.ts.map
|
|
@@ -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,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;CAC9B,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;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,SAAS,GACf,UAAU,EAAE,CAiHd"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { parseEndpointRef } from "../../model/endpoint-ref.js";
|
|
2
|
+
import { facingSides } 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
|
|
11
|
+
* joins. A side hint wins over the cells; a port fixes the side; a side that
|
|
12
|
+
* declares ports takes no edge without one.
|
|
13
|
+
*/
|
|
14
|
+
export function graphConnections(ir, nodes, groups, areas) {
|
|
15
|
+
const shapes = new Map();
|
|
16
|
+
for (const {
|
|
17
|
+
node,
|
|
18
|
+
box,
|
|
19
|
+
radius,
|
|
20
|
+
ports,
|
|
21
|
+
caption
|
|
22
|
+
} of nodes) {
|
|
23
|
+
shapes.set(node.id, {
|
|
24
|
+
id: node.id,
|
|
25
|
+
kind: node.kind === 'junction' ? 'junction' : 'node',
|
|
26
|
+
box,
|
|
27
|
+
radius,
|
|
28
|
+
ports,
|
|
29
|
+
clear: caption ? [caption.box] : [],
|
|
30
|
+
area: areas.nodes.get(node.id)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
for (const [index, {
|
|
34
|
+
group,
|
|
35
|
+
rect,
|
|
36
|
+
ports
|
|
37
|
+
}] of groups.entries()) {
|
|
38
|
+
shapes.set(group.id, {
|
|
39
|
+
id: group.id,
|
|
40
|
+
kind: 'group',
|
|
41
|
+
box: rect,
|
|
42
|
+
radius: LAYOUT_TOKENS.RADIUS,
|
|
43
|
+
ports,
|
|
44
|
+
clear: [],
|
|
45
|
+
area: areas.groups[index].area
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const connect = (name, edge, fromRef, toRef, hints) => {
|
|
49
|
+
const resolve = ref => {
|
|
50
|
+
const {
|
|
51
|
+
nodeId,
|
|
52
|
+
portId
|
|
53
|
+
} = parseEndpointRef(ref);
|
|
54
|
+
const shape = shapes.get(nodeId);
|
|
55
|
+
if (!shape) {
|
|
56
|
+
throw new Error(`Diagram ${name}: "${nodeId}" is not a node or a group.`);
|
|
57
|
+
}
|
|
58
|
+
if (portId === undefined) return {
|
|
59
|
+
shape,
|
|
60
|
+
port: undefined
|
|
61
|
+
};
|
|
62
|
+
const port = shape.ports.find(candidate => candidate.id === portId);
|
|
63
|
+
if (!port) {
|
|
64
|
+
throw new Error(`Diagram ${name}: "${nodeId}" has no port "${portId}".`);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
shape,
|
|
68
|
+
port
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
const from = resolve(fromRef);
|
|
72
|
+
const to = resolve(toRef);
|
|
73
|
+
const [exitByCells, entryByCells] = from.shape === to.shape ?
|
|
74
|
+
// A loop has no cell delta to read; it leaves and re-enters the right side.
|
|
75
|
+
['right', 'right'] : facingSides(from.shape.area, to.shape.area);
|
|
76
|
+
const end = ({
|
|
77
|
+
shape,
|
|
78
|
+
port
|
|
79
|
+
}, hint, byCells, role, ref) => {
|
|
80
|
+
if (port) {
|
|
81
|
+
if (hint !== undefined && hint !== port.side) {
|
|
82
|
+
throw new Error(`Diagram ${name}: ${role} ${hint} contradicts port "${ref}" on side ${port.side}.`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
shape,
|
|
86
|
+
side: port.side,
|
|
87
|
+
port
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const side = hint ?? byCells;
|
|
91
|
+
if (shape.ports.some(candidate => candidate.side === side)) {
|
|
92
|
+
throw new Error(`Diagram ${shape.kind === 'group' ? 'group' : 'node'} "${shape.id}" side ${side} declares ports; ${name} must use "${shape.id}:<port>".`);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
shape,
|
|
96
|
+
side,
|
|
97
|
+
port: undefined
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const a = from.shape.area;
|
|
101
|
+
const b = to.shape.area;
|
|
102
|
+
return {
|
|
103
|
+
name,
|
|
104
|
+
edge,
|
|
105
|
+
from: end(from, hints.exitSide, exitByCells, 'exitSide', fromRef),
|
|
106
|
+
to: end(to, hints.entrySide, entryByCells, 'entrySide', toRef),
|
|
107
|
+
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))
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
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, {})] : [])];
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=connections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.js","names":["parseEndpointRef","facingSides","LAYOUT_TOKENS","graphConnections","ir","nodes","groups","areas","shapes","Map","node","box","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","exitByCells","entryByCells","end","hint","byCells","role","side","some","a","b","exitSide","entrySide","cellDistance","Math","abs","row","rowSpan","col","colSpan","edges","map","flatMap","target"],"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 { facingSides } 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 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};\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\n * joins. A side hint wins over the cells; a port fixes the side; a side that\n * declares ports takes no edge without one.\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, 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 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 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 [exitByCells, entryByCells] =\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'] as const)\n : facingSides(from.shape.area, to.shape.area);\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 };\n }\n const side = hint ?? byCells;\n if (shape.ports.some((candidate) => candidate.side === 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 { shape, side, port: undefined };\n };\n const a = from.shape.area;\n const b = to.shape.area;\n return {\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 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 };\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"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
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,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,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;IAC3B,KAAK,
|
|
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 +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
|
|
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":[]}
|
|
@@ -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,
|
|
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)) {
|
|
@@ -82,8 +81,9 @@ function slotWidths(spans, chromeOf) {
|
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
83
|
* A labelled edge between two neighbouring cells has only the gutter between
|
|
85
|
-
* them for its label
|
|
86
|
-
*
|
|
84
|
+
* them for its label. The label must not straddle the border of a group that
|
|
85
|
+
* opens or closes there, so the middle of that gutter, outside all their
|
|
86
|
+
* chrome, must hold the label and a stub on each side of it.
|
|
87
87
|
*/
|
|
88
88
|
function labelMinimums(edges, nodes) {
|
|
89
89
|
const minimums = {
|
|
@@ -96,7 +96,7 @@ function labelMinimums(edges, nodes) {
|
|
|
96
96
|
const from = nodes.get(parseEndpointRef(edge.from).nodeId);
|
|
97
97
|
const to = nodes.get(parseEndpointRef(edge.to).nodeId);
|
|
98
98
|
if (!from || !to) continue;
|
|
99
|
-
const label = measureEdgeLabel(edge
|
|
99
|
+
const label = measureEdgeLabel(edge);
|
|
100
100
|
const side = sideBySide(from, to);
|
|
101
101
|
if (side) raise('col', side, label.w + 2 * LAYOUT_TOKENS.STUB);
|
|
102
102
|
const stacked = sideBySide(transpose(from), transpose(to));
|
|
@@ -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","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","outer","middle","Math","max","GAP","closingEdges","Map","offset","width","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 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 labelled edge between two neighbouring cells has only the gutter between\n * them for its label. The label must not straddle the border of a group that\n * opens or closes there, so the middle of that gutter, outside all their\n * chrome, must hold the label and a stub on each 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);\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":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Box, Point } from '../../model/diagram-ir.ts';
|
|
2
|
+
export type TagPosition = {
|
|
3
|
+
box: Box;
|
|
4
|
+
segment: number;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* The first free position for a tag of `size` on the route: the longest
|
|
8
|
+
* segment first, and on each segment the fractions in FRACTIONS order. A
|
|
9
|
+
* label sits LABEL_GAP beside the segment (above, then below a horizontal
|
|
10
|
+
* one; right, then left of a vertical one). A badge sits centred on it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function tagPosition(size: {
|
|
13
|
+
w: number;
|
|
14
|
+
h: number;
|
|
15
|
+
}, points: Point[], placement: 'beside' | 'on', isFree: (box: Box) => boolean): TagPosition | undefined;
|
|
16
|
+
//# 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,MAAM,MAAM,WAAW,GAAG;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAC9B,MAAM,EAAE,KAAK,EAAE,EACf,SAAS,EAAE,QAAQ,GAAG,IAAI,EAC1B,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,GAC5B,WAAW,GAAG,SAAS,CAoBzB"}
|