@diagrammo/dgmo 0.8.9 → 0.8.11
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/AGENTS.md +3 -0
- package/dist/cli.cjs +245 -672
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +2 -3
- package/dist/editor.d.ts +2 -3
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +1623 -800
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +153 -1
- package/dist/index.d.ts +153 -1
- package/dist/index.js +1619 -802
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +28 -2
- package/gallery/fixtures/sitemap-full.dgmo +1 -0
- package/package.json +14 -17
- package/src/boxes-and-lines/layout.ts +48 -8
- package/src/boxes-and-lines/parser.ts +59 -13
- package/src/boxes-and-lines/renderer.ts +34 -138
- package/src/c4/layout.ts +31 -10
- package/src/c4/renderer.ts +25 -138
- package/src/class/renderer.ts +185 -186
- package/src/d3.ts +194 -222
- package/src/echarts.ts +56 -57
- package/src/editor/index.ts +1 -2
- package/src/er/renderer.ts +52 -245
- package/src/gantt/renderer.ts +140 -182
- package/src/gantt/resolver.ts +19 -14
- package/src/index.ts +23 -1
- package/src/infra/renderer.ts +91 -244
- package/src/kanban/renderer.ts +29 -133
- package/src/label-layout.ts +286 -0
- package/src/org/renderer.ts +103 -170
- package/src/render.ts +39 -9
- package/src/sequence/parser.ts +4 -0
- package/src/sequence/renderer.ts +47 -154
- package/src/sitemap/layout.ts +180 -38
- package/src/sitemap/parser.ts +64 -23
- package/src/sitemap/renderer.ts +73 -161
- package/src/utils/arrows.ts +1 -1
- package/src/utils/legend-constants.ts +6 -0
- package/src/utils/legend-d3.ts +400 -0
- package/src/utils/legend-layout.ts +491 -0
- package/src/utils/legend-svg.ts +28 -2
- package/src/utils/legend-types.ts +166 -0
- package/src/utils/parsing.ts +1 -1
- package/src/utils/tag-groups.ts +1 -1
package/src/class/renderer.ts
CHANGED
|
@@ -5,22 +5,25 @@
|
|
|
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';
|
|
9
8
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
runInExportContainer,
|
|
10
|
+
extractExportSvg,
|
|
11
|
+
} from '../utils/export-container';
|
|
12
|
+
import { LEGEND_HEIGHT } from '../utils/legend-constants';
|
|
13
|
+
import { renderLegendD3 } from '../utils/legend-d3';
|
|
14
|
+
import type { LegendConfig, LegendState } from '../utils/legend-types';
|
|
15
|
+
import {
|
|
16
|
+
TITLE_FONT_SIZE,
|
|
17
|
+
TITLE_FONT_WEIGHT,
|
|
18
|
+
TITLE_Y,
|
|
19
|
+
} from '../utils/title-constants';
|
|
21
20
|
import type { PaletteColors } from '../palettes';
|
|
22
21
|
import { mix } from '../palettes/color-utils';
|
|
23
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
ParsedClassDiagram,
|
|
24
|
+
ClassModifier,
|
|
25
|
+
RelationshipType,
|
|
26
|
+
} from './types';
|
|
24
27
|
import type { ClassLayoutResult } from './layout';
|
|
25
28
|
import { parseClassDiagram } from './parser';
|
|
26
29
|
import { layoutClassDiagram } from './layout';
|
|
@@ -44,22 +47,41 @@ const MEMBER_PADDING_X = 10;
|
|
|
44
47
|
// Color helpers
|
|
45
48
|
// ============================================================
|
|
46
49
|
|
|
47
|
-
function modifierColor(
|
|
50
|
+
function modifierColor(
|
|
51
|
+
modifier: ClassModifier | undefined,
|
|
52
|
+
palette: PaletteColors,
|
|
53
|
+
colorOff?: boolean
|
|
54
|
+
): string {
|
|
48
55
|
if (colorOff) return palette.textMuted;
|
|
49
56
|
switch (modifier) {
|
|
50
|
-
case 'interface':
|
|
51
|
-
|
|
52
|
-
case '
|
|
53
|
-
|
|
57
|
+
case 'interface':
|
|
58
|
+
return palette.colors.blue;
|
|
59
|
+
case 'abstract':
|
|
60
|
+
return palette.colors.purple;
|
|
61
|
+
case 'enum':
|
|
62
|
+
return palette.colors.yellow;
|
|
63
|
+
default:
|
|
64
|
+
return palette.colors.teal;
|
|
54
65
|
}
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
function nodeFill(
|
|
68
|
+
function nodeFill(
|
|
69
|
+
palette: PaletteColors,
|
|
70
|
+
isDark: boolean,
|
|
71
|
+
modifier: ClassModifier | undefined,
|
|
72
|
+
nodeColor?: string,
|
|
73
|
+
colorOff?: boolean
|
|
74
|
+
): string {
|
|
58
75
|
const color = nodeColor ?? modifierColor(modifier, palette, colorOff);
|
|
59
76
|
return mix(color, isDark ? palette.surface : palette.bg, 25);
|
|
60
77
|
}
|
|
61
78
|
|
|
62
|
-
function nodeStroke(
|
|
79
|
+
function nodeStroke(
|
|
80
|
+
palette: PaletteColors,
|
|
81
|
+
modifier: ClassModifier | undefined,
|
|
82
|
+
nodeColor?: string,
|
|
83
|
+
colorOff?: boolean
|
|
84
|
+
): string {
|
|
63
85
|
return nodeColor ?? modifierColor(modifier, palette, colorOff);
|
|
64
86
|
}
|
|
65
87
|
|
|
@@ -73,10 +95,10 @@ interface ClassLegendEntry {
|
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
const CLASS_TYPE_MAP: Record<string, ClassLegendEntry> = {
|
|
76
|
-
class:
|
|
77
|
-
abstract:
|
|
98
|
+
class: { label: 'Class', colorKey: 'teal' },
|
|
99
|
+
abstract: { label: 'Abstract', colorKey: 'purple' },
|
|
78
100
|
interface: { label: 'Interface', colorKey: 'blue' },
|
|
79
|
-
enum:
|
|
101
|
+
enum: { label: 'Enum', colorKey: 'yellow' },
|
|
80
102
|
};
|
|
81
103
|
|
|
82
104
|
const CLASS_TYPE_ORDER = ['class', 'abstract', 'interface', 'enum'];
|
|
@@ -89,18 +111,14 @@ function collectClassTypes(parsed: ParsedClassDiagram): ClassLegendEntry[] {
|
|
|
89
111
|
if (c.color) continue; // explicit color override — skip
|
|
90
112
|
present.add(c.modifier ?? 'class');
|
|
91
113
|
}
|
|
92
|
-
return CLASS_TYPE_ORDER.filter((k) => present.has(k)).map(
|
|
114
|
+
return CLASS_TYPE_ORDER.filter((k) => present.has(k)).map(
|
|
115
|
+
(k) => CLASS_TYPE_MAP[k]
|
|
116
|
+
);
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
const LEGEND_GROUP_NAME = 'Type';
|
|
96
120
|
|
|
97
|
-
|
|
98
|
-
let w = 0;
|
|
99
|
-
for (const e of entries) {
|
|
100
|
-
w += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(e.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
|
|
101
|
-
}
|
|
102
|
-
return w;
|
|
103
|
-
}
|
|
121
|
+
// legendEntriesWidth removed — centralized legend handles sizing
|
|
104
122
|
|
|
105
123
|
function classTypeKey(modifier: ClassModifier | undefined): string {
|
|
106
124
|
return modifier ?? 'class';
|
|
@@ -112,9 +130,12 @@ function classTypeKey(modifier: ClassModifier | undefined): string {
|
|
|
112
130
|
|
|
113
131
|
function visibilitySymbol(vis: 'public' | 'private' | 'protected'): string {
|
|
114
132
|
switch (vis) {
|
|
115
|
-
case 'public':
|
|
116
|
-
|
|
117
|
-
case '
|
|
133
|
+
case 'public':
|
|
134
|
+
return '+';
|
|
135
|
+
case 'private':
|
|
136
|
+
return '-';
|
|
137
|
+
case 'protected':
|
|
138
|
+
return '#';
|
|
118
139
|
}
|
|
119
140
|
}
|
|
120
141
|
|
|
@@ -122,7 +143,8 @@ function visibilitySymbol(vis: 'public' | 'private' | 'protected'): string {
|
|
|
122
143
|
// Edge path generator
|
|
123
144
|
// ============================================================
|
|
124
145
|
|
|
125
|
-
const lineGenerator = d3Shape
|
|
146
|
+
const lineGenerator = d3Shape
|
|
147
|
+
.line<{ x: number; y: number }>()
|
|
126
148
|
.x((d) => d.x)
|
|
127
149
|
.y((d) => d.y)
|
|
128
150
|
.curve(d3Shape.curveBasis);
|
|
@@ -137,12 +159,18 @@ function isDashedEdge(type: RelationshipType): boolean {
|
|
|
137
159
|
|
|
138
160
|
function markerIdForType(type: RelationshipType): string {
|
|
139
161
|
switch (type) {
|
|
140
|
-
case 'extends':
|
|
141
|
-
|
|
142
|
-
case '
|
|
143
|
-
|
|
144
|
-
case '
|
|
145
|
-
|
|
162
|
+
case 'extends':
|
|
163
|
+
return 'cd-arrow-inherit';
|
|
164
|
+
case 'implements':
|
|
165
|
+
return 'cd-arrow-implement';
|
|
166
|
+
case 'composes':
|
|
167
|
+
return 'cd-arrow-compose';
|
|
168
|
+
case 'aggregates':
|
|
169
|
+
return 'cd-arrow-aggregate';
|
|
170
|
+
case 'depends':
|
|
171
|
+
return 'cd-arrow-depend';
|
|
172
|
+
case 'associates':
|
|
173
|
+
return 'cd-arrow-assoc';
|
|
146
174
|
}
|
|
147
175
|
}
|
|
148
176
|
|
|
@@ -198,14 +226,17 @@ export function renderClassDiagram(
|
|
|
198
226
|
// ── Marker defs ──
|
|
199
227
|
const defs = svg.append('defs');
|
|
200
228
|
const AW = 12; // arrowhead width
|
|
201
|
-
const AH = 8;
|
|
229
|
+
const AH = 8; // arrowhead height
|
|
202
230
|
|
|
203
231
|
// Filled triangle (inheritance) — filled with stroke color
|
|
204
|
-
defs
|
|
232
|
+
defs
|
|
233
|
+
.append('marker')
|
|
205
234
|
.attr('id', 'cd-arrow-inherit')
|
|
206
235
|
.attr('viewBox', `0 0 ${AW} ${AH}`)
|
|
207
|
-
.attr('refX', AW)
|
|
208
|
-
.attr('
|
|
236
|
+
.attr('refX', AW)
|
|
237
|
+
.attr('refY', AH / 2)
|
|
238
|
+
.attr('markerWidth', AW)
|
|
239
|
+
.attr('markerHeight', AH)
|
|
209
240
|
.attr('orient', 'auto')
|
|
210
241
|
.append('polygon')
|
|
211
242
|
.attr('points', `0,0 ${AW},${AH / 2} 0,${AH}`)
|
|
@@ -214,11 +245,14 @@ export function renderClassDiagram(
|
|
|
214
245
|
.attr('stroke-width', 1);
|
|
215
246
|
|
|
216
247
|
// Hollow triangle (implementation) — white/bg fill
|
|
217
|
-
defs
|
|
248
|
+
defs
|
|
249
|
+
.append('marker')
|
|
218
250
|
.attr('id', 'cd-arrow-implement')
|
|
219
251
|
.attr('viewBox', `0 0 ${AW} ${AH}`)
|
|
220
|
-
.attr('refX', AW)
|
|
221
|
-
.attr('
|
|
252
|
+
.attr('refX', AW)
|
|
253
|
+
.attr('refY', AH / 2)
|
|
254
|
+
.attr('markerWidth', AW)
|
|
255
|
+
.attr('markerHeight', AH)
|
|
222
256
|
.attr('orient', 'auto')
|
|
223
257
|
.append('polygon')
|
|
224
258
|
.attr('points', `0,0 ${AW},${AH / 2} 0,${AH}`)
|
|
@@ -229,11 +263,14 @@ export function renderClassDiagram(
|
|
|
229
263
|
// Filled diamond (composition) — at source end
|
|
230
264
|
const DW = 12;
|
|
231
265
|
const DH = 8;
|
|
232
|
-
defs
|
|
266
|
+
defs
|
|
267
|
+
.append('marker')
|
|
233
268
|
.attr('id', 'cd-arrow-compose')
|
|
234
269
|
.attr('viewBox', `0 0 ${DW} ${DH}`)
|
|
235
|
-
.attr('refX', 0)
|
|
236
|
-
.attr('
|
|
270
|
+
.attr('refX', 0)
|
|
271
|
+
.attr('refY', DH / 2)
|
|
272
|
+
.attr('markerWidth', DW)
|
|
273
|
+
.attr('markerHeight', DH)
|
|
237
274
|
.attr('orient', 'auto')
|
|
238
275
|
.append('polygon')
|
|
239
276
|
.attr('points', `${DW / 2},0 ${DW},${DH / 2} ${DW / 2},${DH} 0,${DH / 2}`)
|
|
@@ -242,11 +279,14 @@ export function renderClassDiagram(
|
|
|
242
279
|
.attr('stroke-width', 1);
|
|
243
280
|
|
|
244
281
|
// Hollow diamond (aggregation) — at source end
|
|
245
|
-
defs
|
|
282
|
+
defs
|
|
283
|
+
.append('marker')
|
|
246
284
|
.attr('id', 'cd-arrow-aggregate')
|
|
247
285
|
.attr('viewBox', `0 0 ${DW} ${DH}`)
|
|
248
|
-
.attr('refX', 0)
|
|
249
|
-
.attr('
|
|
286
|
+
.attr('refX', 0)
|
|
287
|
+
.attr('refY', DH / 2)
|
|
288
|
+
.attr('markerWidth', DW)
|
|
289
|
+
.attr('markerHeight', DH)
|
|
250
290
|
.attr('orient', 'auto')
|
|
251
291
|
.append('polygon')
|
|
252
292
|
.attr('points', `${DW / 2},0 ${DW},${DH / 2} ${DW / 2},${DH} 0,${DH / 2}`)
|
|
@@ -255,11 +295,14 @@ export function renderClassDiagram(
|
|
|
255
295
|
.attr('stroke-width', 1);
|
|
256
296
|
|
|
257
297
|
// Open arrowhead (dependency)
|
|
258
|
-
defs
|
|
298
|
+
defs
|
|
299
|
+
.append('marker')
|
|
259
300
|
.attr('id', 'cd-arrow-depend')
|
|
260
301
|
.attr('viewBox', `0 0 ${AW} ${AH}`)
|
|
261
|
-
.attr('refX', AW)
|
|
262
|
-
.attr('
|
|
302
|
+
.attr('refX', AW)
|
|
303
|
+
.attr('refY', AH / 2)
|
|
304
|
+
.attr('markerWidth', AW)
|
|
305
|
+
.attr('markerHeight', AH)
|
|
263
306
|
.attr('orient', 'auto')
|
|
264
307
|
.append('polyline')
|
|
265
308
|
.attr('points', `0,0 ${AW},${AH / 2} 0,${AH}`)
|
|
@@ -268,11 +311,14 @@ export function renderClassDiagram(
|
|
|
268
311
|
.attr('stroke-width', 1.5);
|
|
269
312
|
|
|
270
313
|
// Open arrowhead (association)
|
|
271
|
-
defs
|
|
314
|
+
defs
|
|
315
|
+
.append('marker')
|
|
272
316
|
.attr('id', 'cd-arrow-assoc')
|
|
273
317
|
.attr('viewBox', `0 0 ${AW} ${AH}`)
|
|
274
|
-
.attr('refX', AW)
|
|
275
|
-
.attr('
|
|
318
|
+
.attr('refX', AW)
|
|
319
|
+
.attr('refY', AH / 2)
|
|
320
|
+
.attr('markerWidth', AW)
|
|
321
|
+
.attr('markerHeight', AH)
|
|
276
322
|
.attr('orient', 'auto')
|
|
277
323
|
.append('polyline')
|
|
278
324
|
.attr('points', `0,0 ${AW},${AH / 2} 0,${AH}`)
|
|
@@ -291,7 +337,10 @@ export function renderClassDiagram(
|
|
|
291
337
|
.attr('fill', palette.text)
|
|
292
338
|
.attr('font-size', TITLE_FONT_SIZE)
|
|
293
339
|
.attr('font-weight', TITLE_FONT_WEIGHT)
|
|
294
|
-
.style(
|
|
340
|
+
.style(
|
|
341
|
+
'cursor',
|
|
342
|
+
onClickItem && parsed.titleLineNumber ? 'pointer' : 'default'
|
|
343
|
+
)
|
|
295
344
|
.text(parsed.title);
|
|
296
345
|
|
|
297
346
|
if (parsed.titleLineNumber) {
|
|
@@ -299,8 +348,12 @@ export function renderClassDiagram(
|
|
|
299
348
|
if (onClickItem) {
|
|
300
349
|
titleEl
|
|
301
350
|
.on('click', () => onClickItem(parsed.titleLineNumber!))
|
|
302
|
-
.on('mouseenter', function () {
|
|
303
|
-
|
|
351
|
+
.on('mouseenter', function () {
|
|
352
|
+
d3Selection.select(this).attr('opacity', 0.7);
|
|
353
|
+
})
|
|
354
|
+
.on('mouseleave', function () {
|
|
355
|
+
d3Selection.select(this).attr('opacity', 1);
|
|
356
|
+
});
|
|
304
357
|
}
|
|
305
358
|
}
|
|
306
359
|
}
|
|
@@ -309,108 +362,36 @@ export function renderClassDiagram(
|
|
|
309
362
|
// legendActive: true = expanded (default), false = collapsed pill only
|
|
310
363
|
const isLegendExpanded = legendActive !== false;
|
|
311
364
|
if (hasLegend) {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
365
|
+
const legendGroups = [
|
|
366
|
+
{
|
|
367
|
+
name: LEGEND_GROUP_NAME,
|
|
368
|
+
entries: legendEntries.map((entry) => ({
|
|
369
|
+
value: entry.label,
|
|
370
|
+
color: palette.colors[entry.colorKey],
|
|
371
|
+
})),
|
|
372
|
+
},
|
|
373
|
+
];
|
|
374
|
+
const legendConfig: LegendConfig = {
|
|
375
|
+
groups: legendGroups,
|
|
376
|
+
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
377
|
+
mode: 'fixed',
|
|
378
|
+
};
|
|
379
|
+
const legendState: LegendState = {
|
|
380
|
+
activeGroup: isLegendExpanded ? LEGEND_GROUP_NAME : null,
|
|
381
|
+
};
|
|
327
382
|
const legendG = svg
|
|
328
383
|
.append('g')
|
|
329
384
|
.attr('class', 'cd-legend')
|
|
330
|
-
.attr('
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
.attr('fill', groupBg);
|
|
341
|
-
|
|
342
|
-
// Inner pill
|
|
343
|
-
legendG.append('rect')
|
|
344
|
-
.attr('x', LEGEND_CAPSULE_PAD)
|
|
345
|
-
.attr('y', LEGEND_CAPSULE_PAD)
|
|
346
|
-
.attr('width', pillWidth)
|
|
347
|
-
.attr('height', pillH)
|
|
348
|
-
.attr('rx', pillH / 2)
|
|
349
|
-
.attr('fill', palette.bg);
|
|
350
|
-
|
|
351
|
-
legendG.append('rect')
|
|
352
|
-
.attr('x', LEGEND_CAPSULE_PAD)
|
|
353
|
-
.attr('y', LEGEND_CAPSULE_PAD)
|
|
354
|
-
.attr('width', pillWidth)
|
|
355
|
-
.attr('height', pillH)
|
|
356
|
-
.attr('rx', pillH / 2)
|
|
357
|
-
.attr('fill', 'none')
|
|
358
|
-
.attr('stroke', mix(palette.textMuted, palette.bg, 50))
|
|
359
|
-
.attr('stroke-width', 0.75);
|
|
360
|
-
|
|
361
|
-
legendG.append('text')
|
|
362
|
-
.attr('x', LEGEND_CAPSULE_PAD + pillWidth / 2)
|
|
363
|
-
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2)
|
|
364
|
-
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
365
|
-
.attr('font-weight', '500')
|
|
366
|
-
.attr('fill', palette.text)
|
|
367
|
-
.attr('text-anchor', 'middle')
|
|
368
|
-
.attr('font-family', FONT_FAMILY)
|
|
369
|
-
.text(LEGEND_GROUP_NAME);
|
|
370
|
-
|
|
371
|
-
// Entries
|
|
372
|
-
let entryX = LEGEND_CAPSULE_PAD + pillWidth + LEGEND_ENTRY_TRAIL;
|
|
373
|
-
for (const entry of legendEntries) {
|
|
374
|
-
const color = palette.colors[entry.colorKey];
|
|
375
|
-
const typeKey = CLASS_TYPE_ORDER.find((k) => CLASS_TYPE_MAP[k] === entry)!;
|
|
376
|
-
|
|
377
|
-
const entryG = legendG.append('g')
|
|
378
|
-
.attr('data-legend-entry', typeKey);
|
|
379
|
-
|
|
380
|
-
entryG.append('circle')
|
|
381
|
-
.attr('cx', entryX + LEGEND_DOT_R)
|
|
382
|
-
.attr('cy', LEGEND_HEIGHT / 2)
|
|
383
|
-
.attr('r', LEGEND_DOT_R)
|
|
384
|
-
.attr('fill', color);
|
|
385
|
-
|
|
386
|
-
entryG.append('text')
|
|
387
|
-
.attr('x', entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP)
|
|
388
|
-
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1)
|
|
389
|
-
.attr('font-size', LEGEND_ENTRY_FONT_SIZE)
|
|
390
|
-
.attr('fill', palette.textMuted)
|
|
391
|
-
.attr('font-family', FONT_FAMILY)
|
|
392
|
-
.text(entry.label);
|
|
393
|
-
|
|
394
|
-
entryX += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(entry.label, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
|
|
395
|
-
}
|
|
396
|
-
} else {
|
|
397
|
-
// Collapsed: single muted pill
|
|
398
|
-
legendG.append('rect')
|
|
399
|
-
.attr('width', pillWidth)
|
|
400
|
-
.attr('height', LEGEND_HEIGHT)
|
|
401
|
-
.attr('rx', LEGEND_HEIGHT / 2)
|
|
402
|
-
.attr('fill', groupBg);
|
|
403
|
-
|
|
404
|
-
legendG.append('text')
|
|
405
|
-
.attr('x', pillWidth / 2)
|
|
406
|
-
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2)
|
|
407
|
-
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
408
|
-
.attr('font-weight', '500')
|
|
409
|
-
.attr('fill', palette.textMuted)
|
|
410
|
-
.attr('text-anchor', 'middle')
|
|
411
|
-
.attr('font-family', FONT_FAMILY)
|
|
412
|
-
.text(LEGEND_GROUP_NAME);
|
|
413
|
-
}
|
|
385
|
+
.attr('transform', `translate(0,${titleHeight})`);
|
|
386
|
+
renderLegendD3(
|
|
387
|
+
legendG,
|
|
388
|
+
legendConfig,
|
|
389
|
+
legendState,
|
|
390
|
+
palette,
|
|
391
|
+
isDark,
|
|
392
|
+
undefined,
|
|
393
|
+
width
|
|
394
|
+
);
|
|
414
395
|
}
|
|
415
396
|
|
|
416
397
|
// ── Content group ──
|
|
@@ -461,7 +442,8 @@ export function renderClassDiagram(
|
|
|
461
442
|
const bgW = labelLen * 7 + 8;
|
|
462
443
|
const bgH = 16;
|
|
463
444
|
|
|
464
|
-
edgeG
|
|
445
|
+
edgeG
|
|
446
|
+
.append('rect')
|
|
465
447
|
.attr('x', midPt.x - bgW / 2)
|
|
466
448
|
.attr('y', midPt.y - bgH / 2 - 1)
|
|
467
449
|
.attr('width', bgW)
|
|
@@ -471,7 +453,8 @@ export function renderClassDiagram(
|
|
|
471
453
|
.attr('opacity', 0.85)
|
|
472
454
|
.attr('class', 'cd-edge-label-bg');
|
|
473
455
|
|
|
474
|
-
edgeG
|
|
456
|
+
edgeG
|
|
457
|
+
.append('text')
|
|
475
458
|
.attr('x', midPt.x)
|
|
476
459
|
.attr('y', midPt.y + 4)
|
|
477
460
|
.attr('text-anchor', 'middle')
|
|
@@ -504,11 +487,18 @@ export function renderClassDiagram(
|
|
|
504
487
|
// When legend is collapsed, use neutral color for nodes without explicit color
|
|
505
488
|
const neutralize = hasLegend && !isLegendExpanded && !node.color;
|
|
506
489
|
const effectiveColor = neutralize ? palette.primary : node.color;
|
|
507
|
-
const fill = nodeFill(
|
|
490
|
+
const fill = nodeFill(
|
|
491
|
+
palette,
|
|
492
|
+
isDark,
|
|
493
|
+
node.modifier,
|
|
494
|
+
effectiveColor,
|
|
495
|
+
colorOff
|
|
496
|
+
);
|
|
508
497
|
const stroke = nodeStroke(palette, node.modifier, effectiveColor, colorOff);
|
|
509
498
|
|
|
510
499
|
// Outer rectangle
|
|
511
|
-
nodeG
|
|
500
|
+
nodeG
|
|
501
|
+
.append('rect')
|
|
512
502
|
.attr('x', -w / 2)
|
|
513
503
|
.attr('y', -h / 2)
|
|
514
504
|
.attr('width', w)
|
|
@@ -526,7 +516,8 @@ export function renderClassDiagram(
|
|
|
526
516
|
// Modifier badge <<interface>> etc.
|
|
527
517
|
if (node.modifier) {
|
|
528
518
|
const badgeText = `\u00AB${node.modifier}\u00BB`; // « »
|
|
529
|
-
nodeG
|
|
519
|
+
nodeG
|
|
520
|
+
.append('text')
|
|
530
521
|
.attr('x', 0)
|
|
531
522
|
.attr('y', headerCenterY - 6)
|
|
532
523
|
.attr('text-anchor', 'middle')
|
|
@@ -537,7 +528,8 @@ export function renderClassDiagram(
|
|
|
537
528
|
.text(badgeText);
|
|
538
529
|
|
|
539
530
|
// Class name below badge
|
|
540
|
-
nodeG
|
|
531
|
+
nodeG
|
|
532
|
+
.append('text')
|
|
541
533
|
.attr('x', 0)
|
|
542
534
|
.attr('y', headerCenterY + 10)
|
|
543
535
|
.attr('text-anchor', 'middle')
|
|
@@ -549,7 +541,8 @@ export function renderClassDiagram(
|
|
|
549
541
|
.text(node.name);
|
|
550
542
|
} else {
|
|
551
543
|
// Just class name centered
|
|
552
|
-
nodeG
|
|
544
|
+
nodeG
|
|
545
|
+
.append('text')
|
|
553
546
|
.attr('x', 0)
|
|
554
547
|
.attr('y', headerCenterY)
|
|
555
548
|
.attr('text-anchor', 'middle')
|
|
@@ -569,7 +562,8 @@ export function renderClassDiagram(
|
|
|
569
562
|
if (isEnum) {
|
|
570
563
|
// Enum: single values compartment
|
|
571
564
|
// Separator
|
|
572
|
-
nodeG
|
|
565
|
+
nodeG
|
|
566
|
+
.append('line')
|
|
573
567
|
.attr('x1', -w / 2)
|
|
574
568
|
.attr('y1', yPos)
|
|
575
569
|
.attr('x2', w / 2)
|
|
@@ -580,7 +574,8 @@ export function renderClassDiagram(
|
|
|
580
574
|
|
|
581
575
|
let memberY = yPos + COMPARTMENT_PADDING_Y;
|
|
582
576
|
for (const member of node.members) {
|
|
583
|
-
nodeG
|
|
577
|
+
nodeG
|
|
578
|
+
.append('text')
|
|
584
579
|
.attr('x', -w / 2 + MEMBER_PADDING_X)
|
|
585
580
|
.attr('y', memberY + MEMBER_LINE_HEIGHT / 2)
|
|
586
581
|
.attr('dominant-baseline', 'central')
|
|
@@ -593,7 +588,8 @@ export function renderClassDiagram(
|
|
|
593
588
|
// UML 3-compartment layout: always show both separators
|
|
594
589
|
|
|
595
590
|
// Fields separator
|
|
596
|
-
nodeG
|
|
591
|
+
nodeG
|
|
592
|
+
.append('line')
|
|
597
593
|
.attr('x1', -w / 2)
|
|
598
594
|
.attr('y1', yPos)
|
|
599
595
|
.attr('x2', w / 2)
|
|
@@ -609,7 +605,8 @@ export function renderClassDiagram(
|
|
|
609
605
|
let text = `${vis} ${field.name}`;
|
|
610
606
|
if (field.type) text += `: ${field.type}`;
|
|
611
607
|
|
|
612
|
-
const textEl = nodeG
|
|
608
|
+
const textEl = nodeG
|
|
609
|
+
.append('text')
|
|
613
610
|
.attr('x', -w / 2 + MEMBER_PADDING_X)
|
|
614
611
|
.attr('y', memberY + MEMBER_LINE_HEIGHT / 2)
|
|
615
612
|
.attr('dominant-baseline', 'central')
|
|
@@ -627,7 +624,8 @@ export function renderClassDiagram(
|
|
|
627
624
|
yPos += node.fieldsHeight;
|
|
628
625
|
|
|
629
626
|
// Methods separator
|
|
630
|
-
nodeG
|
|
627
|
+
nodeG
|
|
628
|
+
.append('line')
|
|
631
629
|
.attr('x1', -w / 2)
|
|
632
630
|
.attr('y1', yPos)
|
|
633
631
|
.attr('x2', w / 2)
|
|
@@ -643,7 +641,8 @@ export function renderClassDiagram(
|
|
|
643
641
|
let text = `${vis} ${method.name}(${method.params ?? ''})`;
|
|
644
642
|
if (method.type) text += `: ${method.type}`;
|
|
645
643
|
|
|
646
|
-
const textEl = nodeG
|
|
644
|
+
const textEl = nodeG
|
|
645
|
+
.append('text')
|
|
647
646
|
.attr('x', -w / 2 + MEMBER_PADDING_X)
|
|
648
647
|
.attr('y', memberY + MEMBER_LINE_HEIGHT / 2)
|
|
649
648
|
.attr('dominant-baseline', 'central')
|
|
@@ -679,20 +678,20 @@ export function renderClassDiagramForExport(
|
|
|
679
678
|
|
|
680
679
|
const legendEntries = collectClassTypes(parsed);
|
|
681
680
|
const EXPORT_LEGEND_GAP = 8;
|
|
682
|
-
const legendReserve =
|
|
681
|
+
const legendReserve =
|
|
682
|
+
legendEntries.length > 1 ? LEGEND_HEIGHT + EXPORT_LEGEND_GAP : 0;
|
|
683
683
|
const exportWidth = layout.width + DIAGRAM_PADDING * 2;
|
|
684
|
-
const exportHeight =
|
|
684
|
+
const exportHeight =
|
|
685
|
+
layout.height +
|
|
686
|
+
DIAGRAM_PADDING * 2 +
|
|
687
|
+
(parsed.title ? 40 : 0) +
|
|
688
|
+
legendReserve;
|
|
685
689
|
|
|
686
690
|
return runInExportContainer(exportWidth, exportHeight, (container) => {
|
|
687
|
-
renderClassDiagram(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
palette,
|
|
692
|
-
isDark,
|
|
693
|
-
undefined,
|
|
694
|
-
{ width: exportWidth, height: exportHeight }
|
|
695
|
-
);
|
|
691
|
+
renderClassDiagram(container, parsed, layout, palette, isDark, undefined, {
|
|
692
|
+
width: exportWidth,
|
|
693
|
+
height: exportHeight,
|
|
694
|
+
});
|
|
696
695
|
return extractExportSvg(container, theme);
|
|
697
696
|
});
|
|
698
697
|
}
|