@pixerate/editor 0.46.1 → 0.48.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 (98) hide show
  1. package/THIRD_PARTY_NOTICES.md +20 -0
  2. package/dist/agent/index.cjs +252 -34
  3. package/dist/agent/index.cjs.map +1 -1
  4. package/dist/agent/index.d.cts +19 -7
  5. package/dist/agent/index.d.ts +19 -7
  6. package/dist/agent/index.js +228 -10
  7. package/dist/agent/index.js.map +1 -1
  8. package/dist/{builtins-CZW-aYKS.d.cts → builtins-1kqwLUnb.d.cts} +2 -2
  9. package/dist/{builtins-DghNQbT-.d.ts → builtins-BKYlHH1s.d.ts} +2 -2
  10. package/dist/chart/index.cjs +1596 -1849
  11. package/dist/chart/index.cjs.map +1 -1
  12. package/dist/chart/index.d.cts +760 -548
  13. package/dist/chart/index.d.ts +760 -548
  14. package/dist/chart/index.js +1499 -1752
  15. package/dist/chart/index.js.map +1 -1
  16. package/dist/{chunk-3RPOW4CS.js → chunk-2CJ3LPYQ.js} +12 -10
  17. package/dist/{chunk-3RPOW4CS.js.map → chunk-2CJ3LPYQ.js.map} +1 -1
  18. package/dist/{chunk-PQM5W7ZN.js → chunk-4FX5ACVG.js} +29 -2
  19. package/dist/{chunk-PQM5W7ZN.js.map → chunk-4FX5ACVG.js.map} +1 -1
  20. package/dist/{chunk-HHQ4XJWT.js → chunk-AMWXSYCI.js} +2 -2
  21. package/dist/chunk-EKYHKQPG.js +1895 -0
  22. package/dist/chunk-EKYHKQPG.js.map +1 -0
  23. package/dist/chunk-HXUL3LS7.js +113 -0
  24. package/dist/chunk-HXUL3LS7.js.map +1 -0
  25. package/dist/{chunk-ZRNHGAQT.js → chunk-JQ3BB3E6.js} +43 -1891
  26. package/dist/chunk-JQ3BB3E6.js.map +1 -0
  27. package/dist/chunk-LTUM52X6.js +4074 -0
  28. package/dist/chunk-LTUM52X6.js.map +1 -0
  29. package/dist/{chunk-J3R2OWOE.cjs → chunk-MUCYWI6W.cjs} +608 -15
  30. package/dist/chunk-MUCYWI6W.cjs.map +1 -0
  31. package/dist/chunk-PIMCHMZE.cjs +521 -0
  32. package/dist/chunk-PIMCHMZE.cjs.map +1 -0
  33. package/dist/{chunk-TK2XZK6Z.js → chunk-PKBWLEL7.js} +594 -1
  34. package/dist/chunk-PKBWLEL7.js.map +1 -0
  35. package/dist/chunk-PRSJJZBS.cjs +4074 -0
  36. package/dist/chunk-PRSJJZBS.cjs.map +1 -0
  37. package/dist/{chunk-EFDCLENW.cjs → chunk-RIWPYSKU.cjs} +29 -2
  38. package/dist/chunk-RIWPYSKU.cjs.map +1 -0
  39. package/dist/chunk-U6IZKC7B.cjs +113 -0
  40. package/dist/chunk-U6IZKC7B.cjs.map +1 -0
  41. package/dist/{chunk-EWCOFSCQ.cjs → chunk-UN33YOXW.cjs} +252 -2100
  42. package/dist/chunk-UN33YOXW.cjs.map +1 -0
  43. package/dist/{chunk-Q25ZS4F7.js → chunk-VKPEPWKZ.js} +72 -92
  44. package/dist/chunk-VKPEPWKZ.js.map +1 -0
  45. package/dist/{chunk-N4MLNJPO.cjs → chunk-WXEHCVJQ.cjs} +160 -180
  46. package/dist/chunk-WXEHCVJQ.cjs.map +1 -0
  47. package/dist/{chunk-K2LVZ4FA.cjs → chunk-XFYUFVAX.cjs} +3 -3
  48. package/dist/{chunk-K2LVZ4FA.cjs.map → chunk-XFYUFVAX.cjs.map} +1 -1
  49. package/dist/chunk-XP5IUZUO.cjs +1895 -0
  50. package/dist/chunk-XP5IUZUO.cjs.map +1 -0
  51. package/dist/chunk-XUUD43OV.js +521 -0
  52. package/dist/chunk-XUUD43OV.js.map +1 -0
  53. package/dist/{chunk-U7VGDSNY.cjs → chunk-YU4WQE5M.cjs} +101 -99
  54. package/dist/chunk-YU4WQE5M.cjs.map +1 -0
  55. package/dist/commands/index.cjs +93 -11
  56. package/dist/commands/index.cjs.map +1 -1
  57. package/dist/commands/index.d.cts +9 -7
  58. package/dist/commands/index.d.ts +9 -7
  59. package/dist/commands/index.js +92 -10
  60. package/dist/commands/index.js.map +1 -1
  61. package/dist/controller-ChjCCMLl.d.cts +822 -0
  62. package/dist/controller-DtbEo3om.d.ts +822 -0
  63. package/dist/extensions/index.cjs +32 -3
  64. package/dist/extensions/index.cjs.map +1 -1
  65. package/dist/extensions/index.d.cts +5 -2
  66. package/dist/extensions/index.d.ts +5 -2
  67. package/dist/extensions/index.js +31 -2
  68. package/dist/generation/index.cjs +2 -2
  69. package/dist/generation/index.js +1 -1
  70. package/dist/{index-CfOQf70b.d.cts → index-DoHqT1mq.d.cts} +222 -5
  71. package/dist/{index-jwJ_onWf.d.ts → index-YLZ0fKqE.d.ts} +222 -5
  72. package/dist/index.cjs +38 -10
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.cts +13 -5
  75. package/dist/index.d.ts +13 -5
  76. package/dist/index.js +52 -24
  77. package/dist/index.js.map +1 -1
  78. package/dist/parameters/index.d.cts +1 -1
  79. package/dist/parameters/index.d.ts +1 -1
  80. package/dist/{registry-vz6HOiiy.d.cts → registry-CaR7LC6W.d.cts} +2 -0
  81. package/dist/{registry-BfVD-vVg.d.ts → registry-rVtFJjRM.d.ts} +2 -0
  82. package/dist/spreadsheet/index.cjs +5 -3
  83. package/dist/spreadsheet/index.cjs.map +1 -1
  84. package/dist/spreadsheet/index.js +24 -22
  85. package/dist/tools/index.cjs +3 -3
  86. package/dist/tools/index.js +2 -2
  87. package/dist/vector/index.d.cts +1 -1
  88. package/dist/vector/index.d.ts +1 -1
  89. package/package.json +2 -1
  90. package/dist/chunk-EFDCLENW.cjs.map +0 -1
  91. package/dist/chunk-EWCOFSCQ.cjs.map +0 -1
  92. package/dist/chunk-J3R2OWOE.cjs.map +0 -1
  93. package/dist/chunk-N4MLNJPO.cjs.map +0 -1
  94. package/dist/chunk-Q25ZS4F7.js.map +0 -1
  95. package/dist/chunk-TK2XZK6Z.js.map +0 -1
  96. package/dist/chunk-U7VGDSNY.cjs.map +0 -1
  97. package/dist/chunk-ZRNHGAQT.js.map +0 -1
  98. /package/dist/{chunk-HHQ4XJWT.js.map → chunk-AMWXSYCI.js.map} +0 -0
@@ -1,1677 +1,257 @@
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;
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; var _class2; var _class3;
2
2
 
3
3
 
4
- var _chunkTNNJXF46cjs = require('../chunk-TNNJXF46.cjs');
5
4
 
6
5
 
