@jbrowse/plugin-variants 4.0.4 → 4.1.3

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 (128) hide show
  1. package/esm/LDDisplay/SharedLDConfigSchema.d.ts +102 -0
  2. package/esm/LDDisplay/SharedLDConfigSchema.js +83 -0
  3. package/esm/LDDisplay/afterAttach.d.ts +2 -0
  4. package/esm/LDDisplay/afterAttach.js +123 -0
  5. package/esm/LDDisplay/components/BaseDisplayComponent.d.ts +15 -0
  6. package/esm/LDDisplay/components/BaseDisplayComponent.js +39 -0
  7. package/esm/LDDisplay/components/LDColorLegend.d.ts +15 -0
  8. package/esm/LDDisplay/components/LDColorLegend.js +75 -0
  9. package/esm/LDDisplay/components/LDDisplayComponent.d.ts +5 -0
  10. package/esm/LDDisplay/components/LDDisplayComponent.js +203 -0
  11. package/esm/LDDisplay/components/LinesConnectingMatrixToGenomicPosition.d.ts +16 -0
  12. package/esm/LDDisplay/components/LinesConnectingMatrixToGenomicPosition.js +109 -0
  13. package/esm/LDDisplay/configSchema1.d.ts +115 -0
  14. package/esm/LDDisplay/configSchema1.js +16 -0
  15. package/esm/LDDisplay/configSchema2.d.ts +115 -0
  16. package/esm/LDDisplay/configSchema2.js +16 -0
  17. package/esm/LDDisplay/index.d.ts +2 -0
  18. package/esm/LDDisplay/index.js +35 -0
  19. package/esm/LDDisplay/renderSvg.d.ts +3 -0
  20. package/esm/LDDisplay/renderSvg.js +36 -0
  21. package/esm/LDDisplay/shared.d.ts +367 -0
  22. package/esm/LDDisplay/shared.js +467 -0
  23. package/esm/LDDisplay/stateModel1.d.ts +365 -0
  24. package/esm/LDDisplay/stateModel1.js +10 -0
  25. package/esm/LDDisplay/stateModel2.d.ts +365 -0
  26. package/esm/LDDisplay/stateModel2.js +10 -0
  27. package/esm/LDRenderer/LDRenderer.d.ts +30 -0
  28. package/esm/LDRenderer/LDRenderer.js +109 -0
  29. package/esm/LDRenderer/components/LDRendering.d.ts +2 -0
  30. package/esm/LDRenderer/components/LDRendering.js +4 -0
  31. package/esm/LDRenderer/configSchema.d.ts +8 -0
  32. package/esm/LDRenderer/configSchema.js +10 -0
  33. package/esm/LDRenderer/index.d.ts +2 -0
  34. package/esm/LDRenderer/index.js +11 -0
  35. package/esm/LDRenderer/makeImageData.d.ts +20 -0
  36. package/esm/LDRenderer/makeImageData.js +157 -0
  37. package/esm/LDRenderer/types.d.ts +8 -0
  38. package/esm/LDRenderer/types.js +1 -0
  39. package/esm/LDTrack/configSchema.d.ts +85 -0
  40. package/esm/LDTrack/configSchema.js +7 -0
  41. package/esm/LDTrack/index.d.ts +2 -0
  42. package/esm/LDTrack/index.js +14 -0
  43. package/esm/LinearVariantDisplay/model.d.ts +126 -42
  44. package/esm/LinearVariantDisplay/model.js +46 -8
  45. package/esm/MultiLinearVariantDisplay/configSchema.d.ts +27 -1
  46. package/esm/MultiLinearVariantDisplay/model.d.ts +2635 -31
  47. package/esm/MultiLinearVariantDisplay/model.js +6 -0
  48. package/esm/MultiLinearVariantDisplay/renderSvg.d.ts +10 -2
  49. package/esm/MultiLinearVariantMatrixDisplay/configSchema.d.ts +25 -0
  50. package/esm/MultiLinearVariantMatrixDisplay/configSchema.js +26 -0
  51. package/esm/MultiLinearVariantMatrixDisplay/model.d.ts +2636 -32
  52. package/esm/MultiLinearVariantMatrixDisplay/model.js +6 -0
  53. package/esm/MultiLinearVariantMatrixRenderer/MultiLinearVariantMatrixRenderer.d.ts +2 -2
  54. package/esm/MultiLinearVariantMatrixRenderer/MultiLinearVariantMatrixRenderer.js +11 -9
  55. package/esm/MultiLinearVariantMatrixRenderer/components/MultiLinearVariantMatrixRendering.d.ts +8 -0
  56. package/esm/MultiLinearVariantMatrixRenderer/components/MultiLinearVariantMatrixRendering.js +14 -2
  57. package/esm/MultiLinearVariantMatrixRenderer/makeImageData.js +8 -11
  58. package/esm/MultiLinearVariantRenderer/MultiVariantRenderer.d.ts +2 -2
  59. package/esm/MultiLinearVariantRenderer/MultiVariantRenderer.js +4 -3
  60. package/esm/MultiLinearVariantRenderer/components/MultiLinearVariantRendering.d.ts +4 -0
  61. package/esm/MultiLinearVariantRenderer/components/MultiLinearVariantRendering.js +23 -2
  62. package/esm/MultiLinearVariantRenderer/makeImageData.js +12 -12
  63. package/esm/PlinkLDAdapter/PlinkLDAdapter.d.ts +25 -0
  64. package/esm/PlinkLDAdapter/PlinkLDAdapter.js +147 -0
  65. package/esm/PlinkLDAdapter/PlinkLDTabixAdapter.d.ts +24 -0
  66. package/esm/PlinkLDAdapter/PlinkLDTabixAdapter.js +156 -0
  67. package/esm/PlinkLDAdapter/configSchema.d.ts +10 -0
  68. package/esm/PlinkLDAdapter/configSchema.js +25 -0
  69. package/esm/PlinkLDAdapter/configSchemaTabix.d.ts +24 -0
  70. package/esm/PlinkLDAdapter/configSchemaTabix.js +46 -0
  71. package/esm/PlinkLDAdapter/index.d.ts +2 -0
  72. package/esm/PlinkLDAdapter/index.js +25 -0
  73. package/esm/PlinkLDAdapter/types.d.ts +29 -0
  74. package/esm/PlinkLDAdapter/types.js +1 -0
  75. package/esm/VariantFeatureWidget/VariantSampleGrid/VariantSampleGrid.js +1 -1
  76. package/esm/VariantRPC/MultiVariantGetFeatureDetails.d.ts +14 -0
  77. package/esm/VariantRPC/MultiVariantGetFeatureDetails.js +15 -0
  78. package/esm/VariantRPC/MultiVariantGetGenotypeMatrix.js +4 -1
  79. package/esm/VariantRPC/MultiVariantGetSimplifiedFeatures.js +3 -3
  80. package/esm/VariantRPC/executeClusterGenotypeMatrix.js +6 -3
  81. package/esm/VariantRPC/getGenotypeMatrix.d.ts +2 -3
  82. package/esm/VariantRPC/getGenotypeMatrix.js +4 -5
  83. package/esm/VariantRPC/getLDMatrix.d.ts +47 -0
  84. package/esm/VariantRPC/getLDMatrix.js +387 -0
  85. package/esm/VariantRPC/getLDMatrixFromPlink.d.ts +16 -0
  86. package/esm/VariantRPC/getLDMatrixFromPlink.js +105 -0
  87. package/esm/VariantRPC/getPhasedGenotypeMatrix.d.ts +2 -3
  88. package/esm/VariantRPC/getPhasedGenotypeMatrix.js +4 -5
  89. package/esm/VariantRPC/types.d.ts +3 -0
  90. package/esm/VcfAdapter/VcfAdapter.d.ts +1 -1
  91. package/esm/VcfAdapter/VcfAdapter.js +1 -2
  92. package/esm/VcfExtensionPoints/index.js +29 -3
  93. package/esm/VcfFeature/index.d.ts +2 -1
  94. package/esm/VcfFeature/index.js +4 -2
  95. package/esm/index.d.ts +1 -0
  96. package/esm/index.js +23 -0
  97. package/esm/shared/MultiVariantBaseModel.d.ts +2626 -26
  98. package/esm/shared/MultiVariantBaseModel.js +88 -39
  99. package/esm/shared/SharedVariantConfigSchema.d.ts +27 -1
  100. package/esm/shared/SharedVariantConfigSchema.js +28 -1
  101. package/esm/shared/VariantFeatureCache.d.ts +27 -0
  102. package/esm/shared/VariantFeatureCache.js +48 -0
  103. package/esm/shared/VariantRendererType.d.ts +23 -0
  104. package/esm/shared/VariantRendererType.js +15 -0
  105. package/esm/shared/applyColorPalette.d.ts +9 -0
  106. package/esm/shared/applyColorPalette.js +23 -0
  107. package/esm/shared/colorByAutorun.d.ts +10 -0
  108. package/esm/shared/colorByAutorun.js +39 -0
  109. package/esm/shared/components/AddFiltersDialog.d.ts +3 -3
  110. package/esm/shared/components/AddFiltersDialog.js +29 -22
  111. package/esm/shared/components/LDFilterDialog.d.ts +13 -0
  112. package/esm/shared/components/LDFilterDialog.js +102 -0
  113. package/esm/shared/components/MAFFilterDialog.js +23 -16
  114. package/esm/shared/components/RecombinationTrack.d.ts +21 -0
  115. package/esm/shared/components/RecombinationTrack.js +54 -0
  116. package/esm/shared/components/RecombinationYScaleBar.d.ts +7 -0
  117. package/esm/shared/components/RecombinationYScaleBar.js +34 -0
  118. package/esm/shared/components/SetColorDialogRowPalettizer.d.ts +3 -8
  119. package/esm/shared/components/SetColorDialogRowPalettizer.js +2 -14
  120. package/esm/shared/drawAlleleCount.js +9 -0
  121. package/esm/shared/drawPhased.d.ts +1 -1
  122. package/esm/shared/drawPhased.js +31 -2
  123. package/esm/shared/mafFilterUtils.d.ts +5 -0
  124. package/esm/shared/mafFilterUtils.js +17 -0
  125. package/esm/shared/minorAlleleFrequencyUtils.d.ts +4 -2
  126. package/esm/shared/minorAlleleFrequencyUtils.js +261 -19
  127. package/esm/shared/setupMultiVariantAutoruns.js +2 -0
  128. package/package.json +11 -10
