@diagrammo/dgmo 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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, viewMode?: boolean): 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, viewMode?: boolean): 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, viewMode) {
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,26 +20154,62 @@ 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
+ viewMode
20186
+ );
20187
+ }, drawLegend2 = function() {
20063
20188
  mainSvg.selectAll(".tl-tag-legend-group").remove();
20064
- const totalW = legendGroups.reduce((s, lg) => {
20189
+ const visibleGroups = viewMode ? legendGroups.filter(
20190
+ (lg) => currentActiveGroup != null && lg.group.name.toLowerCase() === currentActiveGroup.toLowerCase()
20191
+ ) : legendGroups;
20192
+ if (visibleGroups.length === 0) return;
20193
+ const totalW = visibleGroups.reduce((s, lg) => {
20065
20194
  const isActive = currentActiveGroup != null && lg.group.name.toLowerCase() === currentActiveGroup.toLowerCase();
20066
20195
  return s + (isActive ? lg.expandedWidth : lg.minifiedWidth);
20067
- }, 0) + (legendGroups.length - 1) * LG_GROUP_GAP;
20196
+ }, 0) + (visibleGroups.length - 1) * LG_GROUP_GAP;
20068
20197
  let cx = (width - totalW) / 2;
20069
- for (const lg of legendGroups) {
20070
- const isActive = currentActiveGroup != null && lg.group.name.toLowerCase() === currentActiveGroup.toLowerCase();
20198
+ for (const lg of visibleGroups) {
20199
+ const groupKey = lg.group.name.toLowerCase();
20200
+ const isActive = currentActiveGroup != null && currentActiveGroup.toLowerCase() === groupKey;
20201
+ const isSwimActive = currentSwimlaneGroup != null && currentSwimlaneGroup.toLowerCase() === groupKey;
20071
20202
  const pillLabel = lg.group.name;
20072
20203
  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();
20075
- currentActiveGroup = currentActiveGroup === groupKey ? null : groupKey;
20076
- drawLegend2();
20077
- recolorEvents2();
20078
- });
20204
+ 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__");
20205
+ if (!viewMode) {
20206
+ gEl.style("cursor", "pointer").on("click", () => {
20207
+ currentActiveGroup = currentActiveGroup === groupKey ? null : groupKey;
20208
+ drawLegend2();
20209
+ recolorEvents2();
20210
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20211
+ });
20212
+ }
20079
20213
  if (isActive) {
20080
20214
  gEl.append("rect").attr("width", lg.expandedWidth).attr("height", LG_HEIGHT).attr("rx", LG_HEIGHT / 2).attr("fill", groupBg);
20081
20215
  }
@@ -20088,27 +20222,44 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20088
20222
  }
20089
20223
  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
