@genome-spy/core 0.14.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 (226) hide show
  1. package/dist/index.js +224 -0
  2. package/dist/style.css +1 -0
  3. package/package.json +54 -0
  4. package/src/data/collector.js +178 -0
  5. package/src/data/collector.test.js +82 -0
  6. package/src/data/dataFlow.js +109 -0
  7. package/src/data/dataFlow.test.js +3 -0
  8. package/src/data/facetNode.js +17 -0
  9. package/src/data/flow.test.js +71 -0
  10. package/src/data/flowBatch.d.ts +40 -0
  11. package/src/data/flowNode.js +283 -0
  12. package/src/data/flowNode.test.js +49 -0
  13. package/src/data/flowOptimizer.js +117 -0
  14. package/src/data/flowOptimizer.test.js +192 -0
  15. package/src/data/flowTestUtils.js +63 -0
  16. package/src/data/formats/fasta.js +32 -0
  17. package/src/data/formats/fasta.test.js +26 -0
  18. package/src/data/sources/dataSource.js +22 -0
  19. package/src/data/sources/dataSourceFactory.js +24 -0
  20. package/src/data/sources/dataUtils.js +31 -0
  21. package/src/data/sources/dynamicCallbackSource.js +56 -0
  22. package/src/data/sources/dynamicSource.js +36 -0
  23. package/src/data/sources/inlineSource.js +69 -0
  24. package/src/data/sources/inlineSource.test.js +55 -0
  25. package/src/data/sources/namedSource.js +74 -0
  26. package/src/data/sources/sequenceSource.js +46 -0
  27. package/src/data/sources/sequenceSource.test.js +45 -0
  28. package/src/data/sources/urlSource.js +74 -0
  29. package/src/data/transforms/aggregate.js +69 -0
  30. package/src/data/transforms/clone.js +40 -0
  31. package/src/data/transforms/clone.test.js +10 -0
  32. package/src/data/transforms/coverage.js +187 -0
  33. package/src/data/transforms/coverage.test.js +122 -0
  34. package/src/data/transforms/filter.js +37 -0
  35. package/src/data/transforms/filter.test.js +17 -0
  36. package/src/data/transforms/filterScoredLabels.js +134 -0
  37. package/src/data/transforms/flattenCompressedExons.js +57 -0
  38. package/src/data/transforms/flattenDelimited.js +68 -0
  39. package/src/data/transforms/flattenDelimited.test.js +86 -0
  40. package/src/data/transforms/flattenSequence.js +39 -0
  41. package/src/data/transforms/flattenSequence.test.js +33 -0
  42. package/src/data/transforms/formula.js +39 -0
  43. package/src/data/transforms/formula.test.js +18 -0
  44. package/src/data/transforms/identifier.js +108 -0
  45. package/src/data/transforms/identifier.test.js +82 -0
  46. package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
  47. package/src/data/transforms/measureText.js +44 -0
  48. package/src/data/transforms/pileup.js +128 -0
  49. package/src/data/transforms/pileup.test.js +69 -0
  50. package/src/data/transforms/project.js +41 -0
  51. package/src/data/transforms/project.test.js +31 -0
  52. package/src/data/transforms/regexExtract.js +61 -0
  53. package/src/data/transforms/regexExtract.test.js +66 -0
  54. package/src/data/transforms/regexFold.js +141 -0
  55. package/src/data/transforms/regexFold.test.js +159 -0
  56. package/src/data/transforms/sample.js +101 -0
  57. package/src/data/transforms/sample.test.js +37 -0
  58. package/src/data/transforms/stack.js +137 -0
  59. package/src/data/transforms/stack.test.js +90 -0
  60. package/src/data/transforms/transformFactory.js +60 -0
  61. package/src/encoder/accessor.js +82 -0
  62. package/src/encoder/accessor.test.js +46 -0
  63. package/src/encoder/encoder.js +369 -0
  64. package/src/encoder/encoder.test.js +97 -0
  65. package/src/fonts/Lato-Regular.json +1267 -0
  66. package/src/fonts/Lato-Regular.png +0 -0
  67. package/src/fonts/OFL.txt +93 -0
  68. package/src/fonts/README.md +3 -0
  69. package/src/fonts/bmFont.d.ts +58 -0
  70. package/src/fonts/bmFontManager.js +357 -0
  71. package/src/fonts/bmFontMetrics.js +108 -0
  72. package/src/genome/genome.js +305 -0
  73. package/src/genome/genome.test.js +152 -0
  74. package/src/genome/genomeStore.js +54 -0
  75. package/src/genome/locusFormat.js +31 -0
  76. package/src/genome/scaleIndex.js +199 -0
  77. package/src/genome/scaleIndex.test.js +61 -0
  78. package/src/genome/scaleLocus.js +112 -0
  79. package/src/genome/scaleLocus.test.js +3 -0
  80. package/src/genomeSpy.js +753 -0
  81. package/src/gl/arrayBuilder.js +199 -0
  82. package/src/gl/dataToVertices.js +621 -0
  83. package/src/gl/includes/common.glsl +63 -0
  84. package/src/gl/includes/fp64-arithmetic.glsl +187 -0
  85. package/src/gl/includes/fp64-utils.js +132 -0
  86. package/src/gl/includes/picking.fragment.glsl +3 -0
  87. package/src/gl/includes/picking.vertex.glsl +29 -0
  88. package/src/gl/includes/sampleFacet.glsl +107 -0
  89. package/src/gl/includes/scales.glsl +79 -0
  90. package/src/gl/includes/scales_fp64.glsl +30 -0
  91. package/src/gl/link.fragment.glsl +18 -0
  92. package/src/gl/link.vertex.glsl +111 -0
  93. package/src/gl/point.fragment.glsl +123 -0
  94. package/src/gl/point.vertex.glsl +128 -0
  95. package/src/gl/rect.fragment.glsl +51 -0
  96. package/src/gl/rect.vertex.glsl +114 -0
  97. package/src/gl/rule.fragment.glsl +52 -0
  98. package/src/gl/rule.vertex.glsl +89 -0
  99. package/src/gl/text.fragment.glsl +31 -0
  100. package/src/gl/text.vertex.glsl +246 -0
  101. package/src/gl/webGLHelper.js +490 -0
  102. package/src/img/bowtie.svg +1 -0
  103. package/src/img/genomespy-favicon.svg +34 -0
  104. package/src/index.html +11 -0
  105. package/src/index.js +151 -0
  106. package/src/marks/link.js +189 -0
  107. package/src/marks/mark.js +867 -0
  108. package/src/marks/markUtils.js +109 -0
  109. package/src/marks/pointMark.js +279 -0
  110. package/src/marks/rectMark.js +236 -0
  111. package/src/marks/rule.js +231 -0
  112. package/src/marks/text.js +274 -0
  113. package/src/options.d.ts +9 -0
  114. package/src/scale/colorUtils.js +184 -0
  115. package/src/scale/glslScaleGenerator.js +462 -0
  116. package/src/scale/scale.js +441 -0
  117. package/src/scale/scale.test.js +323 -0
  118. package/src/scale/ticks.js +198 -0
  119. package/src/scale/ticks.test.js +39 -0
  120. package/src/singlePageApp.js +13 -0
  121. package/src/spec/axis.d.ts +296 -0
  122. package/src/spec/channel.d.ts +127 -0
  123. package/src/spec/data.d.ts +185 -0
  124. package/src/spec/font.d.ts +15 -0
  125. package/src/spec/genome.d.ts +35 -0
  126. package/src/spec/mark.d.ts +432 -0
  127. package/src/spec/root.d.ts +22 -0
  128. package/src/spec/scale.d.ts +265 -0
  129. package/src/spec/tooltip.d.ts +9 -0
  130. package/src/spec/transform.d.ts +479 -0
  131. package/src/spec/view.d.ts +215 -0
  132. package/src/styles/genome-spy.scss +153 -0
  133. package/src/tooltip/dataTooltipHandler.js +59 -0
  134. package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
  135. package/src/tooltip/tooltipHandler.ts +12 -0
  136. package/src/types/filetypes.d.ts +4 -0
  137. package/src/types/flatqueue.d.ts +53 -0
  138. package/src/types/glsl.d.ts +4 -0
  139. package/src/types/object.d.ts +21 -0
  140. package/src/types/vega-scale.d.ts +60 -0
  141. package/src/utils/animator.js +83 -0
  142. package/src/utils/arrayUtils.js +55 -0
  143. package/src/utils/binnedRangeIndex.js +83 -0
  144. package/src/utils/clamp.js +8 -0
  145. package/src/utils/cloner.js +32 -0
  146. package/src/utils/cloner.test.js +23 -0
  147. package/src/utils/coalesce.js +11 -0
  148. package/src/utils/coalesce.test.js +15 -0
  149. package/src/utils/concatIterables.js +26 -0
  150. package/src/utils/concatIterables.test.js +7 -0
  151. package/src/utils/debounce.js +37 -0
  152. package/src/utils/domainArray.js +224 -0
  153. package/src/utils/domainArray.test.js +129 -0
  154. package/src/utils/eerp.js +13 -0
  155. package/src/utils/expression.js +32 -0
  156. package/src/utils/field.js +28 -0
  157. package/src/utils/fisheye.js +60 -0
  158. package/src/utils/formatObject.js +31 -0
  159. package/src/utils/html.js +23 -0
  160. package/src/utils/html.test.js +13 -0
  161. package/src/utils/indexer.js +43 -0
  162. package/src/utils/indexer.test.js +46 -0
  163. package/src/utils/inertia.js +124 -0
  164. package/src/utils/interactionEvent.js +33 -0
  165. package/src/utils/iterateNestedMaps.js +21 -0
  166. package/src/utils/iterateNestedMaps.test.js +32 -0
  167. package/src/utils/kWayMerge.js +42 -0
  168. package/src/utils/kWayMerge.test.js +25 -0
  169. package/src/utils/layout/flexLayout.js +336 -0
  170. package/src/utils/layout/flexLayout.test.js +296 -0
  171. package/src/utils/layout/padding.js +107 -0
  172. package/src/utils/layout/point.js +23 -0
  173. package/src/utils/layout/rectangle.js +282 -0
  174. package/src/utils/layout/rectangle.test.js +171 -0
  175. package/src/utils/mergeObjects.js +99 -0
  176. package/src/utils/mergeObjects.test.js +41 -0
  177. package/src/utils/numberExtractor.js +24 -0
  178. package/src/utils/numberExtractor.test.js +5 -0
  179. package/src/utils/point.js +14 -0
  180. package/src/utils/propertyCacher.js +70 -0
  181. package/src/utils/propertyCacher.test.js +84 -0
  182. package/src/utils/propertyCoalescer.js +37 -0
  183. package/src/utils/propertyCoalescer.test.js +21 -0
  184. package/src/utils/reservationMap.js +103 -0
  185. package/src/utils/reservationMap.test.js +19 -0
  186. package/src/utils/scaleNull.js +19 -0
  187. package/src/utils/setOperations.js +75 -0
  188. package/src/utils/smoothstep.js +10 -0
  189. package/src/utils/throttle.js +34 -0
  190. package/src/utils/topK.js +76 -0
  191. package/src/utils/topK.test.js +63 -0
  192. package/src/utils/transition.js +74 -0
  193. package/src/utils/ui/tooltip.js +189 -0
  194. package/src/utils/url.js +22 -0
  195. package/src/utils/variableTools.js +24 -0
  196. package/src/utils/variableTools.test.js +12 -0
  197. package/src/view/axisResolution.js +135 -0
  198. package/src/view/axisResolution.test.js +200 -0
  199. package/src/view/axisView.js +746 -0
  200. package/src/view/channel.js +5 -0
  201. package/src/view/concatView.js +296 -0
  202. package/src/view/containerView.js +141 -0
  203. package/src/view/decoratorView.js +510 -0
  204. package/src/view/facetView.js +488 -0
  205. package/src/view/flowBuilder.js +362 -0
  206. package/src/view/flowBuilder.test.js +124 -0
  207. package/src/view/importView.js +19 -0
  208. package/src/view/layerView.js +60 -0
  209. package/src/view/rendering.d.ts +44 -0
  210. package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
  211. package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
  212. package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
  213. package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
  214. package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
  215. package/src/view/renderingContext/viewRenderingContext.js +41 -0
  216. package/src/view/scaleResolution.js +756 -0
  217. package/src/view/scaleResolution.test.js +571 -0
  218. package/src/view/scaleResolutionApi.d.ts +40 -0
  219. package/src/view/testUtils.js +48 -0
  220. package/src/view/unitView.js +368 -0
  221. package/src/view/view.js +589 -0
  222. package/src/view/view.test.js +213 -0
  223. package/src/view/viewContext.d.ts +57 -0
  224. package/src/view/viewFactory.js +179 -0
  225. package/src/view/viewFactory.test.js +16 -0
  226. package/src/view/viewUtils.js +420 -0