@@ -0,0 +1,387 @@
1
+ import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache';
2
+ import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain';
3
+ import { checkStopToken2 } from '@jbrowse/core/util/stopToken';
4
+ import { firstValueFrom, toArray } from 'rxjs';
5
+ import { getFeaturesThatPassMinorAlleleFrequencyFilter } from "../shared/minorAlleleFrequencyUtils.js";
6
+ const SPLITTER = /[/|]/;
7
+ function isPhased(genotypes) {
8
+ const firstVal = Object.values(genotypes)[0];
9
+ return firstVal?.includes('|') ?? false;
10
+ }
11
+ function calculateDprime(D, pA, pB, signedLD) {
12
+ const qA = 1 - pA;
13
+ const qB = 1 - pB;
14
+ if (D > 0) {
15
+ const Dmax = Math.min(pA * qB, qA * pB);
16
+ if (Dmax > 0) {
17
+ return Math.min(1, D / Dmax);
18
+ }
19
+ }
20
+ else if (D < 0) {
21
+ const absDmin = Math.min(pA * pB, qA * qB);
22
+ if (absDmin > 0) {
23
+ return signedLD
24
+ ? Math.max(-1, D / absDmin)
25
+ : Math.min(1, Math.abs(D) / absDmin);
26
+ }
27
+ }
28
+ return 0;
29
+ }
30
+ function getChiSquareCritical(pValue) {
31
+ if (pValue <= 0) {
32
+ return Number.POSITIVE_INFINITY;
33
+ }
34
+ if (pValue >= 1) {
35
+ return 0;
36
+ }
37
+ if (pValue === 0.05) {
38
+ return 3.841;
39
+ }
40
+ if (pValue === 0.01) {
41
+ return 6.635;
42
+ }
43
+ if (pValue === 0.001) {
44
+ return 10.828;
45
+ }
46
+ if (pValue === 0.0001) {
47
+ return 15.137;
48
+ }
49
+ return -2 * Math.log(pValue);
50
+ }
51
+ function encodeGenotypes(genotypes, samples, splitCache) {
52
+ const encoded = new Int8Array(samples.length);
53
+ for (const [i, sample] of samples.entries()) {
54
+ const val = genotypes[sample];
55
+ const alleles = splitCache[val] ?? (splitCache[val] = val.split(SPLITTER));
56
+ let nonRefCount = 0;
57
+ let uncalledCount = 0;
58
+ for (const allele of alleles) {
59
+ if (allele === '.') {
60
+ uncalledCount++;
61
+ }
62
+ else if (allele !== '0') {
63
+ nonRefCount++;
64
+ }
65
+ }
66
+ if (uncalledCount === alleles.length) {
67
+ encoded[i] = -1;
68
+ }
69
+ else if (nonRefCount === 0) {
70
+ encoded[i] = 0;
71
+ }
72
+ else if (nonRefCount === alleles.length) {
73
+ encoded[i] = 2;
74
+ }
75
+ else {
76
+ encoded[i] = 1;
77
+ }
78
+ }
79
+ return encoded;
80
+ }
81
+ function encodePhasedHaplotypes(genotypes, samples) {
82
+ const hap1 = new Int8Array(samples.length);
83
+ const hap2 = new Int8Array(samples.length);
84
+ for (const [i, sample] of samples.entries()) {
85
+ const val = genotypes[sample];
86
+ const alleles = val.split('|');
87
+ if (alleles.length !== 2) {
88
+ hap1[i] = -1;
89
+ hap2[i] = -1;
90
+ continue;
91
+ }
92
+ if (alleles[0] === '.') {
93
+ hap1[i] = -1;
94
+ }
95
+ else {
96
+ hap1[i] = alleles[0] === '0' ? 0 : 1;
97
+ }
98
+ if (alleles[1] === '.') {
99
+ hap2[i] = -1;
100
+ }
101
+ else {
102
+ hap2[i] = alleles[1] === '0' ? 0 : 1;
103
+ }
104
+ }
105
+ return { hap1, hap2 };
106
+ }
107
+ function calculateLDStatsPhased(haps1, haps2, signedLD = false) {
108
+ let n01 = 0;
109
+ let n10 = 0;
110
+ let n11 = 0;
111
+ let total = 0;
112
+ const numSamples = haps1.hap1.length;
113
+ for (let i = 0; i < numSamples; i++) {
114
+ const a1 = haps1.hap1[i];
115
+ const b1 = haps2.hap1[i];
116
+ if (a1 >= 0 && b1 >= 0) {
117
+ if (a1 === 0 && b1 === 1) {
118
+ n01++;
119
+ }
120
+ else if (a1 === 1 && b1 === 0) {
121
+ n10++;
122
+ }
123
+ else if (a1 === 1 && b1 === 1) {
124
+ n11++;
125
+ }
126
+ total++;
127
+ }
128
+ }
129
+ for (let i = 0; i < numSamples; i++) {
130
+ const a2 = haps1.hap2[i];
131
+ const b2 = haps2.hap2[i];
132
+ if (a2 >= 0 && b2 >= 0) {
133
+ if (a2 === 0 && b2 === 1) {
134
+ n01++;
135
+ }
136
+ else if (a2 === 1 && b2 === 0) {
137
+ n10++;
138
+ }
139
+ else if (a2 === 1 && b2 === 1) {
140
+ n11++;
141
+ }
142
+ total++;
143
+ }
144
+ }
145
+ if (total < 4) {
146
+ return { r2: 0, dprime: 0 };
147
+ }
148
+ const p01 = n01 / total;
149
+ const p10 = n10 / total;
150
+ const p11 = n11 / total;
151
+ const pA = p10 + p11;
152
+ const pB = p01 + p11;
153
+ const qA = 1 - pA;
154
+ const qB = 1 - pB;
155
+ if (pA <= 0 || pA >= 1 || pB <= 0 || pB >= 1) {
156
+ return { r2: 0, dprime: 0 };
157
+ }
158
+ const D = p11 - pA * pB;
159
+ const denom = pA * qA * pB * qB;
160
+ const r = denom > 0 ? D / Math.sqrt(denom) : 0;
161
+ const r2 = Math.min(1, Math.max(0, r * r));
162
+ const dprime = calculateDprime(D, pA, pB, signedLD);
163
+ return { r2: signedLD ? r : r2, dprime };
164
+ }
165
+ function calculateLDStats(geno1, geno2, signedLD = false) {
166
+ let n = 0;
167
+ let sumG1 = 0;
168
+ let sumG2 = 0;
169
+ let sumG1sq = 0;
170
+ let sumG2sq = 0;
171
+ let sumProd = 0;
172
+ for (const [i, element] of geno1.entries()) {
173
+ const g1 = element;
174
+ const g2 = geno2[i];
175
+ if (g1 >= 0 && g2 >= 0) {
176
+ n++;
177
+ sumG1 += g1;
178
+ sumG2 += g2;
179
+ sumG1sq += g1 * g1;
180
+ sumG2sq += g2 * g2;
181
+ sumProd += g1 * g2;
182
+ }
183
+ }
184
+ if (n < 2) {
185
+ return { r2: 0, dprime: 0 };
186
+ }
187
+ const pA = sumG1 / (2 * n);
188
+ const pB = sumG2 / (2 * n);
189
+ if (pA <= 0 || pA >= 1 || pB <= 0 || pB >= 1) {
190
+ return { r2: 0, dprime: 0 };
191
+ }
192
+ const mean1 = sumG1 / n;
193
+ const mean2 = sumG2 / n;
194
+ const var1 = sumG1sq / n - mean1 * mean1;
195
+ const var2 = sumG2sq / n - mean2 * mean2;
196
+ let r = 0;
197
+ let r2 = 0;
198
+ if (var1 > 0 && var2 > 0) {
199
+ const cov = sumProd / n - mean1 * mean2;
200
+ r = cov / Math.sqrt(var1 * var2);
201
+ r2 = Math.min(1, Math.max(0, r * r));
202
+ }
203
+ const covG = sumProd / n - mean1 * mean2;
204
+ const D = covG / 2;
205
+ const dprime = calculateDprime(D, pA, pB, signedLD);
206
+ return { r2: signedLD ? r : r2, dprime };
207
+ }
208
+ export async function getLDMatrix({ pluginManager, args, }) {
209
+ const { minorAlleleFrequencyFilter, regions, adapterConfig, sessionId, lengthCutoffFilter, hweFilterThreshold = 0.001, callRateFilter = 0, jexlFilters = [], stopTokenCheck, ldMetric = 'r2', signedLD = false, } = args;
210
+ const adapter = await getAdapter(pluginManager, sessionId, adapterConfig);
211
+ const dataAdapter = adapter.dataAdapter;
212
+ const sources = await dataAdapter.getSources(regions);
213
+ const samples = sources.map(s => s.name);
214
+ if (samples.length === 0) {
215
+ return {
216
+ snps: [],
217
+ ldValues: new Float32Array(0),
218
+ metric: ldMetric,
219
+ filterStats: {
220
+ totalVariants: 0,
221
+ passedVariants: 0,
222
+ filteredByMaf: 0,
223
+ filteredByLength: 0,
224
+ filteredByMultiallelic: 0,
225
+ filteredByHwe: 0,
226
+ filteredByCallRate: 0,
227
+ },
228
+ recombination: {
229
+ values: new Float32Array(0),
230
+ positions: [],
231
+ },
232
+ };
233
+ }
234
+ const splitCache = {};
235
+ const rawFeatures = await firstValueFrom(dataAdapter.getFeaturesInMultipleRegions(regions, args).pipe(toArray()));
236
+ const totalVariants = rawFeatures.length;
237
+ const filteredFeatures = getFeaturesThatPassMinorAlleleFrequencyFilter({
238
+ minorAlleleFrequencyFilter,
239
+ lengthCutoffFilter,
240
+ stopTokenCheck,
241
+ splitCache,
242
+ features: rawFeatures,
243
+ });
244
+ let filteredByLength = 0;
245
+ let filteredByMaf = 0;
246
+ for (const feature of rawFeatures) {
247
+ if (feature.get('end') - feature.get('start') > lengthCutoffFilter) {
248
+ filteredByLength++;
249
+ }
250
+ else {
251
+ const featureId = feature.id();
252
+ const wasPassed = filteredFeatures.some(f => f.feature.id() === featureId);
253
+ if (!wasPassed) {
254
+ filteredByMaf++;
255
+ }
256
+ }
257
+ }
258
+ let featuresAfterJexl = filteredFeatures;
259
+ if (jexlFilters.length > 0) {
260
+ const filterChain = new SerializableFilterChain({ filters: jexlFilters });
261
+ featuresAfterJexl = filteredFeatures.filter(({ feature }) => filterChain.passes(feature, undefined, undefined));
262
+ }
263
+ const snps = [];
264
+ const encodedGenotypes = [];
265
+ const phasedHaplotypes = [];
266
+ let filteredByMultiallelic = 0;
267
+ let filteredByHwe = 0;
268
+ let filteredByCallRate = 0;
269
+ const callRateFilterEnabled = callRateFilter > 0;
270
+ let dataIsPhased = false;
271
+ if (featuresAfterJexl.length > 0) {
272
+ const firstGenotypes = featuresAfterJexl[0].feature.get('genotypes');
273
+ dataIsPhased = isPhased(firstGenotypes);
274
+ }
275
+ const chiSqCritical = getChiSquareCritical(hweFilterThreshold);
276
+ const hweFilterEnabled = hweFilterThreshold > 0;
277
+ for (const { feature } of featuresAfterJexl) {
278
+ const alt = feature.get('ALT');
279
+ if (alt && alt.length > 1) {
280
+ filteredByMultiallelic++;
281
+ continue;
282
+ }
283
+ const genotypes = feature.get('genotypes');
284
+ const encoded = encodeGenotypes(genotypes, samples, splitCache);
285
+ let nHomRef = 0;
286
+ let nHet = 0;
287
+ let nHomAlt = 0;
288
+ let nValid = 0;
289
+ for (const g of encoded) {
290
+ if (g === 0) {
291
+ nHomRef++;
292
+ nValid++;
293
+ }
294
+ else if (g === 1) {
295
+ nHet++;
296
+ nValid++;
297
+ }
298
+ else if (g === 2) {
299
+ nHomAlt++;
300
+ nValid++;
301
+ }
302
+ }
303
+ if (callRateFilterEnabled) {
304
+ const callRate = nValid / samples.length;
305
+ if (callRate < callRateFilter) {
306
+ filteredByCallRate++;
307
+ continue;
308
+ }
309
+ }
310
+ if (hweFilterEnabled && nValid > 0) {
311
+ const p = (2 * nHomRef + nHet) / (2 * nValid);
312
+ const q = 1 - p;
313
+ const expectedHomRef = p * p * nValid;
314
+ const expectedHet = 2 * p * q * nValid;
315
+ const expectedHomAlt = q * q * nValid;
316
+ let chiSq = 0;
317
+ if (expectedHomRef > 0) {
318
+ chiSq += (nHomRef - expectedHomRef) ** 2 / expectedHomRef;
319
+ }
320
+ if (expectedHet > 0) {
321
+ chiSq += (nHet - expectedHet) ** 2 / expectedHet;
322
+ }
323
+ if (expectedHomAlt > 0) {
324
+ chiSq += (nHomAlt - expectedHomAlt) ** 2 / expectedHomAlt;
325
+ }
326
+ if (chiSq > chiSqCritical) {
327
+ filteredByHwe++;
328
+ continue;
329
+ }
330
+ }
331
+ snps.push({
332
+ id: feature.get('name') || feature.id(),
333
+ refName: feature.get('refName'),
334
+ start: feature.get('start'),
335
+ end: feature.get('end'),
336
+ });
337
+ encodedGenotypes.push(encoded);
338
+ if (dataIsPhased) {
339
+ phasedHaplotypes.push(encodePhasedHaplotypes(genotypes, samples));
340
+ }
341
+ checkStopToken2(stopTokenCheck);
342
+ }
343
+ const n = snps.length;
344
+ const ldSize = (n * (n - 1)) / 2;
345
+ const ldValues = new Float32Array(ldSize);
346
+ let idx = 0;
347
+ for (let i = 1; i < n; i++) {
348
+ for (let j = 0; j < i; j++) {
349
+ const stats = dataIsPhased
350
+ ? calculateLDStatsPhased(phasedHaplotypes[i], phasedHaplotypes[j], signedLD)
351
+ : calculateLDStats(encodedGenotypes[i], encodedGenotypes[j], signedLD);
352
+ ldValues[idx++] = ldMetric === 'dprime' ? stats.dprime : stats.r2;
353
+ }
354
+ checkStopToken2(stopTokenCheck);
355
+ }
356
+ const filterStats = {
357
+ totalVariants,
358
+ passedVariants: snps.length,
359
+ filteredByMaf,
360
+ filteredByLength,
361
+ filteredByMultiallelic,
362
+ filteredByHwe,
363
+ filteredByCallRate,
364
+ };
365
+ const recombValues = new Float32Array(Math.max(0, n - 1));
366
+ const recombPositions = [];
367
+ for (let i = 0; i < n - 1; i++) {
368
+ let r2;
369
+ if (dataIsPhased) {
370
+ const stats = calculateLDStatsPhased(phasedHaplotypes[i], phasedHaplotypes[i + 1]);
371
+ r2 = stats.r2;
372
+ }
373
+ else {
374
+ const stats = calculateLDStats(encodedGenotypes[i], encodedGenotypes[i + 1]);
375
+ r2 = stats.r2;
376
+ }
377
+ recombValues[i] = 1 - r2;
378
+ const pos1 = snps[i].start;
379
+ const pos2 = snps[i + 1].start;
380
+ recombPositions.push((pos1 + pos2) / 2);
381
+ }
382
+ const recombination = {
383
+ values: recombValues,
384
+ positions: recombPositions,
385
+ };
386
+ return { snps, ldValues, metric: ldMetric, filterStats, recombination };
387
+ }
@@ -0,0 +1,16 @@
1
+ import type { LDMatrixResult, LDMetric } from './getLDMatrix.ts';
2
+ import type PluginManager from '@jbrowse/core/PluginManager';
3
+ import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
4
+ import type { Region } from '@jbrowse/core/util';
5
+ import type { StopToken } from '@jbrowse/core/util/stopToken';
6
+ export declare function getLDMatrixFromPlink({ pluginManager, args, }: {
7
+ pluginManager: PluginManager;
8
+ args: {
9
+ adapterConfig: AnyConfigurationModel;
10
+ stopToken?: StopToken;
11
+ sessionId: string;
12
+ headers?: Record<string, string>;
13
+ regions: Region[];
14
+ ldMetric?: LDMetric;
15
+ };
16
+ }): Promise<LDMatrixResult>;
@@ -0,0 +1,105 @@
1
+ import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache';
2
+ export async function getLDMatrixFromPlink({ pluginManager, args, }) {
3
+ const { regions, adapterConfig, sessionId, ldMetric = 'r2' } = args;
4
+ const adapter = await getAdapter(pluginManager, sessionId, adapterConfig);
5
+ const dataAdapter = adapter.dataAdapter;
6
+ const allRecords = [];
7
+ for (const region of regions) {
8
+ const records = await dataAdapter.getLDRecordsInRegion({
9
+ refName: region.refName,
10
+ start: region.start,
11
+ end: region.end,
12
+ });
13
+ for (const r of records) {
14
+ allRecords.push(r);
15
+ }
16
+ }
17
+ const snpMap = new Map();
18
+ for (const record of allRecords) {
19
+ const keyA = `${record.chrA}:${record.bpA}`;
20
+ if (!snpMap.has(keyA)) {
21
+ snpMap.set(keyA, {
22
+ id: record.snpA,
23
+ refName: record.chrA,
24
+ start: record.bpA,
25
+ end: record.bpA + 1,
26
+ });
27
+ }
28
+ const keyB = `${record.chrB}:${record.bpB}`;
29
+ if (!snpMap.has(keyB)) {
30
+ snpMap.set(keyB, {
31
+ id: record.snpB,
32
+ refName: record.chrB,
33
+ start: record.bpB,
34
+ end: record.bpB + 1,
35
+ });
36
+ }
37
+ }
38
+ const snps = [...snpMap.values()].sort((a, b) => {
39
+ if (a.refName !== b.refName) {
40
+ return a.refName.localeCompare(b.refName);
41
+ }
42
+ return a.start - b.start;
43
+ });
44
+ const snpToIndex = new Map();
45
+ for (const [idx, snp] of snps.entries()) {
46
+ snpToIndex.set(`${snp.refName}:${snp.start}`, idx);
47
+ }
48
+ const n = snps.length;
49
+ const ldSize = (n * (n - 1)) / 2;
50
+ const ldValues = new Float32Array(ldSize);
51
+ ldValues.fill(Number.NaN);
52
+ const getLowerTriIndex = (i, j) => {
53
+ if (i <= j) {
54
+ ;
55
+ [i, j] = [j, i];
56
+ }
57
+ return (i * (i - 1)) / 2 + j;
58
+ };
59
+ for (const record of allRecords) {
60
+ const keyA = `${record.chrA}:${record.bpA}`;
61
+ const keyB = `${record.chrB}:${record.bpB}`;
62
+ const idxA = snpToIndex.get(keyA);
63
+ const idxB = snpToIndex.get(keyB);
64
+ if (idxA !== undefined && idxB !== undefined && idxA !== idxB) {
65
+ const matrixIdx = getLowerTriIndex(idxA, idxB);
66
+ const value = ldMetric === 'dprime' ? (record.dprime ?? record.r2) : record.r2;
67
+ ldValues[matrixIdx] = value;
68
+ }
69
+ }
70
+ for (let i = 0; i < ldValues.length; i++) {
71
+ if (Number.isNaN(ldValues[i])) {
72
+ ldValues[i] = 0;
73
+ }
74
+ }
75
+ const recombValues = new Float32Array(Math.max(0, n - 1));
76
+ const recombPositions = [];
77
+ for (let i = 0; i < n - 1; i++) {
78
+ const snp1 = snps[i];
79
+ const snp2 = snps[i + 1];
80
+ const matrixIdx = getLowerTriIndex(i + 1, i);
81
+ const r2 = ldValues[matrixIdx] ?? 0;
82
+ recombValues[i] = 1 - r2;
83
+ recombPositions.push((snp1.start + snp2.start) / 2);
84
+ }
85
+ const recombination = {
86
+ values: recombValues,
87
+ positions: recombPositions,
88
+ };
89
+ const filterStats = {
90
+ filteredByCallRate: 0,
91
+ totalVariants: snps.length,
92
+ passedVariants: snps.length,
93
+ filteredByMaf: 0,
94
+ filteredByLength: 0,
95
+ filteredByMultiallelic: 0,
96
+ filteredByHwe: 0,
97
+ };
98
+ return {
99
+ snps,
100
+ ldValues,
101
+ metric: ldMetric,
102
+ filterStats,
103
+ recombination,
104
+ };
105
+ }
@@ -1,13 +1,12 @@
1
1
  import type { SampleInfo, Source } from '../shared/types.ts';
