@diagrammo/dgmo 0.3.2 → 0.4.0

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.
@@ -53,16 +53,13 @@ export interface SequenceParticipant {
53
53
 
54
54
  /**
55
55
  * A message between two participants.
56
- * Placeholder for future stories — included in the interface now for completeness.
57
56
  */
58
57
  export interface SequenceMessage {
59
58
  from: string;
60
59
  to: string;
61
60
  label: string;
62
- returnLabel?: string;
63
61
  lineNumber: number;
64
62
  async?: boolean;
65
- bidirectional?: boolean;
66
63
  /** Standalone return — the message itself IS a return (dashed arrow, no call). */
67
64
  standaloneReturn?: boolean;
68
65
  }
@@ -164,70 +161,13 @@ const GROUP_HEADING_PATTERN = /^##\s+(.+?)(?:\(([^)]+)\))?\s*$/;
164
161
  // Section divider pattern — "== Label ==", "== Label(color) ==", or "== Label" (trailing == optional)
165
162
  const SECTION_PATTERN = /^==\s+(.+?)(?:\s*==)?\s*$/;
166
163
 
167
- // Arrow pattern for sequence inference — "A -> B: message", "A ~> B: message",
168
- // "A -label-> B", "A ~label~> B", "A <-> B", "A <~> B"
169
- const ARROW_PATTERN = /\S+\s*(?:<->|<~>|->|~>|-\S+->|~\S+~>|<-\S+->|<~\S+~>)\s*\S+/;
170
-
171
- // <- return syntax: "Login <- 200 OK"
172
- const ARROW_RETURN_PATTERN = /^(.+?)\s*<-\s*(.+)$/;
173
-
174
- // UML method(args): returnType syntax: "getUser(id): UserObj"
175
- const UML_RETURN_PATTERN = /^(\w+\([^)]*\))\s*:\s*(.+)$/;
164
+ // Arrow pattern for sequence inference — detects any arrow form
165
+ const ARROW_PATTERN = /\S+\s*(?:<-\S+-|<~\S+~|-\S+->|~\S+~>|->|~>|<-|<~)\s*\S+/;
176
166
 
177
167
  // Note patterns — "note: text", "note right of API: text", "note left of User"
178
168
  const NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(\S+))?\s*:\s*(.+)$/i;
179
169
  const NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+([^\s:]+))?\s*:?\s*$/i;
180
170
 
181
- /**
182
- * Extract return label from a message label string.
183
- * Priority: `<-` syntax first, then UML `method(): return` syntax,
184
- * then shorthand ` : ` separator (splits on last occurrence).
185
- */
186
- function parseReturnLabel(rawLabel: string): {
187
- label: string;
188
- returnLabel?: string;
189
- standaloneReturn?: boolean;
190
- } {
191
- if (!rawLabel) return { label: '' };
192
-
193
- // Standalone return: label starts with `<-` (no forward label)
194
- const standaloneMatch = rawLabel.match(/^<-\s*(.*)$/);
195
- if (standaloneMatch) {
196
- return {
197
- label: standaloneMatch[1].trim(),
198
- standaloneReturn: true,
199
- };
200
- }
201
-
202
- // Check <- syntax first (separates forward label from return label)
203
- const arrowReturn = rawLabel.match(ARROW_RETURN_PATTERN);
204
- if (arrowReturn) {
205
- return { label: arrowReturn[1].trim(), returnLabel: arrowReturn[2].trim() };
206
- }
207
-
208
- // Check UML method(args): returnType syntax
209
- const umlReturn = rawLabel.match(UML_RETURN_PATTERN);
210
- if (umlReturn) {
211
- return { label: umlReturn[1].trim(), returnLabel: umlReturn[2].trim() };
212
- }
213
-
214
- // Shorthand colon return syntax (split on last ":")
215
- // Skip if the colon is part of a URL scheme (followed by //)
216
- const lastColon = rawLabel.lastIndexOf(':');
217
- if (lastColon > 0 && lastColon < rawLabel.length - 1) {
218
- const afterColon = rawLabel.substring(lastColon + 1);
219
- if (!afterColon.startsWith('//')) {
220
- const reqPart = rawLabel.substring(0, lastColon).trim();
221
- const resPart = afterColon.trim();
222
- if (reqPart && resPart) {
223
- return { label: reqPart, returnLabel: resPart };
224
- }
225
- }
226
- }
227
-
228
- return { label: rawLabel };
229
- }
230
-
231
171
  /**
232
172
  * Parse a .dgmo file with `chart: sequence` into a structured representation.
233
173
  */
