@genome-spy/core 0.41.0 → 0.42.1

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 (117) 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 +4225 -4040
  4. package/dist/bundle/index.js +122 -79
  5. package/dist/schema.json +58 -6
  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/encoder.d.ts +6 -1
  35. package/dist/src/encoder/encoder.d.ts.map +1 -1
  36. package/dist/src/encoder/encoder.js +10 -0
  37. package/dist/src/genomeSpy.d.ts +14 -0
  38. package/dist/src/genomeSpy.d.ts.map +1 -1
  39. package/dist/src/genomeSpy.js +114 -8
  40. package/dist/src/gl/arrayBuilder.js +1 -1
  41. package/dist/src/gl/colorUtils.d.ts.map +1 -0
  42. package/dist/src/{scale → gl}/colorUtils.js +1 -1
  43. package/dist/src/gl/dataToVertices.d.ts +1 -9
  44. package/dist/src/gl/dataToVertices.d.ts.map +1 -1
  45. package/dist/src/gl/dataToVertices.js +33 -73
  46. package/dist/src/{scale → gl}/glslScaleGenerator.d.ts +23 -1
  47. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -0
  48. package/dist/src/{scale → gl}/glslScaleGenerator.js +59 -8
  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 +8 -39
  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 +74 -39
  57. package/dist/src/marks/mark.d.ts +2 -1
  58. package/dist/src/marks/mark.d.ts.map +1 -1
  59. package/dist/src/marks/mark.js +31 -2
  60. package/dist/src/marks/{pointMark.d.ts → point.d.ts} +1 -1
  61. package/dist/src/marks/point.d.ts.map +1 -0
  62. package/dist/src/marks/{pointMark.js → point.js} +3 -3
  63. package/dist/src/marks/{rectMark.d.ts → rect.d.ts} +1 -1
  64. package/dist/src/marks/rect.d.ts.map +1 -0
  65. package/dist/src/marks/{rectMark.js → rect.js} +2 -3
  66. package/dist/src/marks/rect.vertex.glsl.js +2 -0
  67. package/dist/src/marks/rule.js +3 -3
  68. package/dist/src/marks/text.d.ts.map +1 -1
  69. package/dist/src/marks/text.js +19 -20
  70. package/dist/src/spec/data.d.ts +28 -13
  71. package/dist/src/spec/mark.d.ts +0 -8
  72. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  73. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  74. package/dist/src/styles/genome-spy.css.js +33 -4
  75. package/dist/src/styles/genome-spy.scss +40 -4
  76. package/dist/src/types/viewContext.d.ts +9 -0
  77. package/dist/src/utils/binnedIndex.d.ts +2 -0
  78. package/dist/src/utils/binnedIndex.d.ts.map +1 -1
  79. package/dist/src/utils/binnedIndex.js +59 -10
  80. package/dist/src/utils/binnedIndex.test.js +46 -0
  81. package/dist/src/utils/indexer.d.ts.map +1 -1
  82. package/dist/src/utils/indexer.js +10 -1
  83. package/dist/src/utils/indexer.test.js +2 -0
  84. package/dist/src/view/gridView.d.ts.map +1 -1
  85. package/dist/src/view/gridView.js +2 -0
  86. package/dist/src/view/layerView.d.ts.map +1 -1
  87. package/dist/src/view/layerView.js +2 -0
  88. package/dist/src/view/unitView.d.ts +1 -7
  89. package/dist/src/view/unitView.d.ts.map +1 -1
  90. package/dist/src/view/unitView.js +4 -11
  91. package/dist/src/view/view.d.ts +6 -0
  92. package/dist/src/view/view.d.ts.map +1 -1
  93. package/dist/src/view/view.js +11 -0
  94. package/dist/src/view/view.test.js +1 -1
  95. package/package.json +3 -3
  96. package/dist/bundle/index-Cbz74kpR.js +0 -638
  97. package/dist/src/data/sources/dynamic/windowedMixin.d.ts +0 -32
  98. package/dist/src/data/sources/dynamic/windowedMixin.d.ts.map +0 -1
  99. package/dist/src/data/sources/dynamic/windowedMixin.js +0 -53
  100. package/dist/src/gl/rect.vertex.glsl.js +0 -2
  101. package/dist/src/marks/pointMark.d.ts.map +0 -1
  102. package/dist/src/marks/rectMark.d.ts.map +0 -1
  103. package/dist/src/scale/colorUtils.d.ts.map +0 -1
  104. package/dist/src/scale/glslScaleGenerator.d.ts.map +0 -1
  105. /package/dist/src/{scale → gl}/colorUtils.d.ts +0 -0
  106. /package/dist/src/{gl → marks}/link.fragment.glsl.js +0 -0
  107. /package/dist/src/{gl → marks}/link.vertex.glsl.js +0 -0
  108. /package/dist/src/{gl → marks}/point.common.glsl.js +0 -0
  109. /package/dist/src/{gl → marks}/point.fragment.glsl.js +0 -0
  110. /package/dist/src/{gl → marks}/point.vertex.glsl.js +0 -0
  111. /package/dist/src/{gl → marks}/rect.fragment.glsl.js +0 -0
  112. /package/dist/src/{gl → marks}/rule.common.glsl.js +0 -0
  113. /package/dist/src/{gl → marks}/rule.fragment.glsl.js +0 -0
  114. /package/dist/src/{gl → marks}/rule.vertex.glsl.js +0 -0
  115. /package/dist/src/{gl → marks}/text.common.glsl.js +0 -0
  116. /package/dist/src/{gl → marks}/text.fragment.glsl.js +0 -0
  117. /package/dist/src/{gl → marks}/text.vertex.glsl.js +0 -0
