@genome-spy/core 0.67.0 → 0.68.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/bundle/index.es.js +7641 -6313
- package/dist/bundle/index.js +115 -134
- package/dist/schema.json +534 -132
- package/dist/src/data/collector.d.ts +20 -0
- package/dist/src/data/collector.d.ts.map +1 -1
- package/dist/src/data/collector.js +148 -0
- package/dist/src/data/dataFlow.d.ts +6 -0
- package/dist/src/data/dataFlow.d.ts.map +1 -1
- package/dist/src/data/dataFlow.js +10 -0
- package/dist/src/data/flowInit.d.ts.map +1 -1
- package/dist/src/data/flowInit.js +2 -3
- package/dist/src/data/flowNode.d.ts +8 -0
- package/dist/src/data/flowNode.d.ts.map +1 -1
- package/dist/src/data/flowNode.js +18 -0
- package/dist/src/data/keyIndex.d.ts +18 -0
- package/dist/src/data/keyIndex.d.ts.map +1 -0
- package/dist/src/data/keyIndex.js +241 -0
- package/dist/src/data/keyIndex.test.d.ts +2 -0
- package/dist/src/data/keyIndex.test.d.ts.map +1 -0
- package/dist/src/data/sources/dataSource.d.ts.map +1 -1
- package/dist/src/data/sources/dataSource.js +5 -1
- package/dist/src/data/sources/dataSourceFactory.d.ts +14 -12
- package/dist/src/data/sources/dataSourceFactory.d.ts.map +1 -1
- package/dist/src/data/sources/dataSourceFactory.js +52 -16
- package/dist/src/data/sources/lazy/mockLazySource.d.ts +29 -0
- package/dist/src/data/sources/lazy/mockLazySource.d.ts.map +1 -0
- package/dist/src/data/sources/lazy/mockLazySource.js +44 -0
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +22 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.js +34 -2
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +15 -0
- package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/tabixSource.js +15 -5
- package/dist/src/data/transforms/stack.d.ts.map +1 -1
- package/dist/src/data/transforms/stack.js +1 -0
- package/dist/src/encoder/accessor.d.ts +43 -0
- package/dist/src/encoder/accessor.d.ts.map +1 -1
- package/dist/src/encoder/accessor.js +164 -0
- package/dist/src/encoder/encoder.d.ts +11 -2
- package/dist/src/encoder/encoder.d.ts.map +1 -1
- package/dist/src/encoder/encoder.js +24 -4
- package/dist/src/encoder/metadataChannels.d.ts +15 -0
- package/dist/src/encoder/metadataChannels.d.ts.map +1 -0
- package/dist/src/encoder/metadataChannels.js +65 -0
- package/dist/src/encoder/metadataChannels.test.d.ts +2 -0
- package/dist/src/encoder/metadataChannels.test.d.ts.map +1 -0
- package/dist/src/genome/scaleLocus.d.ts.map +1 -1
- package/dist/src/genome/scaleLocus.js +14 -1
- package/dist/src/genomeSpy/containerUi.d.ts +0 -1
- package/dist/src/genomeSpy/containerUi.d.ts.map +1 -1
- package/dist/src/genomeSpy/containerUi.js +0 -14
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +3 -7
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -1
- package/dist/src/genomeSpy/loadingIndicatorManager.js +68 -20
- package/dist/src/genomeSpy/loadingStatusRegistry.d.ts +52 -0
- package/dist/src/genomeSpy/loadingStatusRegistry.d.ts.map +1 -0
- package/dist/src/genomeSpy/loadingStatusRegistry.js +86 -0
- package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
- package/dist/src/genomeSpy/viewContextFactory.js +0 -1
- package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
- package/dist/src/genomeSpy/viewDataInit.js +56 -11
- package/dist/src/genomeSpy.d.ts +0 -2
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +46 -26
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +18 -11
- package/dist/src/marks/markUtils.js +1 -1
- package/dist/src/scale/scale.d.ts +6 -1
- package/dist/src/scale/scale.d.ts.map +1 -1
- package/dist/src/scale/scale.js +83 -23
- package/dist/src/scales/axisResolution.d.ts.map +1 -1
- package/dist/src/scales/axisResolution.js +10 -0
- package/dist/src/scales/{scaleDomainAggregator.d.ts → domainPlanner.d.ts} +6 -3
- package/dist/src/scales/domainPlanner.d.ts.map +1 -0
- package/dist/src/scales/{scaleDomainAggregator.js → domainPlanner.js} +128 -10
- package/dist/src/scales/domainPlanner.test.d.ts +2 -0
- package/dist/src/scales/domainPlanner.test.d.ts.map +1 -0
- package/dist/src/scales/scaleInteractionController.d.ts +6 -0
- package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
- package/dist/src/scales/scaleInteractionController.js +41 -3
- package/dist/src/scales/scaleResolution.d.ts +19 -17
- package/dist/src/scales/scaleResolution.d.ts.map +1 -1
- package/dist/src/scales/scaleResolution.js +181 -70
- package/dist/src/scales/scaleResolution.test.d.ts.map +1 -1
- package/dist/src/selection/selection.d.ts +21 -0
- package/dist/src/selection/selection.d.ts.map +1 -1
- package/dist/src/selection/selection.js +82 -0
- package/dist/src/spec/channel.d.ts +52 -15
- package/dist/src/spec/data.d.ts +4 -0
- package/dist/src/spec/parameter.d.ts +16 -11
- package/dist/src/spec/testing.d.ts +12 -0
- package/dist/src/spec/testing.d.ts.map +1 -0
- package/dist/src/spec/testing.js +20 -0
- package/dist/src/spec/view.d.ts +45 -10
- package/dist/src/styles/genome-spy.css +3 -31
- package/dist/src/styles/genome-spy.css.d.ts +1 -1
- package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
- package/dist/src/styles/genome-spy.css.js +0 -29
- package/dist/src/types/encoder.d.ts +37 -2
- package/dist/src/types/rendering.d.ts +4 -3
- package/dist/src/types/viewContext.d.ts +0 -14
- package/dist/src/utils/throttle.d.ts +4 -1
- package/dist/src/utils/throttle.d.ts.map +1 -1
- package/dist/src/utils/throttle.js +54 -23
- package/dist/src/utils/throttle.test.d.ts +2 -0
- package/dist/src/utils/throttle.test.d.ts.map +1 -0
- package/dist/src/utils/transition.d.ts +21 -0
- package/dist/src/utils/transition.d.ts.map +1 -1
- package/dist/src/utils/transition.js +28 -0
- package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
- package/dist/src/utils/ui/tooltip.js +7 -1
- package/dist/src/utils/ui/tooltip.test.d.ts +2 -0
- package/dist/src/utils/ui/tooltip.test.d.ts.map +1 -0
- package/dist/src/view/axisGridView.d.ts.map +1 -1
- package/dist/src/view/axisGridView.js +22 -5
- package/dist/src/view/axisView.d.ts.map +1 -1
- package/dist/src/view/axisView.js +20 -5
- package/dist/src/view/concatView.js +3 -3
- package/dist/src/view/containerMutationHelper.js +1 -1
- package/dist/src/view/containerView.d.ts +9 -5
- package/dist/src/view/containerView.d.ts.map +1 -1
- package/dist/src/view/containerView.js +34 -9
- package/dist/src/view/dataReadiness.d.ts +46 -0
- package/dist/src/view/dataReadiness.d.ts.map +1 -0
- package/dist/src/view/dataReadiness.js +267 -0
- package/dist/src/view/dataReadiness.test.d.ts +2 -0
- package/dist/src/view/dataReadiness.test.d.ts.map +1 -0
- package/dist/src/view/facetView.d.ts.map +1 -1
- package/dist/src/view/facetView.js +7 -5
- package/dist/src/view/flowBuilder.d.ts.map +1 -1
- package/dist/src/view/flowBuilder.js +5 -1
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +8 -0
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +119 -2
- package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
- package/dist/src/view/gridView/scrollbar.js +3 -0
- package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
- package/dist/src/view/gridView/selectionRect.js +20 -5
- package/dist/src/view/gridView/separatorView.d.ts +51 -0
- package/dist/src/view/gridView/separatorView.d.ts.map +1 -0
- package/dist/src/view/gridView/separatorView.js +275 -0
- package/dist/src/view/layerView.js +3 -3
- package/dist/src/view/layout/flexLayout.d.ts +0 -30
- package/dist/src/view/layout/flexLayout.d.ts.map +1 -1
- package/dist/src/view/layout/flexLayout.js +0 -86
- package/dist/src/view/paramMediator.d.ts +19 -0
- package/dist/src/view/paramMediator.d.ts.map +1 -1
- package/dist/src/view/paramMediator.js +86 -19
- package/dist/src/view/testUtils.d.ts.map +1 -1
- package/dist/src/view/testUtils.js +6 -1
- package/dist/src/view/unitView.d.ts +8 -13
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +110 -41
- package/dist/src/view/view.d.ts +22 -14
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +93 -9
- package/dist/src/view/viewFactory.d.ts.map +1 -1
- package/dist/src/view/viewFactory.js +20 -1
- package/dist/src/view/viewSelectors.d.ts +148 -0
- package/dist/src/view/viewSelectors.d.ts.map +1 -0
- package/dist/src/view/viewSelectors.js +773 -0
- package/dist/src/view/viewSelectors.test.d.ts +2 -0
- package/dist/src/view/viewSelectors.test.d.ts.map +1 -0
- package/dist/src/view/viewUtils.d.ts +0 -8
- package/dist/src/view/viewUtils.d.ts.map +1 -1
- package/dist/src/view/viewUtils.js +1 -21
- package/package.json +3 -3
- package/dist/src/scales/scaleDomainAggregator.d.ts.map +0 -1
- package/dist/src/scales/scaleDomainAggregator.test.d.ts +0 -2
- package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +0 -1
|
@@ -19,6 +19,19 @@ export default class Collector extends FlowNode {
|
|
|
19
19
|
* @returns {() => void}
|
|
20
20
|
*/
|
|
21
21
|
observe(listener: (arg0: Collector) => void): () => void;
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} domainKey
|
|
24
|
+
* @param {import("../spec/channel.js").Type} type
|
|
25
|
+
* @param {import("../types/encoder.js").Accessor} accessor
|
|
26
|
+
* @returns {import("../utils/domainArray.js").DomainArray}
|
|
27
|
+
*/
|
|
28
|
+
getDomain(domainKey: string, type: import("../spec/channel.js").Type, accessor: import("../types/encoder.js").Accessor): import("../utils/domainArray.js").DomainArray;
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} domainKey
|
|
31
|
+
* @param {() => void} listener
|
|
32
|
+
* @returns {() => void}
|
|
33
|
+
*/
|
|
34
|
+
subscribeDomainChanges(domainKey: string, listener: () => void): () => void;
|
|
22
35
|
/**
|
|
23
36
|
* @returns {Iterable<Datum>}
|
|
24
37
|
*/
|
|
@@ -38,6 +51,13 @@ export default class Collector extends FlowNode {
|
|
|
38
51
|
* @param {number} uniqueId
|
|
39
52
|
*/
|
|
40
53
|
findDatumByUniqueId(uniqueId: number): import("./flowNode.js").Datum;
|
|
54
|
+
/**
|
|
55
|
+
* Uses a lazy index to find a datum by its key fields.
|
|
56
|
+
*
|
|
57
|
+
* @param {string[]} keyFields
|
|
58
|
+
* @param {import("../spec/channel.js").Scalar[]} keyTuple
|
|
59
|
+
*/
|
|
60
|
+
findDatumByKey(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]): import("./flowNode.js").Datum;
|
|
41
61
|
#private;
|
|
42
62
|
}
|
|
43
63
|
import FlowNode from "./flowNode.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../../src/data/collector.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../../src/data/collector.js"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH;IA6CI;;OAEG;IACH,qBAFW,OAAO,sBAAsB,EAAE,aAAa,EAiBtD;IAZG,qDAA2C;IAE3C,4CAA4C;IAC5C,WADW,GAAG,CAAC,CAAS,IAAS,EAAT,SAAS,KAAE,IAAI,CAAC,CACd;IAG1B,yFAAyF;IACzF,cADW,GAAG,CAAC,OAAO,oBAAoB,EAAE,MAAM,EAAE,+BAAO,CACN;IAwFzD;;;OAGG;IACH,kBAHW,CAAS,IAAS,EAAT,SAAS,KAAE,IAAI,GACtB,MAAM,IAAI,CAOtB;IAiCD;;;;;OAKG;IACH,qBALW,MAAM,QACN,OAAO,oBAAoB,EAAE,IAAI,YACjC,OAAO,qBAAqB,EAAE,QAAQ,GACpC,OAAO,yBAAyB,EAAE,WAAW,CAkBzD;IAED;;;;OAIG;IACH,kCAJW,MAAM,YACN,MAAM,IAAI,GACR,MAAM,IAAI,CAItB;IAED;;OAEG;IACH,WAFa,QAAQ,+BAAO,CAqB3B;IAED;;;OAGG;IACH,mBAFW,CAAC,KAAK,+BAAO,KAAK,IAAI,QAUhC;IAED;;OAEG;IACH,uBAMC;IAqDD;;;;OAIG;IACH,8BAFW,MAAM,iCA4BhB;IAED;;;;;OAKG;IACH,0BAHW,MAAM,EAAE,YACR,OAAO,oBAAoB,EAAE,MAAM,EAAE,iCAS/C;;CACJ;qBA9XyD,eAAe"}
|
|
@@ -7,6 +7,8 @@ import { field } from "../utils/field.js";
|
|
|
7
7
|
import { asArray } from "../utils/arrayUtils.js";
|
|
8
8
|
import { radixSortIntoLookupArray } from "../utils/radixSort.js";
|
|
9
9
|
import { UNIQUE_ID_KEY } from "./transforms/identifier.js";
|
|
10
|
+
import createDomain from "../utils/domainArray.js";
|
|
11
|
+
import KeyIndex from "./keyIndex.js";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Collects (materializes) the data that flows through this node.
|
|
@@ -33,6 +35,9 @@ export default class Collector extends FlowNode {
|
|
|
33
35
|
*/
|
|
34
36
|
#uniqueIdIndex = [];
|
|
35
37
|
|
|
38
|
+
/** @type {KeyIndex} */
|
|
39
|
+
#keyIndex = new KeyIndex();
|
|
40
|
+
|
|
36
41
|
/**
|
|
37
42
|
* Start and end indices of all facets if they are concatenated into a single array.
|
|
38
43
|
* Used together with the uniqueIdIndex for looking up data items by their unique id.
|
|
@@ -45,6 +50,9 @@ export default class Collector extends FlowNode {
|
|
|
45
50
|
*/
|
|
46
51
|
#comparator;
|
|
47
52
|
|
|
53
|
+
/** @type {DomainCache} */
|
|
54
|
+
#domainCache = new DomainCache();
|
|
55
|
+
|
|
48
56
|
get behavior() {
|
|
49
57
|
return BEHAVIOR_COLLECTS;
|
|
50
58
|
}
|
|
@@ -76,6 +84,7 @@ export default class Collector extends FlowNode {
|
|
|
76
84
|
#init() {
|
|
77
85
|
this.#buffer = [];
|
|
78
86
|
this.#uniqueIdIndex = [];
|
|
87
|
+
this.#keyIndex.invalidate();
|
|
79
88
|
|
|
80
89
|
this.facetBatches.clear();
|
|
81
90
|
this.facetBatches.set(undefined, this.#buffer);
|
|
@@ -97,6 +106,8 @@ export default class Collector extends FlowNode {
|
|
|
97
106
|
* @param {import("../types/flowBatch.js").FlowBatch} flowBatch
|
|
98
107
|
*/
|
|
99
108
|
beginBatch(flowBatch) {
|
|
109
|
+
this.#keyIndex.invalidate();
|
|
110
|
+
|
|
100
111
|
if (isFacetBatch(flowBatch)) {
|
|
101
112
|
this.#buffer = [];
|
|
102
113
|
this.facetBatches.set(asArray(flowBatch.facetId), this.#buffer);
|
|
@@ -144,6 +155,8 @@ export default class Collector extends FlowNode {
|
|
|
144
155
|
|
|
145
156
|
super.complete();
|
|
146
157
|
|
|
158
|
+
this.#invalidateDomains();
|
|
159
|
+
|
|
147
160
|
for (const observer of this.observers) {
|
|
148
161
|
observer(this);
|
|
149
162
|
}
|
|
@@ -187,6 +200,41 @@ export default class Collector extends FlowNode {
|
|
|
187
200
|
for (const child of this.children) {
|
|
188
201
|
child.complete();
|
|
189
202
|
}
|
|
203
|
+
|
|
204
|
+
this.#invalidateDomains();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {string} domainKey
|
|
209
|
+
* @param {import("../spec/channel.js").Type} type
|
|
210
|
+
* @param {import("../types/encoder.js").Accessor} accessor
|
|
211
|
+
* @returns {import("../utils/domainArray.js").DomainArray}
|
|
212
|
+
*/
|
|
213
|
+
getDomain(domainKey, type, accessor) {
|
|
214
|
+
return this.#domainCache.getDomain(domainKey, () => {
|
|
215
|
+
const domain = createDomain(type);
|
|
216
|
+
|
|
217
|
+
if (accessor.constant) {
|
|
218
|
+
domain.extend(accessor({}));
|
|
219
|
+
} else if (this.completed) {
|
|
220
|
+
for (const data of this.facetBatches.values()) {
|
|
221
|
+
for (let i = 0, n = data.length; i < n; i++) {
|
|
222
|
+
domain.extend(accessor(data[i]));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return domain;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @param {string} domainKey
|
|
233
|
+
* @param {() => void} listener
|
|
234
|
+
* @returns {() => void}
|
|
235
|
+
*/
|
|
236
|
+
subscribeDomainChanges(domainKey, listener) {
|
|
237
|
+
return this.#domainCache.subscribe(domainKey, listener);
|
|
190
238
|
}
|
|
191
239
|
|
|
192
240
|
/**
|
|
@@ -246,6 +294,13 @@ export default class Collector extends FlowNode {
|
|
|
246
294
|
}
|
|
247
295
|
}
|
|
248
296
|
|
|
297
|
+
#invalidateDomains() {
|
|
298
|
+
if (this.#domainCache.hasCachedDomains()) {
|
|
299
|
+
this.#domainCache.clear();
|
|
300
|
+
}
|
|
301
|
+
this.#domainCache.notify();
|
|
302
|
+
}
|
|
303
|
+
|
|
249
304
|
/**
|
|
250
305
|
* Builds an index for looking up data items by their unique id.
|
|
251
306
|
* Using a sorted index and binary search for O(log n) complexity.
|
|
@@ -314,6 +369,99 @@ export default class Collector extends FlowNode {
|
|
|
314
369
|
}
|
|
315
370
|
}
|
|
316
371
|
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Uses a lazy index to find a datum by its key fields.
|
|
375
|
+
*
|
|
376
|
+
* @param {string[]} keyFields
|
|
377
|
+
* @param {import("../spec/channel.js").Scalar[]} keyTuple
|
|
378
|
+
*/
|
|
379
|
+
findDatumByKey(keyFields, keyTuple) {
|
|
380
|
+
this.#checkStatus();
|
|
381
|
+
return this.#keyIndex.findDatum(
|
|
382
|
+
keyFields,
|
|
383
|
+
keyTuple,
|
|
384
|
+
this.facetBatches.values()
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Manages cached domains and subscriptions for invalidation.
|
|
391
|
+
*/
|
|
392
|
+
class DomainCache {
|
|
393
|
+
/** @type {Map<string, import("../utils/domainArray.js").DomainArray>} */
|
|
394
|
+
#cache = new Map();
|
|
395
|
+
|
|
396
|
+
/** @type {Map<string, Set<() => void>>} */
|
|
397
|
+
#observers = new Map();
|
|
398
|
+
|
|
399
|
+
hasCachedDomains() {
|
|
400
|
+
return this.#cache.size > 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
clear() {
|
|
404
|
+
this.#cache.clear();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* @param {string} domainKey
|
|
409
|
+
* @param {() => import("../utils/domainArray.js").DomainArray} build
|
|
410
|
+
* @returns {import("../utils/domainArray.js").DomainArray}
|
|
411
|
+
*/
|
|
412
|
+
getDomain(domainKey, build) {
|
|
413
|
+
const cached = this.#cache.get(domainKey);
|
|
414
|
+
if (cached) {
|
|
415
|
+
return cached;
|
|
416
|
+
} else {
|
|
417
|
+
const domain = build();
|
|
418
|
+
this.#cache.set(domainKey, domain);
|
|
419
|
+
return domain;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @param {string} domainKey
|
|
425
|
+
* @param {() => void} listener
|
|
426
|
+
* @returns {() => void}
|
|
427
|
+
*/
|
|
428
|
+
subscribe(domainKey, listener) {
|
|
429
|
+
let listeners = this.#observers.get(domainKey);
|
|
430
|
+
if (!listeners) {
|
|
431
|
+
listeners = new Set();
|
|
432
|
+
this.#observers.set(domainKey, listeners);
|
|
433
|
+
}
|
|
434
|
+
listeners.add(listener);
|
|
435
|
+
|
|
436
|
+
return () => {
|
|
437
|
+
const entry = this.#observers.get(domainKey);
|
|
438
|
+
if (!entry) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
entry.delete(listener);
|
|
442
|
+
if (entry.size === 0) {
|
|
443
|
+
this.#observers.delete(domainKey);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
notify() {
|
|
449
|
+
if (this.#observers.size === 0) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** @type {Set<() => void>} */
|
|
454
|
+
const listeners = new Set();
|
|
455
|
+
for (const observers of this.#observers.values()) {
|
|
456
|
+
for (const observer of observers) {
|
|
457
|
+
listeners.add(observer);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
for (const listener of listeners) {
|
|
462
|
+
listener();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
317
465
|
}
|
|
318
466
|
|
|
319
467
|
/**
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* DataFlow holds data sources and collectors for optimization and initialization.
|
|
3
3
|
*/
|
|
4
4
|
export default class DataFlow {
|
|
5
|
+
/**
|
|
6
|
+
* Registry for per-view loading status. The host may replace this.
|
|
7
|
+
*
|
|
8
|
+
* @type {import("../genomeSpy/loadingStatusRegistry.js").default}
|
|
9
|
+
*/
|
|
10
|
+
loadingStatusRegistry: import("../genomeSpy/loadingStatusRegistry.js").default;
|
|
5
11
|
get dataSources(): DataSource[];
|
|
6
12
|
get collectors(): import("./collector.js").default[];
|
|
7
13
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataFlow.d.ts","sourceRoot":"","sources":["../../../src/data/dataFlow.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataFlow.d.ts","sourceRoot":"","sources":["../../../src/data/dataFlow.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IAOI;;;;OAIG;IACH,uBAFU,OAAO,uCAAuC,EAAE,OAAO,CAE3C;IAYtB,gCAEC;IAED,qDAEC;IAED;;OAEG;IACH,gCAFW,QAAQ,CAAC,OAAO,yBAAyB,EAAE,OAAO,CAAC,QAI7D;IAED;;OAEG;IACH,0BAFW,OAAO,yBAAyB,EAAE,OAAO,QAInD;IAED;;OAEG;IACH,6BAFW,OAAO,yBAAyB,EAAE,OAAO,QAInD;IAED;;OAEG;IACH,wBAFW,OAAO,gBAAgB,EAAE,OAAO,QAI1C;IAED;;OAEG;IACH,2BAFW,OAAO,gBAAgB,EAAE,OAAO,QAK1C;IAED;;;;OAIG;IACH,gCAFW,OAAO,gBAAgB,EAAE,OAAO,QAiB1C;IAED;;OAEG;IACH,0BAFW,MAAM;;MA2BhB;;CAGJ;uBA/HsB,yBAAyB;wBACxB,0BAA0B"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import LoadingStatusRegistry from "../genomeSpy/loadingStatusRegistry.js";
|
|
1
2
|
import DataSource from "./sources/dataSource.js";
|
|
2
3
|
import NamedSource from "./sources/namedSource.js";
|
|
3
4
|
|
|
@@ -11,12 +12,21 @@ export default class DataFlow {
|
|
|
11
12
|
/** @type {Set<import("./collector.js").default>} */
|
|
12
13
|
#collectors;
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Registry for per-view loading status. The host may replace this.
|
|
17
|
+
*
|
|
18
|
+
* @type {import("../genomeSpy/loadingStatusRegistry.js").default}
|
|
19
|
+
*/
|
|
20
|
+
loadingStatusRegistry;
|
|
21
|
+
|
|
14
22
|
constructor() {
|
|
15
23
|
/** @type {Set<import("./sources/dataSource.js").default>} */
|
|
16
24
|
this.#dataSources = new Set();
|
|
17
25
|
|
|
18
26
|
/** @type {Set<import("./collector.js").default>} */
|
|
19
27
|
this.#collectors = new Set();
|
|
28
|
+
|
|
29
|
+
this.loadingStatusRegistry = new LoadingStatusRegistry();
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
get dataSources() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flowInit.d.ts","sourceRoot":"","sources":["../../../src/data/flowInit.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flowInit.d.ts","sourceRoot":"","sources":["../../../src/data/flowInit.js"],"names":[],"mappings":"AAwDA;;;;;GAKG;AACH,sCAHW,OAAO,iBAAiB,EAAE,OAAO,qBACjC,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,EAAE,OAAO,yBAAyB,EAAE,OAAO,CAAC,QAcnG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,mDAXW,OAAO,iBAAiB,EAAE,OAAO,QACjC,OAAO,eAAe,EAAE,OAAO,eAC/B,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,KAAK,OAAO,gCACpD,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,KAAK,OAAO,GAClD;IACN,QAAQ,EAAE,OAAO,eAAe,EAAE,OAAO,CAAC;IAC1C,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,WAAW,EAAE,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,CAAC,CAAC;IAC5D,gBAAgB,EAAE,OAAO,CAAC,OAAO,kBAAkB,EAAE,OAAO,CAAC,EAAE,CAAA;CAClE,CA6FH;AAED;;;;;;;GAOG;AACH,2DAJW,OAAO,iBAAiB,EAAE,OAAO,GAAG,OAAO,iBAAiB,EAAE,OAAO,EAAE,eACvE,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,KAAK,OAAO,GAClD,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,CAAC,CAsB1D;AAED;;;;;;;GAOG;AACH,kEAJW,OAAO,iBAAiB,EAAE,OAAO,eACjC,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,KAAK,OAAO,GAClD,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,CAAC,CAe1D;AAED;;;;;;;;;GASG;AACH,iDANW,OAAO,iBAAiB,EAAE,OAAO,gBACjC,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,CAAC,eAC9C,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,KAAK,OAAO,gBACpD;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GACvB,OAAO,CAAC,IAAI,EAAE,CAAC,CAsB3B;qBA3SoB,qBAAqB"}
|
|
@@ -2,7 +2,6 @@ import UnitView from "../view/unitView.js";
|
|
|
2
2
|
import { buildDataFlow } from "../view/flowBuilder.js";
|
|
3
3
|
import { optimizeDataFlow } from "./flowOptimizer.js";
|
|
4
4
|
import { VISIT_SKIP } from "../view/view.js";
|
|
5
|
-
import { reconfigureScaleDomains } from "../scales/scaleResolution.js";
|
|
6
5
|
|
|
7
6
|
/** @type {WeakMap<import("./sources/dataSource.js").default, Promise<void>>} */
|
|
8
7
|
const inFlightLoads = new WeakMap();
|
|
@@ -163,7 +162,7 @@ export function initializeViewSubtree(
|
|
|
163
162
|
|
|
164
163
|
// Initialize flow nodes for the sources that belong to this subtree.
|
|
165
164
|
for (const dataSource of dataSources) {
|
|
166
|
-
dataSource.visit((node) => node.
|
|
165
|
+
dataSource.visit((node) => node.initializeOnce());
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
/** @type {UnitView[]} */
|
|
@@ -180,6 +179,7 @@ export function initializeViewSubtree(
|
|
|
180
179
|
const mark = view.mark;
|
|
181
180
|
// Encoders can be initialized immediately; graphics need a GL context.
|
|
182
181
|
mark.initializeEncoders();
|
|
182
|
+
view.registerDomainSubscriptions();
|
|
183
183
|
if (canInitializeGraphics) {
|
|
184
184
|
graphicsPromises.push(mark.initializeGraphics().then(() => mark));
|
|
185
185
|
}
|
|
@@ -294,7 +294,6 @@ export function loadViewSubtreeData(
|
|
|
294
294
|
loadDataSourceOnce(dataSource, loadOptions)
|
|
295
295
|
)
|
|
296
296
|
).then((results) => {
|
|
297
|
-
reconfigureScaleDomains(subtreeRoot, viewFilter);
|
|
298
297
|
broadcastSubtreeDataReady(subtreeRoot);
|
|
299
298
|
return results;
|
|
300
299
|
});
|
|
@@ -69,8 +69,16 @@ export default class FlowNode {
|
|
|
69
69
|
/**
|
|
70
70
|
* Allows for doing final initialization after the flow structure has been
|
|
71
71
|
* built and optimized. Must be called before any data are to be propagated.
|
|
72
|
+
* Note: Some transforms call initialize() from reset() to rebuild internal
|
|
73
|
+
* fast paths per batch. Use initializeOnce() for graph-level init to avoid
|
|
74
|
+
* double-initialization issues.
|
|
72
75
|
*/
|
|
73
76
|
initialize(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Calls initialize() once per node instance. Intended for graph-level init
|
|
79
|
+
* passes that should not rerun when reusing existing branches.
|
|
80
|
+
*/
|
|
81
|
+
initializeOnce(): void;
|
|
74
82
|
/**
|
|
75
83
|
* @param {Datum} datum
|
|
76
84
|
* @protected
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flowNode.d.ts","sourceRoot":"","sources":["../../../src/data/flowNode.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"flowNode.d.ts","sourceRoot":"","sources":["../../../src/data/flowNode.js"],"names":[],"mappings":"AAyVA;;;GAGG;AACH,uCAHW,OAAO,uBAAuB,EAAE,SAAS,GACvC,SAAS,IAAI,OAAO,uBAAuB,EAAE,SAAS,CAIlE;AAED;;;GAGG;AACH,wCAHW,OAAO,uBAAuB,EAAE,SAAS,GACvC,SAAS,IAAI,OAAO,uBAAuB,EAAE,UAAU,CAInE;AArWD;;;GAGG;AACH,qCAAsC;AAEtC;;;GAGG;AACH,uCAAwC;AAExC;;;GAGG;AACH,uCAAwC;AAExC;;GAEG;AAEH;;;;;GAKG;AACH;IA8BI;;OAEG;IACH,oCAFW,qBAAqB,EAa/B;IA3CD;;QAEI,oBAAoB;eAAT,KAAK;MAElB;IAKF;;;;OAIG;IACH,uBAFU,qBAAqB,CAEF;IAE7B,uBAEC;IAED;;;;OAIG;IACH,aAFa,MAAM,CAIlB;IAQG,yBAAyB;IACzB,UADW,QAAQ,EAAE,CACH;IAElB,uBAAuB;IACvB,QADW,QAAQ,CACI;IAEvB,2CAA2C;IAC3C,mBAAsB;IAG1B;;;OAGG;IACH,cASC;IAED;;;;;;OAMG;IACH,mBAEC;IAED;;;OAGG;IACH,uBAMC;IAgOD;;;OAGG;IACH,4BAHW,KAAK,QAKf;IA7MD;;;OAGG;IACH,kBAFW,QAAQ,QAIlB;IAED;;;OAGG;IACH,gBAFW,QAAQ,QAUlB;IAED;;OAEG;IACH,YAFW,QAAQ,QAQlB;IAED;;OAEG;IACH,6BAFW,QAAQ,QAMlB;IAED;;OAEG;IACH,0BAFW,QAAQ,QAalB;IAED;;;OAGG;IACH,mBAFW,QAAQ,QAWlB;IAED;;OAEG;IACH,eAiBC;IAED,kBAEC;IAED,uBAEC;IAED,sBAEC;IAED;;;;OAIG;IACH,eAFW,CAAC,CAAS,IAAQ,EAAR,QAAQ,KAAE,IAAI,CAAC,GAAG;QAAE,aAAa,CAAC,EAAE,CAAS,IAAQ,EAAR,QAAQ,KAAE,IAAI,CAAA;KAAC,QAchF;IAED;;;OAGG;IACH,wBAHW,MAAM,GACJ,MAAM,CAWlB;IAED;;;OAGG;IACH,cAFW,KAAK,QAKf;IAED,iBAMC;IAED;;;;OAIG;IACH,sBAFW,OAAO,uBAAuB,EAAE,SAAS,QAMnD;IAED;;;OAGG;IACH,+BAHa,OAAO,0BAA0B,EAAE,OAAO,CAYtD;IAED;;;;OAIG;IACH,8BAQC;;CASJ;oCAlUY;IAAC,aAAa,EAAE,OAAO,0BAA0B,EAAE,OAAO,CAAA;CAAC;;;;oBAM3D,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;mBACnB,KAAK,EAAE"}
|
|
@@ -35,6 +35,9 @@ export default class FlowNode {
|
|
|
35
35
|
first: null,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
/** @type {boolean} */
|
|
39
|
+
#initialized = false;
|
|
40
|
+
|
|
38
41
|
/**
|
|
39
42
|
* An object that provides a paramMediator. (Most likely a View)
|
|
40
43
|
*
|
|
@@ -89,11 +92,26 @@ export default class FlowNode {
|
|
|
89
92
|
/**
|
|
90
93
|
* Allows for doing final initialization after the flow structure has been
|
|
91
94
|
* built and optimized. Must be called before any data are to be propagated.
|
|
95
|
+
* Note: Some transforms call initialize() from reset() to rebuild internal
|
|
96
|
+
* fast paths per batch. Use initializeOnce() for graph-level init to avoid
|
|
97
|
+
* double-initialization issues.
|
|
92
98
|
*/
|
|
93
99
|
initialize() {
|
|
94
100
|
// override
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Calls initialize() once per node instance. Intended for graph-level init
|
|
105
|
+
* passes that should not rerun when reusing existing branches.
|
|
106
|
+
*/
|
|
107
|
+
initializeOnce() {
|
|
108
|
+
if (this.#initialized) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.initialize();
|
|
112
|
+
this.#initialized = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
97
115
|
/**
|
|
98
116
|
* Dynamically updates the propagator method to allow the JavaScript engine
|
|
99
117
|
* to employ optimizations such as inlining.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazily builds and caches lookup indexes for `encoding.key` fields so that
|
|
3
|
+
* bookmarked point selections can be resolved back to datums efficiently.
|
|
4
|
+
*
|
|
5
|
+
* TODO: Use requestIdleCallback to warm up the index to prevent jank on first access.
|
|
6
|
+
*/
|
|
7
|
+
export default class KeyIndex {
|
|
8
|
+
invalidate(): void;
|
|
9
|
+
/**
|
|
10
|
+
* @param {string[]} keyFields
|
|
11
|
+
* @param {import("../spec/channel.js").Scalar[]} keyTuple
|
|
12
|
+
* @param {Iterable<Data>} facetBatches
|
|
13
|
+
* @returns {Datum | undefined}
|
|
14
|
+
*/
|
|
15
|
+
findDatum(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[], facetBatches: Iterable<import("./flowNode.js").Data>): import("./flowNode.js").Datum | undefined;
|
|
16
|
+
#private;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=keyIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyIndex.d.ts","sourceRoot":"","sources":["../../../src/data/keyIndex.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH;IAeI,mBAIC;IAED;;;;;OAKG;IACH,qBALW,MAAM,EAAE,YACR,OAAO,oBAAoB,EAAE,MAAM,EAAE,gBACrC,QAAQ,8BAAM,GACZ,gCAAQ,SAAS,CAwC7B;;CAgGJ"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { field } from "../utils/field.js";
|
|
2
|
+
|
|
3
|
+
const MULTI_KEY_SEPARATOR = "|";
|
|
4
|
+
const MULTI_KEY_ESCAPE = "\\";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Lazily builds and caches lookup indexes for `encoding.key` fields so that
|
|
8
|
+
* bookmarked point selections can be resolved back to datums efficiently.
|
|
9
|
+
*
|
|
10
|
+
* TODO: Use requestIdleCallback to warm up the index to prevent jank on first access.
|
|
11
|
+
*/
|
|
12
|
+
export default class KeyIndex {
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {import("./flowNode.js").Datum} Datum
|
|
15
|
+
* @typedef {import("./flowNode.js").Data} Data
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** @type {Map<import("../spec/channel.js").Scalar | string, Datum> | null} */
|
|
19
|
+
#index = null;
|
|
20
|
+
|
|
21
|
+
/** @type {string[] | null} */
|
|
22
|
+
#keyFields = null;
|
|
23
|
+
|
|
24
|
+
/** @type {boolean} */
|
|
25
|
+
#usesCompositeKey = false;
|
|
26
|
+
|
|
27
|
+
invalidate() {
|
|
28
|
+
this.#index = null;
|
|
29
|
+
this.#keyFields = null;
|
|
30
|
+
this.#usesCompositeKey = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string[]} keyFields
|
|
35
|
+
* @param {import("../spec/channel.js").Scalar[]} keyTuple
|
|
36
|
+
* @param {Iterable<Data>} facetBatches
|
|
37
|
+
* @returns {Datum | undefined}
|
|
38
|
+
*/
|
|
39
|
+
findDatum(keyFields, keyTuple, facetBatches) {
|
|
40
|
+
if (!keyFields || keyFields.length === 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const fieldList = keyFields.join(", ");
|
|
45
|
+
if (keyFields.length !== keyTuple.length) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Key tuple length ${keyTuple.length} does not match fields [${fieldList}]`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!this.#index || !this.#matchesKeyFields(keyFields)) {
|
|
52
|
+
this.#buildIndex(keyFields, facetBatches);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const canonicalFields = /** @type {string[]} */ (this.#keyFields);
|
|
56
|
+
/** @type {import("../spec/channel.js").Scalar | string} */
|
|
57
|
+
let key;
|
|
58
|
+
if (!this.#usesCompositeKey) {
|
|
59
|
+
const fieldName = canonicalFields[0];
|
|
60
|
+
key = validateKeyComponent(keyTuple[0], fieldName);
|
|
61
|
+
} else {
|
|
62
|
+
let compositeKey = "";
|
|
63
|
+
for (let i = 0; i < keyTuple.length; i++) {
|
|
64
|
+
if (i > 0) {
|
|
65
|
+
compositeKey += MULTI_KEY_SEPARATOR;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const fieldName = canonicalFields[i];
|
|
69
|
+
const value = validateKeyComponent(keyTuple[i], fieldName);
|
|
70
|
+
|
|
71
|
+
compositeKey += encodeKeyPart(value);
|
|
72
|
+
}
|
|
73
|
+
key = compositeKey;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this.#index.get(key);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {string[]} keyFields
|
|
81
|
+
* @param {Iterable<Data>} facetBatches
|
|
82
|
+
*/
|
|
83
|
+
#buildIndex(keyFields, facetBatches) {
|
|
84
|
+
/** @type {Array<(datum: Datum) => import("../spec/channel.js").Scalar>} */
|
|
85
|
+
const accessors = keyFields.map((fieldName) => field(fieldName));
|
|
86
|
+
/** @type {Map<import("../spec/channel.js").Scalar | string, Datum>} */
|
|
87
|
+
const index = new Map();
|
|
88
|
+
const fieldList = keyFields.join(", ");
|
|
89
|
+
|
|
90
|
+
const usesCompositeKey = keyFields.length !== 1;
|
|
91
|
+
|
|
92
|
+
if (!usesCompositeKey) {
|
|
93
|
+
const accessor = accessors[0];
|
|
94
|
+
const fieldName = keyFields[0];
|
|
95
|
+
for (const data of facetBatches) {
|
|
96
|
+
for (let i = 0, n = data.length; i < n; i++) {
|
|
97
|
+
const datum = data[i];
|
|
98
|
+
const key = validateKeyComponent(
|
|
99
|
+
accessor(datum),
|
|
100
|
+
fieldName
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const previous = index.get(key);
|
|
104
|
+
if (previous !== undefined) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Duplicate key detected for fields [${fieldList}]: ${JSON.stringify(key)}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
index.set(key, datum);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
for (const data of facetBatches) {
|
|
115
|
+
for (let i = 0, n = data.length; i < n; i++) {
|
|
116
|
+
const datum = data[i];
|
|
117
|
+
let compositeKey = "";
|
|
118
|
+
for (let j = 0; j < accessors.length; j++) {
|
|
119
|
+
if (j > 0) {
|
|
120
|
+
compositeKey += MULTI_KEY_SEPARATOR;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fieldName = keyFields[j];
|
|
124
|
+
const value = validateKeyComponent(
|
|
125
|
+
accessors[j](datum),
|
|
126
|
+
fieldName
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
compositeKey += encodeKeyPart(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const previous = index.get(compositeKey);
|
|
133
|
+
if (previous !== undefined) {
|
|
134
|
+
const duplicateTuple = accessors.map((accessor) =>
|
|
135
|
+
accessor(datum)
|
|
136
|
+
);
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Duplicate key detected for fields [${fieldList}]: ${JSON.stringify(duplicateTuple)}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
index.set(compositeKey, datum);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.#index = index;
|
|
148
|
+
this.#keyFields = [...keyFields];
|
|
149
|
+
this.#usesCompositeKey = usesCompositeKey;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {string[]} keyFields
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
#matchesKeyFields(keyFields) {
|
|
157
|
+
if (!this.#keyFields) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (this.#keyFields.length !== keyFields.length) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < keyFields.length; i++) {
|
|
166
|
+
if (this.#keyFields[i] !== keyFields[i]) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {unknown} value
|
|
177
|
+
* @param {string} fieldName
|
|
178
|
+
* @returns {import("../spec/channel.js").Scalar}
|
|
179
|
+
*/
|
|
180
|
+
function validateKeyComponent(value, fieldName) {
|
|
181
|
+
if (value === undefined) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Key field "${fieldName}" is undefined. Ensure all key fields are present in the data.`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (value === null) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Key field "${fieldName}" is null. Ensure all key fields are present in the data.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
typeof value !== "string" &&
|
|
195
|
+
typeof value !== "number" &&
|
|
196
|
+
typeof value !== "boolean"
|
|
197
|
+
) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Key field "${fieldName}" must be a scalar value (string, number, or boolean).`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {string} value
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
function escapeKeyString(value) {
|
|
211
|
+
const needsEscaping =
|
|
212
|
+
value.indexOf(MULTI_KEY_ESCAPE) !== -1 ||
|
|
213
|
+
value.indexOf(MULTI_KEY_SEPARATOR) !== -1;
|
|
214
|
+
|
|
215
|
+
if (!needsEscaping) {
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let escaped = "";
|
|
220
|
+
for (let i = 0; i < value.length; i++) {
|
|
221
|
+
const char = value[i];
|
|
222
|
+
if (char === MULTI_KEY_ESCAPE || char === MULTI_KEY_SEPARATOR) {
|
|
223
|
+
escaped += MULTI_KEY_ESCAPE;
|
|
224
|
+
}
|
|
225
|
+
escaped += char;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return escaped;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @param {import("../spec/channel.js").Scalar} scalar
|
|
233
|
+
* @returns {string}
|
|
234
|
+
*/
|
|
235
|
+
function encodeKeyPart(scalar) {
|
|
236
|
+
if (typeof scalar === "string") {
|
|
237
|
+
return escapeKeyString(scalar);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return String(scalar);
|
|
241
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyIndex.test.d.ts","sourceRoot":"","sources":["../../../src/data/keyIndex.test.js"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataSource.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/dataSource.js"],"names":[],"mappings":"AAEA;IAMI;;OAEG;IACH,kBAFW,OAAO,oBAAoB,EAAE,OAAO,EAM9C;IAZD;;OAEG;IACH,MAFU,OAAO,oBAAoB,EAAE,OAAO,CAEzC;IAWL;;;;;OAKG;IACH,kBAFY,MAAM,CAIjB;IAED;;;;;;OAMG;IACH,mCAJW,OAAO,4BAA4B,EAAE,iBAAiB,WACtD,MAAM,
|
|
1
|
+
{"version":3,"file":"dataSource.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/dataSource.js"],"names":[],"mappings":"AAEA;IAMI;;OAEG;IACH,kBAFW,OAAO,oBAAoB,EAAE,OAAO,EAM9C;IAZD;;OAEG;IACH,MAFU,OAAO,oBAAoB,EAAE,OAAO,CAEzC;IAWL;;;;;OAKG;IACH,kBAFY,MAAM,CAIjB;IAED;;;;;;OAMG;IACH,mCAJW,OAAO,4BAA4B,EAAE,iBAAiB,WACtD,MAAM,QAShB;IAcD,sBAEC;CAKJ;qBA7DoB,gBAAgB"}
|