@diagrammo/dgmo 0.8.9 → 0.8.10

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.
@@ -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
- LEGEND_HEIGHT,
11
- LEGEND_PILL_PAD,
12
- LEGEND_PILL_FONT_SIZE,
13
- LEGEND_CAPSULE_PAD,
14
- LEGEND_DOT_R,
15
- LEGEND_ENTRY_FONT_SIZE,
16
- LEGEND_ENTRY_DOT_GAP,
17
- LEGEND_ENTRY_TRAIL,
18
- measureLegendText,
19
- } from '../utils/legend-constants';
20
- import { TITLE_FONT_SIZE, TITLE_FONT_WEIGHT, TITLE_Y } from '../utils/title-constants';
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 { ParsedClassDiagram, ClassModifier, RelationshipType } from './types';
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(modifier: ClassModifier | undefined, palette: PaletteColors, colorOff?: boolean): string {
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': return palette.colors.blue;
51
- case 'abstract': return palette.colors.purple;
52
- case 'enum': return palette.colors.yellow;
53
- default: return palette.colors.teal;
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(palette: PaletteColors, isDark: boolean, modifier: ClassModifier | undefined, nodeColor?: string, colorOff?: boolean): string {
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(palette: PaletteColors, modifier: ClassModifier | undefined, nodeColor?: string, colorOff?: boolean): string {
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: { label: 'Class', colorKey: 'teal' },
77
- abstract: { label: 'Abstract', colorKey: 'purple' },
98
+ class: { label: 'Class', colorKey: 'teal' },
99
+ abstract: { label: 'Abstract', colorKey: 'purple' },
78
100
  interface: { label: 'Interface', colorKey: 'blue' },
79
- enum: { label: 'Enum', colorKey: 'yellow' },
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((k) => CLASS_TYPE_MAP[k]);
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
- function legendEntriesWidth(entries: ClassLegendEntry[]): number {
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': return '+';
116
- case 'private': return '-';
117
- case 'protected': return '#';
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.line<{ x: number; y: number }>()
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': return 'cd-arrow-inherit';
141
- case 'implements': return 'cd-arrow-implement';
142
- case 'composes': return 'cd-arrow-compose';
143
- case 'aggregates': return 'cd-arrow-aggregate';
144
- case 'depends': return 'cd-arrow-depend';
145
- case 'associates': return 'cd-arrow-assoc';
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; // arrowhead height
229
+ const AH = 8; // arrowhead height
202
230
 
203
231
  // Filled triangle (inheritance) — filled with stroke color
204
- defs.append('marker')
232
+ defs
233
+ .append('marker')
205
234
  .attr('id', 'cd-arrow-inherit')
206
235
  .attr('viewBox', `0 0 ${AW} ${AH}`)
207
- .attr('refX', AW).attr('refY', AH / 2)
208
- .attr('markerWidth', AW).attr('markerHeight', AH)
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.append('marker')
248
+ defs
249
+ .append('marker')
218
250
  .attr('id', 'cd-arrow-implement')
219
251
  .attr('viewBox', `0 0 ${AW} ${AH}`)
220
- .attr('refX', AW).attr('refY', AH / 2)
221
- .attr('markerWidth', AW).attr('markerHeight', AH)
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.append('marker')
266
+ defs
267
+ .append('marker')
233
268
  .attr('id', 'cd-arrow-compose')
234
269
  .attr('viewBox', `0 0 ${DW} ${DH}`)
235
- .attr('refX', 0).attr('refY', DH / 2)
236
- .attr('markerWidth', DW).attr('markerHeight', DH)
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.append('marker')
282
+ defs
283
+ .append('marker')
246
284
  .attr('id', 'cd-arrow-aggregate')
247
285
  .attr('viewBox', `0 0 ${DW} ${DH}`)
248
- .attr('refX', 0).attr('refY', DH / 2)
249
- .attr('markerWidth', DW).attr('markerHeight', DH)
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.append('marker')
298
+ defs
299
+ .append('marker')
259
300
  .attr('id', 'cd-arrow-depend')
260
301
  .attr('viewBox', `0 0 ${AW} ${AH}`)
261
- .attr('refX', AW).attr('refY', AH / 2)
262
- .attr('markerWidth', AW).attr('markerHeight', AH)
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.append('marker')
314
+ defs
315
+ .append('marker')
272
316
  .attr('id', 'cd-arrow-assoc')
273
317
  .attr('viewBox', `0 0 ${AW} ${AH}`)
274
- .attr('refX', AW).attr('refY', AH / 2)
275
- .attr('markerWidth', AW).attr('markerHeight', AH)
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('cursor', onClickItem && parsed.titleLineNumber ? 'pointer' : 'default')
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 () { d3Selection.select(this).attr('opacity', 0.7); })
303
- .on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
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 groupBg = isDark
313
- ? mix(palette.surface, palette.bg, 50)
314
- : mix(palette.surface, palette.bg, 30);
315
-
316
- const pillWidth = measureLegendText(LEGEND_GROUP_NAME, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
317
- const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
318
- const entriesW = legendEntriesWidth(legendEntries);
319
-
320
- const totalW = isLegendExpanded
321
- ? LEGEND_CAPSULE_PAD * 2 + pillWidth + LEGEND_ENTRY_TRAIL + entriesW
322
- : pillWidth;
323
-
324
- const legendX = (width - totalW) / 2;
325
- const legendY = titleHeight;
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('data-legend-group', 'type')
331
- .attr('transform', `translate(${legendX}, ${legendY})`)
332
- .style('cursor', 'pointer');
333
-
334
- if (isLegendExpanded) {
335
- // Outer capsule
336
- legendG.append('rect')
337
- .attr('width', totalW)
338
- .attr('height', LEGEND_HEIGHT)
339
- .attr('rx', LEGEND_HEIGHT / 2)
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.append('rect')
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.append('text')
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(palette, isDark, node.modifier, effectiveColor, colorOff);
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.append('rect')
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.append('text')
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.append('text')
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.append('text')
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.append('line')
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.append('text')
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.append('line')
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.append('text')
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.append('line')
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.append('text')
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 = legendEntries.length > 1 ? LEGEND_HEIGHT + EXPORT_LEGEND_GAP : 0;
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 = layout.height + DIAGRAM_PADDING * 2 + (parsed.title ? 40 : 0) + legendReserve;
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
- container,
689
- parsed,
690
- layout,
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
  }