@diagrammo/dgmo 0.5.0 → 0.5.1

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/dist/index.d.cts CHANGED
@@ -397,7 +397,7 @@ interface ArcNodeGroup {
397
397
  color: string | null;
398
398
  lineNumber: number;
399
399
  }
400
- type TimelineSort = 'time' | 'group';
400
+ type TimelineSort = 'time' | 'group' | 'tag';
401
401
  interface TimelineEvent {
402
402
  date: string;
403
403
  endDate: string | null;
@@ -477,6 +477,7 @@ interface ParsedD3 {
477
477
  timelineMarkers: TimelineMarker[];
478
478
  timelineTagGroups: TagGroup[];
479
479
  timelineSort: TimelineSort;
480
+ timelineDefaultSwimlaneTG?: string;
480
481
  timelineScale: boolean;
481
482
  timelineSwimlanes: boolean;
482
483
  vennSets: VennSet[];
@@ -545,7 +546,7 @@ declare function computeTimeTicks(domainMin: number, domainMax: number, scale: d
545
546
  * Renders a timeline chart into the given container using D3.
546
547
  * Supports horizontal (default) and vertical orientation.
547
548
  */
548
- declare function renderTimeline(container: HTMLDivElement, parsed: ParsedD3, palette: PaletteColors, isDark: boolean, onClickItem?: (lineNumber: number) => void, exportDims?: D3ExportDimensions, activeTagGroup?: string | null): void;
549
+ declare function renderTimeline(container: HTMLDivElement, parsed: ParsedD3, palette: PaletteColors, isDark: boolean, onClickItem?: (lineNumber: number) => void, exportDims?: D3ExportDimensions, activeTagGroup?: string | null, swimlaneTagGroup?: string | null, onTagStateChange?: (activeTagGroup: string | null, swimlaneTagGroup: string | null) => void): void;
549
550
  /**
550
551
  * Renders a word cloud into the given container using d3-cloud.
551
552
  */
@@ -564,6 +565,7 @@ declare function renderD3ForExport(content: string, theme: 'light' | 'dark' | 't
564
565
  collapsedNodes?: Set<string>;
565
566
  activeTagGroup?: string | null;
566
567
  hiddenAttributes?: Set<string>;
568
+ swimlaneTagGroup?: string | null;
567
569
  }, options?: {
568
570
  branding?: boolean;
569
571
  c4Level?: 'context' | 'containers' | 'components' | 'deployment';
@@ -1974,6 +1976,7 @@ declare const seriesColors: string[];
1974
1976
  interface DiagramViewState {
1975
1977
  activeTagGroup?: string;
1976
1978
  collapsedGroups?: string[];
1979
+ swimlaneTagGroup?: string;
1977
1980
  }
1978
1981
  interface DecodedDiagramUrl {
1979
1982
  dsl: string;
package/dist/index.d.ts CHANGED
@@ -397,7 +397,7 @@ interface ArcNodeGroup {
397
397
  color: string | null;
398
398
  lineNumber: number;
399
399
  }
400
- type TimelineSort = 'time' | 'group';
400
+ type TimelineSort = 'time' | 'group' | 'tag';
401
401
  interface TimelineEvent {
402
402
  date: string;
403
403
  endDate: string | null;
@@ -477,6 +477,7 @@ interface ParsedD3 {
477
477
  timelineMarkers: TimelineMarker[];
478
478
  timelineTagGroups: TagGroup[];
479
479
  timelineSort: TimelineSort;
480
+ timelineDefaultSwimlaneTG?: string;
480
481
  timelineScale: boolean;
481
482
  timelineSwimlanes: boolean;
482
483
  vennSets: VennSet[];
@@ -545,7 +546,7 @@ declare function computeTimeTicks(domainMin: number, domainMax: number, scale: d
545
546
  * Renders a timeline chart into the given container using D3.
546
547
  * Supports horizontal (default) and vertical orientation.
547
548
  */
548
- declare function renderTimeline(container: HTMLDivElement, parsed: ParsedD3, palette: PaletteColors, isDark: boolean, onClickItem?: (lineNumber: number) => void, exportDims?: D3ExportDimensions, activeTagGroup?: string | null): void;
549
+ declare function renderTimeline(container: HTMLDivElement, parsed: ParsedD3, palette: PaletteColors, isDark: boolean, onClickItem?: (lineNumber: number) => void, exportDims?: D3ExportDimensions, activeTagGroup?: string | null, swimlaneTagGroup?: string | null, onTagStateChange?: (activeTagGroup: string | null, swimlaneTagGroup: string | null) => void): void;
549
550
  /**
550
551
  * Renders a word cloud into the given container using d3-cloud.
551
552
  */
@@ -564,6 +565,7 @@ declare function renderD3ForExport(content: string, theme: 'light' | 'dark' | 't
564
565
  collapsedNodes?: Set<string>;
565
566
  activeTagGroup?: string | null;
566
567
  hiddenAttributes?: Set<string>;
568
+ swimlaneTagGroup?: string | null;
567
569
  }, options?: {
568
570
  branding?: boolean;
569
571
  c4Level?: 'context' | 'containers' | 'components' | 'deployment';
@@ -1974,6 +1976,7 @@ declare const seriesColors: string[];
1974
1976
  interface DiagramViewState {
1975
1977
  activeTagGroup?: string;
1976
1978
  collapsedGroups?: string[];
1979
+ swimlaneTagGroup?: string;
1977
1980
  }
1978
1981
  interface DecodedDiagramUrl {
1979
1982
  dsl: string;
package/dist/index.js CHANGED
@@ -18478,9 +18478,18 @@ function parseD3(content, palette) {
18478
18478
  continue;
18479
18479
  }
18480
18480
  if (key === "sort") {
18481
- const v = line10.substring(colonIndex + 1).trim().toLowerCase();
18482
- if (v === "time" || v === "group") {
18483
- result.timelineSort = v;
18481
+ const v = line10.substring(colonIndex + 1).trim();
18482
+ const vLower = v.toLowerCase();
18483
+ if (vLower === "time" || vLower === "group") {
18484
+ result.timelineSort = vLower;
18485
+ } else if (vLower === "tag" || vLower.startsWith("tag:")) {
18486
+ result.timelineSort = "tag";
18487
+ if (vLower.startsWith("tag:")) {
18488
+ const groupRef = v.substring(4).trim();
18489
+ if (groupRef) {
18490
+ result.timelineDefaultSwimlaneTG = groupRef;
18491
+ }
18492
+ }
18484
18493
  }
18485
18494
  continue;
18486
18495
  }
@@ -18630,6 +18639,25 @@ function parseD3(content, palette) {
18630
18639
  }
18631
18640
  }
18632
18641
  }
18642
+ if (result.timelineSort === "tag") {
18643
+ if (result.timelineTagGroups.length === 0) {
18644
+ warn(1, '"sort: tag" requires at least one tag group definition');
18645
+ result.timelineSort = "time";
18646
+ } else if (result.timelineDefaultSwimlaneTG) {
18647
+ const ref = result.timelineDefaultSwimlaneTG.toLowerCase();
18648
+ const match = result.timelineTagGroups.find(
18649
+ (g) => g.name.toLowerCase() === ref || g.alias?.toLowerCase() === ref
18650
+ );
18651
+ if (match) {
18652
+ result.timelineDefaultSwimlaneTG = match.name;
18653
+ } else {
18654
+ warn(1, `"sort: tag:${result.timelineDefaultSwimlaneTG}" \u2014 no tag group matches "${result.timelineDefaultSwimlaneTG}"`);
18655
+ result.timelineDefaultSwimlaneTG = result.timelineTagGroups[0].name;
18656
+ }
18657
+ } else {
18658
+ result.timelineDefaultSwimlaneTG = result.timelineTagGroups[0].name;
18659
+ }
18660
+ }
18633
18661
  return result;
18634
18662
  }
18635
18663
  if (result.type === "venn") {
@@ -19388,7 +19416,7 @@ function buildEventTooltipHtml(ev) {
19388
19416
  function buildEraTooltipHtml(era) {
19389
19417
  return `<strong>${era.label}</strong><br>${formatDateLabel(era.startDate)} \u2192 ${formatDateLabel(era.endDate)}`;
19390
19418
  }
19391
- function renderTimeline(container, parsed, palette, isDark, onClickItem, exportDims, activeTagGroup) {
19419
+ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportDims, activeTagGroup, swimlaneTagGroup, onTagStateChange) {
19392
19420
  d3Selection12.select(container).selectAll(":not([data-d3-tooltip])").remove();
19393
19421
  const {
19394
19422
  timelineEvents,
@@ -19402,6 +19430,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19402
19430
  orientation
19403
19431
  } = parsed;
19404
19432
  if (timelineEvents.length === 0) return;
19433
+ if (swimlaneTagGroup == null && timelineSort === "tag" && parsed.timelineDefaultSwimlaneTG) {
19434
+ swimlaneTagGroup = parsed.timelineDefaultSwimlaneTG;
19435
+ }
19405
19436
  const tooltip = createTooltip(container, palette, isDark);
19406
19437
  const width = exportDims?.width ?? container.clientWidth;
19407
19438
  const height = exportDims?.height ?? container.clientHeight;
@@ -19415,9 +19446,47 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19415
19446
  timelineGroups.forEach((grp, i) => {
19416
19447
  groupColorMap.set(grp.name, grp.color ?? colors[i % colors.length]);
19417
19448
  });
19449
+ let tagLanes = null;
19450
+ if (swimlaneTagGroup) {
19451
+ const tagKey = swimlaneTagGroup.toLowerCase();
19452
+ const tagGroup = parsed.timelineTagGroups.find(
19453
+ (g) => g.name.toLowerCase() === tagKey
19454
+ );
19455
+ if (tagGroup) {
19456
+ const buckets = /* @__PURE__ */ new Map();
19457
+ const otherEvents = [];
19458
+ for (const ev of timelineEvents) {
19459
+ const val = ev.metadata[tagKey];
19460
+ if (val) {
19461
+ const list = buckets.get(val) ?? [];
19462
+ list.push(ev);
19463
+ buckets.set(val, list);
19464
+ } else {
19465
+ otherEvents.push(ev);
19466
+ }
19467
+ }
19468
+ const laneEntries = [...buckets.entries()].sort((a, b) => {
19469
+ const aMin = Math.min(
19470
+ ...a[1].map((e) => parseTimelineDate(e.date))
19471
+ );
19472
+ const bMin = Math.min(
19473
+ ...b[1].map((e) => parseTimelineDate(e.date))
19474
+ );
19475
+ return aMin - bMin;
19476
+ });
19477
+ tagLanes = laneEntries.map(([name, events]) => ({ name, events }));
19478
+ if (otherEvents.length > 0) {
19479
+ tagLanes.push({ name: "(Other)", events: otherEvents });
19480
+ }
19481
+ for (const entry of tagGroup.entries) {
19482
+ groupColorMap.set(entry.value, entry.color);
19483
+ }
19484
+ }
19485
+ }
19486
+ const effectiveColorTG = activeTagGroup ?? swimlaneTagGroup ?? null;
19418
19487
  function eventColor(ev) {
19419
- if (activeTagGroup) {
19420
- const tagColor = resolveTagColor(ev.metadata, parsed.timelineTagGroups, activeTagGroup);
19488
+ if (effectiveColorTG) {
19489
+ const tagColor = resolveTagColor(ev.metadata, parsed.timelineTagGroups, effectiveColorTG);
19421
19490
  if (tagColor) return tagColor;
19422
19491
  }
19423
19492
  if (ev.group && groupColorMap.has(ev.group)) {
@@ -19521,12 +19590,28 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19521
19590
  }
19522
19591
  const tagLegendReserve = parsed.timelineTagGroups.length > 0 ? 36 : 0;
19523
19592
  if (isVertical) {
19524
- if (timelineSort === "group" && timelineGroups.length > 0) {
19525
- const groupNames = timelineGroups.map((gr) => gr.name);
19526
- const ungroupedEvents = timelineEvents.filter(
19527
- (ev) => ev.group === null || !groupNames.includes(ev.group)
19528
- );
19529
- const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19593
+ const useGroupedVertical = tagLanes != null || timelineSort === "group" && timelineGroups.length > 0;
19594
+ if (useGroupedVertical) {
19595
+ let laneNames;
19596
+ let laneEventsByName;
19597
+ if (tagLanes) {
19598
+ laneNames = tagLanes.map((l) => l.name);
19599
+ laneEventsByName = new Map(tagLanes.map((l) => [l.name, l.events]));
19600
+ } else {
19601
+ const groupNames = timelineGroups.map((gr) => gr.name);
19602
+ const ungroupedEvents = timelineEvents.filter(
19603
+ (ev) => ev.group === null || !groupNames.includes(ev.group)
19604
+ );
19605
+ laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19606
+ laneEventsByName = new Map(
19607
+ laneNames.map((name) => [
19608
+ name,
19609
+ timelineEvents.filter(
19610
+ (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19611
+ )
19612
+ ])
19613
+ );
19614
+ }
19530
19615
  const laneCount = laneNames.length;
19531
19616
  const scaleMargin = timelineScale ? 40 : 0;
19532
19617
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
@@ -19581,6 +19666,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19581
19666
  formatDateLabel(latestEndDateStr)
19582
19667
  );
19583
19668
  }
19669
+ if (timelineSwimlanes || tagLanes) {
19670
+ laneNames.forEach((laneName, laneIdx) => {
19671
+ const laneX = laneIdx * laneWidth;
19672
+ const fillColor = laneIdx % 2 === 0 ? textColor : "transparent";
19673
+ g.append("rect").attr("class", "tl-swimlane").attr("data-group", laneName).attr("x", laneX).attr("y", 0).attr("width", laneWidth).attr("height", innerHeight).attr("fill", fillColor).attr("opacity", 0.06);
19674
+ });
19675
+ }
19584
19676
  laneNames.forEach((laneName, laneIdx) => {
19585
19677
  const laneX = laneIdx * laneWidth;
19586
19678
  const laneColor = groupColorMap.get(laneName) ?? textColor;
@@ -19588,9 +19680,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19588
19680
  const headerG = g.append("g").attr("class", "tl-lane-header").attr("data-group", laneName).style("cursor", "pointer").on("mouseenter", () => fadeToGroup(g, laneName)).on("mouseleave", () => fadeReset(g));
19589
19681
  headerG.append("text").attr("x", laneCenter).attr("y", -15).attr("text-anchor", "middle").attr("fill", laneColor).attr("font-size", "12px").attr("font-weight", "600").text(laneName);
19590
19682
  g.append("line").attr("x1", laneCenter).attr("y1", 0).attr("x2", laneCenter).attr("y2", innerHeight).attr("stroke", mutedColor).attr("stroke-width", 1).attr("stroke-dasharray", "4,4");
19591
- const laneEvents = timelineEvents.filter(
19592
- (ev) => laneName === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === laneName
19593
- );
19683
+ const laneEvents = laneEventsByName.get(laneName) ?? [];
19594
19684
  for (const ev of laneEvents) {
19595
19685
  const y = yScale(parseTimelineDate(ev.date));
19596
19686
  const evG = g.append("g").attr("class", "tl-event").attr("data-group", laneName).attr("data-line-number", String(ev.lineNumber)).attr("data-date", String(parseTimelineDate(ev.date))).attr(
@@ -19608,10 +19698,11 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19608
19698
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19609
19699
  });
19610
19700
  setTagAttrs(evG, ev);
19701
+ const evColor = eventColor(ev);
19611
19702
  if (ev.endDate) {
19612
19703
  const y2 = yScale(parseTimelineDate(ev.endDate));
19613
19704
  const rectH = Math.max(y2 - y, 4);
19614
- let fill2 = laneColor;
19705
+ let fill2 = evColor;
19615
19706
  if (ev.uncertain) {
19616
19707
  const gradientId = `uncertain-vg-${ev.lineNumber}`;
19617
19708
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -19625,7 +19716,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19625
19716
  evG.append("rect").attr("x", laneCenter - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2);
19626
19717
  evG.append("text").attr("x", laneCenter + 14).attr("y", y + rectH / 2).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
19627
19718
  } else {
19628
- evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", laneColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19719
+ evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19629
19720
  evG.append("text").attr("x", laneCenter + 10).attr("y", y).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
19630
19721
  }
19631
19722
  }
@@ -19748,18 +19839,24 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19748
19839
  }
19749
19840
  const BAR_H = 22;
19750
19841
  const GROUP_GAP = 12;
19751
- if (timelineSort === "group" && timelineGroups.length > 0) {
19752
- const groupNames = timelineGroups.map((gr) => gr.name);
19753
- const ungroupedEvents = timelineEvents.filter(
19754
- (ev) => ev.group === null || !groupNames.includes(ev.group)
19755
- );
19756
- const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19757
- const lanes = laneNames.map((name) => ({
19758
- name,
19759
- events: timelineEvents.filter(
19760
- (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19761
- )
19762
- }));
19842
+ const useGroupedHorizontal = tagLanes != null || timelineSort === "group" && timelineGroups.length > 0;
19843
+ if (useGroupedHorizontal) {
19844
+ let lanes;
19845
+ if (tagLanes) {
19846
+ lanes = tagLanes;
19847
+ } else {
19848
+ const groupNames = timelineGroups.map((gr) => gr.name);
19849
+ const ungroupedEvents = timelineEvents.filter(
19850
+ (ev) => ev.group === null || !groupNames.includes(ev.group)
19851
+ );
19852
+ const laneNames = ungroupedEvents.length > 0 ? [...groupNames, "(Other)"] : groupNames;
19853
+ lanes = laneNames.map((name) => ({
19854
+ name,
19855
+ events: timelineEvents.filter(
19856
+ (ev) => name === "(Other)" ? ev.group === null || !groupNames.includes(ev.group) : ev.group === name
19857
+ )
19858
+ }));
19859
+ }
19763
19860
  const totalEventRows = lanes.reduce((s, l) => s + l.events.length, 0);
19764
19861
  const scaleMargin = timelineScale ? 24 : 0;
19765
19862
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
@@ -19819,7 +19916,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19819
19916
  );
19820
19917
  }
19821
19918
  let curY = markerMargin;
19822
- if (timelineSwimlanes) {
19919
+ if (timelineSwimlanes || tagLanes) {
19823
19920
  let swimY = markerMargin;
19824
19921
  lanes.forEach((lane, idx) => {
19825
19922
  const laneSpan = lane.events.length * rowH;
@@ -19871,12 +19968,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19871
19968
  if (onClickItem && ev.lineNumber) onClickItem(ev.lineNumber);
19872
19969
  });
19873
19970
  setTagAttrs(evG, ev);
19971
+ const evColor = eventColor(ev);
19874
19972
  if (ev.endDate) {
19875
19973
  const x2 = xScale(parseTimelineDate(ev.endDate));
19876
19974
  const rectW = Math.max(x2 - x, 4);
19877
19975
  const estLabelWidth = ev.label.length * 7 + 16;
19878
19976
  const labelFitsInside = rectW >= estLabelWidth;
19879
- let fill2 = laneColor;
19977
+ let fill2 = evColor;
19880
19978
  if (ev.uncertain) {
19881
19979
  const gradientId = `uncertain-${ev.lineNumber}`;
19882
19980
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -19884,7 +19982,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19884
19982
  { offset: "0%", opacity: 1 },
19885
19983
  { offset: "80%", opacity: 1 },
19886
19984
  { offset: "100%", opacity: 0 }
19887
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", laneColor).attr("stop-opacity", (d) => d.opacity);
19985
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", evColor).attr("stop-opacity", (d) => d.opacity);
19888
19986
  fill2 = `url(#${gradientId})`;
19889
19987
  }
19890
19988
  evG.append("rect").attr("x", x).attr("y", y - BAR_H / 2).attr("width", rectW).attr("height", BAR_H).attr("rx", 4).attr("fill", fill2);
@@ -19901,7 +19999,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19901
19999
  const wouldFlipLeft = x > innerWidth * 0.6;
19902
20000
  const labelFitsLeft = x - 10 - estLabelWidth > 0;
19903
20001
  const flipLeft = wouldFlipLeft && labelFitsLeft;
19904
- evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", laneColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
20002
+ evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
19905
20003
  evG.append("text").attr("x", flipLeft ? x - 10 : x + 10).attr("y", y).attr("dy", "0.35em").attr("text-anchor", flipLeft ? "end" : "start").attr("fill", textColor).attr("font-size", "12px").text(ev.label);
19906
20004
  }
19907
20005
  });
@@ -20056,10 +20154,36 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20056
20154
  const LG_ENTRY_DOT_GAP = 4;
20057
20155
  const LG_ENTRY_TRAIL = 8;
20058
20156
  const LG_GROUP_GAP = 12;
20157
+ const LG_ICON_W = 20;
20059
20158
  const mainSvg = d3Selection12.select(container).select("svg");
20060
20159
  const mainG = mainSvg.select("g");
20061
20160
  if (!mainSvg.empty() && !mainG.empty()) {
20062
- let drawLegend2 = function() {
20161
+ let drawSwimlaneIcon2 = function(parent, x, y, isSwimActive) {
20162
+ const iconG = parent.append("g").attr("class", "tl-swimlane-icon").attr("transform", `translate(${x}, ${y})`).style("cursor", "pointer");
20163
+ const barColor = isSwimActive ? palette.primary : palette.textMuted;
20164
+ const barOpacity = isSwimActive ? 1 : 0.35;
20165
+ const bars = [
20166
+ { y: 0, w: 8 },
20167
+ { y: 4, w: 12 },
20168
+ { y: 8, w: 6 }
20169
+ ];
20170
+ for (const bar of bars) {
20171
+ iconG.append("rect").attr("x", 0).attr("y", bar.y).attr("width", bar.w).attr("height", 2).attr("rx", 1).attr("fill", barColor).attr("opacity", barOpacity);
20172
+ }
20173
+ return iconG;
20174
+ }, relayout2 = function() {
20175
+ renderTimeline(
20176
+ container,
20177
+ parsed,
20178
+ palette,
20179
+ isDark,
20180
+ onClickItem,
20181
+ exportDims,
20182
+ currentActiveGroup,
20183
+ currentSwimlaneGroup,
20184
+ onTagStateChange
20185
+ );
20186
+ }, drawLegend2 = function() {
20063
20187
  mainSvg.selectAll(".tl-tag-legend-group").remove();
20064
20188
  const totalW = legendGroups.reduce((s, lg) => {
20065
20189
  const isActive = currentActiveGroup != null && lg.group.name.toLowerCase() === currentActiveGroup.toLowerCase();
@@ -20067,14 +20191,16 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20067
20191
  }, 0) + (legendGroups.length - 1) * LG_GROUP_GAP;
20068
20192
  let cx = (width - totalW) / 2;
20069
20193
  for (const lg of legendGroups) {
20070
- const isActive = currentActiveGroup != null && lg.group.name.toLowerCase() === currentActiveGroup.toLowerCase();
20194
+ const groupKey = lg.group.name.toLowerCase();
20195
+ const isActive = currentActiveGroup != null && currentActiveGroup.toLowerCase() === groupKey;
20196
+ const isSwimActive = currentSwimlaneGroup != null && currentSwimlaneGroup.toLowerCase() === groupKey;
20071
20197
  const pillLabel = lg.group.name;
20072
20198
  const pillWidth = pillLabel.length * LG_PILL_FONT_W + LG_PILL_PAD;
20073
- const gEl = mainSvg.append("g").attr("transform", `translate(${cx}, ${legendY})`).attr("class", "tl-tag-legend-group tl-tag-legend-entry").attr("data-legend-group", lg.group.name.toLowerCase()).attr("data-tag-group", lg.group.name.toLowerCase()).attr("data-legend-entry", "__group__").style("cursor", "pointer").on("click", () => {
20074
- const groupKey = lg.group.name.toLowerCase();
20199
+ const gEl = mainSvg.append("g").attr("transform", `translate(${cx}, ${legendY})`).attr("class", "tl-tag-legend-group tl-tag-legend-entry").attr("data-legend-group", groupKey).attr("data-tag-group", groupKey).attr("data-legend-entry", "__group__").style("cursor", "pointer").on("click", () => {
20075
20200
  currentActiveGroup = currentActiveGroup === groupKey ? null : groupKey;
20076
20201
  drawLegend2();
20077
20202
  recolorEvents2();
20203
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20078
20204
  });
20079
20205
  if (isActive) {
20080
20206
  gEl.append("rect").attr("width", lg.expandedWidth).attr("height", LG_HEIGHT).attr("rx", LG_HEIGHT / 2).attr("fill", groupBg);
@@ -20088,7 +20214,16 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20088
20214
  }
20089
20215
  gEl.append("text").attr("x", pillXOff + pillWidth / 2).attr("y", LG_HEIGHT / 2 + LG_PILL_FONT_SIZE / 2 - 2).attr("font-size", LG_PILL_FONT_SIZE).attr("font-weight", "500").attr("font-family", FONT_FAMILY).attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
20090
20216
  if (isActive) {
20091
- let entryX = pillXOff + pillWidth + 4;
20217
+ const iconX = pillXOff + pillWidth + 5;
20218
+ const iconY = (LG_HEIGHT - 10) / 2;
20219
+ const iconEl = drawSwimlaneIcon2(gEl, iconX, iconY, isSwimActive);
20220
+ iconEl.attr("data-swimlane-toggle", groupKey).on("click", (event) => {
20221
+ event.stopPropagation();
20222
+ currentSwimlaneGroup = currentSwimlaneGroup === groupKey ? null : groupKey;
20223
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20224
+ relayout2();
20225
+ });
20226
+ let entryX = pillXOff + pillWidth + LG_ICON_W + 4;
20092
20227
  for (const entry of lg.group.entries) {
20093
20228
  const tagKey = lg.group.name.toLowerCase();
20094
20229
  const tagVal = entry.value.toLowerCase();
@@ -20118,17 +20253,18 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20118
20253
  cx += (isActive ? lg.expandedWidth : lg.minifiedWidth) + LG_GROUP_GAP;
20119
20254
  }
20120
20255
  }, recolorEvents2 = function() {
20256
+ const colorTG = currentActiveGroup ?? swimlaneTagGroup ?? null;
20121
20257
  mainG.selectAll(".tl-event").each(function() {
20122
20258
  const el = d3Selection12.select(this);
20123
20259
  const lineNum = el.attr("data-line-number");
20124
20260
  const ev = lineNum ? eventByLine.get(lineNum) : void 0;
20125
20261
  if (!ev) return;
20126
20262
  let color;
20127
- if (currentActiveGroup) {
20263
+ if (colorTG) {
20128
20264
  const tagColor = resolveTagColor(
20129
20265
  ev.metadata,
20130
20266
  parsed.timelineTagGroups,
20131
- currentActiveGroup
20267
+ colorTG
20132
20268
  );
20133
20269
  color = tagColor ?? (ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor);
20134
20270
  } else {
@@ -20138,12 +20274,12 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20138
20274
  el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", color);
20139
20275
  });
20140
20276
  };
20141
- var drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20277
+ var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20142
20278
  const legendY = title ? 50 : 10;
20143
20279
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
20144
20280
  const legendGroups = parsed.timelineTagGroups.map((g) => {
20145
20281
  const pillW = g.name.length * LG_PILL_FONT_W + LG_PILL_PAD;
20146
- let entryX = LG_CAPSULE_PAD + pillW + 4;
20282
+ let entryX = LG_CAPSULE_PAD + pillW + LG_ICON_W + 4;
20147
20283
  for (const entry of g.entries) {
20148
20284
  const textX = entryX + LG_DOT_R * 2 + LG_ENTRY_DOT_GAP;
20149
20285
  entryX = textX + entry.value.length * LG_ENTRY_FONT_W + LG_ENTRY_TRAIL;
@@ -20155,6 +20291,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20155
20291
  };
20156
20292
  });
20157
20293
  let currentActiveGroup = activeTagGroup ?? null;
20294
+ let currentSwimlaneGroup = swimlaneTagGroup ?? null;
20158
20295
  const eventByLine = /* @__PURE__ */ new Map();
20159
20296
  for (const ev of timelineEvents) {
20160
20297
  eventByLine.set(String(ev.lineNumber), ev);
@@ -21129,7 +21266,16 @@ async function renderD3ForExport(content, theme, palette, orgExportState, option
21129
21266
  } else if (parsed.type === "arc") {
21130
21267
  renderArcDiagram(container, parsed, effectivePalette, isDark, void 0, dims);
21131
21268
  } else if (parsed.type === "timeline") {
21132
- renderTimeline(container, parsed, effectivePalette, isDark, void 0, dims);
21269
+ renderTimeline(
21270
+ container,
21271
+ parsed,
21272
+ effectivePalette,
21273
+ isDark,
21274
+ void 0,
21275
+ dims,
21276
+ orgExportState?.activeTagGroup,
21277
+ orgExportState?.swimlaneTagGroup
21278
+ );
21133
21279
  } else if (parsed.type === "venn") {
21134
21280
  renderVenn(container, parsed, effectivePalette, isDark, void 0, dims);
21135
21281
  } else if (parsed.type === "quadrant") {
@@ -22012,6 +22158,9 @@ function encodeDiagramUrl(dsl, options) {
22012
22158
  if (options?.viewState?.collapsedGroups?.length) {
22013
22159
  hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
22014
22160
  }
22161
+ if (options?.viewState?.swimlaneTagGroup) {
22162
+ hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
22163
+ }
22015
22164
  return { url: `${baseUrl}?${hash}#${hash}` };
22016
22165
  }
22017
22166
  function decodeDiagramUrl(hash) {
@@ -22035,6 +22184,9 @@ function decodeDiagramUrl(hash) {
22035
22184
  if (key === "cg" && val) {
22036
22185
  viewState.collapsedGroups = val.split(",").filter(Boolean);
22037
22186
  }
22187
+ if (key === "swim" && val) {
22188
+ viewState.swimlaneTagGroup = val;
22189
+ }
22038
22190
  }
22039
22191
  if (payload.startsWith("dgmo=")) {
22040
22192
  payload = payload.slice(5);