@pixerate/editor 0.162.1 → 0.163.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 (57) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/index.cjs +34 -34
  3. package/dist/agent/index.js +5 -5
  4. package/dist/canvas/index.cjs +6 -6
  5. package/dist/canvas/index.js +2 -2
  6. package/dist/chart/commands.d.cts +2 -1
  7. package/dist/chart/commands.d.ts +2 -1
  8. package/dist/chart/controller.d.cts +109 -1
  9. package/dist/chart/controller.d.ts +109 -1
  10. package/dist/chart/index.cjs +605 -130
  11. package/dist/chart/index.d.cts +1 -0
  12. package/dist/chart/index.d.ts +1 -0
  13. package/dist/chart/index.js +490 -15
  14. package/dist/chart/layout-annotations.d.cts +29 -0
  15. package/dist/chart/layout-annotations.d.ts +29 -0
  16. package/dist/chart/layout-families.d.cts +23 -0
  17. package/dist/chart/layout-families.d.ts +23 -0
  18. package/dist/chart/mermaid.d.cts +2 -1
  19. package/dist/chart/mermaid.d.ts +2 -1
  20. package/dist/chart/schemas.d.cts +13 -0
  21. package/dist/chart/schemas.d.ts +13 -0
  22. package/dist/chart/spec.d.cts +30 -5
  23. package/dist/chart/spec.d.ts +30 -5
  24. package/dist/chart/stats.d.cts +9 -1
  25. package/dist/chart/stats.d.ts +9 -1
  26. package/dist/chart/tools.d.cts +1 -1
  27. package/dist/chart/tools.d.ts +1 -1
  28. package/dist/chart/tree.d.cts +62 -0
  29. package/dist/chart/tree.d.ts +62 -0
  30. package/dist/chart/types.d.cts +77 -3
  31. package/dist/chart/types.d.ts +77 -3
  32. package/dist/{chunk-7YBDRJXT.cjs → chunk-22KLZZGH.cjs} +8 -8
  33. package/dist/{chunk-HLOKB7WW.js → chunk-3NEZJCBY.js} +366 -6
  34. package/dist/{chunk-EVPZ5IKN.js → chunk-5FNGLV2D.js} +65 -3
  35. package/dist/{chunk-EQ46F75O.cjs → chunk-D54JHQK2.cjs} +51 -51
  36. package/dist/{chunk-NO5QQDHZ.js → chunk-DAPQGA55.js} +2 -2
  37. package/dist/{chunk-3IHNXRUV.cjs → chunk-GKNEJ6EN.cjs} +399 -67
  38. package/dist/{chunk-4NGVHHJB.cjs → chunk-NZD2MZJC.cjs} +80 -18
  39. package/dist/{chunk-CX6JLDFC.js → chunk-OXERWBKE.js} +3 -3
  40. package/dist/{chunk-7CKPUUN4.js → chunk-POR54B55.js} +61 -10
  41. package/dist/{chunk-LL3H5XUH.cjs → chunk-R3P2M7HU.cjs} +367 -7
  42. package/dist/{chunk-HDGBM7EA.cjs → chunk-SBQVEBSU.cjs} +580 -53
  43. package/dist/{chunk-XDCFJEU7.js → chunk-SZA42R4N.js} +346 -14
  44. package/dist/{chunk-GLMP22FT.js → chunk-TALQONZ5.js} +3 -3
  45. package/dist/{chunk-NA4UVVJ4.cjs → chunk-YGG7WDWT.cjs} +76 -25
  46. package/dist/{chunk-EY22AY6R.cjs → chunk-YSAGBIRW.cjs} +16 -16
  47. package/dist/{chunk-DTSTNRPH.js → chunk-Z4P2RBMJ.js} +536 -9
  48. package/dist/export/index.cjs +4 -4
  49. package/dist/export/index.js +3 -3
  50. package/dist/extensions/index.cjs +7 -7
  51. package/dist/extensions/index.js +6 -6
  52. package/dist/index.cjs +13 -13
  53. package/dist/index.js +7 -7
  54. package/dist/paint-editor/index.cjs +0 -20
  55. package/dist/paint-editor/index.js +0 -20
  56. package/package.json +1 -1
  57. package/skills/pixerate-canvas/SKILL.md +16 -0
@@ -224,7 +224,7 @@ function parseChartAmountList(text2) {
224
224
  }
