@jbrowse/plugin-alignments 1.7.7 → 1.7.8

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 (31) hide show
  1. package/dist/BamAdapter/BamAdapter.d.ts +1 -1
  2. package/dist/BamAdapter/BamAdapter.js +3 -3
  3. package/dist/BamAdapter/MismatchParser.d.ts +2 -5
  4. package/dist/BamAdapter/MismatchParser.js +104 -44
  5. package/dist/BamAdapter/MismatchParser.test.js +6 -14
  6. package/dist/CramAdapter/CramAdapter.d.ts +10 -9
  7. package/dist/CramAdapter/CramAdapter.js +6 -6
  8. package/dist/CramAdapter/CramSlightlyLazyFeature.js +35 -30
  9. package/dist/LinearPileupDisplay/model.js +1 -1
  10. package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +2 -3
  11. package/dist/LinearSNPCoverageDisplay/models/model.js +1 -1
  12. package/dist/PileupRenderer/PileupLayoutSession.d.ts +3 -0
  13. package/dist/PileupRenderer/PileupLayoutSession.js +3 -1
  14. package/dist/PileupRenderer/PileupRenderer.d.ts +1 -1
  15. package/dist/PileupRenderer/PileupRenderer.js +125 -162
  16. package/dist/PileupRenderer/configSchema.js +2 -2
  17. package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +4 -6
  18. package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +92 -96
  19. package/package.json +3 -3
  20. package/src/BamAdapter/BamAdapter.ts +3 -3
  21. package/src/BamAdapter/MismatchParser.test.ts +5 -7
  22. package/src/BamAdapter/MismatchParser.ts +69 -58
  23. package/src/CramAdapter/CramAdapter.ts +14 -10
  24. package/src/CramAdapter/CramSlightlyLazyFeature.ts +84 -91
  25. package/src/LinearPileupDisplay/model.ts +1 -1
  26. package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +32 -25
  27. package/src/LinearSNPCoverageDisplay/models/model.ts +1 -1
  28. package/src/PileupRenderer/PileupLayoutSession.ts +6 -1
  29. package/src/PileupRenderer/PileupRenderer.tsx +70 -68
  30. package/src/PileupRenderer/configSchema.ts +2 -2
  31. package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +84 -76