@@ -0,0 +1,305 @@
1
+ import { bisect } from "d3-array";
2
+ import { tsvParseRows } from "d3-dsv";
3
+ import { loader } from "vega-loader";
4
+ import { isObject } from "vega-util";
5
+ import { formatRange } from "./locusFormat";
6
+
7
+ const defaultBaseUrl = "https://genomespy.app/data/genomes/";
8
+
9
+ /**
10
+ * @typedef {import("../spec/genome").GenomeConfig} GenomeConfig
11
+ * @typedef {import("../spec/genome").ChromosomalLocus} ChromosomalLocus
12
+ *
13
+ * @typedef {object} Chromosome
14
+ * @prop {string} name
15
+ * @prop {number} size
16
+ *
17
+ * @typedef {object} ChromosomeAnnotation
18
+ * @prop {number} index 0-based index
19
+ * @prop {number} number 1-based index
20
+ * @prop {number} continuousStart zero-based start, inclusive
21
+ * @prop {number} continuousEnd zero-based end, exclusive
22
+ * @prop {number[]} continuousInterval
23
+ * @prop {boolean} odd true if odd chrom number
24
+ */
25
+
26
+ export default class Genome {
27
+ /**
28
+ * @param {GenomeConfig} config
29
+ */
30
+ constructor(config) {
31
+ this.config = config;
32
+
33
+ if (!this.config.contigs && typeof this.config.name !== "string") {
34
+ throw new Error(
35
+ "No name has been defined for the genome assembly!"
36
+ );
37
+ }
38
+
39
+ /** @type {(Chromosome & ChromosomeAnnotation)[]} */
40
+ this.chromosomes = [];
41
+
42
+ /** @type {Map<string | number, number>} */
43
+ this.cumulativeChromPositions = new Map();
44
+
45
+ /** @type {Map<string | number, Chromosome & ChromosomeAnnotation>} */
46
+ this.chromosomesByName = new Map();
47
+
48
+ /** @type {number[]} */
49
+ this.startByIndex = [];
50
+
51
+ this.totalSize = 0;
52
+
53
+ if (this.config.contigs) {
54
+ this.setChromSizes(this.config.contigs);
55
+ }
56
+ }
57
+
58
+ get name() {
59
+ return this.config.name;
60
+ }
61
+
62
+ /**
63
+ * @param {string} baseUrl
64
+ */
65
+ async load(baseUrl) {
66
+ if (this.config.contigs) {
67
+ return;
68
+ }
69
+
70
+ if (this.config.baseUrl) {
71
+ this.baseUrl = /^http(s)?/.test(this.config.baseUrl)
72
+ ? this.config.baseUrl
73
+ : baseUrl + "/" + this.config.baseUrl;
74
+ } else {
75
+ this.baseUrl = defaultBaseUrl;
76
+ }
77
+
78
+ try {
79
+ this.setChromSizes(
80
+ parseChromSizes(
81
+ await loader({ baseURL: this.baseUrl }).load(
82
+ `${this.config.name}/${this.name}.chrom.sizes`
83
+ )
84
+ )
85
+ );
86
+ } catch (e) {
87
+ throw new Error(`Could not load chrom sizes: ${e.message}`);
88
+ }
89
+ }
90
+
91
+ /**
92
+ *
93
+ * @param {Chromosome[]} chromSizes
94
+ */
95
+ setChromSizes(chromSizes) {
96
+ let pos = 0;
97
+ this.startByIndex = [0];
98
+
99
+ for (let i = 0; i < chromSizes.length; i++) {
100
+ this.startByIndex.push(pos);
101
+ const size = chromSizes[i].size;
102
+
103
+ const chrom = {
104
+ ...chromSizes[i],
105
+ continuousStart: pos,
106
+ continuousEnd: pos + size,
107
+ continuousInterval: [pos, pos + size],
108
+ index: i,
109
+ number: i + 1,
110
+ // eslint-disable-next-line no-bitwise
111
+ odd: !(i & 1),
112
+ };
113
+
114
+ this.chromosomes.push(chrom);
115
+
116
+ const plain = chrom.name.replace(/^chr/i, "");
117
+ for (const name of [
118
+ "chr" + plain,
119
+ "CHR" + plain,
120
+ "Chr" + plain,
121
+ // The number is a bit fragile because it depends on the order of the chromosomes.
122
+ // It probably works for autosomes, but X, Y, M, etc., not necessarily.
123
+ chrom.number,
124
+ "" + chrom.number,
125
+ plain,
126
+ chrom.name,
127
+ ]) {
128
+ this.cumulativeChromPositions.set(name, pos);
129
+ this.chromosomesByName.set(name, chrom);
130
+ }
131
+
132
+ pos += chrom.size;
133
+ }
134
+
135
+ this.totalSize = pos;
136
+ }
137
+
138
+ getExtent() {
139
+ return [0, this.totalSize];
140
+ }
141
+
142
+ /**
143
+ * Returns a chromosomal locus in the continuous domain
144
+ *
145
+ * @param {string | number} chrom A number or name with or without a "chr" prefix. Examples: 23, chrX, X
146
+ * @param {number} pos zero-based coordinate
147
+ */
148
+ toContinuous(chrom, pos) {
149
+ let offset = this.cumulativeChromPositions.get(chrom);
150
+ if (offset === undefined) {
151
+ throw new Error("Unknown chromosome/contig: " + chrom);
152
+ }
153
+
154
+ return offset + +pos;
155
+ }
156
+
157
+ /**
158
+ *
159
+ * @param {number} continuousPos
160
+ */
161
+ toChromosome(continuousPos) {
162
+ if (continuousPos >= this.totalSize) {
163
+ return; // TODO: Consider displaying a warning
164
+ }
165
+
166
+ continuousPos = Math.floor(continuousPos);
167
+
168
+ // TODO: Fix the offset by one
169
+ const i = bisect(this.startByIndex, continuousPos) - 1;
170
+ if (i > 0 && i <= this.chromosomes.length) {
171
+ return this.chromosomes[i - 1];
172
+ }
173
+ }
174
+
175
+ /**
176
+ *
177
+ * @param {number} continuousPos
178
+ * @returns {ChromosomalLocus}
179
+ */
180
+ toChromosomal(continuousPos) {
181
+ const chrom = this.toChromosome(continuousPos);
182
+ if (!chrom) {
183
+ return undefined;
184
+ }
185
+
186
+ return {
187
+ chrom: chrom.name,
188
+ pos: Math.floor(continuousPos) - chrom.continuousStart,
189
+ };
190
+ }
191
+
192
+ /**
193
+ *
194
+ * @param {string} name
195
+ */
196
+ getChromosome(name) {
197
+ return this.chromosomesByName.get(name);
198
+ }
199
+
200
+ /**
201
+ * Returns a UCSC Genome Browser -style string presentation of the interval.
202
+ * However, the interval may span multiple chromosomes, which is incompatible
203
+ * with UCSC.
204
+ *
205
+ * The inteval is shown as one-based closed-open range.
206
+ * See https://genome.ucsc.edu/FAQ/FAQtracks#tracks1
207
+ *
208
+ * @param {number[]} interval
209
+ * @returns {string}
210
+ */
211
+ formatInterval(interval) {
212
+ return formatRange(...this.toChromosomalInterval(interval));
213
+ }
214
+
215
+ /**
216
+ * @param {number[]} interval
217
+ * @returns {[ChromosomalLocus, ChromosomalLocus]}
218
+ */
219
+ toChromosomalInterval(interval) {
220
+ // Round the lower end
221
+ const begin = this.toChromosomal(interval[0] + 0.5);
222
+ // Because of the open upper bound, one is first subtracted from the upper bound and later added back.
223
+ const end = this.toChromosomal(interval[1] - 0.5);
224
+ end.pos += 1;
225
+
226
+ return [begin, end];
227
+ }
228
+
229
+ /**
230
+ * Returns a continuous interval. The optional position of the left end defaults to zero,
231
+ * the right end defaults to the size of the chromosome. Thus, the chromosome is inclusive
232
+ * when positions are omitted.
233
+ *
234
+ * @param {ChromosomalLocus[]} chromosomal
235
+ */
236
+ toContinuousInterval(chromosomal) {
237
+ let [a, b] = chromosomal;
238
+ if (!b) {
239
+ // A shortcut for a single chromosome. { domain: [{ chrom: "chr3" }] }
240
+ b = a;
241
+ }
242
+
243
+ return [
244
+ this.toContinuous(a.chrom, a.pos ?? 0),
245
+ this.toContinuous(
246
+ b.chrom,
247
+ b.pos ?? this.chromosomesByName.get(b.chrom)?.size
248
+ ),
249
+ ];
250
+ }
251
+
252
+ /**
253
+ *
254
+ * @param {string} str
255
+ * @returns {[number, number]}
256
+ */
257
+ parseInterval(str) {
258
+ // TODO: consider changing [0-9XY] to support other species besides humans
259
+ const matches = str.match(
260
+ /^(chr[0-9A-Z]+):([0-9,]+)-(?:(chr[0-9A-Z]+):)?([0-9,]+)$/
261
+ );
262
+
263
+ if (matches) {
264
+ const startChr = matches[1];
265
+ const endChr = matches[3] || startChr;
266
+
267
+ const startIndex = parseInt(matches[2].replace(/,/g, ""));
268
+ const endIndex = parseInt(matches[4].replace(/,/g, ""));
269
+
270
+ return [
271
+ this.toContinuous(startChr, startIndex - 1),
272
+ this.toContinuous(endChr, endIndex),
273
+ ];
274
+ }
275
+ }
276
+ }
277
+
278
+ /**
279
+ *
280
+ * @param {string} chromSizesData
281
+ */
282
+ export function parseChromSizes(chromSizesData) {
283
+ // TODO: Support other organisms too
284
+ return tsvParseRows(chromSizesData)
285
+ .filter((row) => /^chr[0-9A-Z]+$/.test(row[0]))
286
+ .map(([name, size]) => ({ name, size: parseInt(size) }));
287
+ }
288
+
289
+ /**
290
+ *
291
+ * @param {any} value
292
+ * @return {value is ChromosomalLocus}
293
+ */
294
+ export function isChromosomalLocus(value) {
295
+ return isObject(value) && "chrom" in value;
296
+ }
297
+
298
+ /**
299
+ *
300
+ * @param {any[]} value
301
+ * @return {value is ChromosomalLocus[]}
302
+ */
303
+ export function isChromosomalLocusInterval(value) {
304
+ return value.every(isChromosomalLocus);
305
+ }
@@ -0,0 +1,152 @@
1
+ import Genome from "./genome";
2
+
3
+ describe("Human genome, chromosome names prefixed with 'chr'", () => {
4
+ // Actually, the chromosomes are just named as in hg38, for example
5
+ const chromosomes = [
6
+ { name: "chr1", size: 10 },
7
+ { name: "chr2", size: 20 },
8
+ { name: "chr3", size: 30 },
9
+ { name: "chrX", size: 40 },
10
+ ];
11
+
12
+ const g = new Genome({ name: "random", contigs: chromosomes });
13
+
14
+ test("Maps chromosome names to continuous", () => {
15
+ expect(() => g.toContinuous("chr0", 2)).toThrow();
16
+ expect(g.toContinuous("chr1", 0)).toEqual(0);
17
+ expect(g.toContinuous("chr1", 2)).toEqual(2);
18
+ expect(g.toContinuous("chr2", 2)).toEqual(12);
19
+ expect(g.toContinuous("chrX", 2)).toEqual(62);
20
+ // TODO: Should this throw to alert about invalid data..?
21
+ // TODO: expect(m.toContinuous("chrX", 40)).toBeUndefined();
22
+ });
23
+
24
+ test("Maps chromosome numbers to continuous", () => {
25
+ expect(() => g.toContinuous(0, 2)).toThrow();
26
+ expect(g.toContinuous(1, 2)).toEqual(2);
27
+ expect(g.toContinuous(2, 2)).toEqual(12);
28
+ expect(g.toContinuous(4, 2)).toEqual(62);
29
+ expect(() => g.toContinuous(5, 2)).toThrow();
30
+ });
31
+
32
+ test("Maps unprefixed names to continuous", () => {
33
+ expect(() => g.toContinuous("0", 2)).toThrow();
34
+ expect(g.toContinuous("1", 2)).toEqual(2);
35
+ expect(g.toContinuous("2", 2)).toEqual(12);
36
+ expect(g.toContinuous("X", 2)).toEqual(62);
37
+ expect(() => g.toContinuous("Y", 2)).toThrow();
38
+ });
39
+
40
+ test("Maps string positions to continuous", () => {
41
+ expect(g.toContinuous("2", /** @type {any} */ ("2"))).toEqual(12);
42
+ });
43
+
44
+ test("Maps continuous to chromosome and locus", () => {
45
+ expect(g.toChromosomal(-1)).toBeUndefined();
46
+ expect(g.toChromosomal(0)).toEqual({ chrom: "chr1", pos: 0 });
47
+ expect(g.toChromosomal(12)).toEqual({ chrom: "chr2", pos: 2 });
48
+ expect(g.toChromosomal(29)).toEqual({ chrom: "chr2", pos: 19 });
49
+ expect(g.toChromosomal(30)).toEqual({ chrom: "chr3", pos: 0 });
50
+ expect(g.toChromosomal(62)).toEqual({ chrom: "chrX", pos: 2 });
51
+ expect(g.toChromosomal(99)).toEqual({ chrom: "chrX", pos: 39 });
52
+ expect(g.toChromosomal(100)).toBeUndefined();
53
+ });
54
+
55
+ // Testing half-open intervals
56
+ test("Maps continuous interval to chromosomal interval", () => {
57
+ expect(g.toChromosomalInterval([0, 10])).toEqual([
58
+ { chrom: "chr1", pos: 0 },
59
+ { chrom: "chr1", pos: 10 },
60
+ ]);
61
+ expect(g.toChromosomalInterval([10, 100])).toEqual([
62
+ { chrom: "chr2", pos: 0 },
63
+ { chrom: "chrX", pos: 40 },
64
+ ]);
65
+ expect(g.toChromosomalInterval([0, 100])).toEqual([
66
+ { chrom: "chr1", pos: 0 },
67
+ { chrom: "chrX", pos: 40 },
68
+ ]);
69
+ });
70
+
71
+ test("Maps interval with fractional parts to chromosomal interval", () => {
72
+ expect(g.toChromosomalInterval([0.1, 99.9])).toEqual([
73
+ { chrom: "chr1", pos: 0 },
74
+ { chrom: "chrX", pos: 40 },
75
+ ]);
76
+ expect(g.toChromosomalInterval([0.6, 99.4])).toEqual([
77
+ { chrom: "chr1", pos: 1 },
78
+ { chrom: "chrX", pos: 39 },
79
+ ]);
80
+ });
81
+
82
+ test("Maps chromosomal interval to continuous interval", () => {
83
+ expect(
84
+ g.toContinuousInterval([
85
+ { chrom: "chr1", pos: 0 },
86
+ { chrom: "chr1", pos: 10 },
87
+ ])
88
+ ).toEqual([0, 10]);
89
+ expect(
90
+ g.toContinuousInterval([
91
+ { chrom: "chr1", pos: 1 },
92
+ { chrom: "chr1", pos: 9 },
93
+ ])
94
+ ).toEqual([1, 9]);
95
+ expect(
96
+ g.toContinuousInterval([
97
+ { chrom: "chr2", pos: 0 },
98
+ { chrom: "chrX", pos: 40 },
99
+ ])
100
+ ).toEqual([10, 100]);
101
+ expect(
102
+ g.toContinuousInterval([
103
+ { chrom: "chr1", pos: 0 },
104
+ { chrom: "chrX", pos: 40 },
105
+ ])
106
+ ).toEqual([0, 100]);
107
+ });
108
+
109
+ test("Maps chromosomal interval without positions to continuous interval", () => {
110
+ expect(
111
+ g.toContinuousInterval([{ chrom: "chr1" }, { chrom: "chr1" }])
112
+ ).toEqual([0, 10]);
113
+ expect(
114
+ g.toContinuousInterval([{ chrom: "chr2" }, { chrom: "chrX" }])
115
+ ).toEqual([10, 100]);
116
+ });
117
+
118
+ test("Returns a properly annotated chromosomes array", () => {
119
+ expect(g.chromosomes[1]).toEqual({
120
+ name: "chr2",
121
+ size: 20,
122
+ index: 1,
123
+ number: 2,
124
+ continuousStart: 10,
125
+ continuousEnd: 30,
126
+ continuousInterval: [10, 30],
127
+ odd: false,
128
+ });
129
+ });
130
+ });
131
+
132
+ describe("C. elegans genome, chromosome names prefixed with 'chr'", () => {
133
+ const chromosomes = [
134
+ { name: "chrI", size: 15072434 },
135
+ { name: "chrII", size: 15279421 },
136
+ { name: "chrIII", size: 13783801 },
137
+ { name: "chrIV", size: 17493829 },
138
+ { name: "chrV", size: 20924180 },
139
+ { name: "chrX", size: 17718942 },
140
+ { name: "chrM", size: 13794 },
141
+ ];
142
+
143
+ const g = new Genome({ name: "random", contigs: chromosomes });
144
+
145
+ test("Maps chromosome names to continuous", () => {
146
+ expect(g.toContinuous("chrIII", 10)).toEqual(30351865);
147
+ });
148
+
149
+ test("Maps unprefixed names to continuous", () => {
150
+ expect(g.toContinuous("III", 10)).toEqual(30351865);
151
+ });
152
+ });
@@ -0,0 +1,54 @@
1
+ import Genome from "./genome";
2
+
3
+ export default class GenomeStore {
4
+ /**
5
+ * @param {import("../genomeSpy").default} genomeSpy
6
+ */
7
+ constructor(genomeSpy) {
8
+ /** @type {Map<string, Genome>} */
9
+ this.genomes = new Map();
10
+ this.genomeSpy = genomeSpy;
11
+ }
12
+
13
+ /**
14
+ * @param {import("../spec/genome").GenomeConfig} genomeConfig
15
+ */
16
+ // eslint-disable-next-line require-await
17
+ async initialize(genomeConfig) {
18
+ const genome = new Genome(genomeConfig);
19
+ this.genomes.set(genome.name, genome);
20
+
21
+ return Promise.all(
22
+ [...this.genomes.values()].map((genome) =>
23
+ genome.load(this.genomeSpy.spec.baseUrl)
24
+ )
25
+ );
26
+ }
27
+
28
+ /**
29
+ * @param {string} [name] If not given, a default genome is returned.
30
+ * @returns {Genome}
31
+ */
32
+ getGenome(name) {
33
+ if (!this.genomes.size) {
34
+ throw new Error("No genomes have been configured!");
35
+ }
36
+
37
+ if (name) {
38
+ const genome = this.genomes.get(name);
39
+ if (!genome) {
40
+ throw new Error(
41
+ `No genome with the name ${name} has been configured!`
42
+ );
43
+ }
44
+ return genome;
45
+ } else {
46
+ if (this.genomes.size > 1) {
47
+ throw new Error(
48
+ "Cannot pick a default genome! More than one have been configured!"
49
+ );
50
+ }
51
+ return this.genomes.values().next().value;
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,31 @@
1
+ import { format as d3format } from "d3-format";
2
+
3
+ const numberFormat = d3format(",d");
4
+
5
+ /**
6
+ * @typedef {import("./genome").ChromosomalLocus} ChromosomalLocus
7
+ */
8
+
9
+ /**
10
+ * @param {ChromosomalLocus} locus
11
+ */
12
+ export function formatLocus(locus) {
13
+ return locus.chrom + ":" + numberFormat(Math.floor(locus.pos + 1));
14
+ }
15
+
16
+ /**
17
+ * @param {ChromosomalLocus} begin
18
+ * @param {ChromosomalLocus} end
19
+ */
20
+ export function formatRange(begin, end) {
21
+ return (
22
+ begin.chrom +
23
+ ":" +
24
+ numberFormat(Math.floor(begin.pos + 1)) +
25
+ "-" +
26
+ (begin.chrom != end.chrom ? end.chrom + ":" : "") +
27
+ numberFormat(Math.ceil(end.pos))
28
+ );
29
+ }
30
+
31
+ // TODO: parseLocus, parseRange