@pixerate/editor 0.48.0 → 0.50.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/dist/agent/index.cjs +24 -22
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +6 -4
- package/dist/agent/index.js.map +1 -1
- package/dist/canvas/index.cjs +151 -25
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +109 -3
- package/dist/canvas/index.d.ts +109 -3
- package/dist/canvas/index.js +127 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/chart/index.cjs +81 -79
- package/dist/chart/index.cjs.map +1 -1
- package/dist/chart/index.js +52 -50
- package/dist/chart/index.js.map +1 -1
- package/dist/{chunk-TXXNBDQX.cjs → chunk-2Q46FKZK.cjs} +113 -34
- package/dist/chunk-2Q46FKZK.cjs.map +1 -0
- package/dist/{chunk-PIMCHMZE.cjs → chunk-3JNRQVTS.cjs} +16 -16
- package/dist/{chunk-PIMCHMZE.cjs.map → chunk-3JNRQVTS.cjs.map} +1 -1
- package/dist/{chunk-HXUL3LS7.js → chunk-5TWIN7ZJ.js} +2 -2
- package/dist/{chunk-VKPEPWKZ.js → chunk-DQPBQXKG.js} +4 -4
- package/dist/{chunk-MUCYWI6W.cjs → chunk-FEMIVHN3.cjs} +17 -15
- package/dist/chunk-FEMIVHN3.cjs.map +1 -0
- package/dist/{chunk-XUUD43OV.js → chunk-GJBIVWB2.js} +2 -2
- package/dist/{chunk-PRSJJZBS.cjs → chunk-GTZXF4BI.cjs} +191 -841
- package/dist/chunk-GTZXF4BI.cjs.map +1 -0
- package/dist/chunk-JOX6MJW4.cjs +674 -0
- package/dist/chunk-JOX6MJW4.cjs.map +1 -0
- package/dist/{chunk-WXEHCVJQ.cjs → chunk-LNKZPIQD.cjs} +52 -52
- package/dist/{chunk-WXEHCVJQ.cjs.map → chunk-LNKZPIQD.cjs.map} +1 -1
- package/dist/chunk-OV7UAKIY.js +674 -0
- package/dist/chunk-OV7UAKIY.js.map +1 -0
- package/dist/{chunk-PKBWLEL7.js → chunk-QFTVLEVN.js} +6 -4
- package/dist/{chunk-PKBWLEL7.js.map → chunk-QFTVLEVN.js.map} +1 -1
- package/dist/{chunk-KRGXUQ2Y.js → chunk-T6XU4B54.js} +82 -3
- package/dist/chunk-T6XU4B54.js.map +1 -0
- package/dist/{chunk-U6IZKC7B.cjs → chunk-TGBCBQFV.cjs} +14 -14
- package/dist/{chunk-U6IZKC7B.cjs.map → chunk-TGBCBQFV.cjs.map} +1 -1
- package/dist/{chunk-3LHWTGHW.js → chunk-TO22LX34.js} +11 -1
- package/dist/chunk-TO22LX34.js.map +1 -0
- package/dist/{chunk-LTUM52X6.js → chunk-WBOAXDKD.js} +109 -759
- package/dist/chunk-WBOAXDKD.js.map +1 -0
- package/dist/{chunk-VNYRI265.cjs → chunk-YNSK4MGT.cjs} +12 -2
- package/dist/chunk-YNSK4MGT.cjs.map +1 -0
- package/dist/export/index.cjs +4 -2
- package/dist/export/index.cjs.map +1 -1
- package/dist/export/index.d.cts +5 -1
- package/dist/export/index.d.ts +5 -1
- package/dist/export/index.js +3 -1
- package/dist/extensions/index.cjs +6 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.js +5 -4
- package/dist/image-editor/index.cjs +141 -1
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +42 -1
- package/dist/image-editor/index.d.ts +42 -1
- package/dist/image-editor/index.js +140 -0
- package/dist/image-editor/index.js.map +1 -1
- package/dist/index.cjs +12 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/{pathfinder-D98S2lV5.d.ts → pathfinder-B9xh_pCZ.d.ts} +2 -1
- package/dist/{pathfinder-DKRcXiLS.d.cts → pathfinder-QdG70sr9.d.cts} +2 -1
- package/dist/vector/index.cjs +153 -153
- package/dist/vector/index.d.cts +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/dist/vector/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3LHWTGHW.js.map +0 -1
- package/dist/chunk-KRGXUQ2Y.js.map +0 -1
- package/dist/chunk-LTUM52X6.js.map +0 -1
- package/dist/chunk-MUCYWI6W.cjs.map +0 -1
- package/dist/chunk-PRSJJZBS.cjs.map +0 -1
- package/dist/chunk-TXXNBDQX.cjs.map +0 -1
- package/dist/chunk-VNYRI265.cjs.map +0 -1
- /package/dist/{chunk-HXUL3LS7.js.map → chunk-5TWIN7ZJ.js.map} +0 -0
- /package/dist/{chunk-VKPEPWKZ.js.map → chunk-DQPBQXKG.js.map} +0 -0
- /package/dist/{chunk-XUUD43OV.js.map → chunk-GJBIVWB2.js.map} +0 -0
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHART_TYPE_LABELS,
|
|
3
|
+
DEFAULT_CHART_PALETTE,
|
|
4
|
+
DEFAULT_CHART_THEME,
|
|
5
|
+
chartColorsByCategory,
|
|
6
|
+
chartDataFromTable,
|
|
7
|
+
chartFamily,
|
|
8
|
+
chartPointColor,
|
|
9
|
+
chartSeriesColor,
|
|
10
|
+
chartSeriesType,
|
|
11
|
+
chartSliceColor,
|
|
12
|
+
checkChartColors,
|
|
13
|
+
cloneChartSeries,
|
|
14
|
+
cloneChartSpec,
|
|
15
|
+
defaultChartAxis,
|
|
16
|
+
normalizeChartSpec,
|
|
17
|
+
normalizeChartTrendline,
|
|
18
|
+
parseChartValue,
|
|
19
|
+
shadeChartColor,
|
|
20
|
+
uniqueChartSeriesId
|
|
21
|
+
} from "./chunk-OV7UAKIY.js";
|
|
1
22
|
import {
|
|
2
23
|
NumberFormat
|
|
3
24
|
} from "./chunk-EKYHKQPG.js";
|
|
@@ -9,644 +30,21 @@ import {
|
|
|
9
30
|
HistoryManager
|
|
10
31
|
} from "./chunk-QVZZGT4X.js";
|
|
11
32
|
|
|
12
|
-
// src/chart/color.ts
|
|
13
|
-
var NAMED = {
|
|
14
|
-
black: "#000000",
|
|
15
|
-
white: "#ffffff",
|
|
16
|
-
red: "#ff0000",
|
|
17
|
-
green: "#008000",
|
|
18
|
-
blue: "#0000ff",
|
|
19
|
-
yellow: "#ffff00",
|
|
20
|
-
orange: "#ffa500",
|
|
21
|
-
purple: "#800080",
|
|
22
|
-
gray: "#808080",
|
|
23
|
-
grey: "#808080",
|
|
24
|
-
teal: "#008080",
|
|
25
|
-
navy: "#000080",
|
|
26
|
-
maroon: "#800000",
|
|
27
|
-
olive: "#808000",
|
|
28
|
-
silver: "#c0c0c0",
|
|
29
|
-
lime: "#00ff00",
|
|
30
|
-
aqua: "#00ffff",
|
|
31
|
-
cyan: "#00ffff",
|
|
32
|
-
fuchsia: "#ff00ff",
|
|
33
|
-
magenta: "#ff00ff",
|
|
34
|
-
pink: "#ffc0cb",
|
|
35
|
-
brown: "#a52a2a"
|
|
36
|
-
};
|
|
37
|
-
function parseChartColor(color) {
|
|
38
|
-
const text2 = color.trim().toLowerCase();
|
|
39
|
-
const hex = NAMED[text2] ?? text2;
|
|
40
|
-
let m = /^#([0-9a-f]{3})$/.exec(hex);
|
|
41
|
-
if (m) return [...m[1]].map((c) => parseInt(c + c, 16) / 255);
|
|
42
|
-
m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/.exec(hex);
|
|
43
|
-
if (m) return [0, 2, 4].map((i) => parseInt(m[1].slice(i, i + 2), 16) / 255);
|
|
44
|
-
m = /^rgba?\(\s*([\d.]+)%?[\s,]+([\d.]+)%?[\s,]+([\d.]+)%?(?:[\s,/]+[\d.]+%?)?\s*\)$/.exec(text2);
|
|
45
|
-
if (m) {
|
|
46
|
-
const pct = text2.includes("%");
|
|
47
|
-
return [m[1], m[2], m[3]].map((v) => Math.max(0, Math.min(1, Number(v) / (pct ? 100 : 255))));
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
function chartRgbToHex(rgb) {
|
|
52
|
-
return `#${rgb.map((c) => Math.round(Math.max(0, Math.min(1, c)) * 255).toString(16).padStart(2, "0")).join("")}`;
|
|
53
|
-
}
|
|
54
|
-
function shadeChartColor(color, amount) {
|
|
55
|
-
const rgb = parseChartColor(color);
|
|
56
|
-
if (!rgb || !amount) return color;
|
|
57
|
-
const t = Math.max(-1, Math.min(1, amount));
|
|
58
|
-
return chartRgbToHex(rgb.map((c) => t > 0 ? c + (1 - c) * t : c * (1 + t)));
|
|
59
|
-
}
|
|
60
|
-
var CHART_PALETTE_SHADES = [0, 0.4, -0.25, 0.6, -0.5, 0.2];
|
|
61
|
-
function cyclePaletteColor(palette, index) {
|
|
62
|
-
const base = palette[index % palette.length];
|
|
63
|
-
const round2 = Math.floor(index / palette.length) % CHART_PALETTE_SHADES.length;
|
|
64
|
-
return shadeChartColor(base, CHART_PALETTE_SHADES[round2]);
|
|
65
|
-
}
|
|
66
|
-
var toLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
67
|
-
var fromLinear = (c) => c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
68
|
-
function chartLuminance(rgb) {
|
|
69
|
-
const [r, g, b] = rgb.map(toLinear);
|
|
70
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
71
|
-
}
|
|
72
|
-
function chartContrastRatio(a, b) {
|
|
73
|
-
const ra = parseChartColor(a);
|
|
74
|
-
const rb = parseChartColor(b);
|
|
75
|
-
if (!ra || !rb) return null;
|
|
76
|
-
const la = chartLuminance(ra);
|
|
77
|
-
const lb = chartLuminance(rb);
|
|
78
|
-
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
79
|
-
}
|
|
80
|
-
function chartTextOn(fill, fallback, dark = "#0f172a", light = "#ffffff") {
|
|
81
|
-
const rgb = parseChartColor(fill);
|
|
82
|
-
if (!rgb) return fallback;
|
|
83
|
-
const darkRatio = chartContrastRatio(fill, dark);
|
|
84
|
-
const lightRatio = chartContrastRatio(fill, light);
|
|
85
|
-
return darkRatio >= lightRatio ? dark : light;
|
|
86
|
-
}
|
|
87
|
-
function chartRgbToOklab(rgb) {
|
|
88
|
-
const [r, g, b] = rgb.map(toLinear);
|
|
89
|
-
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
90
|
-
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
91
|
-
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
92
|
-
return [
|
|
93
|
-
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
94
|
-
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
95
|
-
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
96
|
-
];
|
|
97
|
-
}
|
|
98
|
-
function chartColorDistance(a, b) {
|
|
99
|
-
const p = chartRgbToOklab(a);
|
|
100
|
-
const q = chartRgbToOklab(b);
|
|
101
|
-
return Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]);
|
|
102
|
-
}
|
|
103
|
-
var CVD = {
|
|
104
|
-
protanopia: [
|
|
105
|
-
[0.152286, 1.052583, -0.204868],
|
|
106
|
-
[0.114503, 0.786281, 0.099216],
|
|
107
|
-
[-3882e-6, -0.048116, 1.051998]
|
|
108
|
-
],
|
|
109
|
-
deuteranopia: [
|
|
110
|
-
[0.367322, 0.860646, -0.227968],
|
|
111
|
-
[0.280085, 0.672501, 0.047413],
|
|
112
|
-
[-0.01182, 0.04294, 0.968881]
|
|
113
|
-
],
|
|
114
|
-
tritanopia: [
|
|
115
|
-
[1.255528, -0.076749, -0.178779],
|
|
116
|
-
[-0.078411, 0.930809, 0.147602],
|
|
117
|
-
[4733e-6, 0.691367, 0.3039]
|
|
118
|
-
]
|
|
119
|
-
};
|
|
120
|
-
function simulateChartVision(rgb, vision) {
|
|
121
|
-
if (vision === "normal") return rgb;
|
|
122
|
-
const lin = rgb.map(toLinear);
|
|
123
|
-
const m = CVD[vision];
|
|
124
|
-
return m.map((row) => fromLinear(Math.max(0, Math.min(1, row[0] * lin[0] + row[1] * lin[1] + row[2] * lin[2]))));
|
|
125
|
-
}
|
|
126
|
-
function checkChartColors(colors, options = {}) {
|
|
127
|
-
const surface = options.surface ?? "#ffffff";
|
|
128
|
-
const minDistance = options.minDistance ?? 0.08;
|
|
129
|
-
const minContrast = options.minContrast ?? 1.5;
|
|
130
|
-
const visions = options.visions ?? ["normal", "protanopia", "deuteranopia", "tritanopia"];
|
|
131
|
-
const parsed = colors.map(parseChartColor);
|
|
132
|
-
const skipped = colors.filter((_, i) => !parsed[i]);
|
|
133
|
-
const issues = [];
|
|
134
|
-
const closest = {};
|
|
135
|
-
for (const vision of visions) {
|
|
136
|
-
const seen = parsed.map((c) => c ? simulateChartVision(c, vision) : null);
|
|
137
|
-
let best;
|
|
138
|
-
for (let a = 0; a < seen.length; a++) {
|
|
139
|
-
for (let b = a + 1; b < seen.length; b++) {
|
|
140
|
-
if (!seen[a] || !seen[b]) continue;
|
|
141
|
-
const distance = chartColorDistance(seen[a], seen[b]);
|
|
142
|
-
if (!best || distance < best.distance) best = { a, b, colors: [colors[a], colors[b]], distance: Math.round(distance * 1e3) / 1e3 };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (best) {
|
|
146
|
-
closest[vision] = best;
|
|
147
|
-
if (best.distance < minDistance) {
|
|
148
|
-
const who = vision === "normal" ? "" : ` with ${vision}`;
|
|
149
|
-
issues.push(`Colours ${best.a + 1} (${best.colors[0]}) and ${best.b + 1} (${best.colors[1]}) are hard to tell apart${who} (distance ${best.distance}, want at least ${minDistance}).`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const contrast = colors.map((c) => {
|
|
154
|
-
const ratio = chartContrastRatio(c, surface);
|
|
155
|
-
return ratio === null ? null : Math.round(ratio * 100) / 100;
|
|
156
|
-
});
|
|
157
|
-
contrast.forEach((ratio, i) => {
|
|
158
|
-
if (ratio !== null && ratio < minContrast) issues.push(`Colour ${i + 1} (${colors[i]}) barely stands out from the background ${surface} (contrast ${ratio}:1, want at least ${minContrast}:1).`);
|
|
159
|
-
});
|
|
160
|
-
return { ok: issues.length === 0, closest, contrast, issues, skipped };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// src/chart/spec.ts
|
|
164
|
-
var CHART_TYPES = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap"];
|
|
165
|
-
var CHART_MARKERS = ["circle", "square", "diamond", "triangle", "cross", "none"];
|
|
166
|
-
var CHART_TRENDLINE_TYPES = ["linear", "exponential", "logarithmic", "power", "polynomial", "moving-average"];
|
|
167
|
-
var CHART_LABEL_POSITIONS = ["auto", "inside", "outside", "center"];
|
|
168
|
-
var CHART_AXIS_KINDS = ["category", "date"];
|
|
169
|
-
var CHART_SERIES_TYPES = ["bar", "line", "area", "scatter"];
|
|
170
|
-
var CHART_STACKINGS = ["none", "stacked", "percent"];
|
|
171
|
-
var CHART_CURVES = ["linear", "smooth", "step"];
|
|
172
|
-
var CHART_ORIENTATIONS = ["vertical", "horizontal"];
|
|
173
|
-
var CHART_LEGEND_POSITIONS = ["top", "bottom", "left", "right"];
|
|
174
|
-
var CHART_NUMBER_STYLES = ["number", "percent", "currency", "compact"];
|
|
175
|
-
var CHART_TYPE_LABELS = {
|
|
176
|
-
bar: "Bar",
|
|
177
|
-
line: "Line",
|
|
178
|
-
area: "Area",
|
|
179
|
-
scatter: "Scatter",
|
|
180
|
-
bubble: "Bubble",
|
|
181
|
-
pie: "Pie",
|
|
182
|
-
donut: "Donut",
|
|
183
|
-
radar: "Radar",
|
|
184
|
-
waterfall: "Waterfall",
|
|
185
|
-
histogram: "Histogram",
|
|
186
|
-
box: "Box plot",
|
|
187
|
-
funnel: "Funnel",
|
|
188
|
-
treemap: "Treemap"
|
|
189
|
-
};
|
|
190
|
-
var DEFAULT_CHART_PALETTE = ["#4269d0", "#efb118", "#ff725c", "#6cc5b0", "#3ca951", "#ff8ab7", "#a463f2", "#97bbf5", "#9c6b4e", "#9498a0"];
|
|
191
|
-
var DEFAULT_CHART_THEME = {
|
|
192
|
-
background: "#ffffff",
|
|
193
|
-
surface: "#ffffff",
|
|
194
|
-
text: "#0f172a",
|
|
195
|
-
mutedText: "#64748b",
|
|
196
|
-
grid: "#e2e8f0",
|
|
197
|
-
axis: "#94a3b8",
|
|
198
|
-
selection: "#2563eb",
|
|
199
|
-
positive: "#3ca951",
|
|
200
|
-
negative: "#e5484d",
|
|
201
|
-
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
202
|
-
fontSize: 11,
|
|
203
|
-
titleSize: 15
|
|
204
|
-
};
|
|
205
|
-
var CSS_VARIABLE_CHART_THEME = {
|
|
206
|
-
...DEFAULT_CHART_THEME,
|
|
207
|
-
background: "transparent",
|
|
208
|
-
surface: "var(--background, #ffffff)",
|
|
209
|
-
text: "var(--foreground, #0f172a)",
|
|
210
|
-
mutedText: "var(--muted-foreground, #64748b)",
|
|
211
|
-
grid: "var(--border, #e2e8f0)",
|
|
212
|
-
axis: "var(--muted-foreground, #94a3b8)",
|
|
213
|
-
selection: "var(--primary, #2563eb)",
|
|
214
|
-
fontFamily: "inherit"
|
|
215
|
-
};
|
|
216
|
-
function isCartesianChart(type) {
|
|
217
|
-
return type === "bar" || type === "line" || type === "area" || type === "scatter";
|
|
218
|
-
}
|
|
219
|
-
function isXyChart(spec) {
|
|
220
|
-
if (spec.type === "bubble") return true;
|
|
221
|
-
return spec.type === "scatter" && spec.series.some((s) => !s.hidden && Array.isArray(s.x) && s.x.some((v) => v !== null));
|
|
222
|
-
}
|
|
223
|
-
function chartFamily(spec) {
|
|
224
|
-
if (isXyChart(spec)) return "xy";
|
|
225
|
-
switch (spec.type) {
|
|
226
|
-
case "pie":
|
|
227
|
-
case "donut":
|
|
228
|
-
return "pie";
|
|
229
|
-
case "radar":
|
|
230
|
-
case "waterfall":
|
|
231
|
-
case "histogram":
|
|
232
|
-
case "box":
|
|
233
|
-
case "funnel":
|
|
234
|
-
case "treemap":
|
|
235
|
-
return spec.type;
|
|
236
|
-
case "bubble":
|
|
237
|
-
return "xy";
|
|
238
|
-
default:
|
|
239
|
-
return "cartesian";
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
function chartColorsByCategory(type) {
|
|
243
|
-
return type === "pie" || type === "donut" || type === "funnel" || type === "treemap";
|
|
244
|
-
}
|
|
245
|
-
function defaultChartAxis(value) {
|
|
246
|
-
return { show: true, title: "", min: null, max: null, step: null, gridlines: value, format: {} };
|
|
247
|
-
}
|
|
248
|
-
function createChartSpec(partial = {}) {
|
|
249
|
-
return normalizeChartSpec(partial).spec;
|
|
250
|
-
}
|
|
251
|
-
function chartSeriesColor(spec, seriesIndex) {
|
|
252
|
-
const series = spec.series[seriesIndex];
|
|
253
|
-
if (series?.color) return series.color;
|
|
254
|
-
return cyclePaletteColor(spec.palette.length ? spec.palette : DEFAULT_CHART_PALETTE, seriesIndex);
|
|
255
|
-
}
|
|
256
|
-
function chartSliceColor(spec, index) {
|
|
257
|
-
return cyclePaletteColor(spec.palette.length ? spec.palette : DEFAULT_CHART_PALETTE, index);
|
|
258
|
-
}
|
|
259
|
-
function chartPointColor(spec, seriesIndex, index) {
|
|
260
|
-
return spec.series[seriesIndex]?.pointColors?.[index] || chartSeriesColor(spec, seriesIndex);
|
|
261
|
-
}
|
|
262
|
-
function chartSeriesType(spec, series) {
|
|
263
|
-
if (series.type) return series.type;
|
|
264
|
-
if (spec.type === "bubble") return "scatter";
|
|
265
|
-
return isCartesianChart(spec.type) ? spec.type : "bar";
|
|
266
|
-
}
|
|
267
|
-
function uniqueChartSeriesId(series, base = "series") {
|
|
268
|
-
const used = new Set(series.map((s) => s.id));
|
|
269
|
-
for (let i = series.length + 1; ; i++) {
|
|
270
|
-
const id = `${base}-${i}`;
|
|
271
|
-
if (!used.has(id)) return id;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function parseChartValue(input) {
|
|
275
|
-
if (input === null || input === void 0) return null;
|
|
276
|
-
if (typeof input === "number") return Number.isFinite(input) ? input : void 0;
|
|
277
|
-
if (typeof input !== "string") return void 0;
|
|
278
|
-
let text2 = input.trim();
|
|
279
|
-
if (!text2 || text2 === "-" || text2 === "\u2014") return null;
|
|
280
|
-
let negative = false;
|
|
281
|
-
if (/^\(.*\)$/.test(text2)) {
|
|
282
|
-
negative = true;
|
|
283
|
-
text2 = text2.slice(1, -1).trim();
|
|
284
|
-
}
|
|
285
|
-
const percent = text2.endsWith("%");
|
|
286
|
-
if (percent) text2 = text2.slice(0, -1).trim();
|
|
287
|
-
text2 = text2.replace(/^[$€£¥]/, "").replace(/,/g, "").replace(/\s+/g, "");
|
|
288
|
-
if (!/^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(text2)) return void 0;
|
|
289
|
-
let value = Number(text2);
|
|
290
|
-
if (percent) value /= 100;
|
|
291
|
-
if (negative) value = -value;
|
|
292
|
-
return Number.isFinite(value) ? value : void 0;
|
|
293
|
-
}
|
|
294
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
295
|
-
function pick(value, options, fallback, path, issues) {
|
|
296
|
-
if (value === void 0) return fallback;
|
|
297
|
-
if (typeof value === "string" && options.includes(value)) return value;
|
|
298
|
-
issues.push({ path, message: `${path} must be one of ${options.map((o) => `"${o}"`).join(", ")}; got ${JSON.stringify(value)}.` });
|
|
299
|
-
return fallback;
|
|
300
|
-
}
|
|
301
|
-
function text(value, fallback, path, issues) {
|
|
302
|
-
if (value === void 0 || value === null) return fallback;
|
|
303
|
-
if (typeof value === "string") return value;
|
|
304
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
305
|
-
issues.push({ path, message: `${path} must be text.` });
|
|
306
|
-
return fallback;
|
|
307
|
-
}
|
|
308
|
-
function flag(value, fallback, path, issues) {
|
|
309
|
-
if (value === void 0) return fallback;
|
|
310
|
-
if (typeof value === "boolean") return value;
|
|
311
|
-
issues.push({ path, message: `${path} must be true or false.` });
|
|
312
|
-
return fallback;
|
|
313
|
-
}
|
|
314
|
-
function bound(value, path, issues) {
|
|
315
|
-
if (value === void 0 || value === null || value === "") return null;
|
|
316
|
-
const n = typeof value === "string" ? Number(value) : value;
|
|
317
|
-
if (typeof n === "number" && Number.isFinite(n)) return n;
|
|
318
|
-
issues.push({ path, message: `${path} must be a number or null.` });
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
function normalizeFormat(value, path, issues) {
|
|
322
|
-
if (value === void 0 || value === null) return {};
|
|
323
|
-
if (!isRecord(value)) {
|
|
324
|
-
issues.push({ path, message: `${path} must be an object.` });
|
|
325
|
-
return {};
|
|
326
|
-
}
|
|
327
|
-
const out = {};
|
|
328
|
-
if (value.style !== void 0) out.style = pick(value.style, CHART_NUMBER_STYLES, "number", `${path}.style`, issues);
|
|
329
|
-
if (value.decimals !== void 0) {
|
|
330
|
-
const d = Number(value.decimals);
|
|
331
|
-
if (Number.isInteger(d) && d >= 0 && d <= 10) out.decimals = d;
|
|
332
|
-
else issues.push({ path: `${path}.decimals`, message: `${path}.decimals must be a whole number from 0 to 10.` });
|
|
333
|
-
}
|
|
334
|
-
for (const key2 of ["currency", "prefix", "suffix", "code"]) {
|
|
335
|
-
if (value[key2] !== void 0) out[key2] = text(value[key2], "", `${path}.${key2}`, issues);
|
|
336
|
-
}
|
|
337
|
-
return out;
|
|
338
|
-
}
|
|
339
|
-
function normalizeChartAxis(value, fallback, path, issues = []) {
|
|
340
|
-
if (value === void 0) return { ...fallback, format: { ...fallback.format } };
|
|
341
|
-
if (!isRecord(value)) {
|
|
342
|
-
issues.push({ path, message: `${path} must be an object.` });
|
|
343
|
-
return { ...fallback, format: { ...fallback.format } };
|
|
344
|
-
}
|
|
345
|
-
let min = "min" in value ? bound(value.min, `${path}.min`, issues) : fallback.min;
|
|
346
|
-
let max = "max" in value ? bound(value.max, `${path}.max`, issues) : fallback.max;
|
|
347
|
-
if (min !== null && max !== null && min >= max) {
|
|
348
|
-
issues.push({ path: `${path}.max`, message: `${path}.max (${max}) must be greater than ${path}.min (${min}).` });
|
|
349
|
-
min = fallback.min;
|
|
350
|
-
max = fallback.max;
|
|
351
|
-
}
|
|
352
|
-
let step = "step" in value ? bound(value.step, `${path}.step`, issues) : fallback.step;
|
|
353
|
-
if (step !== null && !(step > 0)) {
|
|
354
|
-
issues.push({ path: `${path}.step`, message: `${path}.step must be greater than 0 (or null for automatic).` });
|
|
355
|
-
step = fallback.step;
|
|
356
|
-
}
|
|
357
|
-
const axis = {
|
|
358
|
-
show: flag(value.show, fallback.show, `${path}.show`, issues),
|
|
359
|
-
title: text(value.title, fallback.title, `${path}.title`, issues),
|
|
360
|
-
min,
|
|
361
|
-
max,
|
|
362
|
-
step,
|
|
363
|
-
gridlines: flag(value.gridlines, fallback.gridlines, `${path}.gridlines`, issues),
|
|
364
|
-
format: "format" in value ? normalizeFormat(value.format, `${path}.format`, issues) : { ...fallback.format }
|
|
365
|
-
};
|
|
366
|
-
const kind = "kind" in value ? value.kind === null ? void 0 : pick(value.kind, CHART_AXIS_KINDS, "category", `${path}.kind`, issues) : fallback.kind;
|
|
367
|
-
if (kind && kind !== "category") axis.kind = kind;
|
|
368
|
-
return axis;
|
|
369
|
-
}
|
|
370
|
-
function normalizeLegend(value, issues) {
|
|
371
|
-
const fallback = { show: true, position: "bottom" };
|
|
372
|
-
if (value === void 0) return fallback;
|
|
373
|
-
if (!isRecord(value)) {
|
|
374
|
-
issues.push({ path: "legend", message: "legend must be an object." });
|
|
375
|
-
return fallback;
|
|
376
|
-
}
|
|
377
|
-
return {
|
|
378
|
-
show: flag(value.show, fallback.show, "legend.show", issues),
|
|
379
|
-
position: pick(value.position, CHART_LEGEND_POSITIONS, fallback.position, "legend.position", issues)
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
function normalizeChartValues(value, length, path, issues = []) {
|
|
383
|
-
if (value === void 0) return length ? Array(length).fill(null) : [];
|
|
384
|
-
if (!Array.isArray(value)) {
|
|
385
|
-
issues.push({ path, message: `${path} must be an array of numbers.` });
|
|
386
|
-
return length ? Array(length).fill(null) : [];
|
|
387
|
-
}
|
|
388
|
-
const out = value.map((v, i) => {
|
|
389
|
-
const parsed = parseChartValue(v);
|
|
390
|
-
if (parsed === void 0) {
|
|
391
|
-
issues.push({ path: `${path}[${i}]`, message: `${path}[${i}] is not a number: ${JSON.stringify(v)}.` });
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
return parsed;
|
|
395
|
-
});
|
|
396
|
-
if (length === void 0) return out;
|
|
397
|
-
if (out.length > length) return out.slice(0, length);
|
|
398
|
-
while (out.length < length) out.push(null);
|
|
399
|
-
return out;
|
|
400
|
-
}
|
|
401
|
-
function normalizeSeries(value, length, used, index, issues) {
|
|
402
|
-
const path = `series[${index}]`;
|
|
403
|
-
if (!isRecord(value)) {
|
|
404
|
-
issues.push({ path, message: `${path} must be an object with a name and values.` });
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
let id = typeof value.id === "string" && value.id.trim() ? value.id.trim() : "";
|
|
408
|
-
if (!id || used.has(id)) {
|
|
409
|
-
if (id) issues.push({ path: `${path}.id`, message: `Series id "${id}" is used twice; the second was renamed.` });
|
|
410
|
-
for (let n = index + 1; ; n++) {
|
|
411
|
-
id = `series-${n}`;
|
|
412
|
-
if (!used.has(id)) break;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
used.add(id);
|
|
416
|
-
const series = {
|
|
417
|
-
id,
|
|
418
|
-
name: text(value.name, `Series ${index + 1}`, `${path}.name`, issues),
|
|
419
|
-
values: normalizeChartValues(value.values, length, `${path}.values`, issues)
|
|
420
|
-
};
|
|
421
|
-
if (typeof value.color === "string" && value.color.trim()) series.color = value.color.trim();
|
|
422
|
-
if (value.type !== void 0 && value.type !== null) series.type = pick(value.type, CHART_SERIES_TYPES, "bar", `${path}.type`, issues);
|
|
423
|
-
if (value.hidden === true) series.hidden = true;
|
|
424
|
-
if (value.axis !== void 0 && value.axis !== null && pick(value.axis, ["primary", "secondary"], "primary", `${path}.axis`, issues) === "secondary") series.axis = "secondary";
|
|
425
|
-
for (const key2 of ["x", "sizes"]) {
|
|
426
|
-
if (value[key2] === void 0 || value[key2] === null) continue;
|
|
427
|
-
const list = normalizeChartValues(value[key2], length, `${path}.${key2}`, issues);
|
|
428
|
-
if (list.some((v) => v !== null)) series[key2] = list;
|
|
429
|
-
}
|
|
430
|
-
if (key(value, "sizes") && series.sizes?.some((v) => v !== null && v < 0)) {
|
|
431
|
-
issues.push({ path: `${path}.sizes`, message: `${path}.sizes must not be negative.` });
|
|
432
|
-
series.sizes = series.sizes.map((v) => v !== null && v < 0 ? null : v);
|
|
433
|
-
}
|
|
434
|
-
if (value.pointColors !== void 0 && value.pointColors !== null) {
|
|
435
|
-
if (!Array.isArray(value.pointColors)) issues.push({ path: `${path}.pointColors`, message: `${path}.pointColors must be an array of colours or nulls.` });
|
|
436
|
-
else {
|
|
437
|
-
const colors = Array.from({ length }, (_, i) => {
|
|
438
|
-
const c = value.pointColors[i];
|
|
439
|
-
return typeof c === "string" && c.trim() ? c.trim() : null;
|
|
440
|
-
});
|
|
441
|
-
if (colors.some((c) => c !== null)) series.pointColors = colors;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (value.marker !== void 0 && value.marker !== null) series.marker = pick(value.marker, CHART_MARKERS, "circle", `${path}.marker`, issues);
|
|
445
|
-
if (value.curve !== void 0 && value.curve !== null) series.curve = pick(value.curve, CHART_CURVES, "linear", `${path}.curve`, issues);
|
|
446
|
-
if (value.dataLabels !== void 0 && value.dataLabels !== null) series.dataLabels = flag(value.dataLabels, false, `${path}.dataLabels`, issues);
|
|
447
|
-
if (value.format !== void 0 && value.format !== null) {
|
|
448
|
-
const format = normalizeFormat(value.format, `${path}.format`, issues);
|
|
449
|
-
if (Object.keys(format).length) series.format = format;
|
|
450
|
-
}
|
|
451
|
-
if (value.trendlines !== void 0 && value.trendlines !== null) {
|
|
452
|
-
if (!Array.isArray(value.trendlines)) issues.push({ path: `${path}.trendlines`, message: `${path}.trendlines must be an array.` });
|
|
453
|
-
else {
|
|
454
|
-
const lines = value.trendlines.map((t, i) => normalizeChartTrendline(t, `${path}.trendlines[${i}]`, issues)).filter((t) => t !== null);
|
|
455
|
-
if (lines.length) series.trendlines = lines;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
return series;
|
|
459
|
-
}
|
|
460
|
-
var key = (value, name) => value[name] !== void 0 && value[name] !== null;
|
|
461
|
-
function normalizeChartTrendline(value, path, issues = []) {
|
|
462
|
-
if (!isRecord(value)) {
|
|
463
|
-
issues.push({ path, message: `${path} must be an object with a type.` });
|
|
464
|
-
return null;
|
|
465
|
-
}
|
|
466
|
-
const type = pick(value.type, CHART_TRENDLINE_TYPES, "linear", `${path}.type`, issues);
|
|
467
|
-
const line = { type };
|
|
468
|
-
const whole = (name, min, max) => {
|
|
469
|
-
if (!key(value, name)) return;
|
|
470
|
-
const n = Number(value[name]);
|
|
471
|
-
if (Number.isInteger(n) && n >= min && n <= max) line[name] = n;
|
|
472
|
-
else issues.push({ path: `${path}.${name}`, message: `${path}.${name} must be a whole number from ${min} to ${max}.` });
|
|
473
|
-
};
|
|
474
|
-
if (type === "polynomial") whole("order", 2, 6);
|
|
475
|
-
if (type === "moving-average") whole("period", 2, 1e3);
|
|
476
|
-
for (const name of ["forward", "backward"]) {
|
|
477
|
-
if (!key(value, name)) continue;
|
|
478
|
-
const n = Number(value[name]);
|
|
479
|
-
if (Number.isFinite(n) && n >= 0) {
|
|
480
|
-
if (n > 0) line[name] = n;
|
|
481
|
-
} else issues.push({ path: `${path}.${name}`, message: `${path}.${name} must be a number of x units, 0 or more.` });
|
|
482
|
-
}
|
|
483
|
-
if (key(value, "intercept")) {
|
|
484
|
-
const n = Number(value.intercept);
|
|
485
|
-
if (!Number.isFinite(n)) issues.push({ path: `${path}.intercept`, message: `${path}.intercept must be a number or null.` });
|
|
486
|
-
else if (type === "linear" || type === "polynomial" || type === "exponential") {
|
|
487
|
-
if (type === "exponential" && n <= 0) issues.push({ path: `${path}.intercept`, message: `${path}.intercept must be greater than 0 for an exponential trendline.` });
|
|
488
|
-
else line.intercept = n;
|
|
489
|
-
} else issues.push({ path: `${path}.intercept`, message: `A ${type} trendline cannot have a fixed intercept; use linear, polynomial or exponential.` });
|
|
490
|
-
}
|
|
491
|
-
for (const name of ["showEquation", "showR2"]) if (key(value, name) && flag(value[name], false, `${path}.${name}`, issues)) line[name] = true;
|
|
492
|
-
for (const name of ["name", "color"]) {
|
|
493
|
-
if (!key(value, name)) continue;
|
|
494
|
-
const t = text(value[name], "", `${path}.${name}`, issues).trim();
|
|
495
|
-
if (t) line[name] = t;
|
|
496
|
-
}
|
|
497
|
-
return line;
|
|
498
|
-
}
|
|
499
|
-
function normalizeChartSpec(input) {
|
|
500
|
-
const issues = [];
|
|
501
|
-
const value = isRecord(input) ? input : {};
|
|
502
|
-
if (input !== void 0 && !isRecord(input)) issues.push({ path: "", message: "A chart must be an object." });
|
|
503
|
-
const type = pick(value.type, CHART_TYPES, "bar", "type", issues);
|
|
504
|
-
const rawSeries = Array.isArray(value.series) ? value.series : [];
|
|
505
|
-
if (value.series !== void 0 && !Array.isArray(value.series)) issues.push({ path: "series", message: "series must be an array." });
|
|
506
|
-
let categories;
|
|
507
|
-
if (Array.isArray(value.categories)) categories = value.categories.map((c, i) => text(c, "", `categories[${i}]`, issues));
|
|
508
|
-
else {
|
|
509
|
-
if (value.categories !== void 0) issues.push({ path: "categories", message: "categories must be an array of labels." });
|
|
510
|
-
const longest = rawSeries.reduce((n, s) => Math.max(n, isRecord(s) && Array.isArray(s.values) ? s.values.length : 0), 0);
|
|
511
|
-
categories = Array.from({ length: longest }, (_, i) => `${i + 1}`);
|
|
512
|
-
}
|
|
513
|
-
const used = /* @__PURE__ */ new Set();
|
|
514
|
-
const series = rawSeries.map((s, i) => normalizeSeries(s, categories.length, used, i, issues)).filter((s) => s !== null);
|
|
515
|
-
let palette = [...DEFAULT_CHART_PALETTE];
|
|
516
|
-
if (Array.isArray(value.palette)) {
|
|
517
|
-
const colors = value.palette.filter((c) => typeof c === "string" && c.trim() !== "");
|
|
518
|
-
if (colors.length) palette = colors;
|
|
519
|
-
else issues.push({ path: "palette", message: "palette must list at least one colour." });
|
|
520
|
-
} else if (value.palette !== void 0) issues.push({ path: "palette", message: "palette must be an array of colours." });
|
|
521
|
-
const fraction = (name, fallback, min, max) => {
|
|
522
|
-
if (value[name] === void 0) return fallback;
|
|
523
|
-
const r = Number(value[name]);
|
|
524
|
-
if (value[name] !== null && Number.isFinite(r) && r >= min && r <= max) return r;
|
|
525
|
-
issues.push({ path: name, message: `${name} must be a number from ${min} to ${max}.` });
|
|
526
|
-
return fallback;
|
|
527
|
-
};
|
|
528
|
-
const innerRadius = fraction("innerRadius", 0.55, 0, 0.9);
|
|
529
|
-
const gapWidth = fraction("gapWidth", 0.28, 0, 0.9);
|
|
530
|
-
const overlap = fraction("overlap", -0.12, -1, 1);
|
|
531
|
-
let bins = null;
|
|
532
|
-
if (value.bins !== void 0 && value.bins !== null) {
|
|
533
|
-
const b = Number(value.bins);
|
|
534
|
-
if (Number.isInteger(b) && b >= 1 && b <= 500) bins = b;
|
|
535
|
-
else issues.push({ path: "bins", message: "bins must be a whole number from 1 to 500, or null for automatic." });
|
|
536
|
-
}
|
|
537
|
-
const spec = {
|
|
538
|
-
version: 1,
|
|
539
|
-
type,
|
|
540
|
-
title: text(value.title, "", "title", issues),
|
|
541
|
-
subtitle: text(value.subtitle, "", "subtitle", issues),
|
|
542
|
-
categories,
|
|
543
|
-
series,
|
|
544
|
-
xAxis: normalizeChartAxis(value.xAxis, defaultChartAxis(false), "xAxis", issues),
|
|
545
|
-
yAxis: normalizeChartAxis(value.yAxis, defaultChartAxis(true), "yAxis", issues),
|
|
546
|
-
yAxis2: normalizeChartAxis(value.yAxis2, defaultChartAxis(false), "yAxis2", issues),
|
|
547
|
-
legend: normalizeLegend(value.legend, issues),
|
|
548
|
-
stacking: pick(value.stacking, CHART_STACKINGS, "none", "stacking", issues),
|
|
549
|
-
orientation: pick(value.orientation, CHART_ORIENTATIONS, "vertical", "orientation", issues),
|
|
550
|
-
curve: pick(value.curve, CHART_CURVES, "linear", "curve", issues),
|
|
551
|
-
points: flag(value.points, true, "points", issues),
|
|
552
|
-
dataLabels: flag(value.dataLabels, false, "dataLabels", issues),
|
|
553
|
-
labelPosition: pick(value.labelPosition, CHART_LABEL_POSITIONS, "auto", "labelPosition", issues),
|
|
554
|
-
palette,
|
|
555
|
-
innerRadius,
|
|
556
|
-
gapWidth,
|
|
557
|
-
overlap,
|
|
558
|
-
bins,
|
|
559
|
-
showTotal: flag(value.showTotal, true, "showTotal", issues)
|
|
560
|
-
};
|
|
561
|
-
return { spec, issues };
|
|
562
|
-
}
|
|
563
|
-
function cloneChartSpec(spec) {
|
|
564
|
-
return {
|
|
565
|
-
...spec,
|
|
566
|
-
categories: [...spec.categories],
|
|
567
|
-
series: spec.series.map(cloneChartSeries),
|
|
568
|
-
xAxis: { ...spec.xAxis, format: { ...spec.xAxis.format } },
|
|
569
|
-
yAxis: { ...spec.yAxis, format: { ...spec.yAxis.format } },
|
|
570
|
-
yAxis2: { ...spec.yAxis2, format: { ...spec.yAxis2.format } },
|
|
571
|
-
legend: { ...spec.legend },
|
|
572
|
-
palette: [...spec.palette]
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
function cloneChartSeries(series) {
|
|
576
|
-
const copy = { ...series, values: [...series.values] };
|
|
577
|
-
if (series.x) copy.x = [...series.x];
|
|
578
|
-
if (series.sizes) copy.sizes = [...series.sizes];
|
|
579
|
-
if (series.pointColors) copy.pointColors = [...series.pointColors];
|
|
580
|
-
if (series.format) copy.format = { ...series.format };
|
|
581
|
-
if (series.trendlines) copy.trendlines = series.trendlines.map((t) => ({ ...t }));
|
|
582
|
-
return copy;
|
|
583
|
-
}
|
|
584
|
-
function chartDataFromTable(rows, options = {}) {
|
|
585
|
-
let grid = rows.map((r) => [...r]).filter((r) => r.some((c) => c !== null && c !== void 0 && String(c).trim() !== ""));
|
|
586
|
-
if (options.seriesIn === "rows") {
|
|
587
|
-
const width2 = Math.max(0, ...grid.map((r) => r.length));
|
|
588
|
-
grid = Array.from({ length: width2 }, (_, c) => grid.map((r) => r[c]));
|
|
589
|
-
}
|
|
590
|
-
const issues = [];
|
|
591
|
-
if (!grid.length) return { categories: [], series: [], issues };
|
|
592
|
-
const width = Math.max(...grid.map((r) => r.length));
|
|
593
|
-
const header = options.header === "auto" ? grid[0].slice(1).some((c) => c !== null && c !== void 0 && String(c).trim() !== "" && parseChartValue(c) === void 0) : options.header ?? true;
|
|
594
|
-
const names = header ? grid[0] : [];
|
|
595
|
-
const body = header ? grid.slice(1) : grid;
|
|
596
|
-
const categories = body.map((r, i) => {
|
|
597
|
-
const c = r[0];
|
|
598
|
-
return c === null || c === void 0 || String(c).trim() === "" ? `${i + 1}` : String(c).trim();
|
|
599
|
-
});
|
|
600
|
-
const series = [];
|
|
601
|
-
for (let col = 1; col < width; col++) {
|
|
602
|
-
const name = names[col] !== void 0 && String(names[col]).trim() ? String(names[col]).trim() : `Series ${col}`;
|
|
603
|
-
const values = body.map((r, i) => {
|
|
604
|
-
const parsed = parseChartValue(r[col] ?? null);
|
|
605
|
-
if (parsed === void 0) {
|
|
606
|
-
issues.push({ path: `${name}[${i}]`, message: `"${String(r[col])}" in ${name} (${categories[i]}) is not a number; it was left blank.` });
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
return parsed;
|
|
610
|
-
});
|
|
611
|
-
series.push({ id: `series-${col}`, name, values });
|
|
612
|
-
}
|
|
613
|
-
if (options.xFromFirstColumn) {
|
|
614
|
-
const x = body.map((r, i) => {
|
|
615
|
-
const parsed = parseChartValue(r[0] ?? null);
|
|
616
|
-
if (parsed === void 0) issues.push({ path: `x[${i}]`, message: `"${String(r[0])}" in the first column is not a number; that point has no x.` });
|
|
617
|
-
return parsed ?? null;
|
|
618
|
-
});
|
|
619
|
-
for (const s of series) s.x = [...x];
|
|
620
|
-
}
|
|
621
|
-
return { categories, series, issues };
|
|
622
|
-
}
|
|
623
|
-
function chartDataToTable(spec, options = {}) {
|
|
624
|
-
return [
|
|
625
|
-
[options.categoryHeader ?? "", ...spec.series.map((s) => s.name)],
|
|
626
|
-
...spec.categories.map((category, i) => [category, ...spec.series.map((s) => s.values[i] ?? null)])
|
|
627
|
-
];
|
|
628
|
-
}
|
|
629
|
-
function chartColorInputValue(color) {
|
|
630
|
-
if (/^#[0-9a-f]{6}$/i.test(color)) return color.toLowerCase();
|
|
631
|
-
if (/^#[0-9a-f]{3}$/i.test(color)) return `#${[...color.slice(1)].map((c) => c + c).join("")}`.toLowerCase();
|
|
632
|
-
return "#000000";
|
|
633
|
-
}
|
|
634
|
-
|
|
635
33
|
// src/chart/mermaid.ts
|
|
636
34
|
var CHART_MERMAID_META_PREFIX = "%% @pixerate ";
|
|
637
|
-
var quote = (
|
|
638
|
-
var unquote = (
|
|
639
|
-
const t =
|
|
35
|
+
var quote = (text) => `"${text.replace(/"/g, "#quot;")}"`;
|
|
36
|
+
var unquote = (text) => {
|
|
37
|
+
const t = text.trim();
|
|
640
38
|
return (t.length >= 2 && t.startsWith('"') && t.endsWith('"') ? t.slice(1, -1) : t).replace(/#quot;/g, '"');
|
|
641
39
|
};
|
|
642
|
-
var word = (
|
|
40
|
+
var word = (text) => /^[A-Za-z0-9_]+$/.test(text) ? text : quote(text);
|
|
643
41
|
var num = (value) => Number.isInteger(value) ? String(value) : String(Number(value.toPrecision(10)));
|
|
644
|
-
function splitOutside(
|
|
42
|
+
function splitOutside(text, sep) {
|
|
645
43
|
const out = [];
|
|
646
44
|
let cur = "";
|
|
647
45
|
let quoted = false;
|
|
648
46
|
let depth = 0;
|
|
649
|
-
for (const ch of
|
|
47
|
+
for (const ch of text) {
|
|
650
48
|
if (ch === '"') quoted = !quoted;
|
|
651
49
|
else if (!quoted && (ch === "[" || ch === "{")) depth++;
|
|
652
50
|
else if (!quoted && (ch === "]" || ch === "}")) depth--;
|
|
@@ -672,13 +70,13 @@ function slug(label, i, taken) {
|
|
|
672
70
|
taken.add(id);
|
|
673
71
|
return id;
|
|
674
72
|
}
|
|
675
|
-
function metaFor(spec,
|
|
676
|
-
const base = chartFromMermaid(
|
|
73
|
+
function metaFor(spec, text) {
|
|
74
|
+
const base = chartFromMermaid(text, { ignoreMeta: true }).spec;
|
|
677
75
|
const meta = {};
|
|
678
76
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
679
|
-
for (const
|
|
680
|
-
if (
|
|
681
|
-
if (!same(spec[
|
|
77
|
+
for (const key of Object.keys(spec)) {
|
|
78
|
+
if (key === "series" || key === "version") continue;
|
|
79
|
+
if (!same(spec[key], base[key])) meta[key] = spec[key];
|
|
682
80
|
}
|
|
683
81
|
const series = spec.series.map((s, i) => {
|
|
684
82
|
const b = base.series[i];
|
|
@@ -745,21 +143,21 @@ function chartToMermaid(spec, options = {}) {
|
|
|
745
143
|
lines.push(`${ind}${kind} ${quote(s.name)} [${s.values.map(v).join(", ")}]`);
|
|
746
144
|
}
|
|
747
145
|
}
|
|
748
|
-
let
|
|
146
|
+
let text = `${lines.join("\n")}
|
|
749
147
|
`;
|
|
750
148
|
if (options.meta !== false) {
|
|
751
|
-
const meta = metaFor(spec,
|
|
149
|
+
const meta = metaFor(spec, text);
|
|
752
150
|
if (meta) {
|
|
753
151
|
const [header, ...rest] = lines;
|
|
754
|
-
|
|
152
|
+
text = `${[header, `${CHART_MERMAID_META_PREFIX}${JSON.stringify({ v: 1, ...meta })}`, ...rest].join("\n")}
|
|
755
153
|
`;
|
|
756
154
|
}
|
|
757
155
|
}
|
|
758
|
-
return
|
|
156
|
+
return text;
|
|
759
157
|
}
|
|
760
158
|
var RANGE = /^(-?\d+(?:\.\d+)?)\s*-->\s*(-?\d+(?:\.\d+)?)$/;
|
|
761
|
-
function takeTitle(
|
|
762
|
-
const t =
|
|
159
|
+
function takeTitle(text) {
|
|
160
|
+
const t = text.trim();
|
|
763
161
|
if (t.startsWith('"')) {
|
|
764
162
|
const end = t.indexOf('"', 1);
|
|
765
163
|
if (end > 0) return { title: unquote(t.slice(0, end + 1)), rest: t.slice(end + 1).trim() };
|
|
@@ -768,8 +166,8 @@ function takeTitle(text2) {
|
|
|
768
166
|
const m = /^(\S+)\s*(.*)$/.exec(t);
|
|
769
167
|
return { title: m[1], rest: m[2].trim() };
|
|
770
168
|
}
|
|
771
|
-
function parseList(
|
|
772
|
-
const t =
|
|
169
|
+
function parseList(text, partial) {
|
|
170
|
+
const t = text.trim();
|
|
773
171
|
let m = /^\[(.*)\]$/s.exec(t);
|
|
774
172
|
if (!m && partial) m = /^\[(.*)$/s.exec(t);
|
|
775
173
|
if (!m) return void 0;
|
|
@@ -781,10 +179,10 @@ function parseList(text2, partial) {
|
|
|
781
179
|
}
|
|
782
180
|
return items.map(unquote);
|
|
783
181
|
}
|
|
784
|
-
function chartFromMermaid(
|
|
182
|
+
function chartFromMermaid(text, options = {}) {
|
|
785
183
|
const issues = [];
|
|
786
184
|
const partial = !!options.partial;
|
|
787
|
-
const all =
|
|
185
|
+
const all = text.replace(/^/, "").split(/\r?\n/);
|
|
788
186
|
let start = 0;
|
|
789
187
|
let frontTitle;
|
|
790
188
|
if (all[0]?.trim() === "---") {
|
|
@@ -978,8 +376,8 @@ function chartFromMermaid(text2, options = {}) {
|
|
|
978
376
|
if (!family && !partial && !issues.length) issues.push({ path: "header", message: "The text is empty: expected a chart header (xychart-beta, pie or radar-beta)." });
|
|
979
377
|
return { spec, issues: [...issues, ...specIssues], family, hasMeta: !!meta };
|
|
980
378
|
}
|
|
981
|
-
function findMermaidCharts(
|
|
982
|
-
const lines =
|
|
379
|
+
function findMermaidCharts(text) {
|
|
380
|
+
const lines = text.replace(/^/, "").split(/\r?\n/);
|
|
983
381
|
const found = [];
|
|
984
382
|
const familyOf = (body) => {
|
|
985
383
|
const header = body.split(/\r?\n/).find((l) => l.trim() && !l.trim().startsWith("%%") && l.trim() !== "---");
|
|
@@ -1005,8 +403,8 @@ function findMermaidCharts(text2) {
|
|
|
1005
403
|
i = end;
|
|
1006
404
|
}
|
|
1007
405
|
if (!found.length) {
|
|
1008
|
-
const family = familyOf(
|
|
1009
|
-
if (family) found.push({ source: `${
|
|
406
|
+
const family = familyOf(text);
|
|
407
|
+
if (family) found.push({ source: `${text.trim()}
|
|
1010
408
|
`, family, line: 1 });
|
|
1011
409
|
}
|
|
1012
410
|
return found;
|
|
@@ -1015,20 +413,20 @@ function findMermaidCharts(text2) {
|
|
|
1015
413
|
// src/chart/date.ts
|
|
1016
414
|
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1017
415
|
function parseChartDate(label) {
|
|
1018
|
-
const
|
|
1019
|
-
let m = /^(\d{4})$/.exec(
|
|
416
|
+
const text = label.trim();
|
|
417
|
+
let m = /^(\d{4})$/.exec(text);
|
|
1020
418
|
if (m) return Date.UTC(Number(m[1]), 0, 1);
|
|
1021
|
-
m = /^(\d{4})[-/](\d{1,2})$/.exec(
|
|
419
|
+
m = /^(\d{4})[-/](\d{1,2})$/.exec(text);
|
|
1022
420
|
if (m) return valid(Number(m[1]), Number(m[2]) - 1, 1);
|
|
1023
|
-
m = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[T ](\d{1,2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/.exec(
|
|
421
|
+
m = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[T ](\d{1,2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/.exec(text);
|
|
1024
422
|
if (m) {
|
|
1025
423
|
if (m[4] !== void 0) {
|
|
1026
|
-
const t = Date.parse(
|
|
424
|
+
const t = Date.parse(text.replace(" ", "T").replace(/\//g, "-") + (m[7] ? "" : "Z"));
|
|
1027
425
|
return Number.isFinite(t) ? t : null;
|
|
1028
426
|
}
|
|
1029
427
|
return valid(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
1030
428
|
}
|
|
1031
|
-
m = /^(?:(\d{1,2})\s+)?([A-Za-z]{3})[a-z]*\.?,?\s+(\d{4})$/.exec(
|
|
429
|
+
m = /^(?:(\d{1,2})\s+)?([A-Za-z]{3})[a-z]*\.?,?\s+(\d{4})$/.exec(text);
|
|
1032
430
|
if (m) {
|
|
1033
431
|
const month = MONTHS.findIndex((x) => x.toLowerCase() === m[2].toLowerCase());
|
|
1034
432
|
if (month >= 0) return valid(Number(m[3]), month, m[1] ? Number(m[1]) : 1);
|
|
@@ -1243,7 +641,7 @@ function linearMap(value, domain, range) {
|
|
|
1243
641
|
}
|
|
1244
642
|
|
|
1245
643
|
// src/chart/layout-shared.ts
|
|
1246
|
-
var estimateChartTextWidth = (
|
|
644
|
+
var estimateChartTextWidth = (text, size, weight = 400) => text.length * size * (weight >= 600 ? 0.6 : 0.56);
|
|
1247
645
|
function sameChartSelection(a, b) {
|
|
1248
646
|
if (!a || !b || a.kind !== b.kind) return false;
|
|
1249
647
|
switch (a.kind) {
|
|
@@ -1326,9 +724,9 @@ function arcPath(cx, cy, outer, inner, start, end) {
|
|
|
1326
724
|
else d += `L${r2(cx)},${r2(cy)}Z`;
|
|
1327
725
|
return d;
|
|
1328
726
|
}
|
|
1329
|
-
function truncate(
|
|
1330
|
-
if (measure(
|
|
1331
|
-
let out =
|
|
727
|
+
function truncate(text, maxWidth, size, measure) {
|
|
728
|
+
if (measure(text, size) <= maxWidth) return text;
|
|
729
|
+
let out = text;
|
|
1332
730
|
while (out.length > 1 && measure(`${out}\u2026`, size) > maxWidth) out = out.slice(0, -1);
|
|
1333
731
|
return `${out}\u2026`;
|
|
1334
732
|
}
|
|
@@ -1841,11 +1239,11 @@ function categoryFrame(ctx, area, input) {
|
|
|
1841
1239
|
const band = catLen / n;
|
|
1842
1240
|
const catCenter = (i) => catStart + band * (i + 0.5);
|
|
1843
1241
|
const map = (p) => horizontal ? [p.v, p.c] : [p.c, p.v];
|
|
1844
|
-
const gridAt = (info,
|
|
1242
|
+
const gridAt = (info, key) => info.scale.ticks.forEach((t, i) => {
|
|
1845
1243
|
const p = info.px(t);
|
|
1846
1244
|
const [x1, y1] = map({ c: catStart, v: p });
|
|
1847
1245
|
const [x2, y2] = map({ c: catStart + catLen, v: p });
|
|
1848
|
-
marks.push({ kind: "line", key: `grid-${
|
|
1246
|
+
marks.push({ kind: "line", key: `grid-${key}-${i}`, role: "grid", x1, y1, x2, y2, stroke: theme.grid, strokeWidth: 1 });
|
|
1849
1247
|
});
|
|
1850
1248
|
if (primary.axis.gridlines) gridAt(value, "y");
|
|
1851
1249
|
if (value2 && secondary.axis.gridlines) gridAt(value2, "y2");
|
|
@@ -1863,12 +1261,12 @@ function categoryFrame(ctx, area, input) {
|
|
|
1863
1261
|
info.scale.ticks.forEach((t, i) => {
|
|
1864
1262
|
const p = info.px(t);
|
|
1865
1263
|
const label = info.tickText(t);
|
|
1866
|
-
const
|
|
1264
|
+
const key = `tick-${v.key}-${i}`;
|
|
1867
1265
|
if (horizontal) {
|
|
1868
|
-
if (far) marks.push({ kind: "text", key
|
|
1869
|
-
else marks.push({ kind: "text", key
|
|
1870
|
-
} else if (far) marks.push({ kind: "text", key
|
|
1871
|
-
else marks.push({ kind: "text", key
|
|
1266
|
+
if (far) marks.push({ kind: "text", key, role: "tick-label", x: p, y: plot.y - 6, text: label, anchor: "middle", baseline: "bottom", size: fs, fill: theme.mutedText, target });
|
|
1267
|
+
else marks.push({ kind: "text", key, role: "tick-label", x: p, y: plot.y + plot.height + 6, text: label, anchor: "middle", baseline: "top", size: fs, fill: theme.mutedText, target });
|
|
1268
|
+
} else if (far) marks.push({ kind: "text", key, role: "tick-label", x: plot.x + plot.width + 8, y: p, text: label, anchor: "start", baseline: "middle", size: fs, fill: theme.mutedText, target });
|
|
1269
|
+
else marks.push({ kind: "text", key, role: "tick-label", x: plot.x - 8, y: p, text: label, anchor: "end", baseline: "middle", size: fs, fill: theme.mutedText, target });
|
|
1872
1270
|
});
|
|
1873
1271
|
};
|
|
1874
1272
|
ticksFor(value, primary, false);
|
|
@@ -1893,18 +1291,18 @@ function categoryFrame(ctx, area, input) {
|
|
|
1893
1291
|
const extra = target ? { target, selected } : {};
|
|
1894
1292
|
if (horizontal) {
|
|
1895
1293
|
const maxW = left - (xAxis.title ? titleBand : 0) - 8;
|
|
1896
|
-
const
|
|
1897
|
-
if (
|
|
1898
|
-
marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: plot.x - 8, y: catCenter(i), text
|
|
1294
|
+
const text = truncate(text0, maxW, fs, measure);
|
|
1295
|
+
if (text !== text0) truncated++;
|
|
1296
|
+
marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: plot.x - 8, y: catCenter(i), text, anchor: "end", baseline: "middle", size: fs, fill, ...extra });
|
|
1899
1297
|
} else if (rotate) {
|
|
1900
1298
|
const room = (catCenter(i) - 2) / Math.cos(Math.PI / 4.5);
|
|
1901
|
-
const
|
|
1902
|
-
if (
|
|
1903
|
-
marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: catCenter(i), y: plot.y + plot.height + 8, text
|
|
1299
|
+
const text = truncate(text0, Math.max(fs, Math.min(rotatedMax, room)), fs, measure);
|
|
1300
|
+
if (text !== text0) truncated++;
|
|
1301
|
+
marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: catCenter(i), y: plot.y + plot.height + 8, text, anchor: "end", baseline: "middle", size: fs, fill, rotate, ...extra });
|
|
1904
1302
|
} else {
|
|
1905
|
-
const
|
|
1906
|
-
if (
|
|
1907
|
-
marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: catCenter(i), y: plot.y + plot.height + 6, text
|
|
1303
|
+
const text = dateLabels ? text0 : truncate(text0, Math.max(band - 4, fs * 2), fs, measure);
|
|
1304
|
+
if (text !== text0) truncated++;
|
|
1305
|
+
marks.push({ kind: "text", key: `tick-x-${i}`, role: "tick-label", x: catCenter(i), y: plot.y + plot.height + 6, text, anchor: dateLabels ? "start" : "middle", baseline: "top", size: fs, fill, ...extra });
|
|
1908
1306
|
}
|
|
1909
1307
|
});
|
|
1910
1308
|
if (dateLabels) {
|
|
@@ -1985,10 +1383,10 @@ function drawTrendLabels(ctx, plot, labels) {
|
|
|
1985
1383
|
(l, i) => ctx.marks.push({ kind: "text", key: `trendline-label-${i}`, role: "trendline-label", x: plot.x + plot.width - 4, y: plot.y + 4 + i * (ctx.fs + 3), text: l.text, anchor: "end", baseline: "top", size: ctx.fs - 1, fill: l.color, target: l.target })
|
|
1986
1384
|
);
|
|
1987
1385
|
}
|
|
1988
|
-
function barLabel(ctx,
|
|
1386
|
+
function barLabel(ctx, text, rect, horizontal, positive, stacked, fill, plot) {
|
|
1989
1387
|
const { measure, fs, spec, theme } = ctx;
|
|
1990
1388
|
const size = fs - 1;
|
|
1991
|
-
const tw = measure(
|
|
1389
|
+
const tw = measure(text, size);
|
|
1992
1390
|
const th = size + 2;
|
|
1993
1391
|
const along = horizontal ? rect.width : rect.height;
|
|
1994
1392
|
const across = horizontal ? rect.height : rect.width;
|
|
@@ -2134,13 +1532,13 @@ function layoutCartesian(ctx, area, visible) {
|
|
|
2134
1532
|
if (selected) ctx.outline(`bar-${s.id}-${i}-selection`, { x: rect.x - 2, y: rect.y - 2, width: rect.width + 4, height: rect.height + 4 });
|
|
2135
1533
|
if (labelsOn) {
|
|
2136
1534
|
const v = percent ? seg[1] - seg[0] : raw;
|
|
2137
|
-
const
|
|
2138
|
-
const at = barLabel(ctx,
|
|
1535
|
+
const text = dataLabel(v);
|
|
1536
|
+
const at = barLabel(ctx, text, rect, horizontal, seg[1] >= seg[0], stacked, fill, plot);
|
|
2139
1537
|
if (!at) {
|
|
2140
1538
|
hiddenLabels++;
|
|
2141
1539
|
return;
|
|
2142
1540
|
}
|
|
2143
|
-
labelMarks.push({ kind: "text", key: `label-${s.id}-${i}`, role: "data-label", ...at, text
|
|
1541
|
+
labelMarks.push({ kind: "text", key: `label-${s.id}-${i}`, role: "data-label", ...at, text, size: fs - 1, target });
|
|
2144
1542
|
}
|
|
2145
1543
|
});
|
|
2146
1544
|
} else {
|
|
@@ -2412,12 +1810,12 @@ function layoutPie(ctx, area, series) {
|
|
|
2412
1810
|
return;
|
|
2413
1811
|
}
|
|
2414
1812
|
const share = v / total;
|
|
2415
|
-
const
|
|
1813
|
+
const text = format.style || format.code ? formatChartNumber(v, format) : `${Math.round(share * 100)}%`;
|
|
2416
1814
|
const lr = outer + fs * 1.1;
|
|
2417
1815
|
const lx = ox + lr * Math.sin(mid);
|
|
2418
1816
|
const ly = oy - lr * Math.cos(mid);
|
|
2419
1817
|
const anchor = Math.abs(Math.sin(mid)) < 0.2 ? "middle" : Math.sin(mid) > 0 ? "start" : "end";
|
|
2420
|
-
marks.push({ kind: "text", key: `label-slice-${i}`, role: "data-label", x: lx, y: ly, text
|
|
1818
|
+
marks.push({ kind: "text", key: `label-slice-${i}`, role: "data-label", x: lx, y: ly, text, anchor, baseline: "middle", size: fs, fill: theme.text, target });
|
|
2421
1819
|
});
|
|
2422
1820
|
if (hidden) ctx.warn("data-labels-hidden", `${hidden} slice label${hidden === 1 ? " is" : "s are"} hidden: the slices are too thin.`);
|
|
2423
1821
|
if (spec.type === "donut" && inner > fs * 2) {
|
|
@@ -2539,10 +1937,10 @@ function layoutWaterfall(ctx, area, series) {
|
|
|
2539
1937
|
}
|
|
2540
1938
|
prevEnd = s.to;
|
|
2541
1939
|
if (labelsOn) {
|
|
2542
|
-
const
|
|
1940
|
+
const text = formatChartNumber(s.total ? s.to : s.to - s.from, format);
|
|
2543
1941
|
const up = s.to >= s.from;
|
|
2544
1942
|
const [lx, ly] = map({ c: catCenter(k), v: horizontal ? Math.max(a, b) + 4 : up ? Math.min(a, b) - 4 : Math.max(a, b) + 4 });
|
|
2545
|
-
marks.push({ kind: "text", key: `label-${series.id}-${s.index ?? "total"}`, role: "data-label", x: lx, y: ly, text
|
|
1943
|
+
marks.push({ kind: "text", key: `label-${series.id}-${s.index ?? "total"}`, role: "data-label", x: lx, y: ly, text, anchor: horizontal ? "start" : "middle", baseline: horizontal ? "middle" : up ? "bottom" : "top", size: fs - 1, fill: theme.text, target });
|
|
2546
1944
|
}
|
|
2547
1945
|
});
|
|
2548
1946
|
return { plot: frame.plot, valueScale: toValueScale(value, horizontal ? "x" : "y") };
|
|
@@ -2612,7 +2010,7 @@ function layoutBox(ctx, area, visible) {
|
|
|
2612
2010
|
const selected = ctx.seriesSelected(s.id);
|
|
2613
2011
|
const c = catCenter(i);
|
|
2614
2012
|
const y = value.px;
|
|
2615
|
-
const line = (
|
|
2013
|
+
const line = (key, x1, y1, x2, y2, width = 1.25, stroke = color) => marks.push({ kind: "line", key, role: "whisker", x1, y1, x2, y2, stroke, strokeWidth: width, target });
|
|
2616
2014
|
line(`whisker-hi-${s.id}`, c, y(st.upperWhisker), c, y(st.q3));
|
|
2617
2015
|
line(`whisker-lo-${s.id}`, c, y(st.q1), c, y(st.lowerWhisker));
|
|
2618
2016
|
line(`cap-hi-${s.id}`, c - bw / 4, y(st.upperWhisker), c + bw / 4, y(st.upperWhisker));
|
|
@@ -2654,9 +2052,9 @@ function layoutFunnel(ctx, area, series) {
|
|
|
2654
2052
|
marks.push({ kind: "rect", key: `bar-${series.id}-${i}`, role: "bar", x: cx - w / 2, y: cy - h / 2, width: w, height: h, fill, radius: 2, target, selected });
|
|
2655
2053
|
if (selected) ctx.outline(`bar-${series.id}-${i}-selection`, { x: cx - w / 2 - 2, y: cy - h / 2 - 2, width: w + 4, height: h + 4 });
|
|
2656
2054
|
if (labelsOn) {
|
|
2657
|
-
const
|
|
2658
|
-
const fits = measure(
|
|
2659
|
-
marks.push({ kind: "text", key: `label-${series.id}-${i}`, role: "data-label", x: fits ? cx : cx + w / 2 + 4, y: cy, text
|
|
2055
|
+
const text = formatChartNumber(v, format);
|
|
2056
|
+
const fits = measure(text, fs) + 8 <= w && fs + 2 <= h;
|
|
2057
|
+
marks.push({ kind: "text", key: `label-${series.id}-${i}`, role: "data-label", x: fits ? cx : cx + w / 2 + 4, y: cy, text, anchor: fits ? "middle" : "start", baseline: "middle", size: fs, fill: fits ? contrastingTextColor(fill) : theme.text, target });
|
|
2660
2058
|
}
|
|
2661
2059
|
});
|
|
2662
2060
|
return { plot };
|
|
@@ -2753,7 +2151,7 @@ function layoutChart(spec, options) {
|
|
|
2753
2151
|
selection,
|
|
2754
2152
|
isSelected: (target) => sameChartSelection(selection, target),
|
|
2755
2153
|
seriesSelected: (id) => selection?.kind === "series" && selection.seriesId === id,
|
|
2756
|
-
outline: (
|
|
2154
|
+
outline: (key, box) => marks.push({ kind: "rect", key, role: "selection", ...box, fill: "none", stroke: theme.selection, strokeWidth: 1, radius: 3 }),
|
|
2757
2155
|
warn: (code, message) => {
|
|
2758
2156
|
if (!warnings.some((w) => w.code === code && w.message === message)) warnings.push({ code, message });
|
|
2759
2157
|
}
|
|
@@ -2924,13 +2322,13 @@ function addReadout(ctx, readout, plot) {
|
|
|
2924
2322
|
} else return;
|
|
2925
2323
|
const axis = series.axis === "secondary" ? spec.yAxis2 : spec.yAxis;
|
|
2926
2324
|
const x = series.x?.[readout.index];
|
|
2927
|
-
const
|
|
2928
|
-
const w = measure(
|
|
2325
|
+
const text = x !== null && x !== void 0 && chartFamily(spec) === "xy" ? `${formatChartNumber(x, spec.xAxis.format)}, ${formatChartNumber(value, axis.format)}` : `${spec.categories[readout.index] ?? ""}: ${formatChartNumber(value, series.format ?? axis.format)}`;
|
|
2326
|
+
const w = measure(text, fs, 600) + 10;
|
|
2929
2327
|
const h = fs + 8;
|
|
2930
2328
|
const bx = Math.max(plot.x, Math.min(plot.x + plot.width - w, ax - w / 2));
|
|
2931
2329
|
const by = Math.max(plot.y, Math.min(plot.y + plot.height - h, ay - h - 6));
|
|
2932
2330
|
marks.push({ kind: "rect", key: "drag-readout-box", role: "drag-readout", x: bx, y: by, width: w, height: h, fill: theme.text, radius: 3, opacity: 0.9 });
|
|
2933
|
-
marks.push({ kind: "text", key: "drag-readout", role: "drag-readout", x: bx + w / 2, y: by + h / 2, text
|
|
2331
|
+
marks.push({ kind: "text", key: "drag-readout", role: "drag-readout", x: bx + w / 2, y: by + h / 2, text, anchor: "middle", baseline: "middle", size: fs, weight: 600, fill: theme.surface });
|
|
2934
2332
|
}
|
|
2935
2333
|
var finite = (...n) => n.every((v) => v === void 0 || Number.isFinite(v));
|
|
2936
2334
|
function finalizeMarks(marks, width, height) {
|
|
@@ -3051,13 +2449,13 @@ function chartTextBaseline(baseline) {
|
|
|
3051
2449
|
function chartTextTransform(mark) {
|
|
3052
2450
|
return mark.rotate ? `rotate(${mark.rotate} ${mark.x} ${mark.y})` : void 0;
|
|
3053
2451
|
}
|
|
3054
|
-
var escape = (
|
|
2452
|
+
var escape = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3055
2453
|
function markParts(mark, fontFamily) {
|
|
3056
2454
|
const common = { opacity: mark.opacity, "data-role": mark.role };
|
|
3057
|
-
const parts = (tag, values,
|
|
2455
|
+
const parts = (tag, values, text) => ({
|
|
3058
2456
|
tag,
|
|
3059
2457
|
attrs: Object.fromEntries(Object.entries(values).filter(([, v]) => v !== void 0 && v !== "").map(([k, v]) => [k, String(v)])),
|
|
3060
|
-
text
|
|
2458
|
+
text
|
|
3061
2459
|
});
|
|
3062
2460
|
switch (mark.kind) {
|
|
3063
2461
|
case "rect":
|
|
@@ -3088,9 +2486,9 @@ function markParts(mark, fontFamily) {
|
|
|
3088
2486
|
}
|
|
3089
2487
|
}
|
|
3090
2488
|
function markToSvg(mark, fontFamily) {
|
|
3091
|
-
const { tag, attrs, text
|
|
2489
|
+
const { tag, attrs, text } = markParts(mark, fontFamily);
|
|
3092
2490
|
const attrText = Object.entries(attrs).map(([k, v]) => ` ${k}="${escape(v)}"`).join("");
|
|
3093
|
-
return
|
|
2491
|
+
return text === void 0 ? `<${tag}${attrText}/>` : `<${tag}${attrText}>${escape(text)}</${tag}>`;
|
|
3094
2492
|
}
|
|
3095
2493
|
function chartSceneToSvg(scene, options = {}) {
|
|
3096
2494
|
const hide = options.hideSelection ?? true;
|
|
@@ -3116,8 +2514,8 @@ function chartSceneToDomSpec(scene, options = {}) {
|
|
|
3116
2514
|
if (options.description) children.push([tag("desc"), {}, options.description]);
|
|
3117
2515
|
for (const mark of scene.marks) {
|
|
3118
2516
|
if (hide && mark.role === "selection") continue;
|
|
3119
|
-
const { tag: name, attrs: attrs2, text
|
|
3120
|
-
children.push(
|
|
2517
|
+
const { tag: name, attrs: attrs2, text } = markParts(mark, font);
|
|
2518
|
+
children.push(text === void 0 ? [tag(name), attrs2] : [tag(name), attrs2, text]);
|
|
3121
2519
|
}
|
|
3122
2520
|
const attrs = {
|
|
3123
2521
|
width: String(scene.width),
|
|
@@ -3376,8 +2774,8 @@ var ChartController = class {
|
|
|
3376
2774
|
}
|
|
3377
2775
|
/** Changes the category (`x`), value (`y`) or secondary value (`y2`) axis. */
|
|
3378
2776
|
setAxis(axis, patch) {
|
|
3379
|
-
const
|
|
3380
|
-
return this.update({ [
|
|
2777
|
+
const key = axis === "x" ? "xAxis" : axis === "y" ? "yAxis" : "yAxis2";
|
|
2778
|
+
return this.update({ [key]: patch }, { label: `Change ${axis === "x" ? "category" : axis === "y" ? "value" : "secondary value"} axis` });
|
|
3381
2779
|
}
|
|
3382
2780
|
/**
|
|
3383
2781
|
* Applies a named quick layout and / or colour scheme (see `CHART_LAYOUT_PRESETS` and
|
|
@@ -3482,10 +2880,10 @@ var ChartController = class {
|
|
|
3482
2880
|
else delete next.hidden;
|
|
3483
2881
|
}
|
|
3484
2882
|
const resettable = ["axis", "x", "sizes", "pointColors", "marker", "curve", "dataLabels", "format"];
|
|
3485
|
-
for (const
|
|
3486
|
-
if (!(
|
|
3487
|
-
if (patch[
|
|
3488
|
-
else next[
|
|
2883
|
+
for (const key of resettable) {
|
|
2884
|
+
if (!(key in patch) || patch[key] === void 0) continue;
|
|
2885
|
+
if (patch[key] === null) delete next[key];
|
|
2886
|
+
else next[key] = patch[key];
|
|
3489
2887
|
}
|
|
3490
2888
|
if (next.axis === "primary") delete next.axis;
|
|
3491
2889
|
const series2 = this.spec.series.map((s) => s.id === id ? next : s);
|
|
@@ -3632,9 +3030,9 @@ var ChartController = class {
|
|
|
3632
3030
|
return { ok: true };
|
|
3633
3031
|
}
|
|
3634
3032
|
/** Imports CSV (or TSV, detected by tabs) text; see `importTable`. */
|
|
3635
|
-
importCsv(
|
|
3636
|
-
const delimiter = options.delimiter ?? (
|
|
3637
|
-
return this.importTable(parseCsv(
|
|
3033
|
+
importCsv(text, options = {}) {
|
|
3034
|
+
const delimiter = options.delimiter ?? (text.includes(" ") ? " " : ",");
|
|
3035
|
+
return this.importTable(parseCsv(text, { delimiter }), options);
|
|
3638
3036
|
}
|
|
3639
3037
|
// -------------------------------------------------------------------------
|
|
3640
3038
|
// Trendlines
|
|
@@ -3780,10 +3178,10 @@ var ChartController = class {
|
|
|
3780
3178
|
return this.setValue(seriesId, index, Number((current + delta).toPrecision(12)), { label: `Edit ${series.name} \xB7 ${this.spec.categories[index] ?? index}` });
|
|
3781
3179
|
}
|
|
3782
3180
|
/** Starts a continuous edit (a drag): every change until `endGesture()` is one undo step. */
|
|
3783
|
-
beginGesture(
|
|
3784
|
-
if (this.gestureKey ===
|
|
3181
|
+
beginGesture(key) {
|
|
3182
|
+
if (this.gestureKey === key) return;
|
|
3785
3183
|
this.history.endGesture();
|
|
3786
|
-
this.gestureKey =
|
|
3184
|
+
this.gestureKey = key;
|
|
3787
3185
|
this.gestureCount++;
|
|
3788
3186
|
this.gestureStart = { spec: this.spec, mark: this.history.mark() };
|
|
3789
3187
|
this.notify(false);
|
|
@@ -3968,54 +3366,6 @@ function sparklineToSvg(values, options) {
|
|
|
3968
3366
|
}
|
|
3969
3367
|
|
|
3970
3368
|
export {
|
|
3971
|
-
parseChartColor,
|
|
3972
|
-
chartRgbToHex,
|
|
3973
|
-
shadeChartColor,
|
|
3974
|
-
CHART_PALETTE_SHADES,
|
|
3975
|
-
cyclePaletteColor,
|
|
3976
|
-
chartLuminance,
|
|
3977
|
-
chartContrastRatio,
|
|
3978
|
-
chartTextOn,
|
|
3979
|
-
chartRgbToOklab,
|
|
3980
|
-
chartColorDistance,
|
|
3981
|
-
simulateChartVision,
|
|
3982
|
-
checkChartColors,
|
|
3983
|
-
CHART_TYPES,
|
|
3984
|
-
CHART_MARKERS,
|
|
3985
|
-
CHART_TRENDLINE_TYPES,
|
|
3986
|
-
CHART_LABEL_POSITIONS,
|
|
3987
|
-
CHART_AXIS_KINDS,
|
|
3988
|
-
CHART_SERIES_TYPES,
|
|
3989
|
-
CHART_STACKINGS,
|
|
3990
|
-
CHART_CURVES,
|
|
3991
|
-
CHART_ORIENTATIONS,
|
|
3992
|
-
CHART_LEGEND_POSITIONS,
|
|
3993
|
-
CHART_NUMBER_STYLES,
|
|
3994
|
-
CHART_TYPE_LABELS,
|
|
3995
|
-
DEFAULT_CHART_PALETTE,
|
|
3996
|
-
DEFAULT_CHART_THEME,
|
|
3997
|
-
CSS_VARIABLE_CHART_THEME,
|
|
3998
|
-
isCartesianChart,
|
|
3999
|
-
isXyChart,
|
|
4000
|
-
chartFamily,
|
|
4001
|
-
chartColorsByCategory,
|
|
4002
|
-
defaultChartAxis,
|
|
4003
|
-
createChartSpec,
|
|
4004
|
-
chartSeriesColor,
|
|
4005
|
-
chartSliceColor,
|
|
4006
|
-
chartPointColor,
|
|
4007
|
-
chartSeriesType,
|
|
4008
|
-
uniqueChartSeriesId,
|
|
4009
|
-
parseChartValue,
|
|
4010
|
-
normalizeChartAxis,
|
|
4011
|
-
normalizeChartValues,
|
|
4012
|
-
normalizeChartTrendline,
|
|
4013
|
-
normalizeChartSpec,
|
|
4014
|
-
cloneChartSpec,
|
|
4015
|
-
cloneChartSeries,
|
|
4016
|
-
chartDataFromTable,
|
|
4017
|
-
chartDataToTable,
|
|
4018
|
-
chartColorInputValue,
|
|
4019
3369
|
CHART_MERMAID_META_PREFIX,
|
|
4020
3370
|
chartMermaidFamily,
|
|
4021
3371
|
chartToMermaid,
|
|
@@ -4071,4 +3421,4 @@ export {
|
|
|
4071
3421
|
layoutSparkline,
|
|
4072
3422
|
sparklineToSvg
|
|
4073
3423
|
};
|
|
4074
|
-
//# sourceMappingURL=chunk-
|
|
3424
|
+
//# sourceMappingURL=chunk-WBOAXDKD.js.map
|