225
225
 
226
226
  // src/chart/spec.ts
227
- var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap"];
227
+ var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap", "sunburst", "slope", "likert", "parliament"];
228
228
  var CHART_MARKERS = ["circle", "square", "diamond", "triangle", "cross", "none"];
229
229
  var CHART_TRENDLINE_TYPES = ["linear", "exponential", "logarithmic", "power", "polynomial", "moving-average"];
230
230
  var CHART_LABEL_POSITIONS = ["auto", "inside", "outside", "center"];
@@ -234,6 +234,9 @@ var CHART_SERIES_TYPES = ["bar", "line", "area", "scatter"];
234
234
  var CHART_STACKINGS = ["none", "stacked", "percent"];
235
235
  var CHART_CURVES = ["linear", "smooth", "step"];
236
236
  var CHART_RADAR_GRATICULES = ["polygon", "circle"];
237
+ var CHART_LIKERT_NEUTRALS = ["split", "separate"];
238
+ var CHART_LIKERT_SORTS = ["none", "positive", "negative"];
239
+ var CHART_REFERENCE_AXES = ["y", "y2", "x"];
237
240
  var CHART_ORIENTATIONS = ["vertical", "horizontal"];
238
241
  var CHART_LEGEND_POSITIONS = ["top", "bottom", "left", "right"];
239
242
  var CHART_NUMBER_STYLES = ["number", "percent", "currency", "compact"];
@@ -250,7 +253,11 @@ var CHART_TYPE_LABELS = {
250
253
  histogram: "Histogram",
251
254
  box: "Box plot",
252
255
  funnel: "Funnel",
253
- treemap: "Treemap"
256
+ treemap: "Treemap",
257
+ slope: "Slope",
258
+ likert: "Likert",
259
+ parliament: "Parliament",
260
+ sunburst: "Sunburst"
254
261
  };
255
262
  var DEFAULT_CHART_PALETTE = ["#4269d0", "#efb118", "#ff725c", "#6cc5b0", "#3ca951", "#ff8ab7", "#a463f2", "#97bbf5", "#9c6b4e", "#9498a0"];
