@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.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/assets/default-PXde2wzm.js.map +1 -0
- package/dist/core/format.js +116 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/labels.js +28 -0
- package/dist/core/labels.js.map +1 -0
- package/dist/core/layout.js +190 -0
- package/dist/core/layout.js.map +1 -0
- package/dist/core/source.js +46 -0
- package/dist/core/source.js.map +1 -0
- package/dist/core/table.js +154 -0
- package/dist/core/table.js.map +1 -0
- package/dist/engine/aggregate.js +51 -0
- package/dist/engine/aggregate.js.map +1 -0
- package/dist/engine/compute.js +735 -0
- package/dist/engine/compute.js.map +1 -0
- package/dist/engine/engine.js +46 -0
- package/dist/engine/engine.js.map +1 -0
- package/dist/engine/formula.js +225 -0
- package/dist/engine/formula.js.map +1 -0
- package/dist/engine/group.js +107 -0
- package/dist/engine/group.js.map +1 -0
- package/dist/engine/hash.js +108 -0
- package/dist/engine/hash.js.map +1 -0
- package/dist/engine/index.js +8 -0
- package/dist/engine/show.js +132 -0
- package/dist/engine/show.js.map +1 -0
- package/dist/engine/source.js +339 -0
- package/dist/engine/source.js.map +1 -0
- package/dist/engine/tree.js +226 -0
- package/dist/engine/tree.js.map +1 -0
- package/dist/fields/fields.js +6 -0
- package/dist/fields/fields.js.map +1 -0
- package/dist/fields/fields.vue_vue_type_script_setup_true_lang.js +881 -0
- package/dist/fields/fields.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/fields/filter-list.js +6 -0
- package/dist/fields/filter-list.js.map +1 -0
- package/dist/fields/filter-list.vue_vue_type_script_setup_true_lang.js +184 -0
- package/dist/fields/filter-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/fields/formula-dialog.js +6 -0
- package/dist/fields/formula-dialog.js.map +1 -0
- package/dist/fields/formula-dialog.vue_vue_type_script_setup_true_lang.js +165 -0
- package/dist/fields/formula-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/fields/model.js +200 -0
- package/dist/fields/model.js.map +1 -0
- package/dist/fields/top-dialog.js +6 -0
- package/dist/fields/top-dialog.js.map +1 -0
- package/dist/fields/top-dialog.vue_vue_type_script_setup_true_lang.js +174 -0
- package/dist/fields/top-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +508 -0
- package/dist/locale.js.map +1 -0
- package/dist/pivot.js +6 -0
- package/dist/pivot.js.map +1 -0
- package/dist/pivot.vue_vue_type_script_setup_true_lang.js +1711 -0
- package/dist/pivot.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +13 -0
- package/dist/types/core/format.d.ts +26 -0
- package/dist/types/core/labels.d.ts +14 -0
- package/dist/types/core/layout.d.ts +72 -0
- package/dist/types/core/source.d.ts +7 -0
- package/dist/types/core/table.d.ts +73 -0
- package/dist/types/engine/aggregate.d.ts +35 -0
- package/dist/types/engine/compute.d.ts +173 -0
- package/dist/types/engine/engine.d.ts +29 -0
- package/dist/types/engine/formula.d.ts +44 -0
- package/dist/types/engine/group.d.ts +34 -0
- package/dist/types/engine/hash.d.ts +36 -0
- package/dist/types/engine/index.d.ts +15 -0
- package/dist/types/engine/show.d.ts +27 -0
- package/dist/types/engine/source.d.ts +108 -0
- package/dist/types/engine/tree.d.ts +87 -0
- package/dist/types/engine/types.d.ts +148 -0
- package/dist/types/fields/fields.vue.d.ts +26 -0
- package/dist/types/fields/filter-list.vue.d.ts +20 -0
- package/dist/types/fields/formula-dialog.vue.d.ts +30 -0
- package/dist/types/fields/model.d.ts +52 -0
- package/dist/types/fields/top-dialog.vue.d.ts +25 -0
- package/dist/types/index.d.ts +458 -0
- package/dist/types/locale.d.ts +184 -0
- package/dist/types/pivot.vue.d.ts +29 -0
- package/dist/types/types.d.ts +199 -0
- package/dist/types/worker/client.d.ts +19 -0
- package/dist/types/worker/default.d.ts +1 -0
- package/dist/types/worker/index.d.ts +5 -0
- package/dist/types/worker/protocol.d.ts +60 -0
- package/dist/types/worker/serve.d.ts +22 -0
- package/dist/worker/client.js +191 -0
- package/dist/worker/client.js.map +1 -0
- package/dist/worker/default.js +20 -0
- package/dist/worker/default.js.map +1 -0
- package/dist/worker/index.js +3 -0
- package/dist/worker/serve.js +101 -0
- package/dist/worker/serve.js.map +1 -0
- package/package.json +64 -4
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// src/engine/hash.ts
|
|
2
|
+
/**
|
|
3
|
+
* A table from whole numbers (0 to 2^53) to the order they were first seen
|
|
4
|
+
* in: open addressing over typed arrays, so millions of lookups allocate
|
|
5
|
+
* nothing and never touch a `Map`.
|
|
6
|
+
*/
|
|
7
|
+
var IntIndex = class IntIndex {
|
|
8
|
+
/** The keys by slot; `-1` marks a free slot. */
|
|
9
|
+
table;
|
|
10
|
+
/** What each slot's key maps to. */
|
|
11
|
+
ids;
|
|
12
|
+
mask;
|
|
13
|
+
/** The keys in the order they were added. */
|
|
14
|
+
keys;
|
|
15
|
+
size = 0;
|
|
16
|
+
constructor(expected = 16) {
|
|
17
|
+
let capacity = 16;
|
|
18
|
+
while (capacity < expected * 2) capacity *= 2;
|
|
19
|
+
this.table = new Float64Array(capacity).fill(-1);
|
|
20
|
+
this.ids = new Int32Array(capacity);
|
|
21
|
+
this.mask = capacity - 1;
|
|
22
|
+
this.keys = new Float64Array(Math.max(8, expected));
|
|
23
|
+
}
|
|
24
|
+
/** The id of `key`, or -1. */
|
|
25
|
+
get(key) {
|
|
26
|
+
const { table, mask } = this;
|
|
27
|
+
let slot = hash(key) & mask;
|
|
28
|
+
for (;;) {
|
|
29
|
+
const at = table[slot];
|
|
30
|
+
if (at === key) return this.ids[slot];
|
|
31
|
+
if (at === -1) return -1;
|
|
32
|
+
slot = slot + 1 & mask;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** The id of `key`, adding it with the next id when it is new. */
|
|
36
|
+
add(key) {
|
|
37
|
+
const { table, mask } = this;
|
|
38
|
+
let slot = hash(key) & mask;
|
|
39
|
+
for (;;) {
|
|
40
|
+
const at = table[slot];
|
|
41
|
+
if (at === key) return this.ids[slot];
|
|
42
|
+
if (at === -1) break;
|
|
43
|
+
slot = slot + 1 & mask;
|
|
44
|
+
}
|
|
45
|
+
const id = this.size++;
|
|
46
|
+
table[slot] = key;
|
|
47
|
+
this.ids[slot] = id;
|
|
48
|
+
if (id >= this.keys.length) {
|
|
49
|
+
const keys = new Float64Array(this.keys.length * 2 || 8);
|
|
50
|
+
keys.set(this.keys);
|
|
51
|
+
this.keys = keys;
|
|
52
|
+
}
|
|
53
|
+
this.keys[id] = key;
|
|
54
|
+
if (this.size * 2 > table.length) this.grow();
|
|
55
|
+
return id;
|
|
56
|
+
}
|
|
57
|
+
/** Its arrays, to send to another thread (moved, not copied) or save. */
|
|
58
|
+
pack() {
|
|
59
|
+
return {
|
|
60
|
+
table: this.table,
|
|
61
|
+
ids: this.ids,
|
|
62
|
+
keys: this.keys,
|
|
63
|
+
size: this.size
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** An index made from `pack()`'s output; plain arrays (from JSON) work too. */
|
|
67
|
+
static unpack(packet) {
|
|
68
|
+
const index = new IntIndex(0);
|
|
69
|
+
index.table = typed(Float64Array, packet.table);
|
|
70
|
+
index.ids = typed(Int32Array, packet.ids);
|
|
71
|
+
index.keys = typed(Float64Array, packet.keys);
|
|
72
|
+
index.mask = index.table.length - 1;
|
|
73
|
+
index.size = packet.size;
|
|
74
|
+
return index;
|
|
75
|
+
}
|
|
76
|
+
grow() {
|
|
77
|
+
const capacity = this.table.length * 2;
|
|
78
|
+
const table = new Float64Array(capacity).fill(-1);
|
|
79
|
+
const ids = new Int32Array(capacity);
|
|
80
|
+
const mask = capacity - 1;
|
|
81
|
+
const { keys, size } = this;
|
|
82
|
+
for (let id = 0; id < size; id++) {
|
|
83
|
+
const key = keys[id];
|
|
84
|
+
let slot = hash(key) & mask;
|
|
85
|
+
while (table[slot] !== -1) slot = slot + 1 & mask;
|
|
86
|
+
table[slot] = key;
|
|
87
|
+
ids[slot] = id;
|
|
88
|
+
}
|
|
89
|
+
this.table = table;
|
|
90
|
+
this.ids = ids;
|
|
91
|
+
this.mask = mask;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
/** The array as it is when it is already of that type (moved from a worker), else a typed copy (from JSON). */
|
|
95
|
+
function typed(Type, array) {
|
|
96
|
+
return array instanceof Type ? array : Type.from(array);
|
|
97
|
+
}
|
|
98
|
+
var HIGH = 4294967296;
|
|
99
|
+
/** Mixes both halves of a key up to 2^53 into 32 bits. */
|
|
100
|
+
function hash(key) {
|
|
101
|
+
let h = Math.imul(key >>> 0 ^ Math.imul(key / HIGH | 0, 2654435761), 2246822507);
|
|
102
|
+
h ^= h >>> 13;
|
|
103
|
+
h = Math.imul(h, 3266489909);
|
|
104
|
+
return h ^ h >>> 16;
|
|
105
|
+
}
|
|
106
|
+
export { IntIndex, typed };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","names":[],"sources":["../../src/engine/hash.ts"],"sourcesContent":["/**\n * A table from whole numbers (0 to 2^53) to the order they were first seen\n * in: open addressing over typed arrays, so millions of lookups allocate\n * nothing and never touch a `Map`.\n */\nexport class IntIndex {\n /** The keys by slot; `-1` marks a free slot. */\n private table: Float64Array\n /** What each slot's key maps to. */\n private ids: Int32Array\n private mask: number\n /** The keys in the order they were added. */\n keys: Float64Array\n size = 0\n\n constructor(expected = 16) {\n let capacity = 16\n while (capacity < expected * 2) capacity *= 2\n this.table = new Float64Array(capacity).fill(-1)\n this.ids = new Int32Array(capacity)\n this.mask = capacity - 1\n this.keys = new Float64Array(Math.max(8, expected))\n }\n\n /** The id of `key`, or -1. */\n get(key: number) {\n const { table, mask } = this\n let slot = hash(key) & mask\n for (;;) {\n const at = table[slot]!\n if (at === key) return this.ids[slot]!\n if (at === -1) return -1\n slot = (slot + 1) & mask\n }\n }\n\n /** The id of `key`, adding it with the next id when it is new. */\n add(key: number) {\n const { table, mask } = this\n let slot = hash(key) & mask\n for (;;) {\n const at = table[slot]!\n if (at === key) return this.ids[slot]!\n if (at === -1) break\n slot = (slot + 1) & mask\n }\n const id = this.size++\n table[slot] = key\n this.ids[slot] = id\n if (id >= this.keys.length) {\n const keys = new Float64Array(this.keys.length * 2 || 8)\n keys.set(this.keys)\n this.keys = keys\n }\n this.keys[id] = key\n if (this.size * 2 > table.length) this.grow()\n return id\n }\n\n /** Its arrays, to send to another thread (moved, not copied) or save. */\n pack(): PivotIndexPacket {\n return { table: this.table, ids: this.ids, keys: this.keys, size: this.size }\n }\n\n /** An index made from `pack()`'s output; plain arrays (from JSON) work too. */\n static unpack(packet: PivotIndexPacket): IntIndex {\n const index = new IntIndex(0)\n index.table = typed(Float64Array, packet.table)\n index.ids = typed(Int32Array, packet.ids)\n index.keys = typed(Float64Array, packet.keys)\n index.mask = index.table.length - 1\n index.size = packet.size\n return index\n }\n\n private grow() {\n const capacity = this.table.length * 2\n const table = new Float64Array(capacity).fill(-1)\n const ids = new Int32Array(capacity)\n const mask = capacity - 1\n const { keys, size } = this\n for (let id = 0; id < size; id++) {\n const key = keys[id]!\n let slot = hash(key) & mask\n while (table[slot] !== -1) slot = (slot + 1) & mask\n table[slot] = key\n ids[slot] = id\n }\n this.table = table\n this.ids = ids\n this.mask = mask\n }\n}\n\ntype TypedArrayOf =\n Float64ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor | Uint8ArrayConstructor\n\n/** The array as it is when it is already of that type (moved from a worker), else a typed copy (from JSON). */\nexport function typed<T extends TypedArrayOf>(Type: T, array: ArrayLike<number>): InstanceType<T> {\n return (array instanceof Type ? array : Type.from(array)) as InstanceType<T>\n}\n\n/** An `IntIndex` as data. */\nexport interface PivotIndexPacket {\n table: ArrayLike<number>\n ids: ArrayLike<number>\n keys: ArrayLike<number>\n size: number\n}\n\nconst HIGH = 4294967296\n\n/** Mixes both halves of a key up to 2^53 into 32 bits. */\nfunction hash(key: number) {\n let h = Math.imul((key >>> 0) ^ Math.imul((key / HIGH) | 0, 0x9e3779b1), 0x85ebca6b)\n h ^= h >>> 13\n h = Math.imul(h, 0xc2b2ae35)\n return h ^ (h >>> 16)\n}\n"],"mappings":";;;;;;AAKA,IAAa,WAAb,MAAa,SAAS;;CAEpB;;CAEA;CACA;;CAEA;CACA,OAAO;CAEP,YAAY,WAAW,IAAI;EACzB,IAAI,WAAW;EACf,OAAO,WAAW,WAAW,GAAG,YAAY;EAC5C,KAAK,QAAQ,IAAI,aAAa,QAAQ,CAAC,CAAC,KAAK,EAAE;EAC/C,KAAK,MAAM,IAAI,WAAW,QAAQ;EAClC,KAAK,OAAO,WAAW;EACvB,KAAK,OAAO,IAAI,aAAa,KAAK,IAAI,GAAG,QAAQ,CAAC;CACpD;;CAGA,IAAI,KAAa;EACf,MAAM,EAAE,OAAO,SAAS;EACxB,IAAI,OAAO,KAAK,GAAG,IAAI;EACvB,SAAS;GACP,MAAM,KAAK,MAAM;GACjB,IAAI,OAAO,KAAK,OAAO,KAAK,IAAI;GAChC,IAAI,OAAO,IAAI,OAAO;GACtB,OAAQ,OAAO,IAAK;EACtB;CACF;;CAGA,IAAI,KAAa;EACf,MAAM,EAAE,OAAO,SAAS;EACxB,IAAI,OAAO,KAAK,GAAG,IAAI;EACvB,SAAS;GACP,MAAM,KAAK,MAAM;GACjB,IAAI,OAAO,KAAK,OAAO,KAAK,IAAI;GAChC,IAAI,OAAO,IAAI;GACf,OAAQ,OAAO,IAAK;EACtB;EACA,MAAM,KAAK,KAAK;EAChB,MAAM,QAAQ;EACd,KAAK,IAAI,QAAQ;EACjB,IAAI,MAAM,KAAK,KAAK,QAAQ;GAC1B,MAAM,OAAO,IAAI,aAAa,KAAK,KAAK,SAAS,KAAK,CAAC;GACvD,KAAK,IAAI,KAAK,IAAI;GAClB,KAAK,OAAO;EACd;EACA,KAAK,KAAK,MAAM;EAChB,IAAI,KAAK,OAAO,IAAI,MAAM,QAAQ,KAAK,KAAK;EAC5C,OAAO;CACT;;CAGA,OAAyB;EACvB,OAAO;GAAE,OAAO,KAAK;GAAO,KAAK,KAAK;GAAK,MAAM,KAAK;GAAM,MAAM,KAAK;EAAK;CAC9E;;CAGA,OAAO,OAAO,QAAoC;EAChD,MAAM,QAAQ,IAAI,SAAS,CAAC;EAC5B,MAAM,QAAQ,MAAM,cAAc,OAAO,KAAK;EAC9C,MAAM,MAAM,MAAM,YAAY,OAAO,GAAG;EACxC,MAAM,OAAO,MAAM,cAAc,OAAO,IAAI;EAC5C,MAAM,OAAO,MAAM,MAAM,SAAS;EAClC,MAAM,OAAO,OAAO;EACpB,OAAO;CACT;CAEA,OAAe;EACb,MAAM,WAAW,KAAK,MAAM,SAAS;EACrC,MAAM,QAAQ,IAAI,aAAa,QAAQ,CAAC,CAAC,KAAK,EAAE;EAChD,MAAM,MAAM,IAAI,WAAW,QAAQ;EACnC,MAAM,OAAO,WAAW;EACxB,MAAM,EAAE,MAAM,SAAS;EACvB,KAAK,IAAI,KAAK,GAAG,KAAK,MAAM,MAAM;GAChC,MAAM,MAAM,KAAK;GACjB,IAAI,OAAO,KAAK,GAAG,IAAI;GACvB,OAAO,MAAM,UAAU,IAAI,OAAQ,OAAO,IAAK;GAC/C,MAAM,QAAQ;GACd,IAAI,QAAQ;EACd;EACA,KAAK,QAAQ;EACb,KAAK,MAAM;EACX,KAAK,OAAO;CACd;AACF;;AAMA,SAAgB,MAA8B,MAAS,OAA2C;CAChG,OAAQ,iBAAiB,OAAO,QAAQ,KAAK,KAAK,KAAK;AACzD;AAUA,IAAM,OAAO;;AAGb,SAAS,KAAK,KAAa;CACzB,IAAI,IAAI,KAAK,KAAM,QAAQ,IAAK,KAAK,KAAM,MAAM,OAAQ,GAAG,UAAU,GAAG,UAAU;CACnF,KAAK,MAAM;CACX,IAAI,KAAK,KAAK,GAAG,UAAU;CAC3B,OAAO,IAAK,MAAM;AACpB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineAggregator } from "./aggregate.js";
|
|
2
|
+
import { PivotFormulaError, parsePivotFormula } from "./formula.js";
|
|
3
|
+
import { DATE_PERIODS, bucketer, isoWeek, readBucket, toDate } from "./group.js";
|
|
4
|
+
import { PivotSource, compareValues, isColumns, isEmpty, toNumber } from "./source.js";
|
|
5
|
+
import { PivotTree } from "./tree.js";
|
|
6
|
+
import { PERCENT_SHOWS, PivotResult, checkPivotFormula, computePivot, resolveConfig } from "./compute.js";
|
|
7
|
+
import { neededColumns } from "./engine.js";
|
|
8
|
+
export { DATE_PERIODS, PERCENT_SHOWS, PivotFormulaError, PivotResult, PivotSource, PivotTree, bucketer, checkPivotFormula, compareValues, computePivot, defineAggregator, isColumns, isEmpty, isoWeek, neededColumns, parsePivotFormula, readBucket, resolveConfig, toDate, toNumber };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// src/engine/show.ts
|
|
2
|
+
var ON_COLUMNS = /* @__PURE__ */ new Set([
|
|
3
|
+
"difference",
|
|
4
|
+
"percentDifference",
|
|
5
|
+
"runningTotal"
|
|
6
|
+
]);
|
|
7
|
+
/** The axis a way of showing works along: as given, or the default when that axis has fields. */
|
|
8
|
+
function resolveShow(show, rows, columns) {
|
|
9
|
+
let axis = show.axis ?? (ON_COLUMNS.has(show.as) ? "columns" : "rows");
|
|
10
|
+
const has = (on) => (on === "rows" ? rows : columns).levels.length > 0;
|
|
11
|
+
if (!show.axis && !has(axis) && has(axis === "rows" ? "columns" : "rows")) axis = axis === "rows" ? "columns" : "rows";
|
|
12
|
+
return {
|
|
13
|
+
as: show.as,
|
|
14
|
+
axis
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Where each group sits among all the children (its index in `children`). */
|
|
18
|
+
var places = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
function placeOf(tree) {
|
|
20
|
+
let place = places.get(tree);
|
|
21
|
+
if (!place) {
|
|
22
|
+
place = new Int32Array(tree.size);
|
|
23
|
+
tree.children.forEach((node, at) => place[node] = at);
|
|
24
|
+
places.set(tree, place);
|
|
25
|
+
}
|
|
26
|
+
return place;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Works out a value as it is shown, from the summed-up values (`get`).
|
|
30
|
+
* Groups come in their label order, whatever order the rows are shown in.
|
|
31
|
+
*/
|
|
32
|
+
var PivotShown = class {
|
|
33
|
+
show;
|
|
34
|
+
rows;
|
|
35
|
+
columns;
|
|
36
|
+
get;
|
|
37
|
+
running = /* @__PURE__ */ new Map();
|
|
38
|
+
sorted = /* @__PURE__ */ new Map();
|
|
39
|
+
constructor(show, rows, columns, get) {
|
|
40
|
+
this.show = show;
|
|
41
|
+
this.rows = rows;
|
|
42
|
+
this.columns = columns;
|
|
43
|
+
this.get = get;
|
|
44
|
+
}
|
|
45
|
+
value(row, column) {
|
|
46
|
+
const { get, show } = this;
|
|
47
|
+
const v = get(row, column);
|
|
48
|
+
switch (show.as) {
|
|
49
|
+
case "percentOfTotal": return ratio(v, get(0, 0));
|
|
50
|
+
case "percentOfRow": return ratio(v, get(row, 0));
|
|
51
|
+
case "percentOfColumn": return ratio(v, get(0, column));
|
|
52
|
+
case "percentOfParent":
|
|
53
|
+
if (show.axis === "rows") return ratio(v, get(row > 0 ? this.rows.parent[row] : 0, column));
|
|
54
|
+
return ratio(v, get(row, column > 0 ? this.columns.parent[column] : 0));
|
|
55
|
+
case "difference":
|
|
56
|
+
case "percentDifference": {
|
|
57
|
+
const prev = this.previous(row, column);
|
|
58
|
+
if (prev === null) return NaN;
|
|
59
|
+
const p = show.axis === "rows" ? get(prev, column) : get(row, prev);
|
|
60
|
+
if (show.as === "difference") return v - p;
|
|
61
|
+
return p === 0 ? NaN : (v - p) / Math.abs(p);
|
|
62
|
+
}
|
|
63
|
+
case "runningTotal": return this.runningTotal(row, column);
|
|
64
|
+
case "rank": return this.rank(row, column, v);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** The group before this one on the axis, or null for the first and for totals. */
|
|
68
|
+
previous(row, column) {
|
|
69
|
+
const tree = this.show.axis === "rows" ? this.rows : this.columns;
|
|
70
|
+
const node = this.show.axis === "rows" ? row : column;
|
|
71
|
+
if (node <= 0) return null;
|
|
72
|
+
const at = placeOf(tree)[node];
|
|
73
|
+
return at > tree.childStart[tree.parent[node]] ? tree.children[at - 1] : null;
|
|
74
|
+
}
|
|
75
|
+
runningTotal(row, column) {
|
|
76
|
+
const onRows = this.show.axis === "rows";
|
|
77
|
+
const tree = onRows ? this.rows : this.columns;
|
|
78
|
+
const node = onRows ? row : column;
|
|
79
|
+
const other = onRows ? column : row;
|
|
80
|
+
const read = (n) => onRows ? this.get(n, other) : this.get(other, n);
|
|
81
|
+
if (node <= 0) return read(node);
|
|
82
|
+
const key = (n) => n * (onRows ? this.columns.size : this.rows.size) + other;
|
|
83
|
+
const cached = this.running.get(key(node));
|
|
84
|
+
if (cached !== void 0) return cached;
|
|
85
|
+
const place = placeOf(tree);
|
|
86
|
+
const first = tree.childStart[tree.parent[node]];
|
|
87
|
+
let at = place[node];
|
|
88
|
+
while (at > first && !this.running.has(key(tree.children[at - 1]))) at--;
|
|
89
|
+
let total = at > first ? this.running.get(key(tree.children[at - 1])) : NaN;
|
|
90
|
+
for (; at <= place[node]; at++) {
|
|
91
|
+
const n = tree.children[at];
|
|
92
|
+
const v = read(n);
|
|
93
|
+
if (v === v) total = total === total ? total + v : v;
|
|
94
|
+
this.running.set(key(n), total);
|
|
95
|
+
}
|
|
96
|
+
return total;
|
|
97
|
+
}
|
|
98
|
+
rank(row, column, v) {
|
|
99
|
+
if (v !== v) return NaN;
|
|
100
|
+
const onRows = this.show.axis === "rows";
|
|
101
|
+
const tree = onRows ? this.rows : this.columns;
|
|
102
|
+
const node = onRows ? row : column;
|
|
103
|
+
if (node <= 0) return NaN;
|
|
104
|
+
const other = onRows ? column : row;
|
|
105
|
+
const parent = tree.parent[node];
|
|
106
|
+
const key = parent * (onRows ? this.columns.size : this.rows.size) + other;
|
|
107
|
+
let values = this.sorted.get(key);
|
|
108
|
+
if (!values) {
|
|
109
|
+
const list = [];
|
|
110
|
+
for (let k = tree.childStart[parent]; k < tree.childStart[parent + 1]; k++) {
|
|
111
|
+
const n = tree.children[k];
|
|
112
|
+
const x = onRows ? this.get(n, other) : this.get(other, n);
|
|
113
|
+
if (x === x) list.push(x);
|
|
114
|
+
}
|
|
115
|
+
values = Float64Array.from(list);
|
|
116
|
+
values.sort();
|
|
117
|
+
this.sorted.set(key, values);
|
|
118
|
+
}
|
|
119
|
+
let lo = 0;
|
|
120
|
+
let hi = values.length;
|
|
121
|
+
while (lo < hi) {
|
|
122
|
+
const mid = lo + hi >> 1;
|
|
123
|
+
if (values[mid] <= v) lo = mid + 1;
|
|
124
|
+
else hi = mid;
|
|
125
|
+
}
|
|
126
|
+
return values.length - lo + 1;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var ratio = (a, b) => b === 0 || b !== b || a !== a ? NaN : a / b;
|
|
130
|
+
export { PivotShown, resolveShow };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=show.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"show.js","names":[],"sources":["../../src/engine/show.ts"],"sourcesContent":["import type { PivotTree } from './tree'\nimport type { PivotShow, PivotShowAs } from './types'\n\n/** A way of showing a value with its axis worked out. */\nexport interface PivotResolvedShow {\n as: PivotShowAs\n axis: 'rows' | 'columns'\n}\n\nconst ON_COLUMNS: ReadonlySet<PivotShowAs> = new Set([\n 'difference',\n 'percentDifference',\n 'runningTotal',\n])\n\n/** The axis a way of showing works along: as given, or the default when that axis has fields. */\nexport function resolveShow(\n show: PivotShow,\n rows: PivotTree,\n columns: PivotTree,\n): PivotResolvedShow {\n let axis = show.axis ?? (ON_COLUMNS.has(show.as) ? 'columns' : 'rows')\n const has = (on: 'rows' | 'columns') => (on === 'rows' ? rows : columns).levels.length > 0\n if (!show.axis && !has(axis) && has(axis === 'rows' ? 'columns' : 'rows')) {\n axis = axis === 'rows' ? 'columns' : 'rows'\n }\n return { as: show.as, axis }\n}\n\n/** Where each group sits among all the children (its index in `children`). */\nconst places = new WeakMap<PivotTree, Int32Array>()\nfunction placeOf(tree: PivotTree) {\n let place = places.get(tree)\n if (!place) {\n place = new Int32Array(tree.size)\n tree.children.forEach((node, at) => (place![node] = at))\n places.set(tree, place)\n }\n return place\n}\n\n/**\n * Works out a value as it is shown, from the summed-up values (`get`).\n * Groups come in their label order, whatever order the rows are shown in.\n */\nexport class PivotShown {\n private running = new Map<number, number>()\n private sorted = new Map<number, Float64Array>()\n\n constructor(\n readonly show: PivotResolvedShow,\n private rows: PivotTree,\n private columns: PivotTree,\n private get: (row: number, column: number) => number,\n ) {}\n\n value(row: number, column: number): number {\n const { get, show } = this\n const v = get(row, column)\n switch (show.as) {\n case 'percentOfTotal':\n return ratio(v, get(0, 0))\n case 'percentOfRow':\n return ratio(v, get(row, 0))\n case 'percentOfColumn':\n return ratio(v, get(0, column))\n case 'percentOfParent': {\n if (show.axis === 'rows') return ratio(v, get(row > 0 ? this.rows.parent[row]! : 0, column))\n return ratio(v, get(row, column > 0 ? this.columns.parent[column]! : 0))\n }\n case 'difference':\n case 'percentDifference': {\n const prev = this.previous(row, column)\n if (prev === null) return Number.NaN\n const p = show.axis === 'rows' ? get(prev, column) : get(row, prev)\n if (show.as === 'difference') return v - p\n return p === 0 ? Number.NaN : (v - p) / Math.abs(p)\n }\n case 'runningTotal':\n return this.runningTotal(row, column)\n case 'rank':\n return this.rank(row, column, v)\n }\n }\n\n /** The group before this one on the axis, or null for the first and for totals. */\n private previous(row: number, column: number): number | null {\n const tree = this.show.axis === 'rows' ? this.rows : this.columns\n const node = this.show.axis === 'rows' ? row : column\n if (node <= 0) return null\n const at = placeOf(tree)[node]!\n return at > tree.childStart[tree.parent[node]!]! ? tree.children[at - 1]! : null\n }\n\n private runningTotal(row: number, column: number) {\n const onRows = this.show.axis === 'rows'\n const tree = onRows ? this.rows : this.columns\n const node = onRows ? row : column\n const other = onRows ? column : row\n const read = (n: number) => (onRows ? this.get(n, other) : this.get(other, n))\n if (node <= 0) return read(node)\n const key = (n: number) => n * (onRows ? this.columns.size : this.rows.size) + other\n const cached = this.running.get(key(node))\n if (cached !== undefined) return cached\n // add up from the first group (or the last one worked out) to this one\n const place = placeOf(tree)\n const first = tree.childStart[tree.parent[node]!]!\n let at = place[node]!\n while (at > first && !this.running.has(key(tree.children[at - 1]!))) at--\n let total = at > first ? this.running.get(key(tree.children[at - 1]!))! : Number.NaN\n for (; at <= place[node]!; at++) {\n const n = tree.children[at]!\n const v = read(n)\n if (v === v) total = total === total ? total + v : v\n this.running.set(key(n), total)\n }\n return total\n }\n\n private rank(row: number, column: number, v: number) {\n if (v !== v) return Number.NaN\n const onRows = this.show.axis === 'rows'\n const tree = onRows ? this.rows : this.columns\n const node = onRows ? row : column\n if (node <= 0) return Number.NaN\n const other = onRows ? column : row\n const parent = tree.parent[node]!\n const key = parent * (onRows ? this.columns.size : this.rows.size) + other\n let values = this.sorted.get(key)\n if (!values) {\n const list: number[] = []\n for (let k = tree.childStart[parent]!; k < tree.childStart[parent + 1]!; k++) {\n const n = tree.children[k]!\n const x = onRows ? this.get(n, other) : this.get(other, n)\n if (x === x) list.push(x)\n }\n values = Float64Array.from(list)\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n values.sort()\n this.sorted.set(key, values)\n }\n // how many are larger: those after the last one not larger, found by bisection\n let lo = 0\n let hi = values.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (values[mid]! <= v) lo = mid + 1\n else hi = mid\n }\n return values.length - lo + 1\n }\n}\n\nconst ratio = (a: number, b: number) => (b === 0 || b !== b || a !== a ? Number.NaN : a / b)\n"],"mappings":"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"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { bucketer, groupKey } from "./group.js";
|
|
2
|
+
// src/engine/source.ts
|
|
3
|
+
var SAMPLE = 100;
|
|
4
|
+
/** Records read between looks at the clock while reading in slices. */
|
|
5
|
+
var STEP = 4096;
|
|
6
|
+
var isColumns = (data) => !!data && typeof data === "object" && !Array.isArray(data);
|
|
7
|
+
/**
|
|
8
|
+
* The records, read field by field into typed arrays the first time each
|
|
9
|
+
* field is used, and kept until the records change.
|
|
10
|
+
*/
|
|
11
|
+
var PivotSource = class {
|
|
12
|
+
/** The records as given: objects, or columns. */
|
|
13
|
+
data;
|
|
14
|
+
length;
|
|
15
|
+
fields;
|
|
16
|
+
locale;
|
|
17
|
+
collator;
|
|
18
|
+
dimensions = /* @__PURE__ */ new Map();
|
|
19
|
+
measures = /* @__PURE__ */ new Map();
|
|
20
|
+
/** Groups read elsewhere (in the page, for a worker), waiting to be put in order. */
|
|
21
|
+
given = /* @__PURE__ */ new Map();
|
|
22
|
+
constructor(data, options = {}) {
|
|
23
|
+
this.data = data;
|
|
24
|
+
this.length = isColumns(data) ? options.length ?? Math.max(0, ...Object.values(data).map((c) => c.length)) : data.length;
|
|
25
|
+
this.locale = options.locale;
|
|
26
|
+
this.collator = new Intl.Collator(options.locale, { numeric: true });
|
|
27
|
+
this.fields = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const field of options.fields ?? inferFields(data)) this.fields.set(field.name, this.resolve(field));
|
|
29
|
+
}
|
|
30
|
+
/** The record at `index`: the object given, or one made from the columns. */
|
|
31
|
+
record(index) {
|
|
32
|
+
const { data } = this;
|
|
33
|
+
if (!isColumns(data)) return data[index];
|
|
34
|
+
const row = {};
|
|
35
|
+
for (const name in data) row[name] = data[name][index];
|
|
36
|
+
return row;
|
|
37
|
+
}
|
|
38
|
+
/** How to read a field from record `i`. */
|
|
39
|
+
reader(field) {
|
|
40
|
+
const { data } = this;
|
|
41
|
+
const read = field.value;
|
|
42
|
+
if (read) {
|
|
43
|
+
if (!isColumns(data)) return (i) => read(data[i], i);
|
|
44
|
+
return (i) => read(this.record(i), i);
|
|
45
|
+
}
|
|
46
|
+
if (isColumns(data)) {
|
|
47
|
+
const column = data[field.name];
|
|
48
|
+
return column ? (i) => column[i] : () => void 0;
|
|
49
|
+
}
|
|
50
|
+
const name = field.name;
|
|
51
|
+
return (i) => data[i]?.[name];
|
|
52
|
+
}
|
|
53
|
+
/** Describes a field (or describes it again), as `fields` would have. */
|
|
54
|
+
define(field) {
|
|
55
|
+
this.fields.set(field.name, this.resolve(field));
|
|
56
|
+
}
|
|
57
|
+
/** Hands over groups read elsewhere, such as in the page for a worker. */
|
|
58
|
+
provide(name, column) {
|
|
59
|
+
this.given.set(name, column);
|
|
60
|
+
for (const key of this.dimensions.keys()) if (key === name || key.startsWith(`${name}\u0000`)) this.dimensions.delete(key);
|
|
61
|
+
}
|
|
62
|
+
/** Hands over a field's numbers read elsewhere. */
|
|
63
|
+
provideMeasure(name, values) {
|
|
64
|
+
this.measures.set(name, values);
|
|
65
|
+
}
|
|
66
|
+
/** The field called `name`; one is made up for keys the fields do not list. */
|
|
67
|
+
field(name) {
|
|
68
|
+
let field = this.fields.get(name);
|
|
69
|
+
if (!field) {
|
|
70
|
+
field = this.resolve({ name });
|
|
71
|
+
this.fields.set(name, field);
|
|
72
|
+
}
|
|
73
|
+
return field;
|
|
74
|
+
}
|
|
75
|
+
/** The field's values, one per record. */
|
|
76
|
+
values(name) {
|
|
77
|
+
const read = this.reader(this.field(name));
|
|
78
|
+
const out = Array.from({ length: this.length });
|
|
79
|
+
for (let i = 0; i < this.length; i++) out[i] = read(i);
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The field grouped by value, or by `by`: dates by a period, numbers in
|
|
84
|
+
* ranges. A grouping is worked out from the field's groups, not from the
|
|
85
|
+
* records, so it costs one pass over the group numbers.
|
|
86
|
+
*/
|
|
87
|
+
dimension(name, by) {
|
|
88
|
+
const key = by === void 0 ? name : `${name}\u0000${groupKey(by)}`;
|
|
89
|
+
let column = this.dimensions.get(key);
|
|
90
|
+
if (!column) {
|
|
91
|
+
column = by === void 0 ? this.encode(name) : this.group(name, by);
|
|
92
|
+
this.dimensions.set(key, column);
|
|
93
|
+
}
|
|
94
|
+
return column;
|
|
95
|
+
}
|
|
96
|
+
group(name, by) {
|
|
97
|
+
const base = this.dimension(name);
|
|
98
|
+
const bucketOf = bucketer(by);
|
|
99
|
+
const labels = [];
|
|
100
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
101
|
+
let empty = -1;
|
|
102
|
+
const map = new Uint32Array(base.labels.length);
|
|
103
|
+
base.labels.forEach((label, code) => {
|
|
104
|
+
const bucket = code === base.empty ? null : bucketOf(label);
|
|
105
|
+
let to;
|
|
106
|
+
if (bucket === null) {
|
|
107
|
+
if (empty < 0) {
|
|
108
|
+
empty = labels.length;
|
|
109
|
+
labels.push(null);
|
|
110
|
+
}
|
|
111
|
+
to = empty;
|
|
112
|
+
} else {
|
|
113
|
+
to = byValue.get(bucket);
|
|
114
|
+
if (to === void 0) {
|
|
115
|
+
to = labels.length;
|
|
116
|
+
labels.push(bucket);
|
|
117
|
+
byValue.set(bucket, to);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
map[code] = to;
|
|
121
|
+
});
|
|
122
|
+
const from = base.codes;
|
|
123
|
+
const codes = new Uint32Array(from.length);
|
|
124
|
+
for (let i = 0; i < from.length; i++) codes[i] = map[from[i]];
|
|
125
|
+
const find = (value) => isEmpty(value) ? empty : byValue.get(value) ?? -1;
|
|
126
|
+
return {
|
|
127
|
+
codes,
|
|
128
|
+
labels,
|
|
129
|
+
empty,
|
|
130
|
+
rank: this.rank(labels, { name }, find),
|
|
131
|
+
find
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** The field as numbers, `NaN` where a record has none. */
|
|
135
|
+
measure(name) {
|
|
136
|
+
let column = this.measures.get(name);
|
|
137
|
+
if (!column) {
|
|
138
|
+
column = run(this.measureSteps(name));
|
|
139
|
+
this.measures.set(name, column);
|
|
140
|
+
}
|
|
141
|
+
return column;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Reads a field's numbers a slice at a time, for reading a large field
|
|
145
|
+
* without holding up the page. The array is new: the source keeps none.
|
|
146
|
+
*/
|
|
147
|
+
*measureSteps(name, copy = false) {
|
|
148
|
+
const field = this.field(name);
|
|
149
|
+
const given = isColumns(this.data) && !field.value ? this.data[name] : void 0;
|
|
150
|
+
if (given instanceof Float64Array && given.length === this.length) return copy ? given.slice() : given;
|
|
151
|
+
const read = this.reader(field);
|
|
152
|
+
const column = new Float64Array(this.length);
|
|
153
|
+
for (let i = 0; i < this.length; i++) {
|
|
154
|
+
if (i % STEP === 0 && i) yield;
|
|
155
|
+
column[i] = toNumber(read(i));
|
|
156
|
+
}
|
|
157
|
+
return column;
|
|
158
|
+
}
|
|
159
|
+
resolve(field) {
|
|
160
|
+
return {
|
|
161
|
+
...field,
|
|
162
|
+
label: field.label ?? field.name,
|
|
163
|
+
type: field.type ?? this.guess(field)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
guess(field) {
|
|
167
|
+
const read = this.reader(field);
|
|
168
|
+
let seen = 0;
|
|
169
|
+
let type;
|
|
170
|
+
for (let i = 0; i < this.length && seen < SAMPLE; i++) {
|
|
171
|
+
const value = read(i);
|
|
172
|
+
if (isEmpty(value)) continue;
|
|
173
|
+
seen++;
|
|
174
|
+
const kind = typeOf(value);
|
|
175
|
+
if (type && type !== kind) return "string";
|
|
176
|
+
type = kind;
|
|
177
|
+
}
|
|
178
|
+
return type ?? "string";
|
|
179
|
+
}
|
|
180
|
+
encode(name) {
|
|
181
|
+
const field = this.field(name);
|
|
182
|
+
const { codes, labels, empty } = this.given.get(name) ?? run(this.encodeSteps(name));
|
|
183
|
+
const find = finder(labels, empty);
|
|
184
|
+
return {
|
|
185
|
+
codes,
|
|
186
|
+
labels,
|
|
187
|
+
empty,
|
|
188
|
+
rank: this.rank(labels, field, find),
|
|
189
|
+
find
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Reads a field's groups a slice at a time, for reading a large field
|
|
194
|
+
* without holding up the page: each record's group number and each
|
|
195
|
+
* group's value, not yet in order.
|
|
196
|
+
*/
|
|
197
|
+
*encodeSteps(name) {
|
|
198
|
+
const read = this.reader(this.field(name));
|
|
199
|
+
const n = this.length;
|
|
200
|
+
const codes = new Uint32Array(n);
|
|
201
|
+
const labels = [];
|
|
202
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
203
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
204
|
+
let empty = -1;
|
|
205
|
+
let last = void 0;
|
|
206
|
+
let lastCode = -1;
|
|
207
|
+
const codeOf = (value) => {
|
|
208
|
+
if (isEmpty(value)) {
|
|
209
|
+
if (empty < 0) {
|
|
210
|
+
empty = labels.length;
|
|
211
|
+
labels.push(null);
|
|
212
|
+
}
|
|
213
|
+
return empty;
|
|
214
|
+
}
|
|
215
|
+
if (value instanceof Date) {
|
|
216
|
+
const time = value.getTime();
|
|
217
|
+
let code = byTime.get(time);
|
|
218
|
+
if (code === void 0) {
|
|
219
|
+
code = labels.length;
|
|
220
|
+
labels.push(value);
|
|
221
|
+
byTime.set(time, code);
|
|
222
|
+
}
|
|
223
|
+
return code;
|
|
224
|
+
}
|
|
225
|
+
let code = byValue.get(value);
|
|
226
|
+
if (code === void 0) {
|
|
227
|
+
code = labels.length;
|
|
228
|
+
labels.push(value);
|
|
229
|
+
byValue.set(value, code);
|
|
230
|
+
}
|
|
231
|
+
return code;
|
|
232
|
+
};
|
|
233
|
+
for (let i = 0; i < n; i++) {
|
|
234
|
+
if (i % STEP === 0 && i) yield;
|
|
235
|
+
const value = read(i);
|
|
236
|
+
if (value === last && lastCode >= 0) {
|
|
237
|
+
codes[i] = lastCode;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
lastCode = codeOf(value);
|
|
241
|
+
last = value;
|
|
242
|
+
codes[i] = lastCode;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
codes,
|
|
246
|
+
labels,
|
|
247
|
+
empty
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/** Each group's place: the field's `order` first, then by value, the empty group last. */
|
|
251
|
+
rank(labels, field, find) {
|
|
252
|
+
const order = new Int32Array(labels.length).fill(-1);
|
|
253
|
+
field.order?.forEach((value, at) => {
|
|
254
|
+
const code = find(value);
|
|
255
|
+
if (code >= 0 && order[code] === -1) order[code] = at;
|
|
256
|
+
});
|
|
257
|
+
const codes = Array.from(labels.keys());
|
|
258
|
+
const { collator } = this;
|
|
259
|
+
codes.sort((a, b) => {
|
|
260
|
+
const oa = order[a];
|
|
261
|
+
const ob = order[b];
|
|
262
|
+
if (oa >= 0 || ob >= 0) {
|
|
263
|
+
if (oa < 0) return 1;
|
|
264
|
+
if (ob < 0) return -1;
|
|
265
|
+
return oa - ob;
|
|
266
|
+
}
|
|
267
|
+
return compareValues(labels[a], labels[b], collator);
|
|
268
|
+
});
|
|
269
|
+
const rank = new Uint32Array(labels.length);
|
|
270
|
+
codes.forEach((code, at) => rank[code] = at);
|
|
271
|
+
return rank;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
/** Empty: `null`, `undefined`, `''`, `NaN` and invalid dates all go in the empty group. */
|
|
275
|
+
var isEmpty = (value) => value === null || value === void 0 || value === "" || typeof value === "number" && Number.isNaN(value) || value instanceof Date && Number.isNaN(value.getTime());
|
|
276
|
+
var typeOf = (value) => typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : value instanceof Date ? "date" : "string";
|
|
277
|
+
var KIND = {
|
|
278
|
+
number: 0,
|
|
279
|
+
date: 1,
|
|
280
|
+
string: 2,
|
|
281
|
+
boolean: 3
|
|
282
|
+
};
|
|
283
|
+
/** Natural order: numbers and dates by size, text by the locale (digits as numbers), empty last. */
|
|
284
|
+
function compareValues(a, b, collator) {
|
|
285
|
+
const ea = isEmpty(a);
|
|
286
|
+
const eb = isEmpty(b);
|
|
287
|
+
if (ea || eb) return ea === eb ? 0 : ea ? 1 : -1;
|
|
288
|
+
const ka = KIND[typeOf(a)];
|
|
289
|
+
const kb = KIND[typeOf(b)];
|
|
290
|
+
if (ka !== kb) return ka - kb;
|
|
291
|
+
if (ka === 2) return collator.compare(a, b);
|
|
292
|
+
return Number(a) - Number(b);
|
|
293
|
+
}
|
|
294
|
+
/** A value as a number: numbers, dates and numeric text; `NaN` for anything else. */
|
|
295
|
+
function toNumber(value) {
|
|
296
|
+
if (typeof value === "number") return value;
|
|
297
|
+
if (value instanceof Date) return value.getTime();
|
|
298
|
+
if (typeof value === "string") {
|
|
299
|
+
const text = value.trim();
|
|
300
|
+
return text === "" ? NaN : Number(text);
|
|
301
|
+
}
|
|
302
|
+
return NaN;
|
|
303
|
+
}
|
|
304
|
+
/** Runs reading in slices to the end, at once. */
|
|
305
|
+
function run(steps) {
|
|
306
|
+
for (;;) {
|
|
307
|
+
const step = steps.next();
|
|
308
|
+
if (step.done) return step.value;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Finds a group by its value; dates by their time. */
|
|
312
|
+
function finder(labels, empty) {
|
|
313
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
314
|
+
const byTime = /* @__PURE__ */ new Map();
|
|
315
|
+
labels.forEach((label, code) => {
|
|
316
|
+
if (code === empty) return;
|
|
317
|
+
if (label instanceof Date) byTime.set(label.getTime(), code);
|
|
318
|
+
else byValue.set(label, code);
|
|
319
|
+
});
|
|
320
|
+
return (value) => {
|
|
321
|
+
if (isEmpty(value)) return empty;
|
|
322
|
+
if (value instanceof Date) return byTime.get(value.getTime()) ?? -1;
|
|
323
|
+
return byValue.get(value) ?? -1;
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/** The fields of records nobody described: every key of the first records, or every column. */
|
|
327
|
+
function inferFields(data) {
|
|
328
|
+
if (isColumns(data)) return Object.keys(data).map((name) => ({ name }));
|
|
329
|
+
const rows = data;
|
|
330
|
+
const names = /* @__PURE__ */ new Set();
|
|
331
|
+
for (let i = 0; i < Math.min(rows.length, SAMPLE); i++) {
|
|
332
|
+
const row = rows[i];
|
|
333
|
+
if (row && typeof row === "object") for (const key of Object.keys(row)) names.add(key);
|
|
334
|
+
}
|
|
335
|
+
return [...names].map((name) => ({ name }));
|
|
336
|
+
}
|
|
337
|
+
export { PivotSource, compareValues, finder, isColumns, isEmpty, run, toNumber };
|
|
338
|
+
|
|
339
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","names":[],"sources":["../../src/engine/source.ts"],"sourcesContent":["import { bucketer, groupKey, type PivotGroupBy } from './group'\nimport type { PivotField, PivotFieldType } from './types'\n\n/** A field's groups: each record's group number, and each group's value. */\nexport interface PivotDimensionColumn {\n /** Each record's group. */\n codes: Uint32Array\n /** Each group's value; `null` for the empty group. */\n labels: unknown[]\n /** The empty group's number, or -1 when every record has a value. */\n empty: number\n /** Each group's place in the field's natural order (its `order`, then sorted, empty last). */\n rank: Uint32Array\n /** The group of `value`, or -1 when no record has it. */\n find: (value: unknown) => number\n}\n\n/** A field with its type worked out. */\nexport interface PivotResolvedField<Row = unknown> extends PivotField<Row> {\n label: string\n type: PivotFieldType\n}\n\nexport interface PivotSourceOptions<Row> {\n fields?: readonly PivotField<Row>[]\n /** For sorting group labels; the runtime's locale when left out. */\n locale?: string\n}\n\n/**\n * Records given column by column: each field's values in one array (or\n * typed array), all of the same length. A million records take far less\n * memory this way than as a million objects.\n */\nexport type PivotColumns = Readonly<Record<string, ArrayLike<unknown>>>\n\n/** The records: an array of objects, or their columns. */\nexport type PivotData<Row = Record<string, unknown>> = readonly Row[] | PivotColumns\n\n/** A field's groups before they are put in order: what a worker is sent. */\nexport interface PivotEncodedColumn {\n codes: Uint32Array\n labels: unknown[]\n empty: number\n}\n\nconst SAMPLE = 100\n/** Records read between looks at the clock while reading in slices. */\nconst STEP = 4096\n\nexport const isColumns = (data: unknown): data is PivotColumns =>\n !!data && typeof data === 'object' && !Array.isArray(data)\n\n/**\n * The records, read field by field into typed arrays the first time each\n * field is used, and kept until the records change.\n */\nexport class PivotSource<Row = Record<string, unknown>> {\n /** The records as given: objects, or columns. */\n readonly data: PivotData<Row>\n readonly length: number\n readonly fields: Map<string, PivotResolvedField<Row>>\n readonly locale: string | undefined\n private collator: Intl.Collator\n private dimensions = new Map<string, PivotDimensionColumn>()\n private measures = new Map<string, Float64Array>()\n /** Groups read elsewhere (in the page, for a worker), waiting to be put in order. */\n private given = new Map<string, PivotEncodedColumn>()\n\n constructor(data: PivotData<Row>, options: PivotSourceOptions<Row> & { length?: number } = {}) {\n this.data = data\n this.length = isColumns(data)\n ? (options.length ?? Math.max(0, ...Object.values(data).map((c) => c.length)))\n : data.length\n this.locale = options.locale\n this.collator = new Intl.Collator(options.locale, { numeric: true })\n this.fields = new Map()\n for (const field of options.fields ?? inferFields(data)) {\n this.fields.set(field.name, this.resolve(field))\n }\n }\n\n /** The record at `index`: the object given, or one made from the columns. */\n record(index: number): Row {\n const { data } = this\n if (!isColumns(data)) return data[index]!\n const row: Record<string, unknown> = {}\n for (const name in data) row[name] = data[name]![index]\n return row as Row\n }\n\n /** How to read a field from record `i`. */\n private reader(field: PivotField<Row>): (i: number) => unknown {\n const { data } = this\n const read = field.value\n if (read) {\n if (!isColumns(data)) return (i) => read(data[i]!, i)\n return (i) => read(this.record(i), i)\n }\n if (isColumns(data)) {\n const column = data[field.name]\n return column ? (i) => column[i] : () => undefined\n }\n const name = field.name\n return (i) => (data[i] as Record<string, unknown> | undefined)?.[name]\n }\n\n /** Describes a field (or describes it again), as `fields` would have. */\n define(field: PivotField<Row>) {\n this.fields.set(field.name, this.resolve(field))\n }\n\n /** Hands over groups read elsewhere, such as in the page for a worker. */\n provide(name: string, column: PivotEncodedColumn) {\n this.given.set(name, column)\n for (const key of this.dimensions.keys())\n if (key === name || key.startsWith(`${name}\\u0000`)) this.dimensions.delete(key)\n }\n\n /** Hands over a field's numbers read elsewhere. */\n provideMeasure(name: string, values: Float64Array) {\n this.measures.set(name, values)\n }\n\n /** The field called `name`; one is made up for keys the fields do not list. */\n field(name: string): PivotResolvedField<Row> {\n let field = this.fields.get(name)\n if (!field) {\n field = this.resolve({ name })\n this.fields.set(name, field)\n }\n return field\n }\n\n /** The field's values, one per record. */\n values(name: string): unknown[] {\n const read = this.reader(this.field(name))\n const out: unknown[] = Array.from({ length: this.length })\n for (let i = 0; i < this.length; i++) out[i] = read(i)\n return out\n }\n\n /**\n * The field grouped by value, or by `by`: dates by a period, numbers in\n * ranges. A grouping is worked out from the field's groups, not from the\n * records, so it costs one pass over the group numbers.\n */\n dimension(name: string, by?: PivotGroupBy): PivotDimensionColumn {\n const key = by === undefined ? name : `${name}\\u0000${groupKey(by)}`\n let column = this.dimensions.get(key)\n if (!column) {\n column = by === undefined ? this.encode(name) : this.group(name, by)\n this.dimensions.set(key, column)\n }\n return column\n }\n\n private group(name: string, by: PivotGroupBy): PivotDimensionColumn {\n const base = this.dimension(name)\n const bucketOf = bucketer(by)\n const labels: unknown[] = []\n const byValue = new Map<unknown, number>()\n let empty = -1\n // each of the field's groups goes in one bucket\n const map = new Uint32Array(base.labels.length)\n base.labels.forEach((label, code) => {\n const bucket = code === base.empty ? null : bucketOf(label)\n let to: number | undefined\n if (bucket === null) {\n if (empty < 0) {\n empty = labels.length\n labels.push(null)\n }\n to = empty\n } else {\n to = byValue.get(bucket)\n if (to === undefined) {\n to = labels.length\n labels.push(bucket)\n byValue.set(bucket, to)\n }\n }\n map[code] = to\n })\n const from = base.codes\n const codes = new Uint32Array(from.length)\n for (let i = 0; i < from.length; i++) codes[i] = map[from[i]!]!\n const find = (value: unknown) => (isEmpty(value) ? empty : (byValue.get(value) ?? -1))\n // buckets sort by themselves; the field's `order` is for its own values\n return { codes, labels, empty, rank: this.rank(labels, { name }, find), find }\n }\n\n /** The field as numbers, `NaN` where a record has none. */\n measure(name: string): Float64Array {\n let column = this.measures.get(name)\n if (!column) {\n column = run(this.measureSteps(name))\n this.measures.set(name, column)\n }\n return column\n }\n\n /**\n * Reads a field's numbers a slice at a time, for reading a large field\n * without holding up the page. The array is new: the source keeps none.\n */\n *measureSteps(name: string, copy = false): Generator<void, Float64Array> {\n const field = this.field(name)\n const given = isColumns(this.data) && !field.value ? this.data[name] : undefined\n // a column of numbers is used as it is (or copied, to be sent away)\n if (given instanceof Float64Array && given.length === this.length)\n return copy ? given.slice() : given\n const read = this.reader(field)\n const column = new Float64Array(this.length)\n for (let i = 0; i < this.length; i++) {\n if (i % STEP === 0 && i) yield\n column[i] = toNumber(read(i))\n }\n return column\n }\n\n private resolve(field: PivotField<Row>): PivotResolvedField<Row> {\n return {\n ...field,\n label: field.label ?? field.name,\n type: field.type ?? this.guess(field),\n }\n }\n\n private guess(field: PivotField<Row>): PivotFieldType {\n const read = this.reader(field)\n let seen = 0\n let type: PivotFieldType | undefined\n for (let i = 0; i < this.length && seen < SAMPLE; i++) {\n const value = read(i)\n if (isEmpty(value)) continue\n seen++\n const kind = typeOf(value)\n if (type && type !== kind) return 'string'\n type = kind\n }\n return type ?? 'string'\n }\n\n private encode(name: string): PivotDimensionColumn {\n const field = this.field(name)\n const given = this.given.get(name)\n const { codes, labels, empty } = given ?? run(this.encodeSteps(name))\n const find = finder(labels, empty)\n return { codes, labels, empty, rank: this.rank(labels, field, find), find }\n }\n\n /**\n * Reads a field's groups a slice at a time, for reading a large field\n * without holding up the page: each record's group number and each\n * group's value, not yet in order.\n */\n *encodeSteps(name: string): Generator<void, PivotEncodedColumn> {\n const read = this.reader(this.field(name))\n const n = this.length\n const codes = new Uint32Array(n)\n const labels: unknown[] = []\n const byValue = new Map<unknown, number>()\n const byTime = new Map<number, number>()\n let empty = -1\n let last: unknown = undefined\n let lastCode = -1\n const codeOf = (value: unknown) => {\n if (isEmpty(value)) {\n if (empty < 0) {\n empty = labels.length\n labels.push(null)\n }\n return empty\n }\n if (value instanceof Date) {\n const time = value.getTime()\n let code = byTime.get(time)\n if (code === undefined) {\n code = labels.length\n labels.push(value)\n byTime.set(time, code)\n }\n return code\n }\n let code = byValue.get(value)\n if (code === undefined) {\n code = labels.length\n labels.push(value)\n byValue.set(value, code)\n }\n return code\n }\n for (let i = 0; i < n; i++) {\n if (i % STEP === 0 && i) yield\n const value = read(i)\n // sorted data repeats itself, so the last lookup is often the answer\n if (value === last && lastCode >= 0) {\n codes[i] = lastCode\n continue\n }\n lastCode = codeOf(value)\n last = value\n codes[i] = lastCode\n }\n return { codes, labels, empty }\n }\n\n /** Each group's place: the field's `order` first, then by value, the empty group last. */\n private rank(labels: unknown[], field: PivotField<Row>, find: (value: unknown) => number) {\n const order = new Int32Array(labels.length).fill(-1)\n field.order?.forEach((value, at) => {\n const code = find(value)\n if (code >= 0 && order[code] === -1) order[code] = at\n })\n const codes = Array.from(labels.keys())\n const { collator } = this\n codes.sort((a, b) => {\n const oa = order[a]!\n const ob = order[b]!\n if (oa >= 0 || ob >= 0) {\n if (oa < 0) return 1\n if (ob < 0) return -1\n return oa - ob\n }\n return compareValues(labels[a], labels[b], collator)\n })\n const rank = new Uint32Array(labels.length)\n codes.forEach((code, at) => (rank[code] = at))\n return rank\n }\n}\n\n/** Empty: `null`, `undefined`, `''`, `NaN` and invalid dates all go in the empty group. */\nexport const isEmpty = (value: unknown) =>\n value === null ||\n value === undefined ||\n value === '' ||\n (typeof value === 'number' && Number.isNaN(value)) ||\n (value instanceof Date && Number.isNaN(value.getTime()))\n\nconst typeOf = (value: unknown): PivotFieldType =>\n typeof value === 'number'\n ? 'number'\n : typeof value === 'boolean'\n ? 'boolean'\n : value instanceof Date\n ? 'date'\n : 'string'\n\nconst KIND: Record<PivotFieldType, number> = { number: 0, date: 1, string: 2, boolean: 3 }\n\n/** Natural order: numbers and dates by size, text by the locale (digits as numbers), empty last. */\nexport function compareValues(a: unknown, b: unknown, collator: Intl.Collator) {\n const ea = isEmpty(a)\n const eb = isEmpty(b)\n if (ea || eb) return ea === eb ? 0 : ea ? 1 : -1\n const ka = KIND[typeOf(a)]\n const kb = KIND[typeOf(b)]\n if (ka !== kb) return ka - kb\n if (ka === 2) return collator.compare(a as string, b as string)\n return Number(a) - Number(b)\n}\n\n/** A value as a number: numbers, dates and numeric text; `NaN` for anything else. */\nexport function toNumber(value: unknown) {\n if (typeof value === 'number') return value\n if (value instanceof Date) return value.getTime()\n if (typeof value === 'string') {\n const text = value.trim()\n return text === '' ? Number.NaN : Number(text)\n }\n return Number.NaN\n}\n\n/** Runs reading in slices to the end, at once. */\nexport function run<T>(steps: Generator<void, T>): T {\n for (;;) {\n const step = steps.next()\n if (step.done) return step.value\n }\n}\n\n/** Finds a group by its value; dates by their time. */\nexport function finder(labels: readonly unknown[], empty: number) {\n const byValue = new Map<unknown, number>()\n const byTime = new Map<number, number>()\n labels.forEach((label, code) => {\n if (code === empty) return\n if (label instanceof Date) byTime.set(label.getTime(), code)\n else byValue.set(label, code)\n })\n return (value: unknown) => {\n if (isEmpty(value)) return empty\n if (value instanceof Date) return byTime.get(value.getTime()) ?? -1\n return byValue.get(value) ?? -1\n }\n}\n\n/** The fields of records nobody described: every key of the first records, or every column. */\nfunction inferFields<Row>(data: PivotData<Row>): PivotField<Row>[] {\n if (isColumns(data)) return Object.keys(data).map((name) => ({ name }))\n const rows = data\n const names = new Set<string>()\n for (let i = 0; i < Math.min(rows.length, SAMPLE); i++) {\n const row = rows[i]\n if (row && typeof row === 'object') for (const key of Object.keys(row)) names.add(key)\n }\n return [...names].map((name) => ({ name }))\n}\n"],"mappings":"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"}
|