@graph-artifact/core 0.1.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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/ThemeContext.d.ts +47 -0
- package/dist/ThemeContext.js +81 -0
- package/dist/components/DiagramCanvas.d.ts +8 -0
- package/dist/components/DiagramCanvas.js +19 -0
- package/dist/components/GraphCanvas.d.ts +9 -0
- package/dist/components/GraphCanvas.js +104 -0
- package/dist/components/NodeDetail.d.ts +9 -0
- package/dist/components/NodeDetail.js +127 -0
- package/dist/components/edges/RoutedEdge.d.ts +11 -0
- package/dist/components/edges/RoutedEdge.js +199 -0
- package/dist/components/nodes/ClassNode.d.ts +5 -0
- package/dist/components/nodes/ClassNode.js +62 -0
- package/dist/components/nodes/EntityNode.d.ts +5 -0
- package/dist/components/nodes/EntityNode.js +57 -0
- package/dist/components/nodes/FlowNode.d.ts +5 -0
- package/dist/components/nodes/FlowNode.js +144 -0
- package/dist/components/nodes/SequenceNodes.d.ts +33 -0
- package/dist/components/nodes/SequenceNodes.js +205 -0
- package/dist/components/nodes/StateNode.d.ts +5 -0
- package/dist/components/nodes/StateNode.js +71 -0
- package/dist/components/nodes/SubgraphNode.d.ts +5 -0
- package/dist/components/nodes/SubgraphNode.js +16 -0
- package/dist/config.d.ts +138 -0
- package/dist/config.js +165 -0
- package/dist/core.d.ts +12 -0
- package/dist/core.js +7 -0
- package/dist/diagrams/detect.d.ts +2 -0
- package/dist/diagrams/detect.js +8 -0
- package/dist/diagrams/plugins.d.ts +2 -0
- package/dist/diagrams/plugins.js +45 -0
- package/dist/diagrams/registry.d.ts +3 -0
- package/dist/diagrams/registry.js +13 -0
- package/dist/diagrams/types.d.ts +7 -0
- package/dist/diagrams/types.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/layout/dagre/index.d.ts +31 -0
- package/dist/layout/dagre/index.js +224 -0
- package/dist/layout/dagre/nodeSizing.d.ts +32 -0
- package/dist/layout/dagre/nodeSizing.js +202 -0
- package/dist/layout/edges/buildEdges.d.ts +18 -0
- package/dist/layout/edges/buildEdges.js +405 -0
- package/dist/layout/edges/classify.d.ts +13 -0
- package/dist/layout/edges/classify.js +36 -0
- package/dist/layout/edges/diamondHandles.d.ts +23 -0
- package/dist/layout/edges/diamondHandles.js +108 -0
- package/dist/layout/edges/index.d.ts +10 -0
- package/dist/layout/edges/index.js +8 -0
- package/dist/layout/edges/paths.d.ts +57 -0
- package/dist/layout/edges/paths.js +279 -0
- package/dist/layout/index.d.ts +59 -0
- package/dist/layout/index.js +131 -0
- package/dist/layout/intersect/circle.d.ts +2 -0
- package/dist/layout/intersect/circle.js +14 -0
- package/dist/layout/intersect/diamond.d.ts +9 -0
- package/dist/layout/intersect/diamond.js +21 -0
- package/dist/layout/intersect/index.d.ts +17 -0
- package/dist/layout/intersect/index.js +28 -0
- package/dist/layout/intersect/rect.d.ts +10 -0
- package/dist/layout/intersect/rect.js +31 -0
- package/dist/layout/intersect/rectRounded.d.ts +20 -0
- package/dist/layout/intersect/rectRounded.js +48 -0
- package/dist/layout/mindmapLayout.d.ts +13 -0
- package/dist/layout/mindmapLayout.js +299 -0
- package/dist/layout/sequenceLayout.d.ts +24 -0
- package/dist/layout/sequenceLayout.js +414 -0
- package/dist/layout/subgraph.d.ts +26 -0
- package/dist/layout/subgraph.js +63 -0
- package/dist/layout/types.d.ts +34 -0
- package/dist/layout/types.js +8 -0
- package/dist/parsers/classDiagram.d.ts +2 -0
- package/dist/parsers/classDiagram.js +105 -0
- package/dist/parsers/er.d.ts +2 -0
- package/dist/parsers/er.js +97 -0
- package/dist/parsers/flowchart.d.ts +2 -0
- package/dist/parsers/flowchart.js +191 -0
- package/dist/parsers/helpers.d.ts +4 -0
- package/dist/parsers/helpers.js +8 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/parsers/index.js +19 -0
- package/dist/parsers/mindmap.d.ts +2 -0
- package/dist/parsers/mindmap.js +124 -0
- package/dist/parsers/sequence.d.ts +18 -0
- package/dist/parsers/sequence.js +196 -0
- package/dist/parsers/state.d.ts +2 -0
- package/dist/parsers/state.js +68 -0
- package/dist/react.d.ts +7 -0
- package/dist/react.js +9 -0
- package/dist/reactDefaults.d.ts +5 -0
- package/dist/reactDefaults.js +37 -0
- package/dist/renderMarkdown.d.ts +9 -0
- package/dist/renderMarkdown.js +103 -0
- package/dist/swagger.d.ts +113 -0
- package/dist/swagger.js +551 -0
- package/dist/theme/dark.d.ts +8 -0
- package/dist/theme/dark.js +190 -0
- package/dist/theme/index.d.ts +18 -0
- package/dist/theme/index.js +29 -0
- package/dist/theme/light.d.ts +8 -0
- package/dist/theme/light.js +190 -0
- package/dist/theme/types.d.ts +97 -0
- package/dist/theme/types.js +7 -0
- package/dist/types.d.ts +235 -0
- package/dist/types.js +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rectRounded.ts — Rectangle intersection with border-radius compensation.
|
|
3
|
+
*
|
|
4
|
+
* The standard intersectRect returns the point where a line from center
|
|
5
|
+
* hits the sharp rectangle boundary. But when the rendered node has
|
|
6
|
+
* rounded corners (border-radius), the actual visual boundary at diagonal
|
|
7
|
+
* angles is INSIDE that sharp corner — creating a gap.
|
|
8
|
+
*
|
|
9
|
+
* This function pushes the intersection point outward along the line
|
|
10
|
+
* from center → point, proportional to how close the intersection is
|
|
11
|
+
* to a corner. At a flat edge (top/bottom/side center), compensation
|
|
12
|
+
* is zero. At a 45° corner, it's maximal.
|
|
13
|
+
*
|
|
14
|
+
* The compensation formula: at corners, the rounded boundary is inset
|
|
15
|
+
* by r - r*cos(θ) where θ is the angle from the corner axis. We
|
|
16
|
+
* approximate this by measuring how "cornery" the intersection is
|
|
17
|
+
* (both x and y are close to their maximums) and pushing outward.
|
|
18
|
+
*/
|
|
19
|
+
import { intersectRect } from './rect.js';
|
|
20
|
+
export function intersectRectRounded(node, point, borderRadius) {
|
|
21
|
+
// Start with the sharp intersection
|
|
22
|
+
const sharp = intersectRect(node, point);
|
|
23
|
+
if (borderRadius <= 0)
|
|
24
|
+
return sharp;
|
|
25
|
+
const w = node.width / 2;
|
|
26
|
+
const h = node.height / 2;
|
|
27
|
+
// How far from center is the intersection, as a ratio of half-width/height
|
|
28
|
+
const rx = Math.abs(sharp.x - node.x) / w; // 0..1
|
|
29
|
+
const ry = Math.abs(sharp.y - node.y) / h; // 0..1
|
|
30
|
+
// "Cornerness": both rx and ry near 1.0 means we're at a corner
|
|
31
|
+
// At flat edges, one of these will be ~1.0 and the other ~0.0
|
|
32
|
+
const cornerness = rx * ry; // 0 at edges, ~1 at corners
|
|
33
|
+
if (cornerness < 0.01)
|
|
34
|
+
return sharp; // flat edge, no compensation needed
|
|
35
|
+
// Push the intersection outward along the center→point direction
|
|
36
|
+
// Maximum compensation at 45° corners: r * (1 - cos(45°)) ≈ 0.293 * r
|
|
37
|
+
const maxCompensation = borderRadius * 0.293;
|
|
38
|
+
const compensation = maxCompensation * cornerness;
|
|
39
|
+
const dx = point.x - node.x;
|
|
40
|
+
const dy = point.y - node.y;
|
|
41
|
+
const dist = Math.hypot(dx, dy);
|
|
42
|
+
if (dist < 1e-6)
|
|
43
|
+
return sharp;
|
|
44
|
+
return {
|
|
45
|
+
x: sharp.x + (dx / dist) * compensation,
|
|
46
|
+
y: sharp.y + (dy / dist) * compensation,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Node } from '@xyflow/react';
|
|
2
|
+
import type { ParsedGraph, NodeMetadata } from '../types.js';
|
|
3
|
+
import type { Theme } from '../ThemeContext.js';
|
|
4
|
+
import type { NodePosition, EdgeWaypoints, HandlePositions } from './types.js';
|
|
5
|
+
interface MindmapLayoutResult {
|
|
6
|
+
nodes: Node[];
|
|
7
|
+
positions: Map<string, NodePosition>;
|
|
8
|
+
edgeWaypoints: EdgeWaypoints[];
|
|
9
|
+
handles: HandlePositions;
|
|
10
|
+
diamondEdgeHandles: Map<number, string>;
|
|
11
|
+
}
|
|
12
|
+
export declare function layoutMindmapGraph(parsed: ParsedGraph, theme: Theme, metadata?: Record<string, NodeMetadata>): MindmapLayoutResult;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { getConfig, getLayoutMapping } from '../config.js';
|
|
2
|
+
import { nodeSize } from './dagre/nodeSizing.js';
|
|
3
|
+
import { directionToHandles } from './dagre/index.js';
|
|
4
|
+
import { computeForwardEdgeCounts, computeDiamondAssignments } from './edges/index.js';
|
|
5
|
+
function clamp255(v) {
|
|
6
|
+
return Math.max(0, Math.min(255, Math.round(v)));
|
|
7
|
+
}
|
|
8
|
+
function shadeHex(hex, factor) {
|
|
9
|
+
const clean = hex.replace('#', '');
|
|
10
|
+
if (!/^[0-9a-fA-F]{6}$/.test(clean))
|
|
11
|
+
return hex;
|
|
12
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
13
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
14
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
15
|
+
const rr = clamp255(r * (1 + factor));
|
|
16
|
+
const gg = clamp255(g * (1 + factor));
|
|
17
|
+
const bb = clamp255(b * (1 + factor));
|
|
18
|
+
return `#${rr.toString(16).padStart(2, '0')}${gg.toString(16).padStart(2, '0')}${bb.toString(16).padStart(2, '0')}`;
|
|
19
|
+
}
|
|
20
|
+
function buildBranchPalette(base, orange2, orange3) {
|
|
21
|
+
// Intentionally stronger but still orange-family variants so branches are
|
|
22
|
+
// visibly distinct while preserving the product's orange visual language.
|
|
23
|
+
return [
|
|
24
|
+
shadeHex(base, -0.22),
|
|
25
|
+
shadeHex(base, -0.12),
|
|
26
|
+
base,
|
|
27
|
+
orange2,
|
|
28
|
+
orange3,
|
|
29
|
+
shadeHex(base, 0.14),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
function centerOf(pos) {
|
|
33
|
+
return { x: pos.x + pos.width / 2, y: pos.y + pos.height / 2 };
|
|
34
|
+
}
|
|
35
|
+
function rootsOf(nodes, edges) {
|
|
36
|
+
const incoming = new Map();
|
|
37
|
+
for (const n of nodes)
|
|
38
|
+
incoming.set(n.id, 0);
|
|
39
|
+
for (const e of edges)
|
|
40
|
+
incoming.set(e.target, (incoming.get(e.target) ?? 0) + 1);
|
|
41
|
+
return nodes.map((n) => n.id).filter((id) => (incoming.get(id) ?? 0) === 0);
|
|
42
|
+
}
|
|
43
|
+
function buildChildren(edges) {
|
|
44
|
+
const out = new Map();
|
|
45
|
+
for (const e of edges) {
|
|
46
|
+
const list = out.get(e.source) ?? [];
|
|
47
|
+
list.push(e.target);
|
|
48
|
+
out.set(e.source, list);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function subtreeLeafCount(id, children) {
|
|
53
|
+
const kids = children.get(id) ?? [];
|
|
54
|
+
if (kids.length === 0)
|
|
55
|
+
return 1;
|
|
56
|
+
return kids.reduce((sum, kid) => sum + subtreeLeafCount(kid, children), 0);
|
|
57
|
+
}
|
|
58
|
+
function normalizeDeg(d) {
|
|
59
|
+
let out = d;
|
|
60
|
+
while (out < -180)
|
|
61
|
+
out += 360;
|
|
62
|
+
while (out > 180)
|
|
63
|
+
out -= 360;
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function resolveMindmapOverlaps(centers, sizes, rootId, rootCenter) {
|
|
67
|
+
const ids = Array.from(centers.keys()).filter((id) => id !== rootId);
|
|
68
|
+
const GAP = 10;
|
|
69
|
+
const MAX_ITERS = 16;
|
|
70
|
+
function overlapAmount(a, b) {
|
|
71
|
+
const ca = centers.get(a);
|
|
72
|
+
const cb = centers.get(b);
|
|
73
|
+
const sa = sizes.get(a);
|
|
74
|
+
const sb = sizes.get(b);
|
|
75
|
+
const dx = Math.abs(ca.x - cb.x);
|
|
76
|
+
const dy = Math.abs(ca.y - cb.y);
|
|
77
|
+
const ox = sa.width / 2 + sb.width / 2 + GAP - dx;
|
|
78
|
+
const oy = sa.height / 2 + sb.height / 2 + GAP - dy;
|
|
79
|
+
return { x: ox, y: oy };
|
|
80
|
+
}
|
|
81
|
+
function pushOutward(id, amount) {
|
|
82
|
+
const c = centers.get(id);
|
|
83
|
+
let vx = c.x - rootCenter.x;
|
|
84
|
+
let vy = c.y - rootCenter.y;
|
|
85
|
+
const len = Math.hypot(vx, vy);
|
|
86
|
+
if (len < 1e-6) {
|
|
87
|
+
vx = 1;
|
|
88
|
+
vy = 0;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
vx /= len;
|
|
92
|
+
vy /= len;
|
|
93
|
+
}
|
|
94
|
+
centers.set(id, { x: c.x + vx * amount, y: c.y + vy * amount });
|
|
95
|
+
}
|
|
96
|
+
for (let iter = 0; iter < MAX_ITERS; iter++) {
|
|
97
|
+
let moved = false;
|
|
98
|
+
for (let i = 0; i < ids.length; i++) {
|
|
99
|
+
for (let j = i + 1; j < ids.length; j++) {
|
|
100
|
+
const a = ids[i];
|
|
101
|
+
const b = ids[j];
|
|
102
|
+
const ov = overlapAmount(a, b);
|
|
103
|
+
if (ov.x > 0 && ov.y > 0) {
|
|
104
|
+
const push = Math.min(24, Math.max(6, Math.min(ov.x, ov.y) * 0.55));
|
|
105
|
+
pushOutward(a, push * 0.5);
|
|
106
|
+
pushOutward(b, push * 0.5);
|
|
107
|
+
moved = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!moved)
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function layoutMindmapGraph(parsed, theme, metadata) {
|
|
116
|
+
const { layout } = getConfig();
|
|
117
|
+
const rfNodeType = getLayoutMapping(parsed.diagramType);
|
|
118
|
+
const sizes = new Map();
|
|
119
|
+
for (const node of parsed.nodes) {
|
|
120
|
+
sizes.set(node.id, nodeSize(node, 'mindmap', layout.nodeSizing));
|
|
121
|
+
}
|
|
122
|
+
const roots = rootsOf(parsed.nodes, parsed.edges);
|
|
123
|
+
const rootId = roots[0] ?? parsed.nodes[0]?.id;
|
|
124
|
+
if (!rootId) {
|
|
125
|
+
return {
|
|
126
|
+
nodes: [],
|
|
127
|
+
positions: new Map(),
|
|
128
|
+
edgeWaypoints: [],
|
|
129
|
+
handles: directionToHandles('LR'),
|
|
130
|
+
diamondEdgeHandles: new Map(),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Keep root sizing stable between layout and render so edge intersections
|
|
134
|
+
// use the same geometry the DOM actually renders.
|
|
135
|
+
const rootSize = sizes.get(rootId);
|
|
136
|
+
if (rootSize) {
|
|
137
|
+
sizes.set(rootId, {
|
|
138
|
+
width: Math.max(rootSize.width, 130),
|
|
139
|
+
height: Math.max(rootSize.height, 130),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const children = buildChildren(parsed.edges);
|
|
143
|
+
const centers = new Map();
|
|
144
|
+
const nodeBranch = new Map();
|
|
145
|
+
const branchPalette = buildBranchPalette(theme.color.orange1, theme.color.orange2, theme.color.orange3);
|
|
146
|
+
const rootCenter = { x: 560, y: 380 };
|
|
147
|
+
centers.set(rootId, rootCenter);
|
|
148
|
+
nodeBranch.set(rootId, -1);
|
|
149
|
+
const level1 = children.get(rootId) ?? [];
|
|
150
|
+
const r1 = 180;
|
|
151
|
+
// Keep first-level branches evenly spaced around the root so trunk lines
|
|
152
|
+
// have balanced separation at the center. Descendants still fan out within
|
|
153
|
+
// each branch wedge based on subtree shape.
|
|
154
|
+
const n1 = Math.max(1, level1.length);
|
|
155
|
+
const baseStart = -150; // deterministic orientation similar to Mermaid
|
|
156
|
+
const step = 360 / n1;
|
|
157
|
+
const branchWedges = new Map();
|
|
158
|
+
for (let i = 0; i < level1.length; i++) {
|
|
159
|
+
const id = level1[i];
|
|
160
|
+
const center = normalizeDeg(baseStart + i * step);
|
|
161
|
+
// Wider wedges when there are fewer top-level branches.
|
|
162
|
+
const span = Math.max(42, Math.min(84, step * 0.78));
|
|
163
|
+
branchWedges.set(id, { center, span });
|
|
164
|
+
}
|
|
165
|
+
function placeSubtree(parentId, centerDeg, spanDeg, depth) {
|
|
166
|
+
const kids = children.get(parentId) ?? [];
|
|
167
|
+
if (kids.length === 0)
|
|
168
|
+
return;
|
|
169
|
+
const parent = centers.get(parentId);
|
|
170
|
+
const r = r1 + (depth - 1) * 140;
|
|
171
|
+
const leafWeights = kids.map((kid) => subtreeLeafCount(kid, children));
|
|
172
|
+
const leafSum = Math.max(1, leafWeights.reduce((a, b) => a + b, 0));
|
|
173
|
+
const minAngles = kids.map((kid) => {
|
|
174
|
+
const size = sizes.get(kid);
|
|
175
|
+
const desiredArc = Math.max(size.width + 40, 90);
|
|
176
|
+
const rad = 2 * Math.atan(desiredArc / (2 * r));
|
|
177
|
+
const deg = (rad * 180) / Math.PI;
|
|
178
|
+
return Math.max(10, Math.min(36, deg));
|
|
179
|
+
});
|
|
180
|
+
const minTotal = minAngles.reduce((a, b) => a + b, 0) + Math.max(0, kids.length - 1) * 4;
|
|
181
|
+
const effectiveSpan = Math.max(spanDeg, minTotal);
|
|
182
|
+
const extra = Math.max(0, effectiveSpan - minTotal);
|
|
183
|
+
let c = centerDeg - effectiveSpan / 2;
|
|
184
|
+
for (let i = 0; i < kids.length; i++) {
|
|
185
|
+
const weightedExtra = extra * (leafWeights[i] / leafSum);
|
|
186
|
+
const childSpan = minAngles[i] + weightedExtra;
|
|
187
|
+
const childCenter = c + childSpan / 2;
|
|
188
|
+
const rr = (Math.PI / 180) * childCenter;
|
|
189
|
+
const p = {
|
|
190
|
+
x: parent.x + Math.cos(rr) * r,
|
|
191
|
+
y: parent.y + Math.sin(rr) * r,
|
|
192
|
+
};
|
|
193
|
+
centers.set(kids[i], p);
|
|
194
|
+
nodeBranch.set(kids[i], nodeBranch.get(parentId) ?? 0);
|
|
195
|
+
// Taper span deeper in the tree so leaves fan outward but stay grouped.
|
|
196
|
+
placeSubtree(kids[i], childCenter, Math.max(18, childSpan * 0.72), depth + 1);
|
|
197
|
+
c += childSpan + 4;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
level1.forEach((child, i) => {
|
|
201
|
+
const wedge = branchWedges.get(child) ?? { center: -150 + i * 40, span: 30 };
|
|
202
|
+
const rr = (Math.PI / 180) * wedge.center;
|
|
203
|
+
centers.set(child, {
|
|
204
|
+
x: rootCenter.x + Math.cos(rr) * r1,
|
|
205
|
+
y: rootCenter.y + Math.sin(rr) * r1,
|
|
206
|
+
});
|
|
207
|
+
nodeBranch.set(child, i % branchPalette.length);
|
|
208
|
+
placeSubtree(child, wedge.center, Math.max(28, wedge.span * 0.9), 1);
|
|
209
|
+
});
|
|
210
|
+
if (roots.length > 1) {
|
|
211
|
+
roots.slice(1).forEach((rid, i) => {
|
|
212
|
+
if (centers.has(rid))
|
|
213
|
+
return;
|
|
214
|
+
centers.set(rid, { x: rootCenter.x + (i + 1) * 180, y: rootCenter.y + 240 });
|
|
215
|
+
nodeBranch.set(rid, i % branchPalette.length);
|
|
216
|
+
placeSubtree(rid, normalizeDeg(35 + i * 20), 26, 1);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
parsed.nodes.forEach((node, i) => {
|
|
220
|
+
if (!centers.has(node.id)) {
|
|
221
|
+
centers.set(node.id, {
|
|
222
|
+
x: rootCenter.x + ((i % 4) - 1.5) * 210,
|
|
223
|
+
y: rootCenter.y + 340 + Math.floor(i / 4) * 130,
|
|
224
|
+
});
|
|
225
|
+
nodeBranch.set(node.id, i % branchPalette.length);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
// Final pass: prevent leaf/card overlaps while preserving branch directions.
|
|
229
|
+
resolveMindmapOverlaps(centers, sizes, rootId, rootCenter);
|
|
230
|
+
const positions = new Map();
|
|
231
|
+
parsed.nodes.forEach((node) => {
|
|
232
|
+
const c = centers.get(node.id);
|
|
233
|
+
const s = sizes.get(node.id);
|
|
234
|
+
positions.set(node.id, {
|
|
235
|
+
x: c.x - s.width / 2,
|
|
236
|
+
y: c.y - s.height / 2,
|
|
237
|
+
width: s.width,
|
|
238
|
+
height: s.height,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
const handles = directionToHandles('LR');
|
|
242
|
+
const fwCounts = computeForwardEdgeCounts(parsed.edges, positions, 'LR');
|
|
243
|
+
const diamondAssignments = computeDiamondAssignments(parsed.nodes, parsed.edges, positions, 'LR');
|
|
244
|
+
const nodes = parsed.nodes.map((node) => {
|
|
245
|
+
const pos = positions.get(node.id);
|
|
246
|
+
const isRoot = node.id === rootId;
|
|
247
|
+
const branch = Math.max(0, nodeBranch.get(node.id) ?? 0);
|
|
248
|
+
const bg = isRoot ? theme.color.orange1 : branchPalette[branch % branchPalette.length];
|
|
249
|
+
return {
|
|
250
|
+
id: node.id,
|
|
251
|
+
position: { x: pos.x, y: pos.y },
|
|
252
|
+
data: {
|
|
253
|
+
label: node.label,
|
|
254
|
+
shape: isRoot ? 'circle' : 'round',
|
|
255
|
+
attributes: node.attributes,
|
|
256
|
+
properties: node.properties,
|
|
257
|
+
methods: node.methods,
|
|
258
|
+
meta: metadata?.[node.id],
|
|
259
|
+
handles,
|
|
260
|
+
fwIn: fwCounts.incoming.get(node.id) ?? 0,
|
|
261
|
+
fwOut: fwCounts.outgoing.get(node.id) ?? 0,
|
|
262
|
+
diamondHandleConfig: diamondAssignments.handleConfigs.get(node.id),
|
|
263
|
+
layoutWidth: pos.width,
|
|
264
|
+
layoutHeight: pos.height,
|
|
265
|
+
nodeBgColor: bg,
|
|
266
|
+
nodeTextColor: theme.color.white,
|
|
267
|
+
},
|
|
268
|
+
type: rfNodeType,
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
const edgeWaypoints = parsed.edges.map((edge) => {
|
|
272
|
+
const src = positions.get(edge.source);
|
|
273
|
+
const tgt = positions.get(edge.target);
|
|
274
|
+
if (!src || !tgt)
|
|
275
|
+
return { source: edge.source, target: edge.target, points: [] };
|
|
276
|
+
const s = centerOf(src);
|
|
277
|
+
const t = centerOf(tgt);
|
|
278
|
+
const mid = { x: (s.x + t.x) / 2, y: (s.y + t.y) / 2 };
|
|
279
|
+
const out = { x: mid.x - rootCenter.x, y: mid.y - rootCenter.y };
|
|
280
|
+
const len = Math.hypot(out.x, out.y) || 1;
|
|
281
|
+
const bend = Math.max(22, Math.min(38, len * 0.08));
|
|
282
|
+
const control = {
|
|
283
|
+
x: mid.x + (out.x / len) * bend,
|
|
284
|
+
y: mid.y + (out.y / len) * bend,
|
|
285
|
+
};
|
|
286
|
+
return {
|
|
287
|
+
source: edge.source,
|
|
288
|
+
target: edge.target,
|
|
289
|
+
points: [s, control, t],
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
return {
|
|
293
|
+
nodes,
|
|
294
|
+
positions,
|
|
295
|
+
edgeWaypoints,
|
|
296
|
+
handles,
|
|
297
|
+
diamondEdgeHandles: diamondAssignments.edgeHandles,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sequenceLayout.ts — Grid-based layout for sequence diagrams.
|
|
3
|
+
*
|
|
4
|
+
* Sequence diagrams don't use dagre. Instead we lay out participants
|
|
5
|
+
* in a fixed horizontal row and create invisible anchor nodes along
|
|
6
|
+
* each lifeline at each message row. Edges connect anchor-to-anchor.
|
|
7
|
+
*
|
|
8
|
+
* Message labels are rendered as positioned node components (above the line),
|
|
9
|
+
* not as React Flow edge labels. Autonumber indicators appear as numbered
|
|
10
|
+
* circles on the source lifeline.
|
|
11
|
+
*
|
|
12
|
+
* Row heights are computed dynamically based on content — multi-line labels
|
|
13
|
+
* and notes get more vertical space so nothing overlaps.
|
|
14
|
+
*
|
|
15
|
+
* Notes span the full width between the participants they're attached to,
|
|
16
|
+
* matching standard sequence diagram rendering.
|
|
17
|
+
*/
|
|
18
|
+
import type { Node, Edge } from '@xyflow/react';
|
|
19
|
+
import type { SequenceData, NodeMetadata } from '../types.js';
|
|
20
|
+
import type { Theme } from '../ThemeContext.js';
|
|
21
|
+
export declare function layoutSequenceDiagram(sequence: SequenceData, theme: Theme, metadata?: Record<string, NodeMetadata>): {
|
|
22
|
+
nodes: Node[];
|
|
23
|
+
edges: Edge[];
|
|
24
|
+
};
|