@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,10 +1,12 @@
1
1
  import {
2
2
  simulateColorVision
3
- } from "./chunk-QYZGBVNC.js";
3
+ } from "./chunk-7272D3K2.js";
4
+ import {
5
+ deltaEOK
6
+ } from "./chunk-YBAPT7NL.js";
4
7
  import {
5
- deltaEOK,
6
8
  rgbToOklab
7
- } from "./chunk-4VU4CP7R.js";
9
+ } from "./chunk-MHLU6GRJ.js";
8
10
 
9
11
  // src/chart/color.ts
10
12
  var NAMED = {
@@ -223,8 +225,208 @@ function parseChartAmountList(text2) {
223
225
  return text2.split(/[;,\s]+/).filter((t) => t !== "").map((t) => t === "-" ? null : t);
224
226
  }
225
227
 
228
+ // src/chart/tree.ts
229
+ var MAX_CHART_TREE_DEPTH = 6;
230
+ var MAX_CHART_TREE_NODES = 5e3;
231
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
232
+ function normalizeChartTree(value, issues = [], path = "tree") {
233
+ if (value === void 0 || value === null) return [];
234
+ if (!Array.isArray(value)) {
235
+ issues.push({ path, message: `${path} must be an array of nodes such as {"name": "Europe", "children": [{"name": "France", "value": 40}]}.` });
236
+ return [];
237
+ }
238
+ let count = 0;
239
+ const read = (list, at, depth) => {
240
+ const out = [];
241
+ const names = /* @__PURE__ */ new Set();
242
+ list.forEach((raw, i) => {
243
+ const where = `${at}[${i}]`;
244
+ if (!isRecord(raw)) {
245
+ issues.push({ path: where, message: `${where} must be an object with a name and a value or children.` });
246
+ return;
247
+ }
248
+ const name = typeof raw.name === "number" ? String(raw.name) : typeof raw.name === "string" ? raw.name.trim() : "";
249
+ if (!name) {
250
+ issues.push({ path: `${where}.name`, message: `${where} needs a name.` });
251
+ return;
252
+ }
253
+ if (names.has(name)) {
254
+ issues.push({ path: `${where}.name`, message: `"${name}" appears twice under the same group in ${at}; sibling names must be unique, so the second was left out.` });
255
+ return;
256
+ }
257
+ if (++count > MAX_CHART_TREE_NODES) {
258
+ if (count === MAX_CHART_TREE_NODES + 1) issues.push({ path, message: `The tree has more than ${MAX_CHART_TREE_NODES} nodes; the rest were left out.` });
259
+ return;
260
+ }
261
+ names.add(name);
262
+ for (const k of Object.keys(raw)) if (!["name", "value", "children", "color"].includes(k)) issues.push({ path: `${where}.${k}`, message: `${where} has no "${k}"; a node takes name, value, children and color.` });
263
+ const node = { name };
264
+ if (typeof raw.color === "string" && raw.color.trim()) node.color = raw.color.trim();
265
+ if (Array.isArray(raw.children) && raw.children.length) {
266
+ if (depth >= MAX_CHART_TREE_DEPTH) {
267
+ issues.push({ path: `${where}.children`, message: `${where} is more than ${MAX_CHART_TREE_DEPTH} levels deep; its children were left out.` });
268
+ node.value = 0;
269
+ } else {
270
+ if (raw.value !== void 0 && raw.value !== null) issues.push({ path: `${where}.value`, message: `"${name}" is a group: its value is the sum of its children, so the value given was ignored.` });
271
+ const children = read(raw.children, `${where}.children`, depth + 1);
272
+ if (children.length) node.children = children;
273
+ else node.value = 0;
274
+ }
275
+ } else {
276
+ if (raw.children !== void 0 && raw.children !== null && !Array.isArray(raw.children)) issues.push({ path: `${where}.children`, message: `${where}.children must be an array of nodes.` });
277
+ const v = parseChartValue(raw.value);
278
+ if (raw.value === void 0 || raw.value === null) node.value = 0;
279
+ else if (typeof v === "number" && v >= 0) node.value = v;
280
+ else {
281
+ issues.push({ path: `${where}.value`, message: `"${name}" needs a value of 0 or more; got ${JSON.stringify(raw.value)}.` });
282
+ node.value = 0;
283
+ }
284
+ }
285
+ out.push(node);
286
+ });
287
+ return out;
288
+ };
289
+ return read(value, path, 1);
290
+ }
291
+ function chartTreeTotal(node) {
292
+ return node.children ? node.children.reduce((t, c) => t + chartTreeTotal(c), 0) : node.value ?? 0;
293
+ }
294
+ function chartTreeDepth(tree) {
295
+ return tree.reduce((d, n) => Math.max(d, 1 + (n.children ? chartTreeDepth(n.children) : 0)), 0);
296
+ }
297
+ function findChartTreeNode(tree, path) {
298
+ let list = tree;
299
+ let node;
300
+ for (const name of path) {
301
+ node = list?.find((n) => n.name === name);
302
+ if (!node) return void 0;
303
+ list = node.children;
304
+ }
305
+ return node;
306
+ }
307
+ function chartTreeToRows(tree) {
308
+ const out = [];
309
+ const walk = (list, prefix) => {
310
+ for (const n of list) {
311
+ if (n.children) walk(n.children, [...prefix, n.name]);
312
+ else out.push({ path: [...prefix, n.name], value: n.value ?? 0 });
313
+ }
314
+ };
315
+ walk(tree, []);
316
+ return out;
317
+ }
318
+ function chartTreeFromRows(rows, issues = []) {
319
+ const tree = [];
320
+ rows.forEach((row, r) => {
321
+ const path = [];
322
+ for (const cell of row.path) {
323
+ const name = cell === null || cell === void 0 ? "" : String(cell).trim();
324
+ if (!name) break;
325
+ path.push(name);
326
+ }
327
+ if (!path.length) return;
328
+ if (path.length > MAX_CHART_TREE_DEPTH) {
329
+ issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} is ${path.length} levels deep; the tree keeps at most ${MAX_CHART_TREE_DEPTH}, so the row was left out.` });
330
+ return;
331
+ }
332
+ const v = parseChartValue(row.value);
333
+ if (row.value !== null && row.value !== void 0 && String(row.value).trim() !== "" && !(typeof v === "number" && v >= 0)) {
334
+ issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} (${path.join(" / ")}) needs a value of 0 or more; got ${JSON.stringify(row.value)}. It was counted as 0.` });
335
+ }
336
+ const value = typeof v === "number" && v >= 0 ? v : 0;
337
+ let list = tree;
338
+ for (let k = 0; k < path.length; k++) {
339
+ const last = k === path.length - 1;
340
+ let node = list.find((n) => n.name === path[k]);
341
+ if (!node) {
342
+ node = last ? { name: path[k], value } : { name: path[k], children: [] };
343
+ list.push(node);
344
+ if (last) return;
345
+ } else if (last) {
346
+ issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} (${path.join(" / ")}) ${node.children ? "names a group that already has items" : "repeats an earlier row"}; it was left out.` });
347
+ return;
348
+ } else if (!node.children) {
349
+ issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} puts items under "${path.slice(0, k + 1).join(" / ")}", which an earlier row made an item with its own value; it was left out.` });
350
+ return;
351
+ }
352
+ list = node.children;
353
+ }
354
+ });
355
+ return tree;
356
+ }
357
+ function chartTreeFromTable(rows, options = {}, issues = []) {
358
+ const grid = rows.filter((r) => r.some((c) => c !== null && c !== void 0 && String(c).trim() !== ""));
359
+ if (!grid.length) return [];
360
+ const width = Math.max(...grid.map((r) => r.length));
361
+ if (width < 2) {
362
+ issues.push({ path: "rows", message: "A tree table needs at least two columns: names, then values." });
363
+ return [];
364
+ }
365
+ const header = options.header ?? "auto";
366
+ const skip = header === true || header === "auto" && typeof parseChartValue(grid[0][width - 1]) !== "number";
367
+ const body = skip ? grid.slice(1) : grid;
368
+ let above = [];
369
+ return chartTreeFromRows(
370
+ body.map((r) => {
371
+ const cells = Array.from({ length: width - 1 }, (_, k) => r[k] === null || r[k] === void 0 ? "" : String(r[k]).trim());
372
+ const first = cells.findIndex((c) => c !== "");
373
+ if (first < 0) return { path: [], value: r[width - 1] };
374
+ const own = [];
375
+ for (const c of cells.slice(first)) {
376
+ if (!c) break;
377
+ own.push(c);
378
+ }
379
+ const path = [...above.slice(0, first), ...own];
380
+ above = path;
381
+ return { path, value: r[width - 1] };
382
+ }),
383
+ issues
384
+ );
385
+ }
386
+ function chartTreeFromSeries(categories, values) {
387
+ const seen = /* @__PURE__ */ new Set();
388
+ const out = [];
389
+ categories.forEach((c, i) => {
390
+ const name = c.trim() || `${i + 1}`;
391
+ if (seen.has(name)) return;
392
+ seen.add(name);
393
+ const v = values[i];
394
+ out.push({ name, value: v !== null && v !== void 0 && Number.isFinite(v) && v > 0 ? v : 0 });
395
+ });
396
+ return out;
397
+ }
398
+ function cloneChartTree(tree) {
399
+ return tree.map((n) => ({ ...n, ...n.children ? { children: cloneChartTree(n.children) } : {} }));
400
+ }
401
+ function chartTreeColumnLabels(depth) {
402
+ if (depth <= 1) return ["Item"];
403
+ return Array.from({ length: depth }, (_, k) => k === 0 ? "Group" : k === depth - 1 ? "Item" : `Level ${k + 1}`);
404
+ }
405
+ function setChartTreeRowName(rows, row, level, name) {
406
+ return rows.map((r, i) => {
407
+ if (i !== row) return { path: [...r.path], value: r.value };
408
+ const path = [...r.path];
409
+ while (path.length <= level) path.push("");
410
+ path[level] = name.trim();
411
+ return { path, value: r.value };
412
+ });
413
+ }
414
+ function setChartTreeRowValue(rows, row, value) {
415
+ return rows.map((r, i) => ({ path: [...r.path], value: i === row ? value : r.value }));
416
+ }
417
+ function addChartTreeRow(rows) {
418
+ const parent = rows.length ? rows[rows.length - 1].path.slice(0, -1) : [];
419
+ const taken = new Set(rows.filter((r) => r.path.length === parent.length + 1 && parent.every((p, k) => r.path[k] === p)).map((r) => r.path[parent.length]));
420
+ let n = rows.length + 1;
421
+ while (taken.has(`Item ${n}`)) n++;
422
+ return [...rows.map((r) => ({ path: [...r.path], value: r.value })), { path: [...parent, `Item ${n}`], value: 0 }];
423
+ }
424
+ function removeChartTreeRow(rows, row) {
425
+ return rows.filter((_, i) => i !== row).map((r) => ({ path: [...r.path], value: r.value }));
426
+ }
427
+
226
428
  // src/chart/spec.ts
