@sentropic/dataviz-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregate.d.ts +38 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +97 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/crossfilter.d.ts +48 -0
- package/dist/crossfilter.d.ts.map +1 -0
- package/dist/crossfilter.js +66 -0
- package/dist/crossfilter.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +58 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +119 -0
- package/dist/model.js.map +1 -0
- package/dist/serialize.d.ts +31 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +84 -0
- package/dist/serialize.js.map +1 -0
- package/dist/store.d.ts +86 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +152 -0
- package/dist/store.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grouping and aggregation primitives.
|
|
3
|
+
*
|
|
4
|
+
* All functions are pure and side-effect free. Aggregations guard against
|
|
5
|
+
* non-finite numbers (NaN, Infinity) and return a sensible neutral value for
|
|
6
|
+
* an empty input.
|
|
7
|
+
*/
|
|
8
|
+
import type { Aggregation, Measure, Row } from './model.js';
|
|
9
|
+
/**
|
|
10
|
+
* Partition rows by the distinct value of `dimensionId`.
|
|
11
|
+
*
|
|
12
|
+
* The returned map preserves first-seen key order. Keys are stringified so the
|
|
13
|
+
* map is stable regardless of the underlying cell type (number/boolean/string).
|
|
14
|
+
* A `null`/`undefined` cell groups under the literal key `"null"`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function groupBy(rows: Row[], dimensionId: string): Map<string, Row[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Aggregate the `measure` over `rows` using the measure's aggregation.
|
|
19
|
+
*
|
|
20
|
+
* - `count` counts *rows* (not non-null values) — it returns `rows.length`.
|
|
21
|
+
* - `sum`/`avg`/`min`/`max` ignore non-finite / non-numeric cells.
|
|
22
|
+
* - An empty input (or one with no numeric cells) returns the neutral value:
|
|
23
|
+
* `0` for sum/avg/count, `0` for min/max with no data.
|
|
24
|
+
*/
|
|
25
|
+
export declare function aggregate(rows: Row[], measure: Measure): number;
|
|
26
|
+
/** Lower-level helper: aggregate a raw value array by aggregation kind. */
|
|
27
|
+
export declare function aggregateValues(values: number[], aggregation: Aggregation, rowCount?: number): number;
|
|
28
|
+
/** Extract the finite numeric values of `field` across `rows`. */
|
|
29
|
+
export declare function extractNumbers(rows: Row[], field: string): number[];
|
|
30
|
+
/**
|
|
31
|
+
* Group rows by a dimension then aggregate a measure within each group.
|
|
32
|
+
* Returns one `{ key, value }` per distinct dimension value, in first-seen order.
|
|
33
|
+
*/
|
|
34
|
+
export declare function groupAggregate(rows: Row[], dimensionId: string, measure: Measure): Array<{
|
|
35
|
+
key: string;
|
|
36
|
+
value: number;
|
|
37
|
+
}>;
|
|
38
|
+
//# sourceMappingURL=aggregate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAa5E;AAiBD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAE/D;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EAAE,EAChB,WAAW,EAAE,WAAW,EACxB,QAAQ,SAAgB,GACvB,MAAM,CAkBR;AAED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAOnE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,GAAG,EAAE,EACX,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,GACf,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAOvC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grouping and aggregation primitives.
|
|
3
|
+
*
|
|
4
|
+
* All functions are pure and side-effect free. Aggregations guard against
|
|
5
|
+
* non-finite numbers (NaN, Infinity) and return a sensible neutral value for
|
|
6
|
+
* an empty input.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Partition rows by the distinct value of `dimensionId`.
|
|
10
|
+
*
|
|
11
|
+
* The returned map preserves first-seen key order. Keys are stringified so the
|
|
12
|
+
* map is stable regardless of the underlying cell type (number/boolean/string).
|
|
13
|
+
* A `null`/`undefined` cell groups under the literal key `"null"`.
|
|
14
|
+
*/
|
|
15
|
+
export function groupBy(rows, dimensionId) {
|
|
16
|
+
const groups = new Map();
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const raw = row[dimensionId];
|
|
19
|
+
const key = raw == null ? 'null' : String(raw);
|
|
20
|
+
const bucket = groups.get(key);
|
|
21
|
+
if (bucket) {
|
|
22
|
+
bucket.push(row);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
groups.set(key, [row]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return groups;
|
|
29
|
+
}
|
|
30
|
+
/** Coerce a cell to a finite number, or `undefined` if it is not numeric. */
|
|
31
|
+
function toFinite(value) {
|
|
32
|
+
if (typeof value === 'number') {
|
|
33
|
+
return Number.isFinite(value) ? value : undefined;
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === 'boolean') {
|
|
36
|
+
return value ? 1 : 0;
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
39
|
+
const n = Number(value);
|
|
40
|
+
return Number.isFinite(n) ? n : undefined;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Aggregate the `measure` over `rows` using the measure's aggregation.
|
|
46
|
+
*
|
|
47
|
+
* - `count` counts *rows* (not non-null values) — it returns `rows.length`.
|
|
48
|
+
* - `sum`/`avg`/`min`/`max` ignore non-finite / non-numeric cells.
|
|
49
|
+
* - An empty input (or one with no numeric cells) returns the neutral value:
|
|
50
|
+
* `0` for sum/avg/count, `0` for min/max with no data.
|
|
51
|
+
*/
|
|
52
|
+
export function aggregate(rows, measure) {
|
|
53
|
+
return aggregateValues(extractNumbers(rows, measure.id), measure.aggregation, rows.length);
|
|
54
|
+
}
|
|
55
|
+
/** Lower-level helper: aggregate a raw value array by aggregation kind. */
|
|
56
|
+
export function aggregateValues(values, aggregation, rowCount = values.length) {
|
|
57
|
+
switch (aggregation) {
|
|
58
|
+
case 'count':
|
|
59
|
+
return rowCount;
|
|
60
|
+
case 'sum':
|
|
61
|
+
return values.reduce((acc, v) => acc + v, 0);
|
|
62
|
+
case 'avg':
|
|
63
|
+
return values.length === 0 ? 0 : values.reduce((acc, v) => acc + v, 0) / values.length;
|
|
64
|
+
case 'min':
|
|
65
|
+
return values.length === 0 ? 0 : values.reduce((acc, v) => (v < acc ? v : acc), values[0]);
|
|
66
|
+
case 'max':
|
|
67
|
+
return values.length === 0 ? 0 : values.reduce((acc, v) => (v > acc ? v : acc), values[0]);
|
|
68
|
+
default: {
|
|
69
|
+
// Exhaustiveness guard — unreachable for a valid Aggregation.
|
|
70
|
+
const _never = aggregation;
|
|
71
|
+
throw new Error(`Unknown aggregation: ${String(_never)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Extract the finite numeric values of `field` across `rows`. */
|
|
76
|
+
export function extractNumbers(rows, field) {
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const row of rows) {
|
|
79
|
+
const n = toFinite(row[field]);
|
|
80
|
+
if (n !== undefined)
|
|
81
|
+
out.push(n);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Group rows by a dimension then aggregate a measure within each group.
|
|
87
|
+
* Returns one `{ key, value }` per distinct dimension value, in first-seen order.
|
|
88
|
+
*/
|
|
89
|
+
export function groupAggregate(rows, dimensionId, measure) {
|
|
90
|
+
const groups = groupBy(rows, dimensionId);
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const [key, bucket] of groups) {
|
|
93
|
+
out.push({ key, value: aggregate(bucket, measure) });
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=aggregate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,IAAW,EAAE,WAAmB;IACtD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAiB,CAAC;IACxC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;QAC7B,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6EAA6E;AAC7E,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAW,EAAE,OAAgB;IACrD,OAAO,eAAe,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7F,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,eAAe,CAC7B,MAAgB,EAChB,WAAwB,EACxB,QAAQ,GAAG,MAAM,CAAC,MAAM;IAExB,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,OAAO;YACV,OAAO,QAAQ,CAAC;QAClB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/C,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;QACzF,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC;QAC9F,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC;QAC9F,OAAO,CAAC,CAAC,CAAC;YACR,8DAA8D;YAC9D,MAAM,MAAM,GAAU,WAAW,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAAC,IAAW,EAAE,KAAa;IACvD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAW,EACX,WAAmB,EACnB,OAAgB;IAEhB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC1C,MAAM,GAAG,GAA0C,EAAE,CAAC;IACtD,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-filter / brushing-and-linking.
|
|
3
|
+
*
|
|
4
|
+
* The {@link CrossfilterGraph} declares, for each *source* view, the set of
|
|
5
|
+
* *target* views its selection should filter. This is the classic crossfilter
|
|
6
|
+
* pattern: selecting a bar in view A narrows views B and C, but A itself is
|
|
7
|
+
* **not** filtered by its own selection (so you can keep refining it).
|
|
8
|
+
*
|
|
9
|
+
* Selections live in the store as `viewId -> selected keys`. Each view must
|
|
10
|
+
* also declare which data field (dimension id) its keys refer to, so a
|
|
11
|
+
* selection can be turned into a row predicate. That mapping is part of the
|
|
12
|
+
* graph (`views[viewId].field`).
|
|
13
|
+
*/
|
|
14
|
+
import type { Row } from './model.js';
|
|
15
|
+
import type { DashboardState } from './store.js';
|
|
16
|
+
/** Declaration of a single view participating in cross-filtering. */
|
|
17
|
+
export interface CrossfilterView {
|
|
18
|
+
/** The dimension id this view's selection keys refer to. */
|
|
19
|
+
field: string;
|
|
20
|
+
/**
|
|
21
|
+
* The view ids this view's selection filters. If omitted, defaults to
|
|
22
|
+
* "every other view" (a fully-linked dashboard). An empty array means the
|
|
23
|
+
* view's selection affects no other view.
|
|
24
|
+
*/
|
|
25
|
+
affects?: string[];
|
|
26
|
+
}
|
|
27
|
+
/** Declarative scope: which view filters which. */
|
|
28
|
+
export interface CrossfilterGraph {
|
|
29
|
+
views: Record<string, CrossfilterView>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the set of source views whose selection should be applied to
|
|
33
|
+
* `targetViewId`, honouring the declared scope and self-exclusion.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sourcesFor(graph: CrossfilterGraph, targetViewId: string): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Compute the rows visible to a given view.
|
|
38
|
+
*
|
|
39
|
+
* Order of operations:
|
|
40
|
+
* 1. Apply all dimension filters from `state.filters` (global slicers).
|
|
41
|
+
* 2. Apply the selection of every *other* view that, per the graph, affects
|
|
42
|
+
* this view. The view's own selection is ignored (self-exclusion).
|
|
43
|
+
*
|
|
44
|
+
* When `viewId` is omitted, only the global dimension filters are applied
|
|
45
|
+
* (useful for "what's the unscoped, globally-filtered dataset").
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyCrossfilter(state: DashboardState, data: readonly Row[], viewId?: string, graph?: CrossfilterGraph): Row[];
|
|
48
|
+
//# sourceMappingURL=crossfilter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossfilter.d.ts","sourceRoot":"","sources":["../src/crossfilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAQ,GAAG,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACxC;AAOD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAYlF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,SAAS,GAAG,EAAE,EACpB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,gBAAgB,GACvB,GAAG,EAAE,CAiBP"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-filter / brushing-and-linking.
|
|
3
|
+
*
|
|
4
|
+
* The {@link CrossfilterGraph} declares, for each *source* view, the set of
|
|
5
|
+
* *target* views its selection should filter. This is the classic crossfilter
|
|
6
|
+
* pattern: selecting a bar in view A narrows views B and C, but A itself is
|
|
7
|
+
* **not** filtered by its own selection (so you can keep refining it).
|
|
8
|
+
*
|
|
9
|
+
* Selections live in the store as `viewId -> selected keys`. Each view must
|
|
10
|
+
* also declare which data field (dimension id) its keys refer to, so a
|
|
11
|
+
* selection can be turned into a row predicate. That mapping is part of the
|
|
12
|
+
* graph (`views[viewId].field`).
|
|
13
|
+
*/
|
|
14
|
+
import { applyFilters } from './store.js';
|
|
15
|
+
/** Stringify a cell the same way selections/groupBy keys are produced. */
|
|
16
|
+
function keyOf(value) {
|
|
17
|
+
return value == null ? 'null' : String(value);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the set of source views whose selection should be applied to
|
|
21
|
+
* `targetViewId`, honouring the declared scope and self-exclusion.
|
|
22
|
+
*/
|
|
23
|
+
export function sourcesFor(graph, targetViewId) {
|
|
24
|
+
const allViews = Object.keys(graph.views);
|
|
25
|
+
const sources = [];
|
|
26
|
+
for (const sourceId of allViews) {
|
|
27
|
+
if (sourceId === targetViewId)
|
|
28
|
+
continue; // a view never filters itself
|
|
29
|
+
const view = graph.views[sourceId];
|
|
30
|
+
const affects = view.affects ?? allViews.filter((v) => v !== sourceId);
|
|
31
|
+
if (affects.includes(targetViewId)) {
|
|
32
|
+
sources.push(sourceId);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return sources;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Compute the rows visible to a given view.
|
|
39
|
+
*
|
|
40
|
+
* Order of operations:
|
|
41
|
+
* 1. Apply all dimension filters from `state.filters` (global slicers).
|
|
42
|
+
* 2. Apply the selection of every *other* view that, per the graph, affects
|
|
43
|
+
* this view. The view's own selection is ignored (self-exclusion).
|
|
44
|
+
*
|
|
45
|
+
* When `viewId` is omitted, only the global dimension filters are applied
|
|
46
|
+
* (useful for "what's the unscoped, globally-filtered dataset").
|
|
47
|
+
*/
|
|
48
|
+
export function applyCrossfilter(state, data, viewId, graph) {
|
|
49
|
+
let rows = applyFilters(state, data);
|
|
50
|
+
if (!viewId || !graph)
|
|
51
|
+
return rows;
|
|
52
|
+
const sources = sourcesFor(graph, viewId);
|
|
53
|
+
for (const sourceId of sources) {
|
|
54
|
+
const selectedKeys = state.selections[sourceId];
|
|
55
|
+
if (!selectedKeys || selectedKeys.length === 0)
|
|
56
|
+
continue;
|
|
57
|
+
const sourceView = graph.views[sourceId];
|
|
58
|
+
if (!sourceView)
|
|
59
|
+
continue;
|
|
60
|
+
const field = sourceView.field;
|
|
61
|
+
const wanted = new Set(selectedKeys);
|
|
62
|
+
rows = rows.filter((row) => wanted.has(keyOf(row[field] ?? null)));
|
|
63
|
+
}
|
|
64
|
+
return rows;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=crossfilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossfilter.js","sourceRoot":"","sources":["../src/crossfilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmB1C,0EAA0E;AAC1E,SAAS,KAAK,CAAC,KAAW;IACxB,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAuB,EAAE,YAAoB;IACtE,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC;QAChC,IAAI,QAAQ,KAAK,YAAY;YAAE,SAAS,CAAC,8BAA8B;QACvE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;QACvE,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAqB,EACrB,IAAoB,EACpB,MAAe,EACf,KAAwB;IAExB,IAAI,IAAI,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAErC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1C,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;QAC/B,MAAM,YAAY,GAAG,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACzD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;QACrC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sentropic/dataviz-core
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic engine for the shared inter-view state of a BI dashboard:
|
|
5
|
+
* data model, observable immutable store, cross-filter graph, aggregation and
|
|
6
|
+
* bookmark serialisation. Zero framework / design-system dependencies.
|
|
7
|
+
*/
|
|
8
|
+
export type { Cell, Row, DimensionType, Aggregation, Dimension, Measure, DataModel, } from './model.js';
|
|
9
|
+
export { isDimensionType, isAggregation, isDimension, isMeasure, isDataModel, validateModel, assertModel, findDimension, findMeasure, } from './model.js';
|
|
10
|
+
export type { FilterSpec, FilterState, SelectionState, DashboardState, FilterInput, DashboardStoreConfig, DashboardStore, } from './store.js';
|
|
11
|
+
export { createDashboardStore, specToPredicate, applyFilters } from './store.js';
|
|
12
|
+
export type { CrossfilterView, CrossfilterGraph } from './crossfilter.js';
|
|
13
|
+
export { sourcesFor, applyCrossfilter } from './crossfilter.js';
|
|
14
|
+
export { groupBy, aggregate, aggregateValues, extractNumbers, groupAggregate, } from './aggregate.js';
|
|
15
|
+
export { isFilterSpec, serializeFilters, deserializeFilters } from './serialize.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,YAAY,EACV,IAAI,EACJ,GAAG,EACH,aAAa,EACb,WAAW,EACX,SAAS,EACT,OAAO,EACP,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,aAAa,EACb,WAAW,EACX,SAAS,EACT,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGjF,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,EACL,OAAO,EACP,SAAS,EACT,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sentropic/dataviz-core
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic engine for the shared inter-view state of a BI dashboard:
|
|
5
|
+
* data model, observable immutable store, cross-filter graph, aggregation and
|
|
6
|
+
* bookmark serialisation. Zero framework / design-system dependencies.
|
|
7
|
+
*/
|
|
8
|
+
export { isDimensionType, isAggregation, isDimension, isMeasure, isDataModel, validateModel, assertModel, findDimension, findMeasure, } from './model.js';
|
|
9
|
+
export { createDashboardStore, specToPredicate, applyFilters } from './store.js';
|
|
10
|
+
export { sourcesFor, applyCrossfilter } from './crossfilter.js';
|
|
11
|
+
// Aggregate
|
|
12
|
+
export { groupBy, aggregate, aggregateValues, extractNumbers, groupAggregate, } from './aggregate.js';
|
|
13
|
+
// Serialize
|
|
14
|
+
export { isFilterSpec, serializeFilters, deserializeFilters } from './serialize.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,OAAO,EACL,eAAe,EACf,aAAa,EACb,WAAW,EACX,SAAS,EACT,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIjF,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEhE,YAAY;AACZ,OAAO,EACL,OAAO,EACP,SAAS,EACT,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,gBAAgB,CAAC;AAExB,YAAY;AACZ,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data model primitives for dataviz.
|
|
3
|
+
*
|
|
4
|
+
* A {@link DataModel} describes the *shape* of a dataset in BI terms:
|
|
5
|
+
* which columns are dimensions (things you slice/group by) and which are
|
|
6
|
+
* measures (things you aggregate). It carries no data itself.
|
|
7
|
+
*/
|
|
8
|
+
/** A single record. Values are JSON-serialisable scalars. */
|
|
9
|
+
export type Cell = string | number | boolean | null;
|
|
10
|
+
/** A row of data keyed by dimension/measure id. */
|
|
11
|
+
export type Row = Record<string, Cell>;
|
|
12
|
+
/** How a dimension's values are distributed. */
|
|
13
|
+
export type DimensionType = 'discrete' | 'continuous';
|
|
14
|
+
/** Aggregation applied to a measure when rolling up rows. */
|
|
15
|
+
export type Aggregation = 'sum' | 'avg' | 'min' | 'max' | 'count';
|
|
16
|
+
/** A column you slice, group or drill by. */
|
|
17
|
+
export interface Dimension {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
type: DimensionType;
|
|
21
|
+
/**
|
|
22
|
+
* Optional drill-down path, ordered from coarse to fine, e.g.
|
|
23
|
+
* `['country', 'region', 'city']`. Each entry is a dimension id.
|
|
24
|
+
*/
|
|
25
|
+
hierarchy?: string[];
|
|
26
|
+
}
|
|
27
|
+
/** A column you aggregate. */
|
|
28
|
+
export interface Measure {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
aggregation: Aggregation;
|
|
32
|
+
}
|
|
33
|
+
/** The full declarative model of a dataset. */
|
|
34
|
+
export interface DataModel {
|
|
35
|
+
dimensions: Dimension[];
|
|
36
|
+
measures: Measure[];
|
|
37
|
+
}
|
|
38
|
+
export declare function isDimensionType(value: unknown): value is DimensionType;
|
|
39
|
+
export declare function isAggregation(value: unknown): value is Aggregation;
|
|
40
|
+
export declare function isDimension(value: unknown): value is Dimension;
|
|
41
|
+
export declare function isMeasure(value: unknown): value is Measure;
|
|
42
|
+
export declare function isDataModel(value: unknown): value is DataModel;
|
|
43
|
+
/**
|
|
44
|
+
* Validate a {@link DataModel} and return the list of problems found.
|
|
45
|
+
* An empty array means the model is valid.
|
|
46
|
+
*
|
|
47
|
+
* Checks structural validity, duplicate ids (a dimension and a measure may
|
|
48
|
+
* not share an id), and that every hierarchy entry references a known
|
|
49
|
+
* dimension id.
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateModel(model: DataModel): string[];
|
|
52
|
+
/** Throwing variant of {@link validateModel}. Returns the model on success. */
|
|
53
|
+
export declare function assertModel(model: DataModel): DataModel;
|
|
54
|
+
/** Look up a dimension by id, or `undefined`. */
|
|
55
|
+
export declare function findDimension(model: DataModel, id: string): Dimension | undefined;
|
|
56
|
+
/** Look up a measure by id, or `undefined`. */
|
|
57
|
+
export declare function findMeasure(model: DataModel, id: string): Measure | undefined;
|
|
58
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,6DAA6D;AAC7D,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEpD,mDAAmD;AACnD,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAEvC,gDAAgD;AAChD,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,YAAY,CAAC;AAEtD,6DAA6D;AAC7D,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAElE,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,8BAA8B;AAC9B,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,+CAA+C;AAC/C,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEtE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAMD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAU9D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAK1D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAK9D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CA+CxD;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAMvD;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAEjF;AAED,+CAA+C;AAC/C,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAE7E"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data model primitives for dataviz.
|
|
3
|
+
*
|
|
4
|
+
* A {@link DataModel} describes the *shape* of a dataset in BI terms:
|
|
5
|
+
* which columns are dimensions (things you slice/group by) and which are
|
|
6
|
+
* measures (things you aggregate). It carries no data itself.
|
|
7
|
+
*/
|
|
8
|
+
const DIMENSION_TYPES = ['discrete', 'continuous'];
|
|
9
|
+
const AGGREGATIONS = ['sum', 'avg', 'min', 'max', 'count'];
|
|
10
|
+
export function isDimensionType(value) {
|
|
11
|
+
return typeof value === 'string' && DIMENSION_TYPES.includes(value);
|
|
12
|
+
}
|
|
13
|
+
export function isAggregation(value) {
|
|
14
|
+
return typeof value === 'string' && AGGREGATIONS.includes(value);
|
|
15
|
+
}
|
|
16
|
+
function isNonEmptyString(value) {
|
|
17
|
+
return typeof value === 'string' && value.length > 0;
|
|
18
|
+
}
|
|
19
|
+
export function isDimension(value) {
|
|
20
|
+
if (typeof value !== 'object' || value === null)
|
|
21
|
+
return false;
|
|
22
|
+
const d = value;
|
|
23
|
+
if (!isNonEmptyString(d.id) || !isNonEmptyString(d.label))
|
|
24
|
+
return false;
|
|
25
|
+
if (!isDimensionType(d.type))
|
|
26
|
+
return false;
|
|
27
|
+
if (d.hierarchy !== undefined) {
|
|
28
|
+
if (!Array.isArray(d.hierarchy))
|
|
29
|
+
return false;
|
|
30
|
+
if (!d.hierarchy.every((h) => isNonEmptyString(h)))
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
export function isMeasure(value) {
|
|
36
|
+
if (typeof value !== 'object' || value === null)
|
|
37
|
+
return false;
|
|
38
|
+
const m = value;
|
|
39
|
+
if (!isNonEmptyString(m.id) || !isNonEmptyString(m.label))
|
|
40
|
+
return false;
|
|
41
|
+
return isAggregation(m.aggregation);
|
|
42
|
+
}
|
|
43
|
+
export function isDataModel(value) {
|
|
44
|
+
if (typeof value !== 'object' || value === null)
|
|
45
|
+
return false;
|
|
46
|
+
const m = value;
|
|
47
|
+
if (!Array.isArray(m.dimensions) || !Array.isArray(m.measures))
|
|
48
|
+
return false;
|
|
49
|
+
return m.dimensions.every(isDimension) && m.measures.every(isMeasure);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Validate a {@link DataModel} and return the list of problems found.
|
|
53
|
+
* An empty array means the model is valid.
|
|
54
|
+
*
|
|
55
|
+
* Checks structural validity, duplicate ids (a dimension and a measure may
|
|
56
|
+
* not share an id), and that every hierarchy entry references a known
|
|
57
|
+
* dimension id.
|
|
58
|
+
*/
|
|
59
|
+
export function validateModel(model) {
|
|
60
|
+
const errors = [];
|
|
61
|
+
if (!Array.isArray(model.dimensions)) {
|
|
62
|
+
errors.push('model.dimensions must be an array');
|
|
63
|
+
}
|
|
64
|
+
if (!Array.isArray(model.measures)) {
|
|
65
|
+
errors.push('model.measures must be an array');
|
|
66
|
+
}
|
|
67
|
+
if (errors.length > 0)
|
|
68
|
+
return errors;
|
|
69
|
+
const seen = new Set();
|
|
70
|
+
const dimensionIds = new Set();
|
|
71
|
+
for (const dim of model.dimensions) {
|
|
72
|
+
if (!isDimension(dim)) {
|
|
73
|
+
errors.push(`invalid dimension: ${JSON.stringify(dim)}`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (seen.has(dim.id)) {
|
|
77
|
+
errors.push(`duplicate id: ${dim.id}`);
|
|
78
|
+
}
|
|
79
|
+
seen.add(dim.id);
|
|
80
|
+
dimensionIds.add(dim.id);
|
|
81
|
+
}
|
|
82
|
+
for (const measure of model.measures) {
|
|
83
|
+
if (!isMeasure(measure)) {
|
|
84
|
+
errors.push(`invalid measure: ${JSON.stringify(measure)}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (seen.has(measure.id)) {
|
|
88
|
+
errors.push(`duplicate id: ${measure.id}`);
|
|
89
|
+
}
|
|
90
|
+
seen.add(measure.id);
|
|
91
|
+
}
|
|
92
|
+
for (const dim of model.dimensions) {
|
|
93
|
+
if (!isDimension(dim) || !dim.hierarchy)
|
|
94
|
+
continue;
|
|
95
|
+
for (const ref of dim.hierarchy) {
|
|
96
|
+
if (!dimensionIds.has(ref)) {
|
|
97
|
+
errors.push(`dimension ${dim.id} hierarchy references unknown dimension: ${ref}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return errors;
|
|
102
|
+
}
|
|
103
|
+
/** Throwing variant of {@link validateModel}. Returns the model on success. */
|
|
104
|
+
export function assertModel(model) {
|
|
105
|
+
const errors = validateModel(model);
|
|
106
|
+
if (errors.length > 0) {
|
|
107
|
+
throw new Error(`Invalid DataModel:\n- ${errors.join('\n- ')}`);
|
|
108
|
+
}
|
|
109
|
+
return model;
|
|
110
|
+
}
|
|
111
|
+
/** Look up a dimension by id, or `undefined`. */
|
|
112
|
+
export function findDimension(model, id) {
|
|
113
|
+
return model.dimensions.find((d) => d.id === id);
|
|
114
|
+
}
|
|
115
|
+
/** Look up a measure by id, or `undefined`. */
|
|
116
|
+
export function findMeasure(model, id) {
|
|
117
|
+
return model.measures.find((m) => m.id === id);
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bookmark / URL serialisation of the filter state.
|
|
3
|
+
*
|
|
4
|
+
* `serializeFilters` produces a compact, URL-safe string; `deserializeFilters`
|
|
5
|
+
* parses it back into a {@link FilterState}, dropping any entry that does not
|
|
6
|
+
* reference a known dimension or whose spec is malformed. The round-trip of a
|
|
7
|
+
* valid state is guaranteed.
|
|
8
|
+
*
|
|
9
|
+
* Encoding: JSON of the filter map, then `encodeURIComponent`. Plain and
|
|
10
|
+
* robust; the result is safe to drop into a query string. (We avoid base64 so
|
|
11
|
+
* the value stays human-inspectable and dependency-free.)
|
|
12
|
+
*/
|
|
13
|
+
import type { DataModel } from './model.js';
|
|
14
|
+
import type { FilterSpec, FilterState } from './store.js';
|
|
15
|
+
/** Validate that an unknown value is a well-formed {@link FilterSpec}. */
|
|
16
|
+
export declare function isFilterSpec(value: unknown): value is FilterSpec;
|
|
17
|
+
/**
|
|
18
|
+
* Serialise the filter portion of a state to a URL-safe string.
|
|
19
|
+
* Selections are intentionally *not* serialised here — they are ephemeral
|
|
20
|
+
* brushing state, not part of a bookmark. (Bookmark = filters.)
|
|
21
|
+
*/
|
|
22
|
+
export declare function serializeFilters(state: {
|
|
23
|
+
filters: FilterState;
|
|
24
|
+
}): string;
|
|
25
|
+
/**
|
|
26
|
+
* Parse a serialised filter string back to a {@link FilterState}, validating
|
|
27
|
+
* against the model. Unknown dimensions and malformed specs are dropped. Any
|
|
28
|
+
* parse error yields an empty state (never throws).
|
|
29
|
+
*/
|
|
30
|
+
export declare function deserializeFilters(encoded: string, model: DataModel): FilterState;
|
|
31
|
+
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM1D,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAehE;AAaD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GAAG,MAAM,CAMxE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,WAAW,CAiBjF"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bookmark / URL serialisation of the filter state.
|
|
3
|
+
*
|
|
4
|
+
* `serializeFilters` produces a compact, URL-safe string; `deserializeFilters`
|
|
5
|
+
* parses it back into a {@link FilterState}, dropping any entry that does not
|
|
6
|
+
* reference a known dimension or whose spec is malformed. The round-trip of a
|
|
7
|
+
* valid state is guaranteed.
|
|
8
|
+
*
|
|
9
|
+
* Encoding: JSON of the filter map, then `encodeURIComponent`. Plain and
|
|
10
|
+
* robust; the result is safe to drop into a query string. (We avoid base64 so
|
|
11
|
+
* the value stays human-inspectable and dependency-free.)
|
|
12
|
+
*/
|
|
13
|
+
import { findDimension } from './model.js';
|
|
14
|
+
function isStringArray(value) {
|
|
15
|
+
return Array.isArray(value) && value.every((v) => typeof v === 'string');
|
|
16
|
+
}
|
|
17
|
+
/** Validate that an unknown value is a well-formed {@link FilterSpec}. */
|
|
18
|
+
export function isFilterSpec(value) {
|
|
19
|
+
if (typeof value !== 'object' || value === null)
|
|
20
|
+
return false;
|
|
21
|
+
const spec = value;
|
|
22
|
+
switch (spec.kind) {
|
|
23
|
+
case 'include':
|
|
24
|
+
case 'exclude':
|
|
25
|
+
return isStringArray(spec.values);
|
|
26
|
+
case 'range':
|
|
27
|
+
return ((spec.min === undefined || typeof spec.min === 'number') &&
|
|
28
|
+
(spec.max === undefined || typeof spec.max === 'number'));
|
|
29
|
+
default:
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Normalise a spec into a minimal, stable object for serialisation. */
|
|
34
|
+
function normaliseSpec(spec) {
|
|
35
|
+
if (spec.kind === 'include' || spec.kind === 'exclude') {
|
|
36
|
+
return { kind: spec.kind, values: [...spec.values] };
|
|
37
|
+
}
|
|
38
|
+
const out = { kind: 'range' };
|
|
39
|
+
if (spec.min !== undefined)
|
|
40
|
+
out.min = spec.min;
|
|
41
|
+
if (spec.max !== undefined)
|
|
42
|
+
out.max = spec.max;
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Serialise the filter portion of a state to a URL-safe string.
|
|
47
|
+
* Selections are intentionally *not* serialised here — they are ephemeral
|
|
48
|
+
* brushing state, not part of a bookmark. (Bookmark = filters.)
|
|
49
|
+
*/
|
|
50
|
+
export function serializeFilters(state) {
|
|
51
|
+
const normalised = {};
|
|
52
|
+
for (const key of Object.keys(state.filters).sort()) {
|
|
53
|
+
normalised[key] = normaliseSpec(state.filters[key]);
|
|
54
|
+
}
|
|
55
|
+
return encodeURIComponent(JSON.stringify(normalised));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse a serialised filter string back to a {@link FilterState}, validating
|
|
59
|
+
* against the model. Unknown dimensions and malformed specs are dropped. Any
|
|
60
|
+
* parse error yields an empty state (never throws).
|
|
61
|
+
*/
|
|
62
|
+
export function deserializeFilters(encoded, model) {
|
|
63
|
+
if (!encoded)
|
|
64
|
+
return {};
|
|
65
|
+
let raw;
|
|
66
|
+
try {
|
|
67
|
+
raw = JSON.parse(decodeURIComponent(encoded));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
if (typeof raw !== 'object' || raw === null)
|
|
73
|
+
return {};
|
|
74
|
+
const result = {};
|
|
75
|
+
for (const [dimensionId, spec] of Object.entries(raw)) {
|
|
76
|
+
if (!findDimension(model, dimensionId))
|
|
77
|
+
continue; // unknown dimension
|
|
78
|
+
if (!isFilterSpec(spec))
|
|
79
|
+
continue; // malformed spec
|
|
80
|
+
result[dimensionId] = normaliseSpec(spec);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.js","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard store: an immutable, observable container for the shared
|
|
3
|
+
* inter-view state (filters + selections).
|
|
4
|
+
*
|
|
5
|
+
* Every mutation produces a brand-new frozen {@link DashboardState} (a different
|
|
6
|
+
* object reference) and then notifies subscribers. The state is fully
|
|
7
|
+
* serialisable: filters are stored as declarative {@link FilterSpec}s rather
|
|
8
|
+
* than as opaque functions, so they can be bookmarked / round-tripped through a
|
|
9
|
+
* URL. A runtime predicate is derived from the spec on demand.
|
|
10
|
+
*/
|
|
11
|
+
import type { Cell, DataModel, Row } from './model.js';
|
|
12
|
+
import type { CrossfilterGraph } from './crossfilter.js';
|
|
13
|
+
/**
|
|
14
|
+
* A declarative, serialisable description of a filter on one dimension.
|
|
15
|
+
*
|
|
16
|
+
* - `include`: keep rows whose cell (stringified) is in `values`.
|
|
17
|
+
* - `exclude`: drop rows whose cell (stringified) is in `values`.
|
|
18
|
+
* - `range`: keep rows whose numeric cell is within `[min, max]` (inclusive,
|
|
19
|
+
* bounds optional).
|
|
20
|
+
*/
|
|
21
|
+
export type FilterSpec = {
|
|
22
|
+
kind: 'include';
|
|
23
|
+
values: string[];
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'exclude';
|
|
26
|
+
values: string[];
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'range';
|
|
29
|
+
min?: number;
|
|
30
|
+
max?: number;
|
|
31
|
+
};
|
|
32
|
+
/** Map of dimensionId -> filter spec. Serialisable. */
|
|
33
|
+
export type FilterState = Record<string, FilterSpec>;
|
|
34
|
+
/** Map of viewId -> selected keys. Serialisable. */
|
|
35
|
+
export type SelectionState = Record<string, string[]>;
|
|
36
|
+
/** Immutable snapshot of the dashboard's shared state. */
|
|
37
|
+
export interface DashboardState {
|
|
38
|
+
readonly filters: FilterState;
|
|
39
|
+
readonly selections: SelectionState;
|
|
40
|
+
}
|
|
41
|
+
/** Accepted argument to {@link DashboardStore.setFilter}. */
|
|
42
|
+
export type FilterInput = FilterSpec | ((value: Cell, row: Row) => boolean);
|
|
43
|
+
export interface DashboardStoreConfig {
|
|
44
|
+
model: DataModel;
|
|
45
|
+
data: Row[];
|
|
46
|
+
crossfilter?: CrossfilterGraph;
|
|
47
|
+
}
|
|
48
|
+
export interface DashboardStore {
|
|
49
|
+
/** Frozen snapshot of the current state. */
|
|
50
|
+
getState(): DashboardState;
|
|
51
|
+
/** The data model (read-only). */
|
|
52
|
+
readonly model: DataModel;
|
|
53
|
+
/** The raw, unfiltered data (read-only copy). */
|
|
54
|
+
readonly data: readonly Row[];
|
|
55
|
+
/** The cross-filter graph, if any. */
|
|
56
|
+
readonly crossfilter?: CrossfilterGraph;
|
|
57
|
+
/** Subscribe to changes. Returns an unsubscribe function. */
|
|
58
|
+
subscribe(listener: () => void): () => void;
|
|
59
|
+
/** Set (or replace) the filter on a dimension. */
|
|
60
|
+
setFilter(dimensionId: string, spec: FilterSpec): void;
|
|
61
|
+
/** Remove the filter on a dimension. No-op if absent. */
|
|
62
|
+
clearFilter(dimensionId: string): void;
|
|
63
|
+
/** Toggle a single selected key for a view. */
|
|
64
|
+
toggleSelection(viewId: string, key: string): void;
|
|
65
|
+
/** Clear all selections for a view. No-op if absent. */
|
|
66
|
+
clearSelection(viewId: string): void;
|
|
67
|
+
/** Reset all filters and selections. */
|
|
68
|
+
clearAll(): void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create an observable, immutable dashboard store.
|
|
72
|
+
*
|
|
73
|
+
* The store starts empty. Mutators copy-on-write: each produces a new frozen
|
|
74
|
+
* state object and synchronously notifies listeners. Listeners are invoked even
|
|
75
|
+
* if a snapshot is logically identical, only when an actual change happened
|
|
76
|
+
* (no-op mutations such as clearing an absent filter do not notify).
|
|
77
|
+
*/
|
|
78
|
+
export declare function createDashboardStore(config: DashboardStoreConfig): DashboardStore;
|
|
79
|
+
/** Compile a {@link FilterSpec} into a runtime predicate over a cell. */
|
|
80
|
+
export declare function specToPredicate(spec: FilterSpec): (value: Cell) => boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Apply only the *filter* portion of a state to a row set (ignoring the
|
|
83
|
+
* cross-filter scope). Use {@link applyCrossfilter} for per-view results.
|
|
84
|
+
*/
|
|
85
|
+
export declare function applyFilters(state: DashboardState, data: readonly Row[]): Row[];
|
|
86
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,uDAAuD;AACvD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAErD,oDAAoD;AACpD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAEtD,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;CACrC;AAED,6DAA6D;AAC7D,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC;AAE5E,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,QAAQ,IAAI,cAAc,CAAC;IAC3B,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IACxC,6DAA6D;IAC7D,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,kDAAkD;IAClD,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACvD,yDAAyD;IACzD,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,+CAA+C;IAC/C,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,wDAAwD;IACxD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,wCAAwC;IACxC,QAAQ,IAAI,IAAI,CAAC;CAClB;AAiCD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CAwEjF;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO,CAsB1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,GAAG,GAAG,EAAE,CAO/E"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard store: an immutable, observable container for the shared
|
|
3
|
+
* inter-view state (filters + selections).
|
|
4
|
+
*
|
|
5
|
+
* Every mutation produces a brand-new frozen {@link DashboardState} (a different
|
|
6
|
+
* object reference) and then notifies subscribers. The state is fully
|
|
7
|
+
* serialisable: filters are stored as declarative {@link FilterSpec}s rather
|
|
8
|
+
* than as opaque functions, so they can be bookmarked / round-tripped through a
|
|
9
|
+
* URL. A runtime predicate is derived from the spec on demand.
|
|
10
|
+
*/
|
|
11
|
+
/** Build the empty initial state, frozen. */
|
|
12
|
+
function emptyState() {
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
filters: Object.freeze({}),
|
|
15
|
+
selections: Object.freeze({}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Deep-freeze a filter spec so snapshots cannot be mutated in place. */
|
|
19
|
+
function freezeSpec(spec) {
|
|
20
|
+
if (spec.kind === 'include' || spec.kind === 'exclude') {
|
|
21
|
+
return Object.freeze({ kind: spec.kind, values: Object.freeze([...spec.values]) });
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({ kind: 'range', min: spec.min, max: spec.max });
|
|
24
|
+
}
|
|
25
|
+
function freezeState(filters, selections) {
|
|
26
|
+
const frozenFilters = {};
|
|
27
|
+
for (const [id, spec] of Object.entries(filters)) {
|
|
28
|
+
frozenFilters[id] = freezeSpec(spec);
|
|
29
|
+
}
|
|
30
|
+
const frozenSelections = {};
|
|
31
|
+
for (const [id, keys] of Object.entries(selections)) {
|
|
32
|
+
frozenSelections[id] = Object.freeze([...keys]);
|
|
33
|
+
}
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
filters: Object.freeze(frozenFilters),
|
|
36
|
+
selections: Object.freeze(frozenSelections),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create an observable, immutable dashboard store.
|
|
41
|
+
*
|
|
42
|
+
* The store starts empty. Mutators copy-on-write: each produces a new frozen
|
|
43
|
+
* state object and synchronously notifies listeners. Listeners are invoked even
|
|
44
|
+
* if a snapshot is logically identical, only when an actual change happened
|
|
45
|
+
* (no-op mutations such as clearing an absent filter do not notify).
|
|
46
|
+
*/
|
|
47
|
+
export function createDashboardStore(config) {
|
|
48
|
+
let state = emptyState();
|
|
49
|
+
const listeners = new Set();
|
|
50
|
+
// Defensive copy: the store owns its data; callers can't mutate it later.
|
|
51
|
+
const data = Object.freeze([...config.data]);
|
|
52
|
+
function notify() {
|
|
53
|
+
for (const listener of [...listeners]) {
|
|
54
|
+
listener();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function commit(next) {
|
|
58
|
+
state = next;
|
|
59
|
+
notify();
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
model: config.model,
|
|
63
|
+
data,
|
|
64
|
+
crossfilter: config.crossfilter,
|
|
65
|
+
getState() {
|
|
66
|
+
return state;
|
|
67
|
+
},
|
|
68
|
+
subscribe(listener) {
|
|
69
|
+
listeners.add(listener);
|
|
70
|
+
return () => {
|
|
71
|
+
listeners.delete(listener);
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
setFilter(dimensionId, spec) {
|
|
75
|
+
const nextFilters = { ...state.filters, [dimensionId]: spec };
|
|
76
|
+
commit(freezeState(nextFilters, state.selections));
|
|
77
|
+
},
|
|
78
|
+
clearFilter(dimensionId) {
|
|
79
|
+
if (!(dimensionId in state.filters))
|
|
80
|
+
return;
|
|
81
|
+
const nextFilters = { ...state.filters };
|
|
82
|
+
delete nextFilters[dimensionId];
|
|
83
|
+
commit(freezeState(nextFilters, state.selections));
|
|
84
|
+
},
|
|
85
|
+
toggleSelection(viewId, key) {
|
|
86
|
+
const current = state.selections[viewId] ?? [];
|
|
87
|
+
const has = current.includes(key);
|
|
88
|
+
const nextKeys = has ? current.filter((k) => k !== key) : [...current, key];
|
|
89
|
+
const nextSelections = { ...state.selections };
|
|
90
|
+
if (nextKeys.length === 0) {
|
|
91
|
+
delete nextSelections[viewId];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
nextSelections[viewId] = nextKeys;
|
|
95
|
+
}
|
|
96
|
+
commit(freezeState(state.filters, nextSelections));
|
|
97
|
+
},
|
|
98
|
+
clearSelection(viewId) {
|
|
99
|
+
if (!(viewId in state.selections))
|
|
100
|
+
return;
|
|
101
|
+
const nextSelections = { ...state.selections };
|
|
102
|
+
delete nextSelections[viewId];
|
|
103
|
+
commit(freezeState(state.filters, nextSelections));
|
|
104
|
+
},
|
|
105
|
+
clearAll() {
|
|
106
|
+
const hasState = Object.keys(state.filters).length > 0 || Object.keys(state.selections).length > 0;
|
|
107
|
+
if (!hasState)
|
|
108
|
+
return;
|
|
109
|
+
commit(emptyState());
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Compile a {@link FilterSpec} into a runtime predicate over a cell. */
|
|
114
|
+
export function specToPredicate(spec) {
|
|
115
|
+
switch (spec.kind) {
|
|
116
|
+
case 'include': {
|
|
117
|
+
const set = new Set(spec.values);
|
|
118
|
+
return (value) => set.has(value == null ? 'null' : String(value));
|
|
119
|
+
}
|
|
120
|
+
case 'exclude': {
|
|
121
|
+
const set = new Set(spec.values);
|
|
122
|
+
return (value) => !set.has(value == null ? 'null' : String(value));
|
|
123
|
+
}
|
|
124
|
+
case 'range': {
|
|
125
|
+
const { min, max } = spec;
|
|
126
|
+
return (value) => {
|
|
127
|
+
if (value == null || value === '')
|
|
128
|
+
return false;
|
|
129
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
130
|
+
if (!Number.isFinite(n))
|
|
131
|
+
return false;
|
|
132
|
+
if (min !== undefined && n < min)
|
|
133
|
+
return false;
|
|
134
|
+
if (max !== undefined && n > max)
|
|
135
|
+
return false;
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Apply only the *filter* portion of a state to a row set (ignoring the
|
|
143
|
+
* cross-filter scope). Use {@link applyCrossfilter} for per-view results.
|
|
144
|
+
*/
|
|
145
|
+
export function applyFilters(state, data) {
|
|
146
|
+
const entries = Object.entries(state.filters);
|
|
147
|
+
if (entries.length === 0)
|
|
148
|
+
return [...data];
|
|
149
|
+
const predicates = entries.map(([dimensionId, spec]) => [dimensionId, specToPredicate(spec)]);
|
|
150
|
+
return data.filter((row) => predicates.every(([dimId, pred]) => pred(row[dimId] ?? null)));
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sentropic/dataviz-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic engine for shared inter-view dashboard state: data model, observable immutable store, cross-filter, aggregation, bookmark serialisation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rhanka/dataviz.git",
|
|
10
|
+
"directory": "packages/dataviz-core"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.json",
|
|
26
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
27
|
+
"test": "vitest run src"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|