@tensor_1/react 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/components/RelationshipGraph.d.ts +81 -3
- package/dist/components/RelationshipGraph.js +81 -57
- package/dist/components/RelationshipGraphLayered.d.ts +8 -0
- package/dist/components/RelationshipGraphLayered.js +420 -0
- package/dist/components/RelationshipGraphList.d.ts +16 -0
- package/dist/components/RelationshipGraphList.js +33 -0
- package/dist/index.cjs +1130 -235
- package/dist/index.d.cts +187 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -2
- package/dist/relationship-graph-model.d.ts +112 -1
- package/dist/relationship-graph-model.js +420 -1
- package/package.json +1 -1
|
@@ -60,9 +60,428 @@ function traverseRelationshipNode(nodes, currentId, direction) {
|
|
|
60
60
|
const current = Math.max(0, nodes.findIndex((node) => node.id === currentId));
|
|
61
61
|
return nodes[(current + direction + nodes.length) % nodes.length]?.id;
|
|
62
62
|
}
|
|
63
|
+
const LAYERED_GEOMETRY = {
|
|
64
|
+
standard: { nodeWidth: 176, nodeHeight: 76, columnGap: 96, rowGap: 12 },
|
|
65
|
+
compact: { nodeWidth: 160, nodeHeight: 60, columnGap: 80, rowGap: 8 }
|
|
66
|
+
};
|
|
67
|
+
const LAYERED_PADDING = 16;
|
|
68
|
+
const LAYERED_HEADER = 28;
|
|
69
|
+
const GROUP_HEADER = 28;
|
|
70
|
+
const AUTO_LABEL_MAX_FANOUT = 4;
|
|
71
|
+
const LABEL_CHAR_WIDTH = 5.6;
|
|
72
|
+
const LABEL_MAX_LINES = 2;
|
|
73
|
+
function wrapEdgeLabel(label, width) {
|
|
74
|
+
const max = Math.max(4, Math.floor(width / LABEL_CHAR_WIDTH));
|
|
75
|
+
const clip = (word) => word.length > max ? `${word.slice(0, max - 1)}\u2026` : word;
|
|
76
|
+
const lines = [];
|
|
77
|
+
let current = "";
|
|
78
|
+
for (const word of label.split(/\s+/).filter(Boolean)) {
|
|
79
|
+
const next = current ? `${current} ${word}` : word;
|
|
80
|
+
if (next.length <= max) {
|
|
81
|
+
current = next;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (current) lines.push(current);
|
|
85
|
+
current = clip(word);
|
|
86
|
+
if (lines.length === LABEL_MAX_LINES) break;
|
|
87
|
+
}
|
|
88
|
+
if (current && lines.length < LABEL_MAX_LINES) lines.push(current);
|
|
89
|
+
if (lines.length === LABEL_MAX_LINES) {
|
|
90
|
+
const consumed = lines.join(" ").replace(/…$/, "").length;
|
|
91
|
+
if (consumed < label.replace(/\s+/g, " ").trim().length) {
|
|
92
|
+
const last = lines[LABEL_MAX_LINES - 1];
|
|
93
|
+
lines[LABEL_MAX_LINES - 1] = last.endsWith("\u2026") ? last : `${last.slice(0, Math.max(1, max - 1))}\u2026`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
const drawableEdge = (edge, ids) => ids.has(edge.source) && ids.has(edge.target) && edge.source !== edge.target;
|
|
99
|
+
function classifyLayeredEdges(nodes, edges, rootId = nodes[0]?.id) {
|
|
100
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
101
|
+
const root = rootId !== void 0 && ids.has(rootId) ? rootId : nodes[0]?.id;
|
|
102
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const edge of edges) {
|
|
104
|
+
if (!drawableEdge(edge, ids)) continue;
|
|
105
|
+
const list = outgoing.get(edge.source);
|
|
106
|
+
if (list) list.push(edge.target);
|
|
107
|
+
else outgoing.set(edge.source, [edge.target]);
|
|
108
|
+
}
|
|
109
|
+
const bfs = /* @__PURE__ */ new Map();
|
|
110
|
+
if (root !== void 0) {
|
|
111
|
+
bfs.set(root, 0);
|
|
112
|
+
const queue = [root];
|
|
113
|
+
for (let head = 0; head < queue.length; head += 1) {
|
|
114
|
+
const current = queue[head];
|
|
115
|
+
for (const next of outgoing.get(current) ?? []) {
|
|
116
|
+
if (!bfs.has(next)) {
|
|
117
|
+
bfs.set(next, bfs.get(current) + 1);
|
|
118
|
+
queue.push(next);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const depthById = /* @__PURE__ */ new Map();
|
|
124
|
+
let deepest = 0;
|
|
125
|
+
for (const node of nodes) {
|
|
126
|
+
const supplied = node.depth !== void 0 && Number.isFinite(node.depth) ? Math.max(0, Math.floor(node.depth)) : void 0;
|
|
127
|
+
const depth = supplied ?? bfs.get(node.id);
|
|
128
|
+
if (depth === void 0) continue;
|
|
129
|
+
depthById.set(node.id, depth);
|
|
130
|
+
deepest = Math.max(deepest, depth);
|
|
131
|
+
}
|
|
132
|
+
for (const node of nodes) if (!depthById.has(node.id)) depthById.set(node.id, deepest + 1);
|
|
133
|
+
const forwardOut = /* @__PURE__ */ new Map();
|
|
134
|
+
const kindById = /* @__PURE__ */ new Map();
|
|
135
|
+
const backEdges = [];
|
|
136
|
+
for (const edge of edges) {
|
|
137
|
+
if (!drawableEdge(edge, ids)) continue;
|
|
138
|
+
const from = depthById.get(edge.source);
|
|
139
|
+
const to = depthById.get(edge.target);
|
|
140
|
+
if (to === from + 1) {
|
|
141
|
+
kindById.set(edge.id, "forward");
|
|
142
|
+
const list = forwardOut.get(edge.source);
|
|
143
|
+
if (list) list.push(edge.target);
|
|
144
|
+
else forwardOut.set(edge.source, [edge.target]);
|
|
145
|
+
} else if (to < from) {
|
|
146
|
+
backEdges.push(edge);
|
|
147
|
+
} else {
|
|
148
|
+
kindById.set(edge.id, "lateral");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const edge of backEdges) {
|
|
152
|
+
const limit = depthById.get(edge.source);
|
|
153
|
+
const seen = /* @__PURE__ */ new Set([edge.target]);
|
|
154
|
+
const stack = [edge.target];
|
|
155
|
+
let closes = false;
|
|
156
|
+
while (stack.length > 0 && !closes) {
|
|
157
|
+
const current = stack.pop();
|
|
158
|
+
for (const next of forwardOut.get(current) ?? []) {
|
|
159
|
+
if (next === edge.source) {
|
|
160
|
+
closes = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (!seen.has(next) && depthById.get(next) < limit) {
|
|
164
|
+
seen.add(next);
|
|
165
|
+
stack.push(next);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
kindById.set(edge.id, closes ? "cycle" : "lateral");
|
|
170
|
+
}
|
|
171
|
+
return { depthById, kindById };
|
|
172
|
+
}
|
|
173
|
+
function clusterIdFor(parentId, type, edgeType) {
|
|
174
|
+
return `cluster:${parentId}:${type ?? ""}:${edgeType ?? ""}`;
|
|
175
|
+
}
|
|
176
|
+
const mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
177
|
+
const round1 = (value) => Math.round(value * 10) / 10;
|
|
178
|
+
function layoutLayeredGraph(nodes, edges, options = {}) {
|
|
179
|
+
const { nodeWidth, nodeHeight, columnGap, rowGap } = LAYERED_GEOMETRY[options.density ?? "standard"];
|
|
180
|
+
const threshold = options.clusterThreshold ?? 6;
|
|
181
|
+
const expanded = options.expandedClusterIds ?? /* @__PURE__ */ new Set();
|
|
182
|
+
const rootId = options.rootId !== void 0 && nodes.some((node) => node.id === options.rootId) ? options.rootId : nodes[0]?.id;
|
|
183
|
+
if (nodes.length === 0) {
|
|
184
|
+
return {
|
|
185
|
+
rootId,
|
|
186
|
+
items: [],
|
|
187
|
+
edges: [],
|
|
188
|
+
clusters: [],
|
|
189
|
+
columns: [],
|
|
190
|
+
itemOf: {},
|
|
191
|
+
width: 0,
|
|
192
|
+
height: 0,
|
|
193
|
+
nodeWidth,
|
|
194
|
+
nodeHeight,
|
|
195
|
+
headerHeight: LAYERED_HEADER
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const { depthById, kindById } = classifyLayeredEdges(nodes, edges, rootId);
|
|
199
|
+
const drawable = edges.filter((edge) => kindById.has(edge.id));
|
|
200
|
+
const forwardParents = /* @__PURE__ */ new Map();
|
|
201
|
+
const forwardChildCount = /* @__PURE__ */ new Map();
|
|
202
|
+
const inCycle = /* @__PURE__ */ new Set();
|
|
203
|
+
const nodeDegree = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const edge of drawable) {
|
|
205
|
+
nodeDegree.set(edge.source, (nodeDegree.get(edge.source) ?? 0) + 1);
|
|
206
|
+
nodeDegree.set(edge.target, (nodeDegree.get(edge.target) ?? 0) + 1);
|
|
207
|
+
if (kindById.get(edge.id) === "forward") {
|
|
208
|
+
const list = forwardParents.get(edge.target);
|
|
209
|
+
if (list) list.push(edge);
|
|
210
|
+
else forwardParents.set(edge.target, [edge]);
|
|
211
|
+
forwardChildCount.set(edge.source, (forwardChildCount.get(edge.source) ?? 0) + 1);
|
|
212
|
+
} else {
|
|
213
|
+
inCycle.add(edge.source);
|
|
214
|
+
inCycle.add(edge.target);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const groups = /* @__PURE__ */ new Map();
|
|
218
|
+
if (threshold > 0) {
|
|
219
|
+
for (const node of nodes) {
|
|
220
|
+
if (node.id === rootId || inCycle.has(node.id) || (forwardChildCount.get(node.id) ?? 0) > 0) continue;
|
|
221
|
+
const parents = forwardParents.get(node.id) ?? [];
|
|
222
|
+
if (parents.length !== 1) continue;
|
|
223
|
+
const via = parents[0];
|
|
224
|
+
const id = clusterIdFor(via.source, node.type, via.type);
|
|
225
|
+
const group = groups.get(id);
|
|
226
|
+
if (group) {
|
|
227
|
+
group.members.push(node);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const created = { id, parentId: via.source, depth: depthById.get(node.id), members: [node] };
|
|
231
|
+
if (node.type !== void 0) created.type = node.type;
|
|
232
|
+
if (via.type !== void 0) created.edgeType = via.type;
|
|
233
|
+
const edgeLabel = via.label ?? via.type;
|
|
234
|
+
if (edgeLabel !== void 0) created.edgeLabel = edgeLabel;
|
|
235
|
+
groups.set(id, created);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const clusterById = /* @__PURE__ */ new Map();
|
|
239
|
+
const itemOf = {};
|
|
240
|
+
const expandedGroupOf = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const group of groups.values()) {
|
|
242
|
+
if (group.members.length < threshold) continue;
|
|
243
|
+
if (expanded.has(group.id)) {
|
|
244
|
+
for (const member of group.members) expandedGroupOf.set(member.id, group);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
clusterById.set(group.id, group);
|
|
248
|
+
for (const member of group.members) itemOf[member.id] = group.id;
|
|
249
|
+
}
|
|
250
|
+
for (const node of nodes) if (itemOf[node.id] === void 0) itemOf[node.id] = node.id;
|
|
251
|
+
const columnsByDepth = /* @__PURE__ */ new Map();
|
|
252
|
+
const itemById = /* @__PURE__ */ new Map();
|
|
253
|
+
for (const node of nodes) {
|
|
254
|
+
const id = itemOf[node.id];
|
|
255
|
+
if (itemById.has(id)) continue;
|
|
256
|
+
const cluster = clusterById.get(id);
|
|
257
|
+
const depth = depthById.get(node.id);
|
|
258
|
+
const item = {
|
|
259
|
+
id,
|
|
260
|
+
kind: cluster ? "cluster" : "node",
|
|
261
|
+
depth,
|
|
262
|
+
index: 0,
|
|
263
|
+
x: 0,
|
|
264
|
+
y: 0,
|
|
265
|
+
width: nodeWidth,
|
|
266
|
+
height: nodeHeight,
|
|
267
|
+
parents: [],
|
|
268
|
+
children: [],
|
|
269
|
+
inCycle: cluster ? false : inCycle.has(node.id),
|
|
270
|
+
degree: cluster ? cluster.members.length : nodeDegree.get(node.id) ?? 0
|
|
271
|
+
};
|
|
272
|
+
if (cluster) item.cluster = cluster;
|
|
273
|
+
else item.node = node;
|
|
274
|
+
itemById.set(id, item);
|
|
275
|
+
const column = columnsByDepth.get(depth);
|
|
276
|
+
if (column) column.push(item);
|
|
277
|
+
else columnsByDepth.set(depth, [item]);
|
|
278
|
+
}
|
|
279
|
+
for (const edge of drawable) {
|
|
280
|
+
if (kindById.get(edge.id) !== "forward") continue;
|
|
281
|
+
const from = itemById.get(itemOf[edge.source]);
|
|
282
|
+
const to = itemById.get(itemOf[edge.target]);
|
|
283
|
+
if (!from.children.includes(to.id)) from.children.push(to.id);
|
|
284
|
+
if (!to.parents.includes(from.id)) to.parents.push(from.id);
|
|
285
|
+
}
|
|
286
|
+
const depths = [...columnsByDepth.keys()].sort((a, b) => a - b);
|
|
287
|
+
const position = /* @__PURE__ */ new Map();
|
|
288
|
+
const reindex = (column) => {
|
|
289
|
+
column.forEach((item, index) => {
|
|
290
|
+
item.index = index;
|
|
291
|
+
position.set(item.id, index);
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
for (const depth of depths) reindex(columnsByDepth.get(depth));
|
|
295
|
+
const groupOfItem = (item) => item.node ? expandedGroupOf.get(item.node.id) : void 0;
|
|
296
|
+
const sweep = (depth, neighbours) => {
|
|
297
|
+
const keyed = columnsByDepth.get(depth).map((item) => {
|
|
298
|
+
const around = neighbours(item).map((id) => position.get(id)).filter((value) => value !== void 0);
|
|
299
|
+
return { item, key: around.length > 0 ? mean(around) : item.index, tie: item.index };
|
|
300
|
+
});
|
|
301
|
+
keyed.sort((a, b) => a.key - b.key || a.tie - b.tie);
|
|
302
|
+
const ordered = [];
|
|
303
|
+
const placed = /* @__PURE__ */ new Set();
|
|
304
|
+
for (const { item } of keyed) {
|
|
305
|
+
if (placed.has(item.id)) continue;
|
|
306
|
+
const group = groupOfItem(item);
|
|
307
|
+
for (const candidate of group ? keyed.map((entry) => entry.item).filter((other) => groupOfItem(other) === group) : [item]) {
|
|
308
|
+
if (placed.has(candidate.id)) continue;
|
|
309
|
+
ordered.push(candidate);
|
|
310
|
+
placed.add(candidate.id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
columnsByDepth.set(depth, ordered);
|
|
314
|
+
reindex(ordered);
|
|
315
|
+
};
|
|
316
|
+
const inner = depths.slice(1);
|
|
317
|
+
for (const depth of inner) sweep(depth, (item) => item.parents);
|
|
318
|
+
for (const depth of [...inner].reverse()) sweep(depth, (item) => item.children);
|
|
319
|
+
for (const depth of inner) sweep(depth, (item) => item.parents);
|
|
320
|
+
const top = LAYERED_PADDING + LAYERED_HEADER;
|
|
321
|
+
const columns = [];
|
|
322
|
+
let width = 0;
|
|
323
|
+
let height = 0;
|
|
324
|
+
for (const depth of depths) {
|
|
325
|
+
const column = columnsByDepth.get(depth);
|
|
326
|
+
const x = LAYERED_PADDING + depth * (nodeWidth + columnGap);
|
|
327
|
+
let cursor = top;
|
|
328
|
+
let count = 0;
|
|
329
|
+
const ledGroups = /* @__PURE__ */ new Set();
|
|
330
|
+
for (const item of column) {
|
|
331
|
+
const parentCentres = item.parents.map((id) => itemById.get(id)).filter((parent) => parent.depth < depth).map((parent) => parent.y + parent.height / 2);
|
|
332
|
+
const group = groupOfItem(item);
|
|
333
|
+
const leadsGroup = group !== void 0 && !ledGroups.has(group);
|
|
334
|
+
if (group) ledGroups.add(group);
|
|
335
|
+
const floor = cursor + (leadsGroup ? GROUP_HEADER : 0);
|
|
336
|
+
const target = parentCentres.length > 0 ? mean(parentCentres) - item.height / 2 : floor;
|
|
337
|
+
item.x = x;
|
|
338
|
+
item.y = round1(Math.max(target, floor));
|
|
339
|
+
if (leadsGroup) item.expandedGroup = group;
|
|
340
|
+
cursor = item.y + item.height + rowGap;
|
|
341
|
+
count += item.cluster ? item.cluster.members.length : 1;
|
|
342
|
+
width = Math.max(width, x + item.width);
|
|
343
|
+
height = Math.max(height, item.y + item.height);
|
|
344
|
+
}
|
|
345
|
+
columns.push({ depth, x, count });
|
|
346
|
+
}
|
|
347
|
+
const byPosition = (a, b) => (position.get(a) ?? 0) - (position.get(b) ?? 0);
|
|
348
|
+
for (const item of itemById.values()) {
|
|
349
|
+
item.children.sort(byPosition);
|
|
350
|
+
item.parents.sort(byPosition);
|
|
351
|
+
}
|
|
352
|
+
const routed = /* @__PURE__ */ new Map();
|
|
353
|
+
for (const edge of drawable) {
|
|
354
|
+
const kind = kindById.get(edge.id);
|
|
355
|
+
const from = itemById.get(itemOf[edge.source]);
|
|
356
|
+
const to = itemById.get(itemOf[edge.target]);
|
|
357
|
+
const key = from.kind === "cluster" || to.kind === "cluster" ? `${from.id}->${to.id}` : edge.id;
|
|
358
|
+
const existing = routed.get(key);
|
|
359
|
+
if (existing) {
|
|
360
|
+
existing.edgeIds.push(edge.id);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
let startX;
|
|
364
|
+
let startY;
|
|
365
|
+
let endX;
|
|
366
|
+
let endY;
|
|
367
|
+
let path;
|
|
368
|
+
let labelX;
|
|
369
|
+
let labelY;
|
|
370
|
+
let startAngle;
|
|
371
|
+
let endAngle;
|
|
372
|
+
if (kind === "forward") {
|
|
373
|
+
startX = from.x + from.width;
|
|
374
|
+
startY = from.y + from.height / 2;
|
|
375
|
+
endX = to.x;
|
|
376
|
+
endY = to.y + to.height / 2;
|
|
377
|
+
const bend = Math.max(columnGap / 2, 24);
|
|
378
|
+
path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bend)} ${round1(startY)} ${round1(endX - bend)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
|
|
379
|
+
labelX = (startX + endX) / 2;
|
|
380
|
+
labelY = (startY + endY) / 2;
|
|
381
|
+
startAngle = Math.PI;
|
|
382
|
+
endAngle = 0;
|
|
383
|
+
} else if (from.depth === to.depth) {
|
|
384
|
+
startX = from.x + from.width;
|
|
385
|
+
startY = from.y + from.height / 2;
|
|
386
|
+
endX = to.x + to.width;
|
|
387
|
+
endY = to.y + to.height / 2;
|
|
388
|
+
const bulge = Math.min(columnGap - 8, 20 + Math.abs(endY - startY) * 0.1);
|
|
389
|
+
path = `M${round1(startX)} ${round1(startY)} C${round1(startX + bulge)} ${round1(startY)} ${round1(endX + bulge)} ${round1(endY)} ${round1(endX)} ${round1(endY)}`;
|
|
390
|
+
labelX = startX + bulge * 0.75;
|
|
391
|
+
labelY = (startY + endY) / 2;
|
|
392
|
+
startAngle = Math.PI;
|
|
393
|
+
endAngle = Math.PI;
|
|
394
|
+
} else {
|
|
395
|
+
startX = from.x + from.width / 2;
|
|
396
|
+
startY = from.y + from.height;
|
|
397
|
+
endX = to.x + to.width / 2;
|
|
398
|
+
endY = to.y + to.height;
|
|
399
|
+
const dip = 28 + Math.abs(endX - startX) * 0.12;
|
|
400
|
+
const low = Math.max(startY, endY) + dip;
|
|
401
|
+
path = `M${round1(startX)} ${round1(startY)} C${round1(startX)} ${round1(low)} ${round1(endX)} ${round1(low)} ${round1(endX)} ${round1(endY)}`;
|
|
402
|
+
labelX = (startX + endX) / 2;
|
|
403
|
+
labelY = low - dip * 0.25;
|
|
404
|
+
startAngle = -Math.PI / 2;
|
|
405
|
+
endAngle = -Math.PI / 2;
|
|
406
|
+
height = Math.max(height, low);
|
|
407
|
+
}
|
|
408
|
+
const arrow = edge.arrow ?? "forward";
|
|
409
|
+
const atStart = arrow === "backward";
|
|
410
|
+
const entry = {
|
|
411
|
+
id: key,
|
|
412
|
+
source: from.id,
|
|
413
|
+
target: to.id,
|
|
414
|
+
kind,
|
|
415
|
+
edgeIds: [edge.id],
|
|
416
|
+
arrow,
|
|
417
|
+
emphasis: edge.emphasis === true,
|
|
418
|
+
path,
|
|
419
|
+
labelX: round1(labelX),
|
|
420
|
+
labelY: round1(labelY),
|
|
421
|
+
arrowX: round1(atStart ? startX : endX),
|
|
422
|
+
arrowY: round1(atStart ? startY : endY),
|
|
423
|
+
arrowAngle: atStart ? startAngle : endAngle,
|
|
424
|
+
labelLines: [],
|
|
425
|
+
autoLabel: to.kind !== "cluster" && (edge.emphasis === true || kind === "forward" && from.children.length <= AUTO_LABEL_MAX_FANOUT)
|
|
426
|
+
};
|
|
427
|
+
if (edge.type !== void 0) entry.type = edge.type;
|
|
428
|
+
const label = edge.label ?? edge.type;
|
|
429
|
+
if (label !== void 0) entry.label = label;
|
|
430
|
+
routed.set(key, entry);
|
|
431
|
+
}
|
|
432
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
433
|
+
for (const entry of routed.values()) {
|
|
434
|
+
if (entry.label === void 0) continue;
|
|
435
|
+
entry.labelLines = wrapEdgeLabel(entry.label, columnGap - 8);
|
|
436
|
+
if (!entry.autoLabel || entry.emphasis || entry.kind !== "forward") continue;
|
|
437
|
+
const key = `${entry.source}|${entry.label}`;
|
|
438
|
+
if (seenLabels.has(key)) entry.autoLabel = false;
|
|
439
|
+
else seenLabels.add(key);
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
rootId,
|
|
443
|
+
items: depths.flatMap((depth) => columnsByDepth.get(depth)),
|
|
444
|
+
edges: [...routed.values()],
|
|
445
|
+
clusters: [...clusterById.values()],
|
|
446
|
+
columns,
|
|
447
|
+
itemOf,
|
|
448
|
+
width: round1(width + LAYERED_PADDING),
|
|
449
|
+
height: round1(height + LAYERED_PADDING + 8),
|
|
450
|
+
nodeWidth,
|
|
451
|
+
nodeHeight,
|
|
452
|
+
headerHeight: LAYERED_HEADER
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function layeredNeighbor(layout, currentId, key) {
|
|
456
|
+
const root = layout.rootId !== void 0 ? layout.itemOf[layout.rootId] : layout.items[0]?.id;
|
|
457
|
+
const current = layout.items.find((item) => item.id === currentId);
|
|
458
|
+
if (!current) return root ?? layout.items[0]?.id;
|
|
459
|
+
const column = layout.items.filter((item) => item.depth === current.depth);
|
|
460
|
+
switch (key) {
|
|
461
|
+
case "ArrowUp":
|
|
462
|
+
return column[current.index - 1]?.id;
|
|
463
|
+
case "ArrowDown":
|
|
464
|
+
return column[current.index + 1]?.id;
|
|
465
|
+
case "ArrowRight":
|
|
466
|
+
return current.children[0];
|
|
467
|
+
case "ArrowLeft":
|
|
468
|
+
return current.parents[0];
|
|
469
|
+
case "Home":
|
|
470
|
+
return root;
|
|
471
|
+
case "End":
|
|
472
|
+
return column[column.length - 1]?.id;
|
|
473
|
+
default:
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
63
477
|
export {
|
|
64
478
|
boundRelationshipGraph,
|
|
479
|
+
classifyLayeredEdges,
|
|
480
|
+
clusterIdFor,
|
|
481
|
+
layeredNeighbor,
|
|
482
|
+
layoutLayeredGraph,
|
|
65
483
|
layoutRelationshipGraph,
|
|
66
484
|
relationshipNeighbors,
|
|
67
|
-
traverseRelationshipNode
|
|
485
|
+
traverseRelationshipNode,
|
|
486
|
+
wrapEdgeLabel
|
|
68
487
|
};
|