@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
@@ -131,7 +131,8 @@ export default class CramSlightlyLazyFeature implements Feature {
131
131
  let sublen = 0
132
132
  if (typeof this.record.readFeatures !== 'undefined') {
133
133
  // @ts-ignore
134
- this.record.readFeatures.forEach(({ code, refPos, sub, data }) => {
134
+ for (let i = 0; i < this.record.readFeatures.length; i++) {
135
+ const { code, refPos, sub, data } = this.record.readFeatures[i]
135
136
  sublen = refPos - last_pos
136
137
  seq += ref.substring(last_pos - refStart, refPos - refStart)
137
138
  last_pos = refPos
@@ -200,7 +201,7 @@ export default class CramSlightlyLazyFeature implements Feature {
200
201
  cigar += `${data}H`
201
202
  oplen = 0
202
203
  } // else q or Q
203
- })
204
+ }
204
205
  } else {
205
206
  sublen = this.record.readLength - seq.length
206
207
  }
@@ -303,95 +304,87 @@ export default class CramSlightlyLazyFeature implements Feature {
303
304
  return []
304
305
  }
305
306
  const start = this.get('start')
306
- const mismatches: Mismatch[] = []
307
- readFeatures.forEach(
308
- (args: {
309
- code: string
310
- refPos: number
311
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
312
- data: any
313
- pos: number
314
- sub: string
315
- ref: string
316
- }) => {
317
- const { code, pos, data, sub, ref } = args
318
- const refPos = args.refPos - 1 - start
319
- if (code === 'X') {
320
- // substitution
321
- mismatches.push({
322
- start: refPos,
323
- length: 1,
324
- base: sub,
325
- qual: qual?.[pos],
326
- altbase: ref,
327
- type: 'mismatch',
328
- })
329
- } else if (code === 'I') {
330
- // insertion
331
- mismatches.push({
332
- start: refPos,
333
- type: 'insertion',
334
- base: `${data.length}`,
335
- length: 0,
336
- })
337
- } else if (code === 'N') {
338
- // reference skip
339
- mismatches.push({
340
- type: 'skip',
341
- length: data,
342
- start: refPos,
343
- base: 'N',
344
- })
345
- } else if (code === 'S') {
346
- // soft clip
347
- const len = data.length
348
- mismatches.push({
349
- start: refPos,
350
- type: 'softclip',
351
- base: `S${len}`,
352
- cliplen: len,
353
- length: 1,
354
- })
355
- } else if (code === 'P') {
356
- // padding
357
- } else if (code === 'H') {
358
- // hard clip
359
- const len = data
360
- mismatches.push({
361
- start: refPos,
362
- type: 'hardclip',
363
- base: `H${len}`,
364
- cliplen: len,
365
- length: 1,
366
- })
367
- } else if (code === 'D') {
368
- // deletion
369
- mismatches.push({
370
- type: 'deletion',
371
- length: data,
372
- start: refPos,
373
- base: '*',
374
- })
375
- } else if (code === 'b') {
376
- // stretch of bases
377
- } else if (code === 'q') {
378
- // stretch of qual scores
379
- } else if (code === 'B') {
380
- // a pair of [base, qual]
381
- } else if (code === 'i') {
382
- // single-base insertion
383
- // insertion
384
- mismatches.push({
385
- start: refPos,
386
- type: 'insertion',
387
- base: data,
388
- length: 1,
389
- })
390
- } else if (code === 'Q') {
391
- // single quality value
307
+ const mismatches: Mismatch[] = new Array(readFeatures.length)
308
+ let j = 0
309
+ for (let i = 0; i < readFeatures.length; i++) {
310
+ const f = readFeatures[i]
311
+ const { code, pos, data, sub, ref } = f
312
+ const refPos = f.refPos - 1 - start
313
+ if (code === 'X') {
314
+ // substitution
315
+ mismatches[j++] = {
316
+ start: refPos,
317
+ length: 1,
318
+ base: sub,
319
+ qual: qual?.[pos],
320
+ altbase: ref,
321
+ type: 'mismatch',
392
322
  }
393
- },
394
- )
395
- return mismatches
323
+ } else if (code === 'I') {
324
+ // insertion
325
+ mismatches[j++] = {
326
+ start: refPos,
327
+ type: 'insertion',
328
+ base: `${data.length}`,
329
+ length: 0,
330
+ }
331
+ } else if (code === 'N') {
332
+ // reference skip
333
+ mismatches[j++] = {
334
+ type: 'skip',
335
+ length: data,
336
+ start: refPos,
337
+ base: 'N',
338
+ }
339
+ } else if (code === 'S') {
340
+ // soft clip
341
+ const len = data.length
342
+ mismatches[j++] = {
343
+ start: refPos,
344
+ type: 'softclip',
345
+ base: `S${len}`,
346
+ cliplen: len,
347
+ length: 1,
348
+ }
349
+ } else if (code === 'P') {
350
+ // padding
351
+ } else if (code === 'H') {
352
+ // hard clip
353
+ const len = data
354
+ mismatches[j++] = {
355
+ start: refPos,
356
+ type: 'hardclip',
357
+ base: `H${len}`,
358
+ cliplen: len,
359
+ length: 1,
360
+ }
361
+ } else if (code === 'D') {
362
+ // deletion
363
+ mismatches[j++] = {
364
+ type: 'deletion',
365
+ length: data,
366
+ start: refPos,
367
+ base: '*',
368
+ }
369
+ } else if (code === 'b') {
370
+ // stretch of bases
371
+ } else if (code === 'q') {
372
+ // stretch of qual scores
373
+ } else if (code === 'B') {
374
+ // a pair of [base, qual]
375
+ } else if (code === 'i') {
376
+ // single-base insertion
377
+ // insertion
378
+ mismatches[j++] = {
379
+ start: refPos,
380
+ type: 'insertion',
381
+ base: data,
382
+ length: 1,
383
+ }
384
+ } else if (code === 'Q') {
385
+ // single quality value
386
+ }
387
+ }
388
+ return mismatches.slice(0, j)
396
389
  }
397
390
  }
