@jbrowse/plugin-alignments 1.6.5 → 1.6.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 (52) hide show
  1. package/dist/AlignmentsFeatureDetail/index.d.ts +1 -1
  2. package/dist/BamAdapter/BamSlightlyLazyFeature.d.ts +2 -10
  3. package/dist/BamAdapter/MismatchParser.d.ts +3 -5
  4. package/dist/BamAdapter/configSchema.d.ts +1 -1
  5. package/dist/CramAdapter/CramSlightlyLazyFeature.d.ts +1 -2
  6. package/dist/CramAdapter/configSchema.d.ts +1 -1
  7. package/dist/HtsgetBamAdapter/configSchema.d.ts +1 -1
  8. package/dist/LinearAlignmentsDisplay/models/configSchema.d.ts +1 -1
  9. package/dist/LinearAlignmentsDisplay/models/model.d.ts +1 -1
  10. package/dist/LinearPileupDisplay/configSchema.d.ts +1 -1
  11. package/dist/LinearSNPCoverageDisplay/components/Tooltip.d.ts +1 -1
  12. package/dist/LinearSNPCoverageDisplay/models/configSchema.d.ts +1 -1
  13. package/dist/LinearSNPCoverageDisplay/models/model.d.ts +2 -2
  14. package/dist/PileupRenderer/PileupRenderer.d.ts +20 -6
  15. package/dist/PileupRenderer/configSchema.d.ts +1 -1
  16. package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +3 -11
  17. package/dist/SNPCoverageAdapter/configSchema.d.ts +1 -1
  18. package/dist/SNPCoverageRenderer/SNPCoverageRenderer.d.ts +1 -1
  19. package/dist/SNPCoverageRenderer/configSchema.d.ts +1 -1
  20. package/dist/SNPCoverageRenderer/index.d.ts +1 -1
  21. package/dist/plugin-alignments.cjs.development.js +795 -682
  22. package/dist/plugin-alignments.cjs.development.js.map +1 -1
  23. package/dist/plugin-alignments.cjs.production.min.js +1 -1
  24. package/dist/plugin-alignments.cjs.production.min.js.map +1 -1
  25. package/dist/plugin-alignments.esm.js +797 -684
  26. package/dist/plugin-alignments.esm.js.map +1 -1
  27. package/dist/util.d.ts +4 -0
  28. package/package.json +4 -4
  29. package/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +23 -14
  30. package/src/BamAdapter/BamAdapter.ts +10 -7
  31. package/src/BamAdapter/BamSlightlyLazyFeature.ts +11 -75
  32. package/src/BamAdapter/MismatchParser.test.ts +53 -297
  33. package/src/BamAdapter/MismatchParser.ts +54 -116
  34. package/src/BamAdapter/configSchema.ts +0 -4
  35. package/src/CramAdapter/CramSlightlyLazyFeature.ts +3 -10
  36. package/src/LinearAlignmentsDisplay/components/AlignmentsDisplay.tsx +38 -30
  37. package/src/LinearAlignmentsDisplay/models/model.tsx +10 -9
  38. package/src/LinearPileupDisplay/components/ColorByModifications.tsx +76 -80
  39. package/src/LinearPileupDisplay/components/ColorByTag.tsx +24 -23
  40. package/src/LinearPileupDisplay/components/FilterByTag.tsx +73 -68
  41. package/src/LinearPileupDisplay/components/SetFeatureHeight.tsx +28 -26
  42. package/src/LinearPileupDisplay/components/SetMaxHeight.tsx +24 -13
  43. package/src/LinearPileupDisplay/components/SortByTag.tsx +29 -21
  44. package/src/LinearPileupDisplay/model.ts +12 -6
  45. package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +5 -3
  46. package/src/LinearSNPCoverageDisplay/models/configSchema.ts +4 -5
  47. package/src/PileupRenderer/PileupRenderer.tsx +202 -70
  48. package/src/PileupRenderer/components/PileupRendering.tsx +5 -3
  49. package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +192 -237
  50. package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +91 -60
  51. package/src/SNPCoverageRenderer/configSchema.js +1 -1
  52. package/src/util.ts +25 -0
