@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.
Files changed (104) hide show
  1. package/dist/bundle/index--cKb-dKG.js +615 -0
  2. package/dist/bundle/{index-gn8bhQ8w.js → index-d7k3kkin.js} +365 -366
  3. package/dist/bundle/index.es.js +4260 -3928
  4. package/dist/bundle/index.js +115 -80
  5. package/dist/schema.json +254 -52
  6. package/dist/src/data/sources/dynamic/axisGenomeSource.js +1 -1
  7. package/dist/src/data/sources/dynamic/axisTickSource.js +3 -3
  8. package/dist/src/data/sources/dynamic/bamSource.d.ts +3 -21
  9. package/dist/src/data/sources/dynamic/bamSource.d.ts.map +1 -1
  10. package/dist/src/data/sources/dynamic/bamSource.js +38 -55
  11. package/dist/src/data/sources/dynamic/bigBedSource.d.ts +2 -38
  12. package/dist/src/data/sources/dynamic/bigBedSource.d.ts.map +1 -1
  13. package/dist/src/data/sources/dynamic/bigBedSource.js +14 -71
  14. package/dist/src/data/sources/dynamic/bigWigSource.d.ts +4 -42
  15. package/dist/src/data/sources/dynamic/bigWigSource.d.ts.map +1 -1
  16. package/dist/src/data/sources/dynamic/bigWigSource.js +23 -60
  17. package/dist/src/data/sources/dynamic/gff3Source.d.ts.map +1 -1
  18. package/dist/src/data/sources/dynamic/gff3Source.js +1 -0
  19. package/dist/src/data/sources/dynamic/indexedFastaSource.d.ts +2 -20
  20. package/dist/src/data/sources/dynamic/indexedFastaSource.d.ts.map +1 -1
  21. package/dist/src/data/sources/dynamic/indexedFastaSource.js +23 -41
  22. package/dist/src/data/sources/dynamic/singleAxisLazySource.d.ts +23 -4
  23. package/dist/src/data/sources/dynamic/singleAxisLazySource.d.ts.map +1 -1
  24. package/dist/src/data/sources/dynamic/singleAxisLazySource.js +29 -4
  25. package/dist/src/data/sources/dynamic/singleAxisWindowedSource.d.ts +60 -0
  26. package/dist/src/data/sources/dynamic/singleAxisWindowedSource.d.ts.map +1 -0
  27. package/dist/src/data/sources/dynamic/singleAxisWindowedSource.js +152 -0
  28. package/dist/src/data/sources/dynamic/tabixSource.d.ts +6 -40
  29. package/dist/src/data/sources/dynamic/tabixSource.d.ts.map +1 -1
  30. package/dist/src/data/sources/dynamic/tabixSource.js +29 -78
  31. package/dist/src/data/transforms/regexFold.d.ts.map +1 -1
  32. package/dist/src/data/transforms/regexFold.js +8 -0
  33. package/dist/src/data/transforms/regexFold.test.js +28 -0
  34. package/dist/src/encoder/accessor.js +4 -2
  35. package/dist/src/genomeSpy.d.ts +16 -0
  36. package/dist/src/genomeSpy.d.ts.map +1 -1
  37. package/dist/src/genomeSpy.js +119 -8
  38. package/dist/src/gl/link.vertex.glsl.js +1 -1
  39. package/dist/src/gl/point.common.glsl.js +2 -0
  40. package/dist/src/gl/point.fragment.glsl.js +1 -1
  41. package/dist/src/gl/point.vertex.glsl.js +1 -1
  42. package/dist/src/gl/rect.vertex.glsl.js +1 -1
  43. package/dist/src/gl/rule.common.glsl.js +2 -0
  44. package/dist/src/gl/rule.fragment.glsl.js +1 -1
  45. package/dist/src/gl/rule.vertex.glsl.js +1 -1
  46. package/dist/src/gl/text.common.glsl.js +2 -0
  47. package/dist/src/gl/text.fragment.glsl.js +1 -1
  48. package/dist/src/gl/text.vertex.glsl.js +1 -1
  49. package/dist/src/gl/webGLHelper.d.ts +6 -21
  50. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  51. package/dist/src/gl/webGLHelper.js +7 -38
  52. package/dist/src/img/90-ring-with-bg.svg +1 -0
  53. package/dist/src/img/README.md +5 -0
  54. package/dist/src/marks/link.d.ts +7 -0
  55. package/dist/src/marks/link.d.ts.map +1 -1
  56. package/dist/src/marks/link.js +99 -50
  57. package/dist/src/marks/mark.d.ts +34 -0
  58. package/dist/src/marks/mark.d.ts.map +1 -1
  59. package/dist/src/marks/mark.js +83 -1
  60. package/dist/src/marks/pointMark.d.ts.map +1 -1
  61. package/dist/src/marks/pointMark.js +21 -9
  62. package/dist/src/marks/rectMark.d.ts +1 -2
  63. package/dist/src/marks/rectMark.d.ts.map +1 -1
  64. package/dist/src/marks/rectMark.js +28 -17
  65. package/dist/src/marks/rule.d.ts.map +1 -1
  66. package/dist/src/marks/rule.js +17 -6
  67. package/dist/src/marks/text.d.ts.map +1 -1
  68. package/dist/src/marks/text.js +32 -18
  69. package/dist/src/paramBroker.d.ts +30 -0
  70. package/dist/src/paramBroker.d.ts.map +1 -0
  71. package/dist/src/paramBroker.js +102 -0
  72. package/dist/src/spec/data.d.ts +28 -13
  73. package/dist/src/spec/mark.d.ts +27 -26
  74. package/dist/src/spec/view.d.ts +2 -1
  75. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  76. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  77. package/dist/src/styles/genome-spy.css.js +33 -4
  78. package/dist/src/styles/genome-spy.scss +40 -4
  79. package/dist/src/types/viewContext.d.ts +11 -0
  80. package/dist/src/utils/binnedIndex.d.ts +2 -0
  81. package/dist/src/utils/binnedIndex.d.ts.map +1 -1
  82. package/dist/src/utils/binnedIndex.js +59 -10
  83. package/dist/src/utils/binnedIndex.test.js +46 -0
  84. package/dist/src/utils/expression.d.ts +12 -2
  85. package/dist/src/utils/expression.d.ts.map +1 -1
  86. package/dist/src/utils/expression.js +68 -9
  87. package/dist/src/utils/linearstep.d.ts +7 -0
  88. package/dist/src/utils/linearstep.d.ts.map +1 -0
  89. package/dist/src/utils/linearstep.js +10 -0
  90. package/dist/src/view/gridView.d.ts.map +1 -1
  91. package/dist/src/view/gridView.js +2 -0
  92. package/dist/src/view/layerView.d.ts.map +1 -1
  93. package/dist/src/view/layerView.js +2 -0
  94. package/dist/src/view/unitView.d.ts +0 -6
  95. package/dist/src/view/unitView.d.ts.map +1 -1
  96. package/dist/src/view/unitView.js +2 -9
  97. package/dist/src/view/view.d.ts +6 -0
  98. package/dist/src/view/view.d.ts.map +1 -1
  99. package/dist/src/view/view.js +17 -0
  100. package/package.json +3 -3
  101. package/dist/bundle/index-Cbz74kpR.js +0 -638
  102. package/dist/src/data/sources/dynamic/windowedMixin.d.ts +0 -32
  103. package/dist/src/data/sources/dynamic/windowedMixin.d.ts.map +0 -1
  104. 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(data) {
138
+ publishData(chunks) {
116
139
  this.reset();
117
140
  this.beginBatch({ type: "file" });
118
141
 
119
- for (const d of data) {
120
- this._propagate(d);
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 TabixSource_base {
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 SingleAxisLazySource from "./singleAxisLazySource.js";
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":";;;yCAeI;;;WAGG;;;;;AAbP;;GAEG;AACH;IAOI;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,SAAS,QACzC,OAAO,uBAAuB,EAAE,OAAO,EAmDjD;IA3DD,8CAA8C;IAC9C,sBAAkB;IAElB,qDAAqD;IACrD,UADW,OAAO,aAAa,EAAE,gBAAgB,CACxC;IAiBL,kDAAgC;IAwCpC;;;;OAIG;IACH,wBAFW,MAAM,EAAE,iBAclB;IA3CG,iCAuBE;IAsBN;;;;OAIG;IACH,oBAFW,MAAM,EAAE,iBAWlB;IAED;;;OAGG;IACH,sBAFW,MAAM,EAAE;;;;OAoClB;IAED;;;;OAIG;IACH,sBAHW,MAAM,EAAE,GACN,CAAC,EAAE,CAIf;CACJ;iCAzJgC,2BAA2B"}
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 windowedMixin(SingleAxisLazySource) {
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
- debounceDomainChange: 200,
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
- if (this.params.debounceDomainChange > 0) {
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.tbiIndex = new TabixIndexedFile({
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 doRequest(interval) {
96
- const featureResponse = await this.getFeatures(interval);
97
-
98
- // Discard late responses
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.tbiIndex.getLines(d.chrom, d.startPos, d.endPos, {
128
- lineCallback: (line) => {
129
- lines.push(line);
130
- },
131
- signal: abortController.signal,
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
- return {
140
- requestId,
141
- abort: () => abortController.abort(),
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,EA2H3D;IAfO,gBALO,GAAG,UAKe;CAgBpC;qBAzIsD,gBAAgB"}
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 accessor = /** @type {Accessor} */ (createFunction(expr));
74
- accessor.constant = accessor.fields.length == 0; // Not bulletproof, eh
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
  }
@@ -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":"AAyCA;IACI;;;;;OAKG;IAEH;;;;;OAKG;IACH,uBAJW,WAAW,qDAEX,OAAO,qBAAqB,EAAE,YAAY,EA6EpD;IA1EG,uBAA0B;IAM1B,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;IAG7B;;;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,0BA2BC;IAvBG,uBAOC;IAED,sCAA0D;IAK1D,iBAA0C;IAW9C;;OAEG;IACH,gBAiBC;IAED,sCAuMC;IAED;;;OAGG;IACH,UAFa,QAAQ,OAAO,CAAC,CAqC5B;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;;;;iCAjvBY,eAAe,GAAG,YAAY,GAAG,QAAQ,GAAG,gBAAgB;4BA/B7C,uBAAuB;4BA0BP,uBAAuB;qBAZ9C,qBAAqB;wBAIlB,yBAAyB;yCARR,yDAAyD;oBAYvD,oBAAoB;wBAdvC,qBAAqB;oBAVzB,uBAAuB;qBAQtB,oBAAoB"}
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"}