@dou-ui/pivot 0.0.0-stage → 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/assets/default-PXde2wzm.js.map +1 -0
  4. package/dist/core/format.js +116 -0
  5. package/dist/core/format.js.map +1 -0
  6. package/dist/core/labels.js +28 -0
  7. package/dist/core/labels.js.map +1 -0
  8. package/dist/core/layout.js +190 -0
  9. package/dist/core/layout.js.map +1 -0
  10. package/dist/core/source.js +46 -0
  11. package/dist/core/source.js.map +1 -0
  12. package/dist/core/table.js +154 -0
  13. package/dist/core/table.js.map +1 -0
  14. package/dist/engine/aggregate.js +51 -0
  15. package/dist/engine/aggregate.js.map +1 -0
  16. package/dist/engine/compute.js +735 -0
  17. package/dist/engine/compute.js.map +1 -0
  18. package/dist/engine/engine.js +46 -0
  19. package/dist/engine/engine.js.map +1 -0
  20. package/dist/engine/formula.js +225 -0
  21. package/dist/engine/formula.js.map +1 -0
  22. package/dist/engine/group.js +107 -0
  23. package/dist/engine/group.js.map +1 -0
  24. package/dist/engine/hash.js +108 -0
  25. package/dist/engine/hash.js.map +1 -0
  26. package/dist/engine/index.js +8 -0
  27. package/dist/engine/show.js +132 -0
  28. package/dist/engine/show.js.map +1 -0
  29. package/dist/engine/source.js +339 -0
  30. package/dist/engine/source.js.map +1 -0
  31. package/dist/engine/tree.js +226 -0
  32. package/dist/engine/tree.js.map +1 -0
  33. package/dist/fields/fields.js +6 -0
  34. package/dist/fields/fields.js.map +1 -0
  35. package/dist/fields/fields.vue_vue_type_script_setup_true_lang.js +881 -0
  36. package/dist/fields/fields.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/fields/filter-list.js +6 -0
  38. package/dist/fields/filter-list.js.map +1 -0
  39. package/dist/fields/filter-list.vue_vue_type_script_setup_true_lang.js +184 -0
  40. package/dist/fields/filter-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/fields/formula-dialog.js +6 -0
  42. package/dist/fields/formula-dialog.js.map +1 -0
  43. package/dist/fields/formula-dialog.vue_vue_type_script_setup_true_lang.js +165 -0
  44. package/dist/fields/formula-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/fields/model.js +200 -0
  46. package/dist/fields/model.js.map +1 -0
  47. package/dist/fields/top-dialog.js +6 -0
  48. package/dist/fields/top-dialog.js.map +1 -0
  49. package/dist/fields/top-dialog.vue_vue_type_script_setup_true_lang.js +174 -0
  50. package/dist/fields/top-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/index.js +16 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/locale.js +508 -0
  54. package/dist/locale.js.map +1 -0
  55. package/dist/pivot.js +6 -0
  56. package/dist/pivot.js.map +1 -0
  57. package/dist/pivot.vue_vue_type_script_setup_true_lang.js +1711 -0
  58. package/dist/pivot.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/style.css +1 -0
  60. package/dist/style.d.ts +1 -0
  61. package/dist/style.js +13 -0
  62. package/dist/types/core/format.d.ts +26 -0
  63. package/dist/types/core/labels.d.ts +14 -0
  64. package/dist/types/core/layout.d.ts +72 -0
  65. package/dist/types/core/source.d.ts +7 -0
  66. package/dist/types/core/table.d.ts +73 -0
  67. package/dist/types/engine/aggregate.d.ts +35 -0
  68. package/dist/types/engine/compute.d.ts +173 -0
  69. package/dist/types/engine/engine.d.ts +29 -0
  70. package/dist/types/engine/formula.d.ts +44 -0
  71. package/dist/types/engine/group.d.ts +34 -0
  72. package/dist/types/engine/hash.d.ts +36 -0
  73. package/dist/types/engine/index.d.ts +15 -0
  74. package/dist/types/engine/show.d.ts +27 -0
  75. package/dist/types/engine/source.d.ts +108 -0
  76. package/dist/types/engine/tree.d.ts +87 -0
  77. package/dist/types/engine/types.d.ts +148 -0
  78. package/dist/types/fields/fields.vue.d.ts +26 -0
  79. package/dist/types/fields/filter-list.vue.d.ts +20 -0
  80. package/dist/types/fields/formula-dialog.vue.d.ts +30 -0
  81. package/dist/types/fields/model.d.ts +52 -0
  82. package/dist/types/fields/top-dialog.vue.d.ts +25 -0
  83. package/dist/types/index.d.ts +458 -0
  84. package/dist/types/locale.d.ts +184 -0
  85. package/dist/types/pivot.vue.d.ts +29 -0
  86. package/dist/types/types.d.ts +199 -0
  87. package/dist/types/worker/client.d.ts +19 -0
  88. package/dist/types/worker/default.d.ts +1 -0
  89. package/dist/types/worker/index.d.ts +5 -0
  90. package/dist/types/worker/protocol.d.ts +60 -0
  91. package/dist/types/worker/serve.d.ts +22 -0
  92. package/dist/worker/client.js +191 -0
  93. package/dist/worker/client.js.map +1 -0
  94. package/dist/worker/default.js +20 -0
  95. package/dist/worker/default.js.map +1 -0
  96. package/dist/worker/index.js +3 -0
  97. package/dist/worker/serve.js +101 -0
  98. package/dist/worker/serve.js.map +1 -0
  99. package/package.json +64 -4
