@diagrammo/dgmo 0.6.0 → 0.6.2
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 +76 -0
- package/dist/cli.cjs +164 -162
- package/dist/index.cjs +1146 -647
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -21
- package/dist/index.d.ts +9 -21
- package/dist/index.js +1146 -647
- package/dist/index.js.map +1 -1
- package/docs/ai-integration.md +33 -50
- package/package.json +4 -3
- package/src/c4/layout.ts +75 -72
- package/src/c4/renderer.ts +122 -119
- package/src/cli.ts +130 -40
- package/src/d3.ts +55 -35
- package/src/echarts.ts +24 -24
- package/src/er/classify.ts +206 -0
- package/src/er/layout.ts +259 -94
- package/src/er/renderer.ts +246 -26
- package/src/index.ts +2 -2
- package/src/infra/compute.ts +1 -21
- package/src/infra/layout.ts +60 -13
- package/src/infra/parser.ts +5 -32
- package/src/infra/renderer.ts +403 -196
- package/src/infra/types.ts +1 -11
- package/src/initiative-status/layout.ts +46 -27
- package/src/kanban/renderer.ts +28 -24
- package/src/org/renderer.ts +24 -23
- package/src/render.ts +2 -2
- package/src/sequence/renderer.ts +24 -19
- package/src/sitemap/layout.ts +7 -14
- package/src/sitemap/renderer.ts +30 -29
- package/src/utils/legend-constants.ts +25 -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.2",
|
|
4
4
|
"description": "DGMO diagram markup language — parser, renderer, and color system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dist",
|
|
27
27
|
"src",
|
|
28
28
|
"docs",
|
|
29
|
-
".claude/
|
|
29
|
+
".claude/commands",
|
|
30
30
|
".github/copilot-instructions.md",
|
|
31
31
|
".cursorrules",
|
|
32
32
|
".windsurfrules"
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"test": "vitest run",
|
|
40
40
|
"test:watch": "vitest",
|
|
41
41
|
"gallery": "pnpm build && node scripts/generate-gallery.mjs",
|
|
42
|
-
"check:duplication": "jscpd ./src"
|
|
42
|
+
"check:duplication": "jscpd ./src",
|
|
43
|
+
"postinstall": "node -e \"console.log('\\n💡 Claude Code user? Run: dgmo --install-claude-skill\\n')\""
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"@dagrejs/dagre": "^2.0.4",
|
package/src/c4/layout.ts
CHANGED
|
@@ -109,17 +109,31 @@ const LEGEND_CAPSULE_PAD = 4;
|
|
|
109
109
|
// Post-Layout Crossing Reduction
|
|
110
110
|
// ============================================================
|
|
111
111
|
|
|
112
|
+
interface NodeGeometry {
|
|
113
|
+
y: number;
|
|
114
|
+
width: number;
|
|
115
|
+
height: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Large penalty per edge-node collision — dominates the distance term so the
|
|
119
|
+
// sifter strongly prefers orderings where edges don't pass through other nodes.
|
|
120
|
+
const EDGE_NODE_COLLISION_WEIGHT = 5000;
|
|
121
|
+
|
|
112
122
|
/**
|
|
113
123
|
* Compute penalty for an edge ordering. Uses degree-weighted edge distance:
|
|
114
124
|
* long edges to high-degree nodes are penalized more than to low-degree nodes.
|
|
115
125
|
* This places shared/important nodes closer to their neighbors, reducing
|
|
116
126
|
* visual edge congestion.
|
|
117
127
|
*
|
|
128
|
+
* When nodeGeometry is provided, also adds a heavy penalty for each case where
|
|
129
|
+
* a straight-line edge bounding box overlaps another node — driving the sifter
|
|
130
|
+
* to prefer orderings that avoid edge-node collisions.
|
|
118
131
|
*/
|
|
119
132
|
function computeEdgePenalty(
|
|
120
133
|
edgeList: { source: string; target: string }[],
|
|
121
134
|
nodePositions: Map<string, number>,
|
|
122
|
-
degrees: Map<string, number
|
|
135
|
+
degrees: Map<string, number>,
|
|
136
|
+
nodeGeometry?: Map<string, NodeGeometry>
|
|
123
137
|
): number {
|
|
124
138
|
let penalty = 0;
|
|
125
139
|
|
|
@@ -135,6 +149,48 @@ function computeEdgePenalty(
|
|
|
135
149
|
penalty += dist * weight;
|
|
136
150
|
}
|
|
137
151
|
|
|
152
|
+
// Edge-node collision penalty: for each edge A→B, check if any other node C
|
|
153
|
+
// has its bounding box inside the straight-line bounding box of the edge.
|
|
154
|
+
// Uses x from nodePositions (updated per permutation) and y/size from nodeGeometry.
|
|
155
|
+
if (nodeGeometry) {
|
|
156
|
+
for (const edge of edgeList) {
|
|
157
|
+
const geomA = nodeGeometry.get(edge.source);
|
|
158
|
+
const geomB = nodeGeometry.get(edge.target);
|
|
159
|
+
if (!geomA || !geomB) continue;
|
|
160
|
+
|
|
161
|
+
const ax = nodePositions.get(edge.source) ?? 0;
|
|
162
|
+
const bx = nodePositions.get(edge.target) ?? 0;
|
|
163
|
+
const ay = geomA.y;
|
|
164
|
+
const by = geomB.y;
|
|
165
|
+
|
|
166
|
+
// Skip edges within the same rank — they have no vertical span to check
|
|
167
|
+
if (ay === by) continue;
|
|
168
|
+
|
|
169
|
+
const edgeMinX = Math.min(ax, bx);
|
|
170
|
+
const edgeMaxX = Math.max(ax, bx);
|
|
171
|
+
const edgeMinY = Math.min(ay, by);
|
|
172
|
+
const edgeMaxY = Math.max(ay, by);
|
|
173
|
+
|
|
174
|
+
for (const [name, geomC] of nodeGeometry) {
|
|
175
|
+
if (name === edge.source || name === edge.target) continue;
|
|
176
|
+
const cx = nodePositions.get(name) ?? 0;
|
|
177
|
+
const cy = geomC.y;
|
|
178
|
+
const hw = geomC.width / 2;
|
|
179
|
+
const hh = geomC.height / 2;
|
|
180
|
+
|
|
181
|
+
// AABB overlap: node C's box intersects the edge's straight-line bounding box
|
|
182
|
+
if (
|
|
183
|
+
cx + hw > edgeMinX &&
|
|
184
|
+
cx - hw < edgeMaxX &&
|
|
185
|
+
cy + hh > edgeMinY &&
|
|
186
|
+
cy - hh < edgeMaxY
|
|
187
|
+
) {
|
|
188
|
+
penalty += EDGE_NODE_COLLISION_WEIGHT;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
138
194
|
return penalty;
|
|
139
195
|
}
|
|
140
196
|
|
|
@@ -160,6 +216,13 @@ function reduceCrossings(
|
|
|
160
216
|
degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
|
|
161
217
|
}
|
|
162
218
|
|
|
219
|
+
// Build geometry map for edge-node collision scoring
|
|
220
|
+
const nodeGeometry = new Map<string, NodeGeometry>();
|
|
221
|
+
for (const name of g.nodes()) {
|
|
222
|
+
const pos = g.node(name);
|
|
223
|
+
if (pos) nodeGeometry.set(name, { y: pos.y, width: pos.width, height: pos.height });
|
|
224
|
+
}
|
|
225
|
+
|
|
163
226
|
// Group nodes by rank
|
|
164
227
|
const rankMap = new Map<number, string[]>();
|
|
165
228
|
for (const name of g.nodes()) {
|
|
@@ -216,7 +279,7 @@ function reduceCrossings(
|
|
|
216
279
|
}
|
|
217
280
|
|
|
218
281
|
// Current penalty
|
|
219
|
-
const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees);
|
|
282
|
+
const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees, nodeGeometry);
|
|
220
283
|
|
|
221
284
|
// Try permutations (feasible for partition sizes ≤ 8)
|
|
222
285
|
let bestPerm = [...partition];
|
|
@@ -229,7 +292,7 @@ function reduceCrossings(
|
|
|
229
292
|
for (let i = 0; i < perm.length; i++) {
|
|
230
293
|
testPositions.set(perm[i]!, xSlots[i]!);
|
|
231
294
|
}
|
|
232
|
-
const penalty = computeEdgePenalty(edgeList, testPositions, degrees);
|
|
295
|
+
const penalty = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
|
|
233
296
|
if (penalty < bestPenalty) {
|
|
234
297
|
bestPenalty = penalty;
|
|
235
298
|
bestPerm = [...perm];
|
|
@@ -248,14 +311,14 @@ function reduceCrossings(
|
|
|
248
311
|
for (let k = 0; k < workingOrder.length; k++) {
|
|
249
312
|
testPositions.set(workingOrder[k]!, xSlots[k]!);
|
|
250
313
|
}
|
|
251
|
-
const before = computeEdgePenalty(edgeList, testPositions, degrees);
|
|
314
|
+
const before = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
|
|
252
315
|
|
|
253
316
|
[workingOrder[i], workingOrder[i + 1]] = [workingOrder[i + 1]!, workingOrder[i]!];
|
|
254
317
|
const testPositions2 = new Map(basePositions);
|
|
255
318
|
for (let k = 0; k < workingOrder.length; k++) {
|
|
256
319
|
testPositions2.set(workingOrder[k]!, xSlots[k]!);
|
|
257
320
|
}
|
|
258
|
-
const after = computeEdgePenalty(edgeList, testPositions2, degrees);
|
|
321
|
+
const after = computeEdgePenalty(edgeList, testPositions2, degrees, nodeGeometry);
|
|
259
322
|
|
|
260
323
|
if (after < before) {
|
|
261
324
|
improved = true;
|
|
@@ -603,19 +666,12 @@ export function computeC4NodeDimensions(
|
|
|
603
666
|
// Legend Helpers
|
|
604
667
|
// ============================================================
|
|
605
668
|
|
|
606
|
-
function computeLegendGroups(
|
|
607
|
-
tagGroups: OrgTagGroup[],
|
|
608
|
-
usedValuesByGroup?: Map<string, Set<string>>
|
|
609
|
-
): C4LegendGroup[] {
|
|
669
|
+
function computeLegendGroups(tagGroups: OrgTagGroup[]): C4LegendGroup[] {
|
|
610
670
|
const result: C4LegendGroup[] = [];
|
|
611
671
|
|
|
612
672
|
for (const group of tagGroups) {
|
|
613
673
|
const entries: C4LegendEntry[] = [];
|
|
614
674
|
for (const entry of group.entries) {
|
|
615
|
-
if (usedValuesByGroup) {
|
|
616
|
-
const used = usedValuesByGroup.get(group.name.toLowerCase());
|
|
617
|
-
if (!used?.has(entry.value.toLowerCase())) continue;
|
|
618
|
-
}
|
|
619
675
|
entries.push({ value: entry.value, color: entry.color });
|
|
620
676
|
}
|
|
621
677
|
if (entries.length === 0) continue;
|
|
@@ -813,20 +869,9 @@ export function layoutC4Context(
|
|
|
813
869
|
let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN * 2 : 0;
|
|
814
870
|
let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN * 2 : 0;
|
|
815
871
|
|
|
816
|
-
// Legend
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
for (const group of parsed.tagGroups) {
|
|
820
|
-
const key = group.name.toLowerCase();
|
|
821
|
-
const val = el.metadata[key];
|
|
822
|
-
if (val) {
|
|
823
|
-
if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
|
|
824
|
-
usedValuesByGroup.get(key)!.add(val.toLowerCase());
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const legendGroups = computeLegendGroups(parsed.tagGroups, usedValuesByGroup);
|
|
872
|
+
// Legend: show all defined tag groups and entries so users see the full
|
|
873
|
+
// tag vocabulary regardless of which elements are visible at this view level.
|
|
874
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
830
875
|
|
|
831
876
|
// Position legend below diagram
|
|
832
877
|
if (legendGroups.length > 0) {
|
|
@@ -1236,20 +1281,7 @@ export function layoutC4Containers(
|
|
|
1236
1281
|
let totalWidth = maxX - minX + MARGIN * 2;
|
|
1237
1282
|
let totalHeight = maxY - minY + MARGIN * 2;
|
|
1238
1283
|
|
|
1239
|
-
|
|
1240
|
-
const usedValuesByGroup = new Map<string, Set<string>>();
|
|
1241
|
-
for (const el of [...containers, ...externals]) {
|
|
1242
|
-
for (const group of parsed.tagGroups) {
|
|
1243
|
-
const key = group.name.toLowerCase();
|
|
1244
|
-
const val = el.metadata[key];
|
|
1245
|
-
if (val) {
|
|
1246
|
-
if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
|
|
1247
|
-
usedValuesByGroup.get(key)!.add(val.toLowerCase());
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
const legendGroups = computeLegendGroups(parsed.tagGroups, usedValuesByGroup);
|
|
1284
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
1253
1285
|
|
|
1254
1286
|
// Position legend below diagram
|
|
1255
1287
|
if (legendGroups.length > 0) {
|
|
@@ -1722,24 +1754,8 @@ export function layoutC4Components(
|
|
|
1722
1754
|
let totalWidth = maxX - minX + MARGIN * 2;
|
|
1723
1755
|
let totalHeight = maxY - minY + MARGIN * 2;
|
|
1724
1756
|
|
|
1725
|
-
// Legend
|
|
1726
|
-
const usedValuesByGroup = new Map<string, Set<string>>();
|
|
1727
|
-
for (const el of [...components, ...externals]) {
|
|
1728
|
-
for (const group of parsed.tagGroups) {
|
|
1729
|
-
const key = group.name.toLowerCase();
|
|
1730
|
-
// Check element + ancestors for inherited values
|
|
1731
|
-
let val = el.metadata[key];
|
|
1732
|
-
if (!val && components.includes(el)) {
|
|
1733
|
-
val = targetContainer.metadata[key] ?? system.metadata[key];
|
|
1734
|
-
}
|
|
1735
|
-
if (val) {
|
|
1736
|
-
if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
|
|
1737
|
-
usedValuesByGroup.get(key)!.add(val.toLowerCase());
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
1757
|
|
|
1742
|
-
const legendGroups = computeLegendGroups(parsed.tagGroups
|
|
1758
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
1743
1759
|
|
|
1744
1760
|
// Position legend below diagram
|
|
1745
1761
|
if (legendGroups.length > 0) {
|
|
@@ -2104,20 +2120,7 @@ export function layoutC4Deployment(
|
|
|
2104
2120
|
let totalWidth = maxX - minX + MARGIN * 2;
|
|
2105
2121
|
let totalHeight = maxY - minY + MARGIN * 2;
|
|
2106
2122
|
|
|
2107
|
-
|
|
2108
|
-
const usedValuesByGroup = new Map<string, Set<string>>();
|
|
2109
|
-
for (const r of refEntries) {
|
|
2110
|
-
for (const group of parsed.tagGroups) {
|
|
2111
|
-
const key = group.name.toLowerCase();
|
|
2112
|
-
const val = r.element.metadata[key];
|
|
2113
|
-
if (val) {
|
|
2114
|
-
if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
|
|
2115
|
-
usedValuesByGroup.get(key)!.add(val.toLowerCase());
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
const legendGroups = computeLegendGroups(parsed.tagGroups, usedValuesByGroup);
|
|
2123
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups);
|
|
2121
2124
|
|
|
2122
2125
|
if (legendGroups.length > 0) {
|
|
2123
2126
|
const legendY = totalHeight + MARGIN;
|