@@ -57,36 +57,17 @@ function isInterbase(type) {
57
57
  function inc(bin, strand, type, field) {
58
58
  var thisBin = bin[type][field];
59
59
 
60
- if (!thisBin) {
60
+ if (thisBin === undefined) {
61
61
  thisBin = bin[type][field] = {
62
62
  total: 0,
63
- strands: {
64
- '-1': 0,
65
- '0': 0,
66
- '1': 0
67
- }
63
+ '-1': 0,
64
+ '0': 0,
65
+ '1': 0
68
66
  };
69
67
  }
70
68
 
71
69
  thisBin.total++;
72
- thisBin.strands[strand]++;
73
- } // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
-
75
-
76
- function dec(bin, strand, type, field) {
77
- if (!bin[type][field]) {
78
- bin[type][field] = {
79
- total: 0,
80
- strands: {
81
- '-1': 0,
82
- '0': 0,
83
- '1': 0
84
- }
85
- };
86
- }
87
-
88
- bin[type][field].total--;
89
- bin[type][field].strands[strand]--;
70
+ thisBin[strand]++;
90
71
  }
91
72
 
92
73
  var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
@@ -396,36 +377,43 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
396
377
 
397
378
  _loop = function _loop(i) {
398
379
  var feature = features[i];
399
- var ops = (0, _MismatchParser.parseCigar)(feature.get('CIGAR'));
400
380
  var fstart = feature.get('start');
401
381
  var fend = feature.get('end');
402
382
  var fstrand = feature.get('strand');
403
383
 
404
- for (var j = fstart; j < fend; j++) {
384
+ for (var j = fstart; j < fend + 1; j++) {
405
385
  var _i = j - region.start;
406
386
 
407
387
  if (_i >= 0 && _i < binMax) {
408
- var bin = bins[_i] || {
409
- total: 0,
410
- lowqual: {},
411
- cov: {},
412
- delskips: {},
413
- noncov: {},
414
- ref: {}
415
- };
388
+ if (bins[_i] === undefined) {
389
+ bins[_i] = {
390
+ total: 0,
391
+ ref: 0,
392
+ '-1': 0,
393
+ '0': 0,
394
+ '1': 0,
395
+ lowqual: {},
396
+ cov: {},
397
+ delskips: {},
398
+ noncov: {}
399
+ };
400
+ }
416
401
 
417
402
  if (j !== fend) {
418
- bin.total++;
419
- inc(bin, fstrand, 'ref', 'ref');
403
+ bins[_i].total++;
404
+ bins[_i].ref++;
405
+ bins[_i][fstrand]++;
420
406
  }
421
-
422
- bins[_i] = bin;
423
407
  }
424
408
  }
425
409
 
426
410
  if ((colorBy === null || colorBy === void 0 ? void 0 : colorBy.type) === 'modifications') {
427
411
  var seq = feature.get('seq');
428
412
  var mm = (0, _util.getTagAlt)(feature, 'MM', 'Mm') || '';
413
+ var ops = (0, _MismatchParser.parseCigar)(feature.get('CIGAR'));
414
+
415
+ var _fend = feature.get('end');
416
+
429
417
  (0, _MismatchParser.getModificationPositions)(mm, seq, fstrand).forEach(function (_ref4) {
430
418
  var type = _ref4.type,
431
419
  positions = _ref4.positions;
@@ -439,9 +427,14 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
439
427
  var pos = _step.value;
440
428
  var epos = pos + fstart - region.start;
441
429
 
442
- if (epos >= 0 && epos < bins.length && pos + fstart < fend) {
443
- var _bin = bins[epos];
444
- inc(_bin, fstrand, 'cov', mod);
430
+ if (epos >= 0 && epos < bins.length && pos + fstart < _fend) {
431
+ var bin = bins[epos];
432
+
433
+ if (bin) {
434
+ inc(bin, fstrand, 'cov', mod);
435
+ } else {
436
+ console.warn('Undefined position in modifications snpcoverage encountered');
437
+ }
445
438
  }
446
439
  }
447
440
  } catch (err) {
@@ -462,13 +455,16 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
462
455
  var _mm = (0, _util.getTagAlt)(feature, 'MM', 'Mm') || '';
463
456
 
464
457
  var methBins = new Array(region.end - region.start).fill(0);
458
+
459
+ var _ops = (0, _MismatchParser.parseCigar)(feature.get('CIGAR'));
460
+
465
461
  (0, _MismatchParser.getModificationPositions)(_mm, _seq, fstrand).forEach(function (_ref5) {
466
462
  var type = _ref5.type,
467
463
  positions = _ref5.positions;
468
464
 
469
465
  // we are processing methylation
470
466
  if (type === 'm') {
471
- var _iterator2 = _createForOfIteratorHelper((0, _MismatchParser.getNextRefPos)(ops, positions)),
467
+ var _iterator2 = _createForOfIteratorHelper((0, _MismatchParser.getNextRefPos)(_ops, positions)),
472
468
  _step2;
473
469
 
474
470
  try {
@@ -496,80 +492,80 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
496
492
 
497
493
  var l2 = regionSeq[_i2 + 1].toLowerCase();
498
494
 
499
- var _bin2 = bins[_i2];
495
+ var bin = bins[_i2];
500
496
  var bin1 = bins[_i2 + 1]; // color
501
497
 
502
498
  if (l1 === 'c' && l2 === 'g') {
503
499
  if (methBins[_i2] || methBins[_i2 + 1]) {
504
- inc(_bin2, fstrand, 'cov', 'meth');
500
+ inc(bin, fstrand, 'cov', 'meth');
505
501
  inc(bin1, fstrand, 'cov', 'meth');
506
- dec(_bin2, fstrand, 'ref', 'ref');
507
- dec(bin1, fstrand, 'ref', 'ref');
502
+ bins[_i2].ref--;
503
+ bins[_i2][fstrand]--;
504
+ bins[_i2 + 1].ref--;
505
+ bins[_i2 + 1][fstrand]--;
508
506
  } else {
509
- inc(_bin2, fstrand, 'cov', 'unmeth');
507
+ inc(bin, fstrand, 'cov', 'unmeth');
510
508
  inc(bin1, fstrand, 'cov', 'unmeth');
511
- dec(_bin2, fstrand, 'ref', 'ref');
512
- dec(bin1, fstrand, 'ref', 'ref');
509
+ bins[_i2].ref--;
510
+ bins[_i2][fstrand]--;
511
+ bins[_i2 + 1].ref--;
512
+ bins[_i2 + 1][fstrand]--;
513
513
  }
514
514
  }
515
515
  }
516
516
  }
517
517
  } // normal SNP based coloring
518
- else {
519
- var mismatches = feature.get('mismatches');
520
518
 
521
- if (mismatches) {
522
- for (var _i3 = 0; _i3 < mismatches.length; _i3++) {
523
- var mismatch = mismatches[_i3];
524
- var mstart = fstart + mismatch.start;
525
519
 
526
- for (var _j2 = mstart; _j2 < mstart + mismatchLen(mismatch); _j2++) {
527
- var epos = _j2 - region.start;
520
+ var mismatches = feature.get('mismatches') || [];
521
+ var colorSNPs = (colorBy === null || colorBy === void 0 ? void 0 : colorBy.type) !== 'modifications' && (colorBy === null || colorBy === void 0 ? void 0 : colorBy.type) !== 'methylation';
528
522
 
529
- if (epos >= 0 && epos < bins.length) {
530
- var _bin3 = bins[epos];
531
- var base = mismatch.base,
532
- type = mismatch.type;
533
- var interbase = isInterbase(type);
523
+ for (var _i3 = 0; _i3 < mismatches.length; _i3++) {
524
+ var mismatch = mismatches[_i3];
525
+ var mstart = fstart + mismatch.start;
526
+ var mlen = mismatchLen(mismatch);
527
+ var mend = mstart + mlen;
534
528
 
535
- if (!interbase) {
536
- dec(_bin3, fstrand, 'ref', 'ref');
537
- } else {
538
- inc(_bin3, fstrand, 'noncov', type);
539
- }
529
+ for (var _j2 = mstart; _j2 < mstart + mlen; _j2++) {
530
+ var epos = _j2 - region.start;
540
531
 
541
- if (type === 'deletion' || type === 'skip') {
542
- inc(_bin3, fstrand, 'delskips', type);
543
- _bin3.total--;
544
- } else if (!interbase) {
545
- inc(_bin3, fstrand, 'cov', base);
546
- }
547
- }
548
- }
549
- }
532
+ if (epos >= 0 && epos < bins.length) {
533
+ var _bin = bins[epos];
534
+ var base = mismatch.base,
535
+ type = mismatch.type;
536
+ var interbase = isInterbase(type);
550
537
 
551
- mismatches.filter(function (mismatch) {
552
- return mismatch.type === 'skip';
553
- }).forEach(function (mismatch) {
554
- var mstart = feature.get('start') + mismatch.start;
555
- var start = mstart;
556
- var end = mstart + mismatch.length;
557
- var strand = feature.get('strand');
558
- var hash = "".concat(start, "_").concat(end, "_").concat(strand);
559
-
560
- if (!skipmap[hash]) {
561
- skipmap[hash] = {
562
- feature: feature,
563
- start: start,
564
- end: end,
565
- strand: strand,
566
- xs: (0, _util.getTag)(feature, 'XS') || (0, _util.getTag)(feature, 'TS'),
567
- score: 1
568
- };
538
+ if (!interbase) {
539
+ _bin.ref--;
540
+ _bin[fstrand]--;
569
541
  } else {
570
- skipmap[hash].score++;
542
+ inc(_bin, fstrand, 'noncov', type);
571
543
  }
572
- });
544
+
545
+ if (type === 'deletion' || type === 'skip') {
546
+ inc(_bin, fstrand, 'delskips', type);
547
+ _bin.total--;
548
+ } else if (!interbase && colorSNPs) {
549
+ inc(_bin, fstrand, 'cov', base);
550
+ }
551
+ }
552
+ }
553
+
554
+ if (mismatch.type === 'skip') {
555
+ var hash = "".concat(mstart, "_").concat(mend, "_").concat(fstrand);
556
+
557
+ if (skipmap[hash] === undefined) {
558
+ skipmap[hash] = {
559
+ feature: feature,
560
+ start: mstart,
561
+ end: mend,
562
+ strand: fstrand,
563
+ xs: (0, _util.getTag)(feature, 'XS') || (0, _util.getTag)(feature, 'TS'),
564
+ score: 0
565
+ };
566
+ }
567
+
568
+ skipmap[hash].score++;
573
569
  }
574
570
  }
575
571
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-alignments",
3
- "version": "1.7.7",
3
+ "version": "1.7.8",
4
4
  "description": "JBrowse 2 alignments adapters, tracks, etc.",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@babel/runtime": "^7.17.9",
37
37
  "@gmod/bam": "^1.1.15",
38
- "@gmod/cram": "^1.6.1",
38
+ "@gmod/cram": "^1.6.4",
39
39
  "@material-ui/icons": "^4.9.1",
40
40
  "color": "^3.1.2",
41
41
  "copy-to-clipboard": "^3.3.1",
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "2c26e04ae942c380bf2f5b79ef7a49cc32b7bfed"
60
+ "gitHead": "b429fa2bb5734fc8a5380988f6dfdd3f7a41a39f"
61
61
  }
@@ -171,7 +171,7 @@ export default class BamAdapter extends BaseFeatureDataAdapter {
171
171
  flagInclude: number
172
172
  flagExclude: number
173
173
  tagFilter: { tag: string; value: unknown }
174
- name: string
174
+ readName: string
175
175
  }
176
176
  },
177
177
  ) {
@@ -187,7 +187,7 @@ export default class BamAdapter extends BaseFeatureDataAdapter {
187
187
  flagInclude = 0,
188
188
  flagExclude = 0,
189
189
  tagFilter,
190
- name,
190
+ readName,
191
191
  } = filterBy || {}
192
192
 
193
193
  for (const record of records) {
@@ -214,7 +214,7 @@ export default class BamAdapter extends BaseFeatureDataAdapter {
214
214
  }
215
215
  }
216
216
 
217
- if (name && record.get('name') !== name) {
217
+ if (readName && record.get('name') !== readName) {
218
218
  continue
219
219
  }
220
220
 
@@ -233,17 +233,15 @@ test('clipping', () => {
233
233
  ])
234
234
  })
235
235
 
236
- test('getNextRefPos basic', () => {
236
+ test('getNextRefPos test 1', () => {
237
237
  const cigar = parseCigar('10S10M1I4M1D15M')
238
238
  const iter = getNextRefPos(cigar, [5, 10, 15, 20, 25, 30, 35])
239
- const [...vals] = iter
240
- expect(vals).toEqual([-5, 0, 5, 10, 14, 20, 25])
239
+ expect([...iter]).toEqual([0, 5, 15, 20, 25])
241
240
  })
242
- test('getNextRefPos with many indels', () => {
243
- const cigar = parseCigar('10S4M1D1IM10')
241
+ test('getNextRefPos test 2', () => {
242
+ const cigar = parseCigar('10S15M')
244
243
  const iter = getNextRefPos(cigar, [5, 10, 15])
245
- const [...vals] = iter
246
- expect(vals).toEqual([-5, 0, 5])
244
+ expect([...iter]).toEqual([0, 5])
247
245
  })
248
246
 
249
247
  test('getModificationPositions', () => {
@@ -232,26 +232,32 @@ export function getMismatches(
232
232
  // get relative reference sequence positions for positions given relative to
233
233
  // the read sequence
234
234
  export function* getNextRefPos(cigarOps: string[], positions: number[]) {
235
- let cigarIdx = 0
236
235
  let readPos = 0
237
236
  let refPos = 0
237
+ let currPos = 0
238
238
 
239
- for (let i = 0; i < positions.length; i++) {
240
- const pos = positions[i]
241
- for (; cigarIdx < cigarOps.length && readPos < pos; cigarIdx += 2) {
242
- const len = +cigarOps[cigarIdx]
243
- const op = cigarOps[cigarIdx + 1]
244
- if (op === 'S' || op === 'I') {
245
- readPos += len
246
- } else if (op === 'D' || op === 'N') {
247
- refPos += len
248
- } else if (op === 'M' || op === 'X' || op === '=') {
249
- readPos += len
250
- refPos += len
239
+ for (let i = 0; i < cigarOps.length && currPos < positions.length; i += 2) {
240
+ const len = +cigarOps[i]
241
+ const op = cigarOps[i + 1]
242
+ if (op === 'S' || op === 'I') {
243
+ for (let i = 0; i < len && currPos < positions.length; i++) {
244
+ if (positions[currPos] === readPos + i) {
245
+ currPos++
246
+ }
247
+ }
248
+ readPos += len
249
+ } else if (op === 'D' || op === 'N') {
250
+ refPos += len
251
+ } else if (op === 'M' || op === 'X' || op === '=') {
252
+ for (let i = 0; i < len && currPos < positions.length; i++) {
253
+ if (positions[currPos] === readPos + i) {
254
+ yield refPos + i
255
+ currPos++
256
+ }
251
257
  }
258
+ readPos += len
259
+ refPos += len
252
260
  }
253
-
254
- yield positions[i] - readPos + refPos
255
261
  }
256
262
  }
257
263
  export function getModificationPositions(
@@ -260,54 +266,59 @@ export function getModificationPositions(
260
266
  fstrand: number,
261
267
  ) {
262
268
  const seq = fstrand === -1 ? revcom(fseq) : fseq
263
- return mm
264
- .split(';')
265
- .filter(mod => !!mod)
266
- .map(mod => {
267
- const [basemod, ...skips] = mod.split(',')
269
+ const mods = mm.split(';').filter(mod => !!mod)
270
+ const result = []
271
+ for (let i = 0; i < mods.length; i++) {
272
+ const mod = mods[i]
273
+ const [basemod, ...skips] = mod.split(',')
268
274
 
269
- // regexes based on parse_mm.pl from hts-specs
270
- const matches = basemod.match(/([A-Z])([-+])([^,.?]+)([.?])?/)
271
- if (!matches) {
272
- throw new Error('bad format for MM tag')
273
- }
274
- const [, base, strand, typestr] = matches
275
+ // regexes based on parse_mm.pl from hts-specs
276
+ const matches = basemod.match(/([A-Z])([-+])([^,.?]+)([.?])?/)
277
+ if (!matches) {
278
+ throw new Error('bad format for MM tag')
279
+ }
280
+ const [, base, strand, typestr] = matches
275
281
 
276
- // can be a multi e.g. C+mh for both meth (m) and hydroxymeth (h) so
277
- // split, and they can also be chemical codes (ChEBI) e.g. C+16061
278
- const types = typestr.split(/(\d+|.)/).filter(f => !!f)
282
+ // can be a multi e.g. C+mh for both meth (m) and hydroxymeth (h) so
283
+ // split, and they can also be chemical codes (ChEBI) e.g. C+16061
284
+ const types = typestr.split(/(\d+|.)/).filter(f => !!f)
279
285
 
280
- if (strand === '-') {
281
- console.warn('unsupported negative strand modifications')
282
- // make sure to return a somewhat matching type even in this case
283
- return { type: 'unsupported', positions: [] }
284
- }
286
+ if (strand === '-') {
287
+ console.warn('unsupported negative strand modifications')
288
+ // make sure to return a somewhat matching type even in this case
289
+ result.push({ type: 'unsupported', positions: [] as number[] })
290
+ }
285
291
 
286
- // this logic also based on parse_mm.pl from hts-specs is that in the
287
- // sequence of the read, if we have a modification type e.g. C+m;2 and a
288
- // sequence ACGTACGTAC we skip the two instances of C and go to the last
289
- // C
290
- return types.map(type => {
291
- let i = 0
292
- return {
293
- type,
294
- positions: skips
295
- .map(score => +score)
296
- .map(delta => {
297
- do {
298
- if (base === 'N' || base === seq[i]) {
299
- delta--
300
- }
301
- i++
302
- } while (delta >= 0 && i < seq.length)
303
- const temp = i - 1
304
- return fstrand === -1 ? seq.length - 1 - temp : temp
305
- })
306
- .sort((a, b) => a - b),
307
- }
292
+ // this logic also based on parse_mm.pl from hts-specs is that in the
293
+ // sequence of the read, if we have a modification type e.g. C+m;2 and a
294
+ // sequence ACGTACGTAC we skip the two instances of C and go to the last
295
+ // C
296
+ for (let j = 0; j < types.length; j++) {
297
+ const type = types[j]
298
+ let i = 0
299
+ const positions = []
300
+ for (let k = 0; k < skips.length; k++) {
301
+ let delta = +skips[k]
302
+ do {
303
+ if (base === 'N' || base === seq[i]) {
304
+ delta--
305
+ }
306
+ i++
307
+ } while (delta >= 0 && i < seq.length)
308
+
309
+ const temp = i - 1
310
+ positions.push(fstrand === -1 ? seq.length - 1 - temp : temp)
311
+ }
312
+ if (fstrand === -1) {
313
+ positions.sort((a, b) => a - b)
314
+ }
315
+ result.push({
316
+ type,
317
+ positions,
308
318
  })
309
- })
310
- .flat()
319
+ }
320
+ }
321
+ return result
311
322
  }
312
323
 
313
324
  export function getModificationTypes(mm: string) {
@@ -20,6 +20,13 @@ interface Header {
20
20
  readGroups?: number[]
21
21
  }
22
22
 
23
+ interface FilterBy {
24
+ flagInclude: number
25
+ flagExclude: number
26
+ tagFilter: { tag: string; value: unknown }
27
+ readName: string
28
+ }
29
+
23
30
  export default class CramAdapter extends BaseFeatureDataAdapter {
24
31
  samHeader: Header = {}
25
32
 
@@ -206,12 +213,7 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
206
213
  getFeatures(
207
214
  region: Region & { originalRefName?: string },
208
215
  opts?: BaseOptions & {
209
- filterBy: {
210
- flagInclude: number
211
- flagExclude: number
212
- tagFilter: { tag: string; value: unknown }
213
- name: string
214
- }
216
+ filterBy: FilterBy
215
217
  },
216
218
  ) {
217
219
  const { signal, filterBy, statusCallback = () => {} } = opts || {}
@@ -234,7 +236,7 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
234
236
  flagInclude = 0,
235
237
  flagExclude = 0,
236
238
  tagFilter,
237
- name,
239
+ readName,
238
240
  } = filterBy || {}
239
241
 
240
242
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -251,9 +253,11 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
251
253
  })
252
254
  }
253
255
 
254
- if (name) {
255
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
- filtered = filtered.filter((record: any) => record.name === name)
256
+ if (readName) {
257
+ filtered = filtered.filter(
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ (record: any) => record.readName === readName,
260
+ )
257
261
  }
258
262
 
259
263
  // eslint-disable-next-line @typescript-eslint/no-explicit-any