@@ -1,5 +1,4 @@
1
- import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema'
2
- import { toArray } from 'rxjs/operators'
1
+ import Color from 'color'
3
2
  import BoxRendererType, {
4
3
  RenderArgs,
5
4
  RenderArgsSerialized,
@@ -10,14 +9,20 @@ import BoxRendererType, {
10
9
  } from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
11
10
  import { Theme } from '@material-ui/core'
12
11
  import { createJBrowseTheme } from '@jbrowse/core/ui'
13
- import { Feature } from '@jbrowse/core/util/simpleFeature'
14
- import { bpSpanPx, iterMap } from '@jbrowse/core/util'
15
- import Color from 'color'
16
- import { Region } from '@jbrowse/core/util/types'
12
+ import {
13
+ bpSpanPx,
14
+ iterMap,
15
+ measureText,
16
+ Region,
17
+ Feature,
18
+ } from '@jbrowse/core/util'
17
19
  import { renderToAbstractCanvas } from '@jbrowse/core/util/offscreenCanvasUtils'
18
20
  import { BaseLayout } from '@jbrowse/core/util/layouts/BaseLayout'
19
21
  import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache'
20
- import { readConfObject } from '@jbrowse/core/configuration'
22
+ import {
23
+ readConfObject,
24
+ AnyConfigurationModel,
25
+ } from '@jbrowse/core/configuration'
21
26
 
22
27
  // locals
23
28
  import {
@@ -27,7 +32,12 @@ import {
27
32
  getNextRefPos,
28
33
  } from '../BamAdapter/MismatchParser'
29
34
  import { sortFeature } from './sortUtil'
30
- import { getTagAlt, orientationTypes } from '../util'
35
+ import {
36
+ getTagAlt,
37
+ orientationTypes,
38
+ fetchSequence,
39
+ shouldFetchReferenceSequence,
40
+ } from '../util'
31
41
  import {
32
42
  PileupLayoutSession,
33
43
  PileupLayoutSessionProps,
@@ -44,6 +54,15 @@ function getColorBaseMap(theme: Theme) {
44
54
  }
45
55
  }
46
56
 
57
+ function getContrastBaseMap(theme: Theme) {
58
+ return Object.fromEntries(
59
+ Object.entries(getColorBaseMap(theme)).map(([key, value]) => [
60
+ key,
61
+ theme.palette.getContrastText(value),
62
+ ]),
63
+ )
64
+ }
65
+
47
66
  export interface RenderArgsDeserialized extends BoxRenderArgsDeserialized {
48
67
  colorBy?: { type: string; tag?: string }
49
68
  colorTagMap?: Record<string, string>
@@ -263,6 +282,64 @@ export default class PileupRenderer extends BoxRendererType {
263
282
  return strand === 1 ? 'color_fwd_strand' : 'color_rev_strand'
264
283
  }
265
284
 
285
+ colorByPerBaseLettering(
286
+ ctx: CanvasRenderingContext2D,
287
+ feat: LayoutFeature,
288
+ _config: AnyConfigurationModel,
289
+ region: Region,
290
+ bpPerPx: number,
291
+ props: {
292
+ colorForBase: Record<string, string>
293
+ contrastForBase: Record<string, string>
294
+ charWidth: number
295
+ charHeight: number
296
+ },
297
+ ) {
298
+ const { colorForBase, contrastForBase, charWidth, charHeight } = props
299
+ const heightLim = charHeight - 2
300
+ const { feature, topPx, heightPx } = feat
301
+ const seq = feature.get('seq') as string
302
+ const cigarOps = parseCigar(feature.get('CIGAR'))
303
+ const widthPx = 1 / bpPerPx
304
+ const start = feature.get('start')
305
+ let soffset = 0 // sequence offset
306
+ let roffset = 0 // reference offset
307
+
308
+ for (let i = 0; i < cigarOps.length; i += 2) {
309
+ const len = +cigarOps[i]
310
+ const op = cigarOps[i + 1]
311
+ if (op === 'S' || op === 'I') {
312
+ soffset += len
313
+ } else if (op === 'D' || op === 'N') {
314
+ roffset += len
315
+ } else if (op === 'M' || op === 'X' || op === '=') {
316
+ for (let m = 0; m < len; m++) {
317
+ const letter = seq[soffset + m]
318
+ ctx.fillStyle = colorForBase[letter]
319
+ const [leftPx] = bpSpanPx(
320
+ start + roffset + m,
321
+ start + roffset + m + 1,
322
+ region,
323
+ bpPerPx,
324
+ )
325
+ ctx.fillRect(leftPx, topPx, widthPx + 0.5, heightPx)
326
+
327
+ if (widthPx >= charWidth && heightPx >= heightLim) {
328
+ // normal SNP coloring
329
+ ctx.fillStyle = contrastForBase[letter]
330
+
331
+ ctx.fillText(
332
+ letter,
333
+ leftPx + (widthPx - charWidth) / 2 + 1,
334
+ topPx + heightPx,
335
+ )
336
+ }
337
+ }
338
+ soffset += len
339
+ roffset += len
340
+ }
341
+ }
342
+ }
266
343
  colorByPerBaseQuality(
267
344
  ctx: CanvasRenderingContext2D,
268
345
  feat: LayoutFeature,
@@ -276,27 +353,30 @@ export default class PileupRenderer extends BoxRendererType {
276
353
  const cigarOps = parseCigar(feature.get('CIGAR'))
277
354
  const width = 1 / bpPerPx
278
355
  const start = feature.get('start')
356
+ let soffset = 0 // sequence offset
357
+ let roffset = 0 // reference offset
279
358
 
280
- for (let i = 0, j = 0, k = 0; k < scores.length; i += 2, k++) {
359
+ for (let i = 0; i < cigarOps.length; i += 2) {
281
360
  const len = +cigarOps[i]
282
361
  const op = cigarOps[i + 1]
283
362
  if (op === 'S' || op === 'I') {
284
- k += len
363
+ soffset += len
285
364
  } else if (op === 'D' || op === 'N') {
286
- j += len
365
+ roffset += len
287
366
  } else if (op === 'M' || op === 'X' || op === '=') {
288
367
  for (let m = 0; m < len; m++) {
289
- const score = scores[k + m]
368
+ const score = scores[soffset + m]
290
369
  ctx.fillStyle = `hsl(${score === 255 ? 150 : score * 1.5},55%,50%)`
291
370
  const [leftPx] = bpSpanPx(
292
- start + j + m,
293
- start + j + m + 1,
371
+ start + roffset + m,
372
+ start + roffset + m + 1,
294
373
  region,
295
374
  bpPerPx,
296
375
  )
297
376
  ctx.fillRect(leftPx, topPx, width + 0.5, heightPx)
298
377
  }
299
- j += len
378
+ soffset += len
379
+ roffset += len
300
380
  }
301
381
  }
302
382
  }
@@ -345,7 +425,8 @@ export default class PileupRenderer extends BoxRendererType {
345
425
 
346
426
  // probIndex applies across multiple modifications e.g.
347
427
  let probIndex = 0
348
- modifications.forEach(({ type, positions }) => {
428
+ for (let i = 0; i < modifications.length; i++) {
429
+ const { type, positions } = modifications[i]
349
430
  const col = modificationTagMap[type] || 'black'
350
431
  const base = Color(col)
351
432
  for (const readPos of getNextRefPos(cigarOps, positions)) {
@@ -367,12 +448,12 @@ export default class PileupRenderer extends BoxRendererType {
367
448
  }
368
449
  probIndex++
369
450
  }
370
- })
451
+ }
371
452
  }
372
453
 
373
454
  // Color by methylation is slightly modified version of color by
374
- // modifications
375
- //
455
+ // modifications that focuses on CpG sites, with non-methylated CpG colored
456
+ // blue
376
457
  colorByMethylation(
377
458
  ctx: CanvasRenderingContext2D,
378
459
  layoutFeature: LayoutFeature,
@@ -399,7 +480,9 @@ export default class PileupRenderer extends BoxRendererType {
399
480
  const { start: rstart, end: rend } = region
400
481
 
401
482
  const methBins = new Array(rend - rstart).fill(0)
402
- getModificationPositions(mm, seq, strand).forEach(({ type, positions }) => {
483
+ const modifications = getModificationPositions(mm, seq, strand)
484
+ for (let i = 0; i < modifications.length; i++) {
485
+ const { type, positions } = modifications[i]
403
486
  if (type === 'm' && positions) {
404
487
  for (const pos of getNextRefPos(cigarOps, positions)) {
405
488
  const epos = pos + fstart - rstart
@@ -408,7 +491,7 @@ export default class PileupRenderer extends BoxRendererType {
408
491
  }
409
492
  }
410
493
  }
411
- })
494
+ }
412
495
 
413
496
  for (let j = fstart; j < fend; j++) {
414
497
  const i = j - rstart
@@ -500,6 +583,10 @@ export default class PileupRenderer extends BoxRendererType {
500
583
  ctx: CanvasRenderingContext2D,
501
584
  feat: LayoutFeature,
502
585
  props: RenderArgsDeserializedWithFeaturesAndLayout & {
586
+ colorForBase: Record<string, string>
587
+ contrastForBase: Record<string, string>
588
+ charWidth: number
589
+ charHeight: number
503
590
  defaultColor: boolean
504
591
  },
505
592
  ) {
@@ -510,6 +597,10 @@ export default class PileupRenderer extends BoxRendererType {
510
597
  regions,
511
598
  colorBy,
512
599
  colorTagMap = {},
600
+ colorForBase,
601
+ contrastForBase,
602
+ charWidth,
603
+ charHeight,
513
604
  } = props
514
605
  const { tag = '', type: colorType = '' } = colorBy || {}
515
606
  const { feature } = feat
@@ -574,6 +665,19 @@ export default class PileupRenderer extends BoxRendererType {
574
665
  case 'insertSizeAndPairOrientation':
575
666
  break
576
667
 
668
+ case 'modifications':
669
+ case 'methylation':
670
+ // this coloring is similar to igv.js, and is helpful to color negative
671
+ // strand reads differently because their c-g will be flipped (e.g. g-c
672
+ // read right to left)
673
+ const flags = feature.get('flags')
674
+ if (flags & 16) {
675
+ ctx.fillStyle = '#c8dcc8'
676
+ } else {
677
+ ctx.fillStyle = '#c8c8c8'
678
+ }
679
+ break
680
+
577
681
  case 'normal':
578
682
  default:
579
683
  if (defaultColor) {
@@ -594,6 +698,15 @@ export default class PileupRenderer extends BoxRendererType {
594
698
  this.colorByPerBaseQuality(ctx, feat, config, region, bpPerPx)
595
699
  break
596
700
 
701
+ case 'perBaseLettering':
702
+ this.colorByPerBaseLettering(ctx, feat, config, region, bpPerPx, {
703
+ colorForBase,
704
+ contrastForBase,
705
+ charWidth,
706
+ charHeight,
707
+ })
708
+ break
709
+
597
710
  case 'modifications':
598
711
  this.colorByModifications(ctx, feat, config, region, bpPerPx, props)
599
712
  break
@@ -608,9 +721,9 @@ export default class PileupRenderer extends BoxRendererType {
608
721
  ctx: CanvasRenderingContext2D,
609
722
  feat: LayoutFeature,
610
723
  props: RenderArgsDeserializedWithFeaturesAndLayout,
611
- theme: Theme,
612
- colorForBase: { [key: string]: string },
613
724
  opts: {
725
+ colorForBase: { [key: string]: string }
726
+ contrastForBase: { [key: string]: string }
614
727
  mismatchAlpha?: boolean
615
728
  drawSNPs?: boolean
616
729
  drawIndels?: boolean
@@ -621,13 +734,15 @@ export default class PileupRenderer extends BoxRendererType {
621
734
  },
622
735
  ) {
623
736
  const {
624
- minSubfeatureWidth: minWidth,
737
+ minSubfeatureWidth,
625
738
  largeInsertionIndicatorScale,
626
739
  mismatchAlpha,
627
740
  drawSNPs = true,
628
741
  drawIndels = true,
629
742
  charWidth,
630
743
  charHeight,
744
+ colorForBase,
745
+ contrastForBase,
631
746
  } = opts
632
747
  const { bpPerPx, regions } = props
633
748
  const { heightPx, topPx, feature } = feat
@@ -635,7 +750,7 @@ export default class PileupRenderer extends BoxRendererType {
635
750
  const start = feature.get('start')
636
751
 
637
752
  const pxPerBp = Math.min(1 / bpPerPx, 2)
638
- const w = Math.max(minWidth, pxPerBp)
753
+ const w = Math.max(minSubfeatureWidth, pxPerBp)
639
754
  const mismatches: Mismatch[] = feature.get('mismatches')
640
755
  const heightLim = charHeight - 2
641
756
 
@@ -650,6 +765,12 @@ export default class PileupRenderer extends BoxRendererType {
650
765
  return color
651
766
  }
652
767
 
768
+ // extraHorizontallyFlippedOffset is used to draw interbase items, which
769
+ // are located to the left when forward and right when reversed
770
+ const extraHorizontallyFlippedOffset = region.reversed
771
+ ? 1 / bpPerPx + 1
772
+ : -1
773
+
653
774
  // two pass rendering: first pass, draw all the mismatches except wide
654
775
  // insertion markers
655
776
  for (let i = 0; i < mismatches.length; i += 1) {
@@ -658,7 +779,7 @@ export default class PileupRenderer extends BoxRendererType {
658
779
  const mlen = mismatch.length
659
780
  const mbase = mismatch.base
660
781
  const [leftPx, rightPx] = bpSpanPx(mstart, mstart + mlen, region, bpPerPx)
661
- const widthPx = Math.max(minWidth, Math.abs(leftPx - rightPx))
782
+ const widthPx = Math.max(minSubfeatureWidth, Math.abs(leftPx - rightPx))
662
783
  if (mismatch.type === 'mismatch' && drawSNPs) {
663
784
  const baseColor = colorForBase[mismatch.base] || '#888'
664
785
 
@@ -669,7 +790,7 @@ export default class PileupRenderer extends BoxRendererType {
669
790
  if (widthPx >= charWidth && heightPx >= heightLim) {
670
791
  // normal SNP coloring
671
792
  ctx.fillStyle = getAlphaColor(
672
- theme.palette.getContrastText(baseColor),
793
+ contrastForBase[mismatch.base],
673
794
  mismatch,
674
795
  )
675
796
  ctx.fillText(
@@ -683,37 +804,36 @@ export default class PileupRenderer extends BoxRendererType {
683
804
  ctx.fillStyle = baseColor
684
805
  ctx.fillRect(leftPx, topPx, widthPx, heightPx)
685
806
  const txt = `${mismatch.length}`
686
- const rect = ctx.measureText(txt)
687
- if (widthPx >= rect.width && heightPx >= heightLim) {
688
- ctx.fillStyle = theme.palette.getContrastText(baseColor)
807
+ const rwidth = measureText(txt, 10)
808
+ if (widthPx >= rwidth && heightPx >= heightLim) {
809
+ ctx.fillStyle = contrastForBase.deletion
689
810
  ctx.fillText(
690
811
  txt,
691
- leftPx + (rightPx - leftPx) / 2 - rect.width / 2,
812
+ (leftPx + rightPx) / 2 - rwidth / 2,
692
813
  topPx + heightPx,
693
814
  )
694
815
  }
695
816
  } else if (mismatch.type === 'insertion' && drawIndels) {
696
817
  ctx.fillStyle = 'purple'
697
- const pos = leftPx - 1
818
+ const pos = leftPx + extraHorizontallyFlippedOffset
698
819
  const len = +mismatch.base || mismatch.length
820
+ const insW = Math.max(minSubfeatureWidth, Math.min(1.2, 1 / bpPerPx))
699
821
  if (len < 10) {
700
- ctx.fillRect(pos, topPx, w, heightPx)
701
- if (1 / bpPerPx >= charWidth) {
702
- ctx.fillRect(pos - w, topPx, w * 3, 1)
703
- ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
704
- }
822
+ ctx.fillRect(pos, topPx, insW, heightPx)
705
823
  if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
706
- ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
824
+ ctx.fillRect(pos - insW, topPx, insW * 3, 1)
825
+ ctx.fillRect(pos - insW, topPx + heightPx - 1, insW * 3, 1)
826
+ ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
707
827
  }
708
828
  }
709
829
  } else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {
710
830
  ctx.fillStyle = mismatch.type === 'hardclip' ? 'red' : 'blue'
711
- const pos = leftPx - 1
712
- ctx.fillRect(pos, topPx + 1, w, heightPx - 2)
713
- ctx.fillRect(pos - w, topPx, w * 3, 1)
714
- ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
715
- if (widthPx >= charWidth && heightPx >= heightLim) {
716
- ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
831
+ const pos = leftPx + extraHorizontallyFlippedOffset
832
+ ctx.fillRect(pos, topPx, w, heightPx)
833
+ if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
834
+ ctx.fillRect(pos - w, topPx, w * 3, 1)
835
+ ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
836
+ ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
717
837
  }
718
838
  } else if (mismatch.type === 'skip') {
719
839
  // fix to avoid bad rendering note that this was also related to chrome
@@ -797,10 +917,9 @@ export default class PileupRenderer extends BoxRendererType {
797
917
  .filter(mismatch => mismatch.type === 'softclip')
798
918
  .forEach(mismatch => {
799
919
  const softClipLength = mismatch.cliplen || 0
920
+ const s = feature.get('start')
800
921
  const softClipStart =
801
- mismatch.start === 0
802
- ? feature.get('start') - softClipLength
803
- : feature.get('start') + mismatch.start
922
+ mismatch.start === 0 ? s - softClipLength : s + mismatch.start
804
923
 
805
924
  for (let k = 0; k < softClipLength; k += 1) {
806
925
  const base = seq.charAt(k + mismatch.start)
@@ -854,6 +973,7 @@ export default class PileupRenderer extends BoxRendererType {
854
973
 
855
974
  const theme = createJBrowseTheme(configTheme)
856
975
  const colorForBase = getColorBaseMap(theme)
976
+ const contrastForBase = getContrastBaseMap(theme)
857
977
  if (!layout) {
858
978
  throw new Error(`layout required`)
859
979
  }
@@ -871,8 +991,12 @@ export default class PileupRenderer extends BoxRendererType {
871
991
  this.drawAlignmentRect(ctx, feat, {
872
992
  ...props,
873
993
  defaultColor,
994
+ colorForBase,
995
+ contrastForBase,
996
+ charWidth,
997
+ charHeight,
874
998
  })
875
- this.drawMismatches(ctx, feat, props, theme, colorForBase, {
999
+ this.drawMismatches(ctx, feat, props, {
876
1000
  mismatchAlpha,
877
1001
  drawSNPs: shouldDrawMismatches(colorBy?.type),
878
1002
  drawIndels: shouldDrawMismatches(colorBy?.type),
@@ -880,6 +1004,8 @@ export default class PileupRenderer extends BoxRendererType {
880
1004
  minSubfeatureWidth,
881
1005
  charWidth,
882
1006
  charHeight,
1007
+ colorForBase,
1008
+ contrastForBase,
883
1009
  })
884
1010
  if (showSoftClip) {
885
1011
  this.drawSoftClipping(ctx, feat, props, config, theme)
@@ -931,34 +1057,40 @@ export default class PileupRenderer extends BoxRendererType {
931
1057
  return layoutRecords
932
1058
  }
933
1059
 
934
- async render(renderProps: RenderArgsDeserialized) {
935
- const { sessionId, bpPerPx, regions, adapterConfig } = renderProps
1060
+ async fetchSequence(renderProps: RenderArgsDeserialized) {
1061
+ const { sessionId, regions, adapterConfig } = renderProps
936
1062
  const { sequenceAdapter } = adapterConfig
1063
+ if (!sequenceAdapter) {
1064
+ return undefined
1065
+ }
1066
+ const { dataAdapter } = await getAdapter(
1067
+ this.pluginManager,
1068
+ sessionId,
1069
+ sequenceAdapter,
1070
+ )
1071
+ const [region] = regions
1072
+ return fetchSequence(region, dataAdapter as BaseFeatureDataAdapter)
1073
+ }
1074
+
1075
+ async render(renderProps: RenderArgsDeserialized) {
937
1076
  const features = await this.getFeatures(renderProps)
938
1077
  const layout = this.createLayoutInWorker(renderProps)
1078
+ const { regions, bpPerPx } = renderProps
939
1079
 
940
- const layoutRecords = this.layoutFeats({ ...renderProps, features, layout })
1080
+ const layoutRecords = this.layoutFeats({
1081
+ ...renderProps,
1082
+ features,
1083
+ layout,
1084
+ })
941
1085
  const [region] = regions
942
- let regionSequence: string | undefined
943
- const { end, start, originalRefName, refName } = region
944
-
945
- if (sequenceAdapter) {
946
- const { dataAdapter } = await getAdapter(
947
- this.pluginManager,
948
- sessionId,
949
- sequenceAdapter,
950
- )
951
1086
 
952
- const feats = await (dataAdapter as BaseFeatureDataAdapter)
953
- .getFeatures({
954
- ...region,
955
- refName: originalRefName || refName,
956
- end: region.end + 1,
957
- })
958
- .pipe(toArray())
959
- .toPromise()
960
- regionSequence = feats[0]?.get('seq')
961
- }
1087
+ // only need reference sequence if there are features and only for some
1088
+ // cases
1089
+ const regionSequence =
1090
+ features.size && shouldFetchReferenceSequence(renderProps.colorBy?.type)
1091
+ ? await this.fetchSequence(renderProps)
1092
+ : undefined
1093
+ const { end, start } = region
962
1094
 
963
1095
  const width = (end - start) / bpPerPx
964
1096
  const height = Math.max(layout.getTotalHeight(), 1)
@@ -136,9 +136,11 @@ function PileupRendering(props: {
136
136
  }
137
137
  let offsetX = 0
138
138
  let offsetY = 0
139
- if (highlightOverlayCanvas.current) {
140
- offsetX = highlightOverlayCanvas.current.getBoundingClientRect().left
141
- offsetY = highlightOverlayCanvas.current.getBoundingClientRect().top
139
+ const canvas = highlightOverlayCanvas.current
140
+ if (canvas) {
141
+ const { left, top } = canvas.getBoundingClientRect()
142
+ offsetX = left
143
+ offsetY = top
142
144
  }
143
145
  offsetX = event.clientX - offsetX
144
146
  offsetY = event.clientY - offsetY