@jbrowse/plugin-alignments 1.5.0 → 1.5.4

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 (58) hide show
  1. package/dist/AlignmentsFeatureDetail/index.d.ts +7 -4
  2. package/dist/AlignmentsTrack/index.d.ts +2 -0
  3. package/dist/BamAdapter/BamSlightlyLazyFeature.d.ts +1 -1
  4. package/dist/BamAdapter/index.d.ts +2 -4
  5. package/dist/CramAdapter/index.d.ts +1 -4
  6. package/dist/HtsgetBamAdapter/HtsgetBamAdapter.d.ts +2 -1
  7. package/dist/HtsgetBamAdapter/index.d.ts +2 -4
  8. package/dist/LinearAlignmentsDisplay/index.d.ts +2 -3
  9. package/dist/LinearPileupDisplay/index.d.ts +2 -2
  10. package/dist/LinearPileupDisplay/model.d.ts +1 -2
  11. package/dist/LinearSNPCoverageDisplay/components/Tooltip.d.ts +1 -1
  12. package/dist/LinearSNPCoverageDisplay/index.d.ts +2 -2
  13. package/dist/LinearSNPCoverageDisplay/models/model.d.ts +1 -0
  14. package/dist/PileupRenderer/PileupRenderer.d.ts +20 -7
  15. package/dist/PileupRenderer/index.d.ts +2 -3
  16. package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +38 -26
  17. package/dist/SNPCoverageAdapter/index.d.ts +1 -5
  18. package/dist/SNPCoverageRenderer/index.d.ts +3 -3
  19. package/dist/plugin-alignments.cjs.development.js +3696 -3526
  20. package/dist/plugin-alignments.cjs.development.js.map +1 -1
  21. package/dist/plugin-alignments.cjs.production.min.js +1 -1
  22. package/dist/plugin-alignments.cjs.production.min.js.map +1 -1
  23. package/dist/plugin-alignments.esm.js +3690 -3520
  24. package/dist/plugin-alignments.esm.js.map +1 -1
  25. package/package.json +4 -4
  26. package/src/AlignmentsFeatureDetail/__snapshots__/index.test.js.snap +8 -0
  27. package/src/AlignmentsFeatureDetail/{index.js → index.ts} +19 -3
  28. package/src/AlignmentsTrack/index.ts +36 -0
  29. package/src/BamAdapter/BamSlightlyLazyFeature.ts +14 -30
  30. package/src/BamAdapter/MismatchParser.test.ts +20 -0
  31. package/src/BamAdapter/MismatchParser.ts +6 -5
  32. package/src/BamAdapter/index.ts +11 -5
  33. package/src/CramAdapter/CramSlightlyLazyFeature.ts +1 -5
  34. package/src/CramAdapter/index.ts +11 -4
  35. package/src/HtsgetBamAdapter/HtsgetBamAdapter.ts +2 -2
  36. package/src/HtsgetBamAdapter/index.ts +18 -5
  37. package/src/LinearAlignmentsDisplay/index.ts +20 -3
  38. package/src/LinearAlignmentsDisplay/models/configSchema.test.js +8 -68
  39. package/src/LinearPileupDisplay/configSchema.test.js +2 -13
  40. package/src/LinearPileupDisplay/index.ts +19 -2
  41. package/src/LinearPileupDisplay/model.ts +15 -20
  42. package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +17 -12
  43. package/src/LinearSNPCoverageDisplay/index.ts +19 -2
  44. package/src/LinearSNPCoverageDisplay/models/configSchema.test.js +2 -13
  45. package/src/LinearSNPCoverageDisplay/models/model.ts +21 -0
  46. package/src/PileupRenderer/PileupRenderer.tsx +154 -128
  47. package/src/PileupRenderer/components/PileupRendering.tsx +2 -0
  48. package/src/PileupRenderer/configSchema.ts +2 -2
  49. package/src/PileupRenderer/index.ts +16 -3
  50. package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +95 -25
  51. package/src/SNPCoverageAdapter/index.ts +17 -5
  52. package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +60 -13
  53. package/src/SNPCoverageRenderer/configSchema.js +5 -0
  54. package/src/SNPCoverageRenderer/index.ts +24 -0
  55. package/src/index.ts +91 -163
  56. package/src/SNPCoverageAdapter/SNPCoverageAdapter.test.ts +0 -275
  57. package/src/SNPCoverageAdapter/__snapshots__/SNPCoverageAdapter.test.ts.snap +0 -579
  58. package/src/SNPCoverageRenderer/index.js +0 -11