@@ -0,0 +1,735 @@
1
+ import { BUILTIN_AGGREGATORS, FAST_AGGREGATES, distinctAggregator } from "./aggregate.js";
2
+ import { PivotFormulaError, parsePivotFormula } from "./formula.js";
3
+ import { IntIndex, typed } from "./hash.js";
4
+ import { PivotShown, resolveShow } from "./show.js";
5
+ import { PivotTree, PivotTreeBuilder } from "./tree.js";
6
+ // src/engine/compute.ts
7
+ /** The ways of showing a value that write it as a percentage. */
8
+ var PERCENT_SHOWS = /* @__PURE__ */ new Set([
9
+ "percentOfTotal",
10
+ "percentOfRow",
11
+ "percentOfColumn",
12
+ "percentOfParent",
13
+ "percentDifference"
14
+ ]);
15
+ /** A `show` as an object. */
16
+ var showOf = (show) => show === void 0 ? void 0 : typeof show === "string" ? { as: show } : show;
17
+ /** A `mark` with its tone filled in. */
18
+ var markOf = (mark) => mark === void 0 ? void 0 : typeof mark === "string" ? {
19
+ type: mark,
20
+ tone: "neutral"
21
+ } : {
22
+ type: mark.type,
23
+ tone: mark.tone ?? "neutral"
24
+ };
25
+ /**
26
+ * Reads a calculated value's formula and finds its fields by name (or by
27
+ * label). Throws a `PivotFormulaError` when it is not well formed, names a
28
+ * field that is not there, or sums up one that is not a number.
29
+ */
30
+ function checkPivotFormula(source, text) {
31
+ const formula = parsePivotFormula(text);
32
+ const byLabel = /* @__PURE__ */ new Map();
33
+ for (const f of source.fields.values()) if (!byLabel.has(f.label)) byLabel.set(f.label, f.name);
34
+ for (const term of formula.terms) {
35
+ if (term.field === null) continue;
36
+ const name = source.fields.has(term.field) ? term.field : byLabel.get(term.field);
37
+ if (name === void 0) {
38
+ const at = text.indexOf(term.field);
39
+ throw new PivotFormulaError("unknownField", Math.max(0, at), term.field);
40
+ }
41
+ if (term.aggregate !== "count" && source.field(name).type !== "number") throw new PivotFormulaError("notNumber", Math.max(0, text.indexOf(term.field)), term.field);
42
+ term.field = name;
43
+ }
44
+ return formula;
45
+ }
46
+ /** The fields of the config resolved against a source, without reading any records. */
47
+ function resolveConfig(source, config, options = {}) {
48
+ const level = (item) => {
49
+ const spec = typeof item === "string" ? { field: item } : item;
50
+ return {
51
+ field: source.field(spec.field),
52
+ sort: spec.sort ?? "asc",
53
+ subtotal: spec.subtotal ?? true,
54
+ by: spec.by
55
+ };
56
+ };
57
+ const formulas = [];
58
+ const values = (config.values ?? []).map((item) => {
59
+ const spec = typeof item === "string" ? { field: item } : item;
60
+ const show = showOf(spec.show);
61
+ const mark = markOf(spec.mark);
62
+ const ownFormat = show && (PERCENT_SHOWS.has(show.as) || show.as === "rank");
63
+ if (spec.formula !== void 0) {
64
+ formulas.push(checkPivotFormula(source, spec.formula));
65
+ return {
66
+ field: null,
67
+ aggregate: "formula",
68
+ label: spec.label,
69
+ format: spec.format,
70
+ integer: false,
71
+ formula: spec.formula,
72
+ ...show ? { show } : {},
73
+ ...mark ? { mark } : {}
74
+ };
75
+ }
76
+ formulas.push(null);
77
+ const field = spec.field === void 0 ? null : source.field(spec.field);
78
+ let name = spec.aggregate ?? (field?.type === "number" ? "sum" : "count");
79
+ if (!field && name !== "count") name = "count";
80
+ const aggregator = FAST_AGGREGATES.has(name) ? void 0 : options.aggregators?.[name] ?? BUILTIN_AGGREGATORS[name];
81
+ if (!FAST_AGGREGATES.has(name) && !aggregator) throw new Error(`[@dou-ui/pivot] there is no aggregate named "${name}"`);
82
+ return {
83
+ field,
84
+ aggregate: name,
85
+ aggregator,
86
+ label: spec.label,
87
+ format: spec.format ?? (ownFormat ? void 0 : field?.format),
88
+ integer: name === "count" || !!aggregator?.integer,
89
+ ...show ? { show } : {},
90
+ ...mark ? { mark } : {}
91
+ };
92
+ });
93
+ if (values.length === 0) {
94
+ values.push({
95
+ field: null,
96
+ aggregate: "count",
97
+ integer: true,
98
+ implicit: true
99
+ });
100
+ formulas.push(null);
101
+ }
102
+ return {
103
+ rows: (config.rows ?? []).map(level),
104
+ columns: (config.columns ?? []).map(level),
105
+ values,
106
+ formulas
107
+ };
108
+ }
109
+ /**
110
+ * Groups the records by the config's row and column fields and sums up
111
+ * its values for every group, subtotal and total.
112
+ *
113
+ * Each record is placed once, in its innermost row group and innermost
114
+ * column group; the values are summed per such pair, and every subtotal
115
+ * is then merged from those pairs, never from the records again.
116
+ */
117
+ function computePivot(source, config, options = {}) {
118
+ const src = source;
119
+ if (Object.values(config.filters ?? {}).some((f) => f.top || f.between)) return computePivot(source, {
120
+ ...config,
121
+ filters: rankedFilters(src, config, options)
122
+ }, options);
123
+ const resolved = resolveConfig(src, config, options);
124
+ const rowLevels = resolved.rows.map((l) => ({
125
+ ...l,
126
+ column: src.dimension(l.field.name, l.by)
127
+ }));
128
+ const colLevels = resolved.columns.map((l) => ({
129
+ ...l,
130
+ column: src.dimension(l.field.name, l.by)
131
+ }));
132
+ const n = source.length;
133
+ const rowTree = new PivotTreeBuilder(stride(rowLevels));
134
+ const colTree = new PivotTreeBuilder(stride(colLevels));
135
+ const rowCodes = rowLevels.map((l) => l.column.codes);
136
+ const colCodes = colLevels.map((l) => l.column.codes);
137
+ const width = n * Math.max(1, colLevels.length) + 2;
138
+ const filters = Object.entries(config.filters ?? {}).flatMap(([name, filter]) => {
139
+ if (!filter.include && !filter.exclude) return [];
140
+ const by = filter.by ?? [...resolved.rows, ...resolved.columns].find((l) => l.field.name === name)?.by;
141
+ const column = src.dimension(name, by);
142
+ const allow = new Uint8Array(column.labels.length).fill(filter.include ? 0 : 1);
143
+ for (const value of filter.include ?? []) {
144
+ const code = column.find(value);
145
+ if (code >= 0) allow[code] = 1;
146
+ }
147
+ for (const value of filter.exclude ?? []) {
148
+ const code = column.find(value);
149
+ if (code >= 0) allow[code] = 0;
150
+ }
151
+ return [{
152
+ codes: column.codes,
153
+ allow
154
+ }];
155
+ });
156
+ const slotOf = new Int32Array(n);
157
+ const cells = new IntIndex(1024);
158
+ let matched = 0;
159
+ const rowCards = rowLevels.map((l) => l.column.labels.length);
160
+ const colCards = colLevels.map((l) => l.column.labels.length);
161
+ const rowSpace = rowCards.reduce((a, b) => a * b, 1);
162
+ const colSpace = colCards.reduce((a, b) => a * b, 1);
163
+ const skip = (i) => {
164
+ for (let f = 0; f < filters.length; f++) {
165
+ const filter = filters[f];
166
+ if (filter.allow[filter.codes[i]] === 0) return true;
167
+ }
168
+ return false;
169
+ };
170
+ if (rowSpace * colSpace <= Number.MAX_SAFE_INTEGER) {
171
+ const space = rowSpace * colSpace;
172
+ const dense = space <= DENSE_PAIRS ? new Int32Array(space).fill(-1) : null;
173
+ const index = dense ? null : new IntIndex(1024);
174
+ let pairRow = /* @__PURE__ */ new Float64Array(1024);
175
+ let pairCol = /* @__PURE__ */ new Float64Array(1024);
176
+ let pairs = 0;
177
+ for (let i = 0; i < n; i++) {
178
+ if (filters.length && skip(i)) {
179
+ slotOf[i] = -1;
180
+ continue;
181
+ }
182
+ let rk = 0;
183
+ for (let d = 0; d < rowCodes.length; d++) rk = rk * rowCards[d] + rowCodes[d][i];
184
+ let ck = 0;
185
+ for (let d = 0; d < colCodes.length; d++) ck = ck * colCards[d] + colCodes[d][i];
186
+ const key = rk * colSpace + ck;
187
+ let slot;
188
+ if (dense) {
189
+ slot = dense[key];
190
+ if (slot < 0) slot = dense[key] = pairs;
191
+ } else slot = index.add(key);
192
+ if (slot === pairs) {
193
+ if (pairs === pairRow.length) {
194
+ const grownRow = new Float64Array(pairs * 2);
195
+ grownRow.set(pairRow);
196
+ pairRow = grownRow;
197
+ const grownCol = new Float64Array(pairs * 2);
198
+ grownCol.set(pairCol);
199
+ pairCol = grownCol;
200
+ }
201
+ pairRow[pairs] = rk;
202
+ pairCol[pairs] = ck;
203
+ pairs++;
204
+ }
205
+ slotOf[i] = slot;
206
+ matched++;
207
+ }
208
+ const rowLeaf = /* @__PURE__ */ new Map();
209
+ const colLeaf = /* @__PURE__ */ new Map();
210
+ const rowDigits = new Int32Array(rowCards.length);
211
+ const colDigits = new Int32Array(colCards.length);
212
+ for (let p = 0; p < pairs; p++) {
213
+ const row = leaf(pairRow[p], rowLeaf, rowTree, rowCards, rowDigits);
214
+ const col = leaf(pairCol[p], colLeaf, colTree, colCards, colDigits);
215
+ cells.add(row * width + col);
216
+ }
217
+ } else for (let i = 0; i < n; i++) {
218
+ if (filters.length && skip(i)) {
219
+ slotOf[i] = -1;
220
+ continue;
221
+ }
222
+ let row = 0;
223
+ for (let d = 0; d < rowCodes.length; d++) row = rowTree.child(row, rowCodes[d][i]);
224
+ let col = 0;
225
+ for (let d = 0; d < colCodes.length; d++) col = colTree.child(col, colCodes[d][i]);
226
+ slotOf[i] = cells.add(row * width + col);
227
+ matched++;
228
+ }
229
+ const leaves = cells.size;
230
+ const rowDepth = rowLevels.length;
231
+ const colDepth = colLevels.length;
232
+ const fan = (rowDepth + 1) * (colDepth + 1) - 1;
233
+ const targets = new Int32Array(leaves * fan);
234
+ const rowUp = new Int32Array(rowDepth + 1);
235
+ const colUp = new Int32Array(colDepth + 1);
236
+ const leafRow = new Int32Array(leaves);
237
+ const leafCol = new Int32Array(leaves);
238
+ for (let s = 0; s < leaves; s++) {
239
+ const key = cells.keys[s];
240
+ const row = Math.floor(key / width);
241
+ const col = key - row * width;
242
+ leafRow[s] = row;
243
+ leafCol[s] = col;
244
+ for (let d = 0, at = row; d <= rowDepth; d++, at = rowTree.parent[at]) rowUp[d] = at;
245
+ for (let d = 0, at = col; d <= colDepth; d++, at = colTree.parent[at]) colUp[d] = at;
246
+ let k = s * fan;
247
+ for (let a = 0; a <= rowDepth; a++) for (let b = 0; b <= colDepth; b++) {
248
+ if (a === 0 && b === 0) continue;
249
+ targets[k++] = cells.add(rowUp[a] * width + colUp[b]);
250
+ }
251
+ }
252
+ const size = cells.size;
253
+ const sum = (value) => aggregate(src, value, slotOf, leaves, size, targets, fan);
254
+ const data = resolved.values.map((value, i) => {
255
+ const formula = resolved.formulas[i];
256
+ return formula ? calculate(src, formula, sum, size) : sum(value);
257
+ });
258
+ return new PivotResult({
259
+ rows: new PivotTree(rowTree, rowLevels),
260
+ columns: new PivotTree(colTree, colLevels),
261
+ values: resolved.values,
262
+ matched,
263
+ cells,
264
+ width,
265
+ data,
266
+ slotOf,
267
+ leafRow,
268
+ leafCol
269
+ });
270
+ }
271
+ /**
272
+ * The filters with `top` and `between` turned into lists of the groups they
273
+ * keep: each field's groups are summed up once on their own, over the
274
+ * records that the `include` and `exclude` lists keep.
275
+ */
276
+ function rankedFilters(source, config, options) {
277
+ const given = config.filters ?? {};
278
+ const plain = {};
279
+ for (const [name, f] of Object.entries(given)) if (f.include || f.exclude) plain[name] = {
280
+ include: f.include,
281
+ exclude: f.exclude,
282
+ by: f.by
283
+ };
284
+ const out = { ...plain };
285
+ const axes = [...config.rows ?? [], ...config.columns ?? []];
286
+ for (const [name, f] of Object.entries(given)) {
287
+ if (!f.top && !f.between) continue;
288
+ const onAxis = axes.find((d) => typeof d === "object" && d.field === name);
289
+ const by = f.by ?? (typeof onAxis === "object" ? onAxis.by : void 0);
290
+ const index = f.top?.valueIndex ?? f.between?.valueIndex ?? 0;
291
+ const value = config.values?.[index];
292
+ const plainValue = value === void 0 ? [] : [typeof value === "string" ? value : {
293
+ ...value,
294
+ show: void 0
295
+ }];
296
+ const totals = computePivot(source, {
297
+ rows: [{
298
+ field: name,
299
+ by,
300
+ subtotal: false
301
+ }],
302
+ values: plainValue,
303
+ filters: plain
304
+ }, options);
305
+ const tree = totals.rows;
306
+ let groups = [];
307
+ for (let k = tree.childStart[0]; k < tree.childStart[1]; k++) {
308
+ const node = tree.children[k];
309
+ groups.push({
310
+ label: tree.label(node),
311
+ total: totals.get(node, 0)
312
+ });
313
+ }
314
+ if (f.between) {
315
+ const { min = -Infinity, max = Infinity } = f.between;
316
+ groups = groups.filter((g) => g.total >= min && g.total <= max);
317
+ }
318
+ if (f.top) {
319
+ const sign = f.top.order === "asc" ? 1 : -1;
320
+ const ranked = groups.filter((g) => g.total === g.total);
321
+ ranked.sort((a, b) => sign * (a.total - b.total));
322
+ groups = ranked.slice(0, Math.max(0, f.top.count));
323
+ }
324
+ out[name] = {
325
+ include: groups.map((g) => g.label),
326
+ by
327
+ };
328
+ }
329
+ return out;
330
+ }
331
+ /** A calculated value: each term summed up as usual, then the formula per cell. */
332
+ function calculate(source, formula, sum, size) {
333
+ const columns = formula.terms.map((term) => sum({
334
+ field: term.field === null ? null : source.field(term.field),
335
+ aggregate: term.aggregate,
336
+ integer: term.aggregate === "count"
337
+ }));
338
+ const out = new Float64Array(size);
339
+ const values = new Float64Array(columns.length);
340
+ for (let s = 0; s < size; s++) {
341
+ for (let t = 0; t < columns.length; t++) values[t] = columns[t][s];
342
+ out[s] = formula.evaluate(values);
343
+ }
344
+ return out;
345
+ }
346
+ /** More than any group number of the axis, so (parent, group) makes a unique key. */
347
+ var stride = (levels) => Math.max(1, ...levels.map((l) => l.column.labels.length)) + 1;
348
+ /** The innermost group whose group numbers are the digits of `key`, made when new. */
349
+ function leaf(key, memo, tree, cards, digits) {
350
+ let node = memo.get(key);
351
+ if (node !== void 0) return node;
352
+ for (let d = cards.length - 1, rest = key; d >= 0; d--) {
353
+ const card = cards[d];
354
+ const digit = rest % card;
355
+ digits[d] = digit;
356
+ rest = (rest - digit) / card;
357
+ }
358
+ node = 0;
359
+ for (let d = 0; d < cards.length; d++) node = tree.child(node, digits[d]);
360
+ memo.set(key, node);
361
+ return node;
362
+ }
363
+ /** Pairs of innermost groups up to this many are found in a plain array instead of a hash. */
364
+ var DENSE_PAIRS = 1 << 22;
365
+ /** One value for every cell: summed per innermost pair, then merged up into the totals. */
366
+ function aggregate(source, value, slotOf, leaves, size, targets, fan) {
367
+ const n = slotOf.length;
368
+ const out = new Float64Array(size);
369
+ const kind = value.aggregate;
370
+ const roll = (fn) => {
371
+ for (let s = 0; s < leaves; s++) {
372
+ const base = s * fan;
373
+ for (let k = 0; k < fan; k++) fn(targets[base + k], s);
374
+ }
375
+ };
376
+ if (kind === "count") {
377
+ if (value.field) {
378
+ const { codes, empty } = source.dimension(value.field.name);
379
+ for (let i = 0; i < n; i++) {
380
+ const s = slotOf[i];
381
+ if (s >= 0 && codes[i] !== empty) out[s]++;
382
+ }
383
+ } else for (let i = 0; i < n; i++) {
384
+ const s = slotOf[i];
385
+ if (s >= 0) out[s]++;
386
+ }
387
+ roll((t, s) => out[t] += out[s]);
388
+ return out;
389
+ }
390
+ if (FAST_AGGREGATES.has(kind)) {
391
+ const values = source.measure(value.field.name);
392
+ if (kind === "min" || kind === "max") {
393
+ out.fill(NaN);
394
+ const max = kind === "max";
395
+ for (let i = 0; i < n; i++) {
396
+ const s = slotOf[i];
397
+ const v = values[i];
398
+ if (s < 0 || v !== v) continue;
399
+ const at = out[s];
400
+ if (max ? !(at >= v) : !(at <= v)) out[s] = v;
401
+ }
402
+ roll((t, s) => {
403
+ const v = out[s];
404
+ if (v !== v) return;
405
+ const at = out[t];
406
+ if (max ? !(at >= v) : !(at <= v)) out[t] = v;
407
+ });
408
+ return out;
409
+ }
410
+ const count = new Float64Array(size);
411
+ for (let i = 0; i < n; i++) {
412
+ const s = slotOf[i];
413
+ const v = values[i];
414
+ if (s < 0 || v !== v) continue;
415
+ out[s] += v;
416
+ count[s]++;
417
+ }
418
+ roll((t, s) => {
419
+ out[t] += out[s];
420
+ count[t] += count[s];
421
+ });
422
+ for (let s = 0; s < size; s++) {
423
+ const c = count[s];
424
+ out[s] = c === 0 ? NaN : kind === "avg" ? out[s] / c : out[s];
425
+ }
426
+ return out;
427
+ }
428
+ const aggregator = value.aggregator;
429
+ const field = value.field;
430
+ const states = Array.from({ length: size });
431
+ const { init, merge } = aggregator;
432
+ const add = aggregator.add;
433
+ if (aggregator.input === "number") {
434
+ const values = source.measure(field.name);
435
+ for (let i = 0; i < n; i++) {
436
+ const s = slotOf[i];
437
+ const v = values[i];
438
+ if (s < 0 || v !== v) continue;
439
+ states[s] = add(states[s] ?? init(), v);
440
+ }
441
+ } else {
442
+ const { codes, labels, empty } = source.dimension(field.name);
443
+ const byCode = aggregator === distinctAggregator;
444
+ for (let i = 0; i < n; i++) {
445
+ const s = slotOf[i];
446
+ const code = codes[i];
447
+ if (s < 0 || code === empty) continue;
448
+ states[s] = add(states[s] ?? init(), byCode ? code : labels[code]);
449
+ }
450
+ }
451
+ roll((t, s) => {
452
+ const from = states[s];
453
+ if (from !== void 0) states[t] = merge(states[t] ?? init(), from);
454
+ });
455
+ const { result } = aggregator;
456
+ for (let s = 0; s < size; s++) {
457
+ const state = states[s];
458
+ out[s] = state === void 0 ? NaN : result(state) ?? NaN;
459
+ }
460
+ return out;
461
+ }
462
+ var plainArray = (a) => Array.from(a);
463
+ /** A packet with plain arrays in place of typed ones, ready for JSON. */
464
+ function toPlain(packet) {
465
+ const tree = (t) => ({
466
+ ...t,
467
+ parent: plainArray(t.parent),
468
+ depth: plainArray(t.depth),
469
+ code: plainArray(t.code),
470
+ childStart: plainArray(t.childStart),
471
+ children: plainArray(t.children),
472
+ enter: plainArray(t.enter),
473
+ exit: plainArray(t.exit),
474
+ index: index(t.index),
475
+ levels: t.levels.map((l) => ({
476
+ ...l,
477
+ rank: plainArray(l.rank)
478
+ }))
479
+ });
480
+ const index = (i) => ({
481
+ size: i.size,
482
+ table: plainArray(i.table),
483
+ ids: plainArray(i.ids),
484
+ keys: plainArray(i.keys).slice(0, i.size)
485
+ });
486
+ return {
487
+ ...packet,
488
+ rows: tree(packet.rows),
489
+ columns: tree(packet.columns),
490
+ cells: index(packet.cells),
491
+ data: packet.data.map((d) => Array.from(d, (v) => v === v ? v : null)),
492
+ slotOf: plainArray(packet.slotOf),
493
+ leafRow: plainArray(packet.leafRow),
494
+ leafCol: plainArray(packet.leafCol)
495
+ };
496
+ }
497
+ /** The groups on both axes and every cell's values. */
498
+ var PivotResult = class PivotResult {
499
+ rows;
500
+ columns;
501
+ values;
502
+ /** How many records passed the filters. */
503
+ matched;
504
+ cells;
505
+ width;
506
+ data;
507
+ /** Each record's innermost cell, -1 when filtered out; and each such cell's groups. */
508
+ slotOf;
509
+ leafRow;
510
+ leafCol;
511
+ constructor(init) {
512
+ this.rows = init.rows;
513
+ this.columns = init.columns;
514
+ this.values = init.values;
515
+ this.matched = init.matched;
516
+ this.cells = init.cells;
517
+ this.width = init.width;
518
+ this.data = init.data;
519
+ this.slotOf = init.slotOf;
520
+ this.leafRow = init.leafRow;
521
+ this.leafCol = init.leafCol;
522
+ }
523
+ /**
524
+ * The result as data, to send from a worker or a server. With `plain`,
525
+ * plain arrays for `JSON.stringify` (typed arrays do not survive it);
526
+ * empty cells go as `null`.
527
+ */
528
+ pack({ plain = false } = {}) {
529
+ const packet = this.packTyped();
530
+ return plain ? toPlain(packet) : packet;
531
+ }
532
+ packTyped() {
533
+ return {
534
+ rows: this.rows.pack(),
535
+ columns: this.columns.pack(),
536
+ values: this.values.map((v) => ({
537
+ field: v.field?.name ?? null,
538
+ aggregate: v.aggregate,
539
+ integer: v.integer,
540
+ ...v.implicit ? { implicit: true } : {},
541
+ ...v.label === void 0 ? {} : { label: v.label },
542
+ ...v.formula === void 0 ? {} : { formula: v.formula },
543
+ ...v.show ? { show: v.show } : {},
544
+ ...v.mark ? { mark: v.mark } : {}
545
+ })),
546
+ matched: this.matched,
547
+ cells: this.cells.pack(),
548
+ width: this.width,
549
+ data: this.data,
550
+ slotOf: this.slotOf,
551
+ leafRow: this.leafRow,
552
+ leafCol: this.leafCol
553
+ };
554
+ }
555
+ /** The typed arrays in a packet, to move them instead of copying (`postMessage`'s transfer list). */
556
+ static transferables(packet) {
557
+ const out = /* @__PURE__ */ new Set();
558
+ const add = (a) => {
559
+ if (ArrayBuffer.isView(a) && a.buffer instanceof ArrayBuffer) out.add(a.buffer);
560
+ };
561
+ for (const tree of [packet.rows, packet.columns]) {
562
+ for (const a of [
563
+ tree.parent,
564
+ tree.depth,
565
+ tree.code,
566
+ tree.childStart,
567
+ tree.children
568
+ ]) add(a);
569
+ for (const a of [
570
+ tree.enter,
571
+ tree.exit,
572
+ tree.index.table,
573
+ tree.index.ids,
574
+ tree.index.keys
575
+ ]) add(a);
576
+ }
577
+ for (const a of [
578
+ packet.cells.table,
579
+ packet.cells.ids,
580
+ packet.cells.keys
581
+ ]) add(a);
582
+ for (const a of [
583
+ ...packet.data,
584
+ packet.slotOf,
585
+ packet.leafRow,
586
+ packet.leafCol
587
+ ]) add(a);
588
+ return [...out];
589
+ }
590
+ /**
591
+ * A result made from `pack()`'s output. Fields are described by
592
+ * `source` (no record is read), formats come from `config`.
593
+ */
594
+ static unpack(packet, source, config) {
595
+ const field = (name) => source.field(name);
596
+ const given = config?.values ?? [];
597
+ return new PivotResult({
598
+ rows: PivotTree.unpack(packet.rows, field),
599
+ columns: PivotTree.unpack(packet.columns, field),
600
+ values: packet.values.map((v, i) => {
601
+ const f = v.field === null ? null : field(v.field);
602
+ const spec = v.implicit ? void 0 : given[i];
603
+ const format = typeof spec === "object" ? spec.format : void 0;
604
+ const own = v.formula !== void 0 || v.show && (PERCENT_SHOWS.has(v.show.as) || v.show.as === "rank");
605
+ return {
606
+ ...v,
607
+ field: f,
608
+ format: format ?? (own ? void 0 : f?.format)
609
+ };
610
+ }),
611
+ matched: packet.matched,
612
+ cells: IntIndex.unpack(packet.cells),
613
+ width: packet.width,
614
+ data: packet.data.map((d) => d instanceof Float64Array ? d : Float64Array.from(d, (v) => v ?? NaN)),
615
+ slotOf: typed(Int32Array, packet.slotOf),
616
+ leafRow: typed(Int32Array, packet.leafRow),
617
+ leafCol: typed(Int32Array, packet.leafCol)
618
+ });
619
+ }
620
+ showing = /* @__PURE__ */ new Map();
621
+ ranges = /* @__PURE__ */ new Map();
622
+ shownBy(valueIndex, show) {
623
+ const spec = show === void 0 ? this.values[valueIndex]?.show : show;
624
+ const key = `${valueIndex}|${spec ? `${spec.as}|${spec.axis ?? ""}` : ""}`;
625
+ let shown = this.showing.get(key);
626
+ if (shown === void 0) {
627
+ shown = spec ? new PivotShown(resolveShow(spec, this.rows, this.columns), this.rows, this.columns, (r, c) => this.get(r, c, valueIndex)) : null;
628
+ this.showing.set(key, shown);
629
+ }
630
+ return shown;
631
+ }
632
+ /**
633
+ * How a value is shown, its `show` with the axis worked out; null when as
634
+ * it is. `show` stands in for the value's own (`null` for none).
635
+ */
636
+ show(valueIndex = 0, show) {
637
+ return this.shownBy(valueIndex, show)?.show ?? null;
638
+ }
639
+ /**
640
+ * The value as it is shown: a share, a difference, a running total or a
641
+ * rank when the value has a `show` (or one is given), the summed-up value
642
+ * otherwise. Nothing is summed up again.
643
+ */
644
+ shown(row, column, valueIndex = 0, show) {
645
+ const shown = this.shownBy(valueIndex, show);
646
+ return shown ? shown.value(row, column) : this.get(row, column, valueIndex);
647
+ }
648
+ /**
649
+ * The smallest and largest shown values of the innermost cells (those
650
+ * under no subtotal or total), which marks are drawn against.
651
+ */
652
+ range(valueIndex = 0, show) {
653
+ const spec = show === void 0 ? this.values[valueIndex]?.show : show;
654
+ const key = `${valueIndex}|${spec ? `${spec.as}|${spec.axis ?? ""}` : ""}`;
655
+ let range = this.ranges.get(key);
656
+ if (!range) {
657
+ const { rows, columns, cells, width } = this;
658
+ const rowDepth = rows.levels.length;
659
+ const colDepth = columns.levels.length;
660
+ let min = Infinity;
661
+ let max = -Infinity;
662
+ for (let s = 0; s < cells.size; s++) {
663
+ const cell = cells.keys[s];
664
+ const row = Math.floor(cell / width);
665
+ const col = cell - row * width;
666
+ if (rows.depth[row] !== rowDepth || columns.depth[col] !== colDepth) continue;
667
+ const v = this.shown(row, col, valueIndex, spec ?? null);
668
+ if (v !== v) continue;
669
+ if (v < min) min = v;
670
+ if (v > max) max = v;
671
+ }
672
+ range = min > max ? {
673
+ min: NaN,
674
+ max: NaN
675
+ } : {
676
+ min,
677
+ max
678
+ };
679
+ this.ranges.set(key, range);
680
+ }
681
+ return range;
682
+ }
683
+ /**
684
+ * The value at `valueIndex` (its place in `values`) of a row group and a
685
+ * column group; `NaN` when no record falls in both. The trees' `total`
686
+ * is the grand total.
687
+ */
688
+ get(row, column, valueIndex = 0) {
689
+ const slot = this.cells.get(row * this.width + column);
690
+ return slot < 0 ? NaN : this.data[valueIndex][slot];
691
+ }
692
+ /** The indexes of the records in a row group and a column group, for showing the details. */
693
+ indices(row, column) {
694
+ const { slotOf, leafRow, leafCol, rows, columns } = this;
695
+ const inside = new Uint8Array(leafRow.length);
696
+ for (let s = 0; s < leafRow.length; s++) inside[s] = rows.within(leafRow[s], row) && columns.within(leafCol[s], column) ? 1 : 0;
697
+ const out = [];
698
+ for (let i = 0; i < slotOf.length; i++) {
699
+ const s = slotOf[i];
700
+ if (s >= 0 && inside[s]) out.push(i);
701
+ }
702
+ return out;
703
+ }
704
+ /**
705
+ * The children of every row group, reordered by a column's values (or as
706
+ * they are when `sort` is empty). Nothing is summed up again.
707
+ */
708
+ rowOrder(sort) {
709
+ const { rows } = this;
710
+ if (!sort || !this.data[sort.valueIndex]) return rows.children;
711
+ const column = this.columns.find(sort.column ?? []);
712
+ if (column < 0) return rows.children;
713
+ const children = rows.children.slice();
714
+ const keys = new Float64Array(rows.size);
715
+ for (let node = 0; node < rows.size; node++) keys[node] = this.get(node, column, sort.valueIndex);
716
+ const sign = sort.order === "desc" ? -1 : 1;
717
+ const place = new Int32Array(rows.size);
718
+ rows.children.forEach((node, at) => place[node] = at);
719
+ for (let node = 0; node < rows.size; node++) {
720
+ const from = rows.childStart[node];
721
+ const to = rows.childStart[node + 1];
722
+ if (to - from < 2) continue;
723
+ children.subarray(from, to).sort((a, b) => {
724
+ const ka = keys[a];
725
+ const kb = keys[b];
726
+ if (ka !== ka || kb !== kb) return ka !== ka ? kb !== kb ? place[a] - place[b] : 1 : -1;
727
+ return ka === kb ? place[a] - place[b] : sign * (ka - kb);
728
+ });
729
+ }
730
+ return children;
731
+ }
732
+ };
733
+ export { PERCENT_SHOWS, PivotResult, checkPivotFormula, computePivot, markOf, resolveConfig, showOf };
734
+
735
+ //# sourceMappingURL=compute.js.map