227
- var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap", "sunburst", "slope", "likert", "parliament"];
429
+ var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap", "sunburst", "sankey", "slope", "likert", "parliament", "barrace"];
228
430
  var CHART_MARKERS = ["circle", "square", "diamond", "triangle", "cross", "none"];
229
431
  var CHART_TRENDLINE_TYPES = ["linear", "exponential", "logarithmic", "power", "polynomial", "moving-average"];
230
432
  var CHART_LABEL_POSITIONS = ["auto", "inside", "outside", "center"];
@@ -236,6 +438,7 @@ var CHART_CURVES = ["linear", "smooth", "step"];
236
438
  var CHART_RADAR_GRATICULES = ["polygon", "circle"];
237
439
  var CHART_LIKERT_NEUTRALS = ["split", "separate"];
238
440
  var CHART_LIKERT_SORTS = ["none", "positive", "negative"];
441
+ var CHART_SANKEY_LINK_COLORS = ["source", "target", "gray"];
239
442
  var CHART_REFERENCE_AXES = ["y", "y2", "x"];
240
443
  var CHART_ORIENTATIONS = ["vertical", "horizontal"];
241
444
  var CHART_LEGEND_POSITIONS = ["top", "bottom", "left", "right"];
@@ -257,7 +460,9 @@ var CHART_TYPE_LABELS = {
257
460
  slope: "Slope",
258
461
  likert: "Likert",
259
462
  parliament: "Parliament",
260
- sunburst: "Sunburst"
463
+ sunburst: "Sunburst",
464
+ sankey: "Sankey",
465
+ barrace: "Bar race"
261
466
  };