2
2
  import type PluginManager from '@jbrowse/core/PluginManager';
3
3
  import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
4
- import type { Region } from '@jbrowse/core/util';
5
- import type { StopToken } from '@jbrowse/core/util/stopToken';
4
+ import type { LastStopTokenCheck, Region } from '@jbrowse/core/util';
6
5
  export declare function getPhasedGenotypeMatrix({ pluginManager, args, }: {
7
6
  pluginManager: PluginManager;
8
7
  args: {
9
8
  adapterConfig: AnyConfigurationModel;
10
- stopToken?: StopToken;
9
+ stopTokenCheck?: LastStopTokenCheck;
11
10
  sessionId: string;
12
11
  headers?: Record<string, string>;
13
12
  regions: Region[];
@@ -1,10 +1,9 @@
1
1
  import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache';
2
- import { checkStopToken2, createStopTokenChecker, } from '@jbrowse/core/util/stopToken';
2
+ import { checkStopToken2 } from '@jbrowse/core/util/stopToken';
3
3
  import { firstValueFrom, toArray } from 'rxjs';
4
4
  import { getFeaturesThatPassMinorAlleleFrequencyFilter } from "../shared/minorAlleleFrequencyUtils.js";
5
5
  export async function getPhasedGenotypeMatrix({ pluginManager, args, }) {
6
- const { sources, minorAlleleFrequencyFilter, regions, adapterConfig, sessionId, lengthCutoffFilter, stopToken, sampleInfo, } = args;
7
- const lastCheck = createStopTokenChecker(stopToken);
6
+ const { sources, minorAlleleFrequencyFilter, regions, adapterConfig, sessionId, lengthCutoffFilter, stopTokenCheck, sampleInfo, } = args;
8
7
  const adapter = await getAdapter(pluginManager, sessionId, adapterConfig);
9
8
  const dataAdapter = adapter.dataAdapter;
10
9
  const rows = {};
@@ -19,7 +18,7 @@ export async function getPhasedGenotypeMatrix({ pluginManager, args, }) {
19
18
  const mafs = getFeaturesThatPassMinorAlleleFrequencyFilter({
20
19
  minorAlleleFrequencyFilter,
21
20
  lengthCutoffFilter,
22
- lastCheck,
21
+ stopTokenCheck,
23
22
  splitCache,
24
23
  features: await firstValueFrom(dataAdapter.getFeaturesInMultipleRegions(regions, args).pipe(toArray())),
25
24
  });
@@ -44,7 +43,7 @@ export async function getPhasedGenotypeMatrix({ pluginManager, args, }) {
44
43
  }
45
44
  }
46
45
  }
47
- checkStopToken2(lastCheck);
46
+ checkStopToken2(stopTokenCheck);
48
47
  }
49
48
  return rows;
50
49
  }
@@ -19,4 +19,7 @@ export interface ClusterGenotypeMatrixArgs extends BaseVariantRpcArgs {
19
19
  statusCallback: (arg: string) => void;
20
20
  sources: Source[];
21
21
  }
22
+ export interface GetLDMatrixArgs extends BaseVariantRpcArgs {
23
+ ldMetric?: 'r2' | 'dprime';
24
+ }
22
25
  export {};
@@ -1,6 +1,6 @@
1
- import { IntervalTree } from '@flatten-js/interval-tree';
2
1
  import VcfParser from '@gmod/vcf';
3
2
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
3
+ import { IntervalTree } from '@jbrowse/core/util';
4
4
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
5
5
  import type { Feature, Region } from '@jbrowse/core/util';
6
6
  import type { StatusCallback } from '@jbrowse/core/util/parseLineByLine';
@@ -1,7 +1,6 @@
1
- import { IntervalTree } from '@flatten-js/interval-tree';
2
1
  import VcfParser from '@gmod/vcf';
3
2
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
4
- import { fetchAndMaybeUnzip } from '@jbrowse/core/util';
3
+ import { IntervalTree, fetchAndMaybeUnzip } from '@jbrowse/core/util';
5
4
  import { openLocation } from '@jbrowse/core/util/io';
6
5
  import { ObservableCreate } from '@jbrowse/core/util/rxjs';
7
6
  import { parseVcfBuffer } from "./vcfParser.js";
@@ -21,6 +21,28 @@ export default function VcfExtensionPointsF(pluginManager) {
21
21
  vcfLocation: file,
22
22
  };
23
23
  }