@@ -409,7 +409,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
409
409
  displayModel: self,
410
410
  sortedBy,
411
411
  colorBy,
412
- filterBy,
412
+ filterBy: JSON.parse(JSON.stringify(filterBy)),
413
413
  colorTagMap: JSON.parse(JSON.stringify(colorTagMap)),
414
414
  modificationTagMap: JSON.parse(JSON.stringify(modificationTagMap)),
415
415
  showSoftClip: self.showSoftClipping,
@@ -7,7 +7,6 @@ import { Tooltip } from '@jbrowse/plugin-wiggle'
7
7
  type Count = {
8
8
  [key: string]: {
9
9
  total: number
10
- strands: { [key: string]: number }
11
10
  }
12
11
  }
13
12
 
@@ -18,6 +17,9 @@ type SNPInfo = {
18
17
  noncov: Count
19
18
  delskips: Count
20
19
  total: number
20
+ '-1': number
21
+ '0': number
22
+ '1': number
21
23
  }
22
24
 
23
25
  const en = (n: number) => n.toLocaleString('en-US')
@@ -50,33 +52,38 @@ const TooltipContents = React.forwardRef(
50
52
  <tbody>
51
53
  <tr>
52
54
  <td>Total</td>
53
- <td>{total}</td>
55
+ <td>{info.total}</td>
56
+ </tr>
57
+ <tr>
58
+ <td>REF</td>
59
+ <td>{info.ref}</td>
60
+ <td>
61
+ {info['-1'] ? `${info['-1']}(-)` : ''}
62
+ {info['1'] ? `${info['1']}(+)` : ''}
63
+ </td>
54
64
  <td />
55
65
  </tr>
56
66
 
57
- {Object.entries(info).map(([key, entry]) => {
58
- return Object.entries(entry).map(([base, score]) => {
59
- const { strands } = score
60
- return (
61
- <tr key={base}>
62
- <td>{base.toUpperCase()}</td>
63
- <td>{score.total}</td>
64
- <td>
65
- {base === 'total' || base === 'skip'
66
- ? '---'
67
- : `${Math.floor(
68
- (score.total / (total || score.total || 1)) * 100,
69
- )}%`}
70
- </td>
71
- <td>
72
- {strands['-1'] ? `${strands['-1']}(-)` : ''}
73
- {strands['1'] ? `${strands['1']}(+)` : ''}
74
- </td>
75
- <td>{key}</td>
76
- </tr>
77
- )
78
- })
79
- })}
67
+ {Object.entries(info).map(([key, entry]) =>
68
+ Object.entries(entry).map(([base, score]) => (
69
+ <tr key={base}>
70
+ <td>{base.toUpperCase()}</td>
71
+ <td>{score.total}</td>
72
+ <td>
73
+ {base === 'total' || base === 'skip'
74
+ ? '---'
75
+ : `${Math.floor(
76
+ (score.total / (total || score.total || 1)) * 100,
77
+ )}%`}
78
+ </td>
79
+ <td>
80
+ {score['-1'] ? `${score['-1']}(-)` : ''}
81
+ {score['1'] ? `${score['1']}(+)` : ''}
82
+ </td>
83
+ <td>{key}</td>
84
+ </tr>
85
+ )),
86
+ )}
80
87
  </tbody>
81
88
  </table>
82
89
  </div>
@@ -143,7 +143,7 @@ const stateModelFactory = (
143
143
  // must use getSnapshot because otherwise changes to e.g. just the
144
144
  // colorBy.type are not read
145
145
  colorBy: self.colorBy ? getSnapshot(self.colorBy) : undefined,
146
- filterBy: self.filterBy,
146
+ filterBy: self.filterBy ? getSnapshot(self.filterBy) : undefined,
147
147
  }
148
148
  },