@@ -1,20 +1,15 @@
1
- import { debounce } from "../../../utils/debounce.js";
2
- import SingleAxisLazySource from "./singleAxisLazySource.js";
3
- import windowedMixin from "./windowedMixin.js";
4
1
  import addBaseUrl from "../../../utils/addBaseUrl.js";
2
+ import SingleAxisWindowedSource from "./singleAxisWindowedSource.js";
5
3
 
6
4
  /**
7
5
  *
8
6
  */
9
- export default class BigWigSource extends windowedMixin(SingleAxisLazySource) {
7
+ export default class BigWigSource extends SingleAxisWindowedSource {
10
8
  /** @type {number[]} */
11
- reductionLevels = [];
12
-
13
- /** Keep track of the order of the requests */
14
- lastRequestId = 0;
9
+ #reductionLevels = [];
15
10
 
16
11
  /** @type {import("@gmod/bbi").BigWig} */
17
- bbi;
12
+ #bbi;
18
13
 
19
14
  /**
20
15
  * @param {import("../../../spec/data.js").BigWigData} params
@@ -25,6 +20,8 @@ export default class BigWigSource extends windowedMixin(SingleAxisLazySource) {
25
20
  const paramsWithDefaults = {
26
21
  pixelsPerBin: 2,
27
22
  channel: "x",
23
+ debounce: 200,
24
+ debounceMode: "window",
28
25
  ...params,
29
26
  };
30
27
 
@@ -36,25 +33,21 @@ export default class BigWigSource extends windowedMixin(SingleAxisLazySource) {
36
33
  throw new Error("No URL provided for BigWigSource");
37
34
  }
38
35
 
39
- this.doDebouncedRequest = debounce(
40
- this.doRequest.bind(this),
41
- 200,
42
- false
43
- );
36
+ this.setupDebouncing(this.params);
44
37
 
45
38
  this.initializedPromise = new Promise((resolve) => {
46
39
  Promise.all([
47
40
  import("@gmod/bbi"),
48
41
  import("generic-filehandle"),
49
42
  ]).then(([{ BigWig }, { RemoteFile }]) => {
50
- this.bbi = new BigWig({
43
+ this.#bbi = new BigWig({
51
44
  filehandle: new RemoteFile(
52
45
  addBaseUrl(this.params.url, this.view.getBaseUrl())
53
46
  ),
54
47
  });
55
48
 
56
- this.bbi.getHeader().then((header) => {
57
- this.reductionLevels =
49
+ this.#bbi.getHeader().then((header) => {
50
+ this.#reductionLevels =
58
51
  /** @type {{reductionLevel: number}[]} */ (
59
52
  header.zoomLevels
60
53
  )
@@ -63,7 +56,7 @@ export default class BigWigSource extends windowedMixin(SingleAxisLazySource) {
63
56
 
64
57
  // Add the non-reduced level. Not sure if this is the best way to do it.
65
58
  // Afaik, the non-reduced bin size is not available in the header.
66
- this.reductionLevels.push(1);
59
+ this.#reductionLevels.push(1);
67
60
 
68
61
  resolve();
69
62
  });
@@ -85,7 +78,7 @@ export default class BigWigSource extends windowedMixin(SingleAxisLazySource) {
85
78
  const reductionLevel = findReductionLevel(
86
79
  domain,
87
80
  length,
88
- this.reductionLevels
81
+ this.#reductionLevels
89
82
  );
90
83
 
91
84
  // The sensible minimum window size actually depends on the non-reduced data density...
@@ -95,51 +88,24 @@ export default class BigWigSource extends windowedMixin(SingleAxisLazySource) {
95
88
  const quantizedInterval = this.quantizeInterval(domain, windowSize);
96
89
 
97
90
  if (this.checkAndUpdateLastInterval(quantizedInterval)) {
98
- this.doDebouncedRequest(quantizedInterval, reductionLevel);
91
+ this.loadInterval(quantizedInterval, reductionLevel);
99
92
  }
100
93
  }
101
94
 
102
95
  /**
103
- * Listen to the domain change event and update data when the covered windows change.
104
- *
105
96
  * @param {number[]} interval linearized domain
106
97
  * @param {number} reductionLevel
107
98
  */
108
- async doRequest(interval, reductionLevel) {
109
- const featureResponse = await this.getFeatures(
99
+ // @ts-expect-error
100
+ async loadInterval(interval, reductionLevel) {
101
+ const scale = 1 / 2 / reductionLevel / this.params.pixelsPerBin;
102
+ const featureChunks = await this.discretizeAndLoad(
110
103
  interval,
111
- 1 / 2 / reductionLevel / this.params.pixelsPerBin
112
- );
113
-
114
- // Discard late responses
115
- if (featureResponse.requestId < this.lastRequestId) {
116
- return;
117
- }
118
-
119
- this.publishData(featureResponse.features);
120
- }
121
-
122
- /**
123
- *
124
- * @param {number[]} interval
125
- * @param {number} scale
126
- */
127
- async getFeatures(interval, scale) {
128
- let requestId = ++this.lastRequestId;
129
-
130
- // TODO: Abort previous requests
131
- const abortController = new AbortController();
132
-
133
- const discreteChromosomeIntervals =
134
- this.genome.continuousToDiscreteChromosomeIntervals(interval);
135
-
136
- // TODO: Error handling
137
- const featuresWithChrom = await Promise.all(
138
- discreteChromosomeIntervals.map((d) =>
139
- this.bbi
104
+ (d, signal) =>
105
+ this.#bbi
140
106
  .getFeatures(d.chrom, d.startPos, d.endPos, {
141
107
  scale,
142
- signal: abortController.signal,
108
+ signal,
143
109
  })
144
110
  .then((features) =>
145
111
  features.map((f) => ({
@@ -149,14 +115,11 @@ export default class BigWigSource extends windowedMixin(SingleAxisLazySource) {
149
115
  score: f.score,
150
116
  }))
151
117
  )
152
- )
153
118
  );
154
119
 
155
- return {
156
- requestId,
157
- abort: () => abortController.abort(),
158
- features: featuresWithChrom.flat(), // TODO: Use batches, not flat
159
- };
120
+ if (featureChunks) {
121
+ this.publishData(featureChunks);
122
+ }
160
123
  }
161
124
  }
162
125
 
@@ -1 +1 @@
1
- {"version":3,"file":"gff3Source.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/gff3Source.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;IAII;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,SAAS,QACzC,OAAO,uBAAuB,EAAE,OAAO,EASjD;IAED;;OAEG;IACH,sBAFW,MAAM,EAAE,OASlB;;CACJ;wBAjCuB,kBAAkB"}
1
+ {"version":3,"file":"gff3Source.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/gff3Source.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;IAII;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,SAAS,QACzC,OAAO,uBAAuB,EAAE,OAAO,EASjD;IAED;;OAEG;IACH,sBAFW,MAAM,EAAE,OAUlB;;CACJ;wBAlCuB,kBAAkB"}
@@ -24,6 +24,7 @@ export default class Gff3Source extends TabixSource {
24
24
  * @param {string[]} lines
25
25
  */
26
26
  _parseFeatures(lines) {
27
+ // Hmm. It's silly that we have to first collect individual lines and then join them.
27
28
  // eslint-disable-next-line no-sync
28
29
  const features = this.#gff?.parseStringSync(lines.join("\n"), {
29
30
  parseSequences: false,
@@ -1,15 +1,4 @@
1
- declare const IndexedFastaSource_base: {
2
- new (...args: any[]): {
3
- [x: string]: any;
4
- lastQuantizedInterval: number[];
5
- quantizeInterval(interval: number[], windowSize: number): number[];
6
- checkAndUpdateLastInterval(interval: number[]): boolean;
7
- };
8
- } & typeof SingleAxisLazySource;
9
- /**
10
- *
11
- */
12
- export default class IndexedFastaSource extends IndexedFastaSource_base {
1
+ export default class IndexedFastaSource extends SingleAxisWindowedSource {
13
2
  /**
14
3
  * @param {import("../../../spec/data.js").IndexedFastaData} params
15
4
  * @param {import("../../../view/view.js").default} view
@@ -18,13 +7,6 @@ export default class IndexedFastaSource extends IndexedFastaSource_base {
18
7
  params: import("../../../spec/data.js").IndexedFastaData;
19
8
  initializedPromise: Promise<any>;
20
9
  fasta: import("@gmod/indexedfasta").IndexedFasta;
21
- /**
22
- * Listen to the domain change event and update data when the covered windows change.
23
- *
24
- * @param {number[]} domain Linearized domain
25
- */
26
- onDomainChanged(domain: number[]): Promise<void>;
27
10
  }
28
- import SingleAxisLazySource from "./singleAxisLazySource.js";
29
- export {};
11
+ import SingleAxisWindowedSource from "./singleAxisWindowedSource.js";
30
12
  //# sourceMappingURL=indexedFastaSource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexedFastaSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/indexedFastaSource.js"],"names":[],"mappings":";;;;;;;;AAIA;;GAEG;AACH;IAGI;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,gBAAgB,QAChD,OAAO,uBAAuB,EAAE,OAAO,EA2CjD;IA/BG,yDAAgC;IAMhC,iCAwBE;IATM,iDAKE;IAOd;;;;OAIG;IACH,wBAFW,MAAM,EAAE,iBAkClB;CACJ;iCA/FgC,2BAA2B"}
1
+ {"version":3,"file":"indexedFastaSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/indexedFastaSource.js"],"names":[],"mappings":"AAGA;IACI;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,gBAAgB,QAChD,OAAO,uBAAuB,EAAE,OAAO,EA+CjD;IAjCG,yDAAgC;IAQhC,iCAwBE;IATM,iDAKE;CA6BjB;qCA5EoC,+BAA+B"}
@@ -1,13 +1,7 @@
1
- import SingleAxisLazySource from "./singleAxisLazySource.js";
2
- import windowedMixin from "./windowedMixin.js";
3
1
  import addBaseUrl from "../../../utils/addBaseUrl.js";
2
+ import SingleAxisWindowedSource from "./singleAxisWindowedSource.js";
4
3
 
5
- /**
6
- *
7
- */
8
- export default class IndexedFastaSource extends windowedMixin(
9
- SingleAxisLazySource
10
- ) {
4
+ export default class IndexedFastaSource extends SingleAxisWindowedSource {
11
5
  /**
12
6
  * @param {import("../../../spec/data.js").IndexedFastaData} params
13
7
  * @param {import("../../../view/view.js").default} view
@@ -17,6 +11,8 @@ export default class IndexedFastaSource extends windowedMixin(
17
11
  const paramsWithDefaults = {
18
12
  channel: "x",
19
13
  windowSize: 7000,
14
+ debounce: 200,
15
+ debounceMode: "window",
20
16
  ...params,
21
17
  };
22
18
 
@@ -28,6 +24,8 @@ export default class IndexedFastaSource extends windowedMixin(
28
24
  throw new Error("No URL provided for IndexedFastaSource");
29
25
  }
30
26
 
27
+ this.setupDebouncing(this.params);
28
+
31
29
  this.initializedPromise = new Promise((resolve) => {
32
30
  Promise.all([
33
31
  import("buffer"),
@@ -56,41 +54,25 @@ export default class IndexedFastaSource extends windowedMixin(
56
54
  }
57
55
 
58
56
  /**
59
- * Listen to the domain change event and update data when the covered windows change.
60
- *
61
- * @param {number[]} domain Linearized domain
57
+ * @param {number[]} interval linearized domain
62
58
  */
63
- async onDomainChanged(domain) {
64
- const windowSize = this.params.windowSize;
65
-
66
- if (domain[1] - domain[0] > windowSize) {
67
- return;
68
- }
69
-
70
- await this.initializedPromise;
71
-
72
- const quantizedInterval = this.quantizeInterval(domain, windowSize);
73
-
74
- if (this.checkAndUpdateLastInterval(quantizedInterval)) {
75
- const discreteChromosomeIntervals =
76
- this.genome.continuousToDiscreteChromosomeIntervals(
77
- quantizedInterval
78
- );
79
-
80
- // TODO: Error handling
81
- const sequencesWithChrom = await Promise.all(
82
- discreteChromosomeIntervals.map((d) =>
83
- this.fasta
84
- .getSequence(d.chrom, d.startPos, d.endPos)
85
- .then((sequence) => ({
86
- chrom: d.chrom,
87
- start: d.startPos,
88
- sequence,
89
- }))
90
- )
91
- );
59
+ async loadInterval(interval) {
60
+ const features = await this.discretizeAndLoad(
61
+ interval,
62
+ async (d, signal) =>
63
+ this.fasta
64
+ .getSequence(d.chrom, d.startPos, d.endPos, {
65
+ signal,
66
+ })
67
+ .then((sequence) => ({
68
+ chrom: d.chrom,
69
+ start: d.startPos,
70
+ sequence,
71
+ }))
72
+ );
92
73
 
93
- this.publishData(sequencesWithChrom);
74
+ if (features) {
75
+ this.publishData([features]);
94
76
  }
95
77
  }
96
78
  }
@@ -7,6 +7,11 @@ export default class SingleAxisLazySource extends DataSource {
7
7
  * @param {import("../../../spec/channel.js").PrimaryPositionalChannel} channel
8
8
  */
9
9
  constructor(view: import("../../../view/view.js").default, channel: import("../../../spec/channel.js").PrimaryPositionalChannel);
10
+ /**
11
+ * Has to be resolved before any data can be requested upon domain changes.
12
+ * @protected
13
+ */
14
+ protected initializedPromise: Promise<void>;
10
15
  view: import("../../../view/view.js").default;
11
16
  /** @type {import("../../../spec/channel.js").PrimaryPositionalChannel} */
12
17
  channel: import("../../../spec/channel.js").PrimaryPositionalChannel;
@@ -18,25 +23,39 @@ export default class SingleAxisLazySource extends DataSource {
18
23
  getAxisLength(): number;
19
24
  /**
20
25
  * Convenience getter for genome.
26
+ *
27
+ * @protected
21
28
  */
22
- get genome(): import("../../../genome/genome.js").default;
29
+ protected get genome(): import("../../../genome/genome.js").default;
23
30
  /**
24
31
  * Listen to the domain change event and update data when the covered windows change.
25
32
  *
26
33
  * @param {number[]} domain Linearized domain
27
34
  * @param {import("../../../spec/genome.js").ChromosomalLocus[]} complexDomain Chrom/Pos domain
35
+ * @abstract
28
36
  */
29
37
  onDomainChanged(domain: number[], complexDomain: import("../../../spec/genome.js").ChromosomalLocus[]): Promise<void>;
38
+ /**
39
+ * Sets the loading status of the data source. The status is shown in the UI.
40
+ *
41
+ * @param {boolean} status true if loading, false otherwise
42
+ * @protected
43
+ */
44
+ protected setLoadingStatus(status: boolean): void;
30
45
  /**
31
46
  * TODO: Get rid of this method.
32
47
  * Rendering should be requested by the collector.
48
+ *
49
+ * @protected
33
50
  */
34
- requestRender(): void;
51
+ protected requestRender(): void;
35
52
  /**
53
+ * Resets the data flow and propagates the data.
36
54
  *
37
- * @param {import("../../flowNode.js").Datum[]} data
55
+ * @param {import("../../flowNode.js").Datum[][]} chunks An array of data chunks.
56
+ * @protected
38
57
  */
39
- publishData(data: import("../../flowNode.js").Datum[]): void;
58
+ protected publishData(chunks: import("../../flowNode.js").Datum[][]): void;
40
59
  }
41
60
  import DataSource from "../dataSource.js";
42
61
  //# sourceMappingURL=singleAxisLazySource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"singleAxisLazySource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/singleAxisLazySource.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IACI;;;OAGG;IACH,kBAHW,OAAO,uBAAuB,EAAE,OAAO,WACvC,OAAO,0BAA0B,EAAE,wBAAwB,EAiDrE;IA5CG,8CAAgB;IAYhB,2EAA2E;IAC3E,SADW,OAAO,0BAA0B,EAAE,wBAAwB,CAChD;IAEtB,oEAA4D;IA+BhE;;;OAGG;IACH,wBAWC;IAED;;OAEG;IACH,0DAEC;IAED;;;;;OAKG;IACH,wBAHW,MAAM,EAAE,iBACR,OAAO,yBAAyB,EAAE,gBAAgB,EAAE,iBAI9D;IAED;;;OAGG;IACH,sBAGC;IAQD;;;OAGG;IACH,kBAFW,OAAO,mBAAmB,EAAE,KAAK,EAAE,QAe7C;CACJ;uBA/HsB,kBAAkB"}
1
+ {"version":3,"file":"singleAxisLazySource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/dynamic/singleAxisLazySource.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IAOI;;;OAGG;IACH,kBAHW,OAAO,uBAAuB,EAAE,OAAO,WACvC,OAAO,0BAA0B,EAAE,wBAAwB,EAiDrE;IAzDD;;;OAGG;IACH,4CAAuC;IASnC,8CAAgB;IAYhB,2EAA2E;IAC3E,SADW,OAAO,0BAA0B,EAAE,wBAAwB,CAChD;IAEtB,oEAA4D;IA+BhE;;;OAGG;IACH,wBAWC;IAED;;;;OAIG;IACH,oEAEC;IAED;;;;;;OAMG;IACH,wBAJW,MAAM,EAAE,iBACR,OAAO,yBAAyB,EAAE,gBAAgB,EAAE,iBAK9D;IAED;;;;;OAKG;IACH,mCAHW,OAAO,QAKjB;IAED;;;;;OAKG;IACH,gCAGC;IAQD;;;;;OAKG;IACH,8BAHW,OAAO,mBAAmB,EAAE,KAAK,EAAE,EAAE,QAkB/C;CACJ;uBAxJsB,kBAAkB"}
@@ -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
+ }