@zukhruf/diagram 0.3.32 → 0.3.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -52
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/compiler/diagram-compile.d.ts +3 -4
- package/dist/lib/compiler/diagram-compile.d.ts.map +1 -1
- package/dist/lib/compiler/diagram-compile.js +85 -370
- package/dist/lib/compiler/diagram-compile.js.map +1 -1
- 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 +272 -0
- package/dist/lib/layouts/graph/attach.js.map +1 -0
- package/dist/lib/layouts/graph/connections.d.ts +45 -0
- package/dist/lib/layouts/graph/connections.d.ts.map +1 -0
- package/dist/lib/layouts/graph/connections.js +138 -0
- package/dist/lib/layouts/graph/connections.js.map +1 -0
- package/dist/lib/layouts/graph/graph-layout-result.d.ts +5 -3
- 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 +41 -22
- package/dist/lib/layouts/graph/gutters.js.map +1 -1
- package/dist/lib/layouts/graph/labels.d.ts +23 -0
- package/dist/lib/layouts/graph/labels.d.ts.map +1 -0
- package/dist/lib/layouts/graph/labels.js +120 -0
- package/dist/lib/layouts/graph/labels.js.map +1 -0
- package/dist/lib/layouts/graph/layout.d.ts +1 -1
- package/dist/lib/layouts/graph/layout.d.ts.map +1 -1
- package/dist/lib/layouts/graph/layout.js +37 -12
- package/dist/lib/layouts/graph/layout.js.map +1 -1
- package/dist/lib/layouts/graph/measure.d.ts +15 -11
- package/dist/lib/layouts/graph/measure.d.ts.map +1 -1
- package/dist/lib/layouts/graph/measure.js +82 -38
- package/dist/lib/layouts/graph/measure.js.map +1 -1
- package/dist/lib/layouts/graph/min-heap.d.ts +8 -0
- package/dist/lib/layouts/graph/min-heap.d.ts.map +1 -0
- package/dist/lib/layouts/graph/min-heap.js +56 -0
- package/dist/lib/layouts/graph/min-heap.js.map +1 -0
- package/dist/lib/layouts/graph/node-content.d.ts +2 -3
- package/dist/lib/layouts/graph/node-content.d.ts.map +1 -1
- package/dist/lib/layouts/graph/node-content.js +67 -47
- package/dist/lib/layouts/graph/node-content.js.map +1 -1
- package/dist/lib/layouts/graph/path.d.ts +6 -0
- package/dist/lib/layouts/graph/path.d.ts.map +1 -0
- package/dist/lib/layouts/graph/path.js +53 -0
- package/dist/lib/layouts/graph/path.js.map +1 -0
- package/dist/lib/layouts/graph/round.d.ts +11 -0
- package/dist/lib/layouts/graph/round.d.ts.map +1 -0
- package/dist/lib/layouts/graph/round.js +181 -0
- package/dist/lib/layouts/graph/round.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 +20 -0
- package/dist/lib/layouts/graph/route.d.ts.map +1 -0
- package/dist/lib/layouts/graph/route.js +276 -0
- package/dist/lib/layouts/graph/route.js.map +1 -0
- package/dist/lib/layouts/graph/sides.d.ts +28 -0
- package/dist/lib/layouts/graph/sides.d.ts.map +1 -0
- package/dist/lib/layouts/graph/sides.js +98 -0
- package/dist/lib/layouts/graph/sides.js.map +1 -0
- package/dist/lib/layouts/timeline/timeline-layout.d.ts +2 -1
- package/dist/lib/layouts/timeline/timeline-layout.d.ts.map +1 -1
- package/dist/lib/layouts/timeline/timeline-layout.js +9 -4
- package/dist/lib/layouts/timeline/timeline-layout.js.map +1 -1
- package/dist/lib/model/diagram-ir.d.ts +19 -156
- package/dist/lib/model/diagram-ir.d.ts.map +1 -1
- package/dist/lib/model/diagram-ir.js.map +1 -1
- package/dist/lib/model/validate-graph.d.ts +7 -0
- package/dist/lib/model/validate-graph.d.ts.map +1 -0
- package/dist/lib/model/validate-graph.js +54 -0
- package/dist/lib/model/validate-graph.js.map +1 -0
- package/dist/lib/react/Diagram.d.ts +3 -9
- package/dist/lib/react/Diagram.d.ts.map +1 -1
- package/dist/lib/react/Diagram.js +1 -13
- package/dist/lib/react/Diagram.js.map +1 -1
- package/dist/lib/react/diagram-types.d.ts +17 -46
- package/dist/lib/react/diagram-types.d.ts.map +1 -1
- package/dist/lib/react/diagram-types.js.map +1 -1
- package/dist/lib/renderers/canvas/graph-canvas.d.ts +2 -5
- package/dist/lib/renderers/canvas/graph-canvas.d.ts.map +1 -1
- package/dist/lib/renderers/canvas/graph-canvas.js +68 -43
- package/dist/lib/renderers/canvas/graph-canvas.js.map +1 -1
- package/dist/lib/renderers/html/graph-html.d.ts +2 -3
- package/dist/lib/renderers/html/graph-html.d.ts.map +1 -1
- package/dist/lib/renderers/html/graph-html.js +298 -289
- package/dist/lib/renderers/html/graph-html.js.map +1 -1
- package/dist/lib/renderers/shared/graph-parts.d.ts +3 -1
- package/dist/lib/renderers/shared/graph-parts.d.ts.map +1 -1
- package/dist/lib/renderers/shared/graph-parts.js +350 -418
- package/dist/lib/renderers/shared/graph-parts.js.map +1 -1
- package/dist/lib/renderers/shared/node-parts.d.ts +5 -6
- package/dist/lib/renderers/shared/node-parts.d.ts.map +1 -1
- package/dist/lib/renderers/shared/node-parts.js +112 -190
- package/dist/lib/renderers/shared/node-parts.js.map +1 -1
- package/dist/lib/renderers/shared/run-spans.js +2 -2
- package/dist/lib/renderers/shared/run-spans.js.map +1 -1
- package/dist/lib/renderers/shared/text-runs.d.ts +0 -6
- package/dist/lib/renderers/shared/text-runs.d.ts.map +1 -1
- package/dist/lib/renderers/shared/text-runs.js +0 -12
- package/dist/lib/renderers/shared/text-runs.js.map +1 -1
- package/dist/lib/renderers/svg/graph-svg.d.ts +1 -3
- package/dist/lib/renderers/svg/graph-svg.d.ts.map +1 -1
- package/dist/lib/renderers/svg/graph-svg.js +66 -80
- package/dist/lib/renderers/svg/graph-svg.js.map +1 -1
- package/dist/lib/renderers/svg/timeline-svg.d.ts.map +1 -1
- package/dist/lib/renderers/svg/timeline-svg.js +7 -5
- package/dist/lib/renderers/svg/timeline-svg.js.map +1 -1
- package/package.json +1 -1
- package/dist/lib/layouts/graph/graph-layout.d.ts +0 -154
- package/dist/lib/layouts/graph/graph-layout.d.ts.map +0 -1
- package/dist/lib/layouts/graph/graph-layout.js +0 -2340
- package/dist/lib/layouts/graph/graph-layout.js.map +0 -1
- package/dist/lib/layouts/graph/legacy-graph-layout.d.ts +0 -6
- package/dist/lib/layouts/graph/legacy-graph-layout.d.ts.map +0 -1
- package/dist/lib/layouts/graph/legacy-graph-layout.js +0 -542
- package/dist/lib/layouts/graph/legacy-graph-layout.js.map +0 -1
- package/dist/lib/layouts/text-metrics.d.ts +0 -7
- package/dist/lib/layouts/text-metrics.d.ts.map +0 -1
- package/dist/lib/layouts/text-metrics.js +0 -40
- package/dist/lib/layouts/text-metrics.js.map +0 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { attachConnections } from "./attach.js";
|
|
2
|
+
import { graphConnections } from "./connections.js";
|
|
3
|
+
import { tagPosition } from "./labels.js";
|
|
4
|
+
import { measureEdgeTags } from "./measure.js";
|
|
5
|
+
import { 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
|
+
/** The routes, before the layout rounds them and draws their paths. */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Routes every edge and note leader orthogonally, nearest cells first, and
|
|
19
|
+
* places each edge's label and badge before the next edge routes around
|
|
20
|
+
* them. A connection with no route takes its next choice of sides, and
|
|
21
|
+
* everything routes again. It throws when a connection has no choice left,
|
|
22
|
+
* or a label has no room; it never draws a diagonal or straight line
|
|
23
|
+
* through what is in the way.
|
|
24
|
+
*/
|
|
25
|
+
export function routeGraph(ir, nodes, groups, areas, /** How far beyond the bounding box of its two shapes a route first searches. */
|
|
26
|
+
searchMargin) {
|
|
27
|
+
const candidates = graphConnections(ir, nodes, groups, areas);
|
|
28
|
+
const choices = candidates.map(() => 0);
|
|
29
|
+
for (;;) {
|
|
30
|
+
const routed = routeConnections(ir, candidates.map((options, index) => options[choices[index]]), nodes, groups, searchMargin);
|
|
31
|
+
if (!('unrouted' in routed)) return routed;
|
|
32
|
+
const options = candidates[routed.unrouted];
|
|
33
|
+
const next = choices[routed.unrouted] + 1;
|
|
34
|
+
if (next === options.length) throw noRoute(options[0]);
|
|
35
|
+
choices[routed.unrouted] = next;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Routes one choice of sides for every connection, or names the first
|
|
41
|
+
* connection, in routing order, that has no route.
|
|
42
|
+
*/
|
|
43
|
+
function routeConnections(ir, connections, nodes, groups, searchMargin) {
|
|
44
|
+
if (connections.length === 0) return {
|
|
45
|
+
edges: [],
|
|
46
|
+
leaders: []
|
|
47
|
+
};
|
|
48
|
+
const groupLabels = groups.flatMap(({
|
|
49
|
+
group,
|
|
50
|
+
label
|
|
51
|
+
}) => label.map(run => ({
|
|
52
|
+
group: group.id,
|
|
53
|
+
box: inflate(run.bounds, LAYOUT_TOKENS.LABEL_GAP)
|
|
54
|
+
})));
|
|
55
|
+
const attachments = attachConnections(connections, groupLabels);
|
|
56
|
+
const content = union([...nodes.map(nodeLayout => nodeLayout.visual), ...groups.map(groupLayout => groupLayout.rect)]);
|
|
57
|
+
const bounds = inflate(content, ROUTING_MARGIN);
|
|
58
|
+
const solid = nodes.filter(({
|
|
59
|
+
box
|
|
60
|
+
}) => box.w > 0 && box.h > 0);
|
|
61
|
+
const routed = [];
|
|
62
|
+
const tags = [];
|
|
63
|
+
const paths = new Map();
|
|
64
|
+
const labels = new Map();
|
|
65
|
+
const order = connections.map((connection, index) => ({
|
|
66
|
+
connection,
|
|
67
|
+
index
|
|
68
|
+
})).sort((a, b) => Number(a.connection.edge === undefined) - Number(b.connection.edge === undefined) || a.connection.cellDistance - b.connection.cellDistance || a.index - b.index);
|
|
69
|
+
for (const {
|
|
70
|
+
connection,
|
|
71
|
+
index
|
|
72
|
+
} of order) {
|
|
73
|
+
const {
|
|
74
|
+
from,
|
|
75
|
+
to
|
|
76
|
+
} = connection;
|
|
77
|
+
const ends = new Set([from.shape.id, to.shape.id]);
|
|
78
|
+
const junctions = new Set([from, to].filter(end => end.shape.kind === 'junction').map(end => end.shape.id));
|
|
79
|
+
const walls = [...solid.flatMap(({
|
|
80
|
+
node,
|
|
81
|
+
box,
|
|
82
|
+
visual,
|
|
83
|
+
frames,
|
|
84
|
+
caption
|
|
85
|
+
}) => {
|
|
86
|
+
if (!ends.has(node.id)) return [inflate(visual, NODE_CLEARANCE)];
|
|
87
|
+
if (node.kind === 'junction') return [];
|
|
88
|
+
// Its own parts, not their bounding box: a caption wider than the frame leaves room beside the frame.
|
|
89
|
+
return [box, ...frames, ...(caption ? [caption.box] : [])];
|
|
90
|
+
}), ...groupLabels.flatMap(({
|
|
91
|
+
group,
|
|
92
|
+
box
|
|
93
|
+
}) => ends.has(group) ? [] : [box]), ...tags.map(tag => inflate(tag, LAYOUT_TOKENS.LABEL_GAP))];
|
|
94
|
+
const runs = routed.filter(route => !overlaps(route.junctions, junctions)).flatMap(route => segments(route.points));
|
|
95
|
+
const {
|
|
96
|
+
start,
|
|
97
|
+
startStub,
|
|
98
|
+
end,
|
|
99
|
+
endStub
|
|
100
|
+
} = attachments[index];
|
|
101
|
+
const stubs = [[start, startStub], [endStub, end]];
|
|
102
|
+
const stubsClear = stubs.every(([a, b]) => walls.every(wall => !crosses(a, b, wall)));
|
|
103
|
+
const search = window => searchRoute({
|
|
104
|
+
window,
|
|
105
|
+
lines: {
|
|
106
|
+
x: [startStub.x, endStub.x],
|
|
107
|
+
y: [startStub.y, endStub.y]
|
|
108
|
+
},
|
|
109
|
+
walls,
|
|
110
|
+
runs,
|
|
111
|
+
borders: groups.flatMap(({
|
|
112
|
+
rect,
|
|
113
|
+
band
|
|
114
|
+
}) => band ? [rect, band] : [rect]),
|
|
115
|
+
start: startStub,
|
|
116
|
+
startHeading: OUTWARD[from.side],
|
|
117
|
+
goal: endStub,
|
|
118
|
+
goalHeading: OUTWARD[OPPOSITE[to.side]]
|
|
119
|
+
});
|
|
120
|
+
const near = intersection(inflate(union([from.shape.box, to.shape.box, spanOf(startStub, endStub)]), searchMargin), bounds);
|
|
121
|
+
const found = stubsClear ? search(near) ?? search(bounds) : undefined;
|
|
122
|
+
if (!found) return {
|
|
123
|
+
unrouted: index
|
|
124
|
+
};
|
|
125
|
+
const points = simplifyPath([start, ...found, end]);
|
|
126
|
+
paths.set(connection, points);
|
|
127
|
+
if (connection.edge !== undefined) {
|
|
128
|
+
const edge = ir.edges[connection.edge];
|
|
129
|
+
const isFree = (box, ownSegments) => solid.every(({
|
|
130
|
+
visual
|
|
131
|
+
}) => !intersects(box, visual)) && groups.every(({
|
|
132
|
+
rect,
|
|
133
|
+
band,
|
|
134
|
+
label
|
|
135
|
+
}) => !(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));
|
|
136
|
+
const measured = measureEdgeTags(edge);
|
|
137
|
+
const {
|
|
138
|
+
label,
|
|
139
|
+
badge
|
|
140
|
+
} = measured;
|
|
141
|
+
const placed = {};
|
|
142
|
+
if (label || badge) {
|
|
143
|
+
const boxes = tagPosition(measured, points, (candidate, kind) => isFree(candidate, kind === 'label'));
|
|
144
|
+
if (!boxes) {
|
|
145
|
+
const named = [];
|
|
146
|
+
if (label) {
|
|
147
|
+
named.push(`label "${edge.label.join(' / ')}" (${Math.ceil(label.w)}×${Math.ceil(label.h)})`);
|
|
148
|
+
}
|
|
149
|
+
if (badge) {
|
|
150
|
+
named.push(`badge "${(edge.badgeLabel ?? []).join(' / ')}" (${Math.ceil(badge.w)}×${Math.ceil(badge.h)})`);
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Diagram ${connection.name}: ${named.join(' and ')} ${named.length > 1 ? 'have' : 'has'} no free position ${badge ? 'on' : 'beside'} its route.`);
|
|
153
|
+
}
|
|
154
|
+
if (label && boxes.label) {
|
|
155
|
+
placed.label = {
|
|
156
|
+
box: boxes.label,
|
|
157
|
+
text: label.place(boxes.label)
|
|
158
|
+
};
|
|
159
|
+
tags.push(boxes.label);
|
|
160
|
+
}
|
|
161
|
+
if (badge && boxes.badge) {
|
|
162
|
+
placed.badge = {
|
|
163
|
+
box: boxes.badge,
|
|
164
|
+
...badge.place(boxes.badge)
|
|
165
|
+
};
|
|
166
|
+
tags.push(boxes.badge);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
labels.set(connection, placed);
|
|
170
|
+
}
|
|
171
|
+
routed.push({
|
|
172
|
+
points,
|
|
173
|
+
junctions
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const edges = [];
|
|
177
|
+
const leaders = [];
|
|
178
|
+
for (const connection of connections) {
|
|
179
|
+
const points = paths.get(connection);
|
|
180
|
+
if (connection.edge === undefined) {
|
|
181
|
+
leaders.push({
|
|
182
|
+
note: connection.from.shape.id,
|
|
183
|
+
target: connection.to.shape.id,
|
|
184
|
+
points
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
edges.push({
|
|
189
|
+
edge: ir.edges[connection.edge],
|
|
190
|
+
exitSide: connection.from.side,
|
|
191
|
+
entrySide: connection.to.side,
|
|
192
|
+
points,
|
|
193
|
+
...labels.get(connection)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
edges,
|
|
198
|
+
leaders
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function noRoute({
|
|
202
|
+
name,
|
|
203
|
+
edge,
|
|
204
|
+
from,
|
|
205
|
+
to
|
|
206
|
+
}) {
|
|
207
|
+
const advice = edge === undefined ? 'Move the note or its target.' : 'Change exitSide/entrySide or move a node.';
|
|
208
|
+
return new Error(`Diagram ${name}: no orthogonal route from ${endName(from)} to ${endName(to)} avoids the nodes and labels. ${advice}`);
|
|
209
|
+
}
|
|
210
|
+
function endName({
|
|
211
|
+
shape,
|
|
212
|
+
side,
|
|
213
|
+
port
|
|
214
|
+
}) {
|
|
215
|
+
if (port) return `port "${shape.id}:${port.id}"`;
|
|
216
|
+
if (shape.kind === 'junction') return `the centre of "${shape.id}"`;
|
|
217
|
+
return `${side} of "${shape.id}"`;
|
|
218
|
+
}
|
|
219
|
+
function segments(points) {
|
|
220
|
+
return points.slice(1).map((point, index) => [points[index], point]);
|
|
221
|
+
}
|
|
222
|
+
function overlaps(a, b) {
|
|
223
|
+
for (const id of a) if (b.has(id)) return true;
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
function spanOf(a, b) {
|
|
227
|
+
return {
|
|
228
|
+
x: Math.min(a.x, b.x),
|
|
229
|
+
y: Math.min(a.y, b.y),
|
|
230
|
+
w: Math.abs(a.x - b.x),
|
|
231
|
+
h: Math.abs(a.y - b.y)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Whether the segment enters the box's interior, not only its border. */
|
|
236
|
+
function crosses(a, b, box) {
|
|
237
|
+
const span = spanOf(a, b);
|
|
238
|
+
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;
|
|
239
|
+
}
|
|
240
|
+
function intersects(a, b) {
|
|
241
|
+
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;
|
|
242
|
+
}
|
|
243
|
+
function contains(outer, inner) {
|
|
244
|
+
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;
|
|
245
|
+
}
|
|
246
|
+
function inflate(box, by) {
|
|
247
|
+
return {
|
|
248
|
+
x: box.x - by,
|
|
249
|
+
y: box.y - by,
|
|
250
|
+
w: box.w + 2 * by,
|
|
251
|
+
h: box.h + 2 * by
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function intersection(a, b) {
|
|
255
|
+
const x = Math.max(a.x, b.x);
|
|
256
|
+
const y = Math.max(a.y, b.y);
|
|
257
|
+
return {
|
|
258
|
+
x,
|
|
259
|
+
y,
|
|
260
|
+
w: Math.min(a.x + a.w, b.x + b.w) - x,
|
|
261
|
+
h: Math.min(a.y + a.h, b.y + b.h) - y
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function union(boxes) {
|
|
265
|
+
const left = Math.min(...boxes.map(box => box.x));
|
|
266
|
+
const top = Math.min(...boxes.map(box => box.y));
|
|
267
|
+
const right = Math.max(...boxes.map(box => box.x + box.w));
|
|
268
|
+
const bottom = Math.max(...boxes.map(box => box.y + box.h));
|
|
269
|
+
return {
|
|
270
|
+
x: left,
|
|
271
|
+
y: top,
|
|
272
|
+
w: right - left,
|
|
273
|
+
h: bottom - top
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.js","names":["attachConnections","graphConnections","tagPosition","measureEdgeTags","simplifyPath","searchRoute","OPPOSITE","OUTWARD","LAYOUT_TOKENS","ROUTING_MARGIN","NODE_CLEARANCE","routeGraph","ir","nodes","groups","areas","searchMargin","candidates","choices","map","routed","routeConnections","options","index","unrouted","next","length","noRoute","connections","edges","leaders","groupLabels","flatMap","group","label","run","id","box","inflate","bounds","LABEL_GAP","attachments","content","union","nodeLayout","visual","groupLayout","rect","solid","filter","w","h","tags","paths","Map","labels","order","connection","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","set","isFree","ownSegments","intersects","contains","measured","badge","placed","boxes","candidate","named","push","join","Math","ceil","badgeLabel","Error","name","text","place","get","note","target","exitSide","entrySide","advice","endName","port","slice","point","min","abs","span","outer","inner","by","max","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 { measureEdgeTags } from './measure.ts';\nimport { 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\n/** The routes, before the layout rounds them and draws their paths. */\nexport type RoutedGraph = {\n edges: Omit<EdgeLayout, 'd'>[];\n leaders: Omit<LeaderLayout, 'd'>[];\n};\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. A connection with no route takes its next choice of sides, and\n * everything routes again. It throws when a connection has no choice left,\n * or a label has no room; it never draws a diagonal or straight line\n * 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): RoutedGraph {\n const candidates = graphConnections(ir, nodes, groups, areas);\n const choices = candidates.map(() => 0);\n for (;;) {\n const routed = routeConnections(\n ir,\n candidates.map((options, index) => options[choices[index]!]!),\n nodes,\n groups,\n searchMargin,\n );\n if (!('unrouted' in routed)) return routed;\n const options = candidates[routed.unrouted]!;\n const next = choices[routed.unrouted]! + 1;\n if (next === options.length) throw noRoute(options[0]!);\n choices[routed.unrouted] = next;\n }\n}\n\n/**\n * Routes one choice of sides for every connection, or names the first\n * connection, in routing order, that has no route.\n */\nfunction routeConnections(\n ir: DiagramGraphIR,\n connections: Connection[],\n nodes: NodeLayout[],\n groups: GroupLayout[],\n searchMargin: number,\n): RoutedGraph | { unrouted: number } {\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) return { unrouted: index };\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 measured = measureEdgeTags(edge);\n const { label, badge } = measured;\n const placed: Pick<EdgeLayout, 'label' | 'badge'> = {};\n if (label || badge) {\n const boxes = tagPosition(measured, points, (candidate, kind) =>\n isFree(candidate, kind === 'label'),\n );\n if (!boxes) {\n const named: string[] = [];\n if (label) {\n named.push(\n `label \"${edge.label!.join(' / ')}\" (${Math.ceil(label.w)}×${Math.ceil(label.h)})`,\n );\n }\n if (badge) {\n named.push(\n `badge \"${(edge.badgeLabel ?? []).join(' / ')}\" (${Math.ceil(badge.w)}×${Math.ceil(badge.h)})`,\n );\n }\n throw new Error(\n `Diagram ${connection.name}: ${named.join(' and ')} ${named.length > 1 ? 'have' : 'has'} no free position ${badge ? 'on' : 'beside'} its route.`,\n );\n }\n if (label && boxes.label) {\n placed.label = { box: boxes.label, text: label.place(boxes.label) };\n tags.push(boxes.label);\n }\n if (badge && boxes.badge) {\n placed.badge = { box: boxes.badge, ...badge.place(boxes.badge) };\n tags.push(boxes.badge);\n }\n }\n labels.set(connection, placed);\n }\n routed.push({ points, junctions });\n }\n\n const edges: RoutedGraph['edges'] = [];\n const leaders: RoutedGraph['leaders'] = [];\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 });\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 ...labels.get(connection),\n });\n }\n return { edges, leaders };\n}\n\nfunction noRoute({ name, edge, from, to }: Connection): Error {\n const advice =\n edge === undefined\n ? 'Move the note or its target.'\n : 'Change exitSide/entrySide or move a node.';\n return new Error(\n `Diagram ${name}: no orthogonal route from ${endName(from)} to ${endName(to)} avoids the nodes and labels. ${advice}`,\n );\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,28 @@
|
|
|
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
|
+
/** The side a connection leaves by, and the side it enters by. */
|
|
9
|
+
export type SidePair = [DiagramSide, DiagramSide];
|
|
10
|
+
/**
|
|
11
|
+
* The pairs of sides a connection may leave and enter by, read from the
|
|
12
|
+
* cells, in order of preference. First the facing pair: areas that share
|
|
13
|
+
* rows face left and right, areas that share columns face top and bottom,
|
|
14
|
+
* and otherwise the axis with the larger cell delta wins, the vertical one
|
|
15
|
+
* on a tie. Then the facing pair on the other axis, then the first pair's
|
|
16
|
+
* exit with the other pair's entry, then the other pair's exit with the
|
|
17
|
+
* first pair's entry.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sidePairs(from: CellArea, to: CellArea): [SidePair, ...SidePair[]];
|
|
20
|
+
/** The coordinate of the side's line: x for left and right, y for top and bottom. */
|
|
21
|
+
export declare function sideLine(box: Box, side: DiagramSide): number;
|
|
22
|
+
/** Where the side runs along its line: [start, end] in x for top and bottom, in y for left and right. */
|
|
23
|
+
export declare function sideSpan(box: Box, side: DiagramSide): [number, number];
|
|
24
|
+
/** The point on `side` of `box` at `along` on the side's span. */
|
|
25
|
+
export declare function pointOnSide(box: Box, side: DiagramSide, along: number): Point;
|
|
26
|
+
/** The point `distance` beyond `point` out of `side`. */
|
|
27
|
+
export declare function beyond(point: Point, side: DiagramSide, distance: number): Point;
|
|
28
|
+
//# 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,kEAAkE;AAClE,MAAM,MAAM,QAAQ,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAElD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,QAAQ,EACd,EAAE,EAAE,QAAQ,GACX,CAAC,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAAC,CAe3B;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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export const OPPOSITE = {
|
|
2
|
+
top: 'bottom',
|
|
3
|
+
bottom: 'top',
|
|
4
|
+
left: 'right',
|
|
5
|
+
right: 'left'
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/** The heading of a line that leaves a box through `side`. */
|
|
9
|
+
export const OUTWARD = {
|
|
10
|
+
right: 0,
|
|
11
|
+
left: 1,
|
|
12
|
+
bottom: 2,
|
|
13
|
+
top: 3
|
|
14
|
+
};
|
|
15
|
+
export function isVertical(side) {
|
|
16
|
+
return side === 'top' || side === 'bottom';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The side a connection leaves by, and the side it enters by. */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The pairs of sides a connection may leave and enter by, read from the
|
|
23
|
+
* cells, in order of preference. First the facing pair: areas that share
|
|
24
|
+
* rows face left and right, areas that share columns face top and bottom,
|
|
25
|
+
* and otherwise the axis with the larger cell delta wins, the vertical one
|
|
26
|
+
* on a tie. Then the facing pair on the other axis, then the first pair's
|
|
27
|
+
* exit with the other pair's entry, then the other pair's exit with the
|
|
28
|
+
* first pair's entry.
|
|
29
|
+
*/
|
|
30
|
+
export function sidePairs(from, to) {
|
|
31
|
+
const shareRows = Math.max(from.row, to.row) < Math.min(from.row + from.rowSpan, to.row + to.rowSpan);
|
|
32
|
+
const shareCols = Math.max(from.col, to.col) < Math.min(from.col + from.colSpan, to.col + to.colSpan);
|
|
33
|
+
const dr = to.row + to.rowSpan / 2 - (from.row + from.rowSpan / 2);
|
|
34
|
+
const dc = to.col + to.colSpan / 2 - (from.col + from.colSpan / 2);
|
|
35
|
+
const across = dc > 0 ? ['right', 'left'] : ['left', 'right'];
|
|
36
|
+
const down = dr >= 0 ? ['bottom', 'top'] : ['top', 'bottom'];
|
|
37
|
+
const horizontal = shareRows !== shareCols ? shareRows : Math.abs(dc) > Math.abs(dr);
|
|
38
|
+
const [first, other] = horizontal ? [across, down] : [down, across];
|
|
39
|
+
return [first, other, [first[0], other[1]], [other[0], first[1]]];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The coordinate of the side's line: x for left and right, y for top and bottom. */
|
|
43
|
+
export function sideLine(box, side) {
|
|
44
|
+
switch (side) {
|
|
45
|
+
case 'top':
|
|
46
|
+
return box.y;
|
|
47
|
+
case 'bottom':
|
|
48
|
+
return box.y + box.h;
|
|
49
|
+
case 'left':
|
|
50
|
+
return box.x;
|
|
51
|
+
case 'right':
|
|
52
|
+
return box.x + box.w;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Where the side runs along its line: [start, end] in x for top and bottom, in y for left and right. */
|
|
57
|
+
export function sideSpan(box, side) {
|
|
58
|
+
return isVertical(side) ? [box.x, box.x + box.w] : [box.y, box.y + box.h];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The point on `side` of `box` at `along` on the side's span. */
|
|
62
|
+
export function pointOnSide(box, side, along) {
|
|
63
|
+
const line = sideLine(box, side);
|
|
64
|
+
return isVertical(side) ? {
|
|
65
|
+
x: along,
|
|
66
|
+
y: line
|
|
67
|
+
} : {
|
|
68
|
+
x: line,
|
|
69
|
+
y: along
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The point `distance` beyond `point` out of `side`. */
|
|
74
|
+
export function beyond(point, side, distance) {
|
|
75
|
+
switch (side) {
|
|
76
|
+
case 'top':
|
|
77
|
+
return {
|
|
78
|
+
x: point.x,
|
|
79
|
+
y: point.y - distance
|
|
80
|
+
};
|
|
81
|
+
case 'bottom':
|
|
82
|
+
return {
|
|
83
|
+
x: point.x,
|
|
84
|
+
y: point.y + distance
|
|
85
|
+
};
|
|
86
|
+
case 'left':
|
|
87
|
+
return {
|
|
88
|
+
x: point.x - distance,
|
|
89
|
+
y: point.y
|
|
90
|
+
};
|
|
91
|
+
case 'right':
|
|
92
|
+
return {
|
|
93
|
+
x: point.x + distance,
|
|
94
|
+
y: point.y
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=sides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sides.js","names":["OPPOSITE","top","bottom","left","right","OUTWARD","isVertical","side","sidePairs","from","to","shareRows","Math","max","row","min","rowSpan","shareCols","col","colSpan","dr","dc","across","down","horizontal","abs","first","other","sideLine","box","y","h","x","w","sideSpan","pointOnSide","along","line","beyond","point","distance"],"sources":["../../../../src/lib/layouts/graph/sides.ts"],"sourcesContent":["import type { Box, DiagramSide, Point } from '../../model/diagram-ir.ts';\nimport type { CellArea } from './areas.ts';\nimport type { Heading } from './route-search.ts';\n\nexport const OPPOSITE: Record<DiagramSide, DiagramSide> = {\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n};\n\n/** The heading of a line that leaves a box through `side`. */\nexport const OUTWARD: Record<DiagramSide, Heading> = {\n right: 0,\n left: 1,\n bottom: 2,\n top: 3,\n};\n\nexport function isVertical(side: DiagramSide): boolean {\n return side === 'top' || side === 'bottom';\n}\n\n/** The side a connection leaves by, and the side it enters by. */\nexport type SidePair = [DiagramSide, DiagramSide];\n\n/**\n * The pairs of sides a connection may leave and enter by, read from the\n * cells, in order of preference. First the facing pair: areas that share\n * rows face left and right, areas that share columns face top and bottom,\n * and otherwise the axis with the larger cell delta wins, the vertical one\n * on a tie. Then the facing pair on the other axis, then the first pair's\n * exit with the other pair's entry, then the other pair's exit with the\n * first pair's entry.\n */\nexport function sidePairs(\n from: CellArea,\n to: CellArea,\n): [SidePair, ...SidePair[]] {\n const shareRows =\n Math.max(from.row, to.row) <\n Math.min(from.row + from.rowSpan, to.row + to.rowSpan);\n const shareCols =\n Math.max(from.col, to.col) <\n Math.min(from.col + from.colSpan, to.col + to.colSpan);\n const dr = to.row + to.rowSpan / 2 - (from.row + from.rowSpan / 2);\n const dc = to.col + to.colSpan / 2 - (from.col + from.colSpan / 2);\n const across: SidePair = dc > 0 ? ['right', 'left'] : ['left', 'right'];\n const down: SidePair = dr >= 0 ? ['bottom', 'top'] : ['top', 'bottom'];\n const horizontal =\n shareRows !== shareCols ? shareRows : Math.abs(dc) > Math.abs(dr);\n const [first, other] = horizontal ? [across, down] : [down, across];\n return [first, other, [first[0], other[1]], [other[0], first[1]]];\n}\n\n/** The coordinate of the side's line: x for left and right, y for top and bottom. */\nexport function sideLine(box: Box, side: DiagramSide): number {\n switch (side) {\n case 'top':\n return box.y;\n case 'bottom':\n return box.y + box.h;\n case 'left':\n return box.x;\n case 'right':\n return box.x + box.w;\n }\n}\n\n/** Where the side runs along its line: [start, end] in x for top and bottom, in y for left and right. */\nexport function sideSpan(box: Box, side: DiagramSide): [number, number] {\n return isVertical(side) ? [box.x, box.x + box.w] : [box.y, box.y + box.h];\n}\n\n/** The point on `side` of `box` at `along` on the side's span. */\nexport function pointOnSide(box: Box, side: DiagramSide, along: number): Point {\n const line = sideLine(box, side);\n return isVertical(side) ? { x: along, y: line } : { x: line, y: along };\n}\n\n/** The point `distance` beyond `point` out of `side`. */\nexport function beyond(\n point: Point,\n side: DiagramSide,\n distance: number,\n): Point {\n switch (side) {\n case 'top':\n return { x: point.x, y: point.y - distance };\n case 'bottom':\n return { x: point.x, y: point.y + distance };\n case 'left':\n return { x: point.x - distance, y: point.y };\n case 'right':\n return { x: point.x + distance, y: point.y };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DiagramSignal, DiagramSignalBox, DiagramTimelineSpec } from '../../model/diagram-ir.ts';
|
|
2
|
+
import type { Font } from '../text/font.ts';
|
|
2
3
|
export type { DiagramFrameElement, DiagramLane, DiagramSignal, DiagramSignalBox, } from '../../model/diagram-ir.ts';
|
|
3
4
|
export type TimelineLayoutSpec = DiagramTimelineSpec & {
|
|
4
5
|
title?: string[];
|
|
@@ -16,7 +17,7 @@ export declare const FRAME_HEADER_H = 22;
|
|
|
16
17
|
export declare const FRAME_PAD = 12;
|
|
17
18
|
export declare const BOTTOM_ACTOR_GAP = 50;
|
|
18
19
|
export declare const DEFAULT_BOXED_STUB = 12;
|
|
19
|
-
export declare const
|
|
20
|
+
export declare const SIGNAL_LABEL_FONT: Font;
|
|
20
21
|
export declare const SIGNAL_LABEL_PAD_X = 14;
|
|
21
22
|
export declare const SIGNAL_LABEL_PAD_Y = 10;
|
|
22
23
|
export declare const SIGNAL_LABEL_LINE_H = 18;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-layout.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/timeline/timeline-layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"timeline-layout.d.ts","sourceRoot":"","sources":["../../../../src/lib/layouts/timeline/timeline-layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fontAscent, measureText } from "../text/measure-text.js";
|
|
2
2
|
export const SEQ_W = 1000;
|
|
3
3
|
export const ACTOR_W = 190;
|
|
4
4
|
export const ACTOR_H = 72;
|
|
@@ -12,7 +12,11 @@ export const FRAME_HEADER_H = 22;
|
|
|
12
12
|
export const FRAME_PAD = 12;
|
|
13
13
|
export const BOTTOM_ACTOR_GAP = 50;
|
|
14
14
|
export const DEFAULT_BOXED_STUB = 12;
|
|
15
|
-
export const
|
|
15
|
+
export const SIGNAL_LABEL_FONT = {
|
|
16
|
+
face: 'mono',
|
|
17
|
+
size: 12,
|
|
18
|
+
weight: 400
|
|
19
|
+
};
|
|
16
20
|
export const SIGNAL_LABEL_PAD_X = 14;
|
|
17
21
|
export const SIGNAL_LABEL_PAD_Y = 10;
|
|
18
22
|
export const SIGNAL_LABEL_LINE_H = 18;
|
|
@@ -62,8 +66,9 @@ export function buildTimelineSegments(totalSignals, hasLoop, frameMsgs) {
|
|
|
62
66
|
return segments;
|
|
63
67
|
}
|
|
64
68
|
export function measureSignalLabel(label) {
|
|
69
|
+
const widestLineW = Math.max(0, ...label.map(line => measureText(line, SIGNAL_LABEL_FONT)));
|
|
65
70
|
return {
|
|
66
|
-
boxW: Math.ceil(
|
|
71
|
+
boxW: Math.ceil(widestLineW) + SIGNAL_LABEL_PAD_X * 2,
|
|
67
72
|
boxH: label.length * SIGNAL_LABEL_LINE_H + SIGNAL_LABEL_PAD_Y * 2
|
|
68
73
|
};
|
|
69
74
|
}
|
|
@@ -71,7 +76,7 @@ export function measureSignalLabel(label) {
|
|
|
71
76
|
/** Distance from a signal's row up to the top of its label or label box. */
|
|
72
77
|
export function signalLabelHeadroom(signal) {
|
|
73
78
|
if (signal.boxed) return measureSignalLabel(signal.label).boxH / 2;
|
|
74
|
-
return SIGNAL_LABEL_BASELINE_GAP + (signal.label.length - 1) * SIGNAL_LABEL_LINE_STEP +
|
|
79
|
+
return SIGNAL_LABEL_BASELINE_GAP + (signal.label.length - 1) * SIGNAL_LABEL_LINE_STEP + fontAscent(SIGNAL_LABEL_FONT);
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
/** Distance from a signal's row down to the bottom of its label box. */
|