256
263
  var DEFAULT_CHART_THEME = {
@@ -297,6 +304,10 @@ function chartFamily(spec) {
297
304
  case "box":
298
305
  case "funnel":
299
306
  case "treemap":
307
+ case "slope":
308
+ case "likert":
309
+ case "parliament":
310
+ case "sunburst":
300
311
  return spec.type;
301
312
  case "bubble":
302
313
  return "xy";
@@ -305,7 +316,10 @@ function chartFamily(spec) {
305
316
  }
306
317
  }
307
318
  function chartColorsByCategory(type) {
308
- return type === "pie" || type === "donut" || type === "funnel" || type === "treemap";
319
+ return type === "pie" || type === "donut" || type === "funnel" || type === "treemap" || type === "slope" || type === "parliament" || type === "sunburst";
320
+ }
321
+ function chartSpecColorsByCategory(spec) {
322
+ return chartColorsByCategory(spec.type) || chartVariesColors(spec);
309
323
  }
310
324
  function defaultChartAxis(value) {
311
325
  return { show: true, title: "", min: null, max: null, step: null, gridlines: value, format: {} };
@@ -316,17 +330,37 @@ function createChartSpec(partial = {}) {
316
330
  function chartSeriesColor(spec, seriesIndex) {
317
331
  const series = spec.series[seriesIndex];
318
332
  if (series?.color) return series.color;
333
+ if (spec.type === "likert") return chartLikertColor(spec.series.length, seriesIndex);
319
334
  return cyclePaletteColor(spec.palette.length ? spec.palette : DEFAULT_CHART_PALETTE, seriesIndex);
320
335
  }
336
+ var LIKERT_NEGATIVE = [["#e66101"], ["#e66101", "#fdb863"], ["#b35806", "#f1a340", "#fee0b6"], ["#b35806", "#e08214", "#fdb863", "#fee0b6"]];
337
+ var LIKERT_POSITIVE = [["#5e3c99"], ["#b2abd2", "#5e3c99"], ["#d8daeb", "#998ec3", "#542788"], ["#d8daeb", "#b2abd2", "#8073ac", "#542788"]];
338
+ var LIKERT_NEUTRAL = "#d4d4d8";
339
+ function chartLikertColor(count, index) {
340
+ const side = Math.floor(count / 2);
341
+ if (count % 2 === 1 && index === side) return LIKERT_NEUTRAL;
342
+ if (!side) return LIKERT_POSITIVE[0][0];
343
+ const ramp = (sets) => sets[Math.min(side, sets.length) - 1];
344
+ const pick2 = (list, k) => list[Math.min(list.length - 1, Math.floor(k * list.length / side))];
345
+ if (index < side) return pick2(ramp(LIKERT_NEGATIVE), index);
346
+ return pick2(ramp(LIKERT_POSITIVE), index - (count - side));
347
+ }
321
348
  function chartSliceColor(spec, index) {
322
349
  return cyclePaletteColor(spec.palette.length ? spec.palette : DEFAULT_CHART_PALETTE, index);
323
350
  }
351
+ function chartVariesColors(spec) {
352
+ return !!spec.varyColors && isCartesianChart(spec.type) && spec.series.filter((s) => !s.hidden).length === 1;
353
+ }
324
354
  function chartPointColor(spec, seriesIndex, index) {
325
- return spec.series[seriesIndex]?.pointColors?.[index] || chartSeriesColor(spec, seriesIndex);
355
+ const own = spec.series[seriesIndex]?.pointColors?.[index];
356
+ if (own) return own;
357
+ if (spec.type && chartVariesColors(spec)) return chartSliceColor(spec, index);
358
+ return chartSeriesColor(spec, seriesIndex);
326
359
  }
327
360
  function chartSeriesType(spec, series) {
328
361
  if (series.type) return series.type;
329
362
  if (spec.type === "bubble") return "scatter";
363
+ if (spec.type === "slope") return "line";
330
364
  return isCartesianChart(spec.type) ? spec.type : "bar";
331
365
  }
332
366
  function uniqueChartSeriesId(series, base = "series") {
@@ -524,6 +558,16 @@ function normalizeSeries(value, length, used, index, issues) {
524
558
  if (colors.some((c) => c !== null)) series.pointColors = colors;
525
559
  }
526
560
  }
561
+ if (value.callouts !== void 0 && value.callouts !== null) {
562
+ if (!Array.isArray(value.callouts)) issues.push({ path: `${path}.callouts`, message: `${path}.callouts must be an array of labels or nulls, one per category.` });
563
+ else {
564
+ const labels = Array.from({ length }, (_, i) => {
565
+ const c = value.callouts[i];
566
+ return typeof c === "string" && c.trim() ? c : typeof c === "number" ? String(c) : null;
567
+ });
568
+ if (labels.some((c) => c !== null)) series.callouts = labels;
569
+ }
570
+ }
527
571
  if (value.marker !== void 0 && value.marker !== null) series.marker = pick(value.marker, CHART_MARKERS, "circle", `${path}.marker`, issues);
528
572
  if (value.curve !== void 0 && value.curve !== null) series.curve = pick(value.curve, CHART_CURVES, "linear", `${path}.curve`, issues);
529
573
  if (value.dataLabels !== void 0 && value.dataLabels !== null) series.dataLabels = flag(value.dataLabels, false, `${path}.dataLabels`, issues);
@@ -546,6 +590,49 @@ function normalizeSeries(value, length, used, index, issues) {
546
590
  return series;
547
591
  }
548
592
  var key = (value, name) => value[name] !== void 0 && value[name] !== null;
593
+ function referenceCommon(value, path, issues) {
594
+ const out = {};
595
+ const label = text(value.label, "", `${path}.label`, issues);
596
+ if (label) out.label = label;
597
+ if (key(value, "axis")) {
598
+ const axis = pick(value.axis, CHART_REFERENCE_AXES, "y", `${path}.axis`, issues);
599
+ if (axis !== "y") out.axis = axis;
600
+ }
601
+ if (typeof value.color === "string" && value.color.trim()) out.color = value.color.trim();
602
+ else if (key(value, "color")) issues.push({ path: `${path}.color`, message: `${path}.color must be a CSS colour.` });
603
+ return out;
604
+ }
605
+ var finiteNumber = (value) => {
606
+ const n = typeof value === "string" && value.trim() ? Number(value) : value;
607
+ return typeof n === "number" && Number.isFinite(n) ? n : null;
608
+ };
609
+ function normalizeChartReferenceLine(value, path, issues = []) {
610
+ if (!isRecord(value)) {
611
+ issues.push({ path, message: `${path} must be an object with a value, e.g. {"value": 100, "label": "Target"}.` });
612
+ return null;
613
+ }
614
+ const at = finiteNumber(value.value);
615
+ if (at === null) {
616
+ issues.push({ path: `${path}.value`, message: `${path}.value must be a number (where the line crosses the axis).` });
617
+ return null;
618
+ }
619
+ for (const k of Object.keys(value)) if (!["value", "label", "axis", "color"].includes(k)) issues.push({ path: `${path}.${k}`, message: `${path} has no "${k}"; it takes value, label, axis and color.` });
620
+ return { value: at, ...referenceCommon(value, path, issues) };
621
+ }
622
+ function normalizeChartReferenceBand(value, path, issues = []) {
623
+ if (!isRecord(value)) {
624
+ issues.push({ path, message: `${path} must be an object with from and to, e.g. {"from": 80, "to": 120, "label": "Target range"}.` });
625
+ return null;
626
+ }
627
+ const from = finiteNumber(value.from);
628
+ const to = finiteNumber(value.to);
629
+ if (from === null || to === null) {
630
+ issues.push({ path, message: `${path} needs numbers for from and to (the ends of the band on its axis).` });
631
+ return null;
632
+ }
633
+ for (const k of Object.keys(value)) if (!["from", "to", "label", "axis", "color"].includes(k)) issues.push({ path: `${path}.${k}`, message: `${path} has no "${k}"; it takes from, to, label, axis and color.` });
634
+ return { from: Math.min(from, to), to: Math.max(from, to), ...referenceCommon(value, path, issues) };
635
+ }
549
636
  function normalizeChartErrorBars(value, length, path, issues = []) {
550
637
  if (!isRecord(value)) {
551
638
  issues.push({ path, message: `${path} must be an object with a type (${CHART_ERROR_BAR_TYPES.join(", ")}), or null for none.` });
@@ -631,8 +718,14 @@ function normalizeChartSpec(input) {
631
718
  const longest = rawSeries.reduce((n, s) => Math.max(n, isRecord(s) && Array.isArray(s.values) ? s.values.length : 0), 0);
632
719
  categories = Array.from({ length: longest }, (_, i) => `${i + 1}`);
633
720
  }
721
+ const tree = normalizeChartTree(value.tree, issues);
722
+ if (tree.length) categories = tree.map((n) => n.name);
634
723
  const used = /* @__PURE__ */ new Set();
635
724
  const series = rawSeries.map((s, i) => normalizeSeries(s, categories.length, used, i, issues)).filter((s) => s !== null);
725
+ if (tree.length) {
726
+ if (!series.length) series.push({ id: uniqueChartSeriesId(series), name: "Values", values: [] });
727
+ series[0] = { ...series[0], values: tree.map(chartTreeTotal) };
728
+ }
636
729
  let palette = [...DEFAULT_CHART_PALETTE];
637
730
  if (Array.isArray(value.palette)) {
638
731
  const colors = value.palette.filter((c) => typeof c === "string" && c.trim() !== "");
@@ -655,6 +748,32 @@ function normalizeChartSpec(input) {
655
748
  if (Number.isInteger(b) && b >= 1 && b <= 500) bins = b;
656
749
  else issues.push({ path: "bins", message: "bins must be a whole number from 1 to 500, or null for automatic." });
657
750
  }
751
+ const highlight = [];
752
+ if (Array.isArray(value.highlight)) {
753
+ for (const h of value.highlight) {
754
+ if (typeof h === "string" || typeof h === "number") {
755
+ const name = String(h);
756
+ if (!highlight.includes(name)) highlight.push(name);
757
+ } else issues.push({ path: "highlight", message: "highlight must list category labels or series names." });
758
+ }
759
+ } else if (value.highlight !== void 0 && value.highlight !== null) issues.push({ path: "highlight", message: "highlight must be an array of category labels or series names." });
760
+ const listOf = (name, read) => {
761
+ const raw = value[name];
762
+ if (raw === void 0 || raw === null) return [];
763
+ if (!Array.isArray(raw)) {
764
+ issues.push({ path: name, message: `${name} must be an array.` });
765
+ return [];
766
+ }
767
+ return raw.map((item, i) => read(item, `${name}[${i}]`, issues)).filter((x) => x !== null);
768
+ };
769
+ const referenceLines = listOf("referenceLines", normalizeChartReferenceLine);
770
+ const referenceBands = listOf("referenceBands", normalizeChartReferenceBand);
771
+ let parliamentMajority = null;
772
+ if (value.parliamentMajority !== void 0 && value.parliamentMajority !== null) {
773
+ const m = Number(value.parliamentMajority);
774
+ if (Number.isInteger(m) && m >= 1) parliamentMajority = m;
775
+ else issues.push({ path: "parliamentMajority", message: "parliamentMajority must be a whole number of seats, 1 or more, or null for half plus one." });
776
+ }
658
777
  let radarTicks = null;
659
778
  if (value.radarTicks !== void 0 && value.radarTicks !== null) {
660
779
  const t = Number(value.radarTicks);
@@ -685,7 +804,19 @@ function normalizeChartSpec(input) {
685
804
  bins,
686
805
  showTotal: flag(value.showTotal, true, "showTotal", issues),
687
806
  radarTicks,
688
- radarGraticule: pick(value.radarGraticule, CHART_RADAR_GRATICULES, "polygon", "radarGraticule", issues)
807
+ radarGraticule: pick(value.radarGraticule, CHART_RADAR_GRATICULES, "polygon", "radarGraticule", issues),
808
+ note: text(value.note, "", "note", issues),
809
+ highlight,
810
+ referenceLines,
811
+ referenceBands,
812
+ varyColors: flag(value.varyColors, false, "varyColors", issues),
813
+ likertNeutral: pick(value.likertNeutral, CHART_LIKERT_NEUTRALS, "split", "likertNeutral", issues),
814
+ likertSort: pick(value.likertSort, CHART_LIKERT_SORTS, "none", "likertSort", issues),
815
+ parliamentArc: fraction("parliamentArc", 180, 90, 360),
816
+ parliamentMajority,
817
+ parliamentLabel: text(value.parliamentLabel, "", "parliamentLabel", issues),
818
+ tree,
819
+ sunburstLabel: text(value.sunburstLabel, "", "sunburstLabel", issues)
689
820
  };
690
821
  if (spec.stacking === "percent")
691
822
  for (const name of ["yAxis", "yAxis2"]) {
@@ -705,7 +836,11 @@ function cloneChartSpec(spec) {
705
836
  yAxis: { ...spec.yAxis, format: { ...spec.yAxis.format } },
706
837
  yAxis2: { ...spec.yAxis2, format: { ...spec.yAxis2.format } },
707
838
  legend: { ...spec.legend },
708
- palette: [...spec.palette]
839
+ palette: [...spec.palette],
840
+ highlight: [...spec.highlight],
841
+ referenceLines: spec.referenceLines.map((l) => ({ ...l })),
842
+ referenceBands: spec.referenceBands.map((b) => ({ ...b })),
843
+ tree: cloneChartTree(spec.tree)
709
844
  };
710
845
  }
711
846
  function cloneChartSeries(series) {
@@ -713,6 +848,7 @@ function cloneChartSeries(series) {
713
848
  if (series.x) copy.x = [...series.x];
714
849
  if (series.sizes) copy.sizes = [...series.sizes];
715
850
  if (series.pointColors) copy.pointColors = [...series.pointColors];
851
+ if (series.callouts) copy.callouts = [...series.callouts];
716
852
  if (series.format) copy.format = { ...series.format };
717
853
  if (series.trendlines) copy.trendlines = series.trendlines.map((t) => ({ ...t }));
718
854
  for (const name of ["errorBars", "xErrorBars"]) {
@@ -773,6 +909,206 @@ function chartColorInputValue(color) {
773
909
  return "#000000";
774
910
  }
775
911
 
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") {
917
+ if (value === void 0 || value === null) return [];
918
+ 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}]}.` });
920
+ return [];
921
+ }
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
+ 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 });
997
+ }
998
+ };
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);
1010
+ }
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.` });
1014
+ return;
1015
+ }
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.` });
1019
+ }
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;
1037
+ }
1038
+ });
1039
+ return tree;
1040
+ }
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);
1062
+ }
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
+ 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
+ });
1080
+ return out;
1081
+ }
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
+ });
1097
+ }
1098
+ function setChartTreeRowValue(rows, row, value) {
1099
+ return rows.map((r, i) => ({ path: [...r.path], value: i === row ? value : r.value }));
1100
+ }
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 }];
1107
+ }
1108
+ function removeChartTreeRow(rows, row) {
1109
+ return rows.filter((_, i) => i !== row).map((r) => ({ path: [...r.path], value: r.value }));
1110
+ }
1111
+
776
1112
  export {
777
1113
  parseChartColor,
778
1114
  chartRgbToHex,
@@ -795,6 +1131,22 @@ export {
795
1131
  chartErrorBarsOfType,
796
1132
  formatChartAmountList,
797
1133
  parseChartAmountList,
1134
+ MAX_CHART_TREE_DEPTH,
1135
+ MAX_CHART_TREE_NODES,
1136
+ normalizeChartTree,
1137
+ chartTreeTotal,
1138
+ chartTreeDepth,
1139
+ findChartTreeNode,
1140
+ chartTreeToRows,
1141
+ chartTreeFromRows,
1142
+ chartTreeFromTable,
1143
+ chartTreeFromSeries,
1144
+ cloneChartTree,
1145
+ chartTreeColumnLabels,
1146
+ setChartTreeRowName,
1147
+ setChartTreeRowValue,
1148
+ addChartTreeRow,
1149
+ removeChartTreeRow,
798
1150
  CHART_TYPES,
799
1151
  CHART_MARKERS,
800
1152
  CHART_TRENDLINE_TYPES,
@@ -805,6 +1157,9 @@ export {
805
1157
  CHART_STACKINGS,
806
1158
  CHART_CURVES,
807
1159
  CHART_RADAR_GRATICULES,
1160
+ CHART_LIKERT_NEUTRALS,
1161
+ CHART_LIKERT_SORTS,
1162
+ CHART_REFERENCE_AXES,
808
1163
  CHART_ORIENTATIONS,
809
1164
  CHART_LEGEND_POSITIONS,
810
1165
  CHART_NUMBER_STYLES,
@@ -816,16 +1171,21 @@ export {
816
1171
  isXyChart,
817
1172
  chartFamily,
818
1173
  chartColorsByCategory,
1174
+ chartSpecColorsByCategory,
819
1175
  defaultChartAxis,
820
1176
  createChartSpec,
821
1177
  chartSeriesColor,
1178
+ chartLikertColor,
822
1179
  chartSliceColor,
1180
+ chartVariesColors,
823
1181
  chartPointColor,
824
1182
  chartSeriesType,
825
1183
  uniqueChartSeriesId,
826
1184
  parseChartValue,
827
1185
  normalizeChartAxis,
828
1186
  normalizeChartValues,
1187
+ normalizeChartReferenceLine,
1188
+ normalizeChartReferenceBand,
829
1189
  normalizeChartErrorBars,
830
1190
  normalizeChartTrendline,
831
1191
  normalizeChartSpec,
@@ -6,15 +6,18 @@ import {
6
6
  CHART_ERROR_BAR_TYPES,
7
7
  CHART_LABEL_POSITIONS,
8
8
  CHART_LEGEND_POSITIONS,
9
+ CHART_LIKERT_NEUTRALS,
10
+ CHART_LIKERT_SORTS,
9
11
  CHART_MARKERS,
10
12
  CHART_NUMBER_STYLES,
11
13
  CHART_ORIENTATIONS,
12
14
  CHART_RADAR_GRATICULES,
15
+ CHART_REFERENCE_AXES,
13
16
  CHART_SERIES_TYPES,
14
17
  CHART_STACKINGS,
15
18
  CHART_TRENDLINE_TYPES,
16
19
  CHART_TYPES
17
- } from "./chunk-HLOKB7WW.js";
20
+ } from "./chunk-3NEZJCBY.js";
18
21
 
19
22
  // src/chart/schemas.ts
20
23
  var VALUE = { type: ["number", "string", "null"], description: 'A number, numeric text ("1,200", "12%", "$40", "(3)") or null for a gap.' };
@@ -69,6 +72,46 @@ var TRENDLINE_PROPS = {
69
72
  name: { type: "string", description: 'Legend name (default e.g. "Linear (Sales)").' },
70
73
  color: { type: "string", description: "CSS colour (default: the series colour)." }
71
74
  };
75
+ var REFERENCE_COMMON = {
76
+ label: { type: "string", description: 'Text written on it, e.g. "Target" (default none).' },
77
+ axis: {
78
+ type: "string",
79
+ enum: [...CHART_REFERENCE_AXES],
80
+ description: "Axis it is measured on: y (default) is the value axis, y2 the secondary value axis (needs a series there), x the numeric x axis of a scatter or bubble chart."
81
+ },
82
+ color: { type: "string", description: "CSS colour (default: the text colour)." }
83
+ };
84
+ var REFERENCE_LINE_PROPS = {
85
+ value: { type: "number", description: "Where the line crosses its axis, in data units." },
86
+ ...REFERENCE_COMMON
87
+ };
88
+ var REFERENCE_BAND_PROPS = {
89
+ from: { type: "number", description: "One end of the band on its axis, in data units." },
90
+ to: { type: "number", description: "The other end of the band, in data units (ends may come in either order)." },
91
+ ...REFERENCE_COMMON
92
+ };
93
+ var REFERENCE_COMMON_PATCH = {
94
+ label: { type: ["string", "null"], description: "Text written on it; null removes it." },
95
+ axis: { type: ["string", "null"], enum: [...CHART_REFERENCE_AXES, null], description: "Axis it is measured on (y, y2 or x); null goes back to y." },
96
+ color: { type: ["string", "null"], description: "CSS colour; null goes back to the text colour." }
97
+ };
98
+ var REFERENCE_LINE_PATCH = { value: REFERENCE_LINE_PROPS.value, ...REFERENCE_COMMON_PATCH };
99
+ var REFERENCE_BAND_PATCH = { from: REFERENCE_BAND_PROPS.from, to: REFERENCE_BAND_PROPS.to, ...REFERENCE_COMMON_PATCH };
100
+ var REFERENCE_INDEX = (what) => ({ type: "integer", minimum: 0, description: `${what} index (0 = first), as get_chart lists them in spec.${what === "Reference line" ? "referenceLines" : "referenceBands"}.` });
101
+ var TREE_NODE = {
102
+ type: "object",
103
+ description: 'An item ({"name", "value"}) or a group ({"name", "children": [nodes]}, whose value is the sum of its items).',
104
+ properties: {
105
+ name: { type: "string", description: "Label; unique among its siblings." },
106
+ value: { type: "number", minimum: 0, description: "Items only: the amount, 0 or more." },
107
+ children: { type: "array", items: { $ref: "#/$defs/chartTreeNode" }, description: "Groups only: the nodes inside it (up to 6 levels in all)." },
108
+ color: { type: "string", description: "CSS colour (default: the palette colour of its top-level group, lighter further in)." }
109
+ },
110
+ required: ["name"],
111
+ additionalProperties: false
112
+ };
113
+ var TREE_DEFS = { chartTreeNode: TREE_NODE };
114
+ var TREE_PATH = { type: "array", items: { type: "string" }, minItems: 1, description: 'Names from the top level down to the node, e.g. ["Europe", "France"] (from get_chart treeTable or spec.tree).' };
72
115
  var ERROR_BARS = (what) => ({
73
116
  type: ["object", "null"],
74
117
  description: `${what} null removes them.`,
@@ -97,6 +140,7 @@ var SERIES_PROPS = {
97
140
  x: { type: ["array", "null"], items: VALUE, description: "Numeric x per category, for scatter and bubble charts." },
98
141
  sizes: { type: ["array", "null"], items: VALUE, description: "Bubble sizes per category (area is proportional to size)." },
99
142
  pointColors: { type: ["array", "null"], items: { type: ["string", "null"] }, description: "Colour per category (null keeps the series colour)." },
143
+ callouts: { type: ["array", "null"], items: { type: ["string", "null"] }, description: "A label pinned to each point that matters, one per category (null: none); null removes them all. Bar, line, area and scatter charts. set_chart_callout sets one." },
100
144
  marker: { type: ["string", "null"], enum: [...CHART_MARKERS, null], description: "Point marker shape for line, area and scatter series." },
101
145
  curve: { type: ["string", "null"], enum: [...CHART_CURVES, null], description: "Line shape for this series: linear, smooth or step." },
102
146
  dataLabels: { type: ["boolean", "null"], description: "Value labels for this series (null follows the chart)." },
@@ -105,7 +149,7 @@ var SERIES_PROPS = {
105
149
  xErrorBars: ERROR_BARS("Error bars along the x axis (scatter and bubble charts with numeric x values; amounts in x units).")
106
150
  };
107
151
  var CHART_UPDATE_PROPERTIES = {
108
- type: { type: "string", enum: [...CHART_TYPES], description: "Chart type." },
152
+ type: { type: "string", enum: [...CHART_TYPES], description: "Chart type. slope joins each category from the first series (before) to the second (after); likert reads each category as a question and the series as answers from most negative to most positive; parliament draws the first series as seats; sunburst draws the grouped data (set_chart_tree) as rings, and a treemap with grouped data nests its groups." },
109
153
  title: { type: "string", description: 'Title ("" for none).' },
110
154
  subtitle: { type: "string", description: 'Subtitle ("" for none).' },
111
155
  xAxis: AXIS("Category axis (or numeric x axis of scatter / bubble charts)."),
@@ -130,7 +174,18 @@ var CHART_UPDATE_PROPERTIES = {
130
174
  bins: { type: ["integer", "null"], minimum: 1, maximum: 500, description: "Histogram bin count, or null for \u221An." },
131
175
  showTotal: { type: "boolean", description: "Waterfall: append a total bar." },
132
176
  radarTicks: { type: ["integer", "null"], minimum: 1, maximum: 50, description: "Radar: number of evenly spaced rings from the value axis min to max, or null for round-number rings (default null)." },
133
- radarGraticule: { type: "string", enum: [...CHART_RADAR_GRATICULES], description: "Radar: draw rings as polygons through the spokes or as circles (default polygon)." }
177
+ radarGraticule: { type: "string", enum: [...CHART_RADAR_GRATICULES], description: "Radar: draw rings as polygons through the spokes or as circles (default polygon)." },
178
+ note: { type: "string", description: 'A source or footnote written under the chart ("" for none).' },
179
+ highlight: { type: "array", items: { type: "string" }, description: "Category labels and series names (or ids) to draw normally while the rest are muted; [] shows everything. highlight_chart checks the names." },
180
+ referenceLines: { type: "array", items: { type: "object", properties: REFERENCE_LINE_PROPS, required: ["value"], additionalProperties: false }, description: "Replaces every reference line (targets, floors, averages across the plot). add_chart_reference_line adds one." },
181
+ referenceBands: { type: "array", items: { type: "object", properties: REFERENCE_BAND_PROPS, required: ["from", "to"], additionalProperties: false }, description: "Replaces every reference band (target ranges across the plot). add_chart_reference_band adds one." },
182
+ varyColors: { type: "boolean", description: "A one-series bar, line, area or scatter chart colours each category from the palette, and its legend lists the categories (default false)." },
183
+ likertNeutral: { type: "string", enum: [...CHART_LIKERT_NEUTRALS], description: "Likert with an odd number of answers: split (default) puts the middle answer across the centre line; separate draws it in its own column on the right." },
184
+ likertSort: { type: "string", enum: [...CHART_LIKERT_SORTS], description: "Likert: order the questions by agreement (positive), by disagreement (negative) or as given (none, default)." },
185
+ parliamentArc: { type: "number", minimum: 90, maximum: 360, description: "Parliament: sweep of the hemicycle in degrees (default 180)." },
186
+ parliamentMajority: { type: ["integer", "null"], minimum: 1, description: "Parliament: seats needed for a majority, drawn as a dashed line (null: half of the seats plus one)." },
187
+ parliamentLabel: { type: "string", description: 'Parliament: caption under the seat total ("" writes "seats").' },
188
+ sunburstLabel: { type: "string", description: 'Sunburst: caption under the total in the centre ("" for none).' }
134
189
  };
135
190
 
136
191
  export {
@@ -140,6 +195,13 @@ export {
140
195
  WIDTH,
141
196
  HEIGHT,
142
197
  TRENDLINE_PROPS,
198
+ REFERENCE_LINE_PROPS,
199
+ REFERENCE_BAND_PROPS,
200
+ REFERENCE_LINE_PATCH,
201
+ REFERENCE_BAND_PATCH,
202
+ REFERENCE_INDEX,
203
+ TREE_DEFS,
204
+ TREE_PATH,
143
205
  SERIES_PROPS,
144
206
  CHART_UPDATE_PROPERTIES
145
207
  };