7
- var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
8
-
9
- // src/chart/spec.ts
10
- var CHART_TYPES = ["bar", "line", "area", "scatter", "pie", "donut"];
11
- var CHART_SERIES_TYPES = ["bar", "line", "area", "scatter"];
12
- var CHART_STACKINGS = ["none", "stacked", "percent"];
13
- var CHART_CURVES = ["linear", "smooth", "step"];
14
- var CHART_ORIENTATIONS = ["vertical", "horizontal"];
15
- var CHART_LEGEND_POSITIONS = ["top", "bottom", "left", "right"];
16
- var CHART_NUMBER_STYLES = ["number", "percent", "currency", "compact"];
17
- var CHART_TYPE_LABELS = {
18
- bar: "Bar",
19
- line: "Line",
20
- area: "Area",
21
- scatter: "Scatter",
22
- pie: "Pie",
23
- donut: "Donut"
24
- };
25
- var DEFAULT_CHART_PALETTE = ["#4269d0", "#efb118", "#ff725c", "#6cc5b0", "#3ca951", "#ff8ab7", "#a463f2", "#97bbf5", "#9c6b4e", "#9498a0"];
26
- var DEFAULT_CHART_THEME = {
27
- background: "#ffffff",
28
- surface: "#ffffff",
29
- text: "#0f172a",
30
- mutedText: "#64748b",
31
- grid: "#e2e8f0",
32
- axis: "#94a3b8",
33
- selection: "#2563eb",
34
- fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
35
- fontSize: 11,
36
- titleSize: 15
37
- };
38
- var CSS_VARIABLE_CHART_THEME = {
39
- ...DEFAULT_CHART_THEME,
40
- background: "transparent",
41
- surface: "var(--background, #ffffff)",
42
- text: "var(--foreground, #0f172a)",
43
- mutedText: "var(--muted-foreground, #64748b)",
44
- grid: "var(--border, #e2e8f0)",
45
- axis: "var(--muted-foreground, #94a3b8)",
46
- selection: "var(--primary, #2563eb)",
47
- fontFamily: "inherit"
48
- };
49
- function isCartesianChart(type) {
50
- return type !== "pie" && type !== "donut";
51
- }
52
- function defaultChartAxis(value) {
53
- return { show: true, title: "", min: null, max: null, gridlines: value, format: {} };
54
- }
55
- function createChartSpec(partial = {}) {
56
- return normalizeChartSpec(partial).spec;
57
- }
58
- function chartSeriesColor(spec, seriesIndex) {
59
- const series = spec.series[seriesIndex];
60
- if (_optionalChain([series, 'optionalAccess', _2 => _2.color])) return series.color;
61
- const palette = spec.palette.length ? spec.palette : DEFAULT_CHART_PALETTE;
62
- return palette[seriesIndex % palette.length];
63
- }
64
- function chartSliceColor(spec, index) {
65
- const palette = spec.palette.length ? spec.palette : DEFAULT_CHART_PALETTE;
66
- return palette[index % palette.length];
67
- }
68
- function chartSeriesType(spec, series) {
69
- if (series.type) return series.type;
70
- return isCartesianChart(spec.type) ? spec.type : "bar";
71
- }
72
- function uniqueChartSeriesId(series, base = "series") {
73
- const used = new Set(series.map((s) => s.id));
74
- for (let i = series.length + 1; ; i++) {
75
- const id = `${base}-${i}`;
76
- if (!used.has(id)) return id;
77
- }
78
- }
79
- function parseChartValue(input) {
80
- if (input === null || input === void 0) return null;
81
- if (typeof input === "number") return Number.isFinite(input) ? input : void 0;
82
- if (typeof input !== "string") return void 0;
83
- let text2 = input.trim();
84
- if (!text2 || text2 === "-" || text2 === "\u2014") return null;
85
- let negative = false;
86
- if (/^\(.*\)$/.test(text2)) {
87
- negative = true;
88
- text2 = text2.slice(1, -1).trim();
89
- }
90
- const percent = text2.endsWith("%");
91
- if (percent) text2 = text2.slice(0, -1).trim();
92
- text2 = text2.replace(/^[$€£¥]/, "").replace(/,/g, "").replace(/\s+/g, "");
93
- if (!/^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(text2)) return void 0;
94
- let value = Number(text2);
95
- if (percent) value /= 100;
96
- if (negative) value = -value;
97
- return Number.isFinite(value) ? value : void 0;
98
- }
99
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
100
- function pick(value, options, fallback, path, issues) {
101
- if (value === void 0) return fallback;
102
- if (typeof value === "string" && options.includes(value)) return value;
103
- issues.push({ path, message: `${path} must be one of ${options.map((o) => `"${o}"`).join(", ")}; got ${JSON.stringify(value)}.` });
104
- return fallback;
105
- }
106
- function text(value, fallback, path, issues) {
107
- if (value === void 0 || value === null) return fallback;
108
- if (typeof value === "string") return value;
109
- if (typeof value === "number" || typeof value === "boolean") return String(value);
110
- issues.push({ path, message: `${path} must be text.` });
111
- return fallback;
112
- }
113
- function flag(value, fallback, path, issues) {
114
- if (value === void 0) return fallback;
115
- if (typeof value === "boolean") return value;
116
- issues.push({ path, message: `${path} must be true or false.` });
117
- return fallback;
118
- }
119
- function bound(value, path, issues) {
120
- if (value === void 0 || value === null || value === "") return null;
121
- const n = typeof value === "string" ? Number(value) : value;
122
- if (typeof n === "number" && Number.isFinite(n)) return n;
123
- issues.push({ path, message: `${path} must be a number or null.` });
124
- return null;
125
- }
126
- function normalizeFormat(value, path, issues) {
127
- if (value === void 0 || value === null) return {};
128
- if (!isRecord(value)) {
129
- issues.push({ path, message: `${path} must be an object.` });
130
- return {};
131
- }
132
- const out = {};
133
- if (value.style !== void 0) out.style = pick(value.style, CHART_NUMBER_STYLES, "number", `${path}.style`, issues);
134
- if (value.decimals !== void 0) {
135
- const d = Number(value.decimals);
136
- if (Number.isInteger(d) && d >= 0 && d <= 10) out.decimals = d;
137
- else issues.push({ path: `${path}.decimals`, message: `${path}.decimals must be a whole number from 0 to 10.` });
138
- }
139
- for (const key of ["currency", "prefix", "suffix"]) {
140
- if (value[key] !== void 0) out[key] = text(value[key], "", `${path}.${key}`, issues);
141
- }
142
- return out;
143
- }
144
- function normalizeChartAxis(value, fallback, path, issues = []) {
145
- if (value === void 0) return { ...fallback, format: { ...fallback.format } };
146
- if (!isRecord(value)) {
147
- issues.push({ path, message: `${path} must be an object.` });
148
- return { ...fallback, format: { ...fallback.format } };
149
- }
150
- let min = "min" in value ? bound(value.min, `${path}.min`, issues) : fallback.min;
151
- let max = "max" in value ? bound(value.max, `${path}.max`, issues) : fallback.max;
152
- if (min !== null && max !== null && min >= max) {
153
- issues.push({ path: `${path}.max`, message: `${path}.max (${max}) must be greater than ${path}.min (${min}).` });
154
- min = fallback.min;
155
- max = fallback.max;
156
- }
157
- return {
158
- show: flag(value.show, fallback.show, `${path}.show`, issues),
159
- title: text(value.title, fallback.title, `${path}.title`, issues),
160
- min,
161
- max,
162
- gridlines: flag(value.gridlines, fallback.gridlines, `${path}.gridlines`, issues),
163
- format: "format" in value ? normalizeFormat(value.format, `${path}.format`, issues) : { ...fallback.format }
164
- };
165
- }
166
- function normalizeLegend(value, issues) {
167
- const fallback = { show: true, position: "bottom" };
168
- if (value === void 0) return fallback;
169
- if (!isRecord(value)) {
170
- issues.push({ path: "legend", message: "legend must be an object." });
171
- return fallback;
172
- }
173
- return {
174
- show: flag(value.show, fallback.show, "legend.show", issues),
175
- position: pick(value.position, CHART_LEGEND_POSITIONS, fallback.position, "legend.position", issues)
176
- };
177
- }
178
- function normalizeChartValues(value, length, path, issues = []) {
179
- if (value === void 0) return length ? Array(length).fill(null) : [];
180
- if (!Array.isArray(value)) {
181
- issues.push({ path, message: `${path} must be an array of numbers.` });
182
- return length ? Array(length).fill(null) : [];
183
- }
184
- const out = value.map((v, i) => {
185
- const parsed = parseChartValue(v);
186
- if (parsed === void 0) {
187
- issues.push({ path: `${path}[${i}]`, message: `${path}[${i}] is not a number: ${JSON.stringify(v)}.` });
188
- return null;
189
- }
190
- return parsed;
191
- });
192
- if (length === void 0) return out;
193
- if (out.length > length) return out.slice(0, length);
194
- while (out.length < length) out.push(null);
195
- return out;
196
- }
197
- function normalizeSeries(value, length, used, index, issues) {
198
- const path = `series[${index}]`;
199
- if (!isRecord(value)) {
200
- issues.push({ path, message: `${path} must be an object with a name and values.` });
201
- return null;
202
- }
203
- let id = typeof value.id === "string" && value.id.trim() ? value.id.trim() : "";
204
- if (!id || used.has(id)) {
205
- if (id) issues.push({ path: `${path}.id`, message: `Series id "${id}" is used twice; the second was renamed.` });
206
- for (let n = index + 1; ; n++) {
207
- id = `series-${n}`;
208
- if (!used.has(id)) break;
209
- }
210
- }
211
- used.add(id);
212
- const series = {
213
- id,
214
- name: text(value.name, `Series ${index + 1}`, `${path}.name`, issues),
215
- values: normalizeChartValues(value.values, length, `${path}.values`, issues)
216
- };
217
- if (typeof value.color === "string" && value.color.trim()) series.color = value.color.trim();
218
- if (value.type !== void 0 && value.type !== null) series.type = pick(value.type, CHART_SERIES_TYPES, "bar", `${path}.type`, issues);
219
- if (value.hidden === true) series.hidden = true;
220
- return series;
221
- }
222
- function normalizeChartSpec(input) {
223
- const issues = [];
224
- const value = isRecord(input) ? input : {};
225
- if (input !== void 0 && !isRecord(input)) issues.push({ path: "", message: "A chart must be an object." });
226
- const type = pick(value.type, CHART_TYPES, "bar", "type", issues);
227
- const rawSeries = Array.isArray(value.series) ? value.series : [];
228
- if (value.series !== void 0 && !Array.isArray(value.series)) issues.push({ path: "series", message: "series must be an array." });
229
- let categories;
230
- if (Array.isArray(value.categories)) categories = value.categories.map((c, i) => text(c, "", `categories[${i}]`, issues));
231
- else {
232
- if (value.categories !== void 0) issues.push({ path: "categories", message: "categories must be an array of labels." });
233
- const longest = rawSeries.reduce((n, s) => Math.max(n, isRecord(s) && Array.isArray(s.values) ? s.values.length : 0), 0);
234
- categories = Array.from({ length: longest }, (_, i) => `${i + 1}`);
235
- }
236
- const used = /* @__PURE__ */ new Set();
237
- const series = rawSeries.map((s, i) => normalizeSeries(s, categories.length, used, i, issues)).filter((s) => s !== null);
238
- let palette = [...DEFAULT_CHART_PALETTE];
239
- if (Array.isArray(value.palette)) {
240
- const colors = value.palette.filter((c) => typeof c === "string" && c.trim() !== "");
241
- if (colors.length) palette = colors;
242
- else issues.push({ path: "palette", message: "palette must list at least one colour." });
243
- } else if (value.palette !== void 0) issues.push({ path: "palette", message: "palette must be an array of colours." });
244
- let innerRadius = 0.55;
245
- if (value.innerRadius !== void 0) {
246
- const r = Number(value.innerRadius);
247
- if (Number.isFinite(r) && r >= 0 && r <= 0.9) innerRadius = r;
248
- else issues.push({ path: "innerRadius", message: "innerRadius must be a number from 0 to 0.9." });
249
- }
250
- const spec = {
251
- version: 1,
252
- type,
253
- title: text(value.title, "", "title", issues),
254
- subtitle: text(value.subtitle, "", "subtitle", issues),
255
- categories,
256
- series,
257
- xAxis: normalizeChartAxis(value.xAxis, defaultChartAxis(false), "xAxis", issues),
258
- yAxis: normalizeChartAxis(value.yAxis, defaultChartAxis(true), "yAxis", issues),
259
- legend: normalizeLegend(value.legend, issues),
260
- stacking: pick(value.stacking, CHART_STACKINGS, "none", "stacking", issues),
261
- orientation: pick(value.orientation, CHART_ORIENTATIONS, "vertical", "orientation", issues),
262
- curve: pick(value.curve, CHART_CURVES, "linear", "curve", issues),
263
- points: flag(value.points, true, "points", issues),
264
- dataLabels: flag(value.dataLabels, false, "dataLabels", issues),
265
- palette,
266
- innerRadius
267
- };
268
- return { spec, issues };
269
- }
270
- function cloneChartSpec(spec) {
271
- return {
272
- ...spec,
273
- categories: [...spec.categories],
274
- series: spec.series.map(cloneChartSeries),
275
- xAxis: { ...spec.xAxis, format: { ...spec.xAxis.format } },
276
- yAxis: { ...spec.yAxis, format: { ...spec.yAxis.format } },
277
- legend: { ...spec.legend },
278
- palette: [...spec.palette]
279
- };
280
- }
281
- function cloneChartSeries(series) {
282
- return { ...series, values: [...series.values] };
283
- }
284
- function chartDataFromTable(rows, options = {}) {
285
- const header = _nullishCoalesce(options.header, () => ( true));
286
- let grid = rows.map((r) => [...r]).filter((r) => r.some((c) => c !== null && c !== void 0 && String(c).trim() !== ""));
287
- if (options.seriesIn === "rows") {
288
- const width2 = Math.max(0, ...grid.map((r) => r.length));
289
- grid = Array.from({ length: width2 }, (_, c) => grid.map((r) => r[c]));
290
- }
291
- const issues = [];
292
- if (!grid.length) return { categories: [], series: [], issues };
293
- const width = Math.max(...grid.map((r) => r.length));
294
- const names = header ? grid[0] : [];
295
- const body = header ? grid.slice(1) : grid;
296
- const categories = body.map((r, i) => {
297
- const c = r[0];
298
- return c === null || c === void 0 || String(c).trim() === "" ? `${i + 1}` : String(c).trim();
299
- });
300
- const series = [];
301
- for (let col = 1; col < width; col++) {
302
- const name = names[col] !== void 0 && String(names[col]).trim() ? String(names[col]).trim() : `Series ${col}`;
303
- const values = body.map((r, i) => {
304
- const parsed = parseChartValue(_nullishCoalesce(r[col], () => ( null)));
305
- if (parsed === void 0) {
306
- issues.push({ path: `${name}[${i}]`, message: `"${String(r[col])}" in ${name} (${categories[i]}) is not a number; it was left blank.` });
307
- return null;
308
- }
309
- return parsed;
310
- });
311
- series.push({ id: `series-${col}`, name, values });
312
- }
313
- return { categories, series, issues };
314
- }
315
- function chartDataToTable(spec, options = {}) {
316
- return [
317
- [_nullishCoalesce(options.categoryHeader, () => ( "")), ...spec.series.map((s) => s.name)],
318
- ...spec.categories.map((category, i) => [category, ...spec.series.map((s) => _nullishCoalesce(s.values[i], () => ( null)))])
319
- ];
320
- }
321
- function chartColorInputValue(color) {
322
- if (/^#[0-9a-f]{6}$/i.test(color)) return color.toLowerCase();
323
- if (/^#[0-9a-f]{3}$/i.test(color)) return `#${[...color.slice(1)].map((c) => c + c).join("")}`.toLowerCase();
324
- return "#000000";
325
- }
326
6
 
327
- // src/chart/scale.ts
328
- function niceStep(span, count) {
329
- if (!(span > 0) || !(count > 0)) return 1;
330
- const raw = span / count;
331
- const power = Math.pow(10, Math.floor(Math.log10(raw)));
332
- const fraction = raw / power;
333
- const nice = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 2.5 ? 2.5 : fraction <= 5 ? 5 : 10;
334
- return nice * power;
335
- }
336
- var clean = (n) => Math.abs(n) < 1e-12 ? 0 : Number(n.toPrecision(12));
337
- function niceScale(min, max, count = 5, fixedMin = null, fixedMax = null) {
338
- if (fixedMin !== null && !Number.isFinite(fixedMin)) fixedMin = null;
339
- if (fixedMax !== null && !Number.isFinite(fixedMax)) fixedMax = null;
340
- let lo = _nullishCoalesce(fixedMin, () => ( min));
341
- let hi = _nullishCoalesce(fixedMax, () => ( max));
342
- if (!Number.isFinite(lo) || !Number.isFinite(hi)) {
343
- fixedMin = fixedMax = null;
344
- lo = 0;
345
- hi = 1;
346
- }
347
- if (lo > hi) [lo, hi] = [hi, lo];
348
- if (lo === hi) {
349
- const pad = lo === 0 ? 1 : Math.abs(lo) * 0.1;
350
- if (fixedMin === null && lo !== 0) lo -= pad;
351
- if (fixedMax === null || lo === hi) hi += pad;
352
- }
353
- const span = hi - lo;
354
- const step = Number.isFinite(span) ? niceStep(span, count) : niceStep(hi / 2 - lo / 2, count) * 2;
355
- const finiteOr = (n2, fallback) => Number.isFinite(n2) ? n2 : fallback;
356
- const niceMin = _nullishCoalesce(fixedMin, () => ( finiteOr(snap(Math.floor(lo / step + 1e-9) * step, step), lo)));
357
- const niceMax = _nullishCoalesce(fixedMax, () => ( finiteOr(snap(Math.ceil(hi / step - 1e-9) * step, step), hi)));
358
- const firstIndex = Math.ceil(niceMin / step - 1e-9);
359
- const lastIndex = Math.floor(niceMax / step + 1e-9);
360
- const n = Math.min(999, Math.max(-1, lastIndex - firstIndex));
361
- const ticks = [];
362
- for (let i = 0; i <= n; i++) {
363
- const t = snap((firstIndex + i) * step, step);
364
- if (Number.isFinite(t) && t !== ticks[ticks.length - 1]) ticks.push(t);
365
- }
366
- return { min: niceMin, max: niceMax, step, ticks };
367
- }
368
- function snap(n, step) {
369
- const c = clean(n);
370
- return Math.abs(c - n) <= step * 1e-6 ? c : n;
371
- }
372
- function stepDecimals(step) {
373
- if (!(step > 0) || !Number.isFinite(step)) return 0;
374
- for (let d = 0; d < 6; d++) {
375
- const scaled = step * Math.pow(10, d);
376
- if (Math.abs(scaled - Math.round(scaled)) < 1e-9) return d;
377
- }
378
- return 6;
379
- }
380
- function magnitudeDecimals(value) {
381
- if (!(value > 0) || value >= 1) return 0;
382
- return Math.min(6, Math.max(0, Math.ceil(-Math.log10(value) - 1e-9)));
383
- }
384
- var COMPACT = [
385
- { value: 1e12, suffix: "T" },
386
- { value: 1e9, suffix: "B" },
387
- { value: 1e6, suffix: "M" },
388
- { value: 1e3, suffix: "K" }
389
- ];
390
- function grouped(value, decimals) {
391
- const fixed = Math.abs(value).toFixed(decimals);
392
- const [int, frac] = fixed.split(".");
393
- const withCommas = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
394
- return `${value < 0 && Number(fixed) !== 0 ? "-" : ""}${withCommas}${frac ? `.${frac}` : ""}`;
395
- }
396
- var CURRENCY_SYMBOLS = { USD: "$", EUR: "\u20AC", GBP: "\xA3", JPY: "\xA5", CNY: "\xA5", INR: "\u20B9", AUD: "A$", CAD: "C$" };
397
- function formatChartNumber(value, format = {}, fallbackDecimals) {
398
- if (value === null || value === void 0 || !Number.isFinite(value)) return "";
399
- const style = _nullishCoalesce(format.style, () => ( "number"));
400
- let body;
401
- const decimalsFor = (v) => {
402
- if (format.decimals !== void 0) return format.decimals;
403
- if (fallbackDecimals !== void 0) return fallbackDecimals;
404
- return Number.isInteger(v) ? 0 : Math.abs(v) < 1 ? Math.min(4, Math.max(2, magnitudeDecimals(Math.abs(v)) + 1)) : 2;
405
- };
406
- const trim = (s) => format.decimals === void 0 && fallbackDecimals === void 0 && s.includes(".") ? s.replace(/\.?0+$/, "") : s;
407
- if (style === "percent") {
408
- const v = value * 100;
409
- const d = _nullishCoalesce(format.decimals, () => ( (fallbackDecimals !== void 0 ? Math.max(0, fallbackDecimals - 2) : Number.isInteger(clean(v)) ? 0 : 1)));
410
- body = `${grouped(clean(v), d)}%`;
411
- } else if (style === "compact") {
412
- const unit = COMPACT.find((u) => Math.abs(value) >= u.value);
413
- if (unit) {
414
- const v = value / unit.value;
415
- const d = _nullishCoalesce(format.decimals, () => ( (Math.abs(v) >= 100 || Number.isInteger(clean(v)) ? 0 : 1)));
416
- body = `${trim(grouped(v, d))}${unit.suffix}`;
417
- } else body = trim(grouped(value, decimalsFor(value)));
418
- } else if (style === "currency") {
419
- const code = (format.currency || "USD").toUpperCase();
420
- const symbol = _nullishCoalesce(CURRENCY_SYMBOLS[code], () => ( `${code} `));
421
- const d = _nullishCoalesce(format.decimals, () => ( (fallbackDecimals !== void 0 ? fallbackDecimals : Number.isInteger(value) ? 0 : 2)));
422
- const s = grouped(Math.abs(value), d);
423
- body = `${value < 0 ? "-" : ""}${symbol}${s}`;
424
- } else body = trim(grouped(value, decimalsFor(value)));
425
- return `${_nullishCoalesce(format.prefix, () => ( ""))}${body}${_nullishCoalesce(format.suffix, () => ( ""))}`;
426
- }
427
- function linearMap(value, domain, range) {
428
- const [d0, d1] = domain;
429
- const [r0, r1] = range;
430
- if (d1 === d0) return (r0 + r1) / 2;
431
- return r0 + (value - d0) / (d1 - d0) * (r1 - r0);
432
- }
433
7
 
434
- // src/chart/layout.ts
435
- var estimateChartTextWidth = (text2, size, weight = 400) => text2.length * size * (weight >= 600 ? 0.6 : 0.56);
436
- function sameChartSelection(a, b) {
437
- if (!a || !b || a.kind !== b.kind) return false;
438
- switch (a.kind) {
439
- case "axis":
440
- return a.axis === b.axis;
441
- case "series":
442
- return a.seriesId === b.seriesId;
443
- case "point":
444
- return a.seriesId === b.seriesId && a.index === b.index;
445
- case "category":
446
- return a.index === b.index;
447
- default:
448
- return true;
449
- }
450
- }
451
- function contrastingTextColor(fill) {
452
- let rgb = null;
453
- const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(fill.trim());
454
- if (hex) {
455
- const h = hex[1].length === 3 ? [...hex[1]].map((c) => c + c).join("") : hex[1];
456
- rgb = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
457
- } else {
458
- const fn = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(fill.trim());
459
- if (fn) rgb = fn.slice(1, 4).map(Number);
460
- }
461
- if (!rgb) return "#ffffff";
462
- const luminance = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;
463
- return luminance > 0.5 ? "#0f172a" : "#ffffff";
464
- }
465
- var r2 = (n) => Math.round(n * 100) / 100;
466
- function curvePath(points, curve, map, moveFirst = true) {
467
- if (!points.length) return "";
468
- const xy = points.map(map);
469
- let d = `${moveFirst ? "M" : "L"}${r2(xy[0][0])},${r2(xy[0][1])}`;
470
- if (points.length === 1) return d;
471
- if (curve === "step") {
472
- for (let i = 1; i < points.length; i++) {
473
- const mid = (points[i - 1].c + points[i].c) / 2;
474
- const a = map({ c: mid, v: points[i - 1].v });
475
- const b = map({ c: mid, v: points[i].v });
476
- d += `L${r2(a[0])},${r2(a[1])}L${r2(b[0])},${r2(b[1])}L${r2(xy[i][0])},${r2(xy[i][1])}`;
477
- }
478
- return d;
479
- }
480
- if (curve === "smooth" && points.length > 2) {
481
- const n = points.length;
482
- const dc = points.slice(1).map((p, i) => p.c - points[i].c);
483
- const slope = points.slice(1).map((p, i) => dc[i] === 0 ? 0 : (p.v - points[i].v) / dc[i]);
484
- const tangent = points.map((_, i) => {
485
- if (i === 0) return slope[0];
486
- if (i === n - 1) return slope[n - 2];
487
- const a = slope[i - 1];
488
- const b = slope[i];
489
- if (a * b <= 0) return 0;
490
- const wa = 2 * dc[i] + dc[i - 1];
491
- const wb = dc[i] + 2 * dc[i - 1];
492
- return (wa + wb) / (wa / a + wb / b);
493
- });
494
- for (let i = 1; i < n; i++) {
495
- const h = dc[i - 1] / 3;
496
- const p0 = points[i - 1];
497
- const p1 = points[i];
498
- const a = map({ c: p0.c + h, v: p0.v + tangent[i - 1] * h });
499
- const b = map({ c: p1.c - h, v: p1.v - tangent[i] * h });
500
- d += `C${r2(a[0])},${r2(a[1])} ${r2(b[0])},${r2(b[1])} ${r2(xy[i][0])},${r2(xy[i][1])}`;
501
- }
502
- return d;
503
- }
504
- for (let i = 1; i < xy.length; i++) d += `L${r2(xy[i][0])},${r2(xy[i][1])}`;
505
- return d;
506
- }
507
- function arcPath(cx, cy, outer, inner, start, end) {
508
- const full = end - start >= Math.PI * 2 - 1e-9;
509
- if (full) end = start + Math.PI * 2 - 1e-4;
510
- const p = (r, a) => `${r2(cx + r * Math.sin(a))},${r2(cy - r * Math.cos(a))}`;
511
- const large = end - start > Math.PI ? 1 : 0;
512
- let d = `M${p(outer, start)}A${r2(outer)},${r2(outer)} 0 ${large} 1 ${p(outer, end)}`;
513
- if (inner > 0) d += `L${p(inner, end)}A${r2(inner)},${r2(inner)} 0 ${large} 0 ${p(inner, start)}Z`;
514
- else d += `L${r2(cx)},${r2(cy)}Z`;
515
- return d;
516
- }
517
- function layoutChart(spec, options) {
518
- const width = Math.max(1, options.width);
519
- const height = Math.max(1, options.height);
520
- const theme = { ...DEFAULT_CHART_THEME, ...options.theme };
521
- const measure = _nullishCoalesce(options.measureText, () => ( estimateChartTextWidth));
522
- const selection = _nullishCoalesce(options.selection, () => ( null));
523
- const fs = theme.fontSize;
524
- const marks = [];
525
- const isSelected = (target) => sameChartSelection(selection, target);
526
- const seriesSelected = (id) => _optionalChain([selection, 'optionalAccess', _3 => _3.kind]) === "series" && selection.seriesId === id;
527
- const pad = 12;
528
- marks.push({ kind: "rect", key: "background", role: "background", x: 0, y: 0, width, height, fill: theme.background, target: { kind: "chart" } });
529
- let top = pad;
530
- const outline = (key, box) => marks.push({ kind: "rect", key, role: "selection", ...box, fill: "none", stroke: theme.selection, strokeWidth: 1, radius: 3 });
531
- const titleMark = (kind, label, size, weight, fill) => {
532
- const target = { kind };
533
- const selected = isSelected(target);
534
- marks.push({ kind: "text", key: kind, role: kind, x: width / 2, y: top, text: label, anchor: "middle", baseline: "top", size, weight, fill, target, selected });
535
- if (selected) {
536
- const w = measure(label, size, weight);
537
- outline(`${kind}-selection`, { x: width / 2 - w / 2 - 4, y: top - 3, width: w + 8, height: size * 1.25 + 6 });
538
- }
539
- top += size * 1.35;
540
- };
541
- if (spec.title) titleMark("title", spec.title, theme.titleSize, 600, theme.text);
542
- if (spec.subtitle) titleMark("subtitle", spec.subtitle, fs + 1, 400, theme.mutedText);
543
- if (spec.title || spec.subtitle) top += 6;
544
- const cartesian = isCartesianChart(spec.type);
545
- const visible = spec.series.filter((s) => !s.hidden);
546
- const legendItems = cartesian ? spec.series.map((s, i) => ({
547
- label: s.name,
548
- color: chartSeriesColor(spec, i),
549
- target: { kind: "series", seriesId: s.id },
550
- faded: !!s.hidden,
551
- selected: seriesSelected(s.id),
552
- shape: chartSeriesType(spec, s) === "line" ? "line" : "square"
553
- })) : spec.categories.map((c, i) => ({
554
- label: c,
555
- color: chartSliceColor(spec, i),
556
- target: { kind: "category", index: i },
557
- faded: false,
558
- selected: isSelected({ kind: "category", index: i }),
559
- shape: "square"
560
- }));
561
- let area = { x: pad, y: top, width: width - pad * 2, height: height - top - pad };
562
- const showLegend = spec.legend.show && legendItems.length > 0 && (!cartesian || spec.series.length > 1 || spec.series.some((s) => s.hidden));
563
- if (showLegend) area = layoutLegend(legendItems, spec.legend.position, area, theme, measure, marks, isSelected({ kind: "legend" }));
564
- const empty = (message) => {
565
- marks.push({ kind: "text", key: "empty", role: "empty", x: area.x + area.width / 2, y: area.y + area.height / 2, text: message, anchor: "middle", baseline: "middle", size: fs + 1, fill: theme.mutedText });
566
- return { width, height, plot: { x: area.x, y: area.y, width: 0, height: 0 }, marks, percent: false, theme };
567
- };
568
- const hasData = visible.some((s) => s.values.some((v) => v !== null));
569
- if (!spec.categories.length || !visible.length || !hasData) return empty(_nullishCoalesce(options.emptyMessage, () => ( "No data")));
570
- if (!cartesian) return layoutPie(spec, visible[0], area, theme, marks, selection, width, height);
571
- const horizontal = spec.orientation === "horizontal";
572
- const n = spec.categories.length;
573
- const types = new Map(visible.map((s) => [s.id, chartSeriesType(spec, s)]));
574
- const stacked = spec.stacking !== "none";
575
- const percent = spec.stacking === "percent";
576
- const stacks = /* @__PURE__ */ new Map();
577
- if (stacked) {
578
- for (const kind of ["bar", "area"]) {
579
- const group = visible.filter((s) => types.get(s.id) === kind);
580
- if (!group.length) continue;
581
- const totals = Array.from({ length: n }, (_, i) => group.reduce((t, s) => t + Math.abs(_nullishCoalesce(s.values[i], () => ( 0))), 0));
582
- const pos = Array(n).fill(0);
583
- const neg = Array(n).fill(0);
584
- for (const s of group) {
585
- const segments = s.values.map((raw, i) => {
586
- if (raw === null && kind === "bar") return null;
587
- let v = _nullishCoalesce(raw, () => ( 0));
588
- if (percent) v = totals[i] ? v / totals[i] : 0;
589
- const base = v >= 0 ? pos[i] : neg[i];
590
- const seg = [base, base + v];
591
- if (v >= 0) pos[i] += v;
592
- else neg[i] += v;
593
- return seg;
594
- });
595
- stacks.set(s.id, { segments });
596
- }
8
+
9
+
10
+ var _chunkU6IZKC7Bcjs = require('../chunk-U6IZKC7B.cjs');
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+ var _chunkPIMCHMZEcjs = require('../chunk-PIMCHMZE.cjs');
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+ var _chunkPRSJJZBScjs = require('../chunk-PRSJJZBS.cjs');
123
+ require('../chunk-XP5IUZUO.cjs');
124
+
125
+
126
+ var _chunkTNNJXF46cjs = require('../chunk-TNNJXF46.cjs');
127
+ require('../chunk-QKLRAA4T.cjs');
128
+
129
+ // src/chart/recommend.ts
130
+ var blank = (c) => c === null || c === void 0 || String(c).trim() === "";
131
+ var isText = (c) => blank(c) || _chunkPRSJJZBScjs.parseChartValue.call(void 0, c) === void 0;
132
+ function analyzeChartTable(rows) {
133
+ const grid = rows.filter((r) => r.some((c) => !blank(c)));
134
+ const height = grid.length;
135
+ const width = Math.max(0, ...grid.map((r) => r.length));
136
+ const cell = (r, c) => _optionalChain([grid, 'access', _2 => _2[r], 'optionalAccess', _3 => _3[c]]);
137
+ const headerRow = height > 1 && grid[0].some((c) => !blank(c) && isText(c)) && Array.from({ length: width - 1 }, (_, i) => i + 1).every((c) => isText(cell(0, c)));
138
+ const blankCorner = headerRow && blank(cell(0, 0));
139
+ const labelColumn = width > 1 && (blankCorner || Array.from({ length: height - (headerRow ? 1 : 0) }, (_, i) => i + (headerRow ? 1 : 0)).every((r) => isText(cell(r, 0))));
140
+ const byRows = width > height + 1;
141
+ const dataRows = height - (headerRow ? 1 : 0);
142
+ const dataCols = width - (labelColumn ? 1 : 0);
143
+ let anyNumber = false;
144
+ let nonNegative = true;
145
+ let anyNegative = false;
146
+ let anyPositive = false;
147
+ for (let r = headerRow ? 1 : 0; r < height; r++)
148
+ for (let c = labelColumn ? 1 : 0; c < width; c++) {
149
+ const v = _chunkPRSJJZBScjs.parseChartValue.call(void 0, cell(r, c));
150
+ if (typeof v !== "number") continue;
151
+ anyNumber = true;
152
+ if (v < 0) {
153
+ nonNegative = false;
154
+ anyNegative = true;
155
+ } else if (v > 0) anyPositive = true;
597
156
  }
598
- }
599
- let lo = Infinity;
600
- let hi = -Infinity;
601
- const see = (v) => {
602
- lo = Math.min(lo, v);
603
- hi = Math.max(hi, v);
604
- };
605
- for (const s of visible) {
606
- const stack = stacks.get(s.id);
607
- if (stack) stack.segments.forEach((seg) => seg && (see(seg[0]), see(seg[1])));
608
- else s.values.forEach((v) => v !== null && see(v));
609
- }
610
- if (!Number.isFinite(lo)) {
611
- lo = 0;
612
- hi = 1;
613
- }
614
- if ([...types.values()].some((t) => t === "bar" || t === "area")) {
615
- lo = Math.min(lo, 0);
616
- hi = Math.max(hi, 0);
617
- }
618
- const valueFormat = percent && !spec.yAxis.format.style ? { ...spec.yAxis.format, style: "percent" } : spec.yAxis.format;
619
- const xAxis = spec.xAxis;
620
- const yAxis = spec.yAxis;
621
- const titleBand = fs * 1.3 + 6;
622
- const frozen = options.valueDomain;
623
- const fixedMin = frozen && Number.isFinite(frozen.min) ? frozen.min : yAxis.min;
624
- const fixedMax = frozen && Number.isFinite(frozen.max) ? frozen.max : yAxis.max;
625
- const valueLen0 = horizontal ? area.width : area.height;
626
- let scale = niceScale(lo, hi, Math.max(2, Math.min(10, Math.round(valueLen0 / 56))), fixedMin, fixedMax);
627
- const tickText = (t) => formatChartNumber(t, valueFormat, _nullishCoalesce(valueFormat.decimals, () => ( stepDecimals(scale.step))));
628
- const valueLabelWidth = Math.max(0, ...scale.ticks.map((t) => measure(tickText(t), fs)));
629
- const catLabelWidth = Math.max(0, ...spec.categories.map((c) => measure(c, fs)));
630
- let left;
631
- let bottom;
632
- let rotate = 0;
633
- if (horizontal) {
634
- left = (xAxis.show ? Math.min(catLabelWidth, area.width * 0.35) + 8 : 2) + (xAxis.title ? titleBand : 0);
635
- bottom = (yAxis.show ? fs * 1.6 : 2) + (yAxis.title ? titleBand : 0);
636
- } else {
637
- left = (yAxis.show ? valueLabelWidth + 8 : 2) + (yAxis.title ? titleBand : 0);
638
- const band2 = (area.width - left - 8) / n;
639
- if (xAxis.show && catLabelWidth > band2 * 0.92) rotate = -40;
640
- const catHeight = rotate ? Math.min(catLabelWidth * Math.sin(Math.PI / 4.5) + fs, area.height * 0.4) : fs * 1.6;
641
- bottom = (xAxis.show ? catHeight : 2) + (xAxis.title ? titleBand : 0);
642
- }
643
- const rightPad = horizontal ? Math.max(8, valueLabelWidth / 2) : 8;
644
- const plot = {
645
- x: area.x + left,
646
- y: area.y + (horizontal ? 4 : fs / 2 + 2),
647
- width: Math.max(1, area.width - left - rightPad),
648
- height: Math.max(1, area.height - bottom - (horizontal ? 4 : fs / 2 + 2))
157
+ const labels = byRows ? _nullishCoalesce(_optionalChain([grid, 'access', _4 => _4[0], 'optionalAccess', _5 => _5.slice, 'call', _6 => _6(labelColumn ? 1 : 0)]), () => ( [])) : grid.slice(headerRow ? 1 : 0).map((r) => r[0]);
158
+ const dates = (byRows ? headerRow : labelColumn) && labels.length > 1 && labels.filter((l) => !blank(l) && _chunkPRSJJZBScjs.parseChartDate.call(void 0, String(l)) !== null).length >= labels.length * 0.8;
159
+ return {
160
+ headerRow,
161
+ labelColumn,
162
+ byRows,
163
+ series: Math.max(0, byRows ? dataRows : dataCols),
164
+ points: Math.max(0, byRows ? dataCols : dataRows),
165
+ anyNumber,
166
+ nonNegative,
167
+ mixedSigns: anyNegative && anyPositive,
168
+ dates
649
169
  };
650
- scale = niceScale(lo, hi, Math.max(2, Math.min(10, Math.round((horizontal ? plot.width : plot.height) / 56))), fixedMin, fixedMax);
651
- const domain = [scale.min, scale.max];
652
- const valueRange = horizontal ? [plot.x, plot.x + plot.width] : [plot.y + plot.height, plot.y];
653
- const valuePx = (v) => linearMap(Math.min(scale.max, Math.max(scale.min, v)), domain, valueRange);
654
- const catStart = horizontal ? plot.y : plot.x;
655
- const catLen = horizontal ? plot.height : plot.width;
656
- const band = catLen / n;
657
- const catCenter = (i) => catStart + band * (i + 0.5);
658
- const map = (p) => horizontal ? [p.v, p.c] : [p.c, p.v];
659
- const zero = valuePx(Math.min(scale.max, Math.max(scale.min, 0)));
660
- const axisSelected = (axis) => isSelected({ kind: "axis", axis });
661
- if (yAxis.gridlines) {
662
- scale.ticks.forEach((t, i) => {
663
- const p = valuePx(t);
664
- const [x1, y1] = map({ c: catStart, v: p });
665
- const [x2, y2] = map({ c: catStart + catLen, v: p });
666
- marks.push({ kind: "line", key: `grid-y-${i}`, role: "grid", x1, y1, x2, y2, stroke: theme.grid, strokeWidth: 1 });
667
- });
668
- }
669
- if (xAxis.gridlines) {
670
- for (let i = 1; i < n; i++) {
671
- const c = catStart + band * i;
672
- const [x1, y1] = map({ c, v: valueRange[0] });
673
- const [x2, y2] = map({ c, v: valueRange[1] });
674
- marks.push({ kind: "line", key: `grid-x-${i}`, role: "grid", x1, y1, x2, y2, stroke: theme.grid, strokeWidth: 1 });
675
- }
676
- }
677
- const yTarget = { kind: "axis", axis: "y" };
678
- const xTarget = { kind: "axis", axis: "x" };
679
- if (yAxis.show) {
680
- scale.ticks.forEach((t, i) => {
681
- const p = valuePx(t);
682
- const label = tickText(t);
683
- if (horizontal)
684
- marks.push({ kind: "text", key: `tick-y-${i}`, role: "tick-label", x: p, y: plot.y + plot.height + 6, text: label, anchor: "middle", baseline: "top", size: fs, fill: theme.mutedText, target: yTarget });
685
- else marks.push({ kind: "text", key: `tick-y-${i}`, role: "tick-label", x: plot.x - 8, y: p, text: label, anchor: "end", baseline: "middle", size: fs, fill: theme.mutedText, target: yTarget });
686
- });
687
- }
688
- if (xAxis.show) {
689
- const [x1, y1] = map({ c: catStart, v: zero });
690
- const [x2, y2] = map({ c: catStart + catLen, v: zero });
691
- marks.push({ kind: "line", key: "axis-x", role: "axis", x1, y1, x2, y2, stroke: theme.axis, strokeWidth: 1, target: xTarget });
692
- const every = horizontal ? Math.max(1, Math.ceil(fs * 1.3 / band)) : rotate ? Math.max(1, Math.ceil(fs * 1.4 / band)) : 1;
693
- spec.categories.forEach((c, i) => {
694
- if (i % every) return;
695
- const target = { kind: "category", index: i };
696
- const selected = isSelected(target);
697
- const fill = selected ? theme.selection : theme.mutedText;
698
- if (horizontal) {
699
- const maxW = left - (xAxis.title ? titleBand : 0) - 8;
700
- marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: plot.x - 8, y: catCenter(i), text: truncate(c, maxW, fs, measure), anchor: "end", baseline: "middle", size: fs, fill, target, selected });
701
- } else if (rotate)
702
- marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: catCenter(i), y: plot.y + plot.height + 8, text: c, anchor: "end", baseline: "middle", size: fs, fill, rotate, target, selected });
703
- else marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: catCenter(i), y: plot.y + plot.height + 6, text: c, anchor: "middle", baseline: "top", size: fs, fill, target, selected });
704
- });
705
- }
706
- const bottomTitle = horizontal ? yAxis.title : xAxis.title;
707
- const leftTitle = horizontal ? xAxis.title : yAxis.title;
708
- if (bottomTitle)
709
- marks.push({ kind: "text", key: `axis-title-${horizontal ? "y" : "x"}`, role: "axis-title", x: plot.x + plot.width / 2, y: area.y + area.height, text: bottomTitle, anchor: "middle", baseline: "bottom", size: fs + 1, weight: 500, fill: theme.text, target: horizontal ? yTarget : xTarget });
710
- if (leftTitle)
711
- marks.push({ kind: "text", key: `axis-title-${horizontal ? "x" : "y"}`, role: "axis-title", x: area.x, y: plot.y + plot.height / 2, text: leftTitle, anchor: "middle", baseline: "top", size: fs + 1, weight: 500, fill: theme.text, rotate: -90, target: horizontal ? xTarget : yTarget });
712
- for (const axis of ["x", "y"]) {
713
- if (!axisSelected(axis)) continue;
714
- const onLeft = axis === "y" !== horizontal;
715
- const box = onLeft ? { x: area.x, y: plot.y - fs / 2, width: left - 2, height: plot.height + fs } : { x: plot.x - 4, y: plot.y + plot.height + 2, width: plot.width + 8, height: area.y + area.height - plot.y - plot.height - 2 };
716
- outline(`axis-${axis}-selection`, box);
717
- }
718
- const bars = visible.filter((s) => types.get(s.id) === "bar");
719
- const slots = stacked ? 1 : bars.length;
720
- const groupWidth = band * (bars.length ? 0.72 : 0);
721
- const barWidth = slots ? groupWidth / slots : 0;
722
- const labelMarks = [];
723
- const decimals = _nullishCoalesce(valueFormat.decimals, () => ( void 0));
724
- const dataLabel = (v) => formatChartNumber(v, valueFormat, decimals);
725
- const drawOrder = [...visible].sort((a, b) => order(types.get(a.id)) - order(types.get(b.id)));
726
- for (const s of drawOrder) {
727
- const si = spec.series.indexOf(s);
728
- const color = chartSeriesColor(spec, si);
729
- const type = types.get(s.id);
730
- const stack = stacks.get(s.id);
731
- const selSeries = seriesSelected(s.id);
732
- const seriesTarget = { kind: "series", seriesId: s.id };
733
- if (type === "bar") {
734
- const slot = stacked ? 0 : bars.indexOf(s);
735
- s.values.forEach((raw, i) => {
736
- const seg = stack ? stack.segments[i] : raw === null ? null : [0, raw];
737
- if (!seg || raw === null) return;
738
- const a = valuePx(seg[0]);
739
- const b = valuePx(seg[1]);
740
- const c0 = catCenter(i) - groupWidth / 2 + slot * barWidth + barWidth * 0.06;
741
- const cw = barWidth * 0.88;
742
- const target = { kind: "point", seriesId: s.id, index: i };
743
- const selected = isSelected(target);
744
- const [x, y] = map({ c: c0, v: Math.min(a, b) });
745
- const rect = horizontal ? { x: Math.min(a, b), y: c0, width: Math.abs(b - a), height: cw } : { x, y, width: cw, height: Math.abs(b - a) };
746
- marks.push({
747
- kind: "rect",
748
- key: `bar-${s.id}-${i}`,
749
- role: "bar",
750
- ...rect,
751
- fill: color,
752
- radius: Math.min(2, cw / 4),
753
- target,
754
- selected: selected || selSeries,
755
- ...selSeries ? { stroke: theme.selection, strokeWidth: 1.5 } : {}
756
- });
757
- if (selected) outline(`bar-${s.id}-${i}-selection`, { x: rect.x - 2, y: rect.y - 2, width: rect.width + 4, height: rect.height + 4 });
758
- if (spec.dataLabels) {
759
- const v = percent ? seg[1] - seg[0] : raw;
760
- const text2 = dataLabel(v);
761
- if (stacked && Math.abs(b - a) < (horizontal ? measure(text2, fs - 1) + 4 : fs + 1)) return;
762
- const end = valuePx(seg[1]);
763
- const outward = (seg[1] >= seg[0] ? 1 : -1) * (horizontal ? 1 : -1);
764
- const [lx, ly] = map({ c: c0 + cw / 2, v: stacked ? (a + b) / 2 : end + outward * 4 });
765
- labelMarks.push({
766
- kind: "text",
767
- key: `label-${s.id}-${i}`,
768
- role: "data-label",
769
- x: lx,
770
- y: ly,
771
- text: text2,
772
- anchor: horizontal && !stacked ? outward > 0 ? "start" : "end" : "middle",
773
- baseline: horizontal || stacked ? "middle" : outward < 0 ? "bottom" : "top",
774
- size: fs - 1,
775
- fill: stacked ? contrastingTextColor(color) : theme.text,
776
- target
777
- });
778
- }
779
- });
780
- continue;
781
- }
782
- const pointValue = (i) => stack ? _nullishCoalesce(_optionalChain([stack, 'access', _4 => _4.segments, 'access', _5 => _5[i], 'optionalAccess', _6 => _6[1]]), () => ( null)) : s.values[i];
783
- const runs = [];
784
- let run = [];
785
- for (let i = 0; i < n; i++) {
786
- const v = pointValue(i);
787
- if (v === null) {
788
- if (run.length) runs.push(run);
789
- run = [];
790
- continue;
791
- }
792
- run.push({ c: catCenter(i), v: valuePx(v) });
793
- }
794
- if (run.length) runs.push(run);
795
- if (type === "area") {
796
- const parts = runs.map((r) => {
797
- const baseline = stack ? r.map((p) => ({ c: p.c, v: valuePx(_nullishCoalesce(_optionalChain([stack, 'access', _7 => _7.segments, 'access', _8 => _8[Math.round((p.c - catStart) / band - 0.5)], 'optionalAccess', _9 => _9[0]]), () => ( 0))) })).reverse() : [
798
- { c: r[r.length - 1].c, v: zero },
799
- { c: r[0].c, v: zero }
800
- ];
801
- return `${curvePath(r, spec.curve, map)}${stack ? curvePath(baseline, spec.curve, map, false) : baseline.map((p) => `L${map(p).map(r2).join(",")}`).join("")}Z`;
802
- });
803
- marks.push({ kind: "path", key: `area-${s.id}`, role: "area", d: parts.join(""), fill: color, opacity: 0.22, target: seriesTarget, selected: selSeries });
804
- }
805
- if (type === "line" || type === "area") {
806
- marks.push({
807
- kind: "path",
808
- key: `line-${s.id}`,
809
- role: "line",
810
- d: runs.map((r) => curvePath(r, spec.curve, map)).join(""),
811
- fill: "none",
812
- stroke: color,
813
- strokeWidth: selSeries ? 3.5 : 2,
814
- target: seriesTarget,
815
- selected: selSeries
816
- });
817
- }
818
- const showDots = type === "scatter" || spec.points;
819
- for (let i = 0; i < n; i++) {
820
- const v = pointValue(i);
821
- if (v === null || s.values[i] === null) continue;
822
- const [cx, cy] = map({ c: catCenter(i), v: valuePx(v) });
823
- const target = { kind: "point", seriesId: s.id, index: i };
824
- const selected = isSelected(target);
825
- const r = type === "scatter" ? 4.5 : 3.5;
826
- marks.push({
827
- kind: "circle",
828
- key: `point-${s.id}-${i}`,
829
- role: "point",
830
- cx,
831
- cy,
832
- r: showDots ? r : 6,
833
- fill: showDots ? type === "scatter" ? color : theme.surface : "transparent",
834
- ...showDots ? { stroke: color, strokeWidth: 2 } : {},
835
- opacity: type === "scatter" ? 0.85 : void 0,
836
- target,
837
- selected: selected || selSeries
838
- });
839
- if (selected) marks.push({ kind: "circle", key: `point-${s.id}-${i}-selection`, role: "selection", cx, cy, r: r + 3.5, fill: "none", stroke: theme.selection, strokeWidth: 1.5 });
840
- if (spec.dataLabels) {
841
- const raw = s.values[i];
842
- const shown = percent && stack ? stack.segments[i][1] - stack.segments[i][0] : raw;
843
- labelMarks.push({
844
- kind: "text",
845
- key: `label-${s.id}-${i}`,
846
- role: "data-label",
847
- x: horizontal ? cx + 8 : cx,
848
- y: horizontal ? cy : cy - 8,
849
- text: dataLabel(shown),
850
- anchor: horizontal ? "start" : "middle",
851
- baseline: horizontal ? "middle" : "bottom",
852
- size: fs - 1,
853
- fill: theme.text,
854
- target
855
- });
856
- }
857
- }
858
- }
859
- marks.push(...labelMarks);
860
- const valueScale = { axis: horizontal ? "x" : "y", domain, range: valueRange, ticks: scale.ticks };
861
- return { width, height, plot, marks, valueScale, percent, theme };
862
170
  }
