@diagrammo/dgmo 0.8.10 → 0.8.12

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.
Files changed (43) hide show
  1. package/dist/cli.cjs +245 -672
  2. package/dist/editor.cjs.map +1 -1
  3. package/dist/editor.d.cts +2 -3
  4. package/dist/editor.d.ts +2 -3
  5. package/dist/editor.js.map +1 -1
  6. package/dist/index.cjs +491 -125
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +9 -1
  9. package/dist/index.d.ts +9 -1
  10. package/dist/index.js +489 -125
  11. package/dist/index.js.map +1 -1
  12. package/gallery/fixtures/er.dgmo +36 -0
  13. package/gallery/fixtures/kanban.dgmo +27 -0
  14. package/package.json +14 -17
  15. package/src/boxes-and-lines/parser.ts +2 -0
  16. package/src/boxes-and-lines/renderer.ts +13 -5
  17. package/src/c4/layout.ts +31 -10
  18. package/src/c4/parser.ts +5 -1
  19. package/src/completion.ts +17 -2
  20. package/src/d3.ts +220 -102
  21. package/src/echarts.ts +57 -58
  22. package/src/editor/index.ts +1 -2
  23. package/src/er/parser.ts +5 -1
  24. package/src/gantt/parser.ts +8 -0
  25. package/src/gantt/renderer.ts +6 -7
  26. package/src/gantt/resolver.ts +19 -14
  27. package/src/gantt/types.ts +1 -0
  28. package/src/index.ts +2 -0
  29. package/src/infra/parser.ts +4 -0
  30. package/src/kanban/parser.ts +4 -1
  31. package/src/kanban/renderer.ts +11 -8
  32. package/src/label-layout.ts +286 -0
  33. package/src/org/parser.ts +3 -0
  34. package/src/sequence/parser.ts +6 -0
  35. package/src/sequence/renderer.ts +24 -9
  36. package/src/sharing.ts +15 -5
  37. package/src/sitemap/parser.ts +2 -0
  38. package/src/utils/arrows.ts +1 -1
  39. package/src/utils/legend-layout.ts +8 -8
  40. package/src/utils/legend-svg.ts +2 -2
  41. package/src/utils/legend-types.ts +0 -3
  42. package/src/utils/parsing.ts +1 -1
  43. package/src/utils/tag-groups.ts +65 -1
package/src/d3.ts CHANGED
@@ -5,6 +5,7 @@ import * as d3Array from 'd3-array';
5
5
  import cloud from 'd3-cloud';
6
6
  import { FONT_FAMILY } from './fonts';
7
7
  import { injectBranding } from './branding';
8
+ import { computeQuadrantPointLabels, type LabelRect } from './label-layout';
8
9
 
9
10
  // ============================================================
10
11
  // Types
@@ -19,22 +20,22 @@ export type VisualizationType =
19
20
  | 'quadrant'
20
21
  | 'sequence';
21
22
 