20224
  if (isActive) {
20091
- let entryX = pillXOff + pillWidth + 4;
20225
+ let entryX;
20226
+ if (!viewMode) {
20227
+ const iconX = pillXOff + pillWidth + 5;
20228
+ const iconY = (LG_HEIGHT - 10) / 2;
20229
+ const iconEl = drawSwimlaneIcon2(gEl, iconX, iconY, isSwimActive);
20230
+ iconEl.attr("data-swimlane-toggle", groupKey).on("click", (event) => {
20231
+ event.stopPropagation();
20232
+ currentSwimlaneGroup = currentSwimlaneGroup === groupKey ? null : groupKey;
20233
+ onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
20234
+ relayout2();
20235
+ });
20236
+ entryX = pillXOff + pillWidth + LG_ICON_W + 4;
20237
+ } else {
20238
+ entryX = pillXOff + pillWidth + 8;
20239
+ }
20092
20240
  for (const entry of lg.group.entries) {
20093
20241
  const tagKey = lg.group.name.toLowerCase();
20094
20242
  const tagVal = entry.value.toLowerCase();
20095
- const entryG = gEl.append("g").attr("class", "tl-tag-legend-entry").attr("data-tag-group", tagKey).attr("data-legend-entry", tagVal).style("cursor", "pointer").on("mouseenter", (event) => {
20096
- event.stopPropagation();
20097
- fadeToTagValue(mainG, tagKey, tagVal);
20098
- mainSvg.selectAll(".tl-tag-legend-entry").each(function() {
20099
- const el = d3Selection12.select(this);
20100
- const ev = el.attr("data-legend-entry");
20101
- if (ev === "__group__") return;
20102
- const eg = el.attr("data-tag-group");
20103
- el.attr("opacity", eg === tagKey && ev === tagVal ? 1 : FADE_OPACITY);
20243
+ const entryG = gEl.append("g").attr("class", "tl-tag-legend-entry").attr("data-tag-group", tagKey).attr("data-legend-entry", tagVal);
20244
+ if (!viewMode) {
20245
+ entryG.style("cursor", "pointer").on("mouseenter", (event) => {
20246
+ event.stopPropagation();
20247
+ fadeToTagValue(mainG, tagKey, tagVal);
20248
+ mainSvg.selectAll(".tl-tag-legend-entry").each(function() {
20249
+ const el = d3Selection12.select(this);
20250
+ const ev = el.attr("data-legend-entry");
20251
+ if (ev === "__group__") return;
20252
+ const eg = el.attr("data-tag-group");
20253
+ el.attr("opacity", eg === tagKey && ev === tagVal ? 1 : FADE_OPACITY);
20254
+ });
20255
+ }).on("mouseleave", (event) => {
20256
+ event.stopPropagation();
20257
+ fadeReset(mainG);
20258
+ mainSvg.selectAll(".tl-tag-legend-entry").attr("opacity", 1);
20259
+ }).on("click", (event) => {
20260
+ event.stopPropagation();
20104
20261
  });
20105
- }).on("mouseleave", (event) => {
20106
- event.stopPropagation();
20107
- fadeReset(mainG);
20108
- mainSvg.selectAll(".tl-tag-legend-entry").attr("opacity", 1);
20109
- }).on("click", (event) => {
20110
- event.stopPropagation();
20111
- });
20262
+ }
20112
20263
  entryG.append("circle").attr("cx", entryX + LG_DOT_R).attr("cy", LG_HEIGHT / 2).attr("r", LG_DOT_R).attr("fill", entry.color);
20113
20264
  const textX = entryX + LG_DOT_R * 2 + LG_ENTRY_DOT_GAP;
20114
20265
  entryG.append("text").attr("x", textX).attr("y", LG_HEIGHT / 2 + LG_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LG_ENTRY_FONT_SIZE).attr("font-family", FONT_FAMILY).attr("fill", palette.textMuted).text(entry.value);
@@ -20118,17 +20269,18 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20118
20269
  cx += (isActive ? lg.expandedWidth : lg.minifiedWidth) + LG_GROUP_GAP;
20119
20270
  }
20120
20271
  }, recolorEvents2 = function() {
20272
+ const colorTG = currentActiveGroup ?? swimlaneTagGroup ?? null;
20121
20273
  mainG.selectAll(".tl-event").each(function() {
20122
20274
  const el = d3Selection12.select(this);
20123
20275
  const lineNum = el.attr("data-line-number");
20124
20276
  const ev = lineNum ? eventByLine.get(lineNum) : void 0;
20125
20277
  if (!ev) return;
20126
20278
  let color;
20127
- if (currentActiveGroup) {
20279
+ if (colorTG) {
20128
20280
  const tagColor = resolveTagColor(
20129
20281
  ev.metadata,
20130
20282
  parsed.timelineTagGroups,
20131
- currentActiveGroup
20283
+ colorTG
20132
20284
  );
20133
20285
  color = tagColor ?? (ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor);
20134
20286
  } else {
@@ -20138,12 +20290,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20138
20290
  el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", color);
20139
20291
  });
20140
20292
  };
20141
- var drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20293
+ var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20142
20294
  const legendY = title ? 50 : 10;
20143
20295
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
20144
20296
  const legendGroups = parsed.timelineTagGroups.map((g) => {
20145
20297
  const pillW = g.name.length * LG_PILL_FONT_W + LG_PILL_PAD;
20146
- let entryX = LG_CAPSULE_PAD + pillW + 4;
20298
+ const iconSpace = viewMode ? 8 : LG_ICON_W + 4;
20299
+ let entryX = LG_CAPSULE_PAD + pillW + iconSpace;
20147
20300
  for (const entry of g.entries) {
20148
20301
  const textX = entryX + LG_DOT_R * 2 + LG_ENTRY_DOT_GAP;
20149
20302
  entryX = textX + entry.value.length * LG_ENTRY_FONT_W + LG_ENTRY_TRAIL;
@@ -20155,6 +20308,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20155
20308
  };
20156
20309
  });
20157
20310
  let currentActiveGroup = activeTagGroup ?? null;
20311
+ let currentSwimlaneGroup = swimlaneTagGroup ?? null;
20158
20312
  const eventByLine = /* @__PURE__ */ new Map();
20159
20313
  for (const ev of timelineEvents) {
20160
20314
  eventByLine.set(String(ev.lineNumber), ev);
@@ -21129,7 +21283,16 @@ async function renderD3ForExport(content, theme, palette, orgExportState, option
21129
21283
  } else if (parsed.type === "arc") {
21130
21284
  renderArcDiagram(container, parsed, effectivePalette, isDark, void 0, dims);
21131
21285
  } else if (parsed.type === "timeline") {
21132
- renderTimeline(container, parsed, effectivePalette, isDark, void 0, dims);
21286
+ renderTimeline(
21287
+ container,
21288
+ parsed,
21289
+ effectivePalette,
21290
+ isDark,
21291
+ void 0,
21292
+ dims,
21293
+ orgExportState?.activeTagGroup,
21294
+ orgExportState?.swimlaneTagGroup
21295
+ );
21133
21296
  } else if (parsed.type === "venn") {
21134
21297
  renderVenn(container, parsed, effectivePalette, isDark, void 0, dims);
21135
21298
  } else if (parsed.type === "quadrant") {
@@ -22012,6 +22175,9 @@ function encodeDiagramUrl(dsl, options) {
22012
22175
  if (options?.viewState?.collapsedGroups?.length) {
22013
22176
  hash += `&cg=${encodeURIComponent(options.viewState.collapsedGroups.join(","))}`;
22014
22177
  }
22178
+ if (options?.viewState?.swimlaneTagGroup) {
22179
+ hash += `&swim=${encodeURIComponent(options.viewState.swimlaneTagGroup)}`;
22180
+ }
22015
22181
  return { url: `${baseUrl}?${hash}#${hash}` };
22016
22182
  }
22017
22183
  function decodeDiagramUrl(hash) {
@@ -22035,6 +22201,9 @@ function decodeDiagramUrl(hash) {
22035
22201
  if (key === "cg" && val) {
22036
22202
  viewState.collapsedGroups = val.split(",").filter(Boolean);
22037
22203
  }
22204
+ if (key === "swim" && val) {
22205
+ viewState.swimlaneTagGroup = val;
22206
+ }
22038
22207
  }
22039
22208
  if (payload.startsWith("dgmo=")) {
22040
22209
  payload = payload.slice(5);