262
467
  var DEFAULT_CHART_PALETTE = ["#4269d0", "#efb118", "#ff725c", "#6cc5b0", "#3ca951", "#ff8ab7", "#a463f2", "#97bbf5", "#9c6b4e", "#9498a0"];
263
468
  var DEFAULT_CHART_THEME = {
@@ -308,6 +513,8 @@ function chartFamily(spec) {
308
513
  case "likert":
309
514
  case "parliament":
310
515
  case "sunburst":
516
+ case "sankey":
517
+ case "barrace":
311
518
  return spec.type;
312
519
  case "bubble":
313
520
  return "xy";
@@ -316,7 +523,7 @@ function chartFamily(spec) {
316
523
  }
317
524
  }
318
525
  function chartColorsByCategory(type) {
319
- return type === "pie" || type === "donut" || type === "funnel" || type === "treemap" || type === "slope" || type === "parliament" || type === "sunburst";
526
+ return type === "pie" || type === "donut" || type === "funnel" || type === "treemap" || type === "slope" || type === "parliament" || type === "sunburst" || type === "sankey";
320
527
  }
321
528
  function chartSpecColorsByCategory(spec) {
322
529
  return chartColorsByCategory(spec.type) || chartVariesColors(spec);
@@ -390,7 +597,7 @@ function parseChartValue(input) {
390
597
  if (negative) value = -value;
391
598
  return Number.isFinite(value) ? value : void 0;
392
599
  }
393
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
600
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
394
601
  function pick(value, options, fallback, path, issues) {
395
602
  if (value === void 0) return fallback;
396
603
  if (typeof value === "string" && options.includes(value)) return value;
@@ -419,7 +626,7 @@ function bound(value, path, issues) {
419
626
  }
420
627
  function normalizeFormat(value, path, issues) {
421
628
  if (value === void 0 || value === null) return {};
422
- if (!isRecord(value)) {
629
+ if (!isRecord2(value)) {
423
630
  issues.push({ path, message: `${path} must be an object.` });
424
631
  return {};
425
632
  }
@@ -437,7 +644,7 @@ function normalizeFormat(value, path, issues) {
437
644
  }
438
645
  function normalizeChartAxis(value, fallback, path, issues = []) {
439
646
  if (value === void 0) return { ...fallback, format: { ...fallback.format } };
440
- if (!isRecord(value)) {
647
+ if (!isRecord2(value)) {
441
648
  issues.push({ path, message: `${path} must be an object.` });
442
649
  return { ...fallback, format: { ...fallback.format } };
443
650
  }
@@ -487,7 +694,7 @@ function normalizeChartAxis(value, fallback, path, issues = []) {
487
694
  function normalizeLegend(value, issues) {
488
695
  const fallback = { show: true, position: "bottom" };
489
696
  if (value === void 0) return fallback;
490
- if (!isRecord(value)) {
697
+ if (!isRecord2(value)) {
491
698
  issues.push({ path: "legend", message: "legend must be an object." });
492
699
  return fallback;
493
700
  }
@@ -517,7 +724,7 @@ function normalizeChartValues(value, length, path, issues = []) {
517
724
  }
518
725
  function normalizeSeries(value, length, used, index, issues) {
519
726
  const path = `series[${index}]`;
520
- if (!isRecord(value)) {
727
+ if (!isRecord2(value)) {
521
728
  issues.push({ path, message: `${path} must be an object with a name and values.` });
522
729
  return null;
523
730
  }
@@ -607,7 +814,7 @@ var finiteNumber = (value) => {
607
814
  return typeof n === "number" && Number.isFinite(n) ? n : null;
608
815
  };
609
816
  function normalizeChartReferenceLine(value, path, issues = []) {
610
- if (!isRecord(value)) {
817
+ if (!isRecord2(value)) {
611
818
  issues.push({ path, message: `${path} must be an object with a value, e.g. {"value": 100, "label": "Target"}.` });
612
819
  return null;
613
820
  }
@@ -620,7 +827,7 @@ function normalizeChartReferenceLine(value, path, issues = []) {
620
827
  return { value: at, ...referenceCommon(value, path, issues) };
621
828
  }
622
829
  function normalizeChartReferenceBand(value, path, issues = []) {
623
- if (!isRecord(value)) {
830
+ if (!isRecord2(value)) {
624
831
  issues.push({ path, message: `${path} must be an object with from and to, e.g. {"from": 80, "to": 120, "label": "Target range"}.` });
625
832
  return null;
626
833
  }
@@ -634,7 +841,7 @@ function normalizeChartReferenceBand(value, path, issues = []) {
634
841
  return { from: Math.min(from, to), to: Math.max(from, to), ...referenceCommon(value, path, issues) };
635
842
  }
636
843
  function normalizeChartErrorBars(value, length, path, issues = []) {
637
- if (!isRecord(value)) {
844
+ if (!isRecord2(value)) {
638
845
  issues.push({ path, message: `${path} must be an object with a type (${CHART_ERROR_BAR_TYPES.join(", ")}), or null for none.` });
639
846
  return null;
640
847
  }
@@ -667,7 +874,7 @@ function normalizeChartErrorBars(value, length, path, issues = []) {
667
874
  return issues.length > before ? null : bars;
668
875
  }
669
876
  function normalizeChartTrendline(value, path, issues = []) {
670
- if (!isRecord(value)) {
877
+ if (!isRecord2(value)) {
671
878
  issues.push({ path, message: `${path} must be an object with a type.` });
672
879
  return null;
673
880
  }
@@ -706,8 +913,8 @@ function normalizeChartTrendline(value, path, issues = []) {
706
913
  }
707
914
  function normalizeChartSpec(input) {
708
915
  const issues = [];
709
- const value = isRecord(input) ? input : {};
710
- if (input !== void 0 && !isRecord(input)) issues.push({ path: "", message: "A chart must be an object." });
916
+ const value = isRecord2(input) ? input : {};
917
+ if (input !== void 0 && !isRecord2(input)) issues.push({ path: "", message: "A chart must be an object." });
711
918
  const type = pick(value.type, CHART_TYPES, "bar", "type", issues);
712
919
  const rawSeries = Array.isArray(value.series) ? value.series : [];
713
920
  if (value.series !== void 0 && !Array.isArray(value.series)) issues.push({ path: "series", message: "series must be an array." });
@@ -715,16 +922,21 @@ function normalizeChartSpec(input) {
715
922
  if (Array.isArray(value.categories)) categories = value.categories.map((c, i) => text(c, "", `categories[${i}]`, issues));
716
923
  else {
717
924
  if (value.categories !== void 0) issues.push({ path: "categories", message: "categories must be an array of labels." });
718
- const longest = rawSeries.reduce((n, s) => Math.max(n, isRecord(s) && Array.isArray(s.values) ? s.values.length : 0), 0);
925
+ const longest = rawSeries.reduce((n, s) => Math.max(n, isRecord2(s) && Array.isArray(s.values) ? s.values.length : 0), 0);
719
926
  categories = Array.from({ length: longest }, (_, i) => `${i + 1}`);
720
927
  }
721
928
  const tree = normalizeChartTree(value.tree, issues);
722
- if (tree.length) categories = tree.map((n) => n.name);
929
+ const links = normalizeChartLinks(value.links, issues);
930
+ const sankeyNodes = chartSankeyNodes(links, Array.isArray(value.sankeyNodes) ? value.sankeyNodes.map(String) : [], issues);
931
+ if (value.sankeyNodes !== void 0 && value.sankeyNodes !== null && !Array.isArray(value.sankeyNodes)) issues.push({ path: "sankeyNodes", message: "sankeyNodes must be an array of node names." });
932
+ if (tree.length && links.length) issues.push({ path: "links", message: "A chart holds either grouped data (tree) or flows (links), not both; the flows were used." });
933
+ if (links.length) categories = [...sankeyNodes];
934
+ else if (tree.length) categories = tree.map((n) => n.name);
723
935
  const used = /* @__PURE__ */ new Set();
724
936
  const series = rawSeries.map((s, i) => normalizeSeries(s, categories.length, used, i, issues)).filter((s) => s !== null);
725
- if (tree.length) {
937
+ if (tree.length || links.length) {
726
938
  if (!series.length) series.push({ id: uniqueChartSeriesId(series), name: "Values", values: [] });
727
- series[0] = { ...series[0], values: tree.map(chartTreeTotal) };
939
+ series[0] = { ...series[0], values: links.length ? chartSankeyNodeValues(links, sankeyNodes) : tree.map(chartTreeTotal) };
728
940
  }
729
941
  let palette = [...DEFAULT_CHART_PALETTE];
730
942
  if (Array.isArray(value.palette)) {
@@ -774,6 +986,12 @@ function normalizeChartSpec(input) {
774
986
  if (Number.isInteger(m) && m >= 1) parliamentMajority = m;
775
987
  else issues.push({ path: "parliamentMajority", message: "parliamentMajority must be a whole number of seats, 1 or more, or null for half plus one." });
776
988
  }
989
+ let raceTopN = 10;
990
+ if (value.raceTopN !== void 0) {
991
+ const n = Number(value.raceTopN);
992
+ if (value.raceTopN !== null && Number.isInteger(n) && n >= 1 && n <= 30) raceTopN = n;
993
+ else issues.push({ path: "raceTopN", message: "raceTopN must be a whole number of bars from 1 to 30." });
994
+ }
777
995
  let radarTicks = null;
778
996
  if (value.radarTicks !== void 0 && value.radarTicks !== null) {
779
997
  const t = Number(value.radarTicks);
@@ -815,8 +1033,12 @@ function normalizeChartSpec(input) {
815
1033
  parliamentArc: fraction("parliamentArc", 180, 90, 360),
816
1034
  parliamentMajority,
817
1035
  parliamentLabel: text(value.parliamentLabel, "", "parliamentLabel", issues),
818
- tree,
819
- sunburstLabel: text(value.sunburstLabel, "", "sunburstLabel", issues)
1036
+ tree: links.length ? [] : tree,
1037
+ sunburstLabel: text(value.sunburstLabel, "", "sunburstLabel", issues),
1038
+ links,
1039
+ sankeyNodes: links.length ? sankeyNodes : [],
1040
+ sankeyLinkColor: pick(value.sankeyLinkColor, CHART_SANKEY_LINK_COLORS, "source", "sankeyLinkColor", issues),
1041
+ raceTopN
820
1042
  };
821
1043
  if (spec.stacking === "percent")
822
1044
  for (const name of ["yAxis", "yAxis2"]) {
@@ -840,7 +1062,9 @@ function cloneChartSpec(spec) {
840
1062
  highlight: [...spec.highlight],
841
1063
  referenceLines: spec.referenceLines.map((l) => ({ ...l })),
842
1064
  referenceBands: spec.referenceBands.map((b) => ({ ...b })),
843
- tree: cloneChartTree(spec.tree)
1065
+ tree: cloneChartTree(spec.tree),
1066
+ links: spec.links.map((l) => ({ ...l })),
1067
+ sankeyNodes: [...spec.sankeyNodes]
844
1068
  };
845
1069
  }
846
1070
  function cloneChartSeries(series) {
@@ -909,204 +1133,120 @@ function chartColorInputValue(color) {
909
1133
  return "#000000";
910
1134
  }
911
1135
 
912
- // src/chart/tree.ts
913
- var MAX_CHART_TREE_DEPTH = 6;
914
- var MAX_CHART_TREE_NODES = 5e3;
915
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
916
- function normalizeChartTree(value, issues = [], path = "tree") {
1136
+ // src/chart/links.ts
1137
+ var MAX_CHART_LINKS = 2e3;
1138
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1139
+ var nameOf = (value) => typeof value === "number" ? String(value) : typeof value === "string" ? value.trim() : "";
1140
+ function normalizeChartLinks(value, issues = [], path = "links") {
917
1141
  if (value === void 0 || value === null) return [];
918
1142
  if (!Array.isArray(value)) {
919
- issues.push({ path, message: `${path} must be an array of nodes such as {"name": "Europe", "children": [{"name": "France", "value": 40}]}.` });
1143
+ issues.push({ path, message: `${path} must be an array of flows such as {"source": "Website", "target": "Signup", "value": 120}.` });
920
1144
  return [];
921
1145
  }
922
- let count = 0;
923
- const read = (list, at, depth) => {
924
- const out = [];
925
- const names = /* @__PURE__ */ new Set();
926
- list.forEach((raw, i) => {
927
- const where = `${at}[${i}]`;
928
- if (!isRecord2(raw)) {
929
- issues.push({ path: where, message: `${where} must be an object with a name and a value or children.` });
930
- return;
931
- }
932
- const name = typeof raw.name === "number" ? String(raw.name) : typeof raw.name === "string" ? raw.name.trim() : "";
933
- if (!name) {
934
- issues.push({ path: `${where}.name`, message: `${where} needs a name.` });
935
- return;
936
- }
937
- if (names.has(name)) {
938
- issues.push({ path: `${where}.name`, message: `"${name}" appears twice under the same group in ${at}; sibling names must be unique, so the second was left out.` });
939
- return;
940
- }
941
- if (++count > MAX_CHART_TREE_NODES) {
942
- if (count === MAX_CHART_TREE_NODES + 1) issues.push({ path, message: `The tree has more than ${MAX_CHART_TREE_NODES} nodes; the rest were left out.` });
943
- return;
944
- }
945
- names.add(name);
946
- for (const k of Object.keys(raw)) if (!["name", "value", "children", "color"].includes(k)) issues.push({ path: `${where}.${k}`, message: `${where} has no "${k}"; a node takes name, value, children and color.` });
947
- const node = { name };
948
- if (typeof raw.color === "string" && raw.color.trim()) node.color = raw.color.trim();
949
- if (Array.isArray(raw.children) && raw.children.length) {
950
- if (depth >= MAX_CHART_TREE_DEPTH) {
951
- issues.push({ path: `${where}.children`, message: `${where} is more than ${MAX_CHART_TREE_DEPTH} levels deep; its children were left out.` });
952
- node.value = 0;
953
- } else {
954
- if (raw.value !== void 0 && raw.value !== null) issues.push({ path: `${where}.value`, message: `"${name}" is a group: its value is the sum of its children, so the value given was ignored.` });
955
- const children = read(raw.children, `${where}.children`, depth + 1);
956
- if (children.length) node.children = children;
957
- else node.value = 0;
958
- }
959
- } else {
960
- if (raw.children !== void 0 && raw.children !== null && !Array.isArray(raw.children)) issues.push({ path: `${where}.children`, message: `${where}.children must be an array of nodes.` });
961
- const v = parseChartValue(raw.value);
962
- if (raw.value === void 0 || raw.value === null) node.value = 0;
963
- else if (typeof v === "number" && v >= 0) node.value = v;
964
- else {
965
- issues.push({ path: `${where}.value`, message: `"${name}" needs a value of 0 or more; got ${JSON.stringify(raw.value)}.` });
966
- node.value = 0;
967
- }
968
- }
969
- out.push(node);
970
- });
971
- return out;
972
- };
973
- return read(value, path, 1);
974
- }
975
- function chartTreeTotal(node) {
976
- return node.children ? node.children.reduce((t, c) => t + chartTreeTotal(c), 0) : node.value ?? 0;
977
- }
978
- function chartTreeDepth(tree) {
979
- return tree.reduce((d, n) => Math.max(d, 1 + (n.children ? chartTreeDepth(n.children) : 0)), 0);
980
- }
981
- function findChartTreeNode(tree, path) {
982
- let list = tree;
983
- let node;
984
- for (const name of path) {
985
- node = list?.find((n) => n.name === name);
986
- if (!node) return void 0;
987
- list = node.children;
988
- }
989
- return node;
990
- }
991
- function chartTreeToRows(tree) {
992
1146
  const out = [];
993
- const walk = (list, prefix) => {
994
- for (const n of list) {
995
- if (n.children) walk(n.children, [...prefix, n.name]);
996
- else out.push({ path: [...prefix, n.name], value: n.value ?? 0 });
1147
+ const seen = /* @__PURE__ */ new Set();
1148
+ const next = /* @__PURE__ */ new Map();
1149
+ const reaches = (to, from) => {
1150
+ const stack = [to];
1151
+ const visited = /* @__PURE__ */ new Set();
1152
+ while (stack.length) {
1153
+ const n = stack.pop();
1154
+ if (n === from) return true;
1155
+ if (visited.has(n)) continue;
1156
+ visited.add(n);
1157
+ for (const m of next.get(n) ?? []) stack.push(m);
997
1158
  }
1159
+ return false;
998
1160
  };
999
- walk(tree, []);
1000
- return out;
1001
- }
1002
- function chartTreeFromRows(rows, issues = []) {
1003
- const tree = [];
1004
- rows.forEach((row, r) => {
1005
- const path = [];
1006
- for (const cell of row.path) {
1007
- const name = cell === null || cell === void 0 ? "" : String(cell).trim();
1008
- if (!name) break;
1009
- path.push(name);
1161
+ value.forEach((raw, i) => {
1162
+ const where = `${path}[${i}]`;
1163
+ if (out.length >= MAX_CHART_LINKS) {
1164
+ if (out.length === MAX_CHART_LINKS && i === MAX_CHART_LINKS) issues.push({ path, message: `There are more than ${MAX_CHART_LINKS} flows; the rest were left out.` });
1165
+ return;
1010
1166
  }
1011
- if (!path.length) return;
1012
- if (path.length > MAX_CHART_TREE_DEPTH) {
1013
- issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} is ${path.length} levels deep; the tree keeps at most ${MAX_CHART_TREE_DEPTH}, so the row was left out.` });
1167
+ if (!isRecord3(raw)) {
1168
+ issues.push({ path: where, message: `${where} must be an object with source, target and value.` });
1014
1169
  return;
1015
1170
  }
1016
- const v = parseChartValue(row.value);
1017
- if (row.value !== null && row.value !== void 0 && String(row.value).trim() !== "" && !(typeof v === "number" && v >= 0)) {
1018
- issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} (${path.join(" / ")}) needs a value of 0 or more; got ${JSON.stringify(row.value)}. It was counted as 0.` });
1171
+ for (const k of Object.keys(raw)) if (!["source", "target", "value", "color"].includes(k)) issues.push({ path: `${where}.${k}`, message: `${where} has no "${k}"; a flow takes source, target, value and color.` });
1172
+ const source = nameOf(raw.source);
1173
+ const target = nameOf(raw.target);
1174
+ if (!source || !target) {
1175
+ issues.push({ path: where, message: `${where} needs a source and a target (node names).` });
1176
+ return;
1019
1177
  }
1020
- const value = typeof v === "number" && v >= 0 ? v : 0;
1021
- let list = tree;
1022
- for (let k = 0; k < path.length; k++) {
1023
- const last = k === path.length - 1;
1024
- let node = list.find((n) => n.name === path[k]);
1025
- if (!node) {
1026
- node = last ? { name: path[k], value } : { name: path[k], children: [] };
1027
- list.push(node);
1028
- if (last) return;
1029
- } else if (last) {
1030
- issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} (${path.join(" / ")}) ${node.children ? "names a group that already has items" : "repeats an earlier row"}; it was left out.` });
1031
- return;
1032
- } else if (!node.children) {
1033
- issues.push({ path: `rows[${r}]`, message: `Row ${r + 1} puts items under "${path.slice(0, k + 1).join(" / ")}", which an earlier row made an item with its own value; it was left out.` });
1034
- return;
1035
- }
1036
- list = node.children;
1178
+ if (source === target) {
1179
+ issues.push({ path: where, message: `${where} goes from "${source}" to itself; a flow must join two different nodes.` });
1180
+ return;
1181
+ }
1182
+ const v = parseChartValue(raw.value);
1183
+ if (!(typeof v === "number" && v > 0)) {
1184
+ issues.push({ path: `${where}.value`, message: `The flow from "${source}" to "${target}" needs a value above 0; got ${JSON.stringify(raw.value)}.` });
1185
+ return;
1186
+ }
1187
+ const key2 = `${source}\0${target}`;
1188
+ if (seen.has(key2)) {
1189
+ issues.push({ path: where, message: `The flow from "${source}" to "${target}" appears twice; add the values into one flow. The second was left out.` });
1190
+ return;
1037
1191
  }
1192
+ if (reaches(target, source)) {
1193
+ issues.push({ path: where, message: `The flow from "${source}" to "${target}" would close a loop (${target} already leads back to ${source}); flows must only go forwards, so it was left out.` });
1194
+ return;
1195
+ }
1196
+ seen.add(key2);
1197
+ if (!next.has(source)) next.set(source, /* @__PURE__ */ new Set());
1198
+ next.get(source).add(target);
1199
+ const link = { source, target, value: v };
1200
+ if (typeof raw.color === "string" && raw.color.trim()) link.color = raw.color.trim();
1201
+ out.push(link);
1038
1202
  });
1039
- return tree;
1203
+ return out;
1040
1204
  }
1041
- function chartTreeFromTable(rows, options = {}, issues = []) {
1042
- const grid = rows.filter((r) => r.some((c) => c !== null && c !== void 0 && String(c).trim() !== ""));
1043
- if (!grid.length) return [];
1044
- const width = Math.max(...grid.map((r) => r.length));
1045
- if (width < 2) {
1046
- issues.push({ path: "rows", message: "A tree table needs at least two columns: names, then values." });
1047
- return [];
1048
- }
1049
- const header = options.header ?? "auto";
1050
- const skip = header === true || header === "auto" && typeof parseChartValue(grid[0][width - 1]) !== "number";
1051
- const body = skip ? grid.slice(1) : grid;
1052
- let above = [];
1053
- return chartTreeFromRows(
1054
- body.map((r) => {
1055
- const cells = Array.from({ length: width - 1 }, (_, k) => r[k] === null || r[k] === void 0 ? "" : String(r[k]).trim());
1056
- const first = cells.findIndex((c) => c !== "");
1057
- if (first < 0) return { path: [], value: r[width - 1] };
1058
- const own = [];
1059
- for (const c of cells.slice(first)) {
1060
- if (!c) break;
1061
- own.push(c);
1205
+ function chartSankeyNodes(links, order = [], issues = []) {
1206
+ const used = /* @__PURE__ */ new Set();
1207
+ const named = [];
1208
+ for (const l of links)
1209
+ for (const n of [l.source, l.target])
1210
+ if (!used.has(n)) {
1211
+ used.add(n);
1212
+ named.push(n);
1062
1213
  }
1063
- const path = [...above.slice(0, first), ...own];
1064
- above = path;
1065
- return { path, value: r[width - 1] };
1066
- }),
1067
- issues
1068
- );
1069
- }
1070
- function chartTreeFromSeries(categories, values) {
1071
- const seen = /* @__PURE__ */ new Set();
1072
1214
  const out = [];
1073
- categories.forEach((c, i) => {
1074
- const name = c.trim() || `${i + 1}`;
1075
- if (seen.has(name)) return;
1076
- seen.add(name);
1077
- const v = values[i];
1078
- out.push({ name, value: v !== null && v !== void 0 && Number.isFinite(v) && v > 0 ? v : 0 });
1079
- });
1215
+ for (const n of order) {
1216
+ if (!used.has(n)) {
1217
+ if (links.length) issues.push({ path: "sankeyNodes", message: `sankeyNodes names "${n}", which no flow uses; it was left out.` });
1218
+ continue;
1219
+ }
1220
+ if (!out.includes(n)) out.push(n);
1221
+ }
1222
+ for (const n of named) if (!out.includes(n)) out.push(n);
1080
1223
  return out;
1081
1224
  }
1082
- function cloneChartTree(tree) {
1083
- return tree.map((n) => ({ ...n, ...n.children ? { children: cloneChartTree(n.children) } : {} }));
1084
- }
1085
- function chartTreeColumnLabels(depth) {
1086
- if (depth <= 1) return ["Item"];
1087
- return Array.from({ length: depth }, (_, k) => k === 0 ? "Group" : k === depth - 1 ? "Item" : `Level ${k + 1}`);
1088
- }
1089
- function setChartTreeRowName(rows, row, level, name) {
1090
- return rows.map((r, i) => {
1091
- if (i !== row) return { path: [...r.path], value: r.value };
1092
- const path = [...r.path];
1093
- while (path.length <= level) path.push("");
1094
- path[level] = name.trim();
1095
- return { path, value: r.value };
1096
- });
1225
+ function chartSankeyNodeValues(links, nodes) {
1226
+ const inflow = /* @__PURE__ */ new Map();
1227
+ const outflow = /* @__PURE__ */ new Map();
1228
+ for (const l of links) {
1229
+ outflow.set(l.source, (outflow.get(l.source) ?? 0) + l.value);
1230
+ inflow.set(l.target, (inflow.get(l.target) ?? 0) + l.value);
1231
+ }
1232
+ return nodes.map((n) => Math.max(inflow.get(n) ?? 0, outflow.get(n) ?? 0));
1097
1233
  }
1098
- function setChartTreeRowValue(rows, row, value) {
1099
- return rows.map((r, i) => ({ path: [...r.path], value: i === row ? value : r.value }));
1234
+ function chartLinksFromTable(rows, options = {}) {
1235
+ const grid = rows.filter((r) => r.some((c) => c !== null && c !== void 0 && String(c).trim() !== ""));
1236
+ if (!grid.length) return [];
1237
+ const header = options.header ?? "auto";
1238
+ const skip = header === true || header === "auto" && typeof parseChartValue(grid[0][2]) !== "number";
1239
+ return (skip ? grid.slice(1) : grid).map((r) => ({ source: r[0], target: r[1], value: r[2] }));
1100
1240
  }
1101
- function addChartTreeRow(rows) {
1102
- const parent = rows.length ? rows[rows.length - 1].path.slice(0, -1) : [];
1103
- const taken = new Set(rows.filter((r) => r.path.length === parent.length + 1 && parent.every((p, k) => r.path[k] === p)).map((r) => r.path[parent.length]));
1104
- let n = rows.length + 1;
1105
- while (taken.has(`Item ${n}`)) n++;
1106
- return [...rows.map((r) => ({ path: [...r.path], value: r.value })), { path: [...parent, `Item ${n}`], value: 0 }];
1241
+ function setChartLinkField(links, index, field, text2) {
1242
+ return links.map((l, i) => i === index ? { ...l, [field]: field === "value" ? text2 : text2.trim() } : { ...l });
1107
1243
  }
1108
- function removeChartTreeRow(rows, row) {
1109
- return rows.filter((_, i) => i !== row).map((r) => ({ path: [...r.path], value: r.value }));
1244
+ function addChartLink(links) {
1245
+ const last = links[links.length - 1];
1246
+ const names = new Set(links.flatMap((l) => [l.source, l.target]));
1247
+ let n = names.size + 1;
1248
+ while (names.has(`Step ${n}`)) n++;
1249
+ return [...links.map((l) => ({ ...l })), { source: last?.target ?? "Start", target: `Step ${n}`, value: last?.value ?? 1 }];
1110
1250
  }
1111
1251
 
1112
1252
  export {
@@ -1131,6 +1271,13 @@ export {
1131
1271
  chartErrorBarsOfType,
1132
1272
  formatChartAmountList,
1133
1273
  parseChartAmountList,
1274
+ MAX_CHART_LINKS,
1275
+ normalizeChartLinks,
1276
+ chartSankeyNodes,
1277
+ chartSankeyNodeValues,
1278
+ chartLinksFromTable,
1279
+ setChartLinkField,
1280
+ addChartLink,
1134
1281
  MAX_CHART_TREE_DEPTH,
1135
1282
  MAX_CHART_TREE_NODES,
1136
1283
  normalizeChartTree,
@@ -1159,6 +1306,7 @@ export {
1159
1306
  CHART_RADAR_GRATICULES,
1160
1307
  CHART_LIKERT_NEUTRALS,
1161
1308
  CHART_LIKERT_SORTS,
1309
+ CHART_SANKEY_LINK_COLORS,
1162
1310
  CHART_REFERENCE_AXES,
1163
1311
  CHART_ORIENTATIONS,
1164
1312
  CHART_LEGEND_POSITIONS,