@pixerate/editor 0.164.0 → 0.166.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.
Files changed (146) hide show
  1. package/dist/agent/index.cjs +54 -39
  2. package/dist/agent/index.js +24 -9
  3. package/dist/animation/index.cjs +6 -5
  4. package/dist/animation/index.js +5 -4
  5. package/dist/animation-editor/index.cjs +65 -64
  6. package/dist/animation-editor/index.js +8 -7
  7. package/dist/bridge/cli.cjs +3 -3
  8. package/dist/bridge/cli.js +2 -2
  9. package/dist/bridge/index.cjs +3 -3
  10. package/dist/bridge/index.js +2 -2
  11. package/dist/canvas/index.cjs +89 -87
  12. package/dist/canvas/index.js +11 -9
  13. package/dist/chart/controller.d.cts +60 -2
  14. package/dist/chart/controller.d.ts +60 -2
  15. package/dist/chart/index.cjs +657 -163
  16. package/dist/chart/index.d.cts +3 -0
  17. package/dist/chart/index.d.ts +3 -0
  18. package/dist/chart/index.js +512 -18
  19. package/dist/chart/layout-families.d.cts +14 -0
  20. package/dist/chart/layout-families.d.ts +14 -0
  21. package/dist/chart/layout-shared.d.cts +5 -0
  22. package/dist/chart/layout-shared.d.ts +5 -0
  23. package/dist/chart/links.d.cts +34 -0
  24. package/dist/chart/links.d.ts +34 -0
  25. package/dist/chart/mermaid.d.cts +9 -6
  26. package/dist/chart/mermaid.d.ts +9 -6
  27. package/dist/chart/race.d.cts +26 -0
  28. package/dist/chart/race.d.ts +26 -0
  29. package/dist/chart/schemas.d.cts +5 -0
  30. package/dist/chart/schemas.d.ts +5 -0
  31. package/dist/chart/spec.d.cts +3 -2
  32. package/dist/chart/spec.d.ts +3 -2
  33. package/dist/chart/style.d.cts +33 -0
  34. package/dist/chart/style.d.ts +33 -0
  35. package/dist/chart/types.d.cts +28 -2
  36. package/dist/chart/types.d.ts +28 -2
  37. package/dist/{chunk-ZVNAMTFO.cjs → chunk-3FSUJIXN.cjs} +18 -174
  38. package/dist/{chunk-RQCMSEM5.cjs → chunk-4QUUHTGJ.cjs} +2 -2
  39. package/dist/{chunk-CIPIESX4.cjs → chunk-4ZC5QJE2.cjs} +3 -3
  40. package/dist/{chunk-S6X2QRVU.cjs → chunk-5UEFBM2M.cjs} +1 -138
  41. package/dist/{chunk-5FNGLV2D.js → chunk-5UNQDC3Q.js} +18 -3
  42. package/dist/{chunk-YSAGBIRW.cjs → chunk-6FYIFGBW.cjs} +16 -16
  43. package/dist/{chunk-QYZGBVNC.js → chunk-7272D3K2.js} +10 -8
  44. package/dist/{chunk-462WGGXX.cjs → chunk-AMX23CSN.cjs} +27 -16
  45. package/dist/{chunk-3VLOOO3G.js → chunk-BDIM6NVW.js} +1 -1
  46. package/dist/{chunk-R3P2M7HU.cjs → chunk-BPX4Z3N3.cjs} +355 -207
  47. package/dist/{chunk-D54JHQK2.cjs → chunk-CHLV7UFP.cjs} +51 -51
  48. package/dist/{chunk-OXERWBKE.js → chunk-DFCAR7AW.js} +3 -3
  49. package/dist/{chunk-GZ2FM3ZB.js → chunk-DKNEQX7T.js} +6 -162
  50. package/dist/{chunk-JCXMJUQO.cjs → chunk-EGZM52EO.cjs} +3 -3
  51. package/dist/{chunk-ES3UEVUT.cjs → chunk-EUIDTZNQ.cjs} +112 -110
  52. package/dist/{chunk-H3OEM3Q2.js → chunk-F6R67I3A.js} +1 -1
  53. package/dist/{chunk-3NEZJCBY.js → chunk-FR432ROA.js} +349 -201
  54. package/dist/chunk-HIDFL5LQ.js +484 -0
  55. package/dist/{chunk-22KLZZGH.cjs → chunk-HXOBOHH2.cjs} +8 -8
  56. package/dist/{chunk-MKTDELCD.js → chunk-I5MRJW7B.js} +1 -1
  57. package/dist/{chunk-SDHZG5TF.js → chunk-ILP77UD6.js} +160 -2
  58. package/dist/{chunk-Z4P2RBMJ.js → chunk-ITANXMZD.js} +177 -3
  59. package/dist/chunk-KGRZMHZB.js +204 -0
  60. package/dist/{chunk-SET6KJOP.js → chunk-LPIKXPBM.js} +1 -1
  61. package/dist/{chunk-BQWGN5WH.js → chunk-M2TPMYJE.js} +1 -1
  62. package/dist/{chunk-GTELFJRE.cjs → chunk-M5GLQQWG.cjs} +2 -2
  63. package/dist/{chunk-4VU4CP7R.js → chunk-MHLU6GRJ.js} +1 -138
  64. package/dist/{chunk-NZD2MZJC.cjs → chunk-MZFZSVUJ.cjs} +38 -23
  65. package/dist/{chunk-DAPQGA55.js → chunk-NQQDGGXY.js} +2 -2
  66. package/dist/chunk-O6OGNDND.js +164 -0
  67. package/dist/{chunk-PA3IMPPS.cjs → chunk-OVYQAYGH.cjs} +81 -81
  68. package/dist/{chunk-YXOW456B.js → chunk-PPEDZ5WR.js} +1 -1
  69. package/dist/{chunk-24M7VD6M.cjs → chunk-S4EIAIEW.cjs} +7 -7
  70. package/dist/{chunk-M4MIZPQI.cjs → chunk-SJ2CPTQC.cjs} +189 -15
  71. package/dist/{chunk-YGG7WDWT.cjs → chunk-SZWVMB3B.cjs} +24 -18
  72. package/dist/chunk-TFF2GTGN.cjs +153 -0
  73. package/dist/chunk-TTCJPX4U.cjs +204 -0
  74. package/dist/{chunk-SBQVEBSU.cjs → chunk-UUUTEVX6.cjs} +242 -68
  75. package/dist/{chunk-WI7QJA7A.cjs → chunk-UXETV2NC.cjs} +161 -3
  76. package/dist/{chunk-GKNEJ6EN.cjs → chunk-V5L2G22R.cjs} +321 -107
  77. package/dist/{chunk-TALQONZ5.js → chunk-VB2CY6EG.js} +3 -3
  78. package/dist/{chunk-QY4KUDMY.js → chunk-VRXXAXUT.js} +2 -2
  79. package/dist/chunk-VU6WQSMB.cjs +164 -0
  80. package/dist/{chunk-NHLYK3AX.js → chunk-XBQX76CT.js} +182 -8
  81. package/dist/{chunk-5AARIS3R.cjs → chunk-XCDGM7TD.cjs} +37 -37
  82. package/dist/{chunk-POR54B55.js → chunk-XNU45TYA.js} +7 -1
  83. package/dist/{chunk-EVXQNQND.js → chunk-XZB3TJ6K.js} +16 -5
  84. package/dist/chunk-YBAPT7NL.js +153 -0
  85. package/dist/{chunk-SZA42R4N.js → chunk-YUFTVNLX.js} +230 -16
  86. package/dist/chunk-Z2UQYMPS.cjs +484 -0
  87. package/dist/color/index.cjs +5 -3
  88. package/dist/color/index.js +12 -10
  89. package/dist/diagram/index.cjs +55 -243
  90. package/dist/diagram/index.js +12 -200
  91. package/dist/export/index.cjs +7 -6
  92. package/dist/export/index.js +6 -5
  93. package/dist/extensions/index.cjs +12 -9
  94. package/dist/extensions/index.js +11 -8
  95. package/dist/gradient/index.cjs +29 -27
  96. package/dist/gradient/index.js +6 -4
  97. package/dist/image-editor/index.cjs +644 -643
  98. package/dist/image-editor/index.js +7 -6
  99. package/dist/index.cjs +21 -18
  100. package/dist/index.js +14 -11
  101. package/dist/paint-editor/index.cjs +19 -18
  102. package/dist/paint-editor/index.js +7 -6
  103. package/dist/parameters/index.cjs +2 -2
  104. package/dist/parameters/index.js +1 -1
  105. package/dist/persistence/assetWrites.d.cts +22 -0
  106. package/dist/persistence/assetWrites.d.ts +22 -0
  107. package/dist/persistence/assets.d.cts +71 -0
  108. package/dist/persistence/assets.d.ts +71 -0
  109. package/dist/persistence/documentStore.d.cts +17 -1
  110. package/dist/persistence/documentStore.d.ts +17 -1
  111. package/dist/persistence/documentTools.d.cts +5 -1
  112. package/dist/persistence/documentTools.d.ts +5 -1
  113. package/dist/persistence/editorAutosave.d.cts +7 -0
  114. package/dist/persistence/editorAutosave.d.ts +7 -0
  115. package/dist/persistence/index.cjs +141 -21
  116. package/dist/persistence/index.js +130 -10
  117. package/dist/persistence/storage.d.cts +13 -1
  118. package/dist/persistence/storage.d.ts +13 -1
  119. package/dist/spreadsheet/index.cjs +3 -3
  120. package/dist/spreadsheet/index.js +2 -2
  121. package/dist/tokens/index.cjs +11 -3
  122. package/dist/tokens/index.d.cts +1 -0
  123. package/dist/tokens/index.d.ts +1 -0
  124. package/dist/tokens/index.js +10 -2
  125. package/dist/tokens/style.d.cts +36 -0
  126. package/dist/tokens/style.d.ts +36 -0
  127. package/dist/tools/index.cjs +15 -3
  128. package/dist/tools/index.d.cts +2 -0
  129. package/dist/tools/index.d.ts +2 -0
  130. package/dist/tools/index.js +14 -2
  131. package/dist/tools/style-tools.d.cts +19 -0
  132. package/dist/tools/style-tools.d.ts +19 -0
  133. package/dist/tools/style.d.cts +212 -0
  134. package/dist/tools/style.d.ts +212 -0
  135. package/dist/tools/view.d.cts +6 -0
  136. package/dist/tools/view.d.ts +6 -0
  137. package/dist/typography/index.cjs +8 -6
  138. package/dist/typography/index.js +5 -3
  139. package/dist/vector/index.cjs +953 -952
  140. package/dist/vector/index.js +7 -6
  141. package/dist/video-editor/index.cjs +8 -7
  142. package/dist/video-editor/index.js +6 -5
  143. package/package.json +1 -1
  144. package/skills/pixerate-chart/SKILL.md +20 -2
  145. package/dist/chunk-ERAGQL2N.cjs +0 -68
  146. package/dist/chunk-KMU6QJB7.js +0 -68
@@ -1,17 +1,18 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
2
2
 
3
3
 
4
+ var _chunkTTCJPX4Ucjs = require('./chunk-TTCJPX4U.cjs');
4
5
 
5
6
 
6
7
 
7
8
 
8
9
 
9
10
 
10
- var _chunkSBQVEBSUcjs = require('./chunk-SBQVEBSU.cjs');
11
11
 
12
12
 
13
13
 
14
14
 
15
+ var _chunkUUUTEVX6cjs = require('./chunk-UUUTEVX6.cjs');
15
16
 
16
17
 
17
18
 