22
- export interface D3DataItem {
23
+ interface D3DataItem {
23
24
  label: string;
24
25
  values: number[];
25
26
  color: string | null;
26
27
  lineNumber: number;
27
28
  }
28
29
 
29
- export interface WordCloudWord {
30
+ interface WordCloudWord {
30
31
  text: string;
31
32
  weight: number;
32
33
  lineNumber: number;
33
34
  }
34
35
 
35
- export type WordCloudRotate = 'none' | 'mixed' | 'angled';
36
+ type WordCloudRotate = 'none' | 'mixed' | 'angled';
36
37
 
37
- export interface WordCloudOptions {
38
+ interface WordCloudOptions {
38
39
  rotate: WordCloudRotate;
39
40
  max: number;
40
41
  minSize: number;
@@ -56,7 +57,7 @@ export interface ArcLink {
56
57
  lineNumber: number;
57
58
  }
58
59
 
59
- export type ArcOrder = 'appearance' | 'name' | 'group' | 'degree';
60
+ type ArcOrder = 'appearance' | 'name' | 'group' | 'degree';
60
61
 
61
62
  export interface ArcNodeGroup {
62
63
  name: string;
@@ -65,9 +66,9 @@ export interface ArcNodeGroup {
65
66
  lineNumber: number;
66
67
  }
67
68
 
68
- export type TimelineSort = 'time' | 'group' | 'tag';
69
+ type TimelineSort = 'time' | 'group' | 'tag';
69
70
 
70
- export interface TimelineEvent {
71
+ interface TimelineEvent {
71
72
  date: string;
72
73
  endDate: string | null;
73
74
  label: string;
@@ -77,13 +78,13 @@ export interface TimelineEvent {
77
78
  uncertain?: boolean;
78
79
  }
79
80
 
80
- export interface TimelineGroup {
81
+ interface TimelineGroup {
81
82
  name: string;
82
83
  color: string | null;
83
84
  lineNumber: number;
84
85
  }
85
86
 
86
- export interface TimelineEra {
87
+ interface TimelineEra {
87
88
  startDate: string;
88
89
  endDate: string;
89
90
  label: string;
@@ -91,40 +92,40 @@ export interface TimelineEra {
91
92
  lineNumber: number;
92
93
  }
93
94
 
94
- export interface TimelineMarker {
95
+ interface TimelineMarker {
95
96
  date: string;
96
97
  label: string;
97
98
  color: string | null;
98
99
  lineNumber: number;
99
100
  }
100
101
 
101
- export interface VennSet {
102
+ interface VennSet {
102
103
  name: string;
103
104
  alias: string | null;
104
105
  color: string | null;
105
106
  lineNumber: number;
106
107
  }
107
108
 
108
- export interface VennOverlap {
109
+ interface VennOverlap {
109
110
  sets: string[];
110
111
  label: string | null;
111
112
  lineNumber: number;
112
113
  }
113
114
 
114
- export interface QuadrantLabel {
115
+ interface QuadrantLabel {
115
116
  text: string;
116
117
  color: string | null;
117
118
  lineNumber: number;
118
119
  }
119
120
 
120
- export interface QuadrantPoint {
121
+ interface QuadrantPoint {
121
122
  label: string;
122
123
  x: number;
123
124
  y: number;
124
125
  lineNumber: number;
125
126
  }
126
127
 
127
- export interface QuadrantLabels {
128
+ interface QuadrantLabels {
128
129
  topRight: QuadrantLabel | null;
129
130
  topLeft: QuadrantLabel | null;
130
131
  bottomLeft: QuadrantLabel | null;
@@ -192,7 +193,9 @@ import {
192
193
  import {
193
194
  matchTagBlockHeading,
194
195
  validateTagValues,
196
+ validateTagGroupNames,
195
197
  resolveTagColor,
198
+ resolveActiveTagGroup,
196
199
  stripDefaultModifier,
197
200
  } from './utils/tag-groups';
198
201
  import type { TagGroup } from './utils/tag-groups';
@@ -1476,6 +1479,9 @@ export function parseVisualization(
1476
1479
  result.diagnostics.push(makeDgmoError(line, msg, 'warning')),
1477
1480
  suggest
1478
1481
  );
1482
+ validateTagGroupNames(result.timelineTagGroups, (line, msg) =>
1483
+ result.diagnostics.push(makeDgmoError(line, msg, 'warning'))
1484
+ );
1479
1485
  for (const group of result.timelineTagGroups) {
1480
1486
  if (!group.defaultValue) continue;
1481
1487
  const key = group.name.toLowerCase();
@@ -3404,6 +3410,78 @@ function buildEraTooltipHtml(era: TimelineEra): string {
3404
3410
  // Timeline Renderer
3405
3411
  // ============================================================
3406
3412
 
3413
+ /**
3414
+ * Renders timeline group legend as pills (colored dot + text in rounded rect),
3415
+ * matching the centralized legend pill style.
3416
+ */
3417
+ function renderTimelineGroupLegend(
3418
+ g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
3419
+ groups: TimelineGroup[],
3420
+ groupColorMap: Map<string, string>,
3421
+ textColor: string,
3422
+ palette: PaletteColors,
3423
+ isDark: boolean,
3424
+ legendY: number,
3425
+ onHover: (name: string) => void,
3426
+ onLeave: () => void
3427
+ ): void {
3428
+ const PILL_H = 22;
3429
+ const DOT_R = 4;
3430
+ const DOT_GAP = 4;
3431
+ const PAD_X = 10;
3432
+ const FONT_SIZE = 11;
3433
+ const GAP = 8;
3434
+ const pillBg = isDark
3435
+ ? mix(palette.surface, palette.bg, 50)
3436
+ : mix(palette.surface, palette.bg, 30);
3437
+
3438
+ let legendX = 0;
3439
+ for (const grp of groups) {
3440
+ const color = groupColorMap.get(grp.name) ?? textColor;
3441
+ const textW = measureLegendText(grp.name, FONT_SIZE);
3442
+ const pillW = PAD_X + DOT_R * 2 + DOT_GAP + textW + PAD_X;
3443
+
3444
+ const itemG = g
3445
+ .append('g')
3446
+ .attr('class', 'tl-legend-item')
3447
+ .attr('data-group', grp.name)
3448
+ .style('cursor', 'pointer')
3449
+ .on('mouseenter', () => onHover(grp.name))
3450
+ .on('mouseleave', () => onLeave());
3451
+
3452
+ // Pill background
3453
+ itemG
3454
+ .append('rect')
3455
+ .attr('x', legendX)
3456
+ .attr('y', legendY - PILL_H / 2)
3457
+ .attr('width', pillW)
3458
+ .attr('height', PILL_H)
3459
+ .attr('rx', PILL_H / 2)
3460
+ .attr('fill', pillBg);
3461
+
3462
+ // Colored dot
3463
+ itemG
3464
+ .append('circle')
3465
+ .attr('cx', legendX + PAD_X + DOT_R)
3466
+ .attr('cy', legendY)
3467
+ .attr('r', DOT_R)
3468
+ .attr('fill', color);
3469
+
3470
+ // Label text
3471
+ itemG
3472
+ .append('text')
3473
+ .attr('x', legendX + PAD_X + DOT_R * 2 + DOT_GAP)
3474
+ .attr('y', legendY)
3475
+ .attr('dy', '0.35em')
3476
+ .attr('fill', textColor)
3477
+ .attr('font-size', `${FONT_SIZE}px`)
3478
+ .attr('font-family', FONT_FAMILY)
3479
+ .text(grp.name);
3480
+
3481
+ legendX += pillW + GAP;
3482
+ }
3483
+ }
3484
+
3407
3485
  /**
3408
3486
  * Renders a timeline chart into the given container using D3.
3409
3487
  * Supports horizontal (default) and vertical orientation.
@@ -4037,38 +4115,19 @@ export function renderTimeline(
4037
4115
  );
4038
4116
  }
4039
4117
 
4040
- // Group legend
4118
+ // Group legend (pill style)
4041
4119
  if (timelineGroups.length > 0) {
4042
- let legendX = 0;
4043
- const legendY = -55;
4044
- for (const grp of timelineGroups) {
4045
- const color = groupColorMap.get(grp.name) ?? textColor;
4046
- const itemG = g
4047
- .append('g')
4048
- .attr('class', 'tl-legend-item')
4049
- .attr('data-group', grp.name)
4050
- .style('cursor', 'pointer')
4051
- .on('mouseenter', () => fadeToGroup(g, grp.name))
4052
- .on('mouseleave', () => fadeReset(g));
4053
-
4054
- itemG
4055
- .append('circle')
4056
- .attr('cx', legendX)
4057
- .attr('cy', legendY)
4058
- .attr('r', 5)
4059
- .attr('fill', color);
4060
-
4061
- itemG
4062
- .append('text')
4063
- .attr('x', legendX + 10)
4064
- .attr('y', legendY)
4065
- .attr('dy', '0.35em')
4066
- .attr('fill', textColor)
4067
- .attr('font-size', '11px')
4068
- .text(grp.name);
4069
-
4070
- legendX += grp.name.length * 7 + 30;
4071
- }
4120
+ renderTimelineGroupLegend(
4121
+ g,
4122
+ timelineGroups,
4123
+ groupColorMap,
4124
+ textColor,
4125
+ palette,
4126
+ isDark,
4127
+ -55,
4128
+ (name) => fadeToGroup(g, name),
4129
+ () => fadeReset(g)
4130
+ );
4072
4131
  }
4073
4132
 
4074
4133
  g.append('line')
@@ -4525,8 +4584,7 @@ export function renderTimeline(
4525
4584
  .attr('dy', '0.35em')
4526
4585
  .attr('text-anchor', 'start')
4527
4586
  .attr('fill', textColor)
4528
- .attr('font-size', '14px')
4529
- .attr('font-weight', '700')
4587
+ .attr('font-size', '13px')
4530
4588
  .text(ev.label);
4531
4589
  } else {
4532
4590
  // Text outside bar - check if it fits on left or must go right
@@ -4656,38 +4714,20 @@ export function renderTimeline(
4656
4714
  );
4657
4715
  }
4658
4716
 
4659
- // Group legend at top-left
4717
+ // Group legend at top-left (pill style)
4660
4718
  if (timelineGroups.length > 0) {
4661
- let legendX = 0;
4662
4719
  const legendY = timelineScale ? -75 : -55;
4663
- for (const grp of timelineGroups) {
4664
- const color = groupColorMap.get(grp.name) ?? textColor;
4665
- const itemG = g
4666
- .append('g')
4667
- .attr('class', 'tl-legend-item')
4668
- .attr('data-group', grp.name)
4669
- .style('cursor', 'pointer')
4670
- .on('mouseenter', () => fadeToGroup(g, grp.name))
4671
- .on('mouseleave', () => fadeReset(g));
4672
-
4673
- itemG
4674
- .append('circle')
4675
- .attr('cx', legendX)
4676
- .attr('cy', legendY)
4677
- .attr('r', 5)
4678
- .attr('fill', color);
4679
-
4680
- itemG
4681
- .append('text')
4682
- .attr('x', legendX + 10)
4683
- .attr('y', legendY)
4684
- .attr('dy', '0.35em')
4685
- .attr('fill', textColor)
4686
- .attr('font-size', '11px')
4687
- .text(grp.name);
4688
-
4689
- legendX += grp.name.length * 7 + 30;
4690
- }
4720
+ renderTimelineGroupLegend(
4721
+ g,
4722
+ timelineGroups,
4723
+ groupColorMap,
4724
+ textColor,
4725
+ palette,
4726
+ isDark,
4727
+ legendY,
4728
+ (name) => fadeToGroup(g, name),
4729
+ () => fadeReset(g)
4730
+ );
4691
4731
  }
4692
4732
 
4693
4733
  sorted.forEach((ev, i) => {
@@ -4815,8 +4855,7 @@ export function renderTimeline(
4815
4855
  .attr('dy', '0.35em')
4816
4856
  .attr('text-anchor', 'start')
4817
4857
  .attr('fill', textColor)
4818
- .attr('font-size', '14px')
4819
- .attr('font-weight', '700')
4858
+ .attr('font-size', '13px')
4820
4859
  .text(ev.label);
4821
4860
  } else {
4822
4861
  // Text outside bar - check if it fits on left or must go right
@@ -5461,7 +5500,7 @@ export function renderVenn(
5461
5500
  exportDims?: D3ExportDimensions
5462
5501
  ): void {
5463
5502
  const { vennSets, vennOverlaps, title } = parsed;
5464
- if (vennSets.length < 2) return;
5503
+ if (vennSets.length < 2 || vennSets.length > 3) return;
5465
5504
 
5466
5505
  const init = initD3Chart(container, palette, exportDims);
5467
5506
  if (!init) return;
@@ -5539,7 +5578,9 @@ export function renderVenn(
5539
5578
  // Suppress WebKit focus ring on interactive SVG elements
5540
5579
  svg
5541
5580
  .append('style')
5542
- .text('circle:focus, circle:focus-visible { outline: none !important; }');
5581
+ .text(
5582
+ 'circle:focus, circle:focus-visible { outline-solid: none !important; }'
5583
+ );
5543
5584
 
5544
5585
  // Title
5545
5586
  renderChartTitle(
@@ -5870,7 +5911,7 @@ export function renderVenn(
5870
5911
  .attr('class', 'venn-hit-target')
5871
5912
  .attr('data-line-number', String(vennSets[i].lineNumber))
5872
5913
  .style('cursor', onClickItem ? 'pointer' : 'default')
5873
- .style('outline', 'none')
5914
+ .style('outline-solid', 'none')
5874
5915
  .on('mouseenter', () => {
5875
5916
  showRegionOverlay([i]);
5876
5917
  })
@@ -5926,7 +5967,7 @@ export function renderVenn(
5926
5967
  .attr('class', 'venn-hit-target')
5927
5968
  .attr('data-line-number', declaredOv ? String(declaredOv.lineNumber) : '')
5928
5969
  .style('cursor', onClickItem && declaredOv ? 'pointer' : 'default')
5929
- .style('outline', 'none')
5970
+ .style('outline-solid', 'none')
5930
5971
  .on('mouseenter', () => {
5931
5972
  showRegionOverlay(idxs);
5932
5973
  })
@@ -6416,40 +6457,88 @@ export function renderQuadrant(
6416
6457
  return 'bottom-right';
6417
6458
  };
6418
6459
 
6460
+ // Build obstacle rects from quadrant watermark labels for collision avoidance
6461
+ const POINT_RADIUS = 6;
6462
+ const POINT_LABEL_FONT_SIZE = 12;
6463
+ const quadrantLabelObstacles: LabelRect[] = quadrantDefsWithLabel.map((d) => {
6464
+ const layout = labelLayouts.get(d.label!.text)!;
6465
+ const totalW =
6466
+ Math.max(...layout.lines.map((l) => l.length)) *
6467
+ layout.fontSize *
6468
+ CHAR_WIDTH_RATIO;
6469
+ const totalH = layout.lines.length * layout.fontSize * 1.2;
6470
+ return {
6471
+ x: d.labelX - totalW / 2,
6472
+ y: d.labelY - totalH / 2,
6473
+ w: totalW,
6474
+ h: totalH,
6475
+ };
6476
+ });
6477
+
6478
+ // Compute collision-free label positions for all points
6479
+ const pointPixels = quadrantPoints.map((point) => ({
6480
+ label: point.label,
6481
+ cx: xScale(point.x),
6482
+ cy: yScale(point.y),
6483
+ }));
6484
+
6485
+ const placedPointLabels = computeQuadrantPointLabels(
6486
+ pointPixels,
6487
+ { left: 0, top: 0, right: chartWidth, bottom: chartHeight },
6488
+ quadrantLabelObstacles,
6489
+ POINT_RADIUS,
6490
+ POINT_LABEL_FONT_SIZE
6491
+ );
6492
+
6419
6493
  // Draw data points (circles and labels)
6420
6494
  const pointsG = chartG.append('g').attr('class', 'points');
6421
6495
 
6422
- quadrantPoints.forEach((point) => {
6496
+ quadrantPoints.forEach((point, i) => {
6423
6497
  const cx = xScale(point.x);
6424
6498
  const cy = yScale(point.y);
6425
6499
  const quadrant = getPointQuadrant(point.x, point.y);
6426
6500
  const quadDef = quadrantDefs.find((d) => d.position === quadrant);
6427
6501
  const pointColor =
6428
6502
  quadDef?.label?.color ?? defaultColors[quadDef?.colorIdx ?? 0];
6503
+ const placed = placedPointLabels[i];
6429
6504
 
6430
6505
  const pointG = pointsG
6431
6506
  .append('g')
6432
6507
  .attr('class', 'point-group')
6433
6508
  .attr('data-line-number', String(point.lineNumber));
6434
6509
 
6510
+ // Connector line (drawn first so it renders behind circle and label)
6511
+ if (placed.connectorLine) {
6512
+ pointG
6513
+ .append('line')
6514
+ .attr('x1', placed.connectorLine.x1)
6515
+ .attr('y1', placed.connectorLine.y1)
6516
+ .attr('x2', placed.connectorLine.x2)
6517
+ .attr('y2', placed.connectorLine.y2)
6518
+ .attr('stroke', pointColor)
6519
+ .attr('stroke-width', 1)
6520
+ .attr('opacity', 0.5);
6521
+ }
6522
+
6435
6523
  // Circle with white fill and colored border for visibility on opaque quadrants
6436
6524
  pointG
6437
6525
  .append('circle')
6438
6526
  .attr('cx', cx)
6439
6527
  .attr('cy', cy)
6440
- .attr('r', 6)
6528
+ .attr('r', POINT_RADIUS)
6441
6529
  .attr('fill', '#ffffff')
6442
6530
  .attr('stroke', pointColor)
6443
6531
  .attr('stroke-width', 2);
6444
6532
 
6445
- // Label (palette text color adapts to light/dark mode)
6533
+ // Label at computed position
6446
6534
  pointG
6447
6535
  .append('text')
6448
- .attr('x', cx)
6449
- .attr('y', cy - 10)
6450
- .attr('text-anchor', 'middle')
6536
+ .attr('x', placed.x)
6537
+ .attr('y', placed.y)
6538
+ .attr('text-anchor', placed.anchor)
6539
+ .attr('dominant-baseline', 'central')
6451
6540
  .attr('fill', textColor)
6452
- .attr('font-size', '12px')
6541
+ .attr('font-size', `${POINT_LABEL_FONT_SIZE}px`)
6453
6542
  .attr('font-weight', '700')
6454
6543
  .style('text-shadow', `0 1px 2px ${shadowColor}`)
6455
6544
  .text(point.label);
@@ -6468,7 +6557,7 @@ export function renderQuadrant(
6468
6557
  })
6469
6558
  .on('mouseleave', () => {
6470
6559
  hideTooltip(tooltip);
6471
- pointG.select('circle').attr('r', 6);
6560
+ pointG.select('circle').attr('r', POINT_RADIUS);
6472
6561
  })
6473
6562
  .on('click', () => {
6474
6563
  if (onClickItem && point.lineNumber) onClickItem(point.lineNumber);
@@ -6609,8 +6698,11 @@ export async function renderForExport(
6609
6698
 
6610
6699
  // Apply interactive collapse state when provided
6611
6700
  const collapsedNodes = orgExportState?.collapsedNodes;
6612
- const activeTagGroup =
6613
- orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
6701
+ const activeTagGroup = resolveActiveTagGroup(
6702
+ orgParsed.tagGroups,
6703
+ orgParsed.options['active-tag'],
6704
+ orgExportState?.activeTagGroup ?? options?.tagGroup
6705
+ );
6614
6706
  const hiddenAttributes = orgExportState?.hiddenAttributes;
6615
6707
 
6616
6708
  const { parsed: effectiveParsed, hiddenCounts } =
@@ -6660,8 +6752,11 @@ export async function renderForExport(
6660
6752
 
6661
6753
  // Apply interactive collapse state when provided
6662
6754
  const collapsedNodes = orgExportState?.collapsedNodes;
6663
- const activeTagGroup =
6664
- orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
6755
+ const activeTagGroup = resolveActiveTagGroup(
6756
+ sitemapParsed.tagGroups,
6757
+ sitemapParsed.options['active-tag'],
6758
+ orgExportState?.activeTagGroup ?? options?.tagGroup
6759
+ );
6665
6760
  const hiddenAttributes = orgExportState?.hiddenAttributes;
6666
6761
 
6667
6762
  const { parsed: effectiveParsed, hiddenCounts } =
@@ -6718,7 +6813,11 @@ export async function renderForExport(
6718
6813
  theme === 'dark',
6719
6814
  undefined,
6720
6815
  undefined,
6721
- options?.tagGroup
6816
+ resolveActiveTagGroup(
6817
+ kanbanParsed.tagGroups,
6818
+ kanbanParsed.options['active-tag'],
6819
+ options?.tagGroup
6820
+ )
6722
6821
  );
6723
6822
  return finalizeSvgExport(container, theme, effectivePalette, options);
6724
6823
  }
@@ -6775,7 +6874,11 @@ export async function renderForExport(
6775
6874
  theme === 'dark',
6776
6875
  undefined,
6777
6876
  { width: exportWidth, height: exportHeight },
6778
- options?.tagGroup
6877
+ resolveActiveTagGroup(
6878
+ erParsed.tagGroups,
6879
+ erParsed.options['active-tag'],
6880
+ options?.tagGroup
6881
+ )
6779
6882
  );
6780
6883
  return finalizeSvgExport(container, theme, effectivePalette, options);
6781
6884
  }
@@ -6803,7 +6906,10 @@ export async function renderForExport(
6803
6906
  blLayout,
6804
6907
  effectivePalette,
6805
6908
  theme === 'dark',
6806
- { exportDims: { width: exportWidth, height: exportHeight } }
6909
+ {
6910
+ exportDims: { width: exportWidth, height: exportHeight },
6911
+ activeTagGroup: options?.tagGroup,
6912
+ }
6807
6913
  );
6808
6914
  return finalizeSvgExport(container, theme, effectivePalette, options);
6809
6915
  }
@@ -6860,7 +6966,11 @@ export async function renderForExport(
6860
6966
  theme === 'dark',
6861
6967
  undefined,
6862
6968
  { width: exportWidth, height: exportHeight },
6863
- options?.tagGroup
6969
+ resolveActiveTagGroup(
6970
+ c4Parsed.tagGroups,
6971
+ c4Parsed.options['active-tag'],
6972
+ options?.tagGroup
6973
+ )
6864
6974
  );
6865
6975
  return finalizeSvgExport(container, theme, effectivePalette, options);
6866
6976
  }
@@ -6902,7 +7012,11 @@ export async function renderForExport(
6902
7012
 
6903
7013
  const infraComputed = computeInfra(infraParsed);
6904
7014
  const infraLayout = layoutInfra(infraComputed);
6905
- const activeTagGroup = options?.tagGroup ?? null;
7015
+ const activeTagGroup = resolveActiveTagGroup(
7016
+ infraParsed.tagGroups,
7017
+ infraParsed.options['active-tag'],
7018
+ options?.tagGroup
7019
+ );
6906
7020
 
6907
7021
  const titleOffset = infraParsed.title ? 40 : 0;
6908
7022
  const legendGroups = computeInfraLegendGroups(
@@ -7055,7 +7169,11 @@ export async function renderForExport(
7055
7169
  isDark,
7056
7170
  undefined,
7057
7171
  dims,
7058
- orgExportState?.activeTagGroup ?? options?.tagGroup,
7172
+ resolveActiveTagGroup(
7173
+ parsed.timelineTagGroups,
7174
+ undefined,
7175
+ orgExportState?.activeTagGroup ?? options?.tagGroup
7176
+ ),
7059
7177
  orgExportState?.swimlaneTagGroup
7060
7178
  );
7061
7179
  } else if (parsed.type === 'venn') {