@diagrammo/dgmo 0.2.5 → 0.2.7
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 +213 -57
- package/dist/cli.cjs +92 -86
- package/dist/index.cjs +1337 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -2
- package/dist/index.d.ts +107 -2
- package/dist/index.js +1332 -193
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/d3.ts +59 -1
- package/src/dgmo-router.ts +5 -1
- package/src/echarts.ts +2 -2
- package/src/graph/flowchart-parser.ts +499 -0
- package/src/graph/flowchart-renderer.ts +503 -0
- package/src/graph/layout.ts +222 -0
- package/src/graph/types.ts +44 -0
- package/src/index.ts +24 -0
- package/src/sequence/parser.ts +229 -37
- package/src/sequence/renderer.ts +310 -16
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import dagre from '@dagrejs/dagre';
|
|
2
|
+
import type {
|
|
3
|
+
ParsedGraph,
|
|
4
|
+
GraphNode,
|
|
5
|
+
GraphEdge,
|
|
6
|
+
GraphShape,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
export interface LayoutNode {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
shape: GraphShape;
|
|
13
|
+
color?: string;
|
|
14
|
+
group?: string;
|
|
15
|
+
lineNumber: number;
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LayoutEdge {
|
|
23
|
+
source: string;
|
|
24
|
+
target: string;
|
|
25
|
+
points: { x: number; y: number }[];
|
|
26
|
+
label?: string;
|
|
27
|
+
color?: string;
|
|
28
|
+
lineNumber: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LayoutGroup {
|
|
32
|
+
id: string;
|
|
33
|
+
label: string;
|
|
34
|
+
color?: string;
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface LayoutResult {
|
|
42
|
+
nodes: LayoutNode[];
|
|
43
|
+
edges: LayoutEdge[];
|
|
44
|
+
groups: LayoutGroup[];
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const GROUP_PADDING = 20;
|
|
50
|
+
|
|
51
|
+
function computeNodeWidth(label: string, shape: GraphShape): number {
|
|
52
|
+
const base = Math.max(120, label.length * 9 + 40);
|
|
53
|
+
if (shape === 'subroutine') return base + 10;
|
|
54
|
+
return base;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function computeNodeHeight(shape: GraphShape): number {
|
|
58
|
+
return shape === 'decision' ? 60 : 50;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function layoutGraph(graph: ParsedGraph): LayoutResult {
|
|
62
|
+
if (graph.nodes.length === 0) {
|
|
63
|
+
return { nodes: [], edges: [], groups: [], width: 0, height: 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const g = new dagre.graphlib.Graph({ compound: true });
|
|
67
|
+
g.setGraph({
|
|
68
|
+
rankdir: graph.direction,
|
|
69
|
+
nodesep: 50,
|
|
70
|
+
ranksep: 60,
|
|
71
|
+
edgesep: 20,
|
|
72
|
+
});
|
|
73
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
74
|
+
|
|
75
|
+
// Build a lookup for original node data
|
|
76
|
+
const nodeDataMap = new Map<string, GraphNode>();
|
|
77
|
+
for (const node of graph.nodes) {
|
|
78
|
+
nodeDataMap.set(node.id, node);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Add group parent nodes
|
|
82
|
+
if (graph.groups) {
|
|
83
|
+
for (const group of graph.groups) {
|
|
84
|
+
g.setNode(group.id, {
|
|
85
|
+
label: group.label,
|
|
86
|
+
clusterLabelPos: 'top',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Add nodes with computed dimensions
|
|
92
|
+
for (const node of graph.nodes) {
|
|
93
|
+
const width = computeNodeWidth(node.label, node.shape);
|
|
94
|
+
const height = computeNodeHeight(node.shape);
|
|
95
|
+
g.setNode(node.id, { label: node.label, width, height });
|
|
96
|
+
|
|
97
|
+
// Set parent for grouped nodes
|
|
98
|
+
if (node.group && graph.groups?.some((gr) => gr.id === node.group)) {
|
|
99
|
+
g.setParent(node.id, node.group);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Build edge lookup for original data
|
|
104
|
+
const edgeDataMap = new Map<string, GraphEdge>();
|
|
105
|
+
for (const edge of graph.edges) {
|
|
106
|
+
const key = `${edge.source}->${edge.target}`;
|
|
107
|
+
edgeDataMap.set(key, edge);
|
|
108
|
+
g.setEdge(edge.source, edge.target, {
|
|
109
|
+
label: edge.label ?? '',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Run layout
|
|
114
|
+
dagre.layout(g);
|
|
115
|
+
|
|
116
|
+
// Extract positioned nodes
|
|
117
|
+
const layoutNodes: LayoutNode[] = graph.nodes.map((node) => {
|
|
118
|
+
const pos = g.node(node.id);
|
|
119
|
+
return {
|
|
120
|
+
id: node.id,
|
|
121
|
+
label: node.label,
|
|
122
|
+
shape: node.shape,
|
|
123
|
+
color: node.color,
|
|
124
|
+
group: node.group,
|
|
125
|
+
lineNumber: node.lineNumber,
|
|
126
|
+
x: pos.x,
|
|
127
|
+
y: pos.y,
|
|
128
|
+
width: pos.width,
|
|
129
|
+
height: pos.height,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Extract edge waypoints
|
|
134
|
+
const layoutEdges: LayoutEdge[] = graph.edges.map((edge) => {
|
|
135
|
+
const edgeData = g.edge(edge.source, edge.target);
|
|
136
|
+
return {
|
|
137
|
+
source: edge.source,
|
|
138
|
+
target: edge.target,
|
|
139
|
+
points: edgeData?.points ?? [],
|
|
140
|
+
label: edge.label,
|
|
141
|
+
color: edge.color,
|
|
142
|
+
lineNumber: edge.lineNumber,
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Compute group bounding boxes from member node positions
|
|
147
|
+
const layoutGroups: LayoutGroup[] = [];
|
|
148
|
+
if (graph.groups) {
|
|
149
|
+
const nodeMap = new Map(layoutNodes.map((n) => [n.id, n]));
|
|
150
|
+
for (const group of graph.groups) {
|
|
151
|
+
const members = group.nodeIds
|
|
152
|
+
.map((id) => nodeMap.get(id))
|
|
153
|
+
.filter((n): n is LayoutNode => n !== undefined);
|
|
154
|
+
|
|
155
|
+
if (members.length === 0) {
|
|
156
|
+
layoutGroups.push({
|
|
157
|
+
id: group.id,
|
|
158
|
+
label: group.label,
|
|
159
|
+
color: group.color,
|
|
160
|
+
x: 0,
|
|
161
|
+
y: 0,
|
|
162
|
+
width: 0,
|
|
163
|
+
height: 0,
|
|
164
|
+
});
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let minX = Infinity;
|
|
169
|
+
let minY = Infinity;
|
|
170
|
+
let maxX = -Infinity;
|
|
171
|
+
let maxY = -Infinity;
|
|
172
|
+
|
|
173
|
+
for (const member of members) {
|
|
174
|
+
const left = member.x - member.width / 2;
|
|
175
|
+
const right = member.x + member.width / 2;
|
|
176
|
+
const top = member.y - member.height / 2;
|
|
177
|
+
const bottom = member.y + member.height / 2;
|
|
178
|
+
if (left < minX) minX = left;
|
|
179
|
+
if (right > maxX) maxX = right;
|
|
180
|
+
if (top < minY) minY = top;
|
|
181
|
+
if (bottom > maxY) maxY = bottom;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
layoutGroups.push({
|
|
185
|
+
id: group.id,
|
|
186
|
+
label: group.label,
|
|
187
|
+
color: group.color,
|
|
188
|
+
x: minX - GROUP_PADDING,
|
|
189
|
+
y: minY - GROUP_PADDING,
|
|
190
|
+
width: maxX - minX + GROUP_PADDING * 2,
|
|
191
|
+
height: maxY - minY + GROUP_PADDING * 2,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Compute total diagram dimensions
|
|
197
|
+
let totalWidth = 0;
|
|
198
|
+
let totalHeight = 0;
|
|
199
|
+
for (const node of layoutNodes) {
|
|
200
|
+
const right = node.x + node.width / 2;
|
|
201
|
+
const bottom = node.y + node.height / 2;
|
|
202
|
+
if (right > totalWidth) totalWidth = right;
|
|
203
|
+
if (bottom > totalHeight) totalHeight = bottom;
|
|
204
|
+
}
|
|
205
|
+
for (const group of layoutGroups) {
|
|
206
|
+
const right = group.x + group.width;
|
|
207
|
+
const bottom = group.y + group.height;
|
|
208
|
+
if (right > totalWidth) totalWidth = right;
|
|
209
|
+
if (bottom > totalHeight) totalHeight = bottom;
|
|
210
|
+
}
|
|
211
|
+
// Add margin
|
|
212
|
+
totalWidth += 40;
|
|
213
|
+
totalHeight += 40;
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
nodes: layoutNodes,
|
|
217
|
+
edges: layoutEdges,
|
|
218
|
+
groups: layoutGroups,
|
|
219
|
+
width: totalWidth,
|
|
220
|
+
height: totalHeight,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type GraphShape =
|
|
2
|
+
| 'terminal' // () — rounded/stadium
|
|
3
|
+
| 'process' // [] — rectangle
|
|
4
|
+
| 'decision' // <> — diamond
|
|
5
|
+
| 'io' // // — parallelogram
|
|
6
|
+
| 'subroutine' // [[]] — double-bordered rectangle
|
|
7
|
+
| 'document'; // [~] — wavy-bottom rectangle
|
|
8
|
+
|
|
9
|
+
export type GraphDirection = 'TB' | 'LR';
|
|
10
|
+
|
|
11
|
+
export interface GraphNode {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
shape: GraphShape;
|
|
15
|
+
color?: string;
|
|
16
|
+
group?: string;
|
|
17
|
+
lineNumber: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GraphEdge {
|
|
21
|
+
source: string;
|
|
22
|
+
target: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
color?: string;
|
|
25
|
+
lineNumber: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GraphGroup {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
color?: string;
|
|
32
|
+
nodeIds: string[];
|
|
33
|
+
lineNumber: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ParsedGraph {
|
|
37
|
+
type: 'flowchart';
|
|
38
|
+
title?: string;
|
|
39
|
+
direction: GraphDirection;
|
|
40
|
+
nodes: GraphNode[];
|
|
41
|
+
edges: GraphEdge[];
|
|
42
|
+
groups?: GraphGroup[];
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -37,13 +37,16 @@ export {
|
|
|
37
37
|
parseSequenceDgmo,
|
|
38
38
|
looksLikeSequence,
|
|
39
39
|
isSequenceBlock,
|
|
40
|
+
isSequenceNote,
|
|
40
41
|
} from './sequence/parser';
|
|
41
42
|
export type {
|
|
42
43
|
ParsedSequenceDgmo,
|
|
43
44
|
SequenceParticipant,
|
|
44
45
|
SequenceMessage,
|
|
45
46
|
SequenceBlock,
|
|
47
|
+
ElseIfBranch,
|
|
46
48
|
SequenceSection,
|
|
49
|
+
SequenceNote,
|
|
47
50
|
SequenceElement,
|
|
48
51
|
SequenceGroup,
|
|
49
52
|
ParticipantType,
|
|
@@ -57,6 +60,27 @@ export {
|
|
|
57
60
|
export { parseQuadrant } from './dgmo-mermaid';
|
|
58
61
|
export type { ParsedQuadrant } from './dgmo-mermaid';
|
|
59
62
|
|
|
63
|
+
export { parseFlowchart, looksLikeFlowchart } from './graph/flowchart-parser';
|
|
64
|
+
|
|
65
|
+
export type {
|
|
66
|
+
ParsedGraph,
|
|
67
|
+
GraphNode,
|
|
68
|
+
GraphEdge,
|
|
69
|
+
GraphGroup,
|
|
70
|
+
GraphShape,
|
|
71
|
+
GraphDirection,
|
|
72
|
+
} from './graph/types';
|
|
73
|
+
|
|
74
|
+
export { layoutGraph } from './graph/layout';
|
|
75
|
+
export type {
|
|
76
|
+
LayoutResult,
|
|
77
|
+
LayoutNode,
|
|
78
|
+
LayoutEdge,
|
|
79
|
+
LayoutGroup,
|
|
80
|
+
} from './graph/layout';
|
|
81
|
+
|
|
82
|
+
export { renderFlowchart, renderFlowchartForExport } from './graph/flowchart-renderer';
|
|
83
|
+
|
|
60
84
|
// ============================================================
|
|
61
85
|
// Config Builders (produce framework-specific config objects)
|
|
62
86
|
// ============================================================
|