24
+ else if (testAdapter(fileName, /\.ld\.b?gz$/i, adapterHint, 'PlinkLDTabixAdapter')) {
25
+ return {
26
+ type: 'PlinkLDTabixAdapter',
27
+ ldLocation: file,
28
+ index: {
29
+ location: index || makeIndex(file, '.tbi'),
30
+ indexType: makeIndexType(indexName, 'CSI', 'TBI'),
31
+ },
32
+ };
33
+ }
34
+ else if (testAdapter(fileName, /\.ld$/i, adapterHint, 'PlinkLDAdapter')) {
35
+ return {
36
+ type: 'PlinkLDAdapter',
37
+ ldLocation: file,
38
+ };
39
+ }
40
+ else if (testAdapter(fileName, /\.h5$/i, adapterHint, 'LdmatAdapter')) {
41
+ return {
42
+ type: 'LdmatAdapter',
43
+ ldmatLocation: file,
44
+ };
45
+ }
24
46
  else {
25
47
  return adapterGuesser(file, index, adapterHint);
26
48
  }
@@ -28,9 +50,13 @@ export default function VcfExtensionPointsF(pluginManager) {
28
50
  });
29
51
  pluginManager.addToExtensionPoint('Core-guessTrackTypeForLocation', (trackTypeGuesser) => {
30
52
  return (adapterName) => {
31
- return ['VcfTabixAdapter', 'VcfAdapter'].includes(adapterName)
32
- ? 'VariantTrack'
33
- : trackTypeGuesser(adapterName);
53
+ if (['VcfTabixAdapter', 'VcfAdapter'].includes(adapterName)) {
54
+ return 'VariantTrack';
55
+ }
56
+ if (['PlinkLDAdapter', 'PlinkLDTabixAdapter', 'LdmatAdapter'].includes(adapterName)) {
57
+ return 'LDTrack';
58
+ }
59
+ return trackTypeGuesser(adapterName);
34
60
  };
35
61
  });