@@ -365,7 +305,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
365
305
  // Parse header key: value lines (always top-level)
366
306
  // Skip 'note' lines — parsed in the indent-aware section below
367
307
  const colonIndex = trimmed.indexOf(':');
368
- if (colonIndex > 0 && !trimmed.includes('->') && !trimmed.includes('~>')) {
308
+ if (colonIndex > 0 && !trimmed.includes('->') && !trimmed.includes('~>') && !trimmed.includes('<-') && !trimmed.includes('<~')) {
369
309
  const key = trimmed.substring(0, colonIndex).trim().toLowerCase();
370
310
  if (key === 'note' || key.startsWith('note ')) {
371
311
  // Fall through to indent-aware note parsing below
@@ -531,17 +471,16 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
531
471
  }
532
472
  if (labeledArrow) {
533
473
  contentStarted = true;
534
- const { from, to, label, async: isAsync, bidirectional } = labeledArrow;
474
+ const { from, to, label, async: isAsync, isReturn } = labeledArrow;
535
475
  lastMsgFrom = from;
536
476
 
537
477
  const msg: SequenceMessage = {
538
478
  from,
539
479
  to,
540
480
  label,
541
- returnLabel: undefined,
542
481
  lineNumber,
543
482
  ...(isAsync ? { async: true } : {}),
544
- ...(bidirectional ? { bidirectional: true } : {}),
483
+ ...(isReturn ? { standaloneReturn: true } : {}),
545
484
  };
546
485
  result.messages.push(msg);
547
486
  currentContainer().push(msg);
@@ -566,30 +505,56 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
566
505
  continue;
567
506
  }
568
507
 
569
- // ---- Plain bidi arrows: <-> and <~> ----
570
- // Must be checked BEFORE unidirectional plain arrows
571
- const bidiSyncMatch = trimmed.match(
572
- /^(\S+)\s*<->\s*([^\s:]+)\s*(?::\s*(.+))?$/
508
+ // ---- Error: old colon-postfix syntax (A -> B: msg) ----
509
+ const colonPostfixSync = trimmed.match(
510
+ /^(\S+)\s*->\s*([^\s:]+)\s*:\s*(.+)$/
573
511
  );
574
- const bidiAsyncMatch = trimmed.match(
575
- /^(\S+)\s*<~>\s*([^\s:]+)\s*(?::\s*(.+))?$/
512
+ const colonPostfixAsync = trimmed.match(
513
+ /^(\S+)\s*~>\s*([^\s:]+)\s*:\s*(.+)$/
576
514
  );
577
- const bidiMatch = bidiSyncMatch || bidiAsyncMatch;
578
- if (bidiMatch) {
515
+ const colonPostfix = colonPostfixSync || colonPostfixAsync;
516
+ if (colonPostfix) {
517
+ const a = colonPostfix[1];
518
+ const b = colonPostfix[2];
519
+ const msg = colonPostfix[3].trim();
520
+ const arrowChar = colonPostfixAsync ? '~' : '-';
521
+ const arrowEnd = colonPostfixAsync ? '~>' : '->';
522
+ pushError(
523
+ lineNumber,
524
+ `Colon syntax is no longer supported. Use '${a} ${arrowChar}${msg}${arrowEnd} ${b}' instead`
525
+ );
526
+ continue;
527
+ }
528
+
529
+ // ---- Error: plain bidirectional arrows (A <-> B, A <~> B) ----
530
+ const bidiPlainMatch = trimmed.match(
531
+ /^(\S+)\s*(?:<->|<~>)\s*(\S+)/
532
+ );
533
+ if (bidiPlainMatch) {
534
+ pushError(
535
+ lineNumber,
536
+ "Bidirectional arrows are no longer supported. Use two separate lines: 'A -msg-> B' and 'B -msg-> A'"
537
+ );
538
+ continue;
539
+ }
540
+
541
+ // ---- Bare (unlabeled) return arrows: A <- B, A <~ B ----
542
+ const bareReturnSync = trimmed.match(/^(\S+)\s+<-\s+(\S+)$/);
543
+ const bareReturnAsync = trimmed.match(/^(\S+)\s+<~\s+(\S+)$/);
544
+ const bareReturn = bareReturnSync || bareReturnAsync;
545
+ if (bareReturn) {
579
546
  contentStarted = true;
580
- const from = bidiMatch[1];
581
- const to = bidiMatch[2];
547
+ const to = bareReturn[1]; // left side = receiver
548
+ const from = bareReturn[2]; // right side = sender
582
549
  lastMsgFrom = from;
583
- const rawLabel = bidiMatch[3]?.trim() || '';
584
- const isBidiAsync = !!bidiAsyncMatch;
585
550
 
586
551
  const msg: SequenceMessage = {
587
552
  from,
588
553
  to,
589
- label: rawLabel,
554
+ label: '',
590
555
  lineNumber,
591
- bidirectional: true,
592
- ...(isBidiAsync ? { async: true } : {}),
556
+ standaloneReturn: true,
557
+ ...(bareReturnAsync ? { async: true } : {}),
593
558
  };
594
559
  result.messages.push(msg);
595
560
  currentContainer().push(msg);
@@ -613,42 +578,26 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
613
578
  continue;
614
579
  }
615
580
 
616
- // Match ~> (async arrow) or -> (sync arrow)
617
- let isAsync = false;
618
- const asyncArrowMatch = trimmed.match(
619
- /^(\S+)\s*~>\s*([^\s:]+)\s*(?::\s*(.+))?$/
620
- );
621
- const syncArrowMatch = trimmed.match(
622
- /^(\S+)\s*->\s*([^\s:]+)\s*(?::\s*(.+))?$/
623
- );
624
- const arrowMatch = asyncArrowMatch || syncArrowMatch;
625
- if (asyncArrowMatch) isAsync = true;
626
-
627
- if (arrowMatch) {
581
+ // ---- Bare (unlabeled) call arrows: A -> B, A ~> B ----
582
+ const bareCallSync = trimmed.match(/^(\S+)\s*->\s*(\S+)$/);
583
+ const bareCallAsync = trimmed.match(/^(\S+)\s*~>\s*(\S+)$/);
584
+ const bareCall = bareCallSync || bareCallAsync;
585
+ if (bareCall) {
628
586
  contentStarted = true;
629
- const from = arrowMatch[1];
630
- const to = arrowMatch[2];
587
+ const from = bareCall[1];
588
+ const to = bareCall[2];
631
589
  lastMsgFrom = from;
632
- const rawLabel = arrowMatch[3]?.trim() || '';
633
-
634
- // Extract return label — skip for async messages
635
- const { label, returnLabel, standaloneReturn } = isAsync
636
- ? { label: rawLabel, returnLabel: undefined, standaloneReturn: undefined }
637
- : parseReturnLabel(rawLabel);
638
590
 
639
591
  const msg: SequenceMessage = {
640
592
  from,
641
593
  to,
642
- label,
643
- returnLabel,
594
+ label: '',
644
595
  lineNumber,
645
- ...(isAsync ? { async: true } : {}),
646
- ...(standaloneReturn ? { standaloneReturn: true } : {}),
596
+ ...(bareCallAsync ? { async: true } : {}),
647
597
  };
648
598
  result.messages.push(msg);
649
599
  currentContainer().push(msg);
650
600
 
651
- // Auto-register participants from message usage with type inference
652
601
  if (!result.participants.some((p) => p.id === from)) {
653
602
  result.participants.push({
654
603
  id: from,
@@ -538,7 +538,6 @@ export interface RenderStep {
538
538
  label: string;
539
539
  messageIndex: number;
540
540
  async?: boolean;
541
- bidirectional?: boolean;
542
541
  }
543
542
 
544
543
  /**
@@ -551,7 +550,6 @@ export function buildRenderSequence(messages: SequenceMessage[]): RenderStep[] {
551
550
  const stack: {
552
551
  from: string;
553
552
  to: string;
554
- returnLabel?: string;
555
553
  messageIndex: number;
556
554
  }[] = [];
557
555
 
@@ -566,7 +564,7 @@ export function buildRenderSequence(messages: SequenceMessage[]): RenderStep[] {
566
564
  type: 'return',
567
565
  from: top.to,
568
566
  to: top.from,
569
- label: top.returnLabel || '',
567
+ label: '',
570
568
  messageIndex: top.messageIndex,
571
569
  });
572
570
  }
@@ -601,14 +599,8 @@ export function buildRenderSequence(messages: SequenceMessage[]): RenderStep[] {
601
599
  label: msg.label,
602
600
  messageIndex: mi,
603
601
  ...(msg.async ? { async: true } : {}),
604
- ...(msg.bidirectional ? { bidirectional: true } : {}),
605
602
  });
606
603
 
607
- // Bidirectional messages: no activation bar, no return
608
- if (msg.bidirectional) {
609
- continue;
610
- }
611
-
612
604
  // Async messages: no return arrow, no activation on target
613
605
  if (msg.async) {
614
606
  continue;
@@ -620,7 +612,7 @@ export function buildRenderSequence(messages: SequenceMessage[]): RenderStep[] {
620
612
  type: 'return',
621
613
  from: msg.to,
622
614
  to: msg.from,
623
- label: msg.returnLabel || '',
615
+ label: '',
624
616
  messageIndex: mi,
625
617
  });
626
618
  } else {
@@ -628,7 +620,6 @@ export function buildRenderSequence(messages: SequenceMessage[]): RenderStep[] {
628
620
  stack.push({
629
621
  from: msg.from,
630
622
  to: msg.to,
631
- returnLabel: msg.returnLabel,
632
623
  messageIndex: mi,
633
624
  });
634
625
  }
@@ -641,7 +632,7 @@ export function buildRenderSequence(messages: SequenceMessage[]): RenderStep[] {
641
632
  type: 'return',
642
633
  from: top.to,
643
634
  to: top.from,
644
- label: top.returnLabel || '',
635
+ label: '',
645
636
  messageIndex: top.messageIndex,
646
637
  });
647
638
  }
@@ -1368,42 +1359,6 @@ export function renderSequenceDiagram(
1368
1359
  .attr('stroke', palette.text)
1369
1360
  .attr('stroke-width', 1.2);
1370
1361
 
1371
- // Filled reverse arrowhead for bidirectional sync arrows (marker-start)
1372
- defs
1373
- .append('marker')
1374
- .attr('id', 'seq-arrowhead-reverse')
1375
- .attr('viewBox', `0 0 ${ARROWHEAD_SIZE} ${ARROWHEAD_SIZE}`)
1376
- .attr('refX', 0)
1377
- .attr('refY', ARROWHEAD_SIZE / 2)
1378
- .attr('markerWidth', ARROWHEAD_SIZE)
1379
- .attr('markerHeight', ARROWHEAD_SIZE)
1380
- .attr('orient', 'auto')
1381
- .append('polygon')
1382
- .attr(
1383
- 'points',
1384
- `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
1385
- )
1386
- .attr('fill', palette.text);
1387
-
1388
- // Open reverse arrowhead for bidirectional async arrows (marker-start)
1389
- defs
1390
- .append('marker')
1391
- .attr('id', 'seq-arrowhead-async-reverse')
1392
- .attr('viewBox', `0 0 ${ARROWHEAD_SIZE} ${ARROWHEAD_SIZE}`)
1393
- .attr('refX', 0)
1394
- .attr('refY', ARROWHEAD_SIZE / 2)
1395
- .attr('markerWidth', ARROWHEAD_SIZE)
1396
- .attr('markerHeight', ARROWHEAD_SIZE)
1397
- .attr('orient', 'auto')
1398
- .append('polyline')
1399
- .attr(
1400
- 'points',
1401
- `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
1402
- )
1403
- .attr('fill', 'none')
1404
- .attr('stroke', palette.text)
1405
- .attr('stroke-width', 1.2);
1406
-
1407
1362
  // Render title
1408
1363
  if (title) {
1409
1364
  const titleEl = svg
@@ -1958,12 +1913,7 @@ export function renderSequenceDiagram(
1958
1913
  const markerRef = step.async
1959
1914
  ? 'url(#seq-arrowhead-async)'
1960
1915
  : 'url(#seq-arrowhead)';
1961
- const markerStartRef = step.bidirectional
1962
- ? step.async
1963
- ? 'url(#seq-arrowhead-async-reverse)'
1964
- : 'url(#seq-arrowhead-reverse)'
1965
- : null;
1966
- const line = svg
1916
+ svg
1967
1917
  .append('line')
1968
1918
  .attr('x1', x1)
1969
1919
  .attr('y1', y)
@@ -1979,12 +1929,6 @@ export function renderSequenceDiagram(
1979
1929
  )
1980
1930
  .attr('data-msg-index', String(step.messageIndex))
1981
1931
  .attr('data-step-index', String(i));
1982
- if (markerStartRef) {
1983
- line.attr('marker-start', markerStartRef);
1984
- }
1985
- if (step.bidirectional && step.async) {
1986
- line.attr('stroke-dasharray', '6 4');
1987
- }
1988
1932
 
1989
1933
  if (step.label) {
1990
1934
  const midX = (x1 + x2) / 2;
@@ -2,49 +2,63 @@
2
2
  // Shared Arrow Parsing Utility
3
3
  // ============================================================
4
4
  //
5
- // Labeled arrow syntax: `-label->`, `~label~>`, `<-label->`, `<~label~>`
6
- // Used by sequence, C4, and init-status parsers.
5
+ // Labeled arrow syntax:
6
+ // Forward: `-label->`, `~label~>`
7
+ // Return: `<-label-`, `<~label~`
7
8
 
8
9
  export interface ParsedArrow {
9
10
  from: string;
10
11
  to: string;
11
12
  label: string;
12
13
  async: boolean;
13
- bidirectional: boolean;
14
+ isReturn: boolean;
14
15
  }
15
16
 
16
- // Bidi patterns checked FIRST — longer prefix avoids partial match
17
- const BIDI_SYNC_LABELED_RE = /^(\S+)\s+<-(.+)->\s+(\S+)$/;
18
- const BIDI_ASYNC_LABELED_RE = /^(\S+)\s+<~(.+)~>\s+(\S+)$/;
17
+ // Forward (call) patterns
19
18
  const SYNC_LABELED_RE = /^(\S+)\s+-(.+)->\s+(\S+)$/;
20
19
  const ASYNC_LABELED_RE = /^(\S+)\s+~(.+)~>\s+(\S+)$/;
21
20
 
22
- const ARROW_CHARS = ['->', '~>', '<->', '<~>'];
21
+ // Return patterns A <-msg- B means from=B, to=A
22
+ const RETURN_SYNC_LABELED_RE = /^(\S+)\s+<-(.+)-\s+(\S+)$/;
23
+ const RETURN_ASYNC_LABELED_RE = /^(\S+)\s+<~(.+)~\s+(\S+)$/;
24
+
25
+ // Bidi detection (for error messages only)
26
+ const BIDI_SYNC_RE = /^(\S+)\s+<-(.+)->\s+(\S+)$/;
27
+ const BIDI_ASYNC_RE = /^(\S+)\s+<~(.+)~>\s+(\S+)$/;
28
+
29
+ const ARROW_CHARS = ['->', '~>', '<-', '<~'];
23
30
 
24
31
  /**
25
32
  * Try to parse a labeled arrow from a trimmed line.
26
33
  *
27
34
  * Returns:
28
35
  * - `ParsedArrow` if matched and valid
29
- * - `{ error: string }` if matched but label contains arrow chars
30
- * - `null` if not a labeled arrow (caller should fall through to plain patterns)
36
+ * - `{ error: string }` if matched but invalid (bidi, or arrow chars in label)
37
+ * - `null` if not a labeled arrow (caller should fall through to bare patterns)
31
38
  */
32
39
  export function parseArrow(
33
40
  line: string,
34
41
  ): ParsedArrow | { error: string } | null {
35
- // Order: bidi first (longer prefix), then unidirectional
42
+ // Check bidi patterns first return error
43
+ if (BIDI_SYNC_RE.test(line) || BIDI_ASYNC_RE.test(line)) {
44
+ return {
45
+ error:
46
+ "Bidirectional arrows are no longer supported. Use two separate lines: 'A -msg-> B' and 'B -msg-> A'",
47
+ };
48
+ }
49
+
36
50
  const patterns: {
37
51
  re: RegExp;
38
52
  async: boolean;
39
- bidirectional: boolean;
53
+ isReturn: boolean;
40
54
  }[] = [
41
- { re: BIDI_SYNC_LABELED_RE, async: false, bidirectional: true },
42
- { re: BIDI_ASYNC_LABELED_RE, async: true, bidirectional: true },
43
- { re: SYNC_LABELED_RE, async: false, bidirectional: false },
44
- { re: ASYNC_LABELED_RE, async: true, bidirectional: false },
55
+ { re: RETURN_SYNC_LABELED_RE, async: false, isReturn: true },
56
+ { re: RETURN_ASYNC_LABELED_RE, async: true, isReturn: true },
57
+ { re: SYNC_LABELED_RE, async: false, isReturn: false },
58
+ { re: ASYNC_LABELED_RE, async: true, isReturn: false },
45
59
  ];
46
60
 
47
- for (const { re, async: isAsync, bidirectional } of patterns) {
61
+ for (const { re, async: isAsync, isReturn } of patterns) {
48
62
  const m = line.match(re);
49
63
  if (!m) continue;
50
64
 
@@ -57,17 +71,28 @@ export function parseArrow(
57
71
  for (const arrow of ARROW_CHARS) {
58
72
  if (label.includes(arrow)) {
59
73
  return {
60
- error: 'Arrow characters (->, ~>) are not allowed inside labels',
74
+ error: 'Arrow characters (->, ~>, <-, <~) are not allowed inside labels',
61
75
  };
62
76
  }
63
77
  }
64
78
 
79
+ if (isReturn) {
80
+ // Return arrow: A <-msg- B → from=B (source), to=A (destination)
81
+ return {
82
+ from: m[3],
83
+ to: m[1],
84
+ label,
85
+ async: isAsync,
86
+ isReturn: true,
87
+ };
88
+ }
89
+
65
90
  return {
66
91
  from: m[1],
67
92
  to: m[3],
68
93
  label,
69
94
  async: isAsync,
70
- bidirectional,
95
+ isReturn: false,
71
96
  };
72
97
  }
73
98
 
@@ -75,6 +75,49 @@ export function collectIndentedValues(
75
75
  return { values, newIndex: j - 1 };
76
76
  }
77
77
 
78
+ /**
79
+ * Parse series names from a `series:` value or indented block, extracting
80
+ * optional per-name color suffixes. Shared between chart.ts and echarts.ts.
81
+ *
82
+ * Returns the parsed names, optional colors, and the raw series string
83
+ * (for single-series display), plus `newIndex` if indented values were consumed.
84
+ */
85
+ export function parseSeriesNames(
86
+ value: string,
87
+ lines: string[],
88
+ lineIndex: number,
89
+ palette?: PaletteColors,
90
+ ): {
91
+ series: string;
92
+ names: string[];
93
+ nameColors: (string | undefined)[];
94
+ newIndex: number;
95
+ } {
96
+ let rawNames: string[];
97
+ let series: string;
98
+ let newIndex = lineIndex;
99
+ if (value) {
100
+ series = value;
101
+ rawNames = value.split(',').map((s) => s.trim()).filter(Boolean);
102
+ } else {
103
+ const collected = collectIndentedValues(lines, lineIndex);
104
+ newIndex = collected.newIndex;
105
+ rawNames = collected.values;
106
+ series = rawNames.join(', ');
107
+ }
108
+ const names: string[] = [];
109
+ const nameColors: (string | undefined)[] = [];
110
+ for (const raw of rawNames) {
111
+ const extracted = extractColor(raw, palette);
112
+ nameColors.push(extracted.color);
113
+ names.push(extracted.label);
114
+ }
115
+ if (names.length === 1) {
116
+ series = names[0];
117
+ }
118
+ return { series, names, nameColors, newIndex };
119
+ }
120
+
78
121
  /** Parse pipe-delimited metadata from segments after the first (name) segment. */
79
122
  export function parsePipeMetadata(
80
123
  segments: string[],