@diagrammo/dgmo 0.6.1 → 0.6.3
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/.claude/commands/dgmo.md +294 -0
- package/AGENTS.md +148 -0
- package/dist/cli.cjs +338 -163
- package/dist/index.cjs +1080 -319
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -4
- package/dist/index.d.ts +28 -4
- package/dist/index.js +1078 -319
- package/dist/index.js.map +1 -1
- package/docs/ai-integration.md +33 -50
- package/package.json +8 -5
- package/src/c4/layout.ts +68 -10
- package/src/c4/parser.ts +0 -16
- package/src/c4/renderer.ts +1 -5
- package/src/class/layout.ts +0 -1
- package/src/class/parser.ts +28 -0
- package/src/class/renderer.ts +5 -26
- package/src/cli.ts +673 -2
- package/src/completion.ts +58 -0
- package/src/d3.ts +58 -106
- package/src/dgmo-router.ts +0 -57
- package/src/echarts.ts +96 -55
- package/src/er/classify.ts +206 -0
- package/src/er/layout.ts +259 -94
- package/src/er/parser.ts +30 -1
- package/src/er/renderer.ts +231 -18
- package/src/graph/flowchart-parser.ts +27 -4
- package/src/graph/flowchart-renderer.ts +1 -2
- package/src/graph/state-parser.ts +0 -1
- package/src/graph/state-renderer.ts +1 -3
- package/src/index.ts +10 -0
- package/src/infra/compute.ts +0 -7
- package/src/infra/layout.ts +60 -15
- package/src/infra/parser.ts +46 -4
- package/src/infra/renderer.ts +376 -47
- package/src/initiative-status/layout.ts +46 -30
- package/src/initiative-status/renderer.ts +5 -25
- package/src/kanban/parser.ts +0 -2
- package/src/org/layout.ts +0 -4
- package/src/org/renderer.ts +7 -28
- package/src/sequence/parser.ts +14 -11
- package/src/sequence/renderer.ts +0 -2
- package/src/sequence/tag-resolution.ts +0 -1
- package/src/sitemap/layout.ts +1 -14
- package/src/sitemap/parser.ts +1 -2
- package/src/sitemap/renderer.ts +0 -3
- package/src/utils/arrows.ts +7 -7
- package/src/utils/export-container.ts +40 -0
- package/.claude/skills/dgmo-chart/SKILL.md +0 -141
- package/.claude/skills/dgmo-flowchart/SKILL.md +0 -61
- package/.claude/skills/dgmo-generate/SKILL.md +0 -59
- package/.claude/skills/dgmo-sequence/SKILL.md +0 -104
package/docs/ai-integration.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# DGMO AI Integration Guide
|
|
2
2
|
|
|
3
|
-
Use AI coding tools to generate `.dgmo` diagrams. This guide covers Claude Code, Copilot, Cursor, and
|
|
3
|
+
Use AI coding tools to generate `.dgmo` diagrams. This guide covers Claude Code, Copilot, Cursor, Windsurf, and any tool with an MCP client.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## MCP Server (recommended for Claude)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`@diagrammo/dgmo-mcp` provides an MCP server that gives Claude the ability to render, share, and look up DGMO diagrams directly — no file management needed.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**5 tools:** `render_diagram`, `share_diagram`, `open_in_app`, `list_chart_types`, `get_language_reference`
|
|
12
|
+
|
|
13
|
+
Add to `~/.claude/settings.json` (global) or `.claude/settings.local.json` (project):
|
|
12
14
|
|
|
13
15
|
```json
|
|
14
16
|
{
|
|
@@ -23,60 +25,46 @@ Setup (Claude Code — add to `.claude/settings.local.json`):
|
|
|
23
25
|
|
|
24
26
|
See `dgmo-mcp/README.md` for full configuration options.
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Copy the `.claude/skills/dgmo-*` directories from this repo into your project's `.claude/skills/` directory. This gives you four slash commands:
|
|
28
|
+
---
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|---------|-------------|
|
|
32
|
-
| `/dgmo-generate <description>` | Picks the best diagram type automatically |
|
|
33
|
-
| `/dgmo-sequence <flow>` | Generates a sequence diagram |
|
|
34
|
-
| `/dgmo-flowchart <process>` | Generates a flowchart |
|
|
35
|
-
| `/dgmo-chart <data description>` | Generates a data chart |
|
|
30
|
+
## Claude Code — Skill (slash command)
|
|
36
31
|
|
|
37
|
-
|
|
32
|
+
Installs a `/dgmo` slash command that gives Claude full dgmo context — all chart types, CLI flags, workflow, and tips.
|
|
38
33
|
|
|
39
34
|
```bash
|
|
40
|
-
|
|
41
|
-
cp -r node_modules/@diagrammo/dgmo/.claude/skills/dgmo-* .claude/skills/
|
|
42
|
-
|
|
43
|
-
# Or if dgmo is installed globally
|
|
44
|
-
cp -r $(npm root -g)/@diagrammo/dgmo/.claude/skills/dgmo-* .claude/skills/
|
|
35
|
+
dgmo --install-claude-skill
|
|
45
36
|
```
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
This copies a skill file into `~/.claude/commands/`, making `/dgmo` available in every Claude Code session.
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
/dgmo-generate an ER diagram for a blog with users, posts, and comments
|
|
51
|
-
/dgmo-sequence the OAuth2 authorization code flow
|
|
52
|
-
/dgmo-flowchart CI/CD pipeline with build, test, and deploy stages
|
|
53
|
-
/dgmo-chart quarterly revenue: Q1 100, Q2 120, Q3 110, Q4 130
|
|
54
|
-
```
|
|
40
|
+
---
|
|
55
41
|
|
|
56
42
|
## Claude Code — CLAUDE.md snippet
|
|
57
43
|
|
|
58
|
-
|
|
44
|
+
To teach Claude about DGMO in a specific project without the global skill, add this to your `CLAUDE.md`:
|
|
59
45
|
|
|
60
46
|
```markdown
|
|
61
47
|
## DGMO Diagrams
|
|
62
48
|
|
|
63
|
-
When the user asks for a diagram, generate a `.dgmo` file.
|
|
49
|
+
When the user asks for a diagram, generate a `.dgmo` file.
|
|
64
50
|
|
|
65
51
|
Quick reference:
|
|
66
|
-
- Sequence: `A -> B
|
|
52
|
+
- Sequence: `A -message-> B` or `A <-response- B`
|
|
67
53
|
- Flowchart: `(Start) -> [Process] -> <Decision?> -yes-> (End)`
|
|
68
54
|
- Bar chart: `chart: bar` then `Label: value` lines
|
|
69
55
|
- ER diagram: `chart: er` then table definitions and `table1 1--* table2` relationships
|
|
70
56
|
- Org chart: `chart: org` then indented hierarchy
|
|
71
57
|
|
|
72
|
-
Full reference:
|
|
58
|
+
Full reference: `node_modules/@diagrammo/dgmo/docs/language-reference.md`
|
|
73
59
|
|
|
74
|
-
Render with: `dgmo file.dgmo
|
|
60
|
+
Render with: `dgmo file.dgmo` (PNG) or `dgmo file.dgmo -o url` (shareable link).
|
|
75
61
|
```
|
|
76
62
|
|
|
63
|
+
---
|
|
64
|
+
|
|
77
65
|
## Other AI Tools — Prompt Files
|
|
78
66
|
|
|
79
|
-
DGMO ships
|
|
67
|
+
DGMO ships context files for popular AI coding tools, included in the npm package and auto-loaded when present in a project root.
|
|
80
68
|
|
|
81
69
|
| File | Tool | How it works |
|
|
82
70
|
|------|------|-------------|
|
|
@@ -84,42 +72,37 @@ DGMO ships prompt files for popular AI coding tools. These are included in the n
|
|
|
84
72
|
| `.cursorrules` | Cursor | Auto-loaded when present in project root |
|
|
85
73
|
| `.windsurfrules` | Windsurf | Auto-loaded when present in project root |
|
|
86
74
|
|
|
87
|
-
### Setup
|
|
88
|
-
|
|
89
75
|
Copy the relevant file into your project root:
|
|
90
76
|
|
|
91
77
|
```bash
|
|
92
|
-
# From node_modules
|
|
78
|
+
# From node_modules (if installed as a dependency)
|
|
93
79
|
cp node_modules/@diagrammo/dgmo/.cursorrules .
|
|
94
80
|
cp node_modules/@diagrammo/dgmo/.windsurfrules .
|
|
95
81
|
mkdir -p .github && cp node_modules/@diagrammo/dgmo/.github/copilot-instructions.md .github/
|
|
96
82
|
|
|
97
|
-
#
|
|
83
|
+
# From global npm install
|
|
98
84
|
cp $(npm root -g)/@diagrammo/dgmo/.cursorrules .
|
|
99
85
|
```
|
|
100
86
|
|
|
101
|
-
Each file contains a condensed DGMO syntax reference with examples
|
|
87
|
+
Each file contains a condensed DGMO syntax reference with examples, all chart types listed, rendering commands, and common mistakes to avoid.
|
|
102
88
|
|
|
103
|
-
|
|
89
|
+
---
|
|
104
90
|
|
|
105
|
-
|
|
91
|
+
## Rendering commands
|
|
106
92
|
|
|
107
93
|
```bash
|
|
108
|
-
# Install
|
|
109
|
-
npm install -g @diagrammo/dgmo # or: brew install diagrammo/dgmo/dgmo
|
|
110
|
-
|
|
111
|
-
# Render
|
|
112
94
|
dgmo diagram.dgmo # PNG output
|
|
113
95
|
dgmo diagram.dgmo -o output.svg # SVG output
|
|
114
|
-
dgmo diagram.dgmo -o url # Shareable URL
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
dgmo
|
|
118
|
-
dgmo --chart-types --json # List all chart types
|
|
96
|
+
dgmo diagram.dgmo -o url # Shareable diagrammo.app URL
|
|
97
|
+
dgmo diagram.dgmo -o url --copy # URL copied to clipboard
|
|
98
|
+
dgmo --chart-types # List all supported chart types
|
|
99
|
+
dgmo --chart-types --json # Machine-readable chart type list
|
|
119
100
|
```
|
|
120
101
|
|
|
102
|
+
---
|
|
103
|
+
|
|
121
104
|
## Supported chart types
|
|
122
105
|
|
|
123
106
|
Run `dgmo --chart-types` for the full list, or see `docs/language-reference.md`.
|
|
124
107
|
|
|
125
|
-
|
|
108
|
+
29 types: bar, line, area, multi-line, pie, doughnut, radar, polar-area, bar-stacked, scatter, sankey, chord, function, heatmap, funnel, slope, wordcloud, arc, timeline, venn, quadrant, sequence, flowchart, class, er, org, kanban, c4, initiative-status, infra.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diagrammo/dgmo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "DGMO diagram markup language — parser, renderer, and color system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,10 +26,11 @@
|
|
|
26
26
|
"dist",
|
|
27
27
|
"src",
|
|
28
28
|
"docs",
|
|
29
|
-
".claude/
|
|
29
|
+
".claude/commands",
|
|
30
30
|
".github/copilot-instructions.md",
|
|
31
31
|
".cursorrules",
|
|
32
|
-
".windsurfrules"
|
|
32
|
+
".windsurfrules",
|
|
33
|
+
"AGENTS.md"
|
|
33
34
|
],
|
|
34
35
|
"sideEffects": false,
|
|
35
36
|
"scripts": {
|
|
@@ -39,7 +40,9 @@
|
|
|
39
40
|
"test": "vitest run",
|
|
40
41
|
"test:watch": "vitest",
|
|
41
42
|
"gallery": "pnpm build && node scripts/generate-gallery.mjs",
|
|
42
|
-
"check:duplication": "jscpd ./src"
|
|
43
|
+
"check:duplication": "jscpd ./src",
|
|
44
|
+
"check:deadcode": "knip",
|
|
45
|
+
"postinstall": "node -e \"console.log('\\n💡 Claude Code user? Run: dgmo --install-claude-skill\\n')\""
|
|
43
46
|
},
|
|
44
47
|
"dependencies": {
|
|
45
48
|
"@dagrejs/dagre": "^2.0.4",
|
|
@@ -63,9 +66,9 @@
|
|
|
63
66
|
"@types/d3-scale": "^4.0.8",
|
|
64
67
|
"@types/d3-selection": "^3.0.11",
|
|
65
68
|
"@types/d3-shape": "^3.1.7",
|
|
66
|
-
"@types/dagre": "^0.7.54",
|
|
67
69
|
"@types/jsdom": "^28.0.0",
|
|
68
70
|
"jscpd": "^4.0.8",
|
|
71
|
+
"knip": "^6.0.1",
|
|
69
72
|
"tsup": "^8.5.1",
|
|
70
73
|
"typescript": "^5.7.3",
|
|
71
74
|
"vitest": "^4.0.18"
|
package/src/c4/layout.ts
CHANGED
|
@@ -86,7 +86,6 @@ const DESC_LINE_HEIGHT = 16;
|
|
|
86
86
|
const DESC_CHAR_WIDTH = 6.5;
|
|
87
87
|
const CARD_V_PAD = 14;
|
|
88
88
|
const CARD_H_PAD = 20;
|
|
89
|
-
const TECH_LINE_HEIGHT = 16;
|
|
90
89
|
const META_LINE_HEIGHT = 16;
|
|
91
90
|
const META_CHAR_WIDTH = 6.5;
|
|
92
91
|
const MARGIN = 40;
|
|
@@ -109,17 +108,31 @@ const LEGEND_CAPSULE_PAD = 4;
|
|
|
109
108
|
// Post-Layout Crossing Reduction
|
|
110
109
|
// ============================================================
|
|
111
110
|
|
|
111
|
+
interface NodeGeometry {
|
|
112
|
+
y: number;
|
|
113
|
+
width: number;
|
|
114
|
+
height: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Large penalty per edge-node collision — dominates the distance term so the
|
|
118
|
+
// sifter strongly prefers orderings where edges don't pass through other nodes.
|
|
119
|
+
const EDGE_NODE_COLLISION_WEIGHT = 5000;
|
|
120
|
+
|
|
112
121
|
/**
|
|
113
122
|
* Compute penalty for an edge ordering. Uses degree-weighted edge distance:
|
|
114
123
|
* long edges to high-degree nodes are penalized more than to low-degree nodes.
|
|
115
124
|
* This places shared/important nodes closer to their neighbors, reducing
|
|
116
125
|
* visual edge congestion.
|
|
117
126
|
*
|
|
127
|
+
* When nodeGeometry is provided, also adds a heavy penalty for each case where
|
|
128
|
+
* a straight-line edge bounding box overlaps another node — driving the sifter
|
|
129
|
+
* to prefer orderings that avoid edge-node collisions.
|
|
118
130
|
*/
|
|
119
131
|
function computeEdgePenalty(
|
|
120
132
|
edgeList: { source: string; target: string }[],
|
|
121
133
|
nodePositions: Map<string, number>,
|
|
122
|
-
degrees: Map<string, number
|
|
134
|
+
degrees: Map<string, number>,
|
|
135
|
+
nodeGeometry?: Map<string, NodeGeometry>
|
|
123
136
|
): number {
|
|
124
137
|
let penalty = 0;
|
|
125
138
|
|
|
@@ -135,6 +148,48 @@ function computeEdgePenalty(
|
|
|
135
148
|
penalty += dist * weight;
|
|
136
149
|
}
|
|
137
150
|
|
|
151
|
+
// Edge-node collision penalty: for each edge A→B, check if any other node C
|
|
152
|
+
// has its bounding box inside the straight-line bounding box of the edge.
|
|
153
|
+
// Uses x from nodePositions (updated per permutation) and y/size from nodeGeometry.
|
|
154
|
+
if (nodeGeometry) {
|
|
155
|
+
for (const edge of edgeList) {
|
|
156
|
+
const geomA = nodeGeometry.get(edge.source);
|
|
157
|
+
const geomB = nodeGeometry.get(edge.target);
|
|
158
|
+
if (!geomA || !geomB) continue;
|
|
159
|
+
|
|
160
|
+
const ax = nodePositions.get(edge.source) ?? 0;
|
|
161
|
+
const bx = nodePositions.get(edge.target) ?? 0;
|
|
162
|
+
const ay = geomA.y;
|
|
163
|
+
const by = geomB.y;
|
|
164
|
+
|
|
165
|
+
// Skip edges within the same rank — they have no vertical span to check
|
|
166
|
+
if (ay === by) continue;
|
|
167
|
+
|
|
168
|
+
const edgeMinX = Math.min(ax, bx);
|
|
169
|
+
const edgeMaxX = Math.max(ax, bx);
|
|
170
|
+
const edgeMinY = Math.min(ay, by);
|
|
171
|
+
const edgeMaxY = Math.max(ay, by);
|
|
172
|
+
|
|
173
|
+
for (const [name, geomC] of nodeGeometry) {
|
|
174
|
+
if (name === edge.source || name === edge.target) continue;
|
|
175
|
+
const cx = nodePositions.get(name) ?? 0;
|
|
176
|
+
const cy = geomC.y;
|
|
177
|
+
const hw = geomC.width / 2;
|
|
178
|
+
const hh = geomC.height / 2;
|
|
179
|
+
|
|
180
|
+
// AABB overlap: node C's box intersects the edge's straight-line bounding box
|
|
181
|
+
if (
|
|
182
|
+
cx + hw > edgeMinX &&
|
|
183
|
+
cx - hw < edgeMaxX &&
|
|
184
|
+
cy + hh > edgeMinY &&
|
|
185
|
+
cy - hh < edgeMaxY
|
|
186
|
+
) {
|
|
187
|
+
penalty += EDGE_NODE_COLLISION_WEIGHT;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
138
193
|
return penalty;
|
|
139
194
|
}
|
|
140
195
|
|
|
@@ -160,6 +215,13 @@ function reduceCrossings(
|
|
|
160
215
|
degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
|
|
161
216
|
}
|
|
162
217
|
|
|
218
|
+
// Build geometry map for edge-node collision scoring
|
|
219
|
+
const nodeGeometry = new Map<string, NodeGeometry>();
|
|
220
|
+
for (const name of g.nodes()) {
|
|
221
|
+
const pos = g.node(name);
|
|
222
|
+
if (pos) nodeGeometry.set(name, { y: pos.y, width: pos.width, height: pos.height });
|
|
223
|
+
}
|
|
224
|
+
|
|
163
225
|
// Group nodes by rank
|
|
164
226
|
const rankMap = new Map<number, string[]>();
|
|
165
227
|
for (const name of g.nodes()) {
|
|
@@ -216,7 +278,7 @@ function reduceCrossings(
|
|
|
216
278
|
}
|
|
217
279
|
|
|
218
280
|
// Current penalty
|
|
219
|
-
const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees);
|
|
281
|
+
const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees, nodeGeometry);
|
|
220
282
|
|
|
221
283
|
// Try permutations (feasible for partition sizes ≤ 8)
|
|
222
284
|
let bestPerm = [...partition];
|
|
@@ -229,7 +291,7 @@ function reduceCrossings(
|
|
|
229
291
|
for (let i = 0; i < perm.length; i++) {
|
|
230
292
|
testPositions.set(perm[i]!, xSlots[i]!);
|
|
231
293
|
}
|
|
232
|
-
const penalty = computeEdgePenalty(edgeList, testPositions, degrees);
|
|
294
|
+
const penalty = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
|
|
233
295
|
if (penalty < bestPenalty) {
|
|
234
296
|
bestPenalty = penalty;
|
|
235
297
|
bestPerm = [...perm];
|
|
@@ -248,14 +310,14 @@ function reduceCrossings(
|
|
|
248
310
|
for (let k = 0; k < workingOrder.length; k++) {
|
|
249
311
|
testPositions.set(workingOrder[k]!, xSlots[k]!);
|
|
250
312
|
}
|
|
251
|
-
const before = computeEdgePenalty(edgeList, testPositions, degrees);
|
|
313
|
+
const before = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
|
|
252
314
|
|
|
253
315
|
[workingOrder[i], workingOrder[i + 1]] = [workingOrder[i + 1]!, workingOrder[i]!];
|
|
254
316
|
const testPositions2 = new Map(basePositions);
|
|
255
317
|
for (let k = 0; k < workingOrder.length; k++) {
|
|
256
318
|
testPositions2.set(workingOrder[k]!, xSlots[k]!);
|
|
257
319
|
}
|
|
258
|
-
const after = computeEdgePenalty(edgeList, testPositions2, degrees);
|
|
320
|
+
const after = computeEdgePenalty(edgeList, testPositions2, degrees, nodeGeometry);
|
|
259
321
|
|
|
260
322
|
if (after < before) {
|
|
261
323
|
improved = true;
|
|
@@ -415,10 +477,6 @@ export function rollUpContextRelationships(parsed: ParsedC4): ContextRelationshi
|
|
|
415
477
|
const allRels = collectAllRelationships(parsed.elements, ownerMap);
|
|
416
478
|
|
|
417
479
|
// Also include orphan relationships
|
|
418
|
-
for (const rel of parsed.relationships) {
|
|
419
|
-
// Orphan rels have no source element name — skip them for context roll-up
|
|
420
|
-
}
|
|
421
|
-
|
|
422
480
|
// Separate system-level (explicit) from nested (rolled-up)
|
|
423
481
|
const topLevelNames = new Set(parsed.elements.map((e) => e.name));
|
|
424
482
|
const explicitKeys = new Set<string>();
|
package/src/c4/parser.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// ============================================================
|
|
4
4
|
|
|
5
5
|
import type { PaletteColors } from '../palettes';
|
|
6
|
-
import type { DgmoError } from '../diagnostics';
|
|
7
6
|
import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
8
7
|
import type { TagGroup } from '../utils/tag-groups';
|
|
9
8
|
import { matchTagBlockHeading } from '../utils/tag-groups';
|
|
@@ -751,21 +750,6 @@ function attachElement(
|
|
|
751
750
|
// Post-parse validation
|
|
752
751
|
// ============================================================
|
|
753
752
|
|
|
754
|
-
function collectAllNames(result: ParsedC4): Map<string, number> {
|
|
755
|
-
const names = new Map<string, number>();
|
|
756
|
-
function walk(elements: C4Element[]) {
|
|
757
|
-
for (const el of elements) {
|
|
758
|
-
names.set(el.name.toLowerCase(), el.lineNumber);
|
|
759
|
-
walk(el.children);
|
|
760
|
-
for (const g of el.groups) {
|
|
761
|
-
walk(g.children);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
walk(result.elements);
|
|
766
|
-
return names;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
753
|
function validateRelationshipTargets(
|
|
770
754
|
result: ParsedC4,
|
|
771
755
|
knownNames: Map<string, number>,
|
package/src/c4/renderer.ts
CHANGED
|
@@ -9,8 +9,7 @@ import type { PaletteColors } from '../palettes';
|
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import { renderInlineText } from '../utils/inline-markdown';
|
|
11
11
|
import type { ParsedC4 } from './types';
|
|
12
|
-
import type {
|
|
13
|
-
import type { C4LayoutResult, C4LayoutNode, C4LayoutEdge, C4LayoutBoundary, C4LegendGroup } from './layout';
|
|
12
|
+
import type { C4LayoutResult, C4LayoutEdge, C4LegendGroup } from './layout';
|
|
14
13
|
import { parseC4 } from './parser';
|
|
15
14
|
import { layoutC4Context, layoutC4Containers, layoutC4Components, layoutC4Deployment, collectCardMetadata } from './layout';
|
|
16
15
|
import {
|
|
@@ -50,7 +49,6 @@ const CARD_V_PAD = 14;
|
|
|
50
49
|
const TYPE_LABEL_HEIGHT = 18;
|
|
51
50
|
const DIVIDER_GAP = 6;
|
|
52
51
|
const NAME_HEIGHT = 20;
|
|
53
|
-
const TECH_LINE_HEIGHT = 16;
|
|
54
52
|
const META_FONT_SIZE = 11;
|
|
55
53
|
const META_CHAR_WIDTH = 6.5;
|
|
56
54
|
const META_LINE_HEIGHT = 16;
|
|
@@ -257,7 +255,6 @@ export function renderC4Context(
|
|
|
257
255
|
const scale = Math.min(MAX_SCALE, scaleX, scaleY);
|
|
258
256
|
|
|
259
257
|
const scaledW = diagramW * scale;
|
|
260
|
-
const scaledH = diagramH * scale;
|
|
261
258
|
const offsetX = (width - scaledW) / 2;
|
|
262
259
|
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
263
260
|
|
|
@@ -1300,7 +1297,6 @@ export function renderC4Containers(
|
|
|
1300
1297
|
const scale = Math.min(MAX_SCALE, scaleX, scaleY);
|
|
1301
1298
|
|
|
1302
1299
|
const scaledW = diagramW * scale;
|
|
1303
|
-
const scaledH = diagramH * scale;
|
|
1304
1300
|
const offsetX = (width - scaledW) / 2;
|
|
1305
1301
|
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
1306
1302
|
|
package/src/class/layout.ts
CHANGED
package/src/class/parser.ts
CHANGED
|
@@ -383,3 +383,31 @@ export function looksLikeClassDiagram(content: string): boolean {
|
|
|
383
383
|
|
|
384
384
|
return false;
|
|
385
385
|
}
|
|
386
|
+
|
|
387
|
+
// ============================================================
|
|
388
|
+
// Symbol extraction (for completion API)
|
|
389
|
+
// ============================================================
|
|
390
|
+
|
|
391
|
+
import type { DiagramSymbols } from '../completion';
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Extract class names (entities) from class diagram document text.
|
|
395
|
+
* Used by the dgmo completion API for ghost hints and popup completions.
|
|
396
|
+
*/
|
|
397
|
+
export function extractSymbols(docText: string): DiagramSymbols {
|
|
398
|
+
const entities: string[] = [];
|
|
399
|
+
let inMetadata = true;
|
|
400
|
+
for (const rawLine of docText.split('\n')) {
|
|
401
|
+
const line = rawLine.trim();
|
|
402
|
+
if (inMetadata && /^[a-z-]+\s*:/i.test(line)) continue;
|
|
403
|
+
inMetadata = false;
|
|
404
|
+
if (line.length === 0 || /^\s/.test(rawLine)) continue;
|
|
405
|
+
const m = CLASS_DECL_RE.exec(line);
|
|
406
|
+
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
kind: 'class',
|
|
410
|
+
entities,
|
|
411
|
+
keywords: ['extends', 'implements', 'abstract', 'interface', 'enum'],
|
|
412
|
+
};
|
|
413
|
+
}
|
package/src/class/renderer.ts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
import * as d3Selection from 'd3-selection';
|
|
6
6
|
import * as d3Shape from 'd3-shape';
|
|
7
7
|
import { FONT_FAMILY } from '../fonts';
|
|
8
|
+
import { runInExportContainer, extractExportSvg } from '../utils/export-container';
|
|
8
9
|
import type { PaletteColors } from '../palettes';
|
|
9
10
|
import { mix } from '../palettes/color-utils';
|
|
10
11
|
import type { ParsedClassDiagram, ClassModifier, RelationshipType } from './types';
|
|
11
|
-
import type { ClassLayoutResult
|
|
12
|
+
import type { ClassLayoutResult } from './layout';
|
|
12
13
|
import { parseClassDiagram } from './parser';
|
|
13
14
|
import { layoutClassDiagram } from './layout';
|
|
14
15
|
|
|
@@ -99,8 +100,6 @@ function isSourceMarker(type: RelationshipType): boolean {
|
|
|
99
100
|
// Main renderer
|
|
100
101
|
// ============================================================
|
|
101
102
|
|
|
102
|
-
type GSelection = d3Selection.Selection<SVGGElement, unknown, null, undefined>;
|
|
103
|
-
|
|
104
103
|
export function renderClassDiagram(
|
|
105
104
|
container: HTMLDivElement,
|
|
106
105
|
parsed: ParsedClassDiagram,
|
|
@@ -125,7 +124,6 @@ export function renderClassDiagram(
|
|
|
125
124
|
const scale = Math.min(MAX_SCALE, scaleX, scaleY);
|
|
126
125
|
|
|
127
126
|
const scaledW = diagramW * scale;
|
|
128
|
-
const scaledH = diagramH * scale;
|
|
129
127
|
const offsetX = (width - scaledW) / 2;
|
|
130
128
|
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
131
129
|
|
|
@@ -506,16 +504,10 @@ export function renderClassDiagramForExport(
|
|
|
506
504
|
const layout = layoutClassDiagram(parsed);
|
|
507
505
|
const isDark = theme === 'dark';
|
|
508
506
|
|
|
509
|
-
const container = document.createElement('div');
|
|
510
507
|
const exportWidth = layout.width + DIAGRAM_PADDING * 2;
|
|
511
508
|
const exportHeight = layout.height + DIAGRAM_PADDING * 2 + (parsed.title ? 40 : 0);
|
|
512
|
-
container.style.width = `${exportWidth}px`;
|
|
513
|
-
container.style.height = `${exportHeight}px`;
|
|
514
|
-
container.style.position = 'absolute';
|
|
515
|
-
container.style.left = '-9999px';
|
|
516
|
-
document.body.appendChild(container);
|
|
517
509
|
|
|
518
|
-
|
|
510
|
+
return runInExportContainer(exportWidth, exportHeight, (container) => {
|
|
519
511
|
renderClassDiagram(
|
|
520
512
|
container,
|
|
521
513
|
parsed,
|
|
@@ -525,19 +517,6 @@ export function renderClassDiagramForExport(
|
|
|
525
517
|
undefined,
|
|
526
518
|
{ width: exportWidth, height: exportHeight }
|
|
527
519
|
);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (!svgEl) return '';
|
|
531
|
-
|
|
532
|
-
if (theme === 'transparent') {
|
|
533
|
-
svgEl.style.background = 'none';
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
537
|
-
svgEl.style.fontFamily = FONT_FAMILY;
|
|
538
|
-
|
|
539
|
-
return svgEl.outerHTML;
|
|
540
|
-
} finally {
|
|
541
|
-
document.body.removeChild(container);
|
|
542
|
-
}
|
|
520
|
+
return extractExportSvg(container, theme);
|
|
521
|
+
});
|
|
543
522
|
}
|