@@ -36,7 +37,15 @@ var _chunkSBQVEBSUcjs = require('./chunk-SBQVEBSU.cjs');
36
37
 
37
38
 
38
39
 
39
- var _chunkR3P2M7HUcjs = require('./chunk-R3P2M7HU.cjs');
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+ var _chunkBPX4Z3N3cjs = require('./chunk-BPX4Z3N3.cjs');
40
49
 
41
50
 
42
51
 
@@ -71,9 +80,14 @@ function splitOutside(text, sep) {
71
80
  out.push(cur);
72
81
  return out;
73
82
  }
83
+ var csvField = (text) => /[",\n]|^\s|\s$/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
84
+ var treemapName = (name) => !name.includes('"') ? `"${name}"` : !name.includes("'") ? `'${name}'` : `"${name.replace(/"/g, "'")}"`;
85
+ var yamlText = (text) => /^[\w][\w .,!?()/&+-]*$/.test(text) && !/:\s/.test(text) ? text : JSON.stringify(text);
74
86
  function chartMermaidFamily(spec) {
75
- const family = _chunkR3P2M7HUcjs.chartFamily.call(void 0, spec);
76
- if (family === "pie" || family === "funnel" || family === "treemap" || family === "parliament" || family === "sunburst") return "pie";
87
+ const family = _chunkBPX4Z3N3cjs.chartFamily.call(void 0, spec);
88
+ if (family === "sankey" && _optionalChain([spec, 'access', _2 => _2.links, 'optionalAccess', _3 => _3.length])) return "sankey";
89
+ if (family === "treemap" || family === "sunburst") return "treemap";
90
+ if (family === "pie" || family === "funnel" || family === "parliament") return "pie";
77
91
  if (family === "radar") return "radar";
78
92
  return "xychart";
79
93
  }
@@ -114,12 +128,32 @@ function chartToMermaid(spec, options = {}) {
114
128
  const visible = spec.series.filter((s) => !s.hidden);
115
129
  const lines = [];
116
130
  const v = (x) => x === null || !Number.isFinite(x) ? "0" : num(x);
117
- if (family === "pie") {
131
+ const front = [];
132
+ if (family === "sankey") {
133
+ if (spec.title) front.push("---", `title: ${yamlText(spec.title)}`, "---");
134
+ lines.push("sankey-beta");
135
+ for (const l of spec.links) lines.push(`${csvField(l.source)},${csvField(l.target)},${num(l.value)}`);
136
+ } else if (family === "treemap") {
137
+ lines.push("treemap-beta");
138
+ if (spec.title) lines.push(`title ${spec.title}`);
139
+ const first = _nullishCoalesce(visible[0], () => ( spec.series[0]));
140
+ const tree = spec.tree.length ? spec.tree : _chunkBPX4Z3N3cjs.chartTreeFromSeries.call(void 0, spec.categories, _nullishCoalesce(_optionalChain([first, 'optionalAccess', _4 => _4.values]), () => ( [])));
141
+ const write = (nodes, depth) => {
142
+ for (const n of nodes) {
143
+ const pad = ind.repeat(depth);
144
+ if (n.children) {
145
+ lines.push(`${pad}${treemapName(n.name)}`);
146
+ write(n.children, depth + 1);
147
+ } else lines.push(`${pad}${treemapName(n.name)}: ${num(_nullishCoalesce(n.value, () => ( 0)))}`);
148
+ }
149
+ };
150
+ write(tree, 1);
151
+ } else if (family === "pie") {
118
152
  const s = _nullishCoalesce(visible[0], () => ( spec.series[0]));
119
153
  lines.push(`pie${spec.dataLabels ? " showData" : ""}`);
120
154
  if (spec.title) lines.push(`${ind}title ${spec.title}`);
121
155
  spec.categories.forEach((c, i) => {
122
- const value = _optionalChain([s, 'optionalAccess', _2 => _2.values, 'access', _3 => _3[i]]);
156
+ const value = _optionalChain([s, 'optionalAccess', _5 => _5.values, 'access', _6 => _6[i]]);
123
157
  if (value !== null && value !== void 0 && Number.isFinite(value) && value > 0) lines.push(`${ind}${quote(c)} : ${num(value)}`);
124
158
  });
125
159
  } else if (family === "radar") {
@@ -160,13 +194,13 @@ function chartToMermaid(spec, options = {}) {
160
194
  lines.push(`${ind}${kind} ${quote(s.name)} [${s.values.map(v).join(", ")}]`);
161
195
  }
162
196
  }
163
- let text = `${lines.join("\n")}
197
+ let text = `${[...front, ...lines].join("\n")}
164
198
  `;
165
199
  if (options.meta !== false) {
166
200
  const meta = metaFor(spec, text);
167
201
  if (meta) {
168
202
  const [header, ...rest] = lines;
169
- text = `${[header, `${CHART_MERMAID_META_PREFIX}${JSON.stringify({ v: 1, ...meta })}`, ...rest].join("\n")}
203
+ text = `${[...front, header, `${CHART_MERMAID_META_PREFIX}${JSON.stringify({ v: 1, ...meta })}`, ...rest].join("\n")}
170
204
  `;
171
205
  }
172
206
  }
@@ -202,7 +236,7 @@ function chartFromMermaid(text, options = {}) {
202
236
  const all = text.replace(/^/, "").split(/\r?\n/);
203
237
  let start = 0;
204
238
  let frontTitle;
205
- if (_optionalChain([all, 'access', _4 => _4[0], 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]) === "---") {
239
+ if (_optionalChain([all, 'access', _7 => _7[0], 'optionalAccess', _8 => _8.trim, 'call', _9 => _9()]) === "---") {
206
240
  const end = all.findIndex((l, i) => i > 0 && l.trim() === "---");
207
241
  if (end > 0) {
208
242
  for (const l of all.slice(1, end)) {
@@ -223,21 +257,23 @@ function chartFromMermaid(text, options = {}) {
223
257
  let m;
224
258
  if (m = /^xychart(?:-beta)?(?:\s+(horizontal|vertical))?\s*$/i.exec(t)) {
225
259
  family = "xychart";
226
- horizontal = _optionalChain([m, 'access', _7 => _7[1], 'optionalAccess', _8 => _8.toLowerCase, 'call', _9 => _9()]) === "horizontal";
260
+ horizontal = _optionalChain([m, 'access', _10 => _10[1], 'optionalAccess', _11 => _11.toLowerCase, 'call', _12 => _12()]) === "horizontal";
227
261
  } else if (m = /^pie(\s+showData)?(?:\s+title\s+(.*))?\s*$/i.exec(t)) {
228
262
  family = "pie";
229
263
  showData = !!m[1];
230
264
  if (m[2]) frontTitle = m[2].trim();
231
265
  } else if (/^radar(?:-beta)?\s*$/i.test(t)) family = "radar";
232
- else issues.push({ path: "header", line: i + 1, message: `Line ${i + 1}: expected a chart header (xychart-beta, pie or radar-beta), got "${t.slice(0, 40)}".` });
266
+ else if (/^sankey(?:-beta)?\s*$/i.test(t)) family = "sankey";
267
+ else if (/^treemap(?:-beta)?\s*$/i.test(t)) family = "treemap";
268
+ else issues.push({ path: "header", line: i + 1, message: `Line ${i + 1}: expected a chart header (xychart-beta, pie, radar-beta, sankey-beta or treemap-beta), got "${t.slice(0, 40)}".` });
233
269
  headerLine = i;
234
270
  break;
235
271
  }
236
272
  let title = _nullishCoalesce(frontTitle, () => ( ""));
237
273
  const categories = [];
238
274
  const series = [];
239
- const xAxis = _chunkR3P2M7HUcjs.defaultChartAxis.call(void 0, false);
240
- const yAxis = _chunkR3P2M7HUcjs.defaultChartAxis.call(void 0, true);
275
+ const xAxis = _chunkBPX4Z3N3cjs.defaultChartAxis.call(void 0, false);
276
+ const yAxis = _chunkBPX4Z3N3cjs.defaultChartAxis.call(void 0, true);
241
277
  const radarAxes = [];
242
278
  const pieSlices = [];
243
279
  let legendShow = true;
@@ -245,6 +281,7 @@ function chartFromMermaid(text, options = {}) {
245
281
  let graticule = "circle";
246
282
  const counts = { bar: 0, line: 0 };
247
283
  const lineIssue = (i, message) => issues.push({ path: `line ${i + 1}`, line: i + 1, message: `Line ${i + 1}: ${message}` });
284
+ const whole = family === "sankey" || family === "treemap";
248
285
  for (let i = headerLine + 1; family && i < all.length; i++) {
249
286
  const raw = all[i];
250
287
  const t = raw.trim();
@@ -260,7 +297,7 @@ function chartFromMermaid(text, options = {}) {
260
297
  }
261
298
  continue;
262
299
  }
263
- if (t.startsWith("%%")) continue;
300
+ if (t.startsWith("%%") || whole) continue;
264
301
  const isLast = i === all.length - 1 || all.slice(i + 1).every((l) => !l.trim());
265
302
  let m;
266
303
  if (m = /^title\s+(.+)$/i.exec(t)) {
@@ -362,7 +399,8 @@ function chartFromMermaid(text, options = {}) {
362
399
  if (!(partial && isLast)) lineIssue(i, `could not read "${t.slice(0, 60)}"; it was skipped.`);
363
400
  }
364
401
  let input;
365
- if (family === "pie") {
402
+ if (whole) input = readWholeMermaid(family, all, headerLine, partial, title, issues);
403
+ else if (family === "pie") {
366
404
  input = {
367
405
  type: "pie",
368
406
  title,
@@ -397,19 +435,55 @@ function chartFromMermaid(text, options = {}) {
397
435
  input.series = metaSeries.map((extra, i) => ({ ..._nullishCoalesce(base[i], () => ( {})), ..._nullishCoalesce(extra, () => ( {})) }));
398
436
  }
399
437
  }
400
- const { spec, issues: specIssues } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, input);
401
- if (!family && !partial && !issues.length) issues.push({ path: "header", message: "The text is empty: expected a chart header (xychart-beta, pie or radar-beta)." });
438
+ const { spec, issues: specIssues } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, input);
439
+ if (!family && !partial && !issues.length) issues.push({ path: "header", message: "The text is empty: expected a chart header (xychart-beta, pie, radar-beta, sankey-beta or treemap-beta)." });
402
440
  return { spec, issues: [...issues, ...specIssues], family, hasMeta: !!meta };
403
441
  }
442
+ function readWholeMermaid(family, all, headerLine, partial, title, issues) {
443
+ const body = all.slice(headerLine).map((l) => l.trim().startsWith(CHART_MERMAID_META_PREFIX.trim()) ? "%%" : l);
444
+ const parse = (lines) => family === "sankey" ? _chunkTTCJPX4Ucjs.parseSankey.call(void 0, lines.join("\n")) : _chunkTTCJPX4Ucjs.parseTreemap.call(void 0, lines.join("\n"));
445
+ let read = parse(body);
446
+ if ("error" in read && partial && body.length > 1) read = parse(body.slice(0, -1));
447
+ const lineOf = (n) => n === void 0 ? void 0 : n + headerLine;
448
+ if ("error" in read) {
449
+ const line = lineOf(read.line);
450
+ if (!(partial && body.length <= 2)) issues.push({ path: family, ...line ? { line } : {}, message: line ? `Line ${line}: ${read.error}` : read.error });
451
+ return { type: family === "sankey" ? "sankey" : "treemap", title };
452
+ }
453
+ if (family === "sankey") {
454
+ const data2 = read.data;
455
+ return { type: "sankey", title, links: data2.links, sankeyNodes: data2.nodes, series: [{ id: "series-1", name: "Values", values: [] }] };
456
+ }
457
+ const data = read.data;
458
+ let items = data.items;
459
+ let name = _nullishCoalesce(data.title, () => ( title));
460
+ if (items.length === 1 && _optionalChain([items, 'access', _13 => _13[0], 'access', _14 => _14.children, 'optionalAccess', _15 => _15.length])) {
461
+ name ||= items[0].name;
462
+ items = items[0].children;
463
+ }
464
+ const toNode = (item) => _optionalChain([item, 'access', _16 => _16.children, 'optionalAccess', _17 => _17.length]) ? { name: item.name, children: item.children.map(toNode) } : { name: item.name, value: _nullishCoalesce(item.value, () => ( 0)) };
465
+ const tree = items.map(toNode);
466
+ const leaf = (n) => !n.children;
467
+ const flatOrOneLevel = tree.every(leaf) || tree.every((n) => _optionalChain([n, 'access', _18 => _18.children, 'optionalAccess', _19 => _19.every, 'call', _20 => _20(leaf)]));
468
+ return { type: flatOrOneLevel ? "treemap" : "sunburst", title: name, tree, categories: tree.map((n) => n.name), series: [{ id: "series-1", name: "Values", values: tree.map(_chunkBPX4Z3N3cjs.chartTreeTotal) }] };
469
+ }
404
470
  function findMermaidCharts(text) {
405
471
  const lines = text.replace(/^/, "").split(/\r?\n/);
406
472
  const found = [];
407
473
  const familyOf = (body) => {
408
- const header = body.split(/\r?\n/).find((l) => l.trim() && !l.trim().startsWith("%%") && l.trim() !== "---");
474
+ let rows = body.split(/\r?\n/);
475
+ const firstRow = rows.findIndex((l) => l.trim());
476
+ if (_optionalChain([rows, 'access', _21 => _21[firstRow], 'optionalAccess', _22 => _22.trim, 'call', _23 => _23()]) === "---") {
477
+ const end = rows.findIndex((l, i) => i > firstRow && l.trim() === "---");
478
+ if (end > 0) rows = rows.slice(end + 1);
479
+ }
480
+ const header = rows.find((l) => l.trim() && !l.trim().startsWith("%%"));
409
481
  if (!header) return null;
410
482
  if (/^\s*xychart(-beta)?\b/.test(header)) return "xychart";
411
483
  if (/^\s*pie\b/.test(header)) return "pie";
412
484
  if (/^\s*radar(-beta)?\b/.test(header)) return "radar";
485
+ if (/^\s*sankey(-beta)?\s*$/.test(header)) return "sankey";
486
+ if (/^\s*treemap(-beta)?\s*$/.test(header)) return "treemap";
413
487
  return null;
414
488
  };
415
489
  for (let i = 0; i < lines.length; i++) {
@@ -436,11 +510,11 @@ function findMermaidCharts(text) {
436
510
  }
437
511
 
438
512
  // src/chart/summary.ts
439
- function summarizeChart(spec, size = { width: 640, height: 400 }, scene = _chunkSBQVEBSUcjs.layoutChart.call(void 0, spec, { ...size, selection: null })) {
513
+ function summarizeChart(spec, size = { width: 640, height: 400 }, scene = _chunkUUUTEVX6cjs.layoutChart.call(void 0, spec, { ...size, selection: null })) {
440
514
  const visible = spec.series.filter((s) => !s.hidden);
441
- const series = visible.map((s) => _chunkSBQVEBSUcjs.summarizeChartSeries.call(void 0, s, spec.categories));
442
- const colors = _chunkR3P2M7HUcjs.chartSpecColorsByCategory.call(void 0, spec) ? spec.categories.map((_, i) => _optionalChain([visible, 'access', _10 => _10[0], 'optionalAccess', _11 => _11.pointColors, 'optionalAccess', _12 => _12[i]]) || _chunkR3P2M7HUcjs.chartSliceColor.call(void 0, spec, i)) : visible.map((s) => _chunkR3P2M7HUcjs.chartSeriesColor.call(void 0, spec, spec.series.indexOf(s)));
443
- const check = _chunkR3P2M7HUcjs.checkChartColors.call(void 0, colors, { surface: scene.theme.surface.startsWith("var(") ? "#ffffff" : scene.theme.surface });
515
+ const series = visible.map((s) => _chunkUUUTEVX6cjs.summarizeChartSeries.call(void 0, s, spec.categories));
516
+ const colors = _chunkBPX4Z3N3cjs.chartSpecColorsByCategory.call(void 0, spec) ? spec.categories.map((_, i) => _optionalChain([visible, 'access', _24 => _24[0], 'optionalAccess', _25 => _25.pointColors, 'optionalAccess', _26 => _26[i]]) || _chunkBPX4Z3N3cjs.chartSliceColor.call(void 0, spec, i)) : visible.map((s) => _chunkBPX4Z3N3cjs.chartSeriesColor.call(void 0, spec, spec.series.indexOf(s)));
517
+ const check = _chunkBPX4Z3N3cjs.checkChartColors.call(void 0, colors, { surface: scene.theme.surface.startsWith("var(") ? "#ffffff" : scene.theme.surface });
444
518
  const axis = (s, a) => s ? { min: s.domain[0], max: s.domain[1], ticks: [...s.ticks], ...s.logBase ? { logBase: s.logBase } : {}, ...a.reverse ? { reversed: true } : {} } : void 0;
445
519
  return {
446
520
  type: spec.type,
@@ -455,12 +529,12 @@ function summarizeChart(spec, size = { width: 640, height: 400 }, scene = _chunk
455
529
  description: describeChart(spec, series)
456
530
  };
457
531
  }
458
- function describeChart(spec, series = spec.series.filter((s) => !s.hidden).map((s) => _chunkSBQVEBSUcjs.summarizeChartSeries.call(void 0, s, spec.categories))) {
459
- const kind = _chunkR3P2M7HUcjs.CHART_TYPE_LABELS[spec.type].toLowerCase();
532
+ function describeChart(spec, series = spec.series.filter((s) => !s.hidden).map((s) => _chunkUUUTEVX6cjs.summarizeChartSeries.call(void 0, s, spec.categories))) {
533
+ const kind = _chunkBPX4Z3N3cjs.CHART_TYPE_LABELS[spec.type].toLowerCase();
460
534
  const parts = [];
461
- const family = _chunkR3P2M7HUcjs.chartFamily.call(void 0, spec);
535
+ const family = _chunkBPX4Z3N3cjs.chartFamily.call(void 0, spec);
462
536
  const format = spec.yAxis.format;
463
- const fmt = (v) => v === null ? "blank" : _chunkSBQVEBSUcjs.formatChartNumber.call(void 0, v, format);
537
+ const fmt = (v) => v === null ? "blank" : _chunkUUUTEVX6cjs.formatChartNumber.call(void 0, v, format);
464
538
  const head = spec.title ? `${spec.title}: a ${kind} chart` : `A ${kind} chart`;
465
539
  if (!series.length || series.every((s) => s.count === 0)) return `${head} with no data.`;
466
540
  if (family === "pie" || family === "funnel" || family === "treemap" || family === "sunburst") {
@@ -470,19 +544,34 @@ function describeChart(spec, series = spec.series.filter((s) => !s.hidden).map((
470
544
  parts.push(`${head} of ${s.name} across ${spec.categories.length} categories, totalling ${fmt(s.sum)}.`);
471
545
  parts.push(`The largest is ${s.maxAt} at ${fmt(s.max)}${share(s.max)}; the smallest is ${s.minAt} at ${fmt(s.min)}${share(s.min)}.`);
472
546
  if (spec.tree.length) {
473
- const depth = _chunkR3P2M7HUcjs.chartTreeDepth.call(void 0, spec.tree);
474
- const items = _chunkR3P2M7HUcjs.chartTreeToRows.call(void 0, spec.tree).length;
547
+ const depth = _chunkBPX4Z3N3cjs.chartTreeDepth.call(void 0, spec.tree);
548
+ const items = _chunkBPX4Z3N3cjs.chartTreeToRows.call(void 0, spec.tree).length;
475
549
  parts.push(`The data is grouped ${depth} level${depth === 1 ? "" : "s"} deep with ${items} item${items === 1 ? "" : "s"}.`);
476
550
  }
477
551
  return parts.join(" ");
478
552
  }
553
+ if (family === "barrace") {
554
+ const n = spec.categories.length;
555
+ const first = _chunkUUUTEVX6cjs.chartRaceRanking.call(void 0, spec, 0)[0];
556
+ const last = _chunkUUUTEVX6cjs.chartRaceRanking.call(void 0, spec, n - 1)[0];
557
+ const lead = (e, at) => e ? `${e.name} leads at ${at} with ${fmt(e.value)}` : "";
558
+ return `${head} of ${series.length} names over ${n} time points (${spec.categories[0]} to ${spec.categories[n - 1]}), showing the top ${Math.min(spec.raceTopN, series.length)}. ${lead(first, spec.categories[0])}; ${lead(last, spec.categories[n - 1])}.`;
559
+ }
560
+ if (family === "sankey") {
561
+ if (!spec.links.length) return `${head} with no flows yet.`;
562
+ const total = spec.links.reduce((t, l) => t + l.value, 0);
563
+ const largest = [...spec.links].sort((a, b) => b.value - a.value)[0];
564
+ const sources = spec.sankeyNodes.filter((n) => !spec.links.some((l) => l.target === n));
565
+ const sinks = spec.sankeyNodes.filter((n) => !spec.links.some((l) => l.source === n));
566
+ return `${head} of ${spec.links.length} flows between ${spec.sankeyNodes.length} nodes, from ${sources.join(", ")} to ${sinks.join(", ")}. The largest flow is ${largest.source} \u2192 ${largest.target} (${fmt(largest.value)}); all flows add up to ${fmt(total)}.`;
567
+ }
479
568
  if (family === "parliament") {
480
569
  const s = spec.series.find((x) => !x.hidden);
481
570
  const seats = spec.categories.map((c, i) => ({ c, n: Math.max(0, Math.round(_nullishCoalesce(s.values[i], () => ( 0)))) }));
482
571
  const total = seats.reduce((t, x) => t + x.n, 0);
483
572
  const majority = _nullishCoalesce(spec.parliamentMajority, () => ( Math.floor(total / 2) + 1));
484
573
  const largest = [...seats].sort((a, b) => b.n - a.n)[0];
485
- const verdict = largest && largest.n >= majority ? `${largest.c} holds a majority with ${largest.n}` : `no group reaches the majority of ${majority}; the largest is ${_optionalChain([largest, 'optionalAccess', _13 => _13.c])} with ${_optionalChain([largest, 'optionalAccess', _14 => _14.n])}`;
574
+ const verdict = largest && largest.n >= majority ? `${largest.c} holds a majority with ${largest.n}` : `no group reaches the majority of ${majority}; the largest is ${_optionalChain([largest, 'optionalAccess', _27 => _27.c])} with ${_optionalChain([largest, 'optionalAccess', _28 => _28.n])}`;
486
575
  return `${head} of ${total} seats across ${seats.length} groups: ${verdict}.`;
487
576
  }
488
577
  if (family === "slope") {
@@ -505,7 +594,7 @@ function describeChart(spec, series = spec.series.filter((s) => !s.hidden).map((
505
594
  });
506
595
  const best = [...agree].sort((x, y) => y.share - x.share);
507
596
  const pct = (v) => `${Math.round(v * 100)}%`;
508
- return `${head} of ${spec.categories.length} questions with ${answers.length} answers from ${_optionalChain([answers, 'access', _15 => _15[0], 'optionalAccess', _16 => _16.name])} to ${_optionalChain([answers, 'access', _17 => _17[answers.length - 1], 'optionalAccess', _18 => _18.name])}. Most positive: ${_optionalChain([best, 'access', _19 => _19[0], 'optionalAccess', _20 => _20.c])} (${pct(_nullishCoalesce(_optionalChain([best, 'access', _21 => _21[0], 'optionalAccess', _22 => _22.share]), () => ( 0)))}); least: ${_optionalChain([best, 'access', _23 => _23[best.length - 1], 'optionalAccess', _24 => _24.c])} (${pct(_nullishCoalesce(_optionalChain([best, 'access', _25 => _25[best.length - 1], 'optionalAccess', _26 => _26.share]), () => ( 0)))}).`;
597
+ return `${head} of ${spec.categories.length} questions with ${answers.length} answers from ${_optionalChain([answers, 'access', _29 => _29[0], 'optionalAccess', _30 => _30.name])} to ${_optionalChain([answers, 'access', _31 => _31[answers.length - 1], 'optionalAccess', _32 => _32.name])}. Most positive: ${_optionalChain([best, 'access', _33 => _33[0], 'optionalAccess', _34 => _34.c])} (${pct(_nullishCoalesce(_optionalChain([best, 'access', _35 => _35[0], 'optionalAccess', _36 => _36.share]), () => ( 0)))}); least: ${_optionalChain([best, 'access', _37 => _37[best.length - 1], 'optionalAccess', _38 => _38.c])} (${pct(_nullishCoalesce(_optionalChain([best, 'access', _39 => _39[best.length - 1], 'optionalAccess', _40 => _40.share]), () => ( 0)))}).`;
509
598
  }
510
599
  if (family === "waterfall") {
511
600
  const s = spec.series.find((x) => !x.hidden);
@@ -544,7 +633,7 @@ function describeChart(spec, series = spec.series.filter((s) => !s.hidden).map((
544
633
  }
545
634
  parts.push(...axes);
546
635
  for (const s of spec.series.filter((x) => !x.hidden && (x.errorBars || x.xErrorBars)).slice(0, 8)) {
547
- const bars = [s.errorBars ? _chunkR3P2M7HUcjs.describeChartErrorBars.call(void 0, s.errorBars) : "", s.xErrorBars ? `x ${_chunkR3P2M7HUcjs.describeChartErrorBars.call(void 0, s.xErrorBars)}` : ""].filter(Boolean).join(" and ");
636
+ const bars = [s.errorBars ? _chunkBPX4Z3N3cjs.describeChartErrorBars.call(void 0, s.errorBars) : "", s.xErrorBars ? `x ${_chunkBPX4Z3N3cjs.describeChartErrorBars.call(void 0, s.xErrorBars)}` : ""].filter(Boolean).join(" and ");
548
637
  parts.push(`${s.name} has ${bars}.`);
549
638
  }
550
639
  return parts.join(" ");
@@ -609,7 +698,7 @@ function chartSceneToSvg(scene, options = {}) {
609
698
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${scene.width}" height="${scene.height}" viewBox="0 0 ${scene.width} ${scene.height}" role="img"${label ? ` aria-label="${escape(label)}"` : ""}${labelled ? ` aria-labelledby="${labelled}"` : ""}>${head}${body}</svg>`;
610
699
  }
611
700
  function chartToSvg(spec, options) {
612
- const scene = _chunkSBQVEBSUcjs.layoutChart.call(void 0, spec, { ...options, selection: null });
701
+ const scene = _chunkUUUTEVX6cjs.layoutChart.call(void 0, spec, { ...options, selection: null });
613
702
  return chartSceneToSvg(scene, { ...options, ariaLabel: _nullishCoalesce(options.ariaLabel, () => ( (spec.title || void 0))), description: _nullishCoalesce(options.description, () => ( describeChart(spec))) });
614
703
  }
615
704
  var SVG_NS = "http://www.w3.org/2000/svg";
@@ -647,10 +736,10 @@ var CHART_LAYOUT_PRESETS = [
647
736
  ];
648
737
  function monochromeChartPalette(base, count = 6) {
649
738
  const steps = count <= 1 ? [0] : Array.from({ length: count }, (_, i) => -0.45 + 1.05 * i / (count - 1));
650
- return steps.map((t) => _chunkR3P2M7HUcjs.shadeChartColor.call(void 0, base, t));
739
+ return steps.map((t) => _chunkBPX4Z3N3cjs.shadeChartColor.call(void 0, base, t));
651
740
  }
652
741
  var CHART_COLOR_SCHEMES = [
653
- { id: "default", label: "Default", description: "Ten distinct colours, readable on light and dark backgrounds.", palette: _chunkR3P2M7HUcjs.DEFAULT_CHART_PALETTE },
742
+ { id: "default", label: "Default", description: "Ten distinct colours, readable on light and dark backgrounds.", palette: _chunkBPX4Z3N3cjs.DEFAULT_CHART_PALETTE },
654
743
  { id: "okabe-ito", label: "Okabe\u2013Ito", description: "Eight colours chosen to stay distinct with colour-blindness.", palette: ["#e69f00", "#56b4e9", "#009e73", "#f0e442", "#0072b2", "#d55e00", "#cc79a7", "#000000"] },
655
744
  { id: "office", label: "Office", description: "The six accent colours of the default Office theme.", palette: ["#4472c4", "#ed7d31", "#a5a5a5", "#ffc000", "#5b9bd5", "#70ad47"] },
656
745
  { id: "warm", label: "Warm", description: "Reds, oranges and yellows.", palette: ["#b2182b", "#d6604d", "#f4a582", "#e08214", "#fdb863", "#8c510a"] },
@@ -678,7 +767,7 @@ function chartPresetPatch(options) {
678
767
 
679
768
  // src/chart/controller.ts
680
769
  function pointLists(s) {
681
- const lists = [s.values, s.x, s.sizes, s.pointColors, s.callouts, _optionalChain([s, 'access', _27 => _27.errorBars, 'optionalAccess', _28 => _28.plus]), _optionalChain([s, 'access', _29 => _29.errorBars, 'optionalAccess', _30 => _30.minus]), _optionalChain([s, 'access', _31 => _31.xErrorBars, 'optionalAccess', _32 => _32.plus]), _optionalChain([s, 'access', _33 => _33.xErrorBars, 'optionalAccess', _34 => _34.minus])];
770
+ const lists = [s.values, s.x, s.sizes, s.pointColors, s.callouts, _optionalChain([s, 'access', _41 => _41.errorBars, 'optionalAccess', _42 => _42.plus]), _optionalChain([s, 'access', _43 => _43.errorBars, 'optionalAccess', _44 => _44.minus]), _optionalChain([s, 'access', _45 => _45.xErrorBars, 'optionalAccess', _46 => _46.plus]), _optionalChain([s, 'access', _47 => _47.xErrorBars, 'optionalAccess', _48 => _48.minus])];
682
771
  return lists.filter((l) => Array.isArray(l));
683
772
  }
684
773
  function sameChartJson(a, b) {
@@ -695,6 +784,17 @@ function sameChartJson(a, b) {
695
784
  if (keys.length !== Object.keys(bo).filter((k) => bo[k] !== void 0).length) return false;
696
785
  return keys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && sameChartJson(ao[k], bo[k]));
697
786
  }
787
+ var defaultRaceTicker = {
788
+ now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
789
+ next: (step) => {
790
+ if (typeof requestAnimationFrame === "function") {
791
+ const id2 = requestAnimationFrame(() => step());
792
+ return () => cancelAnimationFrame(id2);
793
+ }
794
+ const id = setTimeout(step, 16);
795
+ return () => clearTimeout(id);
796
+ }
797
+ };
698
798
  var fail = (message, issues) => ({ ok: false, message, ...issues ? { issues } : {} });
699
799
  var ChartController = (_class = class {
700
800
 
@@ -707,20 +807,24 @@ var ChartController = (_class = class {
707
807
  __init6() {this.revision = 0}
708
808
  __init7() {this.applying = false}
709
809
 
710
- __init8() {this.listeners = /* @__PURE__ */ new Set()}
711
810
 
811
+ __init8() {this.raceFrame = null}
812
+ __init9() {this.raceStop = null}
813
+ __init10() {this.listeners = /* @__PURE__ */ new Set()}
712
814
 
713
- constructor(options = {}) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);
815
+
816
+ constructor(options = {}) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);
714
817
  this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkFJ2MSI43cjs.HistoryManager)()));
715
818
  this.onChange = options.onChange;
716
- const { spec, issues } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, _nullishCoalesce(options.spec, () => ( {})));
819
+ this.raceTicker = _nullishCoalesce(options.raceTicker, () => ( defaultRaceTicker));
820
+ const { spec, issues } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, _nullishCoalesce(options.spec, () => ( {})));
717
821
  this.spec = spec;
718
822
  this.issues = issues;
719
823
  this.state = this.computeState();
720
824
  this.connect();
721
825
  }
722
826
  connect() {
723
- _optionalChain([this, 'access', _35 => _35.unsubscribeHistory, 'optionalCall', _36 => _36()]);
827
+ _optionalChain([this, 'access', _49 => _49.unsubscribeHistory, 'optionalCall', _50 => _50()]);
724
828
  this.unsubscribeHistory = this.history.subscribe((h) => {
725
829
  const s = this.state;
726
830
  if (this.applying || s.canUndo === h.canUndo && s.canRedo === h.canRedo && s.undoLabel === h.undoName && s.redoLabel === h.redoName) return;
@@ -731,9 +835,9 @@ var ChartController = (_class = class {
731
835
  // State
732
836
  // -------------------------------------------------------------------------
733
837
  /** The current snapshot (stable until the next change). */
734
- __init9() {this.getState = () => this.state}
838
+ __init11() {this.getState = () => this.state}
735
839
  /** Calls `listener` after every change. Returns an unsubscribe function. */
736
- __init10() {this.subscribe = (listener) => {
840
+ __init12() {this.subscribe = (listener) => {
737
841
  if (!this.unsubscribeHistory) this.connect();
738
842
  this.listeners.add(listener);
739
843
  return () => {
@@ -750,25 +854,27 @@ var ChartController = (_class = class {
750
854
  computeState() {
751
855
  const h = this.history.getState();
752
856
  const sel = this.selection;
753
- const seriesId = _optionalChain([sel, 'optionalAccess', _37 => _37.kind]) === "series" || _optionalChain([sel, 'optionalAccess', _38 => _38.kind]) === "point" || _optionalChain([sel, 'optionalAccess', _39 => _39.kind]) === "trendline" ? sel.seriesId : null;
857
+ const seriesId = _optionalChain([sel, 'optionalAccess', _51 => _51.kind]) === "series" || _optionalChain([sel, 'optionalAccess', _52 => _52.kind]) === "point" || _optionalChain([sel, 'optionalAccess', _53 => _53.kind]) === "trendline" ? sel.seriesId : null;
754
858
  return {
755
859
  spec: this.spec,
756
860
  selection: sel,
757
861
  selectedSeries: seriesId ? _nullishCoalesce(this.spec.series.find((s) => s.id === seriesId), () => ( null)) : null,
758
- selectedIndex: _optionalChain([sel, 'optionalAccess', _40 => _40.kind]) === "point" || _optionalChain([sel, 'optionalAccess', _41 => _41.kind]) === "category" ? sel.index : null,
862
+ selectedIndex: _optionalChain([sel, 'optionalAccess', _54 => _54.kind]) === "point" || _optionalChain([sel, 'optionalAccess', _55 => _55.kind]) === "category" ? sel.index : null,
759
863
  issues: this.issues,
760
864
  gesture: this.gestureKey,
761
865
  canUndo: h.canUndo,
762
866
  canRedo: h.canRedo,
763
867
  undoLabel: h.undoName,
764
868
  redoLabel: h.redoName,
869
+ raceFrame: this.raceFrame === null ? null : Math.min(this.raceFrame, Math.max(0, this.spec.categories.length - 1)),
870
+ racePlaying: this.raceStop !== null,
765
871
  revision: this.revision
766
872
  };
767
873
  }
768
874
  notify(specChanged) {
769
875
  this.revision++;
770
876
  this.state = this.computeState();
771
- if (specChanged) _optionalChain([this, 'access', _42 => _42.onChange, 'optionalCall', _43 => _43(this.spec)]);
877
+ if (specChanged) _optionalChain([this, 'access', _56 => _56.onChange, 'optionalCall', _57 => _57(this.spec)]);
772
878
  const state = this.state;
773
879
  this.listeners.forEach((listener) => {
774
880
  try {
@@ -792,7 +898,7 @@ var ChartController = (_class = class {
792
898
  case "category":
793
899
  return sel.index < spec.categories.length ? sel : null;
794
900
  case "trendline":
795
- return _optionalChain([spec, 'access', _44 => _44.series, 'access', _45 => _45.find, 'call', _46 => _46((s) => s.id === sel.seriesId), 'optionalAccess', _47 => _47.trendlines, 'optionalAccess', _48 => _48[sel.index]]) ? sel : null;
901
+ return _optionalChain([spec, 'access', _58 => _58.series, 'access', _59 => _59.find, 'call', _60 => _60((s) => s.id === sel.seriesId), 'optionalAccess', _61 => _61.trendlines, 'optionalAccess', _62 => _62[sel.index]]) ? sel : null;
796
902
  case "title":
797
903
  return spec.title ? sel : null;
798
904
  case "subtitle":
@@ -816,7 +922,7 @@ var ChartController = (_class = class {
816
922
  const hadIssues = this.issues.length > 0;
817
923
  this.issues = [];
818
924
  if (sameChartJson(before, next)) {
819
- const reselect = select !== void 0 && !_chunkSBQVEBSUcjs.sameChartSelection.call(void 0, this.selection, select) && !(select === null && this.selection === null);
925
+ const reselect = select !== void 0 && !_chunkUUUTEVX6cjs.sameChartSelection.call(void 0, this.selection, select) && !(select === null && this.selection === null);
820
926
  if (reselect) this.selection = select;
821
927
  if (reselect || hadIssues) this.notify(false);
822
928
  return;
@@ -839,7 +945,7 @@ var ChartController = (_class = class {
839
945
  }
840
946
  /** A copy of the spec with `edit` applied (series and categories copied as needed). */
841
947
  edited(edit) {
842
- const draft = _chunkR3P2M7HUcjs.cloneChartSpec.call(void 0, this.spec);
948
+ const draft = _chunkBPX4Z3N3cjs.cloneChartSpec.call(void 0, this.spec);
843
949
  edit(draft);
844
950
  return draft;
845
951
  }
@@ -869,7 +975,7 @@ var ChartController = (_class = class {
869
975
  legend: patch.legend ? { ...s.legend, ...patch.legend } : s.legend,
870
976
  version: 1
871
977
  };
872
- const { spec, issues } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, merged);
978
+ const { spec, issues } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, merged);
873
979
  if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
874
980
  if (JSON.stringify(spec) === JSON.stringify(s)) return { ok: true };
875
981
  this.commit(_nullishCoalesce(options.label, () => ( updateLabel(patch))), spec);
@@ -910,7 +1016,7 @@ var ChartController = (_class = class {
910
1016
  * to the categories; all-or-nothing like `update`.
911
1017
  */
912
1018
  setData(data, options = {}) {
913
- const clear = (data.categories || data.series) && this.spec.tree.length ? { tree: [] } : {};
1019
+ const clear = (data.categories || data.series) && (this.spec.tree.length || this.spec.links.length) ? { tree: [], links: [] } : {};
914
1020
  return this.update({ ...data.categories ? { categories: data.categories } : {}, ...data.series ? { series: data.series } : {}, ...clear }, { label: _nullishCoalesce(options.label, () => ( "Edit chart data")) });
915
1021
  }
916
1022
  /**
@@ -923,7 +1029,7 @@ var ChartController = (_class = class {
923
1029
  const series = this.getSeries(seriesId);
924
1030
  if (!series) return this.unknownSeries(seriesId);
925
1031
  if (!Number.isInteger(index) || index < 0 || index >= this.spec.categories.length) return this.badIndex(index);
926
- const parsed = _chunkR3P2M7HUcjs.parseChartValue.call(void 0, value);
1032
+ const parsed = _chunkBPX4Z3N3cjs.parseChartValue.call(void 0, value);
927
1033
  if (parsed === void 0) return this.reject(`"${String(value)}" is not a number; enter a number or leave the cell blank.`, [{ path: `${seriesId}[${index}]`, message: `"${String(value)}" is not a number.` }]);
928
1034
  if (series.values[index] === parsed) {
929
1035
  if (this.issues.length) {
@@ -948,7 +1054,7 @@ var ChartController = (_class = class {
948
1054
  if (!this.getSeries(cell.seriesId)) issues.push({ path: cell.seriesId, message: this.unknownSeries(cell.seriesId).message });
949
1055
  else if (!Number.isInteger(cell.index) || cell.index < 0 || cell.index >= this.spec.categories.length) issues.push({ path: `${cell.seriesId}[${cell.index}]`, message: this.badIndex(cell.index).message });
950
1056
  else {
951
- const value = _chunkR3P2M7HUcjs.parseChartValue.call(void 0, cell.value);
1057
+ const value = _chunkBPX4Z3N3cjs.parseChartValue.call(void 0, cell.value);
952
1058
  if (value === void 0) issues.push({ path: `${cell.seriesId}[${cell.index}]`, message: `"${String(cell.value)}" is not a number.` });
953
1059
  else parsed.push({ seriesId: cell.seriesId, index: cell.index, value });
954
1060
  }
@@ -962,12 +1068,12 @@ var ChartController = (_class = class {
962
1068
  }
963
1069
  /** Adds a series (values padded to the categories) and selects it. Returns its id. */
964
1070
  addSeries(input = {}, options = {}) {
965
- const id = input.id && !this.getSeries(input.id) ? input.id : _chunkR3P2M7HUcjs.uniqueChartSeriesId.call(void 0, this.spec.series);
1071
+ const id = input.id && !this.getSeries(input.id) ? input.id : _chunkBPX4Z3N3cjs.uniqueChartSeriesId.call(void 0, this.spec.series);
966
1072
  const name = _nullishCoalesce(input.name, () => ( `Series ${this.spec.series.length + 1}`));
967
1073
  const at = Math.max(0, Math.min(this.spec.series.length, _nullishCoalesce(options.at, () => ( this.spec.series.length))));
968
1074
  const series = [...this.spec.series];
969
1075
  series.splice(at, 0, { ...input, id, name, values: _nullishCoalesce(input.values, () => ( [])) });
970
- const { spec, issues } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, { ...this.spec, series });
1076
+ const { spec, issues } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, { ...this.spec, series });
971
1077
  if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
972
1078
  this.commit(`Add series "${name}"`, spec, { kind: "series", seriesId: id });
973
1079
  return { ok: true, id };
@@ -987,7 +1093,7 @@ var ChartController = (_class = class {
987
1093
  updateSeries(id, patch) {
988
1094
  const series = this.getSeries(id);
989
1095
  if (!series) return this.unknownSeries(id);
990
- const next = _chunkR3P2M7HUcjs.cloneChartSeries.call(void 0, series);
1096
+ const next = _chunkBPX4Z3N3cjs.cloneChartSeries.call(void 0, series);
991
1097
  if (patch.name !== void 0) next.name = String(patch.name);
992
1098
  if (patch.color === null || patch.color === "") delete next.color;
993
1099
  else if (patch.color !== void 0) next.color = patch.color;
@@ -1007,7 +1113,7 @@ var ChartController = (_class = class {
1007
1113
  const series2 = this.spec.series.map((s) => s.id === id ? next : s);
1008
1114
  const renamed = next.name !== series.name && this.spec.highlight.includes(series.name) && !this.spec.categories.includes(series.name);
1009
1115
  const highlight = renamed ? this.spec.highlight.map((h) => h === series.name ? next.name : h) : this.spec.highlight;
1010
- const { spec, issues } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, { ...this.spec, series: series2, highlight });
1116
+ const { spec, issues } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, { ...this.spec, series: series2, highlight });
1011
1117
  if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1012
1118
  if (JSON.stringify(spec.series) === JSON.stringify(this.spec.series)) return { ok: true };
1013
1119
  const label = patch.hidden !== void 0 && Object.keys(patch).length === 1 ? `${patch.hidden ? "Hide" : "Show"} "${series.name}"` : patch.name !== void 0 && Object.keys(patch).length === 1 ? "Rename series" : `Change series "${series.name}"`;
@@ -1113,7 +1219,7 @@ var ChartController = (_class = class {
1113
1219
  const width = Math.max(...rows.map((r) => r.length));
1114
1220
  const lastSeries = column + width - 1;
1115
1221
  while (d.series.length <= lastSeries) {
1116
- const id = _chunkR3P2M7HUcjs.uniqueChartSeriesId.call(void 0, d.series);
1222
+ const id = _chunkBPX4Z3N3cjs.uniqueChartSeriesId.call(void 0, d.series);
1117
1223
  d.series.push({ id, name: `Series ${d.series.length + 1}`, values: d.categories.map(() => null) });
1118
1224
  }
1119
1225
  const lastRow = row + rows.length - 1;
@@ -1127,7 +1233,7 @@ var ChartController = (_class = class {
1127
1233
  const index = row + r;
1128
1234
  if (col < 0) d.categories[index] = cell === null || cell === void 0 ? "" : String(cell).trim();
1129
1235
  else {
1130
- const parsed = _chunkR3P2M7HUcjs.parseChartValue.call(void 0, cell);
1236
+ const parsed = _chunkBPX4Z3N3cjs.parseChartValue.call(void 0, cell);
1131
1237
  if (parsed === void 0) issues.push({ path: `${d.series[col].id}[${index}]`, message: `"${String(cell)}" is not a number; it was left blank.` });
1132
1238
  d.series[col].values[index] = _nullishCoalesce(parsed, () => ( null));
1133
1239
  }
@@ -1146,14 +1252,14 @@ var ChartController = (_class = class {
1146
1252
  * colours and types are kept by name.
1147
1253
  */
1148
1254
  importTable(rows, options = {}) {
1149
- const data = _chunkR3P2M7HUcjs.chartDataFromTable.call(void 0, rows, options);
1255
+ const data = _chunkBPX4Z3N3cjs.chartDataFromTable.call(void 0, rows, options);
1150
1256
  if (!data.categories.length || !data.series.length) return this.reject("There is no data to import: expected a header row and at least one row of values.");
1151
1257
  const byName = new Map(this.spec.series.map((s) => [s.name, s]));
1152
1258
  const series = data.series.map((s) => {
1153
1259
  const old = byName.get(s.name);
1154
- return { ...s, ..._optionalChain([old, 'optionalAccess', _49 => _49.color]) ? { color: old.color } : {}, ..._optionalChain([old, 'optionalAccess', _50 => _50.type]) ? { type: old.type } : {}, ...old ? { id: old.id } : {} };
1260
+ return { ...s, ..._optionalChain([old, 'optionalAccess', _63 => _63.color]) ? { color: old.color } : {}, ..._optionalChain([old, 'optionalAccess', _64 => _64.type]) ? { type: old.type } : {}, ...old ? { id: old.id } : {} };
1155
1261
  });
1156
- const { spec } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, { ...this.spec, categories: data.categories, series, tree: [] });
1262
+ const { spec } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, { ...this.spec, categories: data.categories, series, tree: [], links: [] });
1157
1263
  this.commit("Import data", spec);
1158
1264
  if (data.issues.length) {
1159
1265
  this.issues = data.issues;
@@ -1170,8 +1276,12 @@ var ChartController = (_class = class {
1170
1276
  // -------------------------------------------------------------------------
1171
1277
  // Grouped data (tree)
1172
1278
  // -------------------------------------------------------------------------
1173
- /** A refusal while the data is a tree (cell edits would be overwritten by its totals), else null. */
1279
+ /** A refusal while the data is a tree or flows (cell edits would be overwritten by their totals), else null. */
1174
1280
  treeLocked() {
1281
+ if (this.spec.links.length)
1282
+ return this.reject(
1283
+ "This chart's data is flows (links): its categories are the nodes and its values how much passes through each. Change it with setLinks / the flow edits (set_chart_links, add_chart_link, update_chart_link, remove_chart_link), or replace it with new data."
1284
+ );
1175
1285
  if (!this.spec.tree.length) return null;
1176
1286
  return this.reject(
1177
1287
  "This chart's data is grouped (a tree): its categories and values are the top-level groups and their totals. Change it with setTree / the tree node edits (set_chart_tree, add_chart_tree_node, update_chart_tree_node, remove_chart_tree_node), or replace it with new data."
@@ -1180,9 +1290,9 @@ var ChartController = (_class = class {
1180
1290
  /** Replaces the grouped data (an empty list goes back to the table, keeping the totals as values). One undo step. */
1181
1291
  setTree(tree, options = {}) {
1182
1292
  const issues = [];
1183
- const next = _chunkR3P2M7HUcjs.normalizeChartTree.call(void 0, tree, issues);
1293
+ const next = _chunkBPX4Z3N3cjs.normalizeChartTree.call(void 0, tree, issues);
1184
1294
  if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1185
- return this.update({ tree: next }, { label: _nullishCoalesce(options.label, () => ( (next.length ? "Replace grouped data" : "Ungroup data"))) });
1295
+ return this.update({ tree: next, links: [] }, { label: _nullishCoalesce(options.label, () => ( (next.length ? "Replace grouped data" : "Ungroup data"))) });
1186
1296
  }
1187
1297
  /**
1188
1298
  * Replaces the grouped data from rows of names and a value (group, …, item, value), as the
@@ -1190,12 +1300,12 @@ var ChartController = (_class = class {
1190
1300
  */
1191
1301
  setTreeRows(rows, options = {}) {
1192
1302
  const issues = [];
1193
- const next = _chunkR3P2M7HUcjs.chartTreeFromRows.call(void 0, rows, issues);
1303
+ const next = _chunkBPX4Z3N3cjs.chartTreeFromRows.call(void 0, rows, issues);
1194
1304
  if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1195
1305
  const keepColors = (list, prefix) => {
1196
1306
  for (const n of list) {
1197
- const old = _chunkR3P2M7HUcjs.findChartTreeNode.call(void 0, this.spec.tree, [...prefix, n.name]);
1198
- if (_optionalChain([old, 'optionalAccess', _51 => _51.color])) n.color = old.color;
1307
+ const old = _chunkBPX4Z3N3cjs.findChartTreeNode.call(void 0, this.spec.tree, [...prefix, n.name]);
1308
+ if (_optionalChain([old, 'optionalAccess', _65 => _65.color])) n.color = old.color;
1199
1309
  if (n.children) keepColors(n.children, [...prefix, n.name]);
1200
1310
  }
1201
1311
  };
@@ -1205,16 +1315,16 @@ var ChartController = (_class = class {
1205
1315
  /** Replaces the data with a table of names and values (group, …, item, value); see `chartTreeFromTable`. */
1206
1316
  importTreeTable(rows, options = {}) {
1207
1317
  const issues = [];
1208
- const next = _chunkR3P2M7HUcjs.chartTreeFromTable.call(void 0, rows, options, issues);
1318
+ const next = _chunkBPX4Z3N3cjs.chartTreeFromTable.call(void 0, rows, options, issues);
1209
1319
  if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1210
1320
  if (!next.length) return this.reject('There is no grouped data to import: expected rows of names followed by a value, e.g. "Europe,France,40".');
1211
- return this.update({ tree: next }, { label: "Import grouped data" });
1321
+ return this.update({ tree: next, links: [] }, { label: "Import grouped data" });
1212
1322
  }
1213
1323
  nodeAt(path) {
1214
- const node = _chunkR3P2M7HUcjs.findChartTreeNode.call(void 0, this.spec.tree, path);
1324
+ const node = _chunkBPX4Z3N3cjs.findChartTreeNode.call(void 0, this.spec.tree, path);
1215
1325
  if (node) return node;
1216
- const known = path.length > 1 ? _chunkR3P2M7HUcjs.findChartTreeNode.call(void 0, this.spec.tree, path.slice(0, -1)) : null;
1217
- const siblings = _nullishCoalesce((path.length > 1 ? _optionalChain([known, 'optionalAccess', _52 => _52.children]) : this.spec.tree), () => ( []));
1326
+ const known = path.length > 1 ? _chunkBPX4Z3N3cjs.findChartTreeNode.call(void 0, this.spec.tree, path.slice(0, -1)) : null;
1327
+ const siblings = _nullishCoalesce((path.length > 1 ? _optionalChain([known, 'optionalAccess', _66 => _66.children]) : this.spec.tree), () => ( []));
1218
1328
  return fail(
1219
1329
  `There is no node ${JSON.stringify(path)}. ${siblings.length ? `${path.length > 1 && known ? `"${known.name}" contains` : "The top level has"} ${siblings.map((n) => `"${n.name}"`).slice(0, 20).join(", ")}.` : "The chart has no grouped data yet; add nodes with add_chart_tree_node or set_chart_tree."}`
1220
1330
  );
@@ -1229,12 +1339,12 @@ var ChartController = (_class = class {
1229
1339
  siblings = group.children;
1230
1340
  }
1231
1341
  const issues = [];
1232
- const [node] = _chunkR3P2M7HUcjs.normalizeChartTree.call(void 0, [input], issues, "node");
1342
+ const [node] = _chunkBPX4Z3N3cjs.normalizeChartTree.call(void 0, [input], issues, "node");
1233
1343
  if (!node || issues.length) return this.reject(issues.map((i) => i.message).join(" ") || "The node is not valid.", issues);
1234
1344
  if (siblings.some((n) => n.name === node.name)) return this.reject(`"${node.name}" is already there; names under one group must be unique.`);
1235
1345
  const at = Math.max(0, Math.min(siblings.length, _nullishCoalesce(options.at, () => ( siblings.length))));
1236
- const tree = _chunkR3P2M7HUcjs.cloneChartTree.call(void 0, this.spec.tree);
1237
- const list = parent.length ? _chunkR3P2M7HUcjs.findChartTreeNode.call(void 0, tree, parent).children : tree;
1346
+ const tree = _chunkBPX4Z3N3cjs.cloneChartTree.call(void 0, this.spec.tree);
1347
+ const list = parent.length ? _chunkBPX4Z3N3cjs.findChartTreeNode.call(void 0, tree, parent).children : tree;
1238
1348
  list.splice(at, 0, node);
1239
1349
  const result = this.update({ tree }, { label: `Add "${node.name}"` });
1240
1350
  if (!result.ok) return result;
@@ -1246,16 +1356,16 @@ var ChartController = (_class = class {
1246
1356
  if ("ok" in node) return node;
1247
1357
  if (patch.value !== void 0 && node.children) return this.reject(`"${node.name}" is a group: its value is the sum of its items. Change the items instead.`);
1248
1358
  const name = patch.name !== void 0 ? String(patch.name).trim() : node.name;
1249
- const siblings = (path.length > 1 ? _chunkR3P2M7HUcjs.findChartTreeNode.call(void 0, this.spec.tree, path.slice(0, -1)).children : this.spec.tree).filter((n) => n !== node);
1359
+ const siblings = (path.length > 1 ? _chunkBPX4Z3N3cjs.findChartTreeNode.call(void 0, this.spec.tree, path.slice(0, -1)).children : this.spec.tree).filter((n) => n !== node);
1250
1360
  if (siblings.some((n) => n.name === name)) return this.reject(`There is already a "${name}" next to "${node.name}"; names under one group must be unique.`);
1251
- const tree = _chunkR3P2M7HUcjs.cloneChartTree.call(void 0, this.spec.tree);
1252
- const target = _chunkR3P2M7HUcjs.findChartTreeNode.call(void 0, tree, path);
1361
+ const tree = _chunkBPX4Z3N3cjs.cloneChartTree.call(void 0, this.spec.tree);
1362
+ const target = _chunkBPX4Z3N3cjs.findChartTreeNode.call(void 0, tree, path);
1253
1363
  const merged = { ...target, name };
1254
1364
  if (patch.value !== void 0) merged.value = patch.value;
1255
1365
  if (patch.color === null) delete merged.color;
1256
1366
  else if (patch.color !== void 0) merged.color = patch.color;
1257
1367
  const issues = [];
1258
- const [checked] = _chunkR3P2M7HUcjs.normalizeChartTree.call(void 0, [merged], issues, "node");
1368
+ const [checked] = _chunkBPX4Z3N3cjs.normalizeChartTree.call(void 0, [merged], issues, "node");
1259
1369
  if (!checked || issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1260
1370
  Object.assign(target, checked);
1261
1371
  if (patch.color === null) delete target.color;
@@ -1267,8 +1377,8 @@ var ChartController = (_class = class {
1267
1377
  removeTreeNode(path) {
1268
1378
  const node = this.nodeAt(path);
1269
1379
  if ("ok" in node) return node;
1270
- const tree = _chunkR3P2M7HUcjs.cloneChartTree.call(void 0, this.spec.tree);
1271
- const parent = path.length > 1 ? _chunkR3P2M7HUcjs.findChartTreeNode.call(void 0, tree, path.slice(0, -1)) : null;
1380
+ const tree = _chunkBPX4Z3N3cjs.cloneChartTree.call(void 0, this.spec.tree);
1381
+ const parent = path.length > 1 ? _chunkBPX4Z3N3cjs.findChartTreeNode.call(void 0, tree, path.slice(0, -1)) : null;
1272
1382
  const list = parent ? parent.children : tree;
1273
1383
  list.splice(
1274
1384
  list.findIndex((n) => n.name === node.name),
@@ -1281,6 +1391,56 @@ var ChartController = (_class = class {
1281
1391
  return this.update({ tree }, { label: `Delete "${node.name}"` });
1282
1392
  }
1283
1393
  // -------------------------------------------------------------------------
1394
+ // Flows (links)
1395
+ // -------------------------------------------------------------------------
1396
+ /**
1397
+ * Replaces the flows (and, when given, the node order). An empty list goes back to the
1398
+ * table, keeping how much passed through each node as values. One undo step, all-or-nothing.
1399
+ */
1400
+ setLinks(links, options = {}) {
1401
+ const issues = [];
1402
+ const next = _chunkBPX4Z3N3cjs.normalizeChartLinks.call(void 0, links, issues);
1403
+ if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1404
+ const used = new Set(next.flatMap((l) => [l.source, l.target]));
1405
+ const sankeyNodes = options.nodes ? [...options.nodes] : this.spec.sankeyNodes.filter((n) => used.has(n));
1406
+ return this.update({ links: next, tree: [], sankeyNodes }, { label: _nullishCoalesce(options.label, () => ( (next.length ? "Replace flows" : "Remove flows"))) });
1407
+ }
1408
+ /** Replaces the data with flows from a table of source, target and value columns. */
1409
+ importLinksTable(rows, options = {}) {
1410
+ const raw = _chunkBPX4Z3N3cjs.chartLinksFromTable.call(void 0, rows, options);
1411
+ if (!raw.length) return this.reject('There are no flows to import: expected rows of source, target and value, e.g. "Website,Signup,120".');
1412
+ return this.setLinks(raw, { label: "Import flows" });
1413
+ }
1414
+ badLink(index) {
1415
+ const n = this.spec.links.length;
1416
+ return fail(`There is no flow ${index}; ${n ? `expected 0 to ${n - 1}` : "the chart has none yet"}.`);
1417
+ }
1418
+ /** Adds a flow and returns its index. */
1419
+ addLink(link) {
1420
+ const result = this.setLinks([...this.spec.links, link], { label: `Add flow ${String(_nullishCoalesce(_optionalChain([link, 'optionalAccess', _67 => _67.source]), () => ( "")))} \u2192 ${String(_nullishCoalesce(_optionalChain([link, 'optionalAccess', _68 => _68.target]), () => ( "")))}` });
1421
+ if (!result.ok) return result;
1422
+ return { ok: true, index: this.spec.links.length - 1 };
1423
+ }
1424
+ /** Changes a flow's source, target, value or colour (`color: null` goes back to the default). */
1425
+ updateLink(index, patch) {
1426
+ const current = this.spec.links[index];
1427
+ if (!current) return this.badLink(index);
1428
+ const merged = { ...current, ...patch };
1429
+ if (patch.color === null) delete merged.color;
1430
+ return this.setLinks(
1431
+ this.spec.links.map((l, i) => i === index ? merged : l),
1432
+ { label: "Change flow" }
1433
+ );
1434
+ }
1435
+ removeLink(index) {
1436
+ const current = this.spec.links[index];
1437
+ if (!current) return this.badLink(index);
1438
+ return this.setLinks(
1439
+ this.spec.links.filter((_, i) => i !== index),
1440
+ { label: `Delete flow ${current.source} \u2192 ${current.target}` }
1441
+ );
1442
+ }
1443
+ // -------------------------------------------------------------------------
1284
1444
  // Annotations
1285
1445
  // -------------------------------------------------------------------------
1286
1446
  /** Sets the note written under the chart ("" removes it). */
@@ -1311,7 +1471,7 @@ var ChartController = (_class = class {
1311
1471
  /** Why a reference on `axis` at `values` cannot be drawn on this chart, or null. */
1312
1472
  referenceProblem(axis, values) {
1313
1473
  const s = this.spec;
1314
- const family = _chunkR3P2M7HUcjs.chartFamily.call(void 0, s);
1474
+ const family = _chunkBPX4Z3N3cjs.chartFamily.call(void 0, s);
1315
1475
  if (family !== "cartesian" && family !== "xy") return `Reference lines and bands are drawn on bar, line, area, scatter and bubble charts; this is a ${s.type} chart.`;
1316
1476
  if (axis === "x" && family !== "xy") return 'axis "x" needs a scatter or bubble chart with numeric x values; use "y" for the value axis.';
1317
1477
  if (axis === "y2" && (family !== "cartesian" || !s.series.some((x) => !x.hidden && x.axis === "secondary")))
@@ -1330,7 +1490,7 @@ var ChartController = (_class = class {
1330
1490
  /** Adds a reference line (a target, floor or average). Returns its index. */
1331
1491
  addReferenceLine(input) {
1332
1492
  const issues = [];
1333
- const line = _chunkR3P2M7HUcjs.normalizeChartReferenceLine.call(void 0, input, "referenceLine", issues);
1493
+ const line = _chunkBPX4Z3N3cjs.normalizeChartReferenceLine.call(void 0, input, "referenceLine", issues);
1334
1494
  if (!line || issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1335
1495
  const problem = this.referenceProblem(line.axis, [line.value]);
1336
1496
  if (problem) return this.reject(problem);
@@ -1345,7 +1505,7 @@ var ChartController = (_class = class {
1345
1505
  const merged = { ...current, ...patch };
1346
1506
  for (const [k, v] of Object.entries(patch)) if (v === null) delete merged[k];
1347
1507
  const issues = [];
1348
- const line = _chunkR3P2M7HUcjs.normalizeChartReferenceLine.call(void 0, merged, "referenceLine", issues);
1508
+ const line = _chunkBPX4Z3N3cjs.normalizeChartReferenceLine.call(void 0, merged, "referenceLine", issues);
1349
1509
  if (!line || issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1350
1510
  const problem = this.referenceProblem(line.axis, [line.value]);
1351
1511
  if (problem) return this.reject(problem);
@@ -1360,7 +1520,7 @@ var ChartController = (_class = class {
1360
1520
  /** Adds a reference band (a target range). Returns its index. */
1361
1521
  addReferenceBand(input) {
1362
1522
  const issues = [];
1363
- const band = _chunkR3P2M7HUcjs.normalizeChartReferenceBand.call(void 0, input, "referenceBand", issues);
1523
+ const band = _chunkBPX4Z3N3cjs.normalizeChartReferenceBand.call(void 0, input, "referenceBand", issues);
1364
1524
  if (!band || issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1365
1525
  const problem = this.referenceProblem(band.axis, [band.from, band.to]);
1366
1526
  if (problem) return this.reject(problem);
@@ -1375,7 +1535,7 @@ var ChartController = (_class = class {
1375
1535
  const merged = { ...current, ...patch };
1376
1536
  for (const [k, v] of Object.entries(patch)) if (v === null) delete merged[k];
1377
1537
  const issues = [];
1378
- const band = _chunkR3P2M7HUcjs.normalizeChartReferenceBand.call(void 0, merged, "referenceBand", issues);
1538
+ const band = _chunkBPX4Z3N3cjs.normalizeChartReferenceBand.call(void 0, merged, "referenceBand", issues);
1379
1539
  if (!band || issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1380
1540
  const problem = this.referenceProblem(band.axis, [band.from, band.to]);
1381
1541
  if (problem) return this.reject(problem);
@@ -1398,7 +1558,7 @@ var ChartController = (_class = class {
1398
1558
  const series = this.getSeries(seriesId);
1399
1559
  if (!series) return this.unknownSeries(seriesId);
1400
1560
  if (!Number.isInteger(index) || index < 0 || index >= this.spec.categories.length) return this.badIndex(index);
1401
- const family = _chunkR3P2M7HUcjs.chartFamily.call(void 0, this.spec);
1561
+ const family = _chunkBPX4Z3N3cjs.chartFamily.call(void 0, this.spec);
1402
1562
  const text = label === null ? "" : String(label).trim();
1403
1563
  if (text && family !== "cartesian" && family !== "xy") return this.reject(`Callouts are drawn on bar, line, area, scatter and bubble charts; this is a ${this.spec.type} chart.`);
1404
1564
  if (text && (series.values[index] === null || series.values[index] === void 0))
@@ -1420,12 +1580,12 @@ var ChartController = (_class = class {
1420
1580
  addTrendline(seriesId, input) {
1421
1581
  const series = this.getSeries(seriesId);
1422
1582
  if (!series) return this.unknownSeries(seriesId);
1423
- if (!_chunkSBQVEBSUcjs.chartSupportsTrendlines.call(void 0, this.spec.type, this.spec.stacking))
1583
+ if (!_chunkUUUTEVX6cjs.chartSupportsTrendlines.call(void 0, this.spec.type, this.spec.stacking))
1424
1584
  return this.reject(`Trendlines need an unstacked bar, line, area, scatter or bubble chart; this is a ${this.spec.stacking !== "none" ? `${this.spec.stacking} ` : ""}${this.spec.type} chart.`);
1425
1585
  const issues = [];
1426
- const line = _chunkR3P2M7HUcjs.normalizeChartTrendline.call(void 0, input, "trendline", issues);
1586
+ const line = _chunkBPX4Z3N3cjs.normalizeChartTrendline.call(void 0, input, "trendline", issues);
1427
1587
  if (!line || issues.length) return this.reject(issues.map((i) => i.message).join(" ") || "The trendline is not valid.", issues);
1428
- const index = _nullishCoalesce(_optionalChain([series, 'access', _53 => _53.trendlines, 'optionalAccess', _54 => _54.length]), () => ( 0));
1588
+ const index = _nullishCoalesce(_optionalChain([series, 'access', _69 => _69.trendlines, 'optionalAccess', _70 => _70.length]), () => ( 0));
1429
1589
  const next = this.edited((d) => {
1430
1590
  const s = d.series.find((x) => x.id === seriesId);
1431
1591
  s.trendlines = [..._nullishCoalesce(s.trendlines, () => ( [])), line];
@@ -1437,12 +1597,12 @@ var ChartController = (_class = class {
1437
1597
  updateTrendline(seriesId, index, patch) {
1438
1598
  const series = this.getSeries(seriesId);
1439
1599
  if (!series) return this.unknownSeries(seriesId);
1440
- const current = _optionalChain([series, 'access', _55 => _55.trendlines, 'optionalAccess', _56 => _56[index]]);
1441
- if (!current) return fail(`"${series.name}" has no trendline ${index}; ${_optionalChain([series, 'access', _57 => _57.trendlines, 'optionalAccess', _58 => _58.length]) ? `expected 0 to ${series.trendlines.length - 1}` : "it has none yet"}.`);
1600
+ const current = _optionalChain([series, 'access', _71 => _71.trendlines, 'optionalAccess', _72 => _72[index]]);
1601
+ if (!current) return fail(`"${series.name}" has no trendline ${index}; ${_optionalChain([series, 'access', _73 => _73.trendlines, 'optionalAccess', _74 => _74.length]) ? `expected 0 to ${series.trendlines.length - 1}` : "it has none yet"}.`);
1442
1602
  const issues = [];
1443
1603
  const merged = { ...current, ...patch };
1444
1604
  for (const [k, v] of Object.entries(patch)) if (v === null) delete merged[k];
1445
- const line = _chunkR3P2M7HUcjs.normalizeChartTrendline.call(void 0, merged, "trendline", issues);
1605
+ const line = _chunkBPX4Z3N3cjs.normalizeChartTrendline.call(void 0, merged, "trendline", issues);
1446
1606
  if (!line || issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1447
1607
  this.commit(
1448
1608
  "Change trendline",
@@ -1455,7 +1615,7 @@ var ChartController = (_class = class {
1455
1615
  removeTrendline(seriesId, index) {
1456
1616
  const series = this.getSeries(seriesId);
1457
1617
  if (!series) return this.unknownSeries(seriesId);
1458
- if (!_optionalChain([series, 'access', _59 => _59.trendlines, 'optionalAccess', _60 => _60[index]])) return fail(`"${series.name}" has no trendline ${index}.`);
1618
+ if (!_optionalChain([series, 'access', _75 => _75.trendlines, 'optionalAccess', _76 => _76[index]])) return fail(`"${series.name}" has no trendline ${index}.`);
1459
1619
  this.commit(
1460
1620
  "Delete trendline",
1461
1621
  this.edited((d) => {
@@ -1470,25 +1630,25 @@ var ChartController = (_class = class {
1470
1630
  trendlinePoints(seriesId) {
1471
1631
  const series = this.getSeries(seriesId);
1472
1632
  if (!series) return [];
1473
- const xy = _chunkR3P2M7HUcjs.chartFamily.call(void 0, this.spec) === "xy";
1633
+ const xy = _chunkBPX4Z3N3cjs.chartFamily.call(void 0, this.spec) === "xy";
1474
1634
  const out = [];
1475
1635
  series.values.forEach((v, i) => {
1476
- const x = xy ? _nullishCoalesce(_optionalChain([series, 'access', _61 => _61.x, 'optionalAccess', _62 => _62[i]]), () => ( (this.spec.type === "bubble" ? i + 1 : null))) : i + 1;
1636
+ const x = xy ? _nullishCoalesce(_optionalChain([series, 'access', _77 => _77.x, 'optionalAccess', _78 => _78[i]]), () => ( (this.spec.type === "bubble" ? i + 1 : null))) : i + 1;
1477
1637
  if (v !== null && x !== null && Number.isFinite(v) && Number.isFinite(x)) out.push([x, v]);
1478
1638
  });
1479
1639
  return out;
1480
1640
  }
1481
1641
  /** The fitted coefficients, R² and equation of a trendline (null for moving averages or unfittable data). */
1482
1642
  trendlineFit(seriesId, index) {
1483
- const line = _optionalChain([this, 'access', _63 => _63.getSeries, 'call', _64 => _64(seriesId), 'optionalAccess', _65 => _65.trendlines, 'optionalAccess', _66 => _66[index]]);
1643
+ const line = _optionalChain([this, 'access', _79 => _79.getSeries, 'call', _80 => _80(seriesId), 'optionalAccess', _81 => _81.trendlines, 'optionalAccess', _82 => _82[index]]);
1484
1644
  if (!line) return null;
1485
- return _chunkSBQVEBSUcjs.fitChartTrendline.call(void 0, line, this.trendlinePoints(seriesId));
1645
+ return _chunkUUUTEVX6cjs.fitChartTrendline.call(void 0, line, this.trendlinePoints(seriesId));
1486
1646
  }
1487
1647
  /** The moving-average points of a trendline (empty for other kinds). */
1488
1648
  trendlineMovingAverage(seriesId, index) {
1489
- const line = _optionalChain([this, 'access', _67 => _67.getSeries, 'call', _68 => _68(seriesId), 'optionalAccess', _69 => _69.trendlines, 'optionalAccess', _70 => _70[index]]);
1649
+ const line = _optionalChain([this, 'access', _83 => _83.getSeries, 'call', _84 => _84(seriesId), 'optionalAccess', _85 => _85.trendlines, 'optionalAccess', _86 => _86[index]]);
1490
1650
  if (!line || line.type !== "moving-average") return [];
1491
- return _chunkSBQVEBSUcjs.chartMovingAverage.call(void 0, this.trendlinePoints(seriesId), _nullishCoalesce(line.period, () => ( 2)));
1651
+ return _chunkUUUTEVX6cjs.chartMovingAverage.call(void 0, this.trendlinePoints(seriesId), _nullishCoalesce(line.period, () => ( 2)));
1492
1652
  }
1493
1653
  // -------------------------------------------------------------------------
1494
1654
  // Reshaping
@@ -1509,17 +1669,66 @@ var ChartController = (_class = class {
1509
1669
  used.add(id);
1510
1670
  return { id, name, values: s.series.map((x) => _nullishCoalesce(x.values[i], () => ( null))) };
1511
1671
  });
1512
- const { spec } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, { ...s, categories: s.series.map((x) => x.name), series });
1672
+ const { spec } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, { ...s, categories: s.series.map((x) => x.name), series });
1513
1673
  this.commit("Switch rows and columns", spec, null);
1514
1674
  return { ok: true };
1515
1675
  }
1516
1676
  // -------------------------------------------------------------------------
1517
1677
  // Selection and gestures
1518
1678
  // -------------------------------------------------------------------------
1679
+ /**
1680
+ * Shows a bar race at a moment: a category index, fractional between time points (`null`:
1681
+ * the last one). View state like the selection, so not an undo step. Stops playback.
1682
+ */
1683
+ setRaceFrame(frame) {
1684
+ const n = this.spec.categories.length;
1685
+ if (frame !== null && !(Number.isFinite(frame) && frame >= 0 && frame <= Math.max(0, n - 1)))
1686
+ return fail(n ? `The race has time points 0 to ${n - 1}${n > 1 ? ` ("${this.spec.categories[0]}" to "${this.spec.categories[n - 1]}")` : ""}; got ${frame}.` : "The chart has no time points (categories) yet.");
1687
+ this.stopRace();
1688
+ if (frame === this.raceFrame) return { ok: true };
1689
+ this.raceFrame = frame;
1690
+ this.notify(false);
1691
+ return { ok: true };
1692
+ }
1693
+ /**
1694
+ * Plays the bar race from the moment shown (from the start when it is at the end), moving
1695
+ * one time point every `msPerStep` (default 1000) until the last. Not an undo step.
1696
+ */
1697
+ playRace(options = {}) {
1698
+ const last = this.spec.categories.length - 1;
1699
+ if (last < 1) return fail("A race needs at least two time points (categories) to play.");
1700
+ this.stopRace();
1701
+ const per = Math.max(50, _nullishCoalesce(options.msPerStep, () => ( 1e3)));
1702
+ const from = this.raceFrame === null || this.raceFrame >= last ? 0 : this.raceFrame;
1703
+ const start = this.raceTicker.now();
1704
+ const step = () => {
1705
+ const at = Math.min(last, from + (this.raceTicker.now() - start) / per);
1706
+ this.raceFrame = at;
1707
+ if (at >= last) {
1708
+ this.raceStop = null;
1709
+ this.raceFrame = null;
1710
+ } else this.raceStop = this.raceTicker.next(step);
1711
+ this.notify(false);
1712
+ };
1713
+ this.raceFrame = from;
1714
+ this.raceStop = this.raceTicker.next(step);
1715
+ this.notify(false);
1716
+ return { ok: true };
1717
+ }
1718
+ /** Pauses bar race playback where it is. */
1719
+ pauseRace() {
1720
+ if (!this.raceStop) return;
1721
+ this.stopRace();
1722
+ this.notify(false);
1723
+ }
1724
+ stopRace() {
1725
+ _optionalChain([this, 'access', _87 => _87.raceStop, 'optionalCall', _88 => _88()]);
1726
+ this.raceStop = null;
1727
+ }
1519
1728
  /** Selects a chart element (view state; not an undo step). */
1520
1729
  select(selection) {
1521
1730
  const next = this.validSelection(selection, this.spec);
1522
- if (_chunkSBQVEBSUcjs.sameChartSelection.call(void 0, next, this.selection) || next === null && this.selection === null) return;
1731
+ if (_chunkUUUTEVX6cjs.sameChartSelection.call(void 0, next, this.selection) || next === null && this.selection === null) return;
1523
1732
  this.selection = next;
1524
1733
  this.notify(false);
1525
1734
  }
@@ -1596,14 +1805,14 @@ var ChartController = (_class = class {
1596
1805
  // -------------------------------------------------------------------------
1597
1806
  /** The spec, for saving (a copy). */
1598
1807
  toJSON() {
1599
- return _chunkR3P2M7HUcjs.cloneChartSpec.call(void 0, this.spec);
1808
+ return _chunkBPX4Z3N3cjs.cloneChartSpec.call(void 0, this.spec);
1600
1809
  }
1601
1810
  /**
1602
1811
  * Replaces the chart without an undo step (e.g. when opening a document). Problems are
1603
1812
  * fixed with defaults and reported in `state.issues` (and returned).
1604
1813
  */
1605
1814
  load(input, options = {}) {
1606
- const { spec, issues } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, input);
1815
+ const { spec, issues } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, input);
1607
1816
  this.spec = spec;
1608
1817
  this.issues = issues;
1609
1818
  this.selection = this.validSelection(this.selection, spec);
@@ -1623,7 +1832,7 @@ var ChartController = (_class = class {
1623
1832
  * nothing changes and every problem is returned (and shown in `state.issues`).
1624
1833
  */
1625
1834
  replace(input, options = {}) {
1626
- const { spec, issues } = _chunkR3P2M7HUcjs.normalizeChartSpec.call(void 0, input);
1835
+ const { spec, issues } = _chunkBPX4Z3N3cjs.normalizeChartSpec.call(void 0, input);
1627
1836
  if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1628
1837
  this.commit(_nullishCoalesce(options.label, () => ( "Replace chart")), spec, this.validSelection(this.selection, spec));
1629
1838
  return { ok: true };
@@ -1638,7 +1847,8 @@ var ChartController = (_class = class {
1638
1847
  }
1639
1848
  /** Stops listening to the history (reconnected by the next `subscribe`). */
1640
1849
  destroy() {
1641
- _optionalChain([this, 'access', _71 => _71.unsubscribeHistory, 'optionalCall', _72 => _72()]);
1850
+ this.stopRace();
1851
+ _optionalChain([this, 'access', _89 => _89.unsubscribeHistory, 'optionalCall', _90 => _90()]);
1642
1852
  this.unsubscribeHistory = void 0;
1643
1853
  this.listeners.clear();
1644
1854
  }
@@ -1673,8 +1883,12 @@ function updateLabel(patch) {
1673
1883
  parliamentArc: "Change hemicycle sweep",
1674
1884
  parliamentMajority: "Change majority",
1675
1885
  parliamentLabel: "Edit seat caption",
1886
+ raceTopN: "Change bars shown",
1676
1887
  sunburstLabel: "Edit centre caption",
1677
1888
  tree: "Replace grouped data",
1889
+ links: "Replace flows",
1890
+ sankeyNodes: "Reorder nodes",
1891
+ sankeyLinkColor: "Change flow colours",
1678
1892
  radarTicks: "Change radar rings",
1679
1893
  radarGraticule: "Change radar grid",
1680
1894
  legend: "Change legend",
@@ -1697,7 +1911,7 @@ function layoutSparkline(values, options) {
1697
1911
  const h = Number.isFinite(options.height) ? Math.max(1, options.height) : 1;
1698
1912
  const color = _nullishCoalesce(options.color, () => ( "#4269d0"));
1699
1913
  const negative = _nullishCoalesce(options.negativeColor, () => ( "#e5484d"));
1700
- const theme = { ..._chunkR3P2M7HUcjs.DEFAULT_CHART_THEME, background: "transparent" };
1914
+ const theme = { ..._chunkBPX4Z3N3cjs.DEFAULT_CHART_THEME, background: "transparent" };
1701
1915
  const marks = [];
1702
1916
  const scene = { width: w, height: h, plot: { x: 0, y: 0, width: w, height: h }, marks, percent: false, theme, warnings: [] };
1703
1917
  const vals = values.slice(0, MAX_POINTS).map((v) => v !== null && Number.isFinite(v) ? Math.max(-1e300, Math.min(1e300, v)) : null);
@@ -1730,7 +1944,7 @@ function layoutSparkline(values, options) {
1730
1944
  const flush = () => {
1731
1945
  if (run.length >= 2) {
1732
1946
  const pts = run.length > 4e3 ? run.filter((_, i) => i % (Math.floor(run.length / 2e3) + 1) === 0) : run;
1733
- d += pts.map(([x, y], i) => `${i ? "L" : "M"}${_chunkSBQVEBSUcjs.r2.call(void 0, x)},${_chunkSBQVEBSUcjs.r2.call(void 0, y)}`).join("");
1947
+ d += pts.map(([x, y], i) => `${i ? "L" : "M"}${_chunkUUUTEVX6cjs.r2.call(void 0, x)},${_chunkUUUTEVX6cjs.r2.call(void 0, y)}`).join("");
1734
1948
  }
1735
1949
  run = [];
1736
1950
  };