@@ -11,6 +11,15 @@ type Count = {
11
11
  }
12
12
  }
13
13
 
14
+ type SNPInfo = {
15
+ ref: Count
16
+ cov: Count
17
+ lowqual: Count
18
+ noncov: Count
19
+ delskips: Count
20
+ total: number
21
+ }
22
+
14
23
  const en = (n: number) => n.toLocaleString('en-US')
15
24
 
16
25
  const TooltipContents = React.forwardRef(
@@ -22,16 +31,8 @@ const TooltipContents = React.forwardRef(
22
31
  .filter(f => !!f)
23
32
  .join(':')
24
33
 
25
- const info = feature.get('snpinfo') as {
26
- ref: Count
27
- cov: Count
28
- lowqual: Count
29
- noncov: Count
30
- delskips: Count
31
- total: number
32
- }
33
-
34
- const total = info.total
34
+ const info = feature.get('snpinfo') as SNPInfo
35
+ const total = info?.total
35
36
 
36
37
  return (
37
38
  <div ref={ref}>
@@ -61,7 +62,7 @@ const TooltipContents = React.forwardRef(
61
62
  <td>{base.toUpperCase()}</td>
62
63
  <td>{score.total}</td>
63
64
  <td>
64
- {base === 'total'
65
+ {base === 'total' || base === 'skip'
65
66
  ? '---'
66
67
  : `${Math.floor((score.total / total) * 100)}%`}
67
68
  </td>
@@ -91,7 +92,11 @@ const SNPCoverageTooltip = observer(
91
92
  clientMouseCoord: Coord
92
93
  clientRect?: ClientRect
93
94
  }) => {
94
- return <Tooltip TooltipContents={TooltipContents} {...props} />
95
+ const { model } = props
96
+ const { featureUnderMouse: feat } = model
97
+ return feat && feat.get('type') === 'skip' ? null : (
98
+ <Tooltip TooltipContents={TooltipContents} {...props} />
99
+ )
95
100
  },
96
101
  )
97
102
 
@@ -1,2 +1,19 @@
1
- export { default as configSchemaFactory } from './models/configSchema'
2
- export { default as modelFactory } from './models/model'
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import configSchemaFactory from './models/configSchema'
3
+ import modelFactory from './models/model'
4
+ import { LinearWiggleDisplayReactComponent } from '@jbrowse/plugin-wiggle'
5
+ import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType'
6
+
7
+ export default function register(pluginManager: PluginManager) {
8
+ pluginManager.addDisplayType(() => {
9
+ const configSchema = configSchemaFactory(pluginManager)
10
+ return new DisplayType({
11
+ name: 'LinearSNPCoverageDisplay',
12
+ configSchema,
13
+ stateModel: modelFactory(pluginManager, configSchema),
14
+ trackType: 'AlignmentsTrack',
15
+ viewType: 'LinearGenomeView',
16
+ ReactComponent: LinearWiggleDisplayReactComponent,
17
+ })
18
+ })
19
+ }
@@ -1,9 +1,6 @@
1
1
  import Plugin from '@jbrowse/core/Plugin'
2
2
  import PluginManager from '@jbrowse/core/PluginManager'
3
- import SNPCoverageRenderer, {
4
- configSchema as snpCoverageRendererConfigSchema,
5
- ReactComponent as SNPCoverageRendererReactComponent,
6
- } from '../../SNPCoverageRenderer' // change renderer
3
+ import SNPCoverageRenderer from '../../SNPCoverageRenderer' // change renderer
7
4
  import configSchemaFactory from './configSchema'
8
5
 
9
6
  // mock warnings to avoid unnecessary outputs
@@ -17,15 +14,7 @@ afterEach(() => {
17
14
  // change renderer
18
15
  class SNPCoverageRendererPlugin extends Plugin {
19
16
  install(pluginManager) {
20
- pluginManager.addRendererType(
21
- () =>
22
- new SNPCoverageRenderer({
23
- name: 'SNPCoverageRenderer',
24
- ReactComponent: SNPCoverageRendererReactComponent,
25
- configSchema: snpCoverageRendererConfigSchema,
26
- pluginManager,
27
- }),
28
- )
17
+ SNPCoverageRenderer(pluginManager)
29
18
  }
30
19
  }
31
20
 
@@ -30,6 +30,7 @@ const stateModelFactory = (
30
30
  type: types.literal('LinearSNPCoverageDisplay'),
31
31
  drawInterbaseCounts: types.maybe(types.boolean),
32
32
  drawIndicators: types.maybe(types.boolean),
33
+ drawArcs: types.maybe(types.boolean),
33
34
  filterBy: types.optional(
34
35
  types.model({
35
36
  flagInclude: types.optional(types.number, 0),
@@ -98,10 +99,19 @@ const stateModelFactory = (
98
99
  self.drawIndicators === undefined
99
100
  ? configBlob.drawIndicators
100
101
  : self.drawIndicators,
102
+ drawArcs:
103
+ self.drawArcs === undefined
104
+ ? configBlob.drawArcs
105
+ : self.drawArcs,
101
106
  },
102
107
  getEnv(self),
103
108
  )
104
109
  },
110
+ get drawArcsSetting() {
111
+ return self.drawArcs !== undefined
112
+ ? self.drawArcs
113
+ : readConfObject(this.rendererConfig, 'drawArcs')
114
+ },
105
115
  get drawInterbaseCountsSetting() {
106
116
  return self.drawInterbaseCounts !== undefined
107
117
  ? self.drawInterbaseCounts
@@ -143,6 +153,9 @@ const stateModelFactory = (
143
153
  toggleDrawInterbaseCounts() {
144
154
  self.drawInterbaseCounts = !self.drawInterbaseCountsSetting
145
155
  },
156
+ toggleDrawArcs() {
157
+ self.drawArcs = !self.drawArcsSetting
158
+ },
146
159
  afterAttach() {
147
160
  addDisposer(
148
161
  self,
@@ -217,6 +230,14 @@ const stateModelFactory = (
217
230
  self.toggleDrawInterbaseCounts()
218
231
  },
219
232
  },
233
+ {
234
+ label: 'Draw arcs',
235
+ type: 'checkbox',
236
+ checked: self.drawArcsSetting,
237
+ onClick: () => {
238
+ self.toggleDrawArcs()
239
+ },
240
+ },
220
241
  ]
221
242
  },
222
243
  // The SNPCoverage filters are called twice because the BAM/CRAM
@@ -18,6 +18,8 @@ import { renderToAbstractCanvas } from '@jbrowse/core/util/offscreenCanvasUtils'
18
18
  import { BaseLayout } from '@jbrowse/core/util/layouts/BaseLayout'
19
19
  import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache'
20
20
  import { readConfObject } from '@jbrowse/core/configuration'
21
+
22
+ // locals
21
23
  import {
22
24
  Mismatch,
23
25
  parseCigar,
@@ -26,21 +28,12 @@ import {
26
28
  } from '../BamAdapter/MismatchParser'
27
29
  import { sortFeature } from './sortUtil'
28
30
  import { getTagAlt, orientationTypes } from '../util'
29
-
30
31
  import {
31
32
  PileupLayoutSession,
32
33
  PileupLayoutSessionProps,
33
34
  } from './PileupLayoutSession'
34
35
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
35
36
 
36
- export type {
37
- RenderArgs,
38
- RenderArgsSerialized,
39
- RenderResults,
40
- ResultsSerialized,
41
- ResultsDeserialized,
42
- }
43
-
44
37
  function getColorBaseMap(theme: Theme) {
45
38
  return {
46
39
  A: theme.palette.bases.A.main,
@@ -94,7 +87,7 @@ const alignmentColoring: { [key: string]: string } = {
94
87
  color_pair_rr: 'navy',
95
88
  color_pair_rl: 'teal',
96
89
  color_pair_ll: 'green',
97
- color_nostrand: '#999',
90
+ color_nostrand: '#c8c8c8',
98
91
  color_interchrom: 'orange',
99
92
  color_longinsert: 'red',
100
93
  color_shortinsert: 'pink',
@@ -121,25 +114,38 @@ export default class PileupRenderer extends BoxRendererType {
121
114
  return { charWidth, charHeight }
122
115
  }
123
116
 
124
- layoutFeature(
125
- feature: Feature,
126
- layout: BaseLayout<Feature>,
127
- config: AnyConfigurationModel,
128
- bpPerPx: number,
129
- region: Region,
130
- showSoftClip?: boolean,
131
- ): LayoutRecord | null {
117
+ layoutFeature({
118
+ feature,
119
+ layout,
120
+ bpPerPx,
121
+ region,
122
+ showSoftClip,
123
+ heightPx,
124
+ displayMode,
125
+ }: {
126
+ feature: Feature
127
+ layout: BaseLayout<Feature>
128
+ bpPerPx: number
129
+ region: Region
130
+ showSoftClip?: boolean
131
+ heightPx: number
132
+ displayMode: string
133
+ }): LayoutRecord | null {
132
134
  let expansionBefore = 0
133
135
  let expansionAfter = 0
134
- const mismatches: Mismatch[] = feature.get('mismatches')
135
- const seq: string = feature.get('seq')
136
136
 
137
137
  // Expand the start and end of feature when softclipping enabled
138
- if (showSoftClip && seq) {
139
- for (let i = 0; i < mismatches.length; i += 1) {
140
- const { type, start, cliplen = 0 } = mismatches[i]
141
- if (type === 'softclip') {
142
- start === 0 ? (expansionBefore = cliplen) : (expansionAfter = cliplen)
138
+ if (showSoftClip) {
139
+ const mismatches = feature.get('mismatches') as Mismatch[]
140
+ const seq = feature.get('seq') as string
141
+ if (seq) {
142
+ for (let i = 0; i < mismatches.length; i += 1) {
143
+ const { type, start, cliplen = 0 } = mismatches[i]
144
+ if (type === 'softclip') {
145
+ start === 0
146
+ ? (expansionBefore = cliplen)
147
+ : (expansionAfter = cliplen)
148
+ }
143
149
  }
144
150
  }
145
151
  }
@@ -151,8 +157,6 @@ export default class PileupRenderer extends BoxRendererType {
151
157
  bpPerPx,
152
158
  )
153
159
 
154
- let heightPx = readConfObject(config, 'height', { feature })
155
- const displayMode = readConfObject(config, 'displayMode', { feature })
156
160
  if (displayMode === 'compact') {
157
161
  heightPx /= 3
158
162
  }
@@ -495,9 +499,18 @@ export default class PileupRenderer extends BoxRendererType {
495
499
  drawAlignmentRect(
496
500
  ctx: CanvasRenderingContext2D,
497
501
  feat: LayoutFeature,
498
- props: RenderArgsDeserializedWithFeaturesAndLayout,
502
+ props: RenderArgsDeserializedWithFeaturesAndLayout & {
503
+ defaultColor: boolean
504
+ },
499
505
  ) {
500
- const { config, bpPerPx, regions, colorBy, colorTagMap = {} } = props
506
+ const {
507
+ defaultColor,
508
+ config,
509
+ bpPerPx,
510
+ regions,
511
+ colorBy,
512
+ colorTagMap = {},
513
+ } = props
501
514
  const { tag = '', type: colorType = '' } = colorBy || {}
502
515
  const { feature } = feat
503
516
  const region = regions[0]
@@ -554,7 +567,7 @@ export default class PileupRenderer extends BoxRendererType {
554
567
  // fetchValues
555
568
  else {
556
569
  const foundValue = colorTagMap[val]
557
- ctx.fillStyle = foundValue || 'color_nostrand'
570
+ ctx.fillStyle = foundValue || alignmentColoring['color_nostrand']
558
571
  }
559
572
  break
560
573
  }
@@ -563,7 +576,12 @@ export default class PileupRenderer extends BoxRendererType {
563
576
 
564
577
  case 'normal':
565
578
  default:
566
- ctx.fillStyle = readConfObject(config, 'color', { feature })
579
+ if (defaultColor) {
580
+ // avoid a readConfObject call here
581
+ ctx.fillStyle = '#c8c8c8'
582
+ } else {
583
+ ctx.fillStyle = readConfObject(config, 'color', { feature })
584
+ }
567
585
  break
568
586
  }
569
587
 
@@ -596,21 +614,28 @@ export default class PileupRenderer extends BoxRendererType {
596
614
  mismatchAlpha?: boolean
597
615
  drawSNPs?: boolean
598
616
  drawIndels?: boolean
617
+ minSubfeatureWidth: number
618
+ largeInsertionIndicatorScale: number
619
+ charWidth: number
620
+ charHeight: number
599
621
  },
600
622
  ) {
601
- const { mismatchAlpha, drawSNPs = true, drawIndels = true } = opts
602
- const { config, bpPerPx, regions } = props
623
+ const {
624
+ minSubfeatureWidth: minWidth,
625
+ largeInsertionIndicatorScale,
626
+ mismatchAlpha,
627
+ drawSNPs = true,
628
+ drawIndels = true,
629
+ charWidth,
630
+ charHeight,
631
+ } = opts
632
+ const { bpPerPx, regions } = props
603
633
  const { heightPx, topPx, feature } = feat
604
- const { charWidth, charHeight } = this.getCharWidthHeight(ctx)
605
634
  const [region] = regions
606
635
  const start = feature.get('start')
607
- const minFeatWidth = readConfObject(config, 'minSubfeatureWidth')
608
- const insertionScale = readConfObject(
609
- config,
610
- 'largeInsertionIndicatorScale',
611
- )
636
+
612
637
  const pxPerBp = Math.min(1 / bpPerPx, 2)
613
- const w = Math.max(minFeatWidth, pxPerBp)
638
+ const w = Math.max(minWidth, pxPerBp)
614
639
  const mismatches: Mismatch[] = feature.get('mismatches')
615
640
  const heightLim = charHeight - 2
616
641
 
@@ -629,50 +654,45 @@ export default class PileupRenderer extends BoxRendererType {
629
654
  // insertion markers
630
655
  for (let i = 0; i < mismatches.length; i += 1) {
631
656
  const mismatch = mismatches[i]
632
- const [mismatchLeftPx, mismatchRightPx] = bpSpanPx(
633
- start + mismatch.start,
634
- start + mismatch.start + mismatch.length,
635
- region,
636
- bpPerPx,
637
- )
638
- const mismatchWidthPx = Math.max(
639
- minFeatWidth,
640
- Math.abs(mismatchLeftPx - mismatchRightPx),
641
- )
657
+ const mstart = start + mismatch.start
658
+ const mlen = mismatch.length
659
+ const mbase = mismatch.base
660
+ const [leftPx, rightPx] = bpSpanPx(mstart, mstart + mlen, region, bpPerPx)
661
+ const widthPx = Math.max(minWidth, Math.abs(leftPx - rightPx))
642
662
  if (mismatch.type === 'mismatch' && drawSNPs) {
643
663
  const baseColor = colorForBase[mismatch.base] || '#888'
644
664
 
645
665
  ctx.fillStyle = getAlphaColor(baseColor, mismatch)
646
666
 
647
- ctx.fillRect(mismatchLeftPx, topPx, mismatchWidthPx, heightPx)
667
+ ctx.fillRect(leftPx, topPx, widthPx, heightPx)
648
668
 
649
- if (mismatchWidthPx >= charWidth && heightPx >= heightLim) {
669
+ if (widthPx >= charWidth && heightPx >= heightLim) {
650
670
  // normal SNP coloring
651
671
  ctx.fillStyle = getAlphaColor(
652
672
  theme.palette.getContrastText(baseColor),
653
673
  mismatch,
654
674
  )
655
675
  ctx.fillText(
656
- mismatch.base,
657
- mismatchLeftPx + (mismatchWidthPx - charWidth) / 2 + 1,
676
+ mbase,
677
+ leftPx + (widthPx - charWidth) / 2 + 1,
658
678
  topPx + heightPx,
659
679
  )
660
680
  }
661
681
  } else if (mismatch.type === 'deletion' && drawIndels) {
662
682
  const baseColor = colorForBase.deletion
663
683
  ctx.fillStyle = baseColor
664
- ctx.fillRect(mismatchLeftPx, topPx, mismatchWidthPx, heightPx)
665
- if (mismatchWidthPx >= charWidth && heightPx >= heightLim) {
684
+ ctx.fillRect(leftPx, topPx, widthPx, heightPx)
685
+ if (widthPx >= charWidth && heightPx >= heightLim) {
666
686
  ctx.fillStyle = theme.palette.getContrastText(baseColor)
667
687
  ctx.fillText(
668
- mismatch.base,
669
- mismatchLeftPx + (mismatchWidthPx - charWidth) / 2 + 1,
688
+ mbase,
689
+ leftPx + (widthPx - charWidth) / 2 + 1,
670
690
  topPx + heightPx,
671
691
  )
672
692
  }
673
693
  } else if (mismatch.type === 'insertion' && drawIndels) {
674
694
  ctx.fillStyle = 'purple'
675
- const pos = mismatchLeftPx - 1
695
+ const pos = leftPx - 1
676
696
  const len = +mismatch.base || mismatch.length
677
697
  if (len < 10) {
678
698
  ctx.fillRect(pos, topPx, w, heightPx)
@@ -681,41 +701,33 @@ export default class PileupRenderer extends BoxRendererType {
681
701
  ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
682
702
  }
683
703
  if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
684
- ctx.fillText(
685
- `(${mismatch.base})`,
686
- mismatchLeftPx + 2,
687
- topPx + heightPx,
688
- )
704
+ ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
689
705
  }
690
706
  }
691
707
  } else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {
692
708
  ctx.fillStyle = mismatch.type === 'hardclip' ? 'red' : 'blue'
693
- const pos = mismatchLeftPx - 1
709
+ const pos = leftPx - 1
694
710
  ctx.fillRect(pos, topPx + 1, w, heightPx - 2)
695
711
  ctx.fillRect(pos - w, topPx, w * 3, 1)
696
712
  ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
697
- if (mismatchWidthPx >= charWidth && heightPx >= heightLim) {
698
- ctx.fillText(
699
- `(${mismatch.base})`,
700
- mismatchLeftPx + 2,
701
- topPx + heightPx,
702
- )
713
+ if (widthPx >= charWidth && heightPx >= heightLim) {
714
+ ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
703
715
  }
704
716
  } else if (mismatch.type === 'skip') {
705
717
  // fix to avoid bad rendering
706
718
  // note that this was also related to chrome bug https://bugs.chromium.org/p/chro>
707
719
  // ref #1236
708
- if (mismatchLeftPx + mismatchWidthPx > 0) {
720
+ if (leftPx + widthPx > 0) {
721
+ // make small exons more visible when zoomed far out
709
722
  ctx.clearRect(
710
- mismatchLeftPx,
723
+ leftPx,
711
724
  topPx,
712
- // make small exons more visible when zoomed far out
713
- mismatchWidthPx - (bpPerPx > 10 ? 1.5 : 0),
725
+ widthPx - (bpPerPx > 10 ? 1.5 : 0),
714
726
  heightPx,
715
727
  )
716
728
  }
717
729
  ctx.fillStyle = '#333'
718
- ctx.fillRect(mismatchLeftPx, topPx + heightPx / 2, mismatchWidthPx, 2)
730
+ ctx.fillRect(leftPx, topPx + heightPx / 2, widthPx, 2)
719
731
  }
720
732
  }
721
733
 
@@ -723,34 +735,31 @@ export default class PileupRenderer extends BoxRendererType {
723
735
  if (drawIndels) {
724
736
  for (let i = 0; i < mismatches.length; i += 1) {
725
737
  const mismatch = mismatches[i]
726
- const [mismatchLeftPx] = bpSpanPx(
727
- feature.get('start') + mismatch.start,
728
- feature.get('start') + mismatch.start + mismatch.length,
729
- region,
730
- bpPerPx,
731
- )
738
+ const mstart = start + mismatch.start
739
+ const mlen = mismatch.length
740
+ const [leftPx] = bpSpanPx(mstart, mstart + mlen, region, bpPerPx)
732
741
  const len = +mismatch.base || mismatch.length
733
742
  const txt = `${len}`
734
743
  if (mismatch.type === 'insertion' && len >= 10) {
735
- if (bpPerPx > insertionScale) {
744
+ if (bpPerPx > largeInsertionIndicatorScale) {
736
745
  ctx.fillStyle = 'purple'
737
- ctx.fillRect(mismatchLeftPx - 1, topPx, 2, heightPx)
746
+ ctx.fillRect(leftPx - 1, topPx, 2, heightPx)
738
747
  } else if (heightPx > charHeight) {
739
748
  const rect = ctx.measureText(txt)
740
749
  const padding = 5
741
750
  ctx.fillStyle = 'purple'
742
751
  ctx.fillRect(
743
- mismatchLeftPx - rect.width / 2 - padding,
752
+ leftPx - rect.width / 2 - padding,
744
753
  topPx,
745
754
  rect.width + 2 * padding,
746
755
  heightPx,
747
756
  )
748
757
  ctx.fillStyle = 'white'
749
- ctx.fillText(txt, mismatchLeftPx - rect.width / 2, topPx + heightPx)
758
+ ctx.fillText(txt, leftPx - rect.width / 2, topPx + heightPx)
750
759
  } else {
751
760
  const padding = 2
752
761
  ctx.fillStyle = 'purple'
753
- ctx.fillRect(mismatchLeftPx - padding, topPx, 2 * padding, heightPx)
762
+ ctx.fillRect(leftPx - padding, topPx, 2 * padding, heightPx)
754
763
  }
755
764
  }
756
765
  }
@@ -831,11 +840,15 @@ export default class PileupRenderer extends BoxRendererType {
831
840
 
832
841
  async makeImageData(
833
842
  ctx: CanvasRenderingContext2D,
834
- layoutRecords: any, // eslint-disable-line @typescript-eslint/no-explicit-any
843
+ layoutRecords: (LayoutFeature | null)[],
835
844
  props: RenderArgsDeserializedWithFeaturesAndLayout,
836
845
  ) {
837
846
  const { layout, config, showSoftClip, colorBy, theme: configTheme } = props
838
847
  const mismatchAlpha = readConfObject(config, 'mismatchAlpha')
848
+ const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth')
849
+ const insertScale = readConfObject(config, 'largeInsertionIndicatorScale')
850
+ const defaultColor = readConfObject(config, 'color') === '#f0f'
851
+
839
852
  const theme = createJBrowseTheme(configTheme)
840
853
  const colorForBase = getColorBaseMap(theme)
841
854
  if (!layout) {
@@ -845,21 +858,25 @@ export default class PileupRenderer extends BoxRendererType {
845
858
  throw new Error('invalid layout object')
846
859
  }
847
860
  ctx.font = 'bold 10px Courier New,monospace'
848
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
849
- layoutRecords.forEach((feat: any) => {
861
+
862
+ const { charWidth, charHeight } = this.getCharWidthHeight(ctx)
863
+ layoutRecords.forEach(feat => {
850
864
  if (feat === null) {
851
865
  return
852
866
  }
853
867
 
854
- const { feature, topPx, heightPx } = feat
855
-
856
- ctx.fillStyle = readConfObject(config, 'color', { feature })
857
- this.drawAlignmentRect(ctx, { feature, topPx, heightPx }, props)
868
+ this.drawAlignmentRect(ctx, feat, {
869
+ ...props,
870
+ defaultColor,
871
+ })
858
872
  this.drawMismatches(ctx, feat, props, theme, colorForBase, {
859
873
  mismatchAlpha,
860
-
861
874
  drawSNPs: shouldDrawMismatches(colorBy?.type),
862
875
  drawIndels: shouldDrawMismatches(colorBy?.type),
876
+ largeInsertionIndicatorScale: insertScale,
877
+ minSubfeatureWidth,
878
+ charWidth,
879
+ charHeight,
863
880
  })
864
881
  if (showSoftClip) {
865
882
  this.drawSoftClipping(ctx, feat, props, config, theme)
@@ -887,59 +904,60 @@ export default class PileupRenderer extends BoxRendererType {
887
904
  throw new Error('invalid layout object')
888
905
  }
889
906
 
890
- const sortedFeatures =
891
- sortedBy && sortedBy.type && region.start === sortedBy.pos
907
+ const featureMap =
908
+ sortedBy?.type && region.start === sortedBy.pos
892
909
  ? sortFeature(features, sortedBy)
893
- : null
894
- const featureMap = sortedFeatures || features
910
+ : features
911
+
912
+ const heightPx = readConfObject(config, 'height')
913
+ const displayMode = readConfObject(config, 'displayMode')
895
914
  const layoutRecords = iterMap(
896
915
  featureMap.values(),
897
916
  feature =>
898
- this.layoutFeature(
917
+ this.layoutFeature({
899
918
  feature,
900
919
  layout,
901
- config,
902
920
  bpPerPx,
903
921
  region,
904
922
  showSoftClip,
905
- ),
923
+ heightPx,
924
+ displayMode,
925
+ }),
906
926
  featureMap.size,
907
927
  )
908
928
  return layoutRecords
909
929
  }
910
930
 
911
931
  async render(renderProps: RenderArgsDeserialized) {
912
- const { bpPerPx, regions } = renderProps
932
+ const { sessionId, bpPerPx, regions, adapterConfig } = renderProps
933
+ const { sequenceAdapter } = adapterConfig
913
934
  const features = await this.getFeatures(renderProps)
914
935
  const layout = this.createLayoutInWorker(renderProps)
915
936
 
916
937
  const layoutRecords = this.layoutFeats({ ...renderProps, features, layout })
917
-
918
- // @ts-ignore
919
- const { dataAdapter: sequenceAdapter } = renderProps.adapterConfig
920
- .sequenceAdapter
921
- ? await getAdapter(
922
- this.pluginManager,
923
- renderProps.sessionId,
924
- // @ts-ignore
925
- renderProps.adapterConfig.sequenceAdapter,
926
- )
927
- : {}
928
938
  const [region] = regions
929
- const [feat] = sequenceAdapter
930
- ? await (sequenceAdapter as BaseFeatureDataAdapter)
931
- .getFeatures({
932
- start: region.start,
933
- end: region.end + 1,
934
- refName: region.refName,
935
- assemblyName: region.assemblyName,
936
- })
937
- .pipe(toArray())
938
- .toPromise()
939
- : []
940
- const regionSequence = feat?.get('seq')
941
-
942
- const width = (region.end - region.start) / bpPerPx
939
+ let regionSequence: string | undefined
940
+ const { end, start, originalRefName, refName } = region
941
+
942
+ if (sequenceAdapter) {
943
+ const { dataAdapter } = await getAdapter(
944
+ this.pluginManager,
945
+ sessionId,
946
+ sequenceAdapter,
947
+ )
948
+
949
+ const feats = await (dataAdapter as BaseFeatureDataAdapter)
950
+ .getFeatures({
951
+ ...region,
952
+ refName: originalRefName || refName,
953
+ end: region.end + 1,
954
+ })
955
+ .pipe(toArray())
956
+ .toPromise()
957
+ regionSequence = feats[0]?.get('seq')
958
+ }
959
+
960
+ const width = (end - start) / bpPerPx
943
961
  const height = Math.max(layout.getTotalHeight(), 1)
944
962
 
945
963
  const res = await renderToAbstractCanvas(
@@ -979,3 +997,11 @@ export default class PileupRenderer extends BoxRendererType {
979
997
  return new PileupLayoutSession(args)
980
998
  }
981
999
  }
1000
+
1001
+ export type {
1002
+ RenderArgs,
1003
+ RenderArgsSerialized,
1004
+ RenderResults,
1005
+ ResultsSerialized,
1006
+ ResultsDeserialized,
1007
+ }
@@ -158,8 +158,10 @@ function PileupRendering(props: {
158
158
 
159
159
  function callMouseHandler(handlerName: string, event: MouseEvent) {
160
160
  // @ts-ignore
161
+ // eslint-disable-next-line react/destructuring-assignment
161
162
  const featureHandler = props[`onFeature${handlerName}`]
162
163
  // @ts-ignore
164
+ // eslint-disable-next-line react/destructuring-assignment
163
165
  const canvasHandler = props[`on${handlerName}`]
164
166
  if (featureHandler && featureIdUnderMouse) {
165
167
  featureHandler(event, featureIdUnderMouse)