36
62
  }
@@ -1,6 +1,6 @@
1
1
  import { type Feature } from '@jbrowse/core/util';
2
2
  import type VCFParser from '@gmod/vcf';
3
- import type { Variant } from '@gmod/vcf';
3
+ import type { GenotypeCallback, Variant } from '@gmod/vcf';
4
4
  export default class VCFFeature implements Feature {
5
5
  private variant;
6
6
  private parser;
@@ -15,5 +15,6 @@ export default class VCFFeature implements Feature {
15
15
  parent(): undefined;
16
16
  children(): undefined;
17
17
  id(): string;
18
+ processGenotypes(callback: GenotypeCallback): void;
18
19
  toJSON(): any;
19
20
  }
@@ -77,11 +77,13 @@ export default class VCFFeature {
77
77
  id() {
78
78
  return this._id;
79
79
  }
80
+ processGenotypes(callback) {
81
+ this.variant.processGenotypes(callback);
82
+ }
80
83
  toJSON() {
81
- const { SAMPLES, GENOTYPES, ...rest } = this.variant;
82
84
  return {
83
85
  uniqueId: this._id,
84
- ...rest,
86
+ ...this.variant.toJSON(),
85
87
  ...this.data,
86
88
  samples: this.variant.SAMPLES(),
87
89
  };
package/esm/index.d.ts CHANGED
@@ -3,5 +3,6 @@ import type PluginManager from '@jbrowse/core/PluginManager';
3
3
  export default class VariantsPlugin extends Plugin {
4
4
  name: string;
5
5
  install(pluginManager: PluginManager): void;
6
+ configure(pluginManager: PluginManager): void;
6
7
  }
7
8
  export { default as VcfFeature } from './VcfFeature/index.ts';