@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.
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/dist/agent/index.cjs +252 -34
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +19 -7
- package/dist/agent/index.d.ts +19 -7
- package/dist/agent/index.js +228 -10
- package/dist/agent/index.js.map +1 -1
- package/dist/{builtins-CZW-aYKS.d.cts → builtins-1kqwLUnb.d.cts} +2 -2
- package/dist/{builtins-DghNQbT-.d.ts → builtins-BKYlHH1s.d.ts} +2 -2
- package/dist/chart/index.cjs +1596 -1849
- package/dist/chart/index.cjs.map +1 -1
- package/dist/chart/index.d.cts +760 -548
- package/dist/chart/index.d.ts +760 -548
- package/dist/chart/index.js +1499 -1752
- package/dist/chart/index.js.map +1 -1
- package/dist/{chunk-3RPOW4CS.js → chunk-2CJ3LPYQ.js} +12 -10
- package/dist/{chunk-3RPOW4CS.js.map → chunk-2CJ3LPYQ.js.map} +1 -1
- package/dist/{chunk-PQM5W7ZN.js → chunk-4FX5ACVG.js} +29 -2
- package/dist/{chunk-PQM5W7ZN.js.map → chunk-4FX5ACVG.js.map} +1 -1
- package/dist/{chunk-HHQ4XJWT.js → chunk-AMWXSYCI.js} +2 -2
- package/dist/chunk-EKYHKQPG.js +1895 -0
- package/dist/chunk-EKYHKQPG.js.map +1 -0
- package/dist/chunk-HXUL3LS7.js +113 -0
- package/dist/chunk-HXUL3LS7.js.map +1 -0
- package/dist/{chunk-ZRNHGAQT.js → chunk-JQ3BB3E6.js} +43 -1891
- package/dist/chunk-JQ3BB3E6.js.map +1 -0
- package/dist/chunk-LTUM52X6.js +4074 -0
- package/dist/chunk-LTUM52X6.js.map +1 -0
- package/dist/{chunk-J3R2OWOE.cjs → chunk-MUCYWI6W.cjs} +608 -15
- package/dist/chunk-MUCYWI6W.cjs.map +1 -0
- package/dist/chunk-PIMCHMZE.cjs +521 -0
- package/dist/chunk-PIMCHMZE.cjs.map +1 -0
- package/dist/{chunk-TK2XZK6Z.js → chunk-PKBWLEL7.js} +594 -1
- package/dist/chunk-PKBWLEL7.js.map +1 -0
- package/dist/chunk-PRSJJZBS.cjs +4074 -0
- package/dist/chunk-PRSJJZBS.cjs.map +1 -0
- package/dist/{chunk-EFDCLENW.cjs → chunk-RIWPYSKU.cjs} +29 -2
- package/dist/chunk-RIWPYSKU.cjs.map +1 -0
- package/dist/chunk-U6IZKC7B.cjs +113 -0
- package/dist/chunk-U6IZKC7B.cjs.map +1 -0
- package/dist/{chunk-EWCOFSCQ.cjs → chunk-UN33YOXW.cjs} +252 -2100
- package/dist/chunk-UN33YOXW.cjs.map +1 -0
- package/dist/{chunk-Q25ZS4F7.js → chunk-VKPEPWKZ.js} +72 -92
- package/dist/chunk-VKPEPWKZ.js.map +1 -0
- package/dist/{chunk-N4MLNJPO.cjs → chunk-WXEHCVJQ.cjs} +160 -180
- package/dist/chunk-WXEHCVJQ.cjs.map +1 -0
- package/dist/{chunk-K2LVZ4FA.cjs → chunk-XFYUFVAX.cjs} +3 -3
- package/dist/{chunk-K2LVZ4FA.cjs.map → chunk-XFYUFVAX.cjs.map} +1 -1
- package/dist/chunk-XP5IUZUO.cjs +1895 -0
- package/dist/chunk-XP5IUZUO.cjs.map +1 -0
- package/dist/chunk-XUUD43OV.js +521 -0
- package/dist/chunk-XUUD43OV.js.map +1 -0
- package/dist/{chunk-U7VGDSNY.cjs → chunk-YU4WQE5M.cjs} +101 -99
- package/dist/chunk-YU4WQE5M.cjs.map +1 -0
- package/dist/commands/index.cjs +93 -11
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +9 -7
- package/dist/commands/index.d.ts +9 -7
- package/dist/commands/index.js +92 -10
- package/dist/commands/index.js.map +1 -1
- package/dist/controller-ChjCCMLl.d.cts +822 -0
- package/dist/controller-DtbEo3om.d.ts +822 -0
- package/dist/extensions/index.cjs +32 -3
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +5 -2
- package/dist/extensions/index.d.ts +5 -2
- package/dist/extensions/index.js +31 -2
- package/dist/generation/index.cjs +2 -2
- package/dist/generation/index.js +1 -1
- package/dist/{index-CfOQf70b.d.cts → index-DoHqT1mq.d.cts} +222 -5
- package/dist/{index-jwJ_onWf.d.ts → index-YLZ0fKqE.d.ts} +222 -5
- package/dist/index.cjs +38 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +52 -24
- package/dist/index.js.map +1 -1
- package/dist/parameters/index.d.cts +1 -1
- package/dist/parameters/index.d.ts +1 -1
- package/dist/{registry-vz6HOiiy.d.cts → registry-CaR7LC6W.d.cts} +2 -0
- package/dist/{registry-BfVD-vVg.d.ts → registry-rVtFJjRM.d.ts} +2 -0
- package/dist/spreadsheet/index.cjs +5 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.js +24 -22
- package/dist/tools/index.cjs +3 -3
- package/dist/tools/index.js +2 -2
- package/dist/vector/index.d.cts +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/package.json +2 -1
- package/dist/chunk-EFDCLENW.cjs.map +0 -1
- package/dist/chunk-EWCOFSCQ.cjs.map +0 -1
- package/dist/chunk-J3R2OWOE.cjs.map +0 -1
- package/dist/chunk-N4MLNJPO.cjs.map +0 -1
- package/dist/chunk-Q25ZS4F7.js.map +0 -1
- package/dist/chunk-TK2XZK6Z.js.map +0 -1
- package/dist/chunk-U7VGDSNY.cjs.map +0 -1
- package/dist/chunk-ZRNHGAQT.js.map +0 -1
- /package/dist/{chunk-HHQ4XJWT.js.map → chunk-AMWXSYCI.js.map} +0 -0
package/dist/chart/index.cjs
CHANGED
|
@@ -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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
if (
|
|
866
|
-
|
|
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
|
|
871
|
-
const
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
-
|
|
1606
|
-
function createChartController(options = {}) {
|
|
1607
|
-
return new ChartController(options);
|
|
229
|
+
return out;
|
|
1608
230
|
}
|
|
1609
|
-
function
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
const
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
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
|
|
250
|
+
var fail = (message) => {
|
|
1634
251
|
throw new Error(message);
|
|
1635
252
|
};
|
|
1636
253
|
var check = (result) => {
|
|
1637
|
-
if (!result.ok)
|
|
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)
|
|
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))
|
|
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
|
|
1700
|
-
|
|
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 (
|
|
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: "
|
|
1743
|
-
|
|
1744
|
-
|
|
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: "
|
|
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
|
|
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
|
|
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:
|
|
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: "
|
|
1839
|
-
run: (_ctx, params = {}) => controller.addCategory(_optionalChain([params, 'optionalAccess',
|
|
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:
|
|
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
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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
|
-
|
|
1867
|
-
|
|
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
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
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
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
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
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
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/
|
|
1907
|
-
function
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
|
|
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
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
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
|
-
|
|
1920
|
-
|
|
1921
|
-
const
|
|
1922
|
-
const
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
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
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
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
|
-
|
|
1937
|
-
if (ended) return;
|
|
1938
|
-
ended = true;
|
|
1939
|
-
controller.endGesture();
|
|
1940
|
-
}
|
|
1498
|
+
elapsed: () => t - start
|
|
1941
1499
|
};
|
|
1942
1500
|
}
|
|
1943
|
-
function
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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 "
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
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
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
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
|