ooxml-core 0.8.0 → 0.9.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/README.md CHANGED
@@ -51,6 +51,13 @@ import { xml, opc } from 'ooxml-core'; // or by namespace (shared building block
51
51
 
52
52
  Legacy binary formats (`.doc`, `.xls`, `.ppt`) and the compound-file container live in the sibling package [`ole2`](https://github.com/ChristopherVR/ole2); this package never contains binary codecs, and `ole2` never contains modern OOXML.
53
53
 
54
+ The `chart` subpath provides shared chart data calculations (regression, quartiles,
55
+ blank values and stacked series). The `text` subpath provides script-category
56
+ segmentation primitives. These areas are DOM-free and do not import a product model.
57
+ SVG curve flattening is available from `geometry`. See the
58
+ [PowerPoint reuse audit](docs/pptx-shared-logic-audit.md) for extraction evidence
59
+ and remaining candidates.
60
+
54
61
  ## Install
55
62
 
56
63
  ```bash
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Blank-value display resolution for `c:dispBlanksAs`.
3
+ *
4
+ * A chart series carries an optional `blanks` mask (from core) marking category
5
+ * slots whose numeric cache point was absent. This module turns that mask into
6
+ * per-point render instructions per the chart's `c:dispBlanksAs` mode:
7
+ *
8
+ * - `gap` - the blank point is omitted and the line breaks around it.
9
+ * - `zero` - the blank point plots as `0` (its placeholder value).
10
+ * - `span` - the blank is linearly interpolated from its nearest real neighbours.
11
+ *
12
+ * When the mode is unset the output preserves existing behaviour exactly (all
13
+ * points visible, placeholder `0` values), so charts without an explicit
14
+ * `c:dispBlanksAs` render unchanged.
15
+ *
16
+ * @module chart-blank-display
17
+ */
18
+ /** Values of `c:dispBlanksAs`. */
19
+ export type DispBlanksAs = 'gap' | 'zero' | 'span';
20
+ /** Per-point render instructions after applying a blank mode. */
21
+ export interface BlankDisplay {
22
+ /** Effective values (interpolated for `span`; `0` placeholder otherwise). */
23
+ values: number[];
24
+ /** Whether each point is drawn. `false` only for blanks under `gap`. */
25
+ visible: boolean[];
26
+ }
27
+ /**
28
+ * Resolve display values + visibility for a series given its blank mask and mode.
29
+ *
30
+ * @param values - Effective values aligned to display order (blanks carry `0`).
31
+ * @param blanks - Blank mask aligned index-for-index with `values`.
32
+ * @param mode - `c:dispBlanksAs` mode; `undefined` preserves existing behaviour.
33
+ */
34
+ export declare function resolveBlankDisplay(values: ReadonlyArray<number>, blanks: ReadonlyArray<boolean> | undefined, mode: DispBlanksAs | undefined): BlankDisplay;
35
+ /** Contiguous runs of visible indices, used to break a line at gaps. */
36
+ export declare function visibleRuns(visible: ReadonlyArray<boolean>): number[][];
37
+ //# sourceMappingURL=blank-display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blank-display.d.ts","sourceRoot":"","sources":["../../src/chart/blank-display.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC5B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,OAAO,EAAE,CAAC;CACnB;AA+BD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAC7B,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,EAC1C,IAAI,EAAE,YAAY,GAAG,SAAS,GAC5B,YAAY,CAad;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE,EAAE,CAevE"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Row indexes sharing each unique category label, in first-appearance order.
3
+ *
4
+ * A `cx:boxWhisker` chart's raw data repeats its category label once per
5
+ * underlying observation (COM-verified against `charts-com.pptx` slide 32 /
6
+ * `chartEx7.xml`: `Category 1` appears on 9 consecutive rows, `Category 2` on
7
+ * 7, `Category 3` on 6), because every row IS one observation, not one
8
+ * category. A box's quartiles must be computed from the observations that
9
+ * share its category label, not from one value per row.
10
+ */
11
+ export interface CategoryRowGroups {
12
+ /** Unique category labels, in first-appearance (authoring) order. */
13
+ uniqueCategories: string[];
14
+ /** Every raw row index whose category label equals the map key. */
15
+ rowIndexesByCategory: ReadonlyMap<string, number[]>;
16
+ }
17
+ /** Group raw (possibly repeated) category labels by unique text. */
18
+ export declare function groupRowsByCategory(rawCategories: ReadonlyArray<string>): CategoryRowGroups;
19
+ export interface BoxStats {
20
+ min: number;
21
+ q1: number;
22
+ median: number;
23
+ q3: number;
24
+ max: number;
25
+ }
26
+ /** Compute quartiles, preserving the legacy floor-index result when method is absent. */
27
+ export declare function computeBoxStats(values: ReadonlyArray<number>, method?: 'inclusive' | 'exclusive'): BoxStats | undefined;
28
+ //# sourceMappingURL=box-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"box-stats.d.ts","sourceRoot":"","sources":["../../src/chart/box-stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IACjC,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mEAAmE;IACnE,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpD;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAc3F;AAED,MAAM,WAAW,QAAQ;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACZ;AAeD,yFAAyF;AACzF,wBAAgB,eAAe,CAC9B,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAC7B,MAAM,CAAC,EAAE,WAAW,GAAG,WAAW,GAChC,QAAQ,GAAG,SAAS,CAqBtB"}
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+
3
+ // src/chart/regression.ts
4
+ function computeLinearRegression(xVals, yVals) {
5
+ const n = xVals.length;
6
+ if (n < 2) {
7
+ return { slope: 0, intercept: 0, rSquared: 0 };
8
+ }
9
+ let sumX = 0;
10
+ let sumY = 0;
11
+ let sumXY = 0;
12
+ let sumXX = 0;
13
+ for (let i = 0; i < n; i++) {
14
+ sumX += xVals[i];
15
+ sumY += yVals[i];
16
+ sumXY += xVals[i] * yVals[i];
17
+ sumXX += xVals[i] * xVals[i];
18
+ }
19
+ const denom = n * sumXX - sumX * sumX;
20
+ if (Math.abs(denom) < 1e-12) {
21
+ return { slope: 0, intercept: sumY / n, rSquared: 0 };
22
+ }
23
+ const slope = (n * sumXY - sumX * sumY) / denom;
24
+ const intercept = (sumY - slope * sumX) / n;
25
+ const ssRes = yVals.reduce((s, y, i) => s + (y - (slope * xVals[i] + intercept)) ** 2, 0);
26
+ const meanY = sumY / n;
27
+ const ssTot = yVals.reduce((s, y) => s + (y - meanY) ** 2, 0);
28
+ const rSquared = ssTot > 0 ? 1 - ssRes / ssTot : 0;
29
+ return { slope, intercept, rSquared };
30
+ }
31
+ function fitPolynomial(xVals, yVals, order) {
32
+ const n = xVals.length;
33
+ const m = order + 1;
34
+ const matrix = Array.from({ length: m }, () => Array(m + 1).fill(0));
35
+ for (let i = 0; i < m; i++) {
36
+ for (let j = 0; j < m; j++) {
37
+ let sum2 = 0;
38
+ for (let k = 0; k < n; k++) {
39
+ sum2 += xVals[k] ** (i + j);
40
+ }
41
+ matrix[i][j] = sum2;
42
+ }
43
+ let sum = 0;
44
+ for (let k = 0; k < n; k++) {
45
+ sum += yVals[k] * xVals[k] ** i;
46
+ }
47
+ matrix[i][m] = sum;
48
+ }
49
+ for (let i = 0; i < m; i++) {
50
+ let maxRow = i;
51
+ for (let k = i + 1; k < m; k++) {
52
+ if (Math.abs(matrix[k][i]) > Math.abs(matrix[maxRow][i])) {
53
+ maxRow = k;
54
+ }
55
+ }
56
+ [matrix[i], matrix[maxRow]] = [matrix[maxRow], matrix[i]];
57
+ const pivot = matrix[i][i];
58
+ if (Math.abs(pivot) < 1e-12) {
59
+ continue;
60
+ }
61
+ for (let j = i; j <= m; j++) {
62
+ matrix[i][j] = matrix[i][j] / pivot;
63
+ }
64
+ for (let k = 0; k < m; k++) {
65
+ if (k === i) {
66
+ continue;
67
+ }
68
+ const factor = matrix[k][i];
69
+ for (let j = i; j <= m; j++) {
70
+ matrix[k][j] = matrix[k][j] - factor * matrix[i][j];
71
+ }
72
+ }
73
+ }
74
+ return matrix.map((row) => row[m]);
75
+ }
76
+ function computeRSquared(xVals, yVals, evalFn) {
77
+ const n = xVals.length;
78
+ if (n === 0) {
79
+ return 0;
80
+ }
81
+ const meanY = yVals.reduce((s, y) => s + y, 0) / n;
82
+ let ssRes = 0;
83
+ let ssTot = 0;
84
+ for (let i = 0; i < n; i++) {
85
+ ssRes += (yVals[i] - evalFn(xVals[i])) ** 2;
86
+ ssTot += (yVals[i] - meanY) ** 2;
87
+ }
88
+ return ssTot > 0 ? 1 - ssRes / ssTot : 0;
89
+ }
90
+
91
+ // src/chart/box-stats.ts
92
+ function groupRowsByCategory(rawCategories) {
93
+ const uniqueCategories = [];
94
+ const rowIndexesByCategory = /* @__PURE__ */ new Map();
95
+ rawCategories.forEach((rawLabel, rowIndex) => {
96
+ const label = rawLabel ?? "";
97
+ let rows = rowIndexesByCategory.get(label);
98
+ if (!rows) {
99
+ rows = [];
100
+ rowIndexesByCategory.set(label, rows);
101
+ uniqueCategories.push(label);
102
+ }
103
+ rows.push(rowIndex);
104
+ });
105
+ return { uniqueCategories, rowIndexesByCategory };
106
+ }
107
+ function percentile(sorted, p, method) {
108
+ const rank = method === "inclusive" ? (sorted.length - 1) * p : (sorted.length + 1) * p - 1;
109
+ const clamped = Math.max(0, Math.min(rank, sorted.length - 1));
110
+ const lower = Math.floor(clamped);
111
+ const upper = Math.ceil(clamped);
112
+ const fraction = clamped - lower;
113
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * fraction;
114
+ }
115
+ function computeBoxStats(values, method) {
116
+ if (values.length < 2) {
117
+ return void 0;
118
+ }
119
+ const sorted = [...values].sort((a, b) => a - b);
120
+ if (!method) {
121
+ return {
122
+ min: sorted[0],
123
+ q1: sorted[Math.floor(sorted.length * 0.25)],
124
+ median: sorted[Math.floor(sorted.length * 0.5)],
125
+ q3: sorted[Math.floor(sorted.length * 0.75)],
126
+ max: sorted[sorted.length - 1]
127
+ };
128
+ }
129
+ return {
130
+ min: sorted[0],
131
+ q1: percentile(sorted, 0.25, method),
132
+ median: percentile(sorted, 0.5, method),
133
+ q3: percentile(sorted, 0.75, method),
134
+ max: sorted[sorted.length - 1]
135
+ };
136
+ }
137
+
138
+ // src/chart/blank-display.ts
139
+ function interpolate(values, blank) {
140
+ const out = [...values];
141
+ for (let i = 0; i < out.length; i++) {
142
+ if (!blank[i]) {
143
+ continue;
144
+ }
145
+ let prev = i - 1;
146
+ while (prev >= 0 && blank[prev]) {
147
+ prev--;
148
+ }
149
+ let next = i + 1;
150
+ while (next < out.length && blank[next]) {
151
+ next++;
152
+ }
153
+ const hasPrev = prev >= 0;
154
+ const hasNext = next < out.length;
155
+ if (hasPrev && hasNext) {
156
+ const t = (i - prev) / (next - prev);
157
+ out[i] = values[prev] + (values[next] - values[prev]) * t;
158
+ } else if (hasPrev) {
159
+ out[i] = values[prev];
160
+ } else if (hasNext) {
161
+ out[i] = values[next];
162
+ }
163
+ }
164
+ return out;
165
+ }
166
+ function resolveBlankDisplay(values, blanks, mode) {
167
+ const visible = values.map(() => true);
168
+ if (!blanks || !blanks.some(Boolean) || mode === void 0 || mode === "zero") {
169
+ return { values: [...values], visible };
170
+ }
171
+ if (mode === "span") {
172
+ return { values: interpolate(values, blanks), visible };
173
+ }
174
+ return {
175
+ values: [...values],
176
+ visible: values.map((_v, i) => !blanks[i])
177
+ };
178
+ }
179
+ function visibleRuns(visible) {
180
+ const runs = [];
181
+ let current = [];
182
+ for (let i = 0; i < visible.length; i++) {
183
+ if (visible[i]) {
184
+ current.push(i);
185
+ } else if (current.length > 0) {
186
+ runs.push(current);
187
+ current = [];
188
+ }
189
+ }
190
+ if (current.length > 0) {
191
+ runs.push(current);
192
+ }
193
+ return runs;
194
+ }
195
+
196
+ // src/chart/stacked-series.ts
197
+ function categoryAbsTotals(seriesValues, catCount) {
198
+ return Array.from(
199
+ { length: catCount },
200
+ (_, ci) => seriesValues.reduce((sum, values) => sum + Math.abs(values[ci] ?? 0), 0)
201
+ );
202
+ }
203
+ function computeStackedSeriesPlots(seriesValues, catCount, percent) {
204
+ const totals = percent ? categoryAbsTotals(seriesValues, catCount) : void 0, posRunning = new Array(catCount).fill(0), negRunning = new Array(catCount).fill(0);
205
+ return seriesValues.map((values) => {
206
+ const cumulative = [], base = [], own = [];
207
+ for (let ci = 0; ci < catCount; ci++) {
208
+ const raw = values[ci] ?? 0, total = totals?.[ci] ?? 0, val = percent ? total > 0 ? raw / total * 100 : 0 : raw, isNeg = val < 0, from = isNeg ? negRunning[ci] : posRunning[ci], to = from + val;
209
+ base.push(from);
210
+ cumulative.push(to);
211
+ own.push(val);
212
+ if (isNeg) {
213
+ negRunning[ci] = to;
214
+ } else {
215
+ posRunning[ci] = to;
216
+ }
217
+ }
218
+ return { cumulative, base, own };
219
+ });
220
+ }
221
+
222
+ exports.computeBoxStats = computeBoxStats;
223
+ exports.computeLinearRegression = computeLinearRegression;
224
+ exports.computeRSquared = computeRSquared;
225
+ exports.computeStackedSeriesPlots = computeStackedSeriesPlots;
226
+ exports.fitPolynomial = fitPolynomial;
227
+ exports.groupRowsByCategory = groupRowsByCategory;
228
+ exports.resolveBlankDisplay = resolveBlankDisplay;
229
+ exports.visibleRuns = visibleRuns;
@@ -0,0 +1,6 @@
1
+ /** Format-neutral, DOM-free chart data algorithms shared by Office formats. */
2
+ export * from './regression.js';
3
+ export * from './box-stats.js';
4
+ export * from './blank-display.js';
5
+ export * from './stacked-series.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chart/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,220 @@
1
+ // src/chart/regression.ts
2
+ function computeLinearRegression(xVals, yVals) {
3
+ const n = xVals.length;
4
+ if (n < 2) {
5
+ return { slope: 0, intercept: 0, rSquared: 0 };
6
+ }
7
+ let sumX = 0;
8
+ let sumY = 0;
9
+ let sumXY = 0;
10
+ let sumXX = 0;
11
+ for (let i = 0; i < n; i++) {
12
+ sumX += xVals[i];
13
+ sumY += yVals[i];
14
+ sumXY += xVals[i] * yVals[i];
15
+ sumXX += xVals[i] * xVals[i];
16
+ }
17
+ const denom = n * sumXX - sumX * sumX;
18
+ if (Math.abs(denom) < 1e-12) {
19
+ return { slope: 0, intercept: sumY / n, rSquared: 0 };
20
+ }
21
+ const slope = (n * sumXY - sumX * sumY) / denom;
22
+ const intercept = (sumY - slope * sumX) / n;
23
+ const ssRes = yVals.reduce((s, y, i) => s + (y - (slope * xVals[i] + intercept)) ** 2, 0);
24
+ const meanY = sumY / n;
25
+ const ssTot = yVals.reduce((s, y) => s + (y - meanY) ** 2, 0);
26
+ const rSquared = ssTot > 0 ? 1 - ssRes / ssTot : 0;
27
+ return { slope, intercept, rSquared };
28
+ }
29
+ function fitPolynomial(xVals, yVals, order) {
30
+ const n = xVals.length;
31
+ const m = order + 1;
32
+ const matrix = Array.from({ length: m }, () => Array(m + 1).fill(0));
33
+ for (let i = 0; i < m; i++) {
34
+ for (let j = 0; j < m; j++) {
35
+ let sum2 = 0;
36
+ for (let k = 0; k < n; k++) {
37
+ sum2 += xVals[k] ** (i + j);
38
+ }
39
+ matrix[i][j] = sum2;
40
+ }
41
+ let sum = 0;
42
+ for (let k = 0; k < n; k++) {
43
+ sum += yVals[k] * xVals[k] ** i;
44
+ }
45
+ matrix[i][m] = sum;
46
+ }
47
+ for (let i = 0; i < m; i++) {
48
+ let maxRow = i;
49
+ for (let k = i + 1; k < m; k++) {
50
+ if (Math.abs(matrix[k][i]) > Math.abs(matrix[maxRow][i])) {
51
+ maxRow = k;
52
+ }
53
+ }
54
+ [matrix[i], matrix[maxRow]] = [matrix[maxRow], matrix[i]];
55
+ const pivot = matrix[i][i];
56
+ if (Math.abs(pivot) < 1e-12) {
57
+ continue;
58
+ }
59
+ for (let j = i; j <= m; j++) {
60
+ matrix[i][j] = matrix[i][j] / pivot;
61
+ }
62
+ for (let k = 0; k < m; k++) {
63
+ if (k === i) {
64
+ continue;
65
+ }
66
+ const factor = matrix[k][i];
67
+ for (let j = i; j <= m; j++) {
68
+ matrix[k][j] = matrix[k][j] - factor * matrix[i][j];
69
+ }
70
+ }
71
+ }
72
+ return matrix.map((row) => row[m]);
73
+ }
74
+ function computeRSquared(xVals, yVals, evalFn) {
75
+ const n = xVals.length;
76
+ if (n === 0) {
77
+ return 0;
78
+ }
79
+ const meanY = yVals.reduce((s, y) => s + y, 0) / n;
80
+ let ssRes = 0;
81
+ let ssTot = 0;
82
+ for (let i = 0; i < n; i++) {
83
+ ssRes += (yVals[i] - evalFn(xVals[i])) ** 2;
84
+ ssTot += (yVals[i] - meanY) ** 2;
85
+ }
86
+ return ssTot > 0 ? 1 - ssRes / ssTot : 0;
87
+ }
88
+
89
+ // src/chart/box-stats.ts
90
+ function groupRowsByCategory(rawCategories) {
91
+ const uniqueCategories = [];
92
+ const rowIndexesByCategory = /* @__PURE__ */ new Map();
93
+ rawCategories.forEach((rawLabel, rowIndex) => {
94
+ const label = rawLabel ?? "";
95
+ let rows = rowIndexesByCategory.get(label);
96
+ if (!rows) {
97
+ rows = [];
98
+ rowIndexesByCategory.set(label, rows);
99
+ uniqueCategories.push(label);
100
+ }
101
+ rows.push(rowIndex);
102
+ });
103
+ return { uniqueCategories, rowIndexesByCategory };
104
+ }
105
+ function percentile(sorted, p, method) {
106
+ const rank = method === "inclusive" ? (sorted.length - 1) * p : (sorted.length + 1) * p - 1;
107
+ const clamped = Math.max(0, Math.min(rank, sorted.length - 1));
108
+ const lower = Math.floor(clamped);
109
+ const upper = Math.ceil(clamped);
110
+ const fraction = clamped - lower;
111
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * fraction;
112
+ }
113
+ function computeBoxStats(values, method) {
114
+ if (values.length < 2) {
115
+ return void 0;
116
+ }
117
+ const sorted = [...values].sort((a, b) => a - b);
118
+ if (!method) {
119
+ return {
120
+ min: sorted[0],
121
+ q1: sorted[Math.floor(sorted.length * 0.25)],
122
+ median: sorted[Math.floor(sorted.length * 0.5)],
123
+ q3: sorted[Math.floor(sorted.length * 0.75)],
124
+ max: sorted[sorted.length - 1]
125
+ };
126
+ }
127
+ return {
128
+ min: sorted[0],
129
+ q1: percentile(sorted, 0.25, method),
130
+ median: percentile(sorted, 0.5, method),
131
+ q3: percentile(sorted, 0.75, method),
132
+ max: sorted[sorted.length - 1]
133
+ };
134
+ }
135
+
136
+ // src/chart/blank-display.ts
137
+ function interpolate(values, blank) {
138
+ const out = [...values];
139
+ for (let i = 0; i < out.length; i++) {
140
+ if (!blank[i]) {
141
+ continue;
142
+ }
143
+ let prev = i - 1;
144
+ while (prev >= 0 && blank[prev]) {
145
+ prev--;
146
+ }
147
+ let next = i + 1;
148
+ while (next < out.length && blank[next]) {
149
+ next++;
150
+ }
151
+ const hasPrev = prev >= 0;
152
+ const hasNext = next < out.length;
153
+ if (hasPrev && hasNext) {
154
+ const t = (i - prev) / (next - prev);
155
+ out[i] = values[prev] + (values[next] - values[prev]) * t;
156
+ } else if (hasPrev) {
157
+ out[i] = values[prev];
158
+ } else if (hasNext) {
159
+ out[i] = values[next];
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+ function resolveBlankDisplay(values, blanks, mode) {
165
+ const visible = values.map(() => true);
166
+ if (!blanks || !blanks.some(Boolean) || mode === void 0 || mode === "zero") {
167
+ return { values: [...values], visible };
168
+ }
169
+ if (mode === "span") {
170
+ return { values: interpolate(values, blanks), visible };
171
+ }
172
+ return {
173
+ values: [...values],
174
+ visible: values.map((_v, i) => !blanks[i])
175
+ };
176
+ }
177
+ function visibleRuns(visible) {
178
+ const runs = [];
179
+ let current = [];
180
+ for (let i = 0; i < visible.length; i++) {
181
+ if (visible[i]) {
182
+ current.push(i);
183
+ } else if (current.length > 0) {
184
+ runs.push(current);
185
+ current = [];
186
+ }
187
+ }
188
+ if (current.length > 0) {
189
+ runs.push(current);
190
+ }
191
+ return runs;
192
+ }
193
+
194
+ // src/chart/stacked-series.ts
195
+ function categoryAbsTotals(seriesValues, catCount) {
196
+ return Array.from(
197
+ { length: catCount },
198
+ (_, ci) => seriesValues.reduce((sum, values) => sum + Math.abs(values[ci] ?? 0), 0)
199
+ );
200
+ }
201
+ function computeStackedSeriesPlots(seriesValues, catCount, percent) {
202
+ const totals = percent ? categoryAbsTotals(seriesValues, catCount) : void 0, posRunning = new Array(catCount).fill(0), negRunning = new Array(catCount).fill(0);
203
+ return seriesValues.map((values) => {
204
+ const cumulative = [], base = [], own = [];
205
+ for (let ci = 0; ci < catCount; ci++) {
206
+ const raw = values[ci] ?? 0, total = totals?.[ci] ?? 0, val = percent ? total > 0 ? raw / total * 100 : 0 : raw, isNeg = val < 0, from = isNeg ? negRunning[ci] : posRunning[ci], to = from + val;
207
+ base.push(from);
208
+ cumulative.push(to);
209
+ own.push(val);
210
+ if (isNeg) {
211
+ negRunning[ci] = to;
212
+ } else {
213
+ posRunning[ci] = to;
214
+ }
215
+ }
216
+ return { cumulative, base, own };
217
+ });
218
+ }
219
+
220
+ export { computeBoxStats, computeLinearRegression, computeRSquared, computeStackedSeriesPlots, fitPolynomial, groupRowsByCategory, resolveBlankDisplay, visibleRuns };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * chart-overlays-regression.ts: least-squares regression / polynomial-fit
3
+ * math for chart trendlines. Split out of chart-overlays.ts to keep that
4
+ * module under the repo's file-size guideline.
5
+ *
6
+ * Ported / adapted from:
7
+ * packages/react/src/viewer/utils/chart-trendlines.tsx (regression engine)
8
+ * packages/shared/src/render/chart-trendlines.ts (shared port)
9
+ *
10
+ * @module chart-overlays-regression
11
+ */
12
+ /** Result of an ordinary least-squares linear regression. */
13
+ export interface LinearFit {
14
+ slope: number;
15
+ intercept: number;
16
+ rSquared: number;
17
+ }
18
+ /**
19
+ * Ordinary least-squares linear regression of `yVals` on `xVals`.
20
+ * Returns all zeros for fewer than 2 points. A near-zero denominator
21
+ * (constant-x data) gives slope=0, intercept=mean(y), rSquared=0.
22
+ * Input arrays are finite paired observations of equal length.
23
+ *
24
+ * Mirrors `computeLinearRegression` in chart-trendlines.tsx (React) and
25
+ * chart-trendlines.ts (shared).
26
+ */
27
+ export declare function computeLinearRegression(xVals: number[], yVals: number[]): LinearFit;
28
+ /**
29
+ * Fit polynomial coefficients (ascending order: [a0, a1, ..., a_order]) via
30
+ * Gaussian elimination on the normal equations.
31
+ * Input arrays are finite paired observations; order is a nonnegative integer.
32
+ * Mirrors `fitPolynomial` in chart-trendlines.tsx (React).
33
+ */
34
+ export declare function fitPolynomial(xVals: number[], yVals: number[], order: number): number[];
35
+ /**
36
+ * Coefficient of determination (R-squared) of an arbitrary fit function
37
+ * against data. Mirrors `computeRSquared` in chart-trendlines.tsx (React).
38
+ */
39
+ export declare function computeRSquared(xVals: number[], yVals: number[], evalFn: (x: number) => number): number;
40
+ //# sourceMappingURL=regression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regression.d.ts","sourceRoot":"","sources":["../../src/chart/regression.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CA+BnF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+CvF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAC3B,MAAM,CAaR"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * chart-stacked-series.ts: running-sum plot geometry for stacked and
3
+ * percentStacked LINE / AREA charts.
4
+ *
5
+ * Bar/column stacking (`computeStackedBarRects` in chart-view-model.ts) draws
6
+ * one rect per segment stacked on a shared baseline. Line/area stacking is
7
+ * different: each series is plotted at its cumulative height, so the
8
+ * top-most series traces the category total (line) or the topmost band edge
9
+ * (area). This module owns that running-sum math, shared by `buildLines` and
10
+ * `buildAreas` in chart-cartesian-line-area.ts so the two kinds cannot drift.
11
+ *
12
+ * @module chart-stacked-series
13
+ */
14
+ /** How a line/area series' values combine with its siblings for plotting. */
15
+ export type LineAreaStacking = 'clustered' | 'stacked' | 'percentStacked';
16
+ /** Per-category stacked plot geometry for one series. */
17
+ export interface StackedSeriesPlot {
18
+ /** Cumulative value through and including this series: the plotted line/area top. */
19
+ cumulative: number[];
20
+ /** Cumulative value before this series: the area band's lower edge. */
21
+ base: number[];
22
+ /**
23
+ * This series' own contribution at each category: the raw value for plain
24
+ * `stacked`, or its percent share of the category's absolute total for
25
+ * `percentStacked`. Used for data-label/tooltip text, which reads the
26
+ * series' value rather than the running sum it is plotted at.
27
+ */
28
+ own: number[];
29
+ }
30
+ /**
31
+ * Build running-sum stacked/percentStacked plot values for a set of line or
32
+ * area series, one entry per series in `seriesValues` order (series 0 sits at
33
+ * the bottom of the stack, exactly like stacked bar segments). Positive and
34
+ * negative values accumulate on separate running sums, so a chart mixing
35
+ * signs stacks above and below zero independently instead of the values
36
+ * cancelling out.
37
+ */
38
+ export declare function computeStackedSeriesPlots(seriesValues: ReadonlyArray<ReadonlyArray<number>>, catCount: number, percent: boolean): StackedSeriesPlot[];
39
+ //# sourceMappingURL=stacked-series.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stacked-series.d.ts","sourceRoot":"","sources":["../../src/chart/stacked-series.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAE1E,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,uEAAuE;IACvE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;;;OAKG;IACH,GAAG,EAAE,MAAM,EAAE,CAAC;CACd;AAiBD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACxC,YAAY,EAAE,aAAa,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAClD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACd,iBAAiB,EAAE,CA2BrB"}