863
- var order = (t) => t === "area" ? 0 : t === "bar" ? 1 : t === "line" ? 2 : 3;
864
- function truncate(text2, maxWidth, size, measure) {
865
- if (measure(text2, size) <= maxWidth) return text2;
866
- let out = text2;
867
- while (out.length > 1 && measure(`${out}\u2026`, size) > maxWidth) out = out.slice(0, -1);
868
- return `${out}\u2026`;
171
+ function recommendChartTypes(rows) {
172
+ const shape = analyzeChartTable(rows);
173
+ if (!shape.anyNumber || !shape.series) return [];
174
+ return recommendForShape(shape);
869
175
  }
870
- function layoutLegend(items, position, area, theme, measure, marks, legendSelected) {
871
- const fs = theme.fontSize;
872
- const swatch = 10;
873
- const rowH = fs * 1.7;
874
- const itemW = (item) => swatch + 6 + measure(item.label, fs) + 14;
875
- const placed = [];
876
- let box;
877
- let rest;
878
- if (position === "left" || position === "right") {
879
- const w = Math.min(area.width * 0.35, Math.max(...items.map(itemW)));
880
- const h = Math.min(items.length * rowH, area.height);
881
- const x = position === "left" ? area.x : area.x + area.width - w;
882
- const y = area.y + (area.height - h) / 2;
883
- items.forEach((item, i) => {
884
- if ((i + 1) * rowH <= area.height + 0.5) placed.push({ item, x, y: y + i * rowH + rowH / 2 });
885
- });
886
- box = { x, y, width: w, height: h };
887
- rest = position === "left" ? { ...area, x: area.x + w + 8, width: area.width - w - 8 } : { ...area, width: area.width - w - 8 };
888
- } else {
889
- const rows = [[]];
890
- let used = 0;
891
- for (const item of items) {
892
- const w = itemW(item);
893
- if (used + w > area.width && rows[rows.length - 1].length) {
894
- rows.push([]);
895
- used = 0;
896
- }
897
- rows[rows.length - 1].push(item);
898
- used += w;
899
- }
900
- const shown = rows.slice(0, 3);
901
- const h = shown.length * rowH;
902
- const y0 = position === "top" ? area.y : area.y + area.height - h;
903
- shown.forEach((row, r) => {
904
- const total = row.reduce((t, item) => t + itemW(item), 0) - 14;
905
- let x = area.x + (area.width - total) / 2;
906
- for (const item of row) {
907
- placed.push({ item, x, y: y0 + r * rowH + rowH / 2 });
908
- x += itemW(item);
176
+ function recommendForShape(shape) {
177
+ const out = [];
178
+ const add = (type, label, reason, stacking) => out.push({ type, label, reason, ...stacking ? { stacking } : {} });
179
+ if (shape.series === 2 && !shape.labelColumn && !shape.byRows) {
180
+ add("scatter", "Scatter", "Two numeric columns without labels: the first is x, the second y.");
181
+ add("line", "Line", "Shows the second column in order.");
182
+ add("bar", "Column", "Compares the values side by side.");
183
+ return out;
184
+ }
185
+ if (shape.series === 1 && !shape.labelColumn && shape.points > 20) {
186
+ add("histogram", "Histogram", "Many unlabelled values: shows how they are distributed.");
187
+ add("box", "Box plot", "Summarises the spread: quartiles, median and outliers.");
188
+ add("line", "Line", "Shows the values in order.");
189
+ return out;
190
+ }
191
+ if (shape.dates) {
192
+ add("line", "Line", "The categories are dates: a line shows change over time.");
193
+ add("area", "Area", "Shows change over time with volume.");
194
+ add("bar", "Column", "Compares each period.");
195
+ if (shape.series > 1) add("area", "Stacked area", "Shows how the parts add up over time.", "stacked");
196
+ return out;
197
+ }
198
+ if (shape.series === 1) {
199
+ if (shape.points > 12) {
200
+ add("line", "Line", "One long series: a line shows its shape.");
201
+ add("bar", "Column", "Compares every value.");
202
+ add("area", "Area", "Emphasises the volume under the series.");
203
+ } else {
204
+ add("bar", "Column", "One short series: columns compare the values.");
205
+ if (shape.mixedSigns) add("waterfall", "Waterfall", "Positive and negative values: shows how they add up to a total.");
206
+ add("bar", "Bar", "Horizontal bars fit long category names.");
207
+ if (shape.nonNegative && shape.points >= 2) {
208
+ add("pie", "Pie", "Shares of a whole (all values are positive).");
209
+ add("donut", "Donut", "Shares of a whole with the total in the middle.");
210
+ if (shape.points >= 5) add("treemap", "Treemap", "Compares many shares by area.");
211
+ add("funnel", "Funnel", "Shows stages that narrow down.");
909
212
  }
910
- });
911
- box = { x: area.x, y: y0, width: area.width, height: h };
912
- rest = position === "top" ? { ...area, y: area.y + h + 6, height: area.height - h - 6 } : { ...area, height: area.height - h - 6 };
913
- }
914
- placed.forEach(({ item, x, y }, i) => {
915
- const opacity = item.faded ? 0.4 : void 0;
916
- if (item.shape === "line")
917
- marks.push({ kind: "line", key: `legend-swatch-${i}`, role: "legend-swatch", x1: x, y1: y, x2: x + swatch, y2: y, stroke: item.color, strokeWidth: 2.5, opacity, target: item.target, selected: item.selected });
918
- else
919
- marks.push({ kind: "rect", key: `legend-swatch-${i}`, role: "legend-swatch", x, y: y - swatch / 2, width: swatch, height: swatch, radius: 2, fill: item.color, opacity, target: item.target, selected: item.selected });
920
- marks.push({
921
- kind: "text",
922
- key: `legend-label-${i}`,
923
- role: "legend-label",
924
- x: x + swatch + 6,
925
- y,
926
- text: item.label,
927
- anchor: "start",
928
- baseline: "middle",
929
- size: fs,
930
- fill: item.selected ? theme.selection : theme.text,
931
- weight: item.selected ? 600 : void 0,
932
- opacity,
933
- target: item.target,
934
- selected: item.selected
935
- });
936
- });
937
- if (legendSelected) marks.push({ kind: "rect", key: "legend-selection", role: "selection", x: box.x - 4, y: box.y - 2, width: box.width + 8, height: box.height + 4, fill: "none", stroke: theme.selection, strokeWidth: 1, radius: 3 });
938
- return rest;
939
- }
940
- function layoutPie(spec, series, area, theme, marks, selection, width, height) {
941
- const fs = theme.fontSize;
942
- const values = spec.categories.map((_, i) => Math.max(0, _nullishCoalesce(series.values[i], () => ( 0))));
943
- const total = values.reduce((a, b) => a + b, 0);
944
- const labelRoom = spec.dataLabels ? fs * 2.2 : 4;
945
- const outer = Math.max(4, Math.min(area.width, area.height) / 2 - labelRoom);
946
- const inner = spec.type === "donut" ? outer * spec.innerRadius : 0;
947
- const cx = area.x + area.width / 2;
948
- const cy = area.y + area.height / 2;
949
- const plot = { x: cx - outer, y: cy - outer, width: outer * 2, height: outer * 2 };
950
- if (total <= 0) {
951
- marks.push({ kind: "text", key: "empty", role: "empty", x: cx, y: cy, text: "No positive values", anchor: "middle", baseline: "middle", size: fs + 1, fill: theme.mutedText });
952
- return { width, height, plot, marks, percent: false, theme };
953
- }
954
- let angle = 0;
955
- const format = spec.yAxis.format;
956
- values.forEach((v, i) => {
957
- if (v <= 0) return;
958
- const sweep = v / total * Math.PI * 2;
959
- const start = angle;
960
- const end = angle + sweep;
961
- angle = end;
962
- const target = { kind: "point", seriesId: series.id, index: i };
963
- const selected = sameChartSelection(selection, target) || sameChartSelection(selection, { kind: "category", index: i });
964
- const seriesSel = _optionalChain([selection, 'optionalAccess', _10 => _10.kind]) === "series" && selection.seriesId === series.id;
965
- const mid = (start + end) / 2;
966
- const off = selected ? 6 : 0;
967
- const ox = cx + off * Math.sin(mid);
968
- const oy = cy - off * Math.cos(mid);
969
- marks.push({
970
- kind: "path",
971
- key: `slice-${i}`,
972
- role: "slice",
973
- d: arcPath(ox, oy, outer, inner, start, end),
974
- fill: chartSliceColor(spec, i),
975
- stroke: selected || seriesSel ? theme.selection : theme.surface,
976
- strokeWidth: selected || seriesSel ? 2 : 1,
977
- target,
978
- selected: selected || seriesSel
979
- });
980
- if (spec.dataLabels && sweep > 0.12) {
981
- const share = v / total;
982
- const text2 = format.style ? formatChartNumber(v, format) : `${Math.round(share * 100)}%`;
983
- const lr = outer + fs * 1.1;
984
- const lx = ox + lr * Math.sin(mid);
985
- const ly = oy - lr * Math.cos(mid);
986
- const anchor = Math.abs(Math.sin(mid)) < 0.2 ? "middle" : Math.sin(mid) > 0 ? "start" : "end";
987
- marks.push({ kind: "text", key: `label-slice-${i}`, role: "data-label", x: lx, y: ly, text: text2, anchor, baseline: "middle", size: fs, fill: theme.text, target });
213
+ add("line", "Line", "Shows the values in order.");
988
214
  }
989
- });
990
- if (spec.type === "donut" && inner > fs * 2) {
991
- marks.push({ kind: "text", key: "donut-total", role: "data-label", x: cx, y: cy, text: formatChartNumber(total, format), anchor: "middle", baseline: "middle", size: Math.min(theme.titleSize + 4, inner / 2), weight: 600, fill: theme.text, target: { kind: "series", seriesId: series.id } });
215
+ return out;
992
216
  }
993
- return { width, height, plot, marks, percent: false, theme };
994
- }
995
- function chartValueAtPoint(scene, point) {
996
- const scale = scene.valueScale;
997
- if (!scale || scene.percent) return void 0;
998
- const px = scale.axis === "y" ? point.y : point.x;
999
- return linearMap(px, scale.range, scale.domain);
1000
- }
1001
- function snapChartValue(scene, value, step) {
1002
- const ticks = _nullishCoalesce(_optionalChain([scene, 'access', _11 => _11.valueScale, 'optionalAccess', _12 => _12.ticks]), () => ( []));
1003
- const unit = _nullishCoalesce(step, () => ( (ticks.length > 1 ? (ticks[1] - ticks[0]) / 10 : 1)));
1004
- if (!(unit > 0)) return value;
1005
- const snapped = Math.round(value / unit) * unit;
1006
- return Number(snapped.toPrecision(12));
1007
- }
1008
-
1009
- // src/chart/svg.ts
1010
- function chartTextBaseline(baseline) {
1011
- return baseline === "top" ? "hanging" : baseline === "middle" ? "central" : "text-after-edge";
1012
- }
1013
- function chartTextTransform(mark) {
1014
- return mark.rotate ? `rotate(${mark.rotate} ${mark.x} ${mark.y})` : void 0;
1015
- }
1016
- var escape = (text2) => text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1017
- function attrs(values) {
1018
- return Object.entries(values).filter(([, v]) => v !== void 0 && v !== "").map(([k, v]) => ` ${k}="${escape(String(v))}"`).join("");
1019
- }
1020
- function markToSvg(mark, fontFamily) {
1021
- const common = { opacity: mark.opacity, "data-role": mark.role };
1022
- switch (mark.kind) {
1023
- case "rect":
1024
- return `<rect${attrs({ x: mark.x, y: mark.y, width: mark.width, height: mark.height, rx: mark.radius, fill: mark.fill, stroke: mark.stroke, "stroke-width": mark.strokeWidth, ...common })}/>`;
1025
- case "path":
1026
- return `<path${attrs({ d: mark.d, fill: _nullishCoalesce(mark.fill, () => ( "none")), stroke: mark.stroke, "stroke-width": mark.strokeWidth, "stroke-dasharray": mark.dash, "stroke-linejoin": "round", "stroke-linecap": "round", ...common })}/>`;
1027
- case "circle":
1028
- return `<circle${attrs({ cx: mark.cx, cy: mark.cy, r: mark.r, fill: mark.fill, stroke: mark.stroke, "stroke-width": mark.strokeWidth, ...common })}/>`;
1029
- case "line":
1030
- return `<line${attrs({ x1: mark.x1, y1: mark.y1, x2: mark.x2, y2: mark.y2, stroke: mark.stroke, "stroke-width": mark.strokeWidth, "stroke-dasharray": mark.dash, ...common })}/>`;
1031
- case "text":
1032
- return `<text${attrs({
1033
- x: mark.x,
1034
- y: mark.y,
1035
- "text-anchor": mark.anchor,
1036
- "dominant-baseline": chartTextBaseline(mark.baseline),
1037
- "font-size": mark.size,
1038
- "font-weight": mark.weight,
1039
- "font-family": fontFamily,
1040
- fill: mark.fill,
1041
- transform: chartTextTransform(mark),
1042
- ...common
1043
- })}>${escape(mark.text)}</text>`;
1044
- }
1045
- }
1046
- function chartSceneToSvg(scene, options = {}) {
1047
- const hide = _nullishCoalesce(options.hideSelection, () => ( true));
1048
- const font = scene.theme.fontFamily;
1049
- const body = scene.marks.filter((m) => !(hide && m.role === "selection")).map((m) => markToSvg(m, font)).join("");
1050
- const label = options.ariaLabel;
1051
- 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)}"` : ""}>${body}</svg>`;
1052
- }
1053
- function chartToSvg(spec, options) {
1054
- const scene = layoutChart(spec, { ...options, selection: null });
1055
- return chartSceneToSvg(scene, { ...options, ariaLabel: _nullishCoalesce(options.ariaLabel, () => ( (spec.title || void 0))) });
1056
- }
1057
-
1058
- // src/chart/controller.ts
1059
- function sameChartJson(a, b) {
1060
- if (a === b) return true;
1061
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return Number.isNaN(a) && Number.isNaN(b);
1062
- if (Array.isArray(a) !== Array.isArray(b)) return false;
1063
- if (Array.isArray(a)) {
1064
- const bb = b;
1065
- return a.length === bb.length && a.every((v, i) => sameChartJson(v, bb[i]));
1066
- }
1067
- const ao = a;
1068
- const bo = b;
1069
- const keys = Object.keys(ao).filter((k) => ao[k] !== void 0);
1070
- if (keys.length !== Object.keys(bo).filter((k) => bo[k] !== void 0).length) return false;
1071
- return keys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && sameChartJson(ao[k], bo[k]));
1072
- }
1073
- var fail = (message, issues) => ({ ok: false, message, ...issues ? { issues } : {} });
1074
- var ChartController = (_class = class {
1075
-
1076
-
1077
- __init() {this.selection = null}
1078
- __init2() {this.issues = []}
1079
- __init3() {this.gestureKey = null}
1080
- __init4() {this.gestureCount = 0}
1081
- __init5() {this.gestureStart = null}
1082
- __init6() {this.revision = 0}
1083
- __init7() {this.applying = false}
1084
-
1085
- __init8() {this.listeners = /* @__PURE__ */ new Set()}
1086
-
1087
-
1088
- 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);
1089
- this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
1090
- this.onChange = options.onChange;
1091
- const { spec, issues } = normalizeChartSpec(_nullishCoalesce(options.spec, () => ( {})));
1092
- this.spec = spec;
1093
- this.issues = issues;
1094
- this.state = this.computeState();
1095
- this.connect();
1096
- }
1097
- connect() {
1098
- _optionalChain([this, 'access', _13 => _13.unsubscribeHistory, 'optionalCall', _14 => _14()]);
1099
- this.unsubscribeHistory = this.history.subscribe((h) => {
1100
- const s = this.state;
1101
- if (this.applying || s.canUndo === h.canUndo && s.canRedo === h.canRedo && s.undoLabel === h.undoName && s.redoLabel === h.redoName) return;
1102
- this.notify(false);
1103
- });
1104
- }
1105
- // -------------------------------------------------------------------------
1106
- // State
1107
- // -------------------------------------------------------------------------
1108
- /** The current snapshot (stable until the next change). */
1109
- __init9() {this.getState = () => this.state}
1110
- /** Calls `listener` after every change. Returns an unsubscribe function. */
1111
- __init10() {this.subscribe = (listener) => {
1112
- if (!this.unsubscribeHistory) this.connect();
1113
- this.listeners.add(listener);
1114
- return () => {
1115
- this.listeners.delete(listener);
1116
- };
1117
- }}
1118
- /** The current spec (do not mutate; edit through the controller). */
1119
- getSpec() {
1120
- return this.spec;
1121
- }
1122
- getSeries(id) {
1123
- return this.spec.series.find((s) => s.id === id);
1124
- }
1125
- computeState() {
1126
- const h = this.history.getState();
1127
- const sel = this.selection;
1128
- const seriesId = _optionalChain([sel, 'optionalAccess', _15 => _15.kind]) === "series" || _optionalChain([sel, 'optionalAccess', _16 => _16.kind]) === "point" ? sel.seriesId : null;
1129
- return {
1130
- spec: this.spec,
1131
- selection: sel,
1132
- selectedSeries: seriesId ? _nullishCoalesce(this.spec.series.find((s) => s.id === seriesId), () => ( null)) : null,
1133
- selectedIndex: _optionalChain([sel, 'optionalAccess', _17 => _17.kind]) === "point" || _optionalChain([sel, 'optionalAccess', _18 => _18.kind]) === "category" ? sel.index : null,
1134
- issues: this.issues,
1135
- gesture: this.gestureKey,
1136
- canUndo: h.canUndo,
1137
- canRedo: h.canRedo,
1138
- undoLabel: h.undoName,
1139
- redoLabel: h.redoName,
1140
- revision: this.revision
1141
- };
1142
- }
1143
- notify(specChanged) {
1144
- this.revision++;
1145
- this.state = this.computeState();
1146
- if (specChanged) _optionalChain([this, 'access', _19 => _19.onChange, 'optionalCall', _20 => _20(this.spec)]);
1147
- const state = this.state;
1148
- this.listeners.forEach((listener) => {
1149
- try {
1150
- listener(state);
1151
- } catch (error) {
1152
- console.error("[ChartController] Error in state listener:", error);
1153
- }
1154
- });
1155
- }
1156
- // -------------------------------------------------------------------------
1157
- // Changes: every edit records a before / after snapshot of the spec
1158
- // -------------------------------------------------------------------------
1159
- /** Drops a selection that no longer points at anything. */
1160
- validSelection(sel, spec) {
1161
- if (!sel) return null;
1162
- switch (sel.kind) {
1163
- case "series":
1164
- return spec.series.some((s) => s.id === sel.seriesId) ? sel : null;
1165
- case "point":
1166
- return spec.series.some((s) => s.id === sel.seriesId) && sel.index < spec.categories.length ? sel : null;
1167
- case "category":
1168
- return sel.index < spec.categories.length ? sel : null;
1169
- case "title":
1170
- return spec.title ? sel : null;
1171
- case "subtitle":
1172
- return spec.subtitle ? sel : null;
1173
- default:
1174
- return sel;
1175
- }
1176
- }
1177
- applySpec(spec) {
1178
- this.applying = true;
1179
- try {
1180
- this.spec = spec;
1181
- this.selection = this.validSelection(this.selection, spec);
1182
- } finally {
1183
- this.applying = false;
1184
- }
1185
- this.notify(true);
1186
- }
1187
- commit(label, next, select) {
1188
- const before = this.spec;
1189
- const hadIssues = this.issues.length > 0;
1190
- this.issues = [];
1191
- if (sameChartJson(before, next)) {
1192
- const reselect = select !== void 0 && !sameChartSelection(this.selection, select) && !(select === null && this.selection === null);
1193
- if (reselect) this.selection = select;
1194
- if (reselect || hadIssues) this.notify(false);
1195
- return;
1196
- }
1197
- const command = {
1198
- name: label,
1199
- tag: "chart",
1200
- execute: () => this.applySpec(next),
1201
- undo: () => this.applySpec(before),
1202
- redo: () => this.applySpec(next)
1203
- };
1204
- if (select !== void 0) this.selection = select;
1205
- if (this.gestureKey !== null) this.history.executeMerged(command, { key: `chart.gesture:${this.gestureKey}:${this.gestureCount}` });
1206
- else this.history.execute(command);
1207
- }
1208
- reject(message, issues = []) {
1209
- this.issues = issues.length ? issues : [{ path: "", message }];
1210
- this.notify(false);
1211
- return fail(message, issues.length ? issues : void 0);
1212
- }
1213
- /** A copy of the spec with `edit` applied (series and categories copied as needed). */
1214
- edited(edit) {
1215
- const draft = cloneChartSpec(this.spec);
1216
- edit(draft);
1217
- return draft;
1218
- }
1219
- unknownSeries(id) {
1220
- const ids = this.spec.series.map((s) => `"${s.id}"`);
1221
- return fail(`There is no series "${id}"; ${ids.length ? `expected one of ${ids.join(", ")}` : "the chart has no series yet"}.`);
1222
- }
1223
- badIndex(index) {
1224
- const n = this.spec.categories.length;
1225
- return fail(n ? `Category index ${index} is out of range; expected 0 to ${n - 1}.` : "The chart has no categories yet.");
1226
- }
1227
- // -------------------------------------------------------------------------
1228
- // Presentation
1229
- // -------------------------------------------------------------------------
1230
- /**
1231
- * Changes any presentation or data fields as one undo step. All-or-nothing: if anything
1232
- * is invalid, nothing changes and every problem is returned (and shown in `state.issues`).
1233
- */
1234
- update(patch, options = {}) {
1235
- const s = this.spec;
1236
- const merged = {
1237
- ...s,
1238
- ...patch,
1239
- xAxis: patch.xAxis ? { ...s.xAxis, ...patch.xAxis } : s.xAxis,
1240
- yAxis: patch.yAxis ? { ...s.yAxis, ...patch.yAxis } : s.yAxis,
1241
- legend: patch.legend ? { ...s.legend, ...patch.legend } : s.legend,
1242
- version: 1
1243
- };
1244
- const { spec, issues } = normalizeChartSpec(merged);
1245
- if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1246
- if (JSON.stringify(spec) === JSON.stringify(s)) return { ok: true };
1247
- this.commit(_nullishCoalesce(options.label, () => ( updateLabel(patch))), spec);
1248
- return { ok: true };
1249
- }
1250
- setType(type) {
1251
- return this.update({ type }, { label: `Change chart type` });
1252
- }
1253
- setTitle(title) {
1254
- return this.update({ title }, { label: "Edit title" });
1255
- }
1256
- setSubtitle(subtitle) {
1257
- return this.update({ subtitle }, { label: "Edit subtitle" });
1258
- }
1259
- /** Changes the category (`x`) or value (`y`) axis. */
1260
- setAxis(axis, patch) {
1261
- return this.update(axis === "x" ? { xAxis: patch } : { yAxis: patch }, { label: `Change ${axis === "x" ? "category" : "value"} axis` });
1262
- }
1263
- setLegend(patch) {
1264
- return this.update({ legend: patch }, { label: "Change legend" });
1265
- }
1266
- // -------------------------------------------------------------------------
1267
- // Data
1268
- // -------------------------------------------------------------------------
1269
- /**
1270
- * Replaces categories and / or series as one undo step. Values are validated and padded
1271
- * to the categories; all-or-nothing like `update`.
1272
- */
1273
- setData(data, options = {}) {
1274
- return this.update({ ...data.categories ? { categories: data.categories } : {}, ...data.series ? { series: data.series } : {} }, { label: _nullishCoalesce(options.label, () => ( "Edit chart data")) });
1275
- }
1276
- /**
1277
- * Sets one value (numbers, numeric text such as `1,200` or `12%`, or blank for a gap).
1278
- * One undo step, or part of the current gesture.
1279
- */
1280
- setValue(seriesId, index, value, options = {}) {
1281
- const series = this.getSeries(seriesId);
1282
- if (!series) return this.unknownSeries(seriesId);
1283
- if (!Number.isInteger(index) || index < 0 || index >= this.spec.categories.length) return this.badIndex(index);
1284
- const parsed = parseChartValue(value);
1285
- 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.` }]);
1286
- if (series.values[index] === parsed) {
1287
- if (this.issues.length) {
1288
- this.issues = [];
1289
- this.notify(false);
1290
- }
1291
- return { ok: true };
1292
- }
1293
- const next = this.edited((d) => {
1294
- d.series.find((s) => s.id === seriesId).values[index] = parsed;
1295
- });
1296
- this.commit(_nullishCoalesce(options.label, () => ( `Edit ${series.name} \xB7 ${this.spec.categories[index]}`)), next);
1297
- return { ok: true };
1298
- }
1299
- /** Several values as one undo step (all-or-nothing). */
1300
- setValues(cells, options = {}) {
1301
- const issues = [];
1302
- const parsed = [];
1303
- for (const cell of cells) {
1304
- if (!this.getSeries(cell.seriesId)) issues.push({ path: cell.seriesId, message: this.unknownSeries(cell.seriesId).message });
1305
- 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 });
1306
- else {
1307
- const value = parseChartValue(cell.value);
1308
- if (value === void 0) issues.push({ path: `${cell.seriesId}[${cell.index}]`, message: `"${String(cell.value)}" is not a number.` });
1309
- else parsed.push({ seriesId: cell.seriesId, index: cell.index, value });
1310
- }
1311
- }
1312
- if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1313
- const next = this.edited((d) => {
1314
- for (const cell of parsed) d.series.find((s) => s.id === cell.seriesId).values[cell.index] = cell.value;
1315
- });
1316
- if (JSON.stringify(next.series) !== JSON.stringify(this.spec.series)) this.commit(_nullishCoalesce(options.label, () => ( "Edit values")), next);
1317
- return { ok: true };
1318
- }
1319
- /** Adds a series (values padded to the categories) and selects it. Returns its id. */
1320
- addSeries(input = {}, options = {}) {
1321
- const id = input.id && !this.getSeries(input.id) ? input.id : uniqueChartSeriesId(this.spec.series);
1322
- const name = _nullishCoalesce(input.name, () => ( `Series ${this.spec.series.length + 1}`));
1323
- const at = Math.max(0, Math.min(this.spec.series.length, _nullishCoalesce(options.at, () => ( this.spec.series.length))));
1324
- const series = [...this.spec.series];
1325
- series.splice(at, 0, { ...input, id, name, values: _nullishCoalesce(input.values, () => ( [])) });
1326
- const { spec, issues } = normalizeChartSpec({ ...this.spec, series });
1327
- if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1328
- this.commit(`Add series "${name}"`, spec, { kind: "series", seriesId: id });
1329
- return { ok: true, id };
1330
- }
1331
- removeSeries(id) {
1332
- const series = this.getSeries(id);
1333
- if (!series) return this.unknownSeries(id);
1334
- this.commit(
1335
- `Delete series "${series.name}"`,
1336
- this.edited((d) => {
1337
- d.series = d.series.filter((s) => s.id !== id);
1338
- })
1339
- );
1340
- return { ok: true };
1341
- }
1342
- updateSeries(id, patch) {
1343
- const series = this.getSeries(id);
1344
- if (!series) return this.unknownSeries(id);
1345
- const next = cloneChartSeries(series);
1346
- if (patch.name !== void 0) next.name = String(patch.name);
1347
- if (patch.color === null || patch.color === "") delete next.color;
1348
- else if (patch.color !== void 0) next.color = patch.color;
1349
- if (patch.type === null) delete next.type;
1350
- else if (patch.type !== void 0) next.type = patch.type;
1351
- if (patch.hidden !== void 0) {
1352
- if (patch.hidden) next.hidden = true;
1353
- else delete next.hidden;
1354
- }
1355
- const series2 = this.spec.series.map((s) => s.id === id ? next : s);
1356
- const { spec, issues } = normalizeChartSpec({ ...this.spec, series: series2 });
1357
- if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
1358
- if (JSON.stringify(spec.series) === JSON.stringify(this.spec.series)) return { ok: true };
1359
- 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}"`;
1360
- this.commit(label, spec);
1361
- return { ok: true };
1362
- }
1363
- toggleSeries(id) {
1364
- const series = this.getSeries(id);
1365
- if (!series) return this.unknownSeries(id);
1366
- return this.updateSeries(id, { hidden: !series.hidden });
1367
- }
1368
- /** Moves a series to position `to` (drawing and legend order). */
1369
- moveSeries(id, to) {
1370
- const from = this.spec.series.findIndex((s) => s.id === id);
1371
- if (from < 0) return this.unknownSeries(id);
1372
- const target = Math.max(0, Math.min(this.spec.series.length - 1, to));
1373
- if (target === from) return { ok: true };
1374
- this.commit(
1375
- "Reorder series",
1376
- this.edited((d) => {
1377
- const [s] = d.series.splice(from, 1);
1378
- d.series.splice(target, 0, s);
1379
- })
1380
- );
1381
- return { ok: true };
1382
- }
1383
- /** Inserts a category (with gaps in every series) at `at` (default: the end). Returns its index. */
1384
- addCategory(label, options = {}) {
1385
- const at = Math.max(0, Math.min(this.spec.categories.length, _nullishCoalesce(options.at, () => ( this.spec.categories.length))));
1386
- const name = _nullishCoalesce(label, () => ( `${this.spec.categories.length + 1}`));
1387
- this.commit(
1388
- `Add category "${name}"`,
1389
- this.edited((d) => {
1390
- d.categories.splice(at, 0, name);
1391
- for (const s of d.series) s.values.splice(at, 0, null);
1392
- }),
1393
- { kind: "category", index: at }
1394
- );
1395
- return { ok: true, index: at };
1396
- }
1397
- removeCategory(index) {
1398
- if (!Number.isInteger(index) || index < 0 || index >= this.spec.categories.length) return this.badIndex(index);
1399
- this.commit(
1400
- `Delete category "${this.spec.categories[index]}"`,
1401
- this.edited((d) => {
1402
- d.categories.splice(index, 1);
1403
- for (const s of d.series) s.values.splice(index, 1);
1404
- })
1405
- );
1406
- return { ok: true };
1407
- }
1408
- renameCategory(index, label) {
1409
- if (!Number.isInteger(index) || index < 0 || index >= this.spec.categories.length) return this.badIndex(index);
1410
- if (this.spec.categories[index] === label) return { ok: true };
1411
- this.commit(
1412
- "Rename category",
1413
- this.edited((d) => {
1414
- d.categories[index] = String(label);
1415
- })
1416
- );
1417
- return { ok: true };
1418
- }
1419
- moveCategory(from, to) {
1420
- if (!Number.isInteger(from) || from < 0 || from >= this.spec.categories.length) return this.badIndex(from);
1421
- const target = Math.max(0, Math.min(this.spec.categories.length - 1, to));
1422
- if (target === from) return { ok: true };
1423
- const move = (list) => {
1424
- const [item] = list.splice(from, 1);
1425
- list.splice(target, 0, item);
1426
- };
1427
- this.commit(
1428
- "Reorder categories",
1429
- this.edited((d) => {
1430
- move(d.categories);
1431
- for (const s of d.series) move(s.values);
1432
- })
1433
- );
1434
- return { ok: true };
1435
- }
1436
- /**
1437
- * Pastes a block of cells (TSV / CSV text or rows) into the data grid with its top-left at
1438
- * `row` (category index) and `column` (series index; `-1` is the category column). The
1439
- * grid grows to fit. Non-numeric value cells become gaps and are reported in `issues`.
1440
- */
1441
- pasteCells(row, column, cells) {
1442
- const rows = typeof cells === "string" ? cells.includes(" ") ? _chunkTNNJXF46cjs.parseTsv.call(void 0, cells) : _chunkTNNJXF46cjs.parseCsv.call(void 0, cells) : cells.map((r) => [...r]);
1443
- if (!rows.length) return { ok: true };
1444
- const issues = [];
1445
- const next = this.edited((d) => {
1446
- const width = Math.max(...rows.map((r) => r.length));
1447
- const lastSeries = column + width - 1;
1448
- while (d.series.length <= lastSeries) {
1449
- const id = uniqueChartSeriesId(d.series);
1450
- d.series.push({ id, name: `Series ${d.series.length + 1}`, values: d.categories.map(() => null) });
1451
- }
1452
- const lastRow = row + rows.length - 1;
1453
- while (d.categories.length <= lastRow) {
1454
- d.categories.push(`${d.categories.length + 1}`);
1455
- for (const s of d.series) s.values.push(null);
1456
- }
1457
- rows.forEach((cellsInRow, r) => {
1458
- cellsInRow.forEach((cell, c) => {
1459
- const col = column + c;
1460
- const index = row + r;
1461
- if (col < 0) d.categories[index] = cell === null || cell === void 0 ? "" : String(cell).trim();
1462
- else {
1463
- const parsed = parseChartValue(cell);
1464
- if (parsed === void 0) issues.push({ path: `${d.series[col].id}[${index}]`, message: `"${String(cell)}" is not a number; it was left blank.` });
1465
- d.series[col].values[index] = _nullishCoalesce(parsed, () => ( null));
1466
- }
1467
- });
1468
- });
1469
- });
1470
- this.commit("Paste cells", next);
1471
- if (issues.length) {
1472
- this.issues = issues;
1473
- this.notify(false);
1474
- }
1475
- return issues.length ? { ok: true, issues } : { ok: true };
1476
- }
1477
- /**
1478
- * Replaces the data with a grid of cells (see `chartDataFromTable`): one undo step. Series
1479
- * colours and types are kept by name.
1480
- */
1481
- importTable(rows, options = {}) {
1482
- const data = chartDataFromTable(rows, options);
1483
- 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.");
1484
- const byName = new Map(this.spec.series.map((s) => [s.name, s]));
1485
- const series = data.series.map((s) => {
1486
- const old = byName.get(s.name);
1487
- return { ...s, ..._optionalChain([old, 'optionalAccess', _21 => _21.color]) ? { color: old.color } : {}, ..._optionalChain([old, 'optionalAccess', _22 => _22.type]) ? { type: old.type } : {}, ...old ? { id: old.id } : {} };
1488
- });
1489
- const { spec } = normalizeChartSpec({ ...this.spec, categories: data.categories, series });
1490
- this.commit("Import data", spec);
1491
- if (data.issues.length) {
1492
- this.issues = data.issues;
1493
- this.notify(false);
1494
- return { ok: true, issues: data.issues };
1495
- }
1496
- return { ok: true };
1497
- }
1498
- /** Imports CSV (or TSV, detected by tabs) text; see `importTable`. */
1499
- importCsv(text2, options = {}) {
1500
- const delimiter = _nullishCoalesce(options.delimiter, () => ( (text2.includes(" ") ? " " : ",")));
1501
- return this.importTable(_chunkTNNJXF46cjs.parseCsv.call(void 0, text2, { delimiter }), options);
1502
- }
1503
- // -------------------------------------------------------------------------
1504
- // Selection and gestures
1505
- // -------------------------------------------------------------------------
1506
- /** Selects a chart element (view state; not an undo step). */
1507
- select(selection) {
1508
- const next = this.validSelection(selection, this.spec);
1509
- if (sameChartSelection(next, this.selection) || next === null && this.selection === null) return;
1510
- this.selection = next;
1511
- this.notify(false);
1512
- }
1513
- /**
1514
- * Deletes what is selected: a series or category is removed, a point becomes a gap, a
1515
- * title is cleared, the legend or an axis is hidden.
1516
- */
1517
- deleteSelection() {
1518
- const sel = this.selection;
1519
- if (!sel) return fail("Nothing is selected.");
1520
- switch (sel.kind) {
1521
- case "series":
1522
- return this.removeSeries(sel.seriesId);
1523
- case "category":
1524
- return this.removeCategory(sel.index);
1525
- case "point":
1526
- return this.setValue(sel.seriesId, sel.index, null, { label: "Clear value" });
1527
- case "title":
1528
- return this.setTitle("");
1529
- case "subtitle":
1530
- return this.setSubtitle("");
1531
- case "legend":
1532
- return this.setLegend({ show: false });
1533
- case "axis":
1534
- return this.setAxis(sel.axis, { show: false });
1535
- default:
1536
- return fail("The chart itself cannot be deleted.");
1537
- }
1538
- }
1539
- /** Adds `delta` to a value (a gap counts as 0). */
1540
- nudgeValue(seriesId, index, delta) {
1541
- const series = this.getSeries(seriesId);
1542
- if (!series) return this.unknownSeries(seriesId);
1543
- const current = _nullishCoalesce(series.values[index], () => ( 0));
1544
- return this.setValue(seriesId, index, Number((current + delta).toPrecision(12)), { label: `Edit ${series.name} \xB7 ${_nullishCoalesce(this.spec.categories[index], () => ( index))}` });
1545
- }
1546
- /** Starts a continuous edit (a drag): every change until `endGesture()` is one undo step. */
1547
- beginGesture(key) {
1548
- if (this.gestureKey === key) return;
1549
- this.history.endGesture();
1550
- this.gestureKey = key;
1551
- this.gestureCount++;
1552
- this.gestureStart = { spec: this.spec, mark: this.history.mark() };
1553
- this.notify(false);
1554
- }
1555
- endGesture() {
1556
- if (this.gestureKey === null) return;
1557
- this.gestureKey = null;
1558
- this.history.endGesture();
1559
- const start = this.gestureStart;
1560
- this.gestureStart = null;
1561
- if (start && sameChartJson(start.spec, this.spec)) this.history.revertTo(start.mark);
1562
- this.notify(false);
1563
- }
1564
- // -------------------------------------------------------------------------
1565
- // Persistence and history
1566
- // -------------------------------------------------------------------------
1567
- /** The spec, for saving (a copy). */
1568
- toJSON() {
1569
- return cloneChartSpec(this.spec);
1570
- }
1571
- /**
1572
- * Replaces the chart without an undo step (e.g. when opening a document). Problems are
1573
- * fixed with defaults and reported in `state.issues` (and returned).
1574
- */
1575
- load(input, options = {}) {
1576
- const { spec, issues } = normalizeChartSpec(input);
1577
- this.spec = spec;
1578
- this.issues = issues;
1579
- this.selection = this.validSelection(this.selection, spec);
1580
- if (options.clearHistory) {
1581
- this.applying = true;
1582
- try {
1583
- this.history.clear();
1584
- } finally {
1585
- this.applying = false;
1586
- }
1587
- }
1588
- this.notify(true);
1589
- return issues;
1590
- }
1591
- undo() {
1592
- this.endGesture();
1593
- return this.history.undo();
1594
- }
1595
- redo() {
1596
- this.endGesture();
1597
- return this.history.redo();
1598
- }
1599
- /** Stops listening to the history (reconnected by the next `subscribe`). */
1600
- destroy() {
1601
- _optionalChain([this, 'access', _23 => _23.unsubscribeHistory, 'optionalCall', _24 => _24()]);
1602
- this.unsubscribeHistory = void 0;
1603
- this.listeners.clear();
217
+ if (shape.points > 12) {
218
+ add("line", "Line", "Several long series: lines keep them readable.");
219
+ add("bar", "Column", "Compares the series at each category.");
220
+ add("area", "Area", "Emphasises volume.");
221
+ add("bar", "Stacked column", "Shows how the series add up.", "stacked");
222
+ } else {
223
+ add("bar", "Column", "Several series: clustered columns compare them per category.");
224
+ add("bar", "Bar", "Horizontal bars fit long category names.");
225
+ add("bar", "Stacked column", "Shows how the series add up.", "stacked");
226
+ add("line", "Line", "Shows the series in order.");
227
+ if (shape.points >= 3 && shape.series <= 6) add("radar", "Radar", "Compares several measures across the series.");
1604
228
  }
1605
- }, _class);
1606
- function createChartController(options = {}) {
1607
- return new ChartController(options);
229
+ return out;
1608
230
  }
1609
- function updateLabel(patch) {
1610
- const keys = Object.keys(patch);
1611
- if (keys.length !== 1) return "Change chart";
1612
- const labels = {
1613
- type: "Change chart type",
1614
- title: "Edit title",
1615
- subtitle: "Edit subtitle",
1616
- categories: "Edit categories",
1617
- series: "Edit series",
1618
- xAxis: "Change category axis",
1619
- yAxis: "Change value axis",
1620
- legend: "Change legend",
1621
- stacking: "Change stacking",
1622
- orientation: "Change orientation",
1623
- curve: "Change line style",
1624
- points: "Toggle points",
1625
- dataLabels: "Toggle data labels",
1626
- palette: "Change colours",
1627
- innerRadius: "Change donut hole"
1628
- };
1629
- return _nullishCoalesce(labels[keys[0]], () => ( "Change chart"));
231
+ function chartFromTable(rows, options = {}) {
232
+ const shape = analyzeChartTable(rows);
233
+ const rec = (_nullishCoalesce(options.type, () => ( "auto"))) === "auto" ? recommendForShape(shape)[0] : void 0;
234
+ const type = _nullishCoalesce(_optionalChain([rec, 'optionalAccess', _7 => _7.type]), () => ( (options.type === "auto" || !options.type ? "bar" : options.type)));
235
+ const stacking = _nullishCoalesce(_optionalChain([rec, 'optionalAccess', _8 => _8.stacking]), () => ( "none"));
236
+ const grid = rows.filter((r) => r.some((c) => !blank(c))).map((r) => [...r]);
237
+ const width = Math.max(0, ...grid.map((r) => r.length));
238
+ let table = grid;
239
+ if (!shape.labelColumn) table = table.map((r, i) => [shape.headerRow && i === 0 ? "" : String(i + (shape.headerRow ? 0 : 1)), ...r]);
240
+ if (!shape.headerRow) table = [["", ...Array.from({ length: width }, (_, i) => `Series ${i + 1}`)], ...table];
241
+ const xFromFirst = type === "scatter" && !shape.labelColumn && !shape.byRows && shape.series > 1;
242
+ if (xFromFirst) {
243
+ table = table.map((r) => [r[1], ...r.slice(2)]);
244
+ }
245
+ const data = _chunkPRSJJZBScjs.chartDataFromTable.call(void 0, table, { seriesIn: shape.byRows ? "rows" : "columns", header: true, xFromFirstColumn: xFromFirst });
246
+ return { type, stacking, categories: data.categories, series: data.series, issues: data.issues, shape };
1630
247
  }
1631
248
 
1632
249
  // src/chart/commands.ts
1633
- var fail2 = (message) => {
250
+ var fail = (message) => {
1634
251
  throw new Error(message);
1635
252
  };
1636
253
  var check = (result) => {
1637
- if (!result.ok) fail2(_nullishCoalesce(result.message, () => ( "The change was rejected.")));
1638
- };
1639
- var VALUE = { type: ["number", "string", "null"], description: 'A number, numeric text ("1,200", "12%") or null for a gap.' };
1640
- var FORMAT = {
1641
- type: "object",
1642
- properties: {
1643
- style: { type: "string", enum: [...CHART_NUMBER_STYLES] },
1644
- decimals: { type: "integer", minimum: 0, maximum: 10 },
1645
- currency: { type: "string", description: 'ISO code for style "currency", e.g. "EUR".' },
1646
- prefix: { type: "string" },
1647
- suffix: { type: "string" }
1648
- },
1649
- additionalProperties: false
1650
- };
1651
- var AXIS = {
1652
- type: "object",
1653
- properties: {
1654
- show: { type: "boolean" },
1655
- title: { type: "string" },
1656
- min: { type: ["number", "null"], description: "Fixed lower bound, or null for automatic." },
1657
- max: { type: ["number", "null"], description: "Fixed upper bound, or null for automatic." },
1658
- gridlines: { type: "boolean" },
1659
- format: FORMAT
1660
- },
1661
- additionalProperties: false
1662
- };
1663
- var SERIES = {
1664
- type: "object",
1665
- properties: {
1666
- id: { type: "string" },
1667
- name: { type: "string" },
1668
- values: { type: "array", items: VALUE, description: "One value per category." },
1669
- color: { type: "string", description: "CSS colour." },
1670
- type: { type: "string", enum: [...CHART_SERIES_TYPES], description: "Draw this series differently (combo charts)." },
1671
- hidden: { type: "boolean" }
1672
- },
1673
- required: ["name", "values"],
1674
- additionalProperties: false
254
+ if (!result.ok) fail(_nullishCoalesce(result.message, () => ( "The change was rejected.")));
1675
255
  };
1676
256
  function createChartCommands(controller, options = {}) {
1677
257
  const prefix = _nullishCoalesce(options.prefix, () => ( "chart."));
@@ -1680,53 +260,37 @@ function createChartCommands(controller, options = {}) {
1680
260
  const resolveSeries = (ref) => {
1681
261
  const spec = controller.getSpec();
1682
262
  const series = _nullishCoalesce(spec.series.find((s) => s.id === ref), () => ( spec.series.find((s) => s.name === ref)));
1683
- if (!series) fail2(`There is no series "${ref}"; expected one of ${seriesIds().map((id) => `"${id}"`).join(", ") || "(none yet)"}.`);
263
+ if (!series) fail(`There is no series "${ref}"; expected one of ${seriesIds().map((id) => `"${id}"`).join(", ") || "(none yet)"}.`);
1684
264
  return series;
1685
265
  };
1686
266
  const resolveCategory = (ref) => {
1687
267
  const categories = controller.getSpec().categories;
1688
268
  const index = typeof ref === "number" ? ref : categories.indexOf(ref);
1689
- if (index < 0 || index >= categories.length || !Number.isInteger(index)) fail2(`There is no category ${JSON.stringify(ref)}; expected an index from 0 to ${categories.length - 1} or one of the labels.`);
269
+ if (index < 0 || index >= categories.length || !Number.isInteger(index)) fail(`There is no category ${JSON.stringify(ref)}; expected an index from 0 to ${categories.length - 1} or one of the labels.`);
1690
270
  return index;
1691
271
  };
272
+ const object = (properties, required) => ({ type: "object", properties, ...required ? { required } : {}, additionalProperties: false });
1692
273
  return [
1693
274
  {
1694
275
  ...base,
1695
276
  id: `${prefix}describe`,
1696
277
  label: "Describe Chart",
1697
278
  readOnly: true,
1698
- keywords: ["chart", "data", "spec", "get"],
1699
- description: "Returns the chart spec (type, titles, axes, legend, options) and its data as a table with series names in the header row.",
1700
- run: () => {
279
+ keywords: ["chart", "data", "spec", "get", "summary"],
280
+ description: "Returns the chart spec, its data as a table (series names in the header row) and a summary: per-series min / max / mean / trend, axes as drawn, layout warnings and a colour check.",
281
+ params: object({ width: _chunkU6IZKC7Bcjs.WIDTH, height: _chunkU6IZKC7Bcjs.HEIGHT }),
282
+ run: (_ctx, params = {}) => {
1701
283
  const spec = controller.getSpec();
1702
- return { spec: controller.toJSON(), table: chartDataToTable(spec), issues: controller.getState().issues };
284
+ return { spec: controller.toJSON(), table: _chunkPRSJJZBScjs.chartDataToTable.call(void 0, spec), summary: _chunkPRSJJZBScjs.summarizeChart.call(void 0, controller.toJSON(), { width: _nullishCoalesce(_optionalChain([params, 'optionalAccess', _9 => _9.width]), () => ( 640)), height: _nullishCoalesce(_optionalChain([params, 'optionalAccess', _10 => _10.height]), () => ( 400)) }), issues: controller.getState().issues };
1703
285
  }
1704
286
  },
1705
287
  {
1706
288
  ...base,
1707
289
  id: `${prefix}update`,
1708
290
  label: "Change Chart\u2026",
1709
- keywords: ["chart", "title", "axis", "legend", "stack", "style", "format"],
1710
- description: "Changes chart presentation as one undo step: type, titles, axes (x = categories, y = values), legend, stacking, orientation, curve, points, data labels, palette or donut hole. Invalid values change nothing and return why.",
1711
- params: {
1712
- type: "object",
1713
- properties: {
1714
- type: { type: "string", enum: [...CHART_TYPES] },
1715
- title: { type: "string" },
1716
- subtitle: { type: "string" },
1717
- xAxis: AXIS,
1718
- yAxis: AXIS,
1719
- legend: { type: "object", properties: { show: { type: "boolean" }, position: { type: "string", enum: [...CHART_LEGEND_POSITIONS] } }, additionalProperties: false },
1720
- stacking: { type: "string", enum: [...CHART_STACKINGS] },
1721
- orientation: { type: "string", enum: [...CHART_ORIENTATIONS] },
1722
- curve: { type: "string", enum: [...CHART_CURVES] },
1723
- points: { type: "boolean" },
1724
- dataLabels: { type: "boolean" },
1725
- palette: { type: "array", items: { type: "string" }, minItems: 1 },
1726
- innerRadius: { type: "number", minimum: 0, maximum: 0.9 }
1727
- },
1728
- additionalProperties: false
1729
- },
291
+ keywords: ["chart", "title", "axis", "legend", "stack", "style", "format", "type"],
292
+ description: "Changes chart presentation as one undo step: type, titles, axes (xAxis = categories, yAxis = values, yAxis2 = secondary values), legend, stacking, orientation, curve, points, data labels, palette, donut hole, bar spacing, histogram bins or waterfall total. Invalid values change nothing and return why.",
293
+ params: object(_chunkU6IZKC7Bcjs.CHART_UPDATE_PROPERTIES),
1730
294
  run: (_ctx, params) => {
1731
295
  check(controller.update(params));
1732
296
  return { spec: controller.toJSON() };
@@ -1738,17 +302,13 @@ function createChartCommands(controller, options = {}) {
1738
302
  label: "Replace Chart Data\u2026",
1739
303
  keywords: ["chart", "data", "series", "categories"],
1740
304
  description: "Replaces the categories and series as one undo step. Each series has one value per category (padded with gaps).",
1741
- params: {
1742
- type: "object",
1743
- properties: {
1744
- categories: { type: "array", items: { type: "string" } },
1745
- series: { type: "array", items: SERIES }
1746
- },
1747
- additionalProperties: false
1748
- },
305
+ params: object({
306
+ categories: { type: "array", items: { type: "string" }, description: "Category labels in order." },
307
+ series: { type: "array", description: "The new series.", items: object({ id: { type: "string", description: "Stable id (optional)." }, ..._chunkU6IZKC7Bcjs.SERIES_PROPS }, ["name", "values"]) }
308
+ }),
1749
309
  run: (_ctx, params) => {
1750
310
  check(controller.setData(params));
1751
- return { table: chartDataToTable(controller.getSpec()) };
311
+ return { table: _chunkPRSJJZBScjs.chartDataToTable.call(void 0, controller.getSpec()) };
1752
312
  }
1753
313
  },
1754
314
  {
@@ -1757,25 +317,10 @@ function createChartCommands(controller, options = {}) {
1757
317
  label: "Set Chart Values\u2026",
1758
318
  keywords: ["chart", "value", "cell", "edit"],
1759
319
  description: "Sets individual values as one undo step. Series by id or name; category by index or label.",
1760
- params: {
1761
- type: "object",
1762
- properties: {
1763
- cells: {
1764
- type: "array",
1765
- minItems: 1,
1766
- items: {
1767
- type: "object",
1768
- properties: { series: { type: "string" }, category: { type: ["string", "integer"] }, value: VALUE },
1769
- required: ["series", "category", "value"],
1770
- additionalProperties: false
1771
- }
1772
- }
1773
- },
1774
- required: ["cells"]
1775
- },
320
+ params: object({ cells: { type: "array", minItems: 1, description: "Values to set.", items: object({ series: _chunkU6IZKC7Bcjs.SERIES_REF, category: _chunkU6IZKC7Bcjs.CATEGORY_REF, value: _chunkU6IZKC7Bcjs.VALUE }, ["series", "category", "value"]) } }, ["cells"]),
1776
321
  run: (_ctx, params) => {
1777
322
  check(controller.setValues(params.cells.map((c) => ({ seriesId: resolveSeries(c.series).id, index: resolveCategory(c.category), value: c.value }))));
1778
- return { table: chartDataToTable(controller.getSpec()) };
323
+ return { table: _chunkPRSJJZBScjs.chartDataToTable.call(void 0, controller.getSpec()) };
1779
324
  }
1780
325
  },
1781
326
  {
@@ -1784,10 +329,11 @@ function createChartCommands(controller, options = {}) {
1784
329
  label: "Add Series",
1785
330
  keywords: ["chart", "series", "new"],
1786
331
  description: "Adds a series (default: an empty one at the end). Returns its id.",
1787
- params: { type: "object", properties: { ...SERIES.properties, at: { type: "integer", minimum: 0 } }, additionalProperties: false },
332
+ params: object({ ..._chunkU6IZKC7Bcjs.SERIES_PROPS, id: { type: "string", description: "Id to use (default series-N)." }, at: { type: "integer", minimum: 0, description: "Position (default: last)." } }),
1788
333
  run: (_ctx, params = {}) => {
1789
334
  const { at, ...input } = _nullishCoalesce(params, () => ( {}));
1790
- const result = controller.addSeries(input, { at });
335
+ const clean = Object.fromEntries(Object.entries(input).filter(([, v]) => v !== null));
336
+ const result = controller.addSeries(clean, { at });
1791
337
  check(result);
1792
338
  return { id: result.id };
1793
339
  }
@@ -1796,20 +342,9 @@ function createChartCommands(controller, options = {}) {
1796
342
  ...base,
1797
343
  id: `${prefix}updateSeries`,
1798
344
  label: "Change Series\u2026",
1799
- keywords: ["chart", "series", "rename", "colour", "color", "hide"],
1800
- description: "Renames, recolours, hides or changes how a series is drawn (null colour / type resets it).",
1801
- params: {
1802
- type: "object",
1803
- properties: {
1804
- series: { type: "string", description: "Series id or name." },
1805
- name: { type: "string" },
1806
- color: { type: ["string", "null"] },
1807
- type: { type: ["string", "null"], enum: [...CHART_SERIES_TYPES, null] },
1808
- hidden: { type: "boolean" }
1809
- },
1810
- required: ["series"],
1811
- additionalProperties: false
1812
- },
345
+ keywords: ["chart", "series", "rename", "colour", "color", "hide", "axis", "marker"],
346
+ description: "Renames, recolours, hides, moves to the secondary axis or changes how a series is drawn (null resets a field).",
347
+ params: object({ series: _chunkU6IZKC7Bcjs.SERIES_REF, ...Object.fromEntries(Object.entries(_chunkU6IZKC7Bcjs.SERIES_PROPS).filter(([k]) => k !== "values")) }, ["series"]),
1813
348
  run: (_ctx, params) => {
1814
349
  const { series, ...patch } = params;
1815
350
  const id = resolveSeries(series).id;
@@ -1823,7 +358,7 @@ function createChartCommands(controller, options = {}) {
1823
358
  label: "Delete Series",
1824
359
  keywords: ["chart", "series", "remove"],
1825
360
  description: "Deletes a series by id or name.",
1826
- params: { type: "object", properties: { series: { type: "string" } }, required: ["series"], additionalProperties: false },
361
+ params: object({ series: _chunkU6IZKC7Bcjs.SERIES_REF }, ["series"]),
1827
362
  run: (_ctx, params) => {
1828
363
  check(controller.removeSeries(resolveSeries(params.series).id));
1829
364
  return { series: seriesIds() };
@@ -1835,8 +370,8 @@ function createChartCommands(controller, options = {}) {
1835
370
  label: "Add Category",
1836
371
  keywords: ["chart", "category", "row", "label"],
1837
372
  description: "Inserts a category (gaps in every series) at `at` (default: the end).",
1838
- params: { type: "object", properties: { label: { type: "string" }, at: { type: "integer", minimum: 0 } }, additionalProperties: false },
1839
- run: (_ctx, params = {}) => controller.addCategory(_optionalChain([params, 'optionalAccess', _25 => _25.label]), { at: _optionalChain([params, 'optionalAccess', _26 => _26.at]) })
373
+ params: object({ label: { type: "string", description: "Category label (default: its number)." }, at: { type: "integer", minimum: 0, description: "Index to insert at (default: the end)." } }),
374
+ run: (_ctx, params = {}) => controller.addCategory(_optionalChain([params, 'optionalAccess', _11 => _11.label]), { at: _optionalChain([params, 'optionalAccess', _12 => _12.at]) })
1840
375
  },
1841
376
  {
1842
377
  ...base,
@@ -1844,161 +379,1373 @@ function createChartCommands(controller, options = {}) {
1844
379
  label: "Delete Category",
1845
380
  keywords: ["chart", "category", "row", "remove"],
1846
381
  description: "Deletes a category (and its values) by index or label.",
1847
- params: { type: "object", properties: { category: { type: ["string", "integer"] } }, required: ["category"], additionalProperties: false },
382
+ params: object({ category: _chunkU6IZKC7Bcjs.CATEGORY_REF }, ["category"]),
1848
383
  run: (_ctx, params) => {
1849
384
  check(controller.removeCategory(resolveCategory(params.category)));
1850
385
  return { categories: controller.getSpec().categories };
1851
386
  }
1852
387
  },
388
+ {
389
+ ...base,
390
+ id: `${prefix}addTrendline`,
391
+ label: "Add Trendline\u2026",
392
+ keywords: ["chart", "trend", "regression", "fit", "forecast", "moving average"],
393
+ description: "Fits a trendline to a series (linear, exponential, logarithmic, power, polynomial or moving average). Returns its index and the fit.",
394
+ params: object({ series: _chunkU6IZKC7Bcjs.SERIES_REF, ..._chunkU6IZKC7Bcjs.TRENDLINE_PROPS }, ["series", "type"]),
395
+ run: (_ctx, params) => {
396
+ const { series, ...line } = params;
397
+ const result = controller.addTrendline(resolveSeries(series).id, line);
398
+ check(result);
399
+ const ok = result;
400
+ return { index: ok.index, ...ok.fit ? { coefficients: ok.fit.coefficients, r2: ok.fit.r2, equation: ok.fit.equation } : {} };
401
+ }
402
+ },
403
+ {
404
+ ...base,
405
+ id: `${prefix}removeTrendline`,
406
+ label: "Delete Trendline",
407
+ keywords: ["chart", "trend", "remove"],
408
+ description: "Deletes a trendline from a series.",
409
+ params: object({ series: _chunkU6IZKC7Bcjs.SERIES_REF, index: { type: "integer", minimum: 0, description: "Trendline index (0 = first)." } }, ["series", "index"]),
410
+ run: (_ctx, params) => {
411
+ check(controller.removeTrendline(resolveSeries(params.series).id, params.index));
412
+ return { trendlines: _nullishCoalesce(_optionalChain([controller, 'access', _13 => _13.getSeries, 'call', _14 => _14(resolveSeries(params.series).id), 'optionalAccess', _15 => _15.trendlines]), () => ( [])) };
413
+ }
414
+ },
415
+ {
416
+ ...base,
417
+ id: `${prefix}applyPreset`,
418
+ label: "Apply Chart Style\u2026",
419
+ keywords: ["chart", "quick layout", "style", "colours", "colors", "monochrome", "theme"],
420
+ description: "Applies a quick layout and / or colour scheme as one undo step.",
421
+ params: object({
422
+ layout: { type: "string", enum: _chunkPRSJJZBScjs.CHART_LAYOUT_PRESETS.map((p) => p.id), description: "Quick layout id." },
423
+ colors: { type: "string", enum: _chunkPRSJJZBScjs.CHART_COLOR_SCHEMES.map((p) => p.id), description: "Colour scheme id." }
424
+ }),
425
+ run: (_ctx, params = {}) => {
426
+ check(controller.applyPreset(_nullishCoalesce(params, () => ( {}))));
427
+ return { spec: controller.toJSON() };
428
+ }
429
+ },
430
+ {
431
+ ...base,
432
+ id: `${prefix}transpose`,
433
+ label: "Switch Rows and Columns",
434
+ keywords: ["chart", "transpose", "swap", "rows", "columns", "series"],
435
+ description: "Swaps series and categories as one undo step.",
436
+ run: () => {
437
+ check(controller.transpose());
438
+ return { table: _chunkPRSJJZBScjs.chartDataToTable.call(void 0, controller.getSpec()) };
439
+ }
440
+ },
1853
441
  {
1854
442
  ...base,
1855
443
  id: `${prefix}importCsv`,
1856
444
  label: "Import Chart Data from CSV\u2026",
1857
445
  keywords: ["chart", "csv", "tsv", "paste", "import"],
1858
446
  description: "Replaces the data with CSV / TSV text: by default the first column holds categories and the first row series names.",
1859
- params: {
1860
- type: "object",
1861
- properties: {
1862
- csv: { type: "string" },
1863
- seriesIn: { type: "string", enum: ["columns", "rows"] },
1864
- header: { type: "boolean" }
447
+ params: object(
448
+ {
449
+ csv: { type: "string", description: "CSV or TSV text." },
450
+ seriesIn: { type: "string", enum: ["columns", "rows"], description: "Series run down columns (default) or along rows." },
451
+ header: { type: ["boolean", "string"], enum: [true, false, "auto"], description: "The first row holds series names (default true)." }
1865
452
  },
1866
- required: ["csv"],
1867
- additionalProperties: false
453
+ ["csv"]
454
+ ),
455
+ run: (_ctx, params) => {
456
+ const result = controller.importCsv(params.csv, { seriesIn: params.seriesIn, header: params.header });
457
+ check(result);
458
+ return { table: _chunkPRSJJZBScjs.chartDataToTable.call(void 0, controller.getSpec()), issues: _nullishCoalesce(result.issues, () => ( [])) };
459
+ }
460
+ },
461
+ {
462
+ ...base,
463
+ id: `${prefix}importMermaid`,
464
+ label: "Import Mermaid Chart\u2026",
465
+ keywords: ["chart", "mermaid", "markdown", "xychart", "pie", "radar", "import"],
466
+ description: "Replaces the chart with Mermaid xychart-beta, pie or radar-beta text (one undo step). Unreadable lines are listed with line numbers.",
467
+ params: object({ mermaid: { type: "string", description: "Mermaid text." } }, ["mermaid"]),
468
+ run: (_ctx, params) => {
469
+ const read = _chunkPRSJJZBScjs.chartFromMermaid.call(void 0, params.mermaid);
470
+ if (!read.family) fail(read.issues.map((i) => i.message).join(" ") || "Expected Mermaid starting with xychart-beta, pie or radar-beta.");
471
+ check(controller.update({ ...read.spec }, { label: "Import Mermaid chart" }));
472
+ return { spec: controller.toJSON(), issues: read.issues };
473
+ }
474
+ },
475
+ {
476
+ ...base,
477
+ id: `${prefix}exportMermaid`,
478
+ label: "Copy Chart as Mermaid",
479
+ readOnly: true,
480
+ keywords: ["chart", "mermaid", "markdown", "export"],
481
+ description: "Returns the chart as Mermaid text (with a round-trip comment unless meta is false).",
482
+ params: object({ meta: { type: "boolean", description: "Include the %% @pixerate round-trip comment (default true)." } }),
483
+ run: (_ctx, params = {}) => ({ mermaid: _chunkPRSJJZBScjs.chartToMermaid.call(void 0, controller.toJSON(), { meta: _optionalChain([params, 'optionalAccess', _16 => _16.meta]) }) })
484
+ },
485
+ {
486
+ ...base,
487
+ id: `${prefix}exportOoxml`,
488
+ label: "Export Chart as Office XML",
489
+ readOnly: true,
490
+ keywords: ["chart", "office", "word", "powerpoint", "docx", "pptx", "xml", "export"],
491
+ description: "Returns the chart as a DrawingML chart part (c:chartSpace) and what could not be written.",
492
+ run: () => _chunkPIMCHMZEcjs.chartToOoxml.call(void 0, controller.toJSON())
493
+ },
494
+ {
495
+ ...base,
496
+ id: `${prefix}exportSvg`,
497
+ label: "Export Chart as SVG",
498
+ readOnly: true,
499
+ keywords: ["chart", "svg", "export", "image"],
500
+ description: "Returns the chart drawn as a standalone, accessible SVG string (with <title> and <desc>).",
501
+ params: object({ width: _chunkU6IZKC7Bcjs.WIDTH, height: _chunkU6IZKC7Bcjs.HEIGHT }),
502
+ run: (_ctx, params = {}) => ({
503
+ svg: _chunkPRSJJZBScjs.chartToSvg.call(void 0, controller.toJSON(), { width: _nullishCoalesce(_optionalChain([params, 'optionalAccess', _17 => _17.width]), () => ( 640)), height: _nullishCoalesce(_optionalChain([params, 'optionalAccess', _18 => _18.height]), () => ( 400)) })
504
+ })
505
+ },
506
+ {
507
+ ...base,
508
+ id: `${prefix}recommend`,
509
+ label: "Recommend Chart Types",
510
+ readOnly: true,
511
+ keywords: ["chart", "recommend", "suggest", "type"],
512
+ description: "Suggests chart types for the current data (or CSV), best first, each with a reason.",
513
+ params: object({ csv: { type: "string", description: "CSV text to analyse instead of the current data." } }),
514
+ run: (_ctx, params = {}) => ({
515
+ recommendations: recommendChartTypes(_optionalChain([params, 'optionalAccess', _19 => _19.csv]) !== void 0 ? _chunkTNNJXF46cjs.parseCsv.call(void 0, params.csv) : _chunkPRSJJZBScjs.chartDataToTable.call(void 0, controller.getSpec()))
516
+ })
517
+ },
518
+ {
519
+ ...base,
520
+ id: `${prefix}checkColors`,
521
+ label: "Check Chart Colours",
522
+ readOnly: true,
523
+ keywords: ["chart", "colour", "color", "accessibility", "colour-blind", "contrast"],
524
+ description: "Checks that the series colours can be told apart (including with colour-blindness) and stand out from the background.",
525
+ run: () => {
526
+ const s = controller.getSpec();
527
+ const visible = s.series.filter((x) => !x.hidden);
528
+ const colors = _chunkPRSJJZBScjs.chartColorsByCategory.call(void 0, s.type) ? s.categories.map((_, i) => _optionalChain([visible, 'access', _20 => _20[0], 'optionalAccess', _21 => _21.pointColors, 'optionalAccess', _22 => _22[i]]) || _chunkPRSJJZBScjs.chartSliceColor.call(void 0, s, i)) : visible.map((x) => _chunkPRSJJZBScjs.chartSeriesColor.call(void 0, s, s.series.indexOf(x)));
529
+ return { colors, ..._chunkPRSJJZBScjs.checkChartColors.call(void 0, colors) };
530
+ }
531
+ },
532
+ {
533
+ ...base,
534
+ id: `${prefix}undo`,
535
+ label: "Undo Chart Edit",
536
+ keywords: ["chart", "undo"],
537
+ enabled: () => controller.getState().canUndo || "Nothing to undo",
538
+ run: () => ({ undone: controller.undo() })
539
+ },
540
+ {
541
+ ...base,
542
+ id: `${prefix}redo`,
543
+ label: "Redo Chart Edit",
544
+ keywords: ["chart", "redo"],
545
+ enabled: () => controller.getState().canRedo || "Nothing to redo",
546
+ run: () => ({ redone: controller.redo() })
547
+ }
548
+ ];
549
+ }
550
+
551
+ // src/chart/interaction.ts
552
+ function chartClientToScene(scene, box, clientX, clientY) {
553
+ const sx = box.width ? scene.width / box.width : 1;
554
+ const sy = box.height ? scene.height / box.height : 1;
555
+ return { x: (clientX - box.left) * sx, y: (clientY - box.top) * sy };
556
+ }
557
+ function canDragChartPoint(controller, scene, target) {
558
+ if (_optionalChain([target, 'optionalAccess', _23 => _23.kind]) !== "point" || !scene.valueScale || scene.percent) return false;
559
+ const spec = controller.getSpec();
560
+ const family = _chunkPRSJJZBScjs.chartFamily.call(void 0, spec);
561
+ if (family !== "cartesian" && family !== "xy" && family !== "waterfall") return false;
562
+ const series = controller.getSeries(target.seriesId);
563
+ return !!series && !series.hidden && series.values[target.index] !== null;
564
+ }
565
+ function snapChartDragValue(scene, value, options = {}) {
566
+ const scale = _nullishCoalesce(options.scale, () => ( scene.valueScale));
567
+ const tolerance = _nullishCoalesce(options.tolerancePx, () => ( 6));
568
+ if (scale) {
569
+ const px = (v) => _chunkPRSJJZBScjs.linearMap.call(void 0, v, scale.domain, scale.range);
570
+ const at = px(value);
571
+ let best = null;
572
+ let bestDist = tolerance;
573
+ for (const c of [..._nullishCoalesce(options.candidates, () => ( [])), ...scale.ticks]) {
574
+ if (!Number.isFinite(c)) continue;
575
+ const d = Math.abs(px(c) - at);
576
+ if (d <= bestDist) {
577
+ best = c;
578
+ bestDist = d;
579
+ }
580
+ }
581
+ if (best !== null) return best;
582
+ }
583
+ return _chunkPRSJJZBScjs.snapChartValue.call(void 0, scene, value, options.step, _optionalChain([scale, 'optionalAccess', _24 => _24.ticks]));
584
+ }
585
+ function startChartPointDrag(controller, scene, target, start, options = {}) {
586
+ if (!canDragChartPoint(controller, scene, target)) return null;
587
+ const { seriesId, index } = target;
588
+ const spec = controller.getSpec();
589
+ const series = controller.getSeries(seriesId);
590
+ const secondary = series.axis === "secondary" && !!scene.valueScale2;
591
+ const scale = secondary ? scene.valueScale2 : scene.valueScale;
592
+ const xy = _chunkPRSJJZBScjs.chartFamily.call(void 0, spec) === "xy" && !!scene.xScale;
593
+ const startValue = _nullishCoalesce(series.values[index], () => ( 0));
594
+ const startX = _nullishCoalesce(_optionalChain([series, 'access', _25 => _25.x, 'optionalAccess', _26 => _26[index]]), () => ( null));
595
+ const startAt = _chunkPRSJJZBScjs.chartValueAtPoint.call(void 0, scene, start, secondary);
596
+ const startAtX = xy ? _chunkPRSJJZBScjs.chartXAtPoint.call(void 0, scene, start) : void 0;
597
+ if (startAt === void 0) return null;
598
+ const candidates = [0, startValue];
599
+ if (!xy) {
600
+ for (const s of spec.series) if (s.id !== seriesId && !s.hidden && s.axis === "secondary" === secondary) candidates.push(_nullishCoalesce(s.values[index], () => ( NaN)));
601
+ candidates.push(_nullishCoalesce(series.values[index - 1], () => ( NaN)), _nullishCoalesce(series.values[index + 1], () => ( NaN)));
602
+ }
603
+ controller.beginGesture(`drag:${seriesId}:${index}`);
604
+ let ended = false;
605
+ const [min, max] = scene.valueScale.domain;
606
+ const label = `Drag ${series.name}`;
607
+ return {
608
+ valueDomain: { min, max },
609
+ ...scene.valueScale2 ? { valueDomain2: { min: scene.valueScale2.domain[0], max: scene.valueScale2.domain[1] } } : {},
610
+ ...xy ? { xDomain: { min: scene.xScale.domain[0], max: scene.xScale.domain[1] } } : {},
611
+ target: { seriesId, index },
612
+ move(point, moveOptions = {}) {
613
+ if (ended) return;
614
+ const at = _chunkPRSJJZBScjs.chartValueAtPoint.call(void 0, scene, point, secondary);
615
+ if (at === void 0) return;
616
+ const factor = moveOptions.fine ? 0.1 : 1;
617
+ let dy = (at - startAt) * factor;
618
+ let dx = 0;
619
+ if (xy && startX !== null && startAtX !== void 0) {
620
+ const atX = _chunkPRSJJZBScjs.chartXAtPoint.call(void 0, scene, point);
621
+ if (atX !== void 0) dx = (atX - startAtX) * factor;
622
+ if (moveOptions.lockAxis) {
623
+ if (Math.abs(point.x - start.x) >= Math.abs(point.y - start.y)) dy = 0;
624
+ else dx = 0;
625
+ }
626
+ }
627
+ const snap = (v, s, c) => {
628
+ if (moveOptions.noSnap) return Number(v.toPrecision(12));
629
+ if (moveOptions.fine) {
630
+ const unit = options.step ? options.step / 10 : s.ticks.length > 1 ? (s.ticks[1] - s.ticks[0]) / 100 : void 0;
631
+ return _chunkPRSJJZBScjs.snapChartValue.call(void 0, scene, v, unit, s.ticks);
632
+ }
633
+ return snapChartDragValue(scene, v, { candidates: c, tolerancePx: options.tolerancePx, step: options.step, scale: s });
634
+ };
635
+ const y = snap(startValue + dy, scale, candidates);
636
+ if (xy && startX !== null) {
637
+ const x = dx !== 0 ? snap(startX + dx, scene.xScale, [0, startX]) : startX;
638
+ const xs = [..._nullishCoalesce(series.x, () => ( []))];
639
+ xs[index] = x;
640
+ controller.setValue(seriesId, index, y, { label });
641
+ if (x !== _optionalChain([controller, 'access', _27 => _27.getSeries, 'call', _28 => _28(seriesId), 'optionalAccess', _29 => _29.x, 'optionalAccess', _30 => _30[index]])) controller.updateSeries(seriesId, { x: xs });
642
+ } else controller.setValue(seriesId, index, y, { label });
643
+ },
644
+ end() {
645
+ if (ended) return;
646
+ ended = true;
647
+ controller.endGesture();
648
+ },
649
+ cancel() {
650
+ if (ended) return;
651
+ ended = true;
652
+ controller.cancelGesture();
653
+ }
654
+ };
655
+ }
656
+ function handleChartKey(controller, scene, event) {
657
+ if (event.metaKey || event.ctrlKey) return false;
658
+ const { selection, spec, gesture } = controller.getState();
659
+ switch (event.key) {
660
+ case "Escape":
661
+ if (gesture) {
662
+ controller.cancelGesture();
663
+ return true;
664
+ }
665
+ if (!selection) return false;
666
+ controller.select(null);
667
+ return true;
668
+ case "Delete":
669
+ case "Backspace":
670
+ if (event.altKey || !selection || selection.kind === "chart") return false;
671
+ controller.deleteSelection();
672
+ return true;
673
+ case "ArrowLeft":
674
+ case "ArrowRight": {
675
+ if (event.altKey || _optionalChain([selection, 'optionalAccess', _31 => _31.kind]) !== "point" && _optionalChain([selection, 'optionalAccess', _32 => _32.kind]) !== "category") return false;
676
+ const n = spec.categories.length;
677
+ const index = Math.max(0, Math.min(n - 1, selection.index + (event.key === "ArrowRight" ? 1 : -1)));
678
+ controller.select({ ...selection, index });
679
+ return true;
680
+ }
681
+ case "ArrowUp":
682
+ case "ArrowDown": {
683
+ if (_optionalChain([selection, 'optionalAccess', _33 => _33.kind]) !== "point" || _optionalChain([scene, 'optionalAccess', _34 => _34.percent])) return false;
684
+ const secondary = _optionalChain([controller, 'access', _35 => _35.getSeries, 'call', _36 => _36(selection.seriesId), 'optionalAccess', _37 => _37.axis]) === "secondary";
685
+ const ticks = _nullishCoalesce(_nullishCoalesce(_optionalChain([(secondary ? _optionalChain([scene, 'optionalAccess', _38 => _38.valueScale2]) : null), 'optionalAccess', _39 => _39.ticks]), () => ( _optionalChain([scene, 'optionalAccess', _40 => _40.valueScale, 'optionalAccess', _41 => _41.ticks]))), () => ( []));
686
+ const step = ticks.length > 1 ? ticks[1] - ticks[0] : 1;
687
+ const amount = event.shiftKey ? step : event.altKey ? step / 100 : step / 10;
688
+ controller.nudgeValue(selection.seriesId, selection.index, amount * (event.key === "ArrowUp" ? 1 : -1));
689
+ return true;
690
+ }
691
+ default:
692
+ return false;
693
+ }
694
+ }
695
+ function describeChartSelection(controller, selection = controller.getState().selection) {
696
+ if (!selection) return "Nothing selected";
697
+ const spec = controller.getSpec();
698
+ switch (selection.kind) {
699
+ case "chart":
700
+ return "Chart";
701
+ case "title":
702
+ return "Title";
703
+ case "subtitle":
704
+ return "Subtitle";
705
+ case "legend":
706
+ return "Legend";
707
+ case "axis":
708
+ return selection.axis === "x" ? _chunkPRSJJZBScjs.chartFamily.call(void 0, spec) === "xy" ? "X axis" : "Category axis" : selection.axis === "y" ? "Value axis" : "Secondary value axis";
709
+ case "series":
710
+ return `Series "${_nullishCoalesce(_optionalChain([controller, 'access', _42 => _42.getSeries, 'call', _43 => _43(selection.seriesId), 'optionalAccess', _44 => _44.name]), () => ( selection.seriesId))}"`;
711
+ case "category":
712
+ return `Category "${_nullishCoalesce(spec.categories[selection.index], () => ( selection.index))}"`;
713
+ case "trendline": {
714
+ const series = controller.getSeries(selection.seriesId);
715
+ const line = _optionalChain([series, 'optionalAccess', _45 => _45.trendlines, 'optionalAccess', _46 => _46[selection.index]]);
716
+ return `${line ? line.type.replace("-", " ") : "Trendline"} trendline on "${_nullishCoalesce(_optionalChain([series, 'optionalAccess', _47 => _47.name]), () => ( selection.seriesId))}"`;
717
+ }
718
+ case "point": {
719
+ const series = controller.getSeries(selection.seriesId);
720
+ const value = _optionalChain([series, 'optionalAccess', _48 => _48.values, 'access', _49 => _49[selection.index]]);
721
+ const x = _optionalChain([series, 'optionalAccess', _50 => _50.x, 'optionalAccess', _51 => _51[selection.index]]);
722
+ if (x !== void 0 && x !== null && _chunkPRSJJZBScjs.chartFamily.call(void 0, spec) === "xy") return `${_nullishCoalesce(_optionalChain([series, 'optionalAccess', _52 => _52.name]), () => ( selection.seriesId))}: (${x}, ${_nullishCoalesce(value, () => ( "blank"))})`;
723
+ return `${_nullishCoalesce(_optionalChain([series, 'optionalAccess', _53 => _53.name]), () => ( selection.seriesId))} \xB7 ${_nullishCoalesce(spec.categories[selection.index], () => ( selection.index))}: ${_nullishCoalesce(value, () => ( "blank"))}`;
724
+ }
725
+ }
726
+ }
727
+ function chartHitTest(scene, point, measure = (t, s, w = 400) => t.length * s * (w >= 600 ? 0.6 : 0.56)) {
728
+ for (let i = scene.marks.length - 1; i >= 0; i--) {
729
+ const m = scene.marks[i];
730
+ if (!m.target || m.role === "selection" || m.role === "background") continue;
731
+ let hit = false;
732
+ switch (m.kind) {
733
+ case "rect":
734
+ hit = point.x >= m.x && point.x <= m.x + m.width && point.y >= m.y && point.y <= m.y + m.height;
735
+ break;
736
+ case "circle":
737
+ hit = Math.hypot(point.x - m.cx, point.y - m.cy) <= Math.max(m.r, 4) + 2;
738
+ break;
739
+ case "line": {
740
+ const dx = m.x2 - m.x1;
741
+ const dy = m.y2 - m.y1;
742
+ const len2 = dx * dx + dy * dy;
743
+ const t = len2 ? Math.max(0, Math.min(1, ((point.x - m.x1) * dx + (point.y - m.y1) * dy) / len2)) : 0;
744
+ hit = Math.hypot(point.x - (m.x1 + t * dx), point.y - (m.y1 + t * dy)) <= 4;
745
+ break;
746
+ }
747
+ case "text": {
748
+ if (m.rotate) break;
749
+ const w = measure(m.text, m.size, m.weight);
750
+ const x0 = m.anchor === "start" ? m.x : m.anchor === "end" ? m.x - w : m.x - w / 2;
751
+ const y0 = m.baseline === "top" ? m.y : m.baseline === "bottom" ? m.y - m.size : m.y - m.size / 2;
752
+ hit = point.x >= x0 - 2 && point.x <= x0 + w + 2 && point.y >= y0 - 2 && point.y <= y0 + m.size + 2;
753
+ break;
754
+ }
755
+ default:
756
+ break;
757
+ }
758
+ if (hit) return m.target;
759
+ }
760
+ return point.x >= 0 && point.y >= 0 && point.x <= scene.width && point.y <= scene.height ? { kind: "chart" } : null;
761
+ }
762
+
763
+ // src/chart/tools.ts
764
+ var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
765
+ var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
766
+ var WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
767
+ var DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
768
+ var fail2 = (message) => {
769
+ throw new Error(message);
770
+ };
771
+ function resolve(result) {
772
+ if (!result.ok) fail2(_nullishCoalesce(result.message, () => ( "The change was rejected.")));
773
+ return result;
774
+ }
775
+ function createChartToolProvider(controller, options = {}) {
776
+ const size = _nullishCoalesce(options.size, () => ( { width: 640, height: 400 }));
777
+ const readOnlyReason = () => {
778
+ const r = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
779
+ if (!r) return null;
780
+ return typeof r === "string" ? r : "This chart is read-only.";
781
+ };
782
+ const spec = () => controller.getSpec();
783
+ const seriesById = (ref) => {
784
+ const s = _nullishCoalesce(spec().series.find((x) => x.id === ref), () => ( spec().series.find((x) => x.name === ref)));
785
+ if (!s) fail2(`There is no series "${ref}"; expected one of ${spec().series.map((x) => `"${x.id}" (${x.name})`).join(", ") || "(none yet; add one with add_chart_series)"}.`);
786
+ return s;
787
+ };
788
+ const categoryIndex = (ref) => {
789
+ const categories = spec().categories;
790
+ const index = typeof ref === "number" ? ref : categories.indexOf(ref);
791
+ if (!Number.isInteger(index) || index < 0 || index >= categories.length)
792
+ fail2(`There is no category ${JSON.stringify(ref)}; expected an index from 0 to ${categories.length - 1} or one of ${categories.slice(0, 20).map((c) => JSON.stringify(c)).join(", ")}.`);
793
+ return index;
794
+ };
795
+ const table = () => _chunkPRSJJZBScjs.chartDataToTable.call(void 0, spec());
796
+ const Parser = () => _nullishCoalesce(options.DOMParser, () => ( globalThis.DOMParser));
797
+ const getChart = {
798
+ name: "get_chart",
799
+ title: "Read the chart",
800
+ description: "Returns the chart spec (type, titles, axes, legend, series with ids and settings), its data as a table (header row of series names, then one row per category), the current selection, and a summary at the given size: per-series min / max / mean / trend, the axes as drawn, layout warnings (hidden labels, thinned lines), a colour check and a plain-language description. Use the series ids and category indexes it returns with the write tools.",
801
+ inputSchema: { type: "object", properties: { width: _chunkU6IZKC7Bcjs.WIDTH, height: _chunkU6IZKC7Bcjs.HEIGHT }, additionalProperties: false },
802
+ annotations: { title: "Read the chart", ...READ },
803
+ handler: ({ width = size.width, height = size.height }) => {
804
+ const reason = readOnlyReason();
805
+ return {
806
+ spec: controller.toJSON(),
807
+ table: table(),
808
+ selection: controller.getState().selection,
809
+ summary: _chunkPRSJJZBScjs.summarizeChart.call(void 0, controller.toJSON(), { width, height }),
810
+ issues: controller.getState().issues,
811
+ ...reason ? { readOnly: reason } : {}
812
+ };
813
+ }
814
+ };
815
+ const renderChart = {
816
+ name: "render_chart",
817
+ title: "Render the chart",
818
+ description: 'Draws the chart without changing it, to check it visually: SVG text by default, or a PNG image (format "png") when the host can rasterize. Look for overlapping or hidden labels; get_chart lists layout warnings without a render.',
819
+ inputSchema: {
820
+ type: "object",
821
+ properties: { width: _chunkU6IZKC7Bcjs.WIDTH, height: _chunkU6IZKC7Bcjs.HEIGHT, format: { type: "string", enum: ["svg", "png"], description: "svg (default) or png (only when the host supports it)." } },
822
+ additionalProperties: false
823
+ },
824
+ annotations: { title: "Render the chart", ...READ },
825
+ handler: async ({ width = size.width, height = size.height, format = "svg" }) => {
826
+ const svg = _chunkPRSJJZBScjs.chartToSvg.call(void 0, controller.toJSON(), { width, height });
827
+ if (format === "png") {
828
+ if (!options.rasterize) fail2('This host cannot rasterize charts; call render_chart with format "svg".');
829
+ const image = await options.rasterize(svg, { width, height });
830
+ return { content: [{ type: "image", data: image.data, mimeType: image.mimeType }], structuredContent: { width, height, mimeType: image.mimeType } };
831
+ }
832
+ return { svg, width, height };
833
+ }
834
+ };
835
+ const checkColors = {
836
+ name: "check_chart_colors",
837
+ title: "Check chart colours",
838
+ description: "Checks that colours can be told apart with normal vision and with protanopia, deuteranopia and tritanopia (OKLab distance after simulation), and that they stand out from the background. Checks the chart's own series (or slice) colours unless colours are given. Returns the closest pair per vision, contrast ratios and problems as sentences.",
839
+ inputSchema: {
840
+ type: "object",
841
+ properties: {
842
+ colors: { type: "array", items: { type: "string" }, minItems: 1, description: "Colours to check instead of the chart's (hex or rgb())." },
843
+ surface: { type: "string", description: "Background colour (default white)." }
844
+ },
845
+ additionalProperties: false
846
+ },
847
+ annotations: { title: "Check chart colours", ...READ },
848
+ handler: ({ colors, surface }) => {
849
+ const s = spec();
850
+ const visible = s.series.filter((x) => !x.hidden);
851
+ const own = _chunkPRSJJZBScjs.chartColorsByCategory.call(void 0, s.type) ? s.categories.map((_, i) => _optionalChain([visible, 'access', _54 => _54[0], 'optionalAccess', _55 => _55.pointColors, 'optionalAccess', _56 => _56[i]]) || _chunkPRSJJZBScjs.chartSliceColor.call(void 0, s, i)) : visible.map((x) => _chunkPRSJJZBScjs.chartSeriesColor.call(void 0, s, s.series.indexOf(x)));
852
+ return { colors: _nullishCoalesce(colors, () => ( own)), ..._chunkPRSJJZBScjs.checkChartColors.call(void 0, _nullishCoalesce(colors, () => ( own)), { surface }) };
853
+ }
854
+ };
855
+ const recommend = {
856
+ name: "recommend_chart",
857
+ title: "Recommend chart types",
858
+ description: "Suggests chart types for data, best first, each with a reason: from CSV text when given, else from the chart's current data. Uses the shape of the data (series count, points, dates, negative values).",
859
+ inputSchema: { type: "object", properties: { csv: { type: "string", description: "CSV or TSV text to analyse instead of the current data (first row may be series names, first column categories)." } }, additionalProperties: false },
860
+ annotations: { title: "Recommend chart types", ...READ },
861
+ handler: ({ csv }) => {
862
+ const rows = csv !== void 0 ? _chunkTNNJXF46cjs.parseCsv.call(void 0, csv, { delimiter: csv.includes(" ") ? " " : "," }) : table();
863
+ const recommendations = recommendChartTypes(rows);
864
+ if (!recommendations.length) fail2("There are no numbers to chart: give CSV with at least one numeric column.");
865
+ return { recommendations };
866
+ }
867
+ };
868
+ const getTrendline = {
869
+ name: "get_chart_trendline",
870
+ title: "Read a trendline fit",
871
+ description: "Returns a trendline's fitted coefficients (in x for category charts, x = 1, 2, 3, \u2026), R\xB2, equation and the number of points, or the moving-average points.",
872
+ inputSchema: {
873
+ type: "object",
874
+ properties: { series: _chunkU6IZKC7Bcjs.SERIES_REF, index: { type: "integer", minimum: 0, description: "Trendline index on the series (0 = first)." } },
875
+ required: ["series", "index"],
876
+ additionalProperties: false
877
+ },
878
+ annotations: { title: "Read a trendline fit", ...READ },
879
+ handler: ({ series, index }) => {
880
+ const s = seriesById(series);
881
+ const line = _optionalChain([s, 'access', _57 => _57.trendlines, 'optionalAccess', _58 => _58[index]]);
882
+ if (!line) fail2(`"${s.name}" has no trendline ${index}; it has ${_nullishCoalesce(_optionalChain([s, 'access', _59 => _59.trendlines, 'optionalAccess', _60 => _60.length]), () => ( 0))}.`);
883
+ if (line.type === "moving-average") return { trendline: line, points: controller.trendlineMovingAverage(s.id, index) };
884
+ const fit = controller.trendlineFit(s.id, index);
885
+ if (!fit) fail2(`The ${line.type} trendline on "${s.name}" cannot be fitted: it needs more points (and positive values for log, exponential and power fits).`);
886
+ return { trendline: line, coefficients: fit.coefficients, r2: fit.r2, equation: fit.equation, points: fit.points };
887
+ }
888
+ };
889
+ const listPresets = {
890
+ name: "list_chart_presets",
891
+ title: "List chart presets",
892
+ description: "Lists the quick layouts and colour schemes apply_chart_preset accepts, with what each changes.",
893
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
894
+ annotations: { title: "List chart presets", ...READ },
895
+ handler: () => ({
896
+ layouts: _chunkPRSJJZBScjs.CHART_LAYOUT_PRESETS.map(({ id, label, description }) => ({ id, label, description })),
897
+ colorSchemes: _chunkPRSJJZBScjs.CHART_COLOR_SCHEMES.map(({ id, label, description, palette }) => ({ id, label, description, palette }))
898
+ })
899
+ };
900
+ const exportMermaid = {
901
+ name: "export_chart_mermaid",
902
+ title: "Export chart as Mermaid",
903
+ description: 'Returns the chart as Mermaid text (xychart-beta, pie or radar-beta) for Markdown or chat. With meta (default true) one "%% @pixerate {\u2026}" comment keeps what Mermaid cannot express, so import_chart_mermaid gets the chart back exactly.',
904
+ inputSchema: { type: "object", properties: { meta: { type: "boolean", description: "Include the round-trip comment (default true)." } }, additionalProperties: false },
905
+ annotations: { title: "Export chart as Mermaid", ...READ },
906
+ handler: ({ meta = true }) => ({ mermaid: _chunkPRSJJZBScjs.chartToMermaid.call(void 0, controller.toJSON(), { meta }) })
907
+ };
908
+ const exportOoxml = {
909
+ name: "export_chart_ooxml",
910
+ title: "Export chart as Office XML",
911
+ description: "Returns the chart as a DrawingML chart part (c:chartSpace) for Word or PowerPoint files, with what could not be written as sentences.",
912
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
913
+ annotations: { title: "Export chart as Office XML", ...READ },
914
+ handler: () => _chunkPIMCHMZEcjs.chartToOoxml.call(void 0, controller.toJSON())
915
+ };
916
+ const updateChart = {
917
+ name: "update_chart",
918
+ title: "Change chart presentation",
919
+ description: "Changes how the chart looks, as one undo step: type, titles, axes (xAxis = categories or numeric x, yAxis = values, yAxis2 = secondary values), legend, stacking, orientation, curve, points, data labels and their position, palette, donut hole, bar spacing, histogram bins, waterfall total. All-or-nothing: invalid values change nothing and come back as sentences. Returns the new spec.",
920
+ inputSchema: {
921
+ type: "object",
922
+ properties: _chunkU6IZKC7Bcjs.CHART_UPDATE_PROPERTIES,
923
+ additionalProperties: false
924
+ },
925
+ annotations: { title: "Change chart presentation", ...WRITE_IDEMPOTENT },
926
+ handler: (args) => {
927
+ if (!Object.keys(args).length) fail2('Nothing to change: pass at least one field, e.g. {"type": "line"}.');
928
+ resolve(controller.update(args));
929
+ return { spec: controller.toJSON() };
930
+ }
931
+ };
932
+ const setData = {
933
+ name: "set_chart_data",
934
+ title: "Replace chart data",
935
+ description: "Replaces the categories and / or the series as one undo step. Each series has one value per category (padded with gaps). Returns the data table and the series ids.",
936
+ inputSchema: {
937
+ type: "object",
938
+ properties: {
939
+ categories: { type: "array", items: { type: "string" }, description: "Category labels in order." },
940
+ series: {
941
+ type: "array",
942
+ description: "The new series (replaces all).",
943
+ items: { type: "object", properties: { id: { type: "string", description: "Stable id (optional)." }, ..._chunkU6IZKC7Bcjs.SERIES_PROPS }, required: ["name", "values"], additionalProperties: false }
944
+ }
945
+ },
946
+ additionalProperties: false
947
+ },
948
+ annotations: { title: "Replace chart data", ...DESTRUCTIVE },
949
+ handler: (args) => {
950
+ if (!args.categories && !args.series) fail2("Pass categories, series or both.");
951
+ resolve(controller.setData(args));
952
+ return { table: table(), series: spec().series.map((s) => ({ id: s.id, name: s.name })) };
953
+ }
954
+ };
955
+ const setValues = {
956
+ name: "set_chart_values",
957
+ title: "Set chart values",
958
+ description: "Sets individual values as one undo step (all-or-nothing). Series by id or name, category by index or label. Returns the values as stored.",
959
+ inputSchema: {
960
+ type: "object",
961
+ properties: {
962
+ cells: {
963
+ type: "array",
964
+ minItems: 1,
965
+ description: "Values to set.",
966
+ items: { type: "object", properties: { series: _chunkU6IZKC7Bcjs.SERIES_REF, category: _chunkU6IZKC7Bcjs.CATEGORY_REF, value: _chunkU6IZKC7Bcjs.VALUE }, required: ["series", "category", "value"], additionalProperties: false }
967
+ }
968
+ },
969
+ required: ["cells"],
970
+ additionalProperties: false
971
+ },
972
+ annotations: { title: "Set chart values", ...WRITE_IDEMPOTENT },
973
+ handler: async ({ cells }) => {
974
+ const resolved = cells.map((c) => ({ seriesId: seriesById(c.series).id, index: categoryIndex(c.category), value: c.value }));
975
+ const parsed = resolved.map((c) => _chunkPRSJJZBScjs.parseChartValue.call(void 0, c.value));
976
+ const bad = resolved.find((_, i) => parsed[i] === void 0);
977
+ if (bad) fail2(`"${String(bad.value)}" is not a number; pass a number, numeric text or null for a gap. Nothing was changed.`);
978
+ if (options.director && parsed.every((v) => typeof v === "number")) {
979
+ const results = await options.director.perform(_nullishCoalesce(options.actor, () => ( { id: "agent", name: "Agent" })), resolved.map((c, i) => ({ kind: "setValue", seriesId: c.seriesId, index: c.index, value: parsed[i] })));
980
+ const failed = results.find((r) => !r.ok);
981
+ if (failed && !failed.ok) fail2(failed.message);
982
+ } else resolve(controller.setValues(resolved));
983
+ return { cells: resolved.map((c) => ({ series: c.seriesId, category: spec().categories[c.index], index: c.index, value: controller.getSeries(c.seriesId).values[c.index] })) };
984
+ }
985
+ };
986
+ const addSeries = {
987
+ name: "add_chart_series",
988
+ title: "Add a series",
989
+ description: "Adds a series (values padded to the categories) and selects it. Returns its id and stored values.",
990
+ inputSchema: {
991
+ type: "object",
992
+ properties: { ..._chunkU6IZKC7Bcjs.SERIES_PROPS, id: { type: "string", description: "Id to use (default: series-N)." }, at: { type: "integer", minimum: 0, description: "Position (default: last)." } },
993
+ required: ["name"],
994
+ additionalProperties: false
995
+ },
996
+ annotations: { title: "Add a series", ...WRITE },
997
+ handler: ({ at, ...input }) => {
998
+ const clean = Object.fromEntries(Object.entries(input).filter(([, v]) => v !== null));
999
+ const result = resolve(controller.addSeries(clean, { at }));
1000
+ const s = controller.getSeries(result.id);
1001
+ return { id: s.id, name: s.name, values: s.values };
1002
+ }
1003
+ };
1004
+ const updateSeries = {
1005
+ name: "update_chart_series",
1006
+ title: "Change a series",
1007
+ description: "Renames, recolours, hides, moves to the secondary axis or changes how a series is drawn (marker, curve, labels, format, x values, bubble sizes, point colours). null resets a field. Values change with set_chart_values. Returns the series.",
1008
+ inputSchema: {
1009
+ type: "object",
1010
+ properties: { series: _chunkU6IZKC7Bcjs.SERIES_REF, ...Object.fromEntries(Object.entries(_chunkU6IZKC7Bcjs.SERIES_PROPS).filter(([k]) => k !== "values")) },
1011
+ required: ["series"],
1012
+ additionalProperties: false
1013
+ },
1014
+ annotations: { title: "Change a series", ...WRITE_IDEMPOTENT },
1015
+ handler: ({ series, ...patch }) => {
1016
+ const id = seriesById(series).id;
1017
+ if (!Object.keys(patch).length) fail2('Nothing to change: pass a field such as {"color": "#e8710a"} or {"axis": "secondary"}.');
1018
+ resolve(controller.updateSeries(id, patch));
1019
+ return { series: controller.getSeries(id) };
1020
+ }
1021
+ };
1022
+ const removeSeries = {
1023
+ name: "remove_chart_series",
1024
+ title: "Delete a series",
1025
+ description: "Deletes a series and its values. Returns the remaining series ids.",
1026
+ inputSchema: { type: "object", properties: { series: _chunkU6IZKC7Bcjs.SERIES_REF }, required: ["series"], additionalProperties: false },
1027
+ annotations: { title: "Delete a series", ...DESTRUCTIVE },
1028
+ handler: ({ series }) => {
1029
+ resolve(controller.removeSeries(seriesById(series).id));
1030
+ return { series: spec().series.map((s) => ({ id: s.id, name: s.name })) };
1031
+ }
1032
+ };
1033
+ const addCategory = {
1034
+ name: "add_chart_category",
1035
+ title: "Add a category",
1036
+ description: "Inserts a category (a gap in every series) and returns its index.",
1037
+ inputSchema: {
1038
+ type: "object",
1039
+ properties: { label: { type: "string", description: "Category label (default: its number)." }, at: { type: "integer", minimum: 0, description: "Index to insert at (default: the end)." } },
1040
+ additionalProperties: false
1041
+ },
1042
+ annotations: { title: "Add a category", ...WRITE },
1043
+ handler: ({ label, at }) => {
1044
+ const result = controller.addCategory(label, { at });
1045
+ return { index: result.index, categories: spec().categories };
1046
+ }
1047
+ };
1048
+ const removeCategory = {
1049
+ name: "remove_chart_category",
1050
+ title: "Delete a category",
1051
+ description: "Deletes a category and its value in every series. Returns the remaining categories.",
1052
+ inputSchema: { type: "object", properties: { category: _chunkU6IZKC7Bcjs.CATEGORY_REF }, required: ["category"], additionalProperties: false },
1053
+ annotations: { title: "Delete a category", ...DESTRUCTIVE },
1054
+ handler: ({ category }) => {
1055
+ resolve(controller.removeCategory(categoryIndex(category)));
1056
+ return { categories: spec().categories };
1057
+ }
1058
+ };
1059
+ const importCsv = {
1060
+ name: "import_chart_csv",
1061
+ title: "Import chart data from CSV",
1062
+ description: 'Replaces the data with CSV or TSV text as one undo step. By default the first row holds series names and the first column categories (detected when "auto"). chartType "auto" also picks the recommended chart type (two unlabelled numeric columns become a scatter of x against y). Returns the data table, the chart type and cells that were not numbers.',
1063
+ inputSchema: {
1064
+ type: "object",
1065
+ properties: {
1066
+ csv: { type: "string", description: "CSV or TSV text (tabs are detected)." },
1067
+ seriesIn: { type: "string", enum: ["columns", "rows"], description: 'Series run down columns (default) or along rows. Ignored with chartType "auto".' },
1068
+ header: { type: ["boolean", "string"], enum: [true, false, "auto"], description: 'The first row holds series names: true (default), false or "auto". Ignored with chartType "auto".' },
1069
+ chartType: { type: "string", enum: ["keep", "auto"], description: '"keep" (default) keeps the chart type; "auto" reads the table shape and picks a type.' }
1070
+ },
1071
+ required: ["csv"],
1072
+ additionalProperties: false
1073
+ },
1074
+ annotations: { title: "Import chart data from CSV", ...DESTRUCTIVE },
1075
+ handler: ({ csv, seriesIn, header, chartType = "keep" }) => {
1076
+ if (chartType === "auto") {
1077
+ const rows = _chunkTNNJXF46cjs.parseCsv.call(void 0, csv, { delimiter: csv.includes(" ") ? " " : "," });
1078
+ const built = chartFromTable(rows);
1079
+ if (!built.series.length || !built.categories.length) fail2("There is no data to import: expected at least one row of numbers.");
1080
+ resolve(controller.update({ type: built.type, stacking: built.stacking, categories: built.categories, series: built.series }, { label: "Import data" }));
1081
+ return { type: built.type, table: table(), issues: built.issues };
1082
+ }
1083
+ const result = resolve(controller.importCsv(csv, { seriesIn, header }));
1084
+ return { type: spec().type, table: table(), issues: _nullishCoalesce(result.issues, () => ( [])) };
1085
+ }
1086
+ };
1087
+ const importMermaid = {
1088
+ name: "import_chart_mermaid",
1089
+ title: "Import a Mermaid chart",
1090
+ description: 'Replaces the chart with Mermaid xychart-beta, pie or radar-beta text as one undo step (a "%% @pixerate" comment from export_chart_mermaid restores everything else). Lines that cannot be read are skipped and listed with their line numbers. Returns the spec.',
1091
+ inputSchema: { type: "object", properties: { mermaid: { type: "string", description: "Mermaid text, optionally inside a ```mermaid fence." } }, required: ["mermaid"], additionalProperties: false },
1092
+ annotations: { title: "Import a Mermaid chart", ...DESTRUCTIVE },
1093
+ handler: ({ mermaid }) => {
1094
+ const fenced = /```(?:mermaid)?\s*\n([\s\S]*?)```/.exec(mermaid);
1095
+ const read = _chunkPRSJJZBScjs.chartFromMermaid.call(void 0, fenced ? fenced[1] : mermaid);
1096
+ if (!read.family) fail2(read.issues.map((i) => i.message).join(" ") || "Expected Mermaid starting with xychart-beta, pie or radar-beta.");
1097
+ resolve(controller.update({ ...read.spec }, { label: "Import Mermaid chart" }));
1098
+ return { spec: controller.toJSON(), issues: read.issues };
1099
+ }
1100
+ };
1101
+ const importOoxml = {
1102
+ name: "import_chart_ooxml",
1103
+ title: "Import an Office chart",
1104
+ description: "Replaces the chart with an Office chart part (DrawingML c:chartSpace, or chartex for waterfall, funnel, treemap, histogram and box charts) as one undo step. Returns the spec, problems, and the features the editor cannot hold.",
1105
+ inputSchema: { type: "object", properties: { xml: { type: "string", description: "The chart part XML, e.g. word/charts/chart1.xml." } }, required: ["xml"], additionalProperties: false },
1106
+ annotations: { title: "Import an Office chart", ...DESTRUCTIVE },
1107
+ handler: ({ xml }) => {
1108
+ const read = _chunkPIMCHMZEcjs.chartFromOoxml.call(void 0, xml, { DOMParser: Parser() });
1109
+ if (read.issues.some((i) => i.path === "" || i.path === "plotArea")) fail2(read.issues.map((i) => i.message).join(" "));
1110
+ resolve(controller.update({ ...read.spec }, { label: "Import Office chart" }));
1111
+ return { spec: controller.toJSON(), issues: read.issues.map((i) => i.message), unsupported: read.unsupported, lossless: read.lossless };
1112
+ }
1113
+ };
1114
+ const addTrendline = {
1115
+ name: "add_chart_trendline",
1116
+ title: "Add a trendline",
1117
+ description: "Fits a trendline to a series and draws it (unstacked bar, line, area, scatter and bubble charts). Returns its index and the fit: coefficients, R\xB2 and equation.",
1118
+ inputSchema: { type: "object", properties: { series: _chunkU6IZKC7Bcjs.SERIES_REF, ..._chunkU6IZKC7Bcjs.TRENDLINE_PROPS }, required: ["series", "type"], additionalProperties: false },
1119
+ annotations: { title: "Add a trendline", ...WRITE },
1120
+ handler: ({ series, ...line }) => {
1121
+ const s = seriesById(series);
1122
+ const result = resolve(controller.addTrendline(s.id, line));
1123
+ return { series: s.id, index: result.index, ...result.fit ? { coefficients: result.fit.coefficients, r2: result.fit.r2, equation: result.fit.equation } : {} };
1124
+ }
1125
+ };
1126
+ const removeTrendline = {
1127
+ name: "remove_chart_trendline",
1128
+ title: "Delete a trendline",
1129
+ description: "Deletes a trendline from a series. Returns the series' remaining trendlines.",
1130
+ inputSchema: {
1131
+ type: "object",
1132
+ properties: { series: _chunkU6IZKC7Bcjs.SERIES_REF, index: { type: "integer", minimum: 0, description: "Trendline index on the series (0 = first)." } },
1133
+ required: ["series", "index"],
1134
+ additionalProperties: false
1135
+ },
1136
+ annotations: { title: "Delete a trendline", ...DESTRUCTIVE },
1137
+ handler: ({ series, index }) => {
1138
+ const s = seriesById(series);
1139
+ resolve(controller.removeTrendline(s.id, index));
1140
+ return { series: s.id, trendlines: _nullishCoalesce(_optionalChain([controller, 'access', _61 => _61.getSeries, 'call', _62 => _62(s.id), 'optionalAccess', _63 => _63.trendlines]), () => ( [])) };
1141
+ }
1142
+ };
1143
+ const applyPreset = {
1144
+ name: "apply_chart_preset",
1145
+ title: "Apply a chart style",
1146
+ description: "Applies a quick layout (legend, labels and grid lines together) and / or a colour scheme as one undo step. See list_chart_presets for the names. Returns the new spec.",
1147
+ inputSchema: {
1148
+ type: "object",
1149
+ properties: {
1150
+ layout: { type: "string", enum: _chunkPRSJJZBScjs.CHART_LAYOUT_PRESETS.map((p) => p.id), description: "Quick layout id." },
1151
+ colors: { type: "string", enum: _chunkPRSJJZBScjs.CHART_COLOR_SCHEMES.map((p) => p.id), description: "Colour scheme id." }
1868
1152
  },
1869
- run: (_ctx, params) => {
1870
- const result = controller.importCsv(params.csv, { seriesIn: params.seriesIn, header: params.header });
1871
- check(result);
1872
- return { table: chartDataToTable(controller.getSpec()), issues: _nullishCoalesce(result.issues, () => ( [])) };
1153
+ additionalProperties: false
1154
+ },
1155
+ annotations: { title: "Apply a chart style", ...WRITE_IDEMPOTENT },
1156
+ handler: ({ layout, colors }) => {
1157
+ resolve(controller.applyPreset({ layout, colors }));
1158
+ return { spec: controller.toJSON() };
1159
+ }
1160
+ };
1161
+ const transpose = {
1162
+ name: "transpose_chart",
1163
+ title: "Switch rows and columns",
1164
+ description: "Swaps series and categories (each category becomes a series) as one undo step. Per-series colours and settings are dropped. Returns the data table.",
1165
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
1166
+ annotations: { title: "Switch rows and columns", ...WRITE },
1167
+ handler: () => {
1168
+ resolve(controller.transpose());
1169
+ return { table: table(), series: spec().series.map((s) => ({ id: s.id, name: s.name })) };
1170
+ }
1171
+ };
1172
+ const select = {
1173
+ name: "select_chart_element",
1174
+ title: "Select a chart element",
1175
+ description: 'Selects part of the chart (as a click would), for press_chart_key and for the user to see. kind "none" clears the selection. Returns the selection.',
1176
+ inputSchema: {
1177
+ type: "object",
1178
+ properties: {
1179
+ kind: { type: "string", enum: ["none", "chart", "title", "subtitle", "legend", "axis", "series", "point", "category", "trendline"], description: "What to select." },
1180
+ series: { ..._chunkU6IZKC7Bcjs.SERIES_REF, description: "Series id or name (series, point, trendline)." },
1181
+ category: { ..._chunkU6IZKC7Bcjs.CATEGORY_REF, description: "Category index or label (point, category)." },
1182
+ axis: { type: "string", enum: ["x", "y", "y2"], description: "Axis (axis)." },
1183
+ index: { type: "integer", minimum: 0, description: "Trendline index (trendline)." }
1184
+ },
1185
+ required: ["kind"],
1186
+ additionalProperties: false
1187
+ },
1188
+ annotations: { title: "Select a chart element", ...WRITE_IDEMPOTENT },
1189
+ handler: async ({ kind, series, category, axis, index }) => {
1190
+ let sel;
1191
+ const need = (v, what) => v === void 0 && fail2(`kind "${kind}" needs ${what}.`);
1192
+ switch (kind) {
1193
+ case "none":
1194
+ sel = null;
1195
+ break;
1196
+ case "axis":
1197
+ need(axis, "axis");
1198
+ sel = { kind, axis };
1199
+ break;
1200
+ case "series":
1201
+ need(series, "series");
1202
+ sel = { kind, seriesId: seriesById(series).id };
1203
+ break;
1204
+ case "point":
1205
+ need(series, "series");
1206
+ need(category, "category");
1207
+ sel = { kind, seriesId: seriesById(series).id, index: categoryIndex(category) };
1208
+ break;
1209
+ case "category":
1210
+ need(category, "category");
1211
+ sel = { kind, index: categoryIndex(category) };
1212
+ break;
1213
+ case "trendline":
1214
+ need(series, "series");
1215
+ need(index, "index");
1216
+ sel = { kind, seriesId: seriesById(series).id, index };
1217
+ break;
1218
+ default:
1219
+ sel = { kind };
1873
1220
  }
1221
+ if (options.director) await options.director.perform(_nullishCoalesce(options.actor, () => ( { id: "agent", name: "Agent" })), [{ kind: "select", selection: sel }]);
1222
+ else controller.select(sel);
1223
+ const now = controller.getState().selection;
1224
+ if (sel && !now) fail2(`Nothing to select there (e.g. the chart has no ${kind} of that kind).`);
1225
+ return { selection: now };
1226
+ }
1227
+ };
1228
+ const drag = {
1229
+ name: "drag_chart_point",
1230
+ title: "Drag a chart point",
1231
+ description: "Drags on the chart as a pointer would, through the same code as the mouse: from a bar or point to a new position, with the same snapping (to other values, zero and ticks), fine adjustment (fine: Alt) and axis lock on scatter charts (lockAxis: Shift). Positions are 0\u20131 fractions of the chart's width and height at the given size (use render_chart to see where marks are). One undo step. Returns what was dragged and the value before and after.",
1232
+ inputSchema: {
1233
+ type: "object",
1234
+ properties: {
1235
+ from: { type: "object", description: "Start, as fractions of the chart (0,0 top-left).", properties: { x: { type: "number", minimum: 0, maximum: 1, description: "Fraction of the width." }, y: { type: "number", minimum: 0, maximum: 1, description: "Fraction of the height." } }, required: ["x", "y"], additionalProperties: false },
1236
+ to: { type: "object", description: "End, as fractions of the chart.", properties: { x: { type: "number", minimum: 0, maximum: 1, description: "Fraction of the width." }, y: { type: "number", minimum: 0, maximum: 1, description: "Fraction of the height." } }, required: ["x", "y"], additionalProperties: false },
1237
+ fine: { type: "boolean", description: "Move a tenth as far (Alt)." },
1238
+ lockAxis: { type: "boolean", description: "Scatter charts: change only the axis moved along most (Shift)." },
1239
+ width: _chunkU6IZKC7Bcjs.WIDTH,
1240
+ height: _chunkU6IZKC7Bcjs.HEIGHT
1241
+ },
1242
+ required: ["from", "to"],
1243
+ additionalProperties: false
1874
1244
  },
1875
- {
1876
- ...base,
1877
- id: `${prefix}exportSvg`,
1878
- label: "Export Chart as SVG",
1879
- readOnly: true,
1880
- keywords: ["chart", "svg", "export", "image"],
1881
- description: "Returns the chart drawn as a standalone SVG string.",
1882
- params: { type: "object", properties: { width: { type: "number", minimum: 50 }, height: { type: "number", minimum: 50 } }, additionalProperties: false },
1883
- run: (_ctx, params = {}) => ({
1884
- svg: chartToSvg(controller.getSpec(), { width: _nullishCoalesce(_optionalChain([params, 'optionalAccess', _27 => _27.width]), () => ( 640)), height: _nullishCoalesce(_optionalChain([params, 'optionalAccess', _28 => _28.height]), () => ( 400)) })
1885
- })
1245
+ annotations: { title: "Drag a chart point", ...WRITE },
1246
+ handler: ({ from, to, fine, lockAxis, width = size.width, height = size.height }) => {
1247
+ const scene = _chunkPRSJJZBScjs.layoutChart.call(void 0, controller.toJSON(), { width, height });
1248
+ const start = { x: from.x * width, y: from.y * height };
1249
+ const target = chartHitTest(scene, start);
1250
+ if (_optionalChain([target, 'optionalAccess', _64 => _64.kind]) !== "point") fail2(`There is no bar or point at (${from.x}, ${from.y}); ${target ? `that is the ${target.kind}` : "that is outside the chart"}. Use render_chart to find marks.`);
1251
+ const t = target;
1252
+ const before = _nullishCoalesce(_optionalChain([controller, 'access', _65 => _65.getSeries, 'call', _66 => _66(t.seriesId), 'optionalAccess', _67 => _67.values, 'access', _68 => _68[t.index]]), () => ( null));
1253
+ const session = startChartPointDrag(controller, scene, t, start);
1254
+ if (!session) fail2("That point cannot be dragged here (percent-stacked charts, pies and gaps cannot be dragged).");
1255
+ session.move({ x: to.x * width, y: to.y * height }, { fine, lockAxis });
1256
+ session.end();
1257
+ const s = controller.getSeries(t.seriesId);
1258
+ return { series: s.id, category: spec().categories[t.index], index: t.index, before, after: s.values[t.index], ...s.x ? { x: s.x[t.index] } : {} };
1259
+ }
1260
+ };
1261
+ const key = {
1262
+ name: "press_chart_key",
1263
+ title: "Press a key on the chart",
1264
+ description: "Sends a key to the chart as the keyboard would, acting on the selection (select_chart_element first): \u2190 \u2192 move between categories, \u2191 \u2193 change the selected value by a tenth of a tick (shift: a whole tick, alt: a hundredth), Delete removes the selection, Escape clears it. Returns whether the key did anything and the new selection.",
1265
+ inputSchema: {
1266
+ type: "object",
1267
+ properties: {
1268
+ key: { type: "string", enum: ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Delete", "Backspace", "Escape"], description: "Key name." },
1269
+ shift: { type: "boolean", description: "Hold Shift." },
1270
+ alt: { type: "boolean", description: "Hold Alt / Option." }
1271
+ },
1272
+ required: ["key"],
1273
+ additionalProperties: false
1886
1274
  },
1887
- {
1888
- ...base,
1889
- id: `${prefix}undo`,
1890
- label: "Undo Chart Edit",
1891
- keywords: ["chart", "undo"],
1892
- enabled: () => controller.getState().canUndo || "Nothing to undo",
1893
- run: () => ({ undone: controller.undo() })
1275
+ annotations: { title: "Press a key on the chart", ...DESTRUCTIVE },
1276
+ handler: ({ key: k, shift, alt }) => {
1277
+ const scene = _chunkPRSJJZBScjs.layoutChart.call(void 0, controller.toJSON(), size);
1278
+ const used = handleChartKey(controller, scene, { key: k, shiftKey: shift, altKey: alt });
1279
+ const sel = controller.getState().selection;
1280
+ const value = _optionalChain([sel, 'optionalAccess', _69 => _69.kind]) === "point" ? _nullishCoalesce(_optionalChain([controller, 'access', _70 => _70.getSeries, 'call', _71 => _71(sel.seriesId), 'optionalAccess', _72 => _72.values, 'access', _73 => _73[sel.index]]), () => ( null)) : void 0;
1281
+ return { used, selection: sel, ...value !== void 0 ? { value } : {} };
1282
+ }
1283
+ };
1284
+ const undo = {
1285
+ name: "undo_chart",
1286
+ title: "Undo a chart edit",
1287
+ description: "Undoes the last chart edit. Returns whether something was undone and what the next undo would be.",
1288
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
1289
+ annotations: { title: "Undo a chart edit", ...WRITE },
1290
+ handler: () => {
1291
+ const label = controller.getState().undoLabel;
1292
+ const undone = controller.undo();
1293
+ return { undone, ...undone ? { label } : {}, next: controller.getState().undoLabel || null };
1294
+ }
1295
+ };
1296
+ const redo = {
1297
+ name: "redo_chart",
1298
+ title: "Redo a chart edit",
1299
+ description: "Redoes the last undone chart edit. Returns whether something was redone.",
1300
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
1301
+ annotations: { title: "Redo a chart edit", ...WRITE },
1302
+ handler: () => {
1303
+ const label = controller.getState().redoLabel;
1304
+ const redone = controller.redo();
1305
+ return { redone, ...redone ? { label } : {} };
1306
+ }
1307
+ };
1308
+ const reads = [getChart, renderChart, checkColors, recommend, getTrendline, listPresets, exportMermaid, exportOoxml];
1309
+ const writes = [updateChart, setData, setValues, addSeries, updateSeries, removeSeries, addCategory, removeCategory, importCsv, importMermaid, addTrendline, removeTrendline, applyPreset, transpose, select, drag, key, undo, redo];
1310
+ return {
1311
+ id: _nullishCoalesce(options.id, () => ( "chart")),
1312
+ namespace: options.namespace,
1313
+ tools: () => {
1314
+ if (readOnlyReason()) return reads;
1315
+ return [...reads, ...writes.slice(0, 10), ...Parser() ? [importOoxml] : [], ...writes.slice(10)];
1894
1316
  },
1895
- {
1896
- ...base,
1897
- id: `${prefix}redo`,
1898
- label: "Redo Chart Edit",
1899
- keywords: ["chart", "redo"],
1900
- enabled: () => controller.getState().canRedo || "Nothing to redo",
1901
- run: () => ({ redone: controller.redo() })
1317
+ subscribe: (notify) => {
1318
+ let last = readOnlyReason();
1319
+ return controller.subscribe(() => {
1320
+ const now = readOnlyReason();
1321
+ if (now !== last) {
1322
+ last = now;
1323
+ notify();
1324
+ }
1325
+ });
1902
1326
  }
1903
- ];
1327
+ };
1904
1328
  }
1905
1329
 
1906
- // src/chart/interaction.ts
1907
- function chartClientToScene(scene, box, clientX, clientY) {
1908
- const sx = box.width ? scene.width / box.width : 1;
1909
- const sy = box.height ? scene.height / box.height : 1;
1910
- return { x: (clientX - box.left) * sx, y: (clientY - box.top) * sy };
1330
+ // src/chart/stream.ts
1331
+ function parsePartialChartJson(text) {
1332
+ const src = text.trim();
1333
+ if (!src) return void 0;
1334
+ try {
1335
+ return JSON.parse(src);
1336
+ } catch (e2) {
1337
+ }
1338
+ const stack = [];
1339
+ let inString = false;
1340
+ let escaped = false;
1341
+ let out = "";
1342
+ for (const ch of src) {
1343
+ out += ch;
1344
+ if (inString) {
1345
+ if (escaped) escaped = false;
1346
+ else if (ch === "\\") escaped = true;
1347
+ else if (ch === '"') inString = false;
1348
+ continue;
1349
+ }
1350
+ if (ch === '"') inString = true;
1351
+ else if (ch === "{" || ch === "[") stack.push(ch === "{" ? "}" : "]");
1352
+ else if (ch === "}" || ch === "]") stack.pop();
1353
+ }
1354
+ if (inString) out += '"';
1355
+ for (let attempt = 0; attempt < 6; attempt++) {
1356
+ const trimmed = out.replace(/\s+$/, "");
1357
+ const candidate = trimmed + [...stack].reverse().join("");
1358
+ try {
1359
+ return JSON.parse(candidate);
1360
+ } catch (e3) {
1361
+ const next = trimmed.replace(/,\s*$/, "").replace(/(?:,|\{)\s*"[^"]*"\s*:?\s*$/, (m) => m.trim().startsWith("{") ? "{" : "").replace(/:\s*$/, ": null").replace(/(?<=[\[,:]\s*)(-|tr?u?|fa?l?s?|nu?l?)$/, "").replace(/(?<=[\[,:]\s*)(-?\d+\.)$/, (m) => m.slice(0, -1));
1362
+ if (next === trimmed) break;
1363
+ out = next;
1364
+ }
1365
+ }
1366
+ return void 0;
1911
1367
  }
1912
- function canDragChartPoint(controller, scene, target) {
1913
- if (_optionalChain([target, 'optionalAccess', _29 => _29.kind]) !== "point" || !scene.valueScale || scene.percent) return false;
1914
- const spec = controller.getSpec();
1915
- if (!isCartesianChart(spec.type)) return false;
1916
- const series = controller.getSeries(target.seriesId);
1917
- return !!series && !series.hidden && series.values[target.index] !== null;
1368
+ var ChartStream = (_class = class {
1369
+ constructor(format = "auto") {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);
1370
+ this.format = format;
1371
+ }
1372
+
1373
+ __init() {this.lo = Infinity}
1374
+ __init2() {this.hi = -Infinity}
1375
+ __init3() {this.last = null}
1376
+ /** Reads the source so far. `complete` when nothing more will arrive. */
1377
+ update(source, complete = false) {
1378
+ const text = source.replace(/^\s*```(?:mermaid|json)?\s*\n?/, "").replace(/```\s*$/, "");
1379
+ const format = this.format === "auto" ? /^\s*[[{]/.test(text) ? "json" : "mermaid" : this.format;
1380
+ let spec = null;
1381
+ let issues = [];
1382
+ if (format === "json") {
1383
+ const value = complete ? safeParse(text) : parsePartialChartJson(text);
1384
+ if (value !== void 0) {
1385
+ const read = _chunkPRSJJZBScjs.normalizeChartSpec.call(void 0, value);
1386
+ spec = read.spec;
1387
+ issues = complete ? read.issues : [];
1388
+ }
1389
+ } else {
1390
+ const read = _chunkPRSJJZBScjs.chartFromMermaid.call(void 0, text, { partial: !complete });
1391
+ if (read.family) {
1392
+ spec = read.spec;
1393
+ issues = complete ? read.issues : [];
1394
+ }
1395
+ }
1396
+ if (spec && !spec.series.some((s) => s.values.some((v) => v !== null))) spec = complete ? spec : this.last;
1397
+ if (spec) this.last = spec;
1398
+ if (spec) {
1399
+ for (const s of spec.series) for (const v of s.values) if (v !== null && Number.isFinite(v)) this.lo = Math.min(this.lo, v), this.hi = Math.max(this.hi, v);
1400
+ }
1401
+ const valueDomain = !complete && Number.isFinite(this.lo) && this.hi > this.lo ? { min: Math.min(0, this.lo), max: this.hi } : null;
1402
+ return { spec, issues, valueDomain };
1403
+ }
1404
+ /** Forgets the axis bounds (a new source). */
1405
+ reset() {
1406
+ this.lo = Infinity;
1407
+ this.hi = -Infinity;
1408
+ this.last = null;
1409
+ }
1410
+ }, _class);
1411
+ function safeParse(text) {
1412
+ try {
1413
+ return JSON.parse(text);
1414
+ } catch (e4) {
1415
+ return parsePartialChartJson(text);
1416
+ }
1918
1417
  }
1919
- function startChartPointDrag(controller, scene, target, start, options = {}) {
1920
- if (!canDragChartPoint(controller, scene, target)) return null;
1921
- const { seriesId, index } = target;
1922
- const startValue = _nullishCoalesce(controller.getSeries(seriesId).values[index], () => ( 0));
1923
- const startAt = chartValueAtPoint(scene, start);
1924
- if (startAt === void 0) return null;
1925
- controller.beginGesture(`drag:${seriesId}:${index}`);
1926
- let ended = false;
1927
- const [min, max] = scene.valueScale.domain;
1418
+ var mix = (a, b, t) => a + (b - a) * t;
1419
+ function blendPath(a, b, t) {
1420
+ const na = a.match(/-?\d*\.?\d+(?:e-?\d+)?/g);
1421
+ const nb = b.match(/-?\d*\.?\d+(?:e-?\d+)?/g);
1422
+ if (!na || !nb || na.length !== nb.length || a.replace(/-?\d*\.?\d+(?:e-?\d+)?/g, "#") !== b.replace(/-?\d*\.?\d+(?:e-?\d+)?/g, "#")) return t < 1 ? a : b;
1423
+ let i = 0;
1424
+ return b.replace(/-?\d*\.?\d+(?:e-?\d+)?/g, () => {
1425
+ const v = mix(Number(na[i]), Number(nb[i]), t);
1426
+ i++;
1427
+ return String(Math.round(v * 100) / 100);
1428
+ });
1429
+ }
1430
+ function blendChartScenes(from, to, t) {
1431
+ if (!from || t >= 1) return to;
1432
+ const k = Math.max(0, Math.min(1, t));
1433
+ const before = new Map(from.marks.map((m) => [m.key, m]));
1434
+ const marks = to.marks.map((m) => {
1435
+ const a = before.get(m.key);
1436
+ if (!a || a.kind !== m.kind) return { ...m, opacity: (_nullishCoalesce(m.opacity, () => ( 1))) * k };
1437
+ switch (m.kind) {
1438
+ case "rect": {
1439
+ const p = a;
1440
+ return { ...m, x: mix(p.x, m.x, k), y: mix(p.y, m.y, k), width: mix(p.width, m.width, k), height: mix(p.height, m.height, k) };
1441
+ }
1442
+ case "circle": {
1443
+ const p = a;
1444
+ return { ...m, cx: mix(p.cx, m.cx, k), cy: mix(p.cy, m.cy, k), r: mix(p.r, m.r, k) };
1445
+ }
1446
+ case "line": {
1447
+ const p = a;
1448
+ return { ...m, x1: mix(p.x1, m.x1, k), y1: mix(p.y1, m.y1, k), x2: mix(p.x2, m.x2, k), y2: mix(p.y2, m.y2, k) };
1449
+ }
1450
+ case "text": {
1451
+ const p = a;
1452
+ return { ...m, x: mix(p.x, m.x, k), y: mix(p.y, m.y, k) };
1453
+ }
1454
+ case "path":
1455
+ return { ...m, d: blendPath(a.d, m.d, k) };
1456
+ }
1457
+ });
1458
+ return { ...to, marks };
1459
+ }
1460
+ var easeChartTween = (t) => 1 - Math.pow(1 - Math.max(0, Math.min(1, t)), 3);
1461
+
1462
+ // src/chart/presence.ts
1463
+ var ChartPresence = (_class2 = class {constructor() { _class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this); }
1464
+ __init4() {this.state = { actors: [] }}
1465
+ __init5() {this.listeners = /* @__PURE__ */ new Set()}
1466
+ __init6() {this.getState = () => this.state}
1467
+ __init7() {this.subscribe = (listener) => {
1468
+ this.listeners.add(listener);
1469
+ return () => {
1470
+ this.listeners.delete(listener);
1471
+ };
1472
+ }}
1473
+ /** Adds or changes an actor. */
1474
+ set(actor) {
1475
+ const current = this.state.actors.find((a) => a.id === actor.id);
1476
+ const next = { name: actor.id, color: CHART_PRESENCE_COLORS[this.state.actors.length % CHART_PRESENCE_COLORS.length], x: 0, y: 0, pressed: false, ...current, ...actor };
1477
+ this.state = { actors: current ? this.state.actors.map((a) => a.id === actor.id ? next : a) : [...this.state.actors, next] };
1478
+ this.listeners.forEach((l) => l(this.state));
1479
+ }
1480
+ remove(id) {
1481
+ if (!this.state.actors.some((a) => a.id === id)) return;
1482
+ this.state = { actors: this.state.actors.filter((a) => a.id !== id) };
1483
+ this.listeners.forEach((l) => l(this.state));
1484
+ }
1485
+ }, _class2);
1486
+ var CHART_PRESENCE_COLORS = ["#7c3aed", "#db2777", "#0891b2", "#ea580c", "#16a34a"];
1487
+ var realChartClock = {
1488
+ now: () => Date.now(),
1489
+ wait: (ms) => new Promise((resolve2) => setTimeout(resolve2, Math.max(0, ms)))
1490
+ };
1491
+ function createInstantChartClock(start = 0) {
1492
+ let t = start;
1928
1493
  return {
1929
- valueDomain: { min, max },
1930
- move(point) {
1931
- if (ended) return;
1932
- const at = chartValueAtPoint(scene, point);
1933
- if (at === void 0) return;
1934
- controller.setValue(seriesId, index, snapChartValue(scene, startValue + (at - startAt), options.step), { label: `Drag ${_nullishCoalesce(_optionalChain([controller, 'access', _30 => _30.getSeries, 'call', _31 => _31(seriesId), 'optionalAccess', _32 => _32.name]), () => ( "value"))}` });
1494
+ now: () => t,
1495
+ wait: async (ms) => {
1496
+ t += Math.max(0, ms);
1935
1497
  },
1936
- end() {
1937
- if (ended) return;
1938
- ended = true;
1939
- controller.endGesture();
1940
- }
1498
+ elapsed: () => t - start
1941
1499
  };
1942
1500
  }
1943
- function handleChartKey(controller, scene, event) {
1944
- if (event.metaKey || event.ctrlKey || event.altKey) return false;
1945
- const { selection, spec } = controller.getState();
1946
- switch (event.key) {
1947
- case "Escape":
1948
- if (!selection) return false;
1949
- controller.select(null);
1950
- return true;
1951
- case "Delete":
1952
- case "Backspace":
1953
- if (!selection || selection.kind === "chart") return false;
1954
- controller.deleteSelection();
1955
- return true;
1956
- case "ArrowLeft":
1957
- case "ArrowRight": {
1958
- if (_optionalChain([selection, 'optionalAccess', _33 => _33.kind]) !== "point" && _optionalChain([selection, 'optionalAccess', _34 => _34.kind]) !== "category") return false;
1959
- const n = spec.categories.length;
1960
- const index = Math.max(0, Math.min(n - 1, selection.index + (event.key === "ArrowRight" ? 1 : -1)));
1961
- controller.select({ ...selection, index });
1962
- return true;
1501
+ function chartPointAnchor(scene, seriesId, index) {
1502
+ const mark = scene.marks.find((m) => (m.role === "bar" || m.role === "point") && _optionalChain([m, 'access', _74 => _74.target, 'optionalAccess', _75 => _75.kind]) === "point" && m.target.seriesId === seriesId && m.target.index === index);
1503
+ if (!mark) return null;
1504
+ if (mark.kind === "rect") return { x: mark.x + mark.width / 2, y: mark.y };
1505
+ if (mark.kind === "circle") return { x: mark.cx, y: mark.cy };
1506
+ return null;
1507
+ }
1508
+ var anchorFor = (scene, role) => {
1509
+ const m = scene.marks.find((x) => x.role === role);
1510
+ if (!m) return null;
1511
+ if (m.kind === "text") return { x: m.x, y: m.y };
1512
+ if (m.kind === "rect") return { x: m.x + m.width / 2, y: m.y + m.height / 2 };
1513
+ if (m.kind === "line") return { x: (m.x1 + m.x2) / 2, y: (m.y1 + m.y2) / 2 };
1514
+ return null;
1515
+ };
1516
+ function planChartCommand(spec, scene, command, speed = 1) {
1517
+ const ms = (n) => Math.round(n / Math.max(0.1, speed));
1518
+ const steps = [];
1519
+ const note = "note" in command && command.note ? command.note : void 0;
1520
+ if (note) steps.push({ type: "say", text: note, ms: ms(500) });
1521
+ switch (command.kind) {
1522
+ case "say":
1523
+ steps.push({ type: "say", text: command.text, ms: ms(900) });
1524
+ return steps;
1525
+ case "setValue": {
1526
+ const at = chartPointAnchor(scene, command.seriesId, command.index);
1527
+ const series = spec.series.find((s) => s.id === command.seriesId);
1528
+ const from = _nullishCoalesce(_optionalChain([series, 'optionalAccess', _76 => _76.values, 'access', _77 => _77[command.index]]), () => ( 0));
1529
+ const scale = _optionalChain([series, 'optionalAccess', _78 => _78.axis]) === "secondary" ? _nullishCoalesce(scene.valueScale2, () => ( scene.valueScale)) : scene.valueScale;
1530
+ if (at && scale) {
1531
+ const px = _chunkPRSJJZBScjs.linearMap.call(void 0, command.value, scale.domain, scale.range);
1532
+ const to = scale.axis === "y" ? { x: at.x, y: px } : { x: px, y: at.y };
1533
+ steps.push({ type: "move", to: at, ms: ms(380) }, { type: "press" }, { type: "drag", to, ms: ms(520), from, value: command.value }, { type: "release" });
1534
+ } else steps.push({ type: "drag", to: { x: scene.plot.x + scene.plot.width / 2, y: scene.plot.y + scene.plot.height / 2 }, ms: ms(200), from, value: command.value });
1535
+ steps.push({ type: "commit" });
1536
+ return steps;
1963
1537
  }
1964
- case "ArrowUp":
1965
- case "ArrowDown": {
1966
- if (_optionalChain([selection, 'optionalAccess', _35 => _35.kind]) !== "point" || _optionalChain([scene, 'optionalAccess', _36 => _36.percent])) return false;
1967
- const ticks = _nullishCoalesce(_optionalChain([scene, 'optionalAccess', _37 => _37.valueScale, 'optionalAccess', _38 => _38.ticks]), () => ( []));
1968
- const step = ticks.length > 1 ? ticks[1] - ticks[0] : 1;
1969
- const delta = (event.shiftKey ? step : step / 10) * (event.key === "ArrowUp" ? 1 : -1);
1970
- controller.nudgeValue(selection.seriesId, selection.index, delta);
1971
- return true;
1538
+ case "select": {
1539
+ const sel = command.selection;
1540
+ let to = null;
1541
+ if (_optionalChain([sel, 'optionalAccess', _79 => _79.kind]) === "point") to = chartPointAnchor(scene, sel.seriesId, sel.index);
1542
+ else if (sel) {
1543
+ const m = scene.marks.find((x) => x.target && JSON.stringify(x.target) === JSON.stringify(sel));
1544
+ if (_optionalChain([m, 'optionalAccess', _80 => _80.kind]) === "text") to = { x: m.x, y: m.y };
1545
+ else if (_optionalChain([m, 'optionalAccess', _81 => _81.kind]) === "rect") to = { x: m.x + m.width / 2, y: m.y + m.height / 2 };
1546
+ else if (_optionalChain([m, 'optionalAccess', _82 => _82.kind]) === "circle") to = { x: m.cx, y: m.cy };
1547
+ }
1548
+ if (to) steps.push({ type: "move", to, ms: ms(380) }, { type: "press" }, { type: "release" });
1549
+ steps.push({ type: "commit" });
1550
+ return steps;
1551
+ }
1552
+ case "update": {
1553
+ const keys = Object.keys(command.patch);
1554
+ const role = keys.includes("title") ? "title" : keys.includes("legend") ? "legend-label" : keys.some((k) => k === "xAxis") ? "axis" : keys.some((k) => k === "yAxis" || k === "yAxis2") ? "tick-label" : null;
1555
+ const to = role && anchorFor(scene, role) || { x: scene.plot.x + scene.plot.width / 2, y: scene.plot.y + 12 };
1556
+ steps.push({ type: "move", to, ms: ms(380) }, { type: "press" }, { type: "release" }, { type: "commit" });
1557
+ return steps;
1972
1558
  }
1973
- default:
1974
- return false;
1975
1559
  }
1976
1560
  }
1977
- function describeChartSelection(controller, selection = controller.getState().selection) {
1978
- if (!selection) return "Nothing selected";
1979
- const spec = controller.getSpec();
1980
- switch (selection.kind) {
1981
- case "chart":
1982
- return "Chart";
1983
- case "title":
1984
- return "Title";
1985
- case "subtitle":
1986
- return "Subtitle";
1987
- case "legend":
1988
- return "Legend";
1989
- case "axis":
1990
- return selection.axis === "x" ? "Category axis" : "Value axis";
1991
- case "series":
1992
- return `Series "${_nullishCoalesce(_optionalChain([controller, 'access', _39 => _39.getSeries, 'call', _40 => _40(selection.seriesId), 'optionalAccess', _41 => _41.name]), () => ( selection.seriesId))}"`;
1993
- case "category":
1994
- return `Category "${_nullishCoalesce(spec.categories[selection.index], () => ( selection.index))}"`;
1995
- case "point": {
1996
- const series = controller.getSeries(selection.seriesId);
1997
- const value = _optionalChain([series, 'optionalAccess', _42 => _42.values, 'access', _43 => _43[selection.index]]);
1998
- return `${_nullishCoalesce(_optionalChain([series, 'optionalAccess', _44 => _44.name]), () => ( selection.seriesId))} \xB7 ${_nullishCoalesce(spec.categories[selection.index], () => ( selection.index))}: ${_nullishCoalesce(value, () => ( "blank"))}`;
1561
+ var ChartDirector = (_class3 = class {
1562
+ constructor(controller, presence, options) {;_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);
1563
+ this.controller = controller;
1564
+ this.presence = presence;
1565
+ this.options = options;
1566
+ this.clock = _nullishCoalesce(options.clock, () => ( realChartClock));
1567
+ }
1568
+
1569
+
1570
+
1571
+ __init8() {this.queues = /* @__PURE__ */ new Map()}
1572
+
1573
+ __init9() {this.calls = 0}
1574
+ /** Acts out `commands` in order for `actor`. Resolves with one result per command. */
1575
+ perform(actor, commands) {
1576
+ const prev = _nullishCoalesce(this.queues.get(actor.id), () => ( Promise.resolve()));
1577
+ const run = prev.then(async () => {
1578
+ const results = [];
1579
+ this.presence.set({ id: actor.id, ...actor.name ? { name: actor.name } : {}, ...actor.color ? { color: actor.color } : {} });
1580
+ this.controller.beginGesture(`agent:${actor.id}:${++this.calls}`);
1581
+ try {
1582
+ for (const command of commands) results.push(await this.performOne(actor.id, command));
1583
+ } finally {
1584
+ this.controller.endGesture();
1585
+ this.presence.set({ id: actor.id, message: void 0, pressed: false });
1586
+ }
1587
+ const linger = _nullishCoalesce(this.options.linger, () => ( 1500));
1588
+ if (linger > 0) {
1589
+ await this.clock.wait(linger);
1590
+ if (this.queues.get(actor.id) === run) this.presence.remove(actor.id);
1591
+ }
1592
+ return results;
1593
+ });
1594
+ this.queues.set(actor.id, run.catch(() => void 0));
1595
+ return run;
1596
+ }
1597
+ stale(command) {
1598
+ const spec = this.controller.getSpec();
1599
+ if (command.kind === "setValue") {
1600
+ const s = spec.series.find((x) => x.id === command.seriesId);
1601
+ if (!s) return `The series "${command.seriesId}" no longer exists.`;
1602
+ if (command.index < 0 || command.index >= spec.categories.length) return `Category ${command.index} no longer exists.`;
1603
+ if (!Number.isFinite(command.value)) return "The value must be a finite number.";
1999
1604
  }
1605
+ return null;
2000
1606
  }
2001
- }
1607
+ async move(id, from, to, ms, each) {
1608
+ const frames = Math.max(1, Math.round(ms / 16));
1609
+ for (let f = 1; f <= frames; f++) {
1610
+ const t = f / frames;
1611
+ const e = 1 - Math.pow(1 - t, 3);
1612
+ this.presence.set({ id, x: from.x + (to.x - from.x) * e, y: from.y + (to.y - from.y) * e });
1613
+ _optionalChain([each, 'optionalCall', _83 => _83(e)]);
1614
+ await this.clock.wait(ms / frames);
1615
+ }
1616
+ }
1617
+ async performOne(id, command) {
1618
+ const stale = this.stale(command);
1619
+ if (stale) return { ok: false, command, message: stale };
1620
+ const steps = planChartCommand(this.controller.getSpec(), this.options.scene(), command, this.options.speed);
1621
+ const where = () => {
1622
+ const a = this.presence.getState().actors.find((x) => x.id === id);
1623
+ return { x: a.x, y: a.y };
1624
+ };
1625
+ {
1626
+ for (const step of steps) {
1627
+ switch (step.type) {
1628
+ case "say":
1629
+ this.presence.set({ id, message: step.text });
1630
+ await this.clock.wait(step.ms);
1631
+ break;
1632
+ case "move":
1633
+ await this.move(id, where(), step.to, step.ms);
1634
+ break;
1635
+ case "press":
1636
+ this.presence.set({ id, pressed: true });
1637
+ break;
1638
+ case "release":
1639
+ this.presence.set({ id, pressed: false });
1640
+ break;
1641
+ case "drag": {
1642
+ if (command.kind !== "setValue") break;
1643
+ const stale2 = this.stale(command);
1644
+ if (stale2) return { ok: false, command, message: stale2 };
1645
+ const label = `${_nullishCoalesce(_optionalChain([this, 'access', _84 => _84.presence, 'access', _85 => _85.getState, 'call', _86 => _86(), 'access', _87 => _87.actors, 'access', _88 => _88.find, 'call', _89 => _89((a) => a.id === id), 'optionalAccess', _90 => _90.name]), () => ( "Agent"))}: edit value`;
1646
+ await this.move(id, where(), step.to, step.ms, (e) => {
1647
+ const v = step.from + (step.value - step.from) * e;
1648
+ this.controller.setValue(command.seriesId, command.index, Number(v.toPrecision(12)), { label });
1649
+ });
1650
+ this.controller.setValue(command.seriesId, command.index, step.value, { label });
1651
+ break;
1652
+ }
1653
+ case "commit": {
1654
+ if (command.kind === "select") this.controller.select(command.selection);
1655
+ else if (command.kind === "update") {
1656
+ const result = this.controller.update(command.patch, command.label ? { label: command.label } : {});
1657
+ if (!result.ok) return { ok: false, command, message: result.message };
1658
+ }
1659
+ break;
1660
+ }
1661
+ }
1662
+ }
1663
+ return { ok: true, command };
1664
+ }
1665
+ }
1666
+ }, _class3);
1667
+
1668
+
1669
+
1670
+
1671
+
1672
+
1673
+
1674
+
1675
+
1676
+
1677
+
1678
+
1679
+
1680
+
1681
+
1682
+
1683
+
1684
+
1685
+
1686
+
1687
+
1688
+
1689
+
1690
+
1691
+
1692
+
1693
+
1694
+
1695
+
1696
+
1697
+
1698
+
1699
+
1700
+
1701
+
1702
+
1703
+
1704
+
1705
+
1706
+
1707
+
1708
+
1709
+
1710
+
1711
+
1712
+
1713
+
1714
+
1715
+
1716
+
1717
+
1718
+
1719
+
1720
+
1721
+
1722
+
1723
+
1724
+
1725
+
1726
+
1727
+
1728
+
1729
+
1730
+
1731
+
1732
+
1733
+
1734
+
1735
+
1736
+
1737
+
1738
+
1739
+
1740
+
1741
+
1742
+
1743
+
1744
+
1745
+
1746
+
1747
+
1748
+
2002
1749
 
2003
1750
 
2004
1751
 
@@ -2050,5 +1797,5 @@ function describeChartSelection(controller, selection = controller.getState().se
2050
1797
 
2051
1798
 
2052
1799
 
2053
- exports.CHART_CURVES = CHART_CURVES; exports.CHART_LEGEND_POSITIONS = CHART_LEGEND_POSITIONS; exports.CHART_NUMBER_STYLES = CHART_NUMBER_STYLES; exports.CHART_ORIENTATIONS = CHART_ORIENTATIONS; exports.CHART_SERIES_TYPES = CHART_SERIES_TYPES; exports.CHART_STACKINGS = CHART_STACKINGS; exports.CHART_TYPES = CHART_TYPES; exports.CHART_TYPE_LABELS = CHART_TYPE_LABELS; exports.CSS_VARIABLE_CHART_THEME = CSS_VARIABLE_CHART_THEME; exports.ChartController = ChartController; exports.DEFAULT_CHART_PALETTE = DEFAULT_CHART_PALETTE; exports.DEFAULT_CHART_THEME = DEFAULT_CHART_THEME; exports.canDragChartPoint = canDragChartPoint; exports.chartClientToScene = chartClientToScene; exports.chartColorInputValue = chartColorInputValue; exports.chartDataFromTable = chartDataFromTable; exports.chartDataToTable = chartDataToTable; exports.chartSceneToSvg = chartSceneToSvg; exports.chartSeriesColor = chartSeriesColor; exports.chartSeriesType = chartSeriesType; exports.chartSliceColor = chartSliceColor; exports.chartTextBaseline = chartTextBaseline; exports.chartTextTransform = chartTextTransform; exports.chartToSvg = chartToSvg; exports.chartValueAtPoint = chartValueAtPoint; exports.cloneChartSeries = cloneChartSeries; exports.cloneChartSpec = cloneChartSpec; exports.contrastingTextColor = contrastingTextColor; exports.createChartCommands = createChartCommands; exports.createChartController = createChartController; exports.createChartSpec = createChartSpec; exports.defaultChartAxis = defaultChartAxis; exports.describeChartSelection = describeChartSelection; exports.estimateChartTextWidth = estimateChartTextWidth; exports.formatChartNumber = formatChartNumber; exports.handleChartKey = handleChartKey; exports.isCartesianChart = isCartesianChart; exports.layoutChart = layoutChart; exports.linearMap = linearMap; exports.niceScale = niceScale; exports.niceStep = niceStep; exports.normalizeChartAxis = normalizeChartAxis; exports.normalizeChartSpec = normalizeChartSpec; exports.normalizeChartValues = normalizeChartValues; exports.parseChartValue = parseChartValue; exports.sameChartSelection = sameChartSelection; exports.snapChartValue = snapChartValue; exports.startChartPointDrag = startChartPointDrag; exports.stepDecimals = stepDecimals; exports.uniqueChartSeriesId = uniqueChartSeriesId;
1800
+ exports.CHART_AXIS_KINDS = _chunkPRSJJZBScjs.CHART_AXIS_KINDS; exports.CHART_COLOR_SCHEMES = _chunkPRSJJZBScjs.CHART_COLOR_SCHEMES; exports.CHART_CURVES = _chunkPRSJJZBScjs.CHART_CURVES; exports.CHART_DATE_STEPS = _chunkPRSJJZBScjs.CHART_DATE_STEPS; exports.CHART_LABEL_POSITIONS = _chunkPRSJJZBScjs.CHART_LABEL_POSITIONS; exports.CHART_LAYOUT_PRESETS = _chunkPRSJJZBScjs.CHART_LAYOUT_PRESETS; exports.CHART_LEGEND_POSITIONS = _chunkPRSJJZBScjs.CHART_LEGEND_POSITIONS; exports.CHART_MARKERS = _chunkPRSJJZBScjs.CHART_MARKERS; exports.CHART_MERMAID_META_PREFIX = _chunkPRSJJZBScjs.CHART_MERMAID_META_PREFIX; exports.CHART_NUMBER_STYLES = _chunkPRSJJZBScjs.CHART_NUMBER_STYLES; exports.CHART_OOXML_CONTENT_TYPE = _chunkPIMCHMZEcjs.CHART_OOXML_CONTENT_TYPE; exports.CHART_OOXML_NS = _chunkPIMCHMZEcjs.CHART_OOXML_NS; exports.CHART_OOXML_RELATIONSHIP = _chunkPIMCHMZEcjs.CHART_OOXML_RELATIONSHIP; exports.CHART_ORIENTATIONS = _chunkPRSJJZBScjs.CHART_ORIENTATIONS; exports.CHART_PALETTE_SHADES = _chunkPRSJJZBScjs.CHART_PALETTE_SHADES; exports.CHART_PRESENCE_COLORS = CHART_PRESENCE_COLORS; exports.CHART_SERIES_TYPES = _chunkPRSJJZBScjs.CHART_SERIES_TYPES; exports.CHART_STACKINGS = _chunkPRSJJZBScjs.CHART_STACKINGS; exports.CHART_TRENDLINE_TYPES = _chunkPRSJJZBScjs.CHART_TRENDLINE_TYPES; exports.CHART_TYPES = _chunkPRSJJZBScjs.CHART_TYPES; exports.CHART_TYPE_LABELS = _chunkPRSJJZBScjs.CHART_TYPE_LABELS; exports.CSS_VARIABLE_CHART_THEME = _chunkPRSJJZBScjs.CSS_VARIABLE_CHART_THEME; exports.ChartController = _chunkPRSJJZBScjs.ChartController; exports.ChartDirector = ChartDirector; exports.ChartPresence = ChartPresence; exports.ChartStream = ChartStream; exports.DEFAULT_CHART_PALETTE = _chunkPRSJJZBScjs.DEFAULT_CHART_PALETTE; exports.DEFAULT_CHART_THEME = _chunkPRSJJZBScjs.DEFAULT_CHART_THEME; exports.MAX_CHART_DATA_LABELS = _chunkPRSJJZBScjs.MAX_CHART_DATA_LABELS; exports.MAX_CHART_LINE_POINTS = _chunkPRSJJZBScjs.MAX_CHART_LINE_POINTS; exports.MAX_CHART_MARKERS = _chunkPRSJJZBScjs.MAX_CHART_MARKERS; exports.analyzeChartTable = analyzeChartTable; exports.blendChartScenes = blendChartScenes; exports.canDragChartPoint = canDragChartPoint; exports.chartBoxStats = _chunkPRSJJZBScjs.chartBoxStats; exports.chartClientToScene = chartClientToScene; exports.chartColorDistance = _chunkPRSJJZBScjs.chartColorDistance; exports.chartColorInputValue = _chunkPRSJJZBScjs.chartColorInputValue; exports.chartColorsByCategory = _chunkPRSJJZBScjs.chartColorsByCategory; exports.chartContrastRatio = _chunkPRSJJZBScjs.chartContrastRatio; exports.chartDataFromTable = _chunkPRSJJZBScjs.chartDataFromTable; exports.chartDataToTable = _chunkPRSJJZBScjs.chartDataToTable; exports.chartDateLabels = _chunkPRSJJZBScjs.chartDateLabels; exports.chartDateToSerial = _chunkPRSJJZBScjs.chartDateToSerial; exports.chartFamily = _chunkPRSJJZBScjs.chartFamily; exports.chartFormatToCode = _chunkPIMCHMZEcjs.chartFormatToCode; exports.chartFromMermaid = _chunkPRSJJZBScjs.chartFromMermaid; exports.chartFromOoxml = _chunkPIMCHMZEcjs.chartFromOoxml; exports.chartFromTable = chartFromTable; exports.chartHistogram = _chunkPRSJJZBScjs.chartHistogram; exports.chartHitTest = chartHitTest; exports.chartLuminance = _chunkPRSJJZBScjs.chartLuminance; exports.chartMermaidFamily = _chunkPRSJJZBScjs.chartMermaidFamily; exports.chartMovingAverage = _chunkPRSJJZBScjs.chartMovingAverage; exports.chartPointAnchor = chartPointAnchor; exports.chartPointColor = _chunkPRSJJZBScjs.chartPointColor; exports.chartPresetPatch = _chunkPRSJJZBScjs.chartPresetPatch; exports.chartQuantile = _chunkPRSJJZBScjs.chartQuantile; exports.chartRgbToHex = _chunkPRSJJZBScjs.chartRgbToHex; exports.chartRgbToOklab = _chunkPRSJJZBScjs.chartRgbToOklab; exports.chartSceneToDomSpec = _chunkPRSJJZBScjs.chartSceneToDomSpec; exports.chartSceneToSvg = _chunkPRSJJZBScjs.chartSceneToSvg; exports.chartSerialToDate = _chunkPRSJJZBScjs.chartSerialToDate; exports.chartSeriesColor = _chunkPRSJJZBScjs.chartSeriesColor; exports.chartSeriesType = _chunkPRSJJZBScjs.chartSeriesType; exports.chartSliceColor = _chunkPRSJJZBScjs.chartSliceColor; exports.chartSquarify = _chunkPRSJJZBScjs.chartSquarify; exports.chartSupportsTrendlines = _chunkPRSJJZBScjs.chartSupportsTrendlines; exports.chartTextBaseline = _chunkPRSJJZBScjs.chartTextBaseline; exports.chartTextOn = _chunkPRSJJZBScjs.chartTextOn; exports.chartTextTransform = _chunkPRSJJZBScjs.chartTextTransform; exports.chartToMermaid = _chunkPRSJJZBScjs.chartToMermaid; exports.chartToOoxml = _chunkPIMCHMZEcjs.chartToOoxml; exports.chartToSvg = _chunkPRSJJZBScjs.chartToSvg; exports.chartTrendlineName = _chunkPRSJJZBScjs.chartTrendlineName; exports.chartValueAtPoint = _chunkPRSJJZBScjs.chartValueAtPoint; exports.chartXAtPoint = _chunkPRSJJZBScjs.chartXAtPoint; exports.checkChartColors = _chunkPRSJJZBScjs.checkChartColors; exports.cloneChartSeries = _chunkPRSJJZBScjs.cloneChartSeries; exports.cloneChartSpec = _chunkPRSJJZBScjs.cloneChartSpec; exports.compileChartFormatCode = _chunkPRSJJZBScjs.compileChartFormatCode; exports.contrastingTextColor = _chunkPRSJJZBScjs.contrastingTextColor; exports.createChartCommands = createChartCommands; exports.createChartController = _chunkPRSJJZBScjs.createChartController; exports.createChartSpec = _chunkPRSJJZBScjs.createChartSpec; exports.createChartToolProvider = createChartToolProvider; exports.createInstantChartClock = createInstantChartClock; exports.cyclePaletteColor = _chunkPRSJJZBScjs.cyclePaletteColor; exports.decimateChartPoints = _chunkPRSJJZBScjs.decimateChartPoints; exports.defaultChartAxis = _chunkPRSJJZBScjs.defaultChartAxis; exports.describeChart = _chunkPRSJJZBScjs.describeChart; exports.describeChartSelection = describeChartSelection; exports.easeChartTween = easeChartTween; exports.estimateChartTextWidth = _chunkPRSJJZBScjs.estimateChartTextWidth; exports.findMermaidCharts = _chunkPRSJJZBScjs.findMermaidCharts; exports.fitChartTrendline = _chunkPRSJJZBScjs.fitChartTrendline; exports.formatChartDate = _chunkPRSJJZBScjs.formatChartDate; exports.formatChartNumber = _chunkPRSJJZBScjs.formatChartNumber; exports.formatChartValueWithCode = _chunkPRSJJZBScjs.formatChartValueWithCode; exports.handleChartKey = handleChartKey; exports.isCartesianChart = _chunkPRSJJZBScjs.isCartesianChart; exports.isXyChart = _chunkPRSJJZBScjs.isXyChart; exports.layoutChart = _chunkPRSJJZBScjs.layoutChart; exports.layoutSparkline = _chunkPRSJJZBScjs.layoutSparkline; exports.linearMap = _chunkPRSJJZBScjs.linearMap; exports.monochromeChartPalette = _chunkPRSJJZBScjs.monochromeChartPalette; exports.niceScale = _chunkPRSJJZBScjs.niceScale; exports.niceStep = _chunkPRSJJZBScjs.niceStep; exports.normalizeChartAxis = _chunkPRSJJZBScjs.normalizeChartAxis; exports.normalizeChartSpec = _chunkPRSJJZBScjs.normalizeChartSpec; exports.normalizeChartTrendline = _chunkPRSJJZBScjs.normalizeChartTrendline; exports.normalizeChartValues = _chunkPRSJJZBScjs.normalizeChartValues; exports.parseChartColor = _chunkPRSJJZBScjs.parseChartColor; exports.parseChartDate = _chunkPRSJJZBScjs.parseChartDate; exports.parseChartValue = _chunkPRSJJZBScjs.parseChartValue; exports.parsePartialChartJson = parsePartialChartJson; exports.planChartCommand = planChartCommand; exports.realChartClock = realChartClock; exports.recommendChartTypes = recommendChartTypes; exports.recommendForShape = recommendForShape; exports.sameChartSelection = _chunkPRSJJZBScjs.sameChartSelection; exports.shadeChartColor = _chunkPRSJJZBScjs.shadeChartColor; exports.simulateChartVision = _chunkPRSJJZBScjs.simulateChartVision; exports.snapChartDragValue = snapChartDragValue; exports.snapChartValue = _chunkPRSJJZBScjs.snapChartValue; exports.sparklineToSvg = _chunkPRSJJZBScjs.sparklineToSvg; exports.startChartPointDrag = startChartPointDrag; exports.stepDecimals = _chunkPRSJJZBScjs.stepDecimals; exports.summarizeChart = _chunkPRSJJZBScjs.summarizeChart; exports.summarizeChartSeries = _chunkPRSJJZBScjs.summarizeChartSeries; exports.trendNumberText = _chunkPRSJJZBScjs.trendNumberText; exports.uniqueChartSeriesId = _chunkPRSJJZBScjs.uniqueChartSeriesId;
2054
1801
  //# sourceMappingURL=index.cjs.map