@uwdata/mosaic-core 0.9.0 → 0.10.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/mosaic-core.js +5768 -4799
- package/dist/mosaic-core.min.js +8 -8
- package/package.json +7 -7
- package/src/Coordinator.js +194 -49
- package/src/DataCubeIndexer.js +247 -142
- package/src/MosaicClient.js +3 -3
- package/src/QueryConsolidator.js +2 -2
- package/src/QueryManager.js +5 -3
- package/src/Selection.js +31 -10
- package/src/SelectionClause.js +22 -8
- package/src/index.js +10 -1
- package/src/util/AsyncDispatch.js +15 -5
- package/src/util/index-columns.js +10 -8
- package/src/util/query-result.js +41 -8
- package/src/util/to-data-columns.js +71 -0
- package/src/FilterGroup.js +0 -81
package/src/SelectionClause.js
CHANGED
|
@@ -24,7 +24,10 @@ import { MosaicClient } from './MosaicClient.js';
|
|
|
24
24
|
* cross-filtering contexts.
|
|
25
25
|
* @returns {SelectionClause} The generated selection clause.
|
|
26
26
|
*/
|
|
27
|
-
export function
|
|
27
|
+
export function clausePoint(field, value, {
|
|
28
|
+
source,
|
|
29
|
+
clients = source ? new Set([source]) : undefined
|
|
30
|
+
}) {
|
|
28
31
|
/** @type {SQLExpression | null} */
|
|
29
32
|
const predicate = value !== undefined
|
|
30
33
|
? isNotDistinct(field, literal(value))
|
|
@@ -50,7 +53,10 @@ export function point(field, value, { source, clients = undefined }) {
|
|
|
50
53
|
* cross-filtering contexts.
|
|
51
54
|
* @returns {SelectionClause} The generated selection clause.
|
|
52
55
|
*/
|
|
53
|
-
export function
|
|
56
|
+
export function clausePoints(fields, value, {
|
|
57
|
+
source,
|
|
58
|
+
clients = source ? new Set([source]) : undefined
|
|
59
|
+
}) {
|
|
54
60
|
/** @type {SQLExpression | null} */
|
|
55
61
|
let predicate = null;
|
|
56
62
|
if (value) {
|
|
@@ -83,13 +89,17 @@ export function points(fields, value, { source, clients = undefined }) {
|
|
|
83
89
|
* @param {number} [options.pixelSize=1] The interactive pixel size.
|
|
84
90
|
* @returns {SelectionClause} The generated selection clause.
|
|
85
91
|
*/
|
|
86
|
-
export function
|
|
87
|
-
source,
|
|
92
|
+
export function clauseInterval(field, value, {
|
|
93
|
+
source,
|
|
94
|
+
clients = source ? new Set([source]) : undefined,
|
|
95
|
+
bin,
|
|
96
|
+
scale,
|
|
97
|
+
pixelSize = 1
|
|
88
98
|
}) {
|
|
89
99
|
/** @type {SQLExpression | null} */
|
|
90
100
|
const predicate = value != null ? isBetween(field, value) : null;
|
|
91
101
|
/** @type {import('./util/selection-types.js').IntervalMetadata} */
|
|
92
|
-
const meta = { type: 'interval', scales: [scale], bin, pixelSize };
|
|
102
|
+
const meta = { type: 'interval', scales: scale && [scale], bin, pixelSize };
|
|
93
103
|
return { meta, source, clients, value, predicate };
|
|
94
104
|
}
|
|
95
105
|
|
|
@@ -108,8 +118,12 @@ export function interval(field, value, {
|
|
|
108
118
|
* @param {number} [options.pixelSize=1] The interactive pixel size.
|
|
109
119
|
* @returns {SelectionClause} The generated selection clause.
|
|
110
120
|
*/
|
|
111
|
-
export function
|
|
112
|
-
source,
|
|
121
|
+
export function clauseIntervals(fields, value, {
|
|
122
|
+
source,
|
|
123
|
+
clients = source ? new Set([source]) : undefined,
|
|
124
|
+
bin,
|
|
125
|
+
scales = [],
|
|
126
|
+
pixelSize = 1
|
|
113
127
|
}) {
|
|
114
128
|
/** @type {SQLExpression | null} */
|
|
115
129
|
const predicate = value != null
|
|
@@ -135,7 +149,7 @@ const MATCH_METHODS = { contains, prefix, suffix, regexp: regexp_matches };
|
|
|
135
149
|
* text matching method to use. Defaults to `'contains'`.
|
|
136
150
|
* @returns {SelectionClause} The generated selection clause.
|
|
137
151
|
*/
|
|
138
|
-
export function
|
|
152
|
+
export function clauseMatch(field, value, {
|
|
139
153
|
source, clients = undefined, method = 'contains'
|
|
140
154
|
}) {
|
|
141
155
|
let fn = MATCH_METHODS[method];
|
package/src/index.js
CHANGED
|
@@ -3,18 +3,27 @@ export { Coordinator, coordinator } from './Coordinator.js';
|
|
|
3
3
|
export { Selection, isSelection } from './Selection.js';
|
|
4
4
|
export { Param, isParam } from './Param.js';
|
|
5
5
|
export { Priority } from './QueryManager.js';
|
|
6
|
-
export { point, points, interval, intervals, match } from './SelectionClause.js';
|
|
7
6
|
|
|
8
7
|
export { restConnector } from './connectors/rest.js';
|
|
9
8
|
export { socketConnector } from './connectors/socket.js';
|
|
10
9
|
export { wasmConnector } from './connectors/wasm.js';
|
|
11
10
|
|
|
11
|
+
export {
|
|
12
|
+
clauseInterval,
|
|
13
|
+
clauseIntervals,
|
|
14
|
+
clausePoint,
|
|
15
|
+
clausePoints,
|
|
16
|
+
clauseMatch
|
|
17
|
+
} from './SelectionClause.js';
|
|
18
|
+
|
|
12
19
|
export {
|
|
13
20
|
isArrowTable,
|
|
14
21
|
convertArrowArrayType,
|
|
15
22
|
convertArrowValue,
|
|
16
23
|
convertArrowColumn
|
|
17
24
|
} from './util/convert-arrow.js'
|
|
25
|
+
|
|
18
26
|
export { distinct } from './util/distinct.js';
|
|
19
27
|
export { synchronizer } from './util/synchronizer.js';
|
|
20
28
|
export { throttle } from './util/throttle.js';
|
|
29
|
+
export { toDataColumns } from './util/to-data-columns.js'
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Event dispatcher supporting asynchronous updates.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* of the same type.
|
|
2
|
+
* Event dispatcher supporting asynchronous updates. If an event handler
|
|
3
|
+
* callback returns a Promise, the dispatcher waits for all such Promises
|
|
4
|
+
* to settle before dispatching future events of the same type.
|
|
6
5
|
*/
|
|
7
6
|
export class AsyncDispatch {
|
|
8
7
|
|
|
@@ -63,7 +62,7 @@ export class AsyncDispatch {
|
|
|
63
62
|
* queue of unemitted event values prior to enqueueing a new value.
|
|
64
63
|
* This default implementation simply returns null, indicating that
|
|
65
64
|
* any other unemitted event values should be dropped (that is, all
|
|
66
|
-
* queued events are filtered)
|
|
65
|
+
* queued events are filtered).
|
|
67
66
|
* @param {string} type The event type.
|
|
68
67
|
* @param {*} value The new event value that will be enqueued.
|
|
69
68
|
* @returns {(value: *) => boolean|null} A dispatch queue filter
|
|
@@ -83,6 +82,17 @@ export class AsyncDispatch {
|
|
|
83
82
|
entry?.queue.clear();
|
|
84
83
|
}
|
|
85
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Returns a promise that resolves when any pending updates complete for
|
|
87
|
+
* the event of the given type currently being processed. The Promise will
|
|
88
|
+
* resolve immediately if the queue for the given event type is empty.
|
|
89
|
+
* @param {string} type The event type to wait for.
|
|
90
|
+
* @returns {Promise} A pending event promise.
|
|
91
|
+
*/
|
|
92
|
+
async pending(type) {
|
|
93
|
+
await this._callbacks.get(type)?.pending;
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
/**
|
|
87
97
|
* Emit an event value to listeners for the given event type.
|
|
88
98
|
* If a previous emit has not yet resolved, the event value
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { Query, agg, sql } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { MosaicClient } from '../MosaicClient.js';
|
|
3
3
|
|
|
4
|
-
export const NO_INDEX = { from: NaN };
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
5
|
* Determine data cube index columns for a given Mosaic client.
|
|
8
6
|
* @param {MosaicClient} client The Mosaic client.
|
|
9
7
|
* @returns An object with necessary column data to generate data
|
|
10
|
-
* cube index columns, null if
|
|
11
|
-
*
|
|
8
|
+
* cube index columns, or null if the client is not indexable or
|
|
9
|
+
* the client query contains an invalid or unsupported expression.
|
|
12
10
|
*/
|
|
13
11
|
export function indexColumns(client) {
|
|
14
|
-
if (!client.filterIndexable) return
|
|
12
|
+
if (!client.filterIndexable) return null;
|
|
15
13
|
const q = client.query();
|
|
16
14
|
const from = getBaseTable(q);
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
|
|
16
|
+
// bail if no base table or the query is not analyzable
|
|
17
|
+
if (typeof from !== 'string' || !q.select) return null;
|
|
19
18
|
|
|
20
19
|
const aggr = []; // list of output aggregate columns
|
|
21
20
|
const dims = []; // list of grouping dimension columns
|
|
@@ -127,11 +126,14 @@ export function indexColumns(client) {
|
|
|
127
126
|
|
|
128
127
|
// otherwise, check if dimension
|
|
129
128
|
default:
|
|
130
|
-
if (
|
|
129
|
+
if (!aggregate) dims.push(as);
|
|
131
130
|
else return null; // unsupported aggregate
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
|
|
134
|
+
// bail if the query has no aggregates
|
|
135
|
+
if (!aggr.length) return null;
|
|
136
|
+
|
|
135
137
|
return { from, dims, aggr, aux };
|
|
136
138
|
}
|
|
137
139
|
|
package/src/util/query-result.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A query result Promise that can allows external callers
|
|
3
|
+
* to resolve or reject the Promise.
|
|
4
|
+
*/
|
|
5
|
+
export class QueryResult extends Promise {
|
|
6
|
+
/**
|
|
7
|
+
* Create a new query result Promise.
|
|
8
|
+
*/
|
|
9
|
+
constructor() {
|
|
10
|
+
let resolve;
|
|
11
|
+
let reject;
|
|
12
|
+
super((r, e) => {
|
|
13
|
+
resolve = r;
|
|
14
|
+
reject = e;
|
|
15
|
+
});
|
|
16
|
+
this._resolve = resolve;
|
|
17
|
+
this._reject = reject;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the result Promise with the provided value.
|
|
22
|
+
* @param {*} value The result value.
|
|
23
|
+
* @returns {this}
|
|
24
|
+
*/
|
|
25
|
+
fulfill(value) {
|
|
26
|
+
this._resolve(value);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Rejects the result Promise with the provided error.
|
|
32
|
+
* @param {*} error The error value.
|
|
33
|
+
* @returns {this}
|
|
34
|
+
*/
|
|
35
|
+
reject(error) {
|
|
36
|
+
this._reject(error);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
9
39
|
}
|
|
40
|
+
|
|
41
|
+
// necessary to make Promise subclass act like a Promise
|
|
42
|
+
QueryResult.prototype.constructor = Promise;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { convertArrowColumn, isArrowTable } from './convert-arrow.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray
|
|
5
|
+
* | Int16Array | Uint16Array | Int32Array | Uint32Array
|
|
6
|
+
* | Float32Array | Float64Array
|
|
7
|
+
* } Arrayish - an Array or TypedArray
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {
|
|
12
|
+
* | { numRows: number, columns: Record<string,Arrayish> }
|
|
13
|
+
* | { numRows: number, values: Arrayish; }
|
|
14
|
+
* } DataColumns
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert input data to a set of column arrays.
|
|
19
|
+
* @param {any} data The input data.
|
|
20
|
+
* @returns {DataColumns} An object with named column arrays.
|
|
21
|
+
*/
|
|
22
|
+
export function toDataColumns(data) {
|
|
23
|
+
return isArrowTable(data)
|
|
24
|
+
? arrowToColumns(data)
|
|
25
|
+
: arrayToColumns(data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert an Arrow table to a set of column arrays.
|
|
30
|
+
* @param {import('apache-arrow').Table} data An Apache Arrow Table.
|
|
31
|
+
* @returns {DataColumns} An object with named column arrays.
|
|
32
|
+
*/
|
|
33
|
+
function arrowToColumns(data) {
|
|
34
|
+
const { numRows, numCols, schema: { fields } } = data;
|
|
35
|
+
const columns = {};
|
|
36
|
+
|
|
37
|
+
for (let col = 0; col < numCols; ++col) {
|
|
38
|
+
const name = fields[col].name;
|
|
39
|
+
if (columns[name]) {
|
|
40
|
+
console.warn(`Redundant column name "${name}". Skipping...`);
|
|
41
|
+
} else {
|
|
42
|
+
columns[name] = convertArrowColumn(data.getChildAt(col));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { numRows, columns };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert an array of values to a set of column arrays.
|
|
51
|
+
* If the array values are objects, build out named columns.
|
|
52
|
+
* We use the keys of the first object as the column names.
|
|
53
|
+
* Otherwise, use a special "values" array.
|
|
54
|
+
* @param {object[]} data An array of data objects.
|
|
55
|
+
* @returns {DataColumns} An object with named column arrays.
|
|
56
|
+
*/
|
|
57
|
+
function arrayToColumns(data) {
|
|
58
|
+
const numRows = data.length;
|
|
59
|
+
if (typeof data[0] === 'object') {
|
|
60
|
+
const names = numRows ? Object.keys(data[0]) : [];
|
|
61
|
+
const columns = {};
|
|
62
|
+
if (names.length > 0) {
|
|
63
|
+
names.forEach(name => {
|
|
64
|
+
columns[name] = data.map(d => d[name]);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { numRows, columns };
|
|
68
|
+
} else {
|
|
69
|
+
return { numRows, values: data };
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/FilterGroup.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { Coordinator } from './Coordinator.js';
|
|
2
|
-
import { DataCubeIndexer } from './DataCubeIndexer.js';
|
|
3
|
-
import { MosaicClient } from './MosaicClient.js';
|
|
4
|
-
import { Selection } from './Selection.js';
|
|
5
|
-
|
|
6
|
-
export class FilterGroup {
|
|
7
|
-
/**
|
|
8
|
-
* @param {Coordinator} coordinator The Mosaic coordinator.
|
|
9
|
-
* @param {Selection} selection The shared filter selection.
|
|
10
|
-
* @param {object|boolean} index Boolean flag or options hash for
|
|
11
|
-
* a data cube indexer. Falsy values disable indexing.
|
|
12
|
-
*/
|
|
13
|
-
constructor(coordinator, selection, index = true) {
|
|
14
|
-
this.mc = coordinator;
|
|
15
|
-
this.selection = selection;
|
|
16
|
-
/** @type {Set<MosaicClient>} */
|
|
17
|
-
this.clients = new Set();
|
|
18
|
-
/** @type {DataCubeIndexer | null} */
|
|
19
|
-
this.indexer = null;
|
|
20
|
-
this.index(index);
|
|
21
|
-
|
|
22
|
-
const { value, activate } = this.handlers = {
|
|
23
|
-
value: () => this.update(),
|
|
24
|
-
activate: clause => { this.indexer?.index(this.clients, clause); }
|
|
25
|
-
};
|
|
26
|
-
selection.addEventListener('value', value);
|
|
27
|
-
selection.addEventListener('activate', activate);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
finalize() {
|
|
31
|
-
const { value, activate } = this.handlers;
|
|
32
|
-
this.selection.removeEventListener('value', value);
|
|
33
|
-
this.selection.removeEventListener('activate', activate);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
index(state) {
|
|
37
|
-
const { selection } = this;
|
|
38
|
-
const { resolver } = selection;
|
|
39
|
-
this.indexer = state && (resolver.single || !resolver.union)
|
|
40
|
-
? new DataCubeIndexer(this.mc, { ...state, selection })
|
|
41
|
-
: null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
reset() {
|
|
45
|
-
this.indexer?.reset();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
add(client) {
|
|
49
|
-
(this.clients = new Set(this.clients)).add(client);
|
|
50
|
-
return this;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
remove(client) {
|
|
54
|
-
if (this.clients.has(client)) {
|
|
55
|
-
(this.clients = new Set(this.clients)).delete(client);
|
|
56
|
-
}
|
|
57
|
-
return this;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Internal method to process a selection update.
|
|
62
|
-
* The return value is passed as a selection callback value.
|
|
63
|
-
* @returns {Promise} A Promise that resolves when the update completes.
|
|
64
|
-
*/
|
|
65
|
-
update() {
|
|
66
|
-
const { mc, indexer, clients, selection } = this;
|
|
67
|
-
const hasIndex = indexer?.index(clients);
|
|
68
|
-
return hasIndex
|
|
69
|
-
? indexer.update()
|
|
70
|
-
: defaultUpdate(mc, clients, selection);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function defaultUpdate(mc, clients, selection) {
|
|
75
|
-
return Promise.all(Array.from(clients).map(client => {
|
|
76
|
-
const filter = selection.predicate(client);
|
|
77
|
-
if (filter != null) {
|
|
78
|
-
return mc.updateClient(client, client.query(filter));
|
|
79
|
-
}
|
|
80
|
-
}));
|
|
81
|
-
}
|