@genome-spy/core 0.40.0 → 0.42.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--cKb-dKG.js +615 -0
- package/dist/bundle/{index-gn8bhQ8w.js → index-d7k3kkin.js} +365 -366
- package/dist/bundle/index.es.js +4260 -3928
- package/dist/bundle/index.js +115 -80
- package/dist/schema.json +254 -52
- package/dist/src/data/sources/dynamic/axisGenomeSource.js +1 -1
- package/dist/src/data/sources/dynamic/axisTickSource.js +3 -3
- package/dist/src/data/sources/dynamic/bamSource.d.ts +3 -21
- package/dist/src/data/sources/dynamic/bamSource.d.ts.map +1 -1
- package/dist/src/data/sources/dynamic/bamSource.js +38 -55
- package/dist/src/data/sources/dynamic/bigBedSource.d.ts +2 -38
- package/dist/src/data/sources/dynamic/bigBedSource.d.ts.map +1 -1
- package/dist/src/data/sources/dynamic/bigBedSource.js +14 -71
- package/dist/src/data/sources/dynamic/bigWigSource.d.ts +4 -42
- package/dist/src/data/sources/dynamic/bigWigSource.d.ts.map +1 -1
- package/dist/src/data/sources/dynamic/bigWigSource.js +23 -60
- package/dist/src/data/sources/dynamic/gff3Source.d.ts.map +1 -1
- package/dist/src/data/sources/dynamic/gff3Source.js +1 -0
- package/dist/src/data/sources/dynamic/indexedFastaSource.d.ts +2 -20
- package/dist/src/data/sources/dynamic/indexedFastaSource.d.ts.map +1 -1
- package/dist/src/data/sources/dynamic/indexedFastaSource.js +23 -41
- package/dist/src/data/sources/dynamic/singleAxisLazySource.d.ts +23 -4
- package/dist/src/data/sources/dynamic/singleAxisLazySource.d.ts.map +1 -1
- package/dist/src/data/sources/dynamic/singleAxisLazySource.js +29 -4
- package/dist/src/data/sources/dynamic/singleAxisWindowedSource.d.ts +60 -0
- package/dist/src/data/sources/dynamic/singleAxisWindowedSource.d.ts.map +1 -0
- package/dist/src/data/sources/dynamic/singleAxisWindowedSource.js +152 -0
- package/dist/src/data/sources/dynamic/tabixSource.d.ts +6 -40
- package/dist/src/data/sources/dynamic/tabixSource.d.ts.map +1 -1
- package/dist/src/data/sources/dynamic/tabixSource.js +29 -78
- package/dist/src/data/transforms/regexFold.d.ts.map +1 -1
- package/dist/src/data/transforms/regexFold.js +8 -0
- package/dist/src/data/transforms/regexFold.test.js +28 -0
- package/dist/src/encoder/accessor.js +4 -2
- package/dist/src/genomeSpy.d.ts +16 -0
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +119 -8
- package/dist/src/gl/link.vertex.glsl.js +1 -1
- package/dist/src/gl/point.common.glsl.js +2 -0
- package/dist/src/gl/point.fragment.glsl.js +1 -1
- package/dist/src/gl/point.vertex.glsl.js +1 -1
- package/dist/src/gl/rect.vertex.glsl.js +1 -1
- package/dist/src/gl/rule.common.glsl.js +2 -0
- package/dist/src/gl/rule.fragment.glsl.js +1 -1
- package/dist/src/gl/rule.vertex.glsl.js +1 -1
- package/dist/src/gl/text.common.glsl.js +2 -0
- package/dist/src/gl/text.fragment.glsl.js +1 -1
- package/dist/src/gl/text.vertex.glsl.js +1 -1
- package/dist/src/gl/webGLHelper.d.ts +6 -21
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +7 -38
- package/dist/src/img/90-ring-with-bg.svg +1 -0
- package/dist/src/img/README.md +5 -0
- package/dist/src/marks/link.d.ts +7 -0
- package/dist/src/marks/link.d.ts.map +1 -1
- package/dist/src/marks/link.js +99 -50
- package/dist/src/marks/mark.d.ts +34 -0
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +83 -1
- package/dist/src/marks/pointMark.d.ts.map +1 -1
- package/dist/src/marks/pointMark.js +21 -9
- package/dist/src/marks/rectMark.d.ts +1 -2
- package/dist/src/marks/rectMark.d.ts.map +1 -1
- package/dist/src/marks/rectMark.js +28 -17
- package/dist/src/marks/rule.d.ts.map +1 -1
- package/dist/src/marks/rule.js +17 -6
- package/dist/src/marks/text.d.ts.map +1 -1
- package/dist/src/marks/text.js +32 -18
- package/dist/src/paramBroker.d.ts +30 -0
- package/dist/src/paramBroker.d.ts.map +1 -0
- package/dist/src/paramBroker.js +102 -0
- package/dist/src/spec/data.d.ts +28 -13
- package/dist/src/spec/mark.d.ts +27 -26
- package/dist/src/spec/view.d.ts +2 -1
- 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 +33 -4
- package/dist/src/styles/genome-spy.scss +40 -4
- package/dist/src/types/viewContext.d.ts +11 -0
- package/dist/src/utils/binnedIndex.d.ts +2 -0
- package/dist/src/utils/binnedIndex.d.ts.map +1 -1
- package/dist/src/utils/binnedIndex.js +59 -10
- package/dist/src/utils/binnedIndex.test.js +46 -0
- package/dist/src/utils/expression.d.ts +12 -2
- package/dist/src/utils/expression.d.ts.map +1 -1
- package/dist/src/utils/expression.js +68 -9
- package/dist/src/utils/linearstep.d.ts +7 -0
- package/dist/src/utils/linearstep.d.ts.map +1 -0
- package/dist/src/utils/linearstep.js +10 -0
- package/dist/src/view/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView.js +2 -0
- package/dist/src/view/layerView.d.ts.map +1 -1
- package/dist/src/view/layerView.js +2 -0
- package/dist/src/view/unitView.d.ts +0 -6
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +2 -9
- package/dist/src/view/view.d.ts +6 -0
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +17 -0
- package/package.json +3 -3
- package/dist/bundle/index-Cbz74kpR.js +0 -638
- package/dist/src/data/sources/dynamic/windowedMixin.d.ts +0 -32
- package/dist/src/data/sources/dynamic/windowedMixin.d.ts.map +0 -1
- package/dist/src/data/sources/dynamic/windowedMixin.js +0 -53
|
@@ -6,6 +6,12 @@ import { reconfigureScales } from "../../../view/scaleResolution.js";
|
|
|
6
6
|
* Base class for data sources that listen a domain and propagate data lazily.
|
|
7
7
|
*/
|
|
8
8
|
export default class SingleAxisLazySource extends DataSource {
|
|
9
|
+
/**
|
|
10
|
+
* Has to be resolved before any data can be requested upon domain changes.
|
|
11
|
+
* @protected
|
|
12
|
+
*/
|
|
13
|
+
initializedPromise = Promise.resolve();
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* @param {import("../../../view/view.js").default} view
|
|
11
17
|
* @param {import("../../../spec/channel.js").PrimaryPositionalChannel} channel
|
|
@@ -78,6 +84,8 @@ export default class SingleAxisLazySource extends DataSource {
|
|
|
78
84
|
|
|
79
85
|
/**
|
|
80
86
|
* Convenience getter for genome.
|
|
87
|
+
*
|
|
88
|
+
* @protected
|
|
81
89
|
*/
|
|
82
90
|
get genome() {
|
|
83
91
|
return this.scaleResolution.getGenome();
|
|
@@ -88,14 +96,27 @@ export default class SingleAxisLazySource extends DataSource {
|
|
|
88
96
|
*
|
|
89
97
|
* @param {number[]} domain Linearized domain
|
|
90
98
|
* @param {import("../../../spec/genome.js").ChromosomalLocus[]} complexDomain Chrom/Pos domain
|
|
99
|
+
* @abstract
|
|
91
100
|
*/
|
|
92
101
|
async onDomainChanged(domain, complexDomain) {
|
|
93
102
|
// Override me
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Sets the loading status of the data source. The status is shown in the UI.
|
|
107
|
+
*
|
|
108
|
+
* @param {boolean} status true if loading, false otherwise
|
|
109
|
+
* @protected
|
|
110
|
+
*/
|
|
111
|
+
setLoadingStatus(status) {
|
|
112
|
+
this.view.context.setDataLoadingStatus(this.view, status);
|
|
113
|
+
}
|
|
114
|
+
|
|
96
115
|
/**
|
|
97
116
|
* TODO: Get rid of this method.
|
|
98
117
|
* Rendering should be requested by the collector.
|
|
118
|
+
*
|
|
119
|
+
* @protected
|
|
99
120
|
*/
|
|
100
121
|
requestRender() {
|
|
101
122
|
// An awfully hacky way.
|
|
@@ -109,15 +130,19 @@ export default class SingleAxisLazySource extends DataSource {
|
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
/**
|
|
133
|
+
* Resets the data flow and propagates the data.
|
|
112
134
|
*
|
|
113
|
-
* @param {import("../../flowNode.js").Datum[]} data
|
|
135
|
+
* @param {import("../../flowNode.js").Datum[][]} chunks An array of data chunks.
|
|
136
|
+
* @protected
|
|
114
137
|
*/
|
|
115
|
-
publishData(
|
|
138
|
+
publishData(chunks) {
|
|
116
139
|
this.reset();
|
|
117
140
|
this.beginBatch({ type: "file" });
|
|
118
141
|
|
|
119
|
-
for (const
|
|
120
|
-
|
|
142
|
+
for (const data of chunks) {
|
|
143
|
+
for (const d of data) {
|
|
144
|
+
this._propagate(d);
|
|
145
|
+
}
|
|
121
146
|
}
|
|
122
147
|
|
|
123
148
|
this.complete();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @abstract
|
|
3
|
+
*/
|
|
4
|
+
export default class SingleAxisWindowedSource extends SingleAxisLazySource {
|
|
5
|
+
/**
|
|
6
|
+
* @type {{windowSize?: number}}
|
|
7
|
+
* @protected
|
|
8
|
+
*/
|
|
9
|
+
protected params: {
|
|
10
|
+
windowSize?: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* @param {import("../../../spec/data.js").DebouncedData} debounceParams
|
|
14
|
+
* @protected
|
|
15
|
+
*/
|
|
16
|
+
protected setupDebouncing(debounceParams: import("../../../spec/data.js").DebouncedData): void;
|
|
17
|
+
/**
|
|
18
|
+
* Listen to the domain change event and update data when the covered windows change.
|
|
19
|
+
*
|
|
20
|
+
* @param {number[]} domain Linearized domain
|
|
21
|
+
*/
|
|
22
|
+
onDomainChanged(domain: number[]): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Listen to the domain change event and update data when the covered windows change.
|
|
25
|
+
*
|
|
26
|
+
* @param {number[]} interval linearized domain
|
|
27
|
+
* @protected
|
|
28
|
+
*/
|
|
29
|
+
protected loadInterval(interval: number[]): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Splits the interval into discrete chromosomal intervals – one for each chromosome –
|
|
32
|
+
* and loads the data for each of them. Handles abort signals and errors.
|
|
33
|
+
*
|
|
34
|
+
* @param {number[]} interval
|
|
35
|
+
* @param {(discreteInteval: import("@genome-spy/core/genome/genome.js").DiscreteChromosomeInterval, signal: AbortSignal) => Promise<T>} loader
|
|
36
|
+
* @return {Promise<T[]>}
|
|
37
|
+
* @template T
|
|
38
|
+
* @protected
|
|
39
|
+
*/
|
|
40
|
+
protected discretizeAndLoad<T>(interval: number[], loader: (discreteInteval: import("@genome-spy/core/genome/genome.js").DiscreteChromosomeInterval, signal: AbortSignal) => Promise<T>): Promise<T[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Returns three consecutive windows. The idea is to immediately have some data
|
|
43
|
+
* to show to the user when they pan the view. The windows are conceptually
|
|
44
|
+
* similar to "tiles" but they are never loaded separately.
|
|
45
|
+
*
|
|
46
|
+
* @param {number[]} interval
|
|
47
|
+
* @param {number} windowSize
|
|
48
|
+
* @protected
|
|
49
|
+
*/
|
|
50
|
+
protected quantizeInterval(interval: number[], windowSize: number): number[];
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* @param {number[]} interval
|
|
54
|
+
* @protected
|
|
55
|
+
*/
|
|
56
|
+
protected checkAndUpdateLastInterval(interval: number[]): boolean;
|
|
57
|
+
#private;
|
|
58
|
+
}
|
|
59
|
+
import SingleAxisLazySource from "./singleAxisLazySource.js";
|
|
60
|
+
//# sourceMappingURL=singleAxisWindowedSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleAxisWindowedSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/singleAxisWindowedSource.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IAQI;;;OAGG;IACH,kBAHU;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAC,CAGxB;IAEP;;;OAGG;IACH,0CAHW,OAAO,uBAAuB,EAAE,aAAa,QAmBvD;IAED;;;;OAIG;IACH,wBAFW,MAAM,EAAE,iBAiBlB;IAED;;;;;OAKG;IACH,iCAHW,MAAM,EAAE,iBAKlB;IAED;;;;;;;;;OASG;IACH,yCANW,MAAM,EAAE,4BACU,OAAO,mCAAmC,EAAE,0BAA0B,UAAU,WAAW,+BAqCvH;IAED;;;;;;;;OAQG;IACH,qCAJW,MAAM,EAAE,cACR,MAAM,YAWhB;IAED;;;;OAIG;IACH,+CAHW,MAAM,EAAE,WAUlB;;CACJ;iCAvJgC,2BAA2B"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import SingleAxisLazySource from "./singleAxisLazySource.js";
|
|
2
|
+
import { shallowArrayEquals } from "../../../utils/arrayUtils.js";
|
|
3
|
+
import { debounce } from "@genome-spy/core/utils/debounce.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @abstract
|
|
7
|
+
*/
|
|
8
|
+
export default class SingleAxisWindowedSource extends SingleAxisLazySource {
|
|
9
|
+
#abortController = new AbortController();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @type {number[]}
|
|
13
|
+
*/
|
|
14
|
+
#lastQuantizedInterval = [0, 0];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @type {{windowSize?: number}}
|
|
18
|
+
* @protected
|
|
19
|
+
*/
|
|
20
|
+
params;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {import("../../../spec/data.js").DebouncedData} debounceParams
|
|
24
|
+
* @protected
|
|
25
|
+
*/
|
|
26
|
+
setupDebouncing(debounceParams) {
|
|
27
|
+
if (debounceParams.debounce > 0) {
|
|
28
|
+
if (debounceParams.debounceMode == "domain") {
|
|
29
|
+
this.onDomainChanged = debounce(
|
|
30
|
+
this.onDomainChanged.bind(this),
|
|
31
|
+
debounceParams.debounce,
|
|
32
|
+
false
|
|
33
|
+
);
|
|
34
|
+
} else if (debounceParams.debounceMode == "window") {
|
|
35
|
+
this.loadInterval = debounce(
|
|
36
|
+
this.loadInterval.bind(this),
|
|
37
|
+
debounceParams.debounce,
|
|
38
|
+
false
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Listen to the domain change event and update data when the covered windows change.
|
|
46
|
+
*
|
|
47
|
+
* @param {number[]} domain Linearized domain
|
|
48
|
+
*/
|
|
49
|
+
async onDomainChanged(domain) {
|
|
50
|
+
const windowSize = this.params?.windowSize ?? -1;
|
|
51
|
+
|
|
52
|
+
if (domain[1] - domain[0] > windowSize) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const quantizedInterval = this.quantizeInterval(domain, windowSize);
|
|
57
|
+
|
|
58
|
+
if (this.checkAndUpdateLastInterval(quantizedInterval)) {
|
|
59
|
+
// Possible metadata must be loaded before the first request.
|
|
60
|
+
await this.initializedPromise;
|
|
61
|
+
|
|
62
|
+
this.loadInterval(quantizedInterval);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Listen to the domain change event and update data when the covered windows change.
|
|
68
|
+
*
|
|
69
|
+
* @param {number[]} interval linearized domain
|
|
70
|
+
* @protected
|
|
71
|
+
*/
|
|
72
|
+
async loadInterval(interval) {
|
|
73
|
+
// Override me if needed
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Splits the interval into discrete chromosomal intervals – one for each chromosome –
|
|
78
|
+
* and loads the data for each of them. Handles abort signals and errors.
|
|
79
|
+
*
|
|
80
|
+
* @param {number[]} interval
|
|
81
|
+
* @param {(discreteInteval: import("@genome-spy/core/genome/genome.js").DiscreteChromosomeInterval, signal: AbortSignal) => Promise<T>} loader
|
|
82
|
+
* @return {Promise<T[]>}
|
|
83
|
+
* @template T
|
|
84
|
+
* @protected
|
|
85
|
+
*/
|
|
86
|
+
async discretizeAndLoad(interval, loader) {
|
|
87
|
+
// Abort previous requests
|
|
88
|
+
this.#abortController.abort();
|
|
89
|
+
|
|
90
|
+
this.setLoadingStatus(true);
|
|
91
|
+
|
|
92
|
+
this.#abortController = new AbortController();
|
|
93
|
+
const signal = this.#abortController.signal;
|
|
94
|
+
|
|
95
|
+
// GMOD libraries expect a single chromosome/sequence for each request.
|
|
96
|
+
// Thus, we split the interval into discrete intervals representing
|
|
97
|
+
// individual chromosomes and load the data for each of them separately
|
|
98
|
+
// but in parallel.
|
|
99
|
+
const discreteChromosomeIntervals =
|
|
100
|
+
this.genome.continuousToDiscreteChromosomeIntervals(interval);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const resultByChrom = await Promise.all(
|
|
104
|
+
discreteChromosomeIntervals.map(async (d) => loader(d, signal))
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!signal.aborted) {
|
|
108
|
+
this.setLoadingStatus(false);
|
|
109
|
+
return resultByChrom;
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (!signal.aborted) {
|
|
113
|
+
// TODO: Nice reporting of errors
|
|
114
|
+
this.setLoadingStatus(false);
|
|
115
|
+
throw e;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns three consecutive windows. The idea is to immediately have some data
|
|
122
|
+
* to show to the user when they pan the view. The windows are conceptually
|
|
123
|
+
* similar to "tiles" but they are never loaded separately.
|
|
124
|
+
*
|
|
125
|
+
* @param {number[]} interval
|
|
126
|
+
* @param {number} windowSize
|
|
127
|
+
* @protected
|
|
128
|
+
*/
|
|
129
|
+
quantizeInterval(interval, windowSize) {
|
|
130
|
+
return [
|
|
131
|
+
Math.max(Math.floor(interval[0] / windowSize - 1) * windowSize, 0),
|
|
132
|
+
Math.min(
|
|
133
|
+
Math.ceil(interval[1] / windowSize + 1) * windowSize,
|
|
134
|
+
this.genome.totalSize // Perhaps scale domain should be used here
|
|
135
|
+
),
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
*
|
|
141
|
+
* @param {number[]} interval
|
|
142
|
+
* @protected
|
|
143
|
+
*/
|
|
144
|
+
checkAndUpdateLastInterval(interval) {
|
|
145
|
+
if (shallowArrayEquals(this.#lastQuantizedInterval, interval)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.#lastQuantizedInterval = interval;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -1,57 +1,23 @@
|
|
|
1
|
-
declare const TabixSource_base: {
|
|
2
|
-
new (...args: any[]): {
|
|
3
|
-
[x: string]: any;
|
|
4
|
-
lastQuantizedInterval: number[]; /**
|
|
5
|
-
* @param {import("../../../spec/data.js").TabixData} params
|
|
6
|
-
* @param {import("../../../view/view.js").default} view
|
|
7
|
-
*/
|
|
8
|
-
quantizeInterval(interval: number[], windowSize: number): number[];
|
|
9
|
-
checkAndUpdateLastInterval(interval: number[]): boolean;
|
|
10
|
-
};
|
|
11
|
-
} & typeof SingleAxisLazySource;
|
|
12
1
|
/**
|
|
13
2
|
* @template T
|
|
3
|
+
* @abstract
|
|
14
4
|
*/
|
|
15
|
-
export default class TabixSource<T> extends
|
|
5
|
+
export default class TabixSource<T> extends SingleAxisWindowedSource {
|
|
16
6
|
/**
|
|
17
7
|
* @param {import("../../../spec/data.js").TabixData} params
|
|
18
8
|
* @param {import("../../../view/view.js").default} view
|
|
19
9
|
*/
|
|
20
10
|
constructor(params: import("../../../spec/data.js").TabixData, view: import("../../../view/view.js").default);
|
|
21
|
-
/** Keep track of the order of the requests */
|
|
22
|
-
lastRequestId: number;
|
|
23
|
-
/** @type {import("@gmod/tabix").TabixIndexedFile} */
|
|
24
|
-
tbiIndex: import("@gmod/tabix").TabixIndexedFile;
|
|
25
11
|
params: import("../../../spec/data.js").TabixData;
|
|
26
|
-
/**
|
|
27
|
-
* Listen to the domain change event and update data when the covered windows change.
|
|
28
|
-
*
|
|
29
|
-
* @param {number[]} domain Linearized domain
|
|
30
|
-
*/
|
|
31
|
-
onDomainChanged(domain: number[]): Promise<void>;
|
|
32
12
|
initializedPromise: Promise<any>;
|
|
33
|
-
/**
|
|
34
|
-
* Listen to the domain change event and update data when the covered windows change.
|
|
35
|
-
*
|
|
36
|
-
* @param {number[]} interval linearized domain
|
|
37
|
-
*/
|
|
38
|
-
doRequest(interval: number[]): Promise<void>;
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
* @param {number[]} interval
|
|
42
|
-
*/
|
|
43
|
-
getFeatures(interval: number[]): Promise<{
|
|
44
|
-
requestId: number;
|
|
45
|
-
abort: () => void;
|
|
46
|
-
features: T[];
|
|
47
|
-
}>;
|
|
48
13
|
/**
|
|
49
14
|
* @abstract
|
|
15
|
+
* @protected
|
|
50
16
|
* @param {string[]} lines
|
|
51
17
|
* @returns {T[]}
|
|
52
18
|
*/
|
|
53
|
-
_parseFeatures(lines: string[]): T[];
|
|
19
|
+
protected _parseFeatures(lines: string[]): T[];
|
|
20
|
+
#private;
|
|
54
21
|
}
|
|
55
|
-
import
|
|
56
|
-
export {};
|
|
22
|
+
import SingleAxisWindowedSource from "./singleAxisWindowedSource.js";
|
|
57
23
|
//# sourceMappingURL=tabixSource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabixSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/tabixSource.js"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"tabixSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/tabixSource.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH;IAII;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,SAAS,QACzC,OAAO,uBAAuB,EAAE,OAAO,EA8CjD;IAhCG,kDAAgC;IAQhC,iCAuBE;IAoCN;;;;;OAKG;IACH,gCAHW,MAAM,EAAE,GACN,CAAC,EAAE,CAKf;;CACJ;qCAvGoC,+BAA+B"}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import SingleAxisLazySource from "./singleAxisLazySource.js";
|
|
2
|
-
import windowedMixin from "./windowedMixin.js";
|
|
3
|
-
import { debounce } from "../../../utils/debounce.js";
|
|
4
1
|
import addBaseUrl from "../../../utils/addBaseUrl.js";
|
|
2
|
+
import SingleAxisWindowedSource from "./singleAxisWindowedSource.js";
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* @template T
|
|
6
|
+
* @abstract
|
|
8
7
|
*/
|
|
9
|
-
export default class TabixSource extends
|
|
10
|
-
/** Keep track of the order of the requests */
|
|
11
|
-
lastRequestId = 0;
|
|
12
|
-
|
|
8
|
+
export default class TabixSource extends SingleAxisWindowedSource {
|
|
13
9
|
/** @type {import("@gmod/tabix").TabixIndexedFile} */
|
|
14
|
-
tbiIndex;
|
|
10
|
+
#tbiIndex;
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* @param {import("../../../spec/data.js").TabixData} params
|
|
@@ -22,7 +18,8 @@ export default class TabixSource extends windowedMixin(SingleAxisLazySource) {
|
|
|
22
18
|
const paramsWithDefaults = {
|
|
23
19
|
channel: "x",
|
|
24
20
|
windowSize: 3_000_000,
|
|
25
|
-
|
|
21
|
+
debounce: 200,
|
|
22
|
+
debounceMode: "domain",
|
|
26
23
|
...params,
|
|
27
24
|
};
|
|
28
25
|
|
|
@@ -34,13 +31,7 @@ export default class TabixSource extends windowedMixin(SingleAxisLazySource) {
|
|
|
34
31
|
throw new Error("No URL provided for TabixSource");
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
this.onDomainChanged = debounce(
|
|
39
|
-
this.onDomainChanged.bind(this),
|
|
40
|
-
this.params.debounceDomainChange,
|
|
41
|
-
false
|
|
42
|
-
);
|
|
43
|
-
}
|
|
34
|
+
this.setupDebouncing(this.params);
|
|
44
35
|
|
|
45
36
|
this.initializedPromise = new Promise((resolve) => {
|
|
46
37
|
Promise.all([
|
|
@@ -56,7 +47,7 @@ export default class TabixSource extends windowedMixin(SingleAxisLazySource) {
|
|
|
56
47
|
const withBase = (/** @type {string} */ uri) =>
|
|
57
48
|
new RemoteFile(addBaseUrl(uri, this.view.getBaseUrl()));
|
|
58
49
|
|
|
59
|
-
this
|
|
50
|
+
this.#tbiIndex = new TabixIndexedFile({
|
|
60
51
|
filehandle: withBase(this.params.url),
|
|
61
52
|
tbiFilehandle: withBase(
|
|
62
53
|
this.params.indexUrl ?? this.params.url + ".tbi"
|
|
@@ -68,87 +59,47 @@ export default class TabixSource extends windowedMixin(SingleAxisLazySource) {
|
|
|
68
59
|
});
|
|
69
60
|
}
|
|
70
61
|
|
|
71
|
-
/**
|
|
72
|
-
* Listen to the domain change event and update data when the covered windows change.
|
|
73
|
-
*
|
|
74
|
-
* @param {number[]} domain Linearized domain
|
|
75
|
-
*/
|
|
76
|
-
async onDomainChanged(domain) {
|
|
77
|
-
const windowSize = this.params.windowSize;
|
|
78
|
-
|
|
79
|
-
if (domain[1] - domain[0] > windowSize) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const quantizedInterval = this.quantizeInterval(domain, windowSize);
|
|
84
|
-
|
|
85
|
-
if (this.checkAndUpdateLastInterval(quantizedInterval)) {
|
|
86
|
-
this.doRequest(quantizedInterval);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
62
|
/**
|
|
91
63
|
* Listen to the domain change event and update data when the covered windows change.
|
|
92
64
|
*
|
|
93
65
|
* @param {number[]} interval linearized domain
|
|
94
66
|
*/
|
|
95
|
-
async
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (featureResponse.requestId < this.lastRequestId) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.publishData(featureResponse.features);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
*
|
|
108
|
-
* @param {number[]} interval
|
|
109
|
-
*/
|
|
110
|
-
async getFeatures(interval) {
|
|
111
|
-
await this.initializedPromise;
|
|
112
|
-
|
|
113
|
-
let requestId = ++this.lastRequestId;
|
|
114
|
-
|
|
115
|
-
// TODO: Abort previous requests
|
|
116
|
-
const abortController = new AbortController();
|
|
117
|
-
|
|
118
|
-
const discreteChromosomeIntervals =
|
|
119
|
-
this.genome.continuousToDiscreteChromosomeIntervals(interval);
|
|
120
|
-
|
|
121
|
-
// TODO: Error handling
|
|
122
|
-
const featuresWithChrom = await Promise.all(
|
|
123
|
-
discreteChromosomeIntervals.map(async (d) => {
|
|
67
|
+
async loadInterval(interval) {
|
|
68
|
+
const featureChunks = await this.discretizeAndLoad(
|
|
69
|
+
interval,
|
|
70
|
+
async (discreteInterval, signal) => {
|
|
124
71
|
/** @type {string[]} */
|
|
125
72
|
const lines = [];
|
|
126
73
|
|
|
127
|
-
await this
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
74
|
+
await this.#tbiIndex.getLines(
|
|
75
|
+
discreteInterval.chrom,
|
|
76
|
+
discreteInterval.startPos,
|
|
77
|
+
discreteInterval.endPos,
|
|
78
|
+
{
|
|
79
|
+
lineCallback: (line) => {
|
|
80
|
+
lines.push(line);
|
|
81
|
+
},
|
|
82
|
+
signal,
|
|
83
|
+
}
|
|
84
|
+
);
|
|
133
85
|
|
|
134
|
-
// Hmm. It's silly that we have to first collect individual lines and then join them.
|
|
135
86
|
return this._parseFeatures(lines);
|
|
136
|
-
}
|
|
87
|
+
}
|
|
137
88
|
);
|
|
138
89
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
features: featuresWithChrom.flat(), // TODO: Use batches, not flat
|
|
143
|
-
};
|
|
90
|
+
if (featureChunks) {
|
|
91
|
+
this.publishData(featureChunks);
|
|
92
|
+
}
|
|
144
93
|
}
|
|
145
94
|
|
|
146
95
|
/**
|
|
147
96
|
* @abstract
|
|
97
|
+
* @protected
|
|
148
98
|
* @param {string[]} lines
|
|
149
99
|
* @returns {T[]}
|
|
150
100
|
*/
|
|
151
101
|
_parseFeatures(lines) {
|
|
102
|
+
// Override me
|
|
152
103
|
return [];
|
|
153
104
|
}
|
|
154
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regexFold.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/regexFold.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IAKI;;OAEG;IACH,oBAFW,OAAO,yBAAyB,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"regexFold.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/regexFold.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IAKI;;OAEG;IACH,oBAFW,OAAO,yBAAyB,EAAE,eAAe,EAmI3D;IAfO,gBALO,GAAG,UAKe;CAgBpC;qBAjJsD,gBAAgB"}
|
|
@@ -48,6 +48,14 @@ export default class RegexFoldTransform extends FlowNode {
|
|
|
48
48
|
const detectColumns = (datum) => {
|
|
49
49
|
const colNames = /** @type {string[]} */ (Object.keys(datum));
|
|
50
50
|
|
|
51
|
+
for (const re of columnRegex) {
|
|
52
|
+
if (!colNames.some((colName) => re.test(colName))) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`No columns matching the regex ${re.toString()} found in the data!`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
/** @type {Map<string, string[]>} */
|
|
52
60
|
const sampleColMap = new Map();
|
|
53
61
|
|
|
@@ -157,4 +157,32 @@ describe("RegexFold", () => {
|
|
|
157
157
|
},
|
|
158
158
|
]);
|
|
159
159
|
});
|
|
160
|
+
|
|
161
|
+
test("Throws error if no columns match the regex", () => {
|
|
162
|
+
const sampleData = [
|
|
163
|
+
{
|
|
164
|
+
row: 1,
|
|
165
|
+
sample1_a: "r1s1a",
|
|
166
|
+
sample2_a: "r1s2a",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
row: 2,
|
|
170
|
+
sample1_a: "r2s1a",
|
|
171
|
+
sample2_a: "r2s2a",
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
/** @type { import("../../spec/transform.js").RegexFoldParams } */
|
|
176
|
+
const singleGatherConfig = {
|
|
177
|
+
type: "regexFold",
|
|
178
|
+
columnRegex: "^(.*)_c$",
|
|
179
|
+
asValue: "a",
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
expect(() =>
|
|
183
|
+
processData(new RegexFoldTransform(singleGatherConfig), sampleData)
|
|
184
|
+
).toThrowError(
|
|
185
|
+
"No columns matching the regex /^(.*)_c$/ found in the data!"
|
|
186
|
+
);
|
|
187
|
+
});
|
|
160
188
|
});
|
|
@@ -70,7 +70,9 @@ export default class AccessorFactory {
|
|
|
70
70
|
* @param {string} expr
|
|
71
71
|
*/
|
|
72
72
|
function createExpressionAccessor(expr) {
|
|
73
|
-
const
|
|
74
|
-
accessor
|
|
73
|
+
const fn = createFunction(expr);
|
|
74
|
+
const accessor = /** @type {Accessor} */ (/** @type {any} */ (fn));
|
|
75
|
+
// Not bulletproof and probably erroneous with global params
|
|
76
|
+
accessor.constant = accessor.fields.length == 0;
|
|
75
77
|
return accessor;
|
|
76
78
|
}
|
package/dist/src/genomeSpy.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export default class GenomeSpy {
|
|
|
13
13
|
*/
|
|
14
14
|
constructor(container: HTMLElement, spec: import("./spec/root.js").RootSpec, options?: import("./types/embedApi.js").EmbedOptions);
|
|
15
15
|
container: HTMLElement;
|
|
16
|
+
/** @type {(() => void)[]} */
|
|
17
|
+
_destructionCallbacks: (() => void)[];
|
|
16
18
|
/** Root level configuration object */
|
|
17
19
|
spec: import("./spec/root.js").RootSpec;
|
|
18
20
|
accessorFactory: AccessorFactory;
|
|
@@ -67,6 +69,13 @@ export default class GenomeSpy {
|
|
|
67
69
|
tooltipHandlers: Record<string, import("./tooltip/tooltipHandler.js").TooltipHandler>;
|
|
68
70
|
/** @type {View} */
|
|
69
71
|
viewRoot: import("./view/view.js").default;
|
|
72
|
+
_paramBroker: ParamBroker;
|
|
73
|
+
/**
|
|
74
|
+
* Views that are currently loading data using lazy sources.
|
|
75
|
+
*
|
|
76
|
+
* @type {Map<View, boolean>}
|
|
77
|
+
*/
|
|
78
|
+
_loadingViews: Map<import("./view/view.js").default, boolean>;
|
|
70
79
|
/**
|
|
71
80
|
*
|
|
72
81
|
* @param {(name: string) => any[]} provider
|
|
@@ -89,9 +98,15 @@ export default class GenomeSpy {
|
|
|
89
98
|
* @param {any} [payload]
|
|
90
99
|
*/
|
|
91
100
|
broadcast(type: BroadcastEventType, payload?: any): void;
|
|
101
|
+
/**
|
|
102
|
+
* Draw some layers on top of the canvas. It's easier to do fancy spinning
|
|
103
|
+
* animations with html elements than with WebGL.
|
|
104
|
+
*/
|
|
105
|
+
_updateLoadingIndicators(): void;
|
|
92
106
|
_prepareContainer(): void;
|
|
93
107
|
_glHelper: WebGLHelper;
|
|
94
108
|
loadingMessageElement: HTMLDivElement;
|
|
109
|
+
loadingIndicatorsElement: HTMLDivElement;
|
|
95
110
|
tooltip: Tooltip;
|
|
96
111
|
/**
|
|
97
112
|
* Unregisters all listeners, removes all created dom elements, removes all css classes from the container
|
|
@@ -135,6 +150,7 @@ import Animator from "./utils/animator.js";
|
|
|
135
150
|
import GenomeStore from "./genome/genomeStore.js";
|
|
136
151
|
import BufferedViewRenderingContext from "./view/renderingContext/bufferedViewRenderingContext.js";
|
|
137
152
|
import Inertia from "./utils/inertia.js";
|
|
153
|
+
import ParamBroker from "./paramBroker.js";
|
|
138
154
|
import WebGLHelper from "./gl/webGLHelper.js";
|
|
139
155
|
import Tooltip from "./utils/ui/tooltip.js";
|
|
140
156
|
import UnitView from "./view/unitView.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"AA6CA;IACI;;;;;OAKG;IAEH;;;;;OAKG;IACH,uBAJW,WAAW,qDAEX,OAAO,qBAAqB,EAAE,YAAY,EAyFpD;IAtFG,uBAA0B;IAE1B,6BAA6B;IAC7B,uBADW,CAAC,MAAM,IAAI,CAAC,EAAE,CACM;IAM/B,sCAAsC;IACtC,wCAAgB;IAEhB,iCAA4C;IAC5C,yBAAoC;IAEpC,4CAA4C;IAC5C,oBADW,QAAU,MAAM,KAAE,MAAM,EAAE,CAAC,EAAE,CACZ;IAE5B,mBAAoD;IAEpD,0BAA0B;IAC1B,aADW,WAAW,CACM;IAE5B;;;;;OAKG;IACH,qEAF0B,OAAO,CAE8B;IAE/D,2CAA2C;IAC3C,mBADW,4BAA4B,CACL;IAClC,2CAA2C;IAC3C,iBADW,4BAA4B,CACP;IAEhC,oDAAoD;IACpD,6BAAgC;IAEhC;;;OAGG;IACH,eAFU;QAAE,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,oBAAoB,EAAE,KAAK,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAEpF;IAE9B,uBAA+C;IAE/C;;;OAGG;IACH,oBAFU,IAAI,MAAM,EAAE,QAAU,aAAa,KAAE,IAAI,CAAC,EAAE,CAAC,CAEpB;IAEnC;;;;;;OAMG;IACH,yCAFkC,GAAG,KAAK,IAAI,GAEd;IAEhC;;;OAGG;IACH,kDAFkC,GAAG,KAAK,IAAI,GAEL;IAEzC,oFAAoF;IACpF,iBADW,OAAO,MAAM,EAAE,OAAO,6BAA6B,EAAE,cAAc,CAAC,CAK9E;IAED,mBAAmB;IACnB,2CAAyB;IAEzB,0BAAqC;IAErC;;;;OAIG;IACH,8DAA8B;IAGlC;;;OAGG;IACH,2CAFkB,MAAM,KAAK,GAAG,EAAE,QAIjC;IAED;;OAEG;IACH,+BAFW,MAAM,YAShB;IAED;;;;OAIG;IACH,sBAHW,MAAM,QACN,GAAG,EAAE,QAaf;IAED;;;;;OAKG;IACH,gBAHW,kBAAkB,YAClB,GAAG,QAQb;IAED;;;OAGG;IACH,iCAyCC;IAED,0BA0EC;IAtEG,uBAOC;IAyCD,sCAA0D;IAS1D,yCAA6D;IAI7D,iBAA0C;IAW9C;;OAEG;IACH,gBAmBC;IAED,sCA8MC;IAED;;;OAGG;IACH,UAFa,QAAQ,OAAO,CAAC,CA6B5B;IAED,4BA6IC;IAjIe,iCAAoC;IAmIpD;;;OAGG;IACH,kBAHW,MAAM,KACN,MAAM,QAiEhB;IAED;;;;;;;OAOG;IACH,oDAHuB,QAAQ,MAAM,GAAG,WAAW,GAAG,OAAO,KAAK,EAAE,cAAc,CAAC,QAYlF;IAED,sBAyCC;IAED,kBAIC;IAED,iCAOC;IAED,iCASC;IAED,qFAWC;CACJ;;;;iCA51BY,eAAe,GAAG,YAAY,GAAG,QAAQ,GAAG,gBAAgB;4BAhC7C,uBAAuB;4BA0BP,uBAAuB;qBAZ9C,qBAAqB;wBAIlB,yBAAyB;yCARR,yDAAyD;oBAYvD,oBAAoB;wBAMvC,kBAAkB;wBApBlB,qBAAqB;oBAVzB,uBAAuB;qBAQtB,oBAAoB"}
|