149
149
  }
@@ -10,6 +10,7 @@ export interface PileupLayoutSessionProps {
10
10
  config: AnyConfigurationModel
11
11
  bpPerPx: number
12
12
  filters: SerializableFilterChain
13
+ filterBy: unknown
13
14
  sortedBy: unknown
14
15
  showSoftClip: unknown
15
16
  }
@@ -19,6 +20,7 @@ interface CachedPileupLayout {
19
20
  layout: MyMultiLayout
20
21
  config: AnyConfigurationModel
21
22
  filters: SerializableFilterChain
23
+ filterBy: unknown
22
24
  sortedBy: unknown
23
25
  showSoftClip: boolean
24
26
  }
@@ -26,6 +28,7 @@ interface CachedPileupLayout {
26
28
  // Adds extra conditions to see if cached layout is valid
27
29
  export class PileupLayoutSession extends LayoutSession {
28
30
  sortedBy: unknown
31
+ filterBy: unknown
29
32
 
30
33
  showSoftClip = false
31
34
 
@@ -38,7 +41,8 @@ export class PileupLayoutSession extends LayoutSession {
38
41
  return (
39
42
  super.cachedLayoutIsValid(cachedLayout) &&
40
43
  this.showSoftClip === cachedLayout.showSoftClip &&
41
- deepEqual(this.sortedBy, cachedLayout.sortedBy)
44
+ deepEqual(this.sortedBy, cachedLayout.sortedBy) &&
45
+ deepEqual(this.filterBy, cachedLayout.filterBy)
42
46
  )
43
47
  }
44
48
 
@@ -50,6 +54,7 @@ export class PileupLayoutSession extends LayoutSession {
50
54
  layout: this.makeLayout(),
51
55
  config: readConfObject(this.config),
52
56
  filters: this.filters,
57
+ filterBy: this.filterBy,
53
58
  sortedBy: this.sortedBy,
54
59
  showSoftClip: this.showSoftClip,
55
60
  }
@@ -118,10 +118,14 @@ interface LayoutFeature {
118
118
  feature: Feature
119
119
  }
120
120
 
121
- function shouldDrawMismatches(type?: string) {
121
+ function shouldDrawSNPs(type?: string) {
122
122
  return !['methylation', 'modifications'].includes(type || '')
123
123
  }
124
124
 
125
+ function shouldDrawIndels(type?: string) {
126
+ return true
127
+ }
128
+
125
129
  export default class PileupRenderer extends BoxRendererType {
126
130
  supportsSVG = true
127
131
 
@@ -417,7 +421,6 @@ export default class PileupRenderer extends BoxRendererType {
417
421
 
418
422
  const cigar = feature.get('CIGAR')
419
423
  const start = feature.get('start')
420
- const end = feature.get('end')
421
424
  const seq = feature.get('seq')
422
425
  const strand = feature.get('strand')
423
426
  const cigarOps = parseCigar(cigar)
@@ -431,22 +434,20 @@ export default class PileupRenderer extends BoxRendererType {
431
434
  const col = modificationTagMap[type] || 'black'
432
435
  const base = Color(col)
433
436
  for (const readPos of getNextRefPos(cigarOps, positions)) {
434
- if (readPos >= 0 && start + readPos < end) {
435
- const [leftPx, rightPx] = bpSpanPx(
436
- start + readPos,
437
- start + readPos + 1,
438
- region,
439
- bpPerPx,
440
- )
441
-
442
- // give it a little boost of 0.1 to not make them fully
443
- // invisible to avoid confusion
444
- ctx.fillStyle = base
445
- .alpha(probabilities[probIndex] + 0.1)
446
- .hsl()
447
- .string()
448
- ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
449
- }
437
+ const r = start + readPos
438
+ const [leftPx, rightPx] = bpSpanPx(r, r + 1, region, bpPerPx)
439
+
440
+ // give it a little boost of 0.1 to not make them fully
441
+ // invisible to avoid confusion
442
+ const prob = probabilities[probIndex]
443
+ ctx.fillStyle =
444
+ prob && prob !== 1
445
+ ? base
446
+ .alpha(prob + 0.1)
447
+ .hsl()
448
+ .string()
449
+ : col
450
+ ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
450
451
  probIndex++
451
452
  }
452
453
  }
@@ -478,15 +479,14 @@ export default class PileupRenderer extends BoxRendererType {
478
479
  const seq = feature.get('seq')
479
480
  const strand = feature.get('strand')
480
481
  const cigarOps = parseCigar(cigar)
481
- const { start: rstart, end: rend } = region
482
482
 
483
- const methBins = new Array(rend - rstart).fill(0)
483
+ const methBins = new Array(region.end - region.start).fill(0)
484
484
  const modifications = getModificationPositions(mm, seq, strand)
485
485
  for (let i = 0; i < modifications.length; i++) {
486
486
  const { type, positions } = modifications[i]
487
487
  if (type === 'm' && positions) {
488
488
  for (const pos of getNextRefPos(cigarOps, positions)) {
489
- const epos = pos + fstart - rstart
489
+ const epos = pos + fstart - region.start
490
490
  if (epos >= 0 && epos < methBins.length) {
491
491
  methBins[epos] = 1
492
492
  }
@@ -495,7 +495,7 @@ export default class PileupRenderer extends BoxRendererType {
495
495
  }
496
496
 
497
497
  for (let j = fstart; j < fend; j++) {
498
- const i = j - rstart
498
+ const i = j - region.start
499
499
  if (i >= 0 && i < methBins.length) {
500
500
  const l1 = regionSequence[i].toLowerCase()
501
501
  const l2 = regionSequence[i + 1].toLowerCase()
@@ -503,13 +503,9 @@ export default class PileupRenderer extends BoxRendererType {
503
503
  // if we are zoomed out, display just a block over the cpg
504
504
  if (bpPerPx > 2) {
505
505
  if (l1 === 'c' && l2 === 'g') {
506
- const s = rstart + i
506
+ const s = region.start + i
507
507
  const [leftPx, rightPx] = bpSpanPx(s, s + 2, region, bpPerPx)
508
- if (methBins[i] || methBins[i + 1]) {
509
- ctx.fillStyle = 'red'
510
- } else {
511
- ctx.fillStyle = 'blue'
512
- }
508
+ ctx.fillStyle = methBins[i] || methBins[i + 1] ? 'red' : 'blue'
513
509
  ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
514
510
  }
515
511
  }
@@ -517,21 +513,13 @@ export default class PileupRenderer extends BoxRendererType {
517
513
  else {
518
514
  // color
519
515
  if (l1 === 'c' && l2 === 'g') {
520
- const s = rstart + i
516
+ const s = region.start + i
521
517
  const [leftPx, rightPx] = bpSpanPx(s, s + 1, region, bpPerPx)
522
- if (methBins[i]) {
523
- ctx.fillStyle = 'red'
524
- } else {
525
- ctx.fillStyle = 'blue'
526
- }
518
+ ctx.fillStyle = methBins[i] ? 'red' : 'blue'
527
519
  ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
528
520
 
529
521
  const [leftPx2, rightPx2] = bpSpanPx(s + 1, s + 2, region, bpPerPx)
530
- if (methBins[i + 1]) {
531
- ctx.fillStyle = 'red'
532
- } else {
533
- ctx.fillStyle = 'blue'
534
- }
522
+ ctx.fillStyle = methBins[i + 1] ? 'red' : 'blue'
535
523
  ctx.fillRect(leftPx2, topPx, rightPx2 - leftPx2 + 0.5, heightPx)
536
524
  }
537
525
  }
@@ -754,17 +742,6 @@ export default class PileupRenderer extends BoxRendererType {
754
742
  const mismatches: Mismatch[] = feature.get('mismatches')
755
743
  const heightLim = charHeight - 2
756
744
 
757
- function getAlphaColor(baseColor: string, mismatch: { qual?: number }) {
758
- let color = baseColor
759
- if (mismatchAlpha && mismatch.qual !== undefined) {
760
- color = Color(baseColor)
761
- .alpha(Math.min(1, mismatch.qual / 50))
762
- .hsl()
763
- .string()
764
- }
765
- return color
766
- }
767
-
768
745
  // extraHorizontallyFlippedOffset is used to draw interbase items, which
769
746
  // are located to the left when forward and right when reversed
770
747
  const extraHorizontallyFlippedOffset = region.reversed
@@ -783,14 +760,28 @@ export default class PileupRenderer extends BoxRendererType {
783
760
  if (mismatch.type === 'mismatch' && drawSNPs) {
784
761
  const baseColor = colorForBase[mismatch.base] || '#888'
785
762
 
786
- ctx.fillStyle = getAlphaColor(baseColor, mismatch)
763
+ ctx.fillStyle = !mismatchAlpha
764
+ ? baseColor
765
+ : mismatch.qual !== undefined
766
+ ? Color(baseColor)
767
+ .alpha(Math.min(1, mismatch.qual / 50))
768
+ .hsl()
769
+ .string()
770
+ : baseColor
787
771
 
788
772
  ctx.fillRect(leftPx, topPx, widthPx, heightPx)
789
773
 
790
774
  if (widthPx >= charWidth && heightPx >= heightLim) {
791
775
  // normal SNP coloring
792
- const contrast = contrastForBase[mismatch.base] || 'black'
793
- ctx.fillStyle = getAlphaColor(contrast, mismatch)
776
+ const contrastColor = contrastForBase[mismatch.base] || 'black'
777
+ ctx.fillStyle = !mismatchAlpha
778
+ ? contrastColor
779
+ : mismatch.qual !== undefined
780
+ ? Color(contrastColor)
781
+ .alpha(Math.min(1, mismatch.qual / 50))
782
+ .hsl()
783
+ .string()
784
+ : contrastColor
794
785
  ctx.fillText(
795
786
  mbase,
796
787
  leftPx + (widthPx - charWidth) / 2 + 1,
@@ -869,17 +860,17 @@ export default class PileupRenderer extends BoxRendererType {
869
860
  ctx.fillStyle = 'purple'
870
861
  ctx.fillRect(leftPx - 1, topPx, 2, heightPx)
871
862
  } else if (heightPx > charHeight) {
872
- const rect = ctx.measureText(txt)
863
+ const rwidth = measureText(txt)
873
864
  const padding = 5
874
865
  ctx.fillStyle = 'purple'
875
866
  ctx.fillRect(
876
- leftPx - rect.width / 2 - padding,
867
+ leftPx - rwidth / 2 - padding,
877
868
  topPx,
878
- rect.width + 2 * padding,
869
+ rwidth + 2 * padding,
879
870
  heightPx,
880
871
  )
881
872
  ctx.fillStyle = 'white'
882
- ctx.fillText(txt, leftPx - rect.width / 2, topPx + heightPx)
873
+ ctx.fillText(txt, leftPx - rwidth / 2, topPx + heightPx)
883
874
  } else {
884
875
  const padding = 2
885
876
  ctx.fillStyle = 'purple'
@@ -961,7 +952,7 @@ export default class PileupRenderer extends BoxRendererType {
961
952
  }
962
953
  }
963
954
 
964
- async makeImageData(
955
+ makeImageData(
965
956
  ctx: CanvasRenderingContext2D,
966
957
  layoutRecords: (LayoutFeature | null)[],
967
958
  props: RenderArgsDeserializedWithFeaturesAndLayout,
@@ -969,7 +960,10 @@ export default class PileupRenderer extends BoxRendererType {
969
960
  const { layout, config, showSoftClip, colorBy, theme: configTheme } = props
970
961
  const mismatchAlpha = readConfObject(config, 'mismatchAlpha')
971
962
  const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth')
972
- const insertScale = readConfObject(config, 'largeInsertionIndicatorScale')
963
+ const largeInsertionIndicatorScale = readConfObject(
964
+ config,
965
+ 'largeInsertionIndicatorScale',
966
+ )
973
967
  const defaultColor = readConfObject(config, 'color') === '#f0f'
974
968
 
975
969
  const theme = createJBrowseTheme(configTheme)
@@ -984,9 +978,12 @@ export default class PileupRenderer extends BoxRendererType {
984
978
  ctx.font = 'bold 10px Courier New,monospace'
985
979
 
986
980
  const { charWidth, charHeight } = this.getCharWidthHeight(ctx)
987
- layoutRecords.forEach(feat => {
981
+ const drawSNPs = shouldDrawSNPs(colorBy?.type)
982
+ const drawIndels = shouldDrawIndels(colorBy?.type)
983
+ for (let i = 0; i < layoutRecords.length; i++) {
984
+ const feat = layoutRecords[i]
988
985
  if (feat === null) {
989
- return
986
+ continue
990
987
  }
991
988
 
992
989
  this.drawAlignmentRect(ctx, feat, {
@@ -999,19 +996,25 @@ export default class PileupRenderer extends BoxRendererType {
999
996
  })
1000
997
  this.drawMismatches(ctx, feat, props, {
1001
998
  mismatchAlpha,
1002
- drawSNPs: shouldDrawMismatches(colorBy?.type),
1003
- drawIndels: shouldDrawMismatches(colorBy?.type),
1004
- largeInsertionIndicatorScale: insertScale,
999
+ drawSNPs,
1000
+ drawIndels,
1001
+ largeInsertionIndicatorScale,
1005
1002
  minSubfeatureWidth,
1006
1003
  charWidth,
1007
1004
  charHeight,
1008
1005
  colorForBase,
1009
1006
  contrastForBase,
1010
1007
  })
1011
- if (showSoftClip) {
1008
+ }
1009
+ if (showSoftClip) {
1010
+ for (let i = 0; i < layoutRecords.length; i++) {
1011
+ const feat = layoutRecords[i]
1012
+ if (feat === null) {
1013
+ continue
1014
+ }
1012
1015
  this.drawSoftClipping(ctx, feat, props, config, theme)
1013
1016
  }
1014
- })
1017
+ }
1015
1018
  }
1016
1019
 
1017
1020
  // we perform a full layout before render as a separate method because the
@@ -1041,7 +1044,7 @@ export default class PileupRenderer extends BoxRendererType {
1041
1044
 
1042
1045
  const heightPx = readConfObject(config, 'height')
1043
1046
  const displayMode = readConfObject(config, 'displayMode')
1044
- const layoutRecords = iterMap(
1047
+ return iterMap(
1045
1048
  featureMap.values(),
1046
1049
  feature =>
1047
1050
  this.layoutFeature({
@@ -1055,7 +1058,6 @@ export default class PileupRenderer extends BoxRendererType {
1055
1058
  }),
1056
1059
  featureMap.size,
1057
1060
  )
1058
- return layoutRecords
1059
1061
  }
1060
1062
 
1061
1063
  async fetchSequence(renderProps: RenderArgsDeserialized) {
@@ -31,8 +31,8 @@ export default ConfigurationSchema(
31
31
  minSubfeatureWidth: {
32
32
  type: 'number',
33
33
  description:
34
- 'the minimum width in px for a pileup mismatch feature. use for increasing mismatch marker widths when zoomed out to e.g. 1px or 0.5px',
35
- defaultValue: 0,
34
+ 'the minimum width in px for a pileup mismatch feature. use for increasing/decreasing mismatch marker widths when zoomed out, e.g. 0 or 1',
35
+ defaultValue: 0.7,
36
36
  },
37
37
  maxHeight: {
38
38
  type: 'integer',