@jbrowse/plugin-alignments 1.6.7 → 1.7.0

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 (94) hide show
  1. package/dist/AlignmentsFeatureDetail/AlignmentsFeatureDetail.js +216 -0
  2. package/dist/AlignmentsFeatureDetail/index.js +63 -0
  3. package/dist/AlignmentsFeatureDetail/index.test.js +60 -0
  4. package/dist/AlignmentsTrack/index.js +37 -0
  5. package/dist/BamAdapter/BamAdapter.js +598 -0
  6. package/dist/BamAdapter/BamAdapter.test.js +177 -0
  7. package/dist/BamAdapter/BamSlightlyLazyFeature.d.ts +1 -10
  8. package/dist/BamAdapter/BamSlightlyLazyFeature.js +176 -0
  9. package/dist/BamAdapter/MismatchParser.d.ts +3 -5
  10. package/dist/BamAdapter/MismatchParser.js +384 -0
  11. package/dist/BamAdapter/MismatchParser.test.js +259 -0
  12. package/dist/BamAdapter/configSchema.js +48 -0
  13. package/dist/BamAdapter/index.js +36 -0
  14. package/dist/CramAdapter/CramAdapter.js +660 -0
  15. package/dist/CramAdapter/CramAdapter.test.js +138 -0
  16. package/dist/CramAdapter/CramSlightlyLazyFeature.d.ts +1 -2
  17. package/dist/CramAdapter/CramSlightlyLazyFeature.js +447 -0
  18. package/dist/CramAdapter/CramTestAdapters.js +234 -0
  19. package/dist/CramAdapter/configSchema.js +40 -0
  20. package/dist/CramAdapter/index.js +36 -0
  21. package/dist/HtsgetBamAdapter/HtsgetBamAdapter.js +97 -0
  22. package/dist/HtsgetBamAdapter/configSchema.js +31 -0
  23. package/dist/HtsgetBamAdapter/index.js +42 -0
  24. package/dist/LinearAlignmentsDisplay/components/AlignmentsDisplay.js +69 -0
  25. package/dist/LinearAlignmentsDisplay/index.js +31 -0
  26. package/dist/LinearAlignmentsDisplay/models/configSchema.js +25 -0
  27. package/dist/LinearAlignmentsDisplay/models/configSchema.test.js +83 -0
  28. package/dist/LinearAlignmentsDisplay/models/model.js +250 -0
  29. package/dist/LinearPileupDisplay/components/ColorByModifications.js +123 -0
  30. package/dist/LinearPileupDisplay/components/ColorByTag.js +98 -0
  31. package/dist/LinearPileupDisplay/components/FilterByTag.js +203 -0
  32. package/dist/LinearPileupDisplay/components/LinearPileupDisplayBlurb.js +32 -0
  33. package/dist/LinearPileupDisplay/components/SetFeatureHeight.js +99 -0
  34. package/dist/LinearPileupDisplay/components/SetMaxHeight.js +90 -0
  35. package/dist/LinearPileupDisplay/components/SortByTag.js +95 -0
  36. package/dist/LinearPileupDisplay/configSchema.js +47 -0
  37. package/dist/LinearPileupDisplay/configSchema.test.js +92 -0
  38. package/dist/LinearPileupDisplay/index.js +30 -0
  39. package/dist/LinearPileupDisplay/model.js +602 -0
  40. package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +63 -0
  41. package/dist/LinearSNPCoverageDisplay/index.js +30 -0
  42. package/dist/LinearSNPCoverageDisplay/models/configSchema.js +57 -0
  43. package/dist/LinearSNPCoverageDisplay/models/configSchema.test.js +62 -0
  44. package/dist/LinearSNPCoverageDisplay/models/model.d.ts +2 -2
  45. package/dist/LinearSNPCoverageDisplay/models/model.js +237 -0
  46. package/dist/NestedFrequencyTable.js +152 -0
  47. package/dist/PileupRPC/rpcMethods.js +285 -0
  48. package/dist/PileupRenderer/PileupLayoutSession.js +79 -0
  49. package/dist/PileupRenderer/PileupRenderer.d.ts +20 -6
  50. package/dist/PileupRenderer/PileupRenderer.js +1220 -0
  51. package/dist/PileupRenderer/components/PileupRendering.js +270 -0
  52. package/dist/PileupRenderer/components/PileupRendering.test.js +36 -0
  53. package/dist/PileupRenderer/configSchema.js +72 -0
  54. package/dist/PileupRenderer/index.js +25 -0
  55. package/dist/PileupRenderer/sortUtil.js +112 -0
  56. package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +3 -11
  57. package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +606 -0
  58. package/dist/SNPCoverageAdapter/configSchema.js +22 -0
  59. package/dist/SNPCoverageAdapter/index.js +45 -0
  60. package/dist/SNPCoverageRenderer/SNPCoverageRenderer.js +296 -0
  61. package/dist/SNPCoverageRenderer/configSchema.js +40 -0
  62. package/dist/SNPCoverageRenderer/index.js +34 -0
  63. package/dist/declare.d.js +1 -0
  64. package/dist/index.js +154 -6
  65. package/dist/index.test.js +26 -0
  66. package/dist/plugin-alignments.cjs.development.js +591 -552
  67. package/dist/plugin-alignments.cjs.development.js.map +1 -1
  68. package/dist/plugin-alignments.cjs.production.min.js +1 -1
  69. package/dist/plugin-alignments.cjs.production.min.js.map +1 -1
  70. package/dist/plugin-alignments.esm.js +594 -555
  71. package/dist/plugin-alignments.esm.js.map +1 -1
  72. package/dist/shared.js +96 -0
  73. package/dist/util.d.ts +4 -0
  74. package/dist/util.js +135 -0
  75. package/package.json +5 -9
  76. package/src/BamAdapter/BamAdapter.ts +45 -15
  77. package/src/BamAdapter/BamSlightlyLazyFeature.ts +11 -79
  78. package/src/BamAdapter/MismatchParser.test.ts +53 -297
  79. package/src/BamAdapter/MismatchParser.ts +54 -116
  80. package/src/BamAdapter/configSchema.ts +0 -4
  81. package/src/CramAdapter/CramAdapter.ts +42 -15
  82. package/src/CramAdapter/CramSlightlyLazyFeature.ts +3 -10
  83. package/src/LinearPileupDisplay/components/ColorByModifications.tsx +76 -80
  84. package/src/LinearPileupDisplay/components/ColorByTag.tsx +24 -23
  85. package/src/LinearPileupDisplay/components/FilterByTag.tsx +73 -68
  86. package/src/LinearPileupDisplay/components/SetFeatureHeight.tsx +28 -26
  87. package/src/LinearPileupDisplay/components/SetMaxHeight.tsx +24 -13
  88. package/src/LinearPileupDisplay/components/SortByTag.tsx +29 -21
  89. package/src/LinearPileupDisplay/model.ts +8 -22
  90. package/src/LinearSNPCoverageDisplay/models/model.ts +6 -36
  91. package/src/PileupRenderer/PileupRenderer.tsx +178 -60
  92. package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +189 -244
  93. package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +12 -11
  94. package/src/util.ts +25 -0
@@ -4,12 +4,14 @@ import {
4
4
  } from '@jbrowse/core/data_adapters/BaseAdapter'
5
5
  import { AugmentedRegion as Region } from '@jbrowse/core/util/types'
6
6
  import SimpleFeature, { Feature } from '@jbrowse/core/util/simpleFeature'
7
- import { readConfObject } from '@jbrowse/core/configuration'
8
- import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain'
9
7
  import { ObservableCreate } from '@jbrowse/core/util/rxjs'
10
- import { reduce, filter, toArray } from 'rxjs/operators'
11
- import { Observable } from 'rxjs'
12
- import { getTag, getTagAlt } from '../util'
8
+ import { toArray } from 'rxjs/operators'
9
+ import {
10
+ getTag,
11
+ getTagAlt,
12
+ fetchSequence,
13
+ shouldFetchReferenceSequence,
14
+ } from '../util'
13
15
  import {
14
16
  parseCigar,
15
17
  getNextRefPos,
@@ -17,10 +19,6 @@ import {
17
19
  Mismatch,
18
20
  } from '../BamAdapter/MismatchParser'
19
21
 
20
- interface SNPCoverageOptions extends BaseOptions {
21
- filters?: SerializableFilterChain
22
- }
23
-
24
22
  function mismatchLen(mismatch: Mismatch) {
25
23
  return !isInterbase(mismatch.type) ? mismatch.length : 1
26
24
  }
@@ -31,11 +29,15 @@ function isInterbase(type: string) {
31
29
 
32
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
31
  function inc(bin: any, strand: number, type: string, field: string) {
34
- if (!bin[type][field]) {
35
- bin[type][field] = { total: 0, strands: { '-1': 0, '0': 0, '1': 0 } }
32
+ let thisBin = bin[type][field]
33
+ if (!thisBin) {
34
+ thisBin = bin[type][field] = {
35
+ total: 0,
36
+ strands: { '-1': 0, '0': 0, '1': 0 },
37
+ }
36
38
  }
37
- bin[type][field].total++
38
- bin[type][field].strands[strand]++
39
+ thisBin.total++
40
+ thisBin.strands[strand]++
39
41
  }
40
42
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
43
  function dec(bin: any, strand: number, type: string, field: string) {
@@ -48,11 +50,8 @@ function dec(bin: any, strand: number, type: string, field: string) {
48
50
 
49
51
  export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
50
52
  protected async configure() {
51
- const subadapterConfig = readConfObject(this.config, 'subadapter')
52
- const sequenceConf = readConfObject(this.config, [
53
- 'subadapter',
54
- 'sequenceAdapter',
55
- ])
53
+ const subadapterConfig = this.getConf('subadapter')
54
+ const sequenceConf = this.getConf(['subadapter', 'sequenceAdapter'])
56
55
  const dataAdapter = await this.getSubAdapter?.(subadapterConfig)
57
56
 
58
57
  const sequenceAdapter = sequenceConf
@@ -71,18 +70,25 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
71
70
  }
72
71
  }
73
72
 
74
- getFeatures(region: Region, opts: SNPCoverageOptions = {}) {
73
+ async fetchSequence(region: Region) {
74
+ const { sequenceAdapter } = await this.configure()
75
+ if (!sequenceAdapter) {
76
+ return undefined
77
+ }
78
+
79
+ return fetchSequence(region, sequenceAdapter)
80
+ }
81
+
82
+ getFeatures(region: Region, opts: BaseOptions = {}) {
75
83
  return ObservableCreate<Feature>(async observer => {
76
84
  const { subadapter } = await this.configure()
77
- let stream = subadapter.getFeatures(region, opts)
78
-
79
- if (opts.filters) {
80
- const { filters } = opts
81
- stream = stream.pipe(filter(f => filters.passes(f, opts)))
82
- }
85
+ const feats = await subadapter
86
+ .getFeatures(region, opts)
87
+ .pipe(toArray())
88
+ .toPromise()
83
89
 
84
90
  const { bins, skipmap } = await this.generateCoverageBins(
85
- stream,
91
+ feats,
86
92
  region,
87
93
  opts,
88
94
  )
@@ -90,7 +96,7 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
90
96
  bins.forEach((bin, index) => {
91
97
  observer.next(
92
98
  new SimpleFeature({
93
- id: `${this.id}-${region.start}-${index}`,
99
+ id: `${this.id}-${region.start + index}`,
94
100
  data: {
95
101
  score: bin.total,
96
102
  snpinfo: bin,
@@ -135,22 +141,12 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
135
141
 
136
142
  freeResources(/* { region } */): void {}
137
143
 
138
- /**
139
- * Generates coverage bins from features which details
140
- * the reference, mismatches, strands, and coverage info
141
- * @param features - Features of region to be passed in
142
- * @param region - Region
143
- * @param bpPerPx - base pairs per pixel
144
- * @returns Array of nested frequency tables
145
- */
146
144
  async generateCoverageBins(
147
- features: Observable<Feature>,
145
+ features: Feature[],
148
146
  region: Region,
149
147
  opts: { bpPerPx?: number; colorBy?: { type: string; tag?: string } },
150
148
  ) {
151
149
  const { colorBy } = opts
152
- const { sequenceAdapter } = await this.configure()
153
- const { originalRefName, refName, start, end } = region
154
150
  const binMax = Math.ceil(region.end - region.start)
155
151
 
156
152
  const skipmap = {} as {
@@ -164,227 +160,176 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
164
160
  }
165
161
  }
166
162
 
167
- // bins contain cov feature if they contribute to coverage, or noncov which
168
- // are interbase or other features that don't contribute to coverage.
169
- // delskips are elements that don't contribute to coverage, but should be
170
- // reported also (and are not interbase)
163
+ // bins contain:
164
+ // - cov feature if they contribute to coverage
165
+ // - noncov are insertions/clip features that don't contribute to coverage
166
+ // - delskips deletions or introns that don't contribute to coverage
171
167
  type BinType = { total: number; strands: { [key: string]: number } }
172
168
 
173
- // request an extra +1 on the end to get CpG crossing region boundary
174
- let regionSeq: string | undefined
175
-
176
- if (sequenceAdapter) {
177
- const [feat] = await sequenceAdapter
178
- .getFeatures({
179
- refName: originalRefName || refName,
180
- start,
181
- end: end + 1,
182
- assemblyName: region.assemblyName,
183
- })
184
- .pipe(toArray())
185
- .toPromise()
186
- regionSeq = feat?.get('seq')
187
- }
169
+ const regionSeq =
170
+ features.length && shouldFetchReferenceSequence(opts.colorBy?.type)
171
+ ? await this.fetchSequence(region)
172
+ : undefined
173
+
174
+ const bins = [] as {
175
+ total: number
176
+ lowqual: BinType
177
+ cov: BinType
178
+ delskips: BinType
179
+ noncov: BinType
180
+ ref: BinType
181
+ }[]
182
+
183
+ for (let i = 0; i < features.length; i++) {
184
+ const feature = features[i]
185
+ const ops = parseCigar(feature.get('CIGAR'))
186
+ const fstart = feature.get('start')
187
+ const fend = feature.get('end')
188
+ const fstrand = feature.get('strand')
189
+
190
+ for (let j = fstart; j < fend; j++) {
191
+ const i = j - region.start
192
+ if (i >= 0 && i < binMax) {
193
+ const bin = bins[i] || {
194
+ total: 0,
195
+ lowqual: {} as BinType,
196
+ cov: {} as BinType,
197
+ delskips: {} as BinType,
198
+ noncov: {} as BinType,
199
+ ref: {} as BinType,
200
+ }
201
+ if (j !== fend) {
202
+ bin.total++
203
+ inc(bin, fstrand, 'ref', 'ref')
204
+ }
205
+ bins[i] = bin
206
+ }
207
+ }
188
208
 
189
- const bins = await features
190
- .pipe(
191
- reduce(
192
- (bins, feature) => {
193
- const cigar = feature.get('CIGAR')
194
- const fstart = feature.get('start')
195
- const fend = feature.get('end')
196
- const fstrand = feature.get('strand')
197
- const cigarOps = parseCigar(cigar)
198
-
199
- for (let j = fstart; j < fend + 1; j++) {
200
- const i = j - region.start
201
- if (i >= 0 && i < binMax) {
202
- const bin = bins[i] || {
203
- total: 0,
204
- lowqual: {} as BinType,
205
- cov: {} as BinType,
206
- delskips: {} as BinType,
207
- noncov: {} as BinType,
208
- ref: {} as BinType,
209
- }
210
- if (j !== fend) {
211
- bin.total++
212
- inc(bin, fstrand, 'ref', 'ref')
213
- }
214
- bins[i] = bin
209
+ if (colorBy?.type === 'modifications') {
210
+ const seq = feature.get('seq') as string
211
+ const mm = (getTagAlt(feature, 'MM', 'Mm') as string) || ''
212
+
213
+ getModificationPositions(mm, seq, fstrand).forEach(
214
+ ({ type, positions }) => {
215
+ const mod = `mod_${type}`
216
+ for (const pos of getNextRefPos(ops, positions)) {
217
+ const epos = pos + fstart - region.start
218
+ if (epos >= 0 && epos < bins.length && pos + fstart < fend) {
219
+ const bin = bins[epos]
220
+ inc(bin, fstrand, 'cov', mod)
215
221
  }
216
222
  }
223
+ },
224
+ )
225
+ }
217
226
 
218
- if (colorBy?.type === 'modifications') {
219
- const seq = feature.get('seq')
220
- const mm = getTagAlt(feature, 'MM', 'Mm') || ''
221
-
222
- const ml =
223
- (getTagAlt(feature, 'ML', 'Ml') as number[] | string) || []
224
-
225
- const probabilities = ml
226
- ? (typeof ml === 'string'
227
- ? ml.split(',').map(e => +e)
228
- : ml
229
- ).map(e => e / 255)
230
- : (getTagAlt(feature, 'MP', 'Mp') as string)
231
- .split('')
232
- .map(s => s.charCodeAt(0) - 33)
233
- .map(elt => Math.min(1, elt / 50))
234
-
235
- let probIndex = 0
236
- getModificationPositions(mm, seq, fstrand).forEach(
237
- ({ type, positions }) => {
238
- const mod = `mod_${type}`
239
- for (const pos of getNextRefPos(cigarOps, positions)) {
240
- const epos = pos + fstart - region.start
241
- if (
242
- epos >= 0 &&
243
- epos < bins.length &&
244
- pos + fstart < fend
245
- ) {
246
- const bin = bins[epos] || {
247
- total: 0,
248
- lowqual: {} as BinType,
249
- cov: {} as BinType,
250
- delskips: {} as BinType,
251
- noncov: {} as BinType,
252
- ref: {} as BinType,
253
- }
254
-
255
- if (probabilities[probIndex] > 0.5) {
256
- inc(bin, fstrand, 'cov', mod)
257
- } else {
258
- inc(bin, fstrand, 'lowqual', mod)
259
- }
260
- }
261
- probIndex++
262
- }
263
- },
264
- )
227
+ // methylation based coloring takes into account both reference
228
+ // sequence CpG detection and reads
229
+ else if (colorBy?.type === 'methylation') {
230
+ if (!regionSeq) {
231
+ throw new Error(
232
+ 'no region sequence detected, need sequenceAdapter configuration',
233
+ )
234
+ }
235
+ const seq = feature.get('seq')
236
+ const mm = getTagAlt(feature, 'MM', 'Mm') || ''
237
+ const methBins = new Array(region.end - region.start).fill(0)
238
+
239
+ getModificationPositions(mm, seq, fstrand).forEach(
240
+ ({ type, positions }) => {
241
+ // we are processing methylation
242
+ if (type === 'm') {
243
+ for (const pos of getNextRefPos(ops, positions)) {
244
+ const epos = pos + fstart - region.start
245
+ if (epos >= 0 && epos < methBins.length) {
246
+ methBins[epos] = 1
247
+ }
248
+ }
265
249
  }
250
+ },
251
+ )
266
252
 
267
- // methylation based coloring takes into account both reference
268
- // sequence CpG detection and reads
269
- else if (colorBy?.type === 'methylation') {
270
- if (!regionSeq) {
271
- throw new Error(
272
- 'no region sequence detected, need sequenceAdapter configuration',
273
- )
274
- }
275
- const seq = feature.get('seq')
276
- const mm = getTagAlt(feature, 'MM', 'Mm') || ''
277
- const methBins = new Array(region.end - region.start).fill(0)
278
-
279
- getModificationPositions(mm, seq, fstrand).forEach(
280
- ({ type, positions }) => {
281
- // we are processing methylation
282
- if (type === 'm') {
283
- for (const pos of getNextRefPos(cigarOps, positions)) {
284
- const epos = pos + fstart - region.start
285
- if (epos >= 0 && epos < methBins.length) {
286
- methBins[epos] = 1
287
- }
288
- }
289
- }
290
- },
291
- )
292
-
293
- for (let j = fstart; j < fend; j++) {
294
- const i = j - region.start
295
- if (i >= 0 && i < bins.length - 1) {
296
- const l1 = regionSeq[i].toLowerCase()
297
- const l2 = regionSeq[i + 1].toLowerCase()
298
- const bin = bins[i]
299
- const bin1 = bins[i + 1]
300
-
301
- // color
302
- if (l1 === 'c' && l2 === 'g') {
303
- if (methBins[i] || methBins[i + 1]) {
304
- inc(bin, fstrand, 'cov', 'meth')
305
- inc(bin1, fstrand, 'cov', 'meth')
306
- dec(bin, fstrand, 'ref', 'ref')
307
- dec(bin1, fstrand, 'ref', 'ref')
308
- } else {
309
- inc(bin, fstrand, 'cov', 'unmeth')
310
- inc(bin1, fstrand, 'cov', 'unmeth')
311
- dec(bin, fstrand, 'ref', 'ref')
312
- dec(bin1, fstrand, 'ref', 'ref')
313
- }
314
- }
315
- }
253
+ for (let j = fstart; j < fend; j++) {
254
+ const i = j - region.start
255
+ if (i >= 0 && i < bins.length - 1) {
256
+ const l1 = regionSeq[i].toLowerCase()
257
+ const l2 = regionSeq[i + 1].toLowerCase()
258
+ const bin = bins[i]
259
+ const bin1 = bins[i + 1]
260
+
261
+ // color
262
+ if (l1 === 'c' && l2 === 'g') {
263
+ if (methBins[i] || methBins[i + 1]) {
264
+ inc(bin, fstrand, 'cov', 'meth')
265
+ inc(bin1, fstrand, 'cov', 'meth')
266
+ dec(bin, fstrand, 'ref', 'ref')
267
+ dec(bin1, fstrand, 'ref', 'ref')
268
+ } else {
269
+ inc(bin, fstrand, 'cov', 'unmeth')
270
+ inc(bin1, fstrand, 'cov', 'unmeth')
271
+ dec(bin, fstrand, 'ref', 'ref')
272
+ dec(bin1, fstrand, 'ref', 'ref')
316
273
  }
317
274
  }
275
+ }
276
+ }
277
+ }
318
278
 
319
- // normal SNP based coloring
320
- else {
321
- const mismatches = feature.get('mismatches') as
322
- | Mismatch[]
323
- | undefined
324
-
325
- if (mismatches) {
326
- for (let i = 0; i < mismatches.length; i++) {
327
- const mismatch = mismatches[i]
328
- const ms = fstart + mismatch.start
329
- for (let j = ms; j < ms + mismatchLen(mismatch); j++) {
330
- const epos = j - region.start
331
- if (epos >= 0 && epos < bins.length) {
332
- const bin = bins[epos]
333
- const { base, type } = mismatch
334
- const interbase = isInterbase(type)
335
- if (!interbase) {
336
- dec(bin, fstrand, 'ref', 'ref')
337
- } else {
338
- inc(bin, fstrand, 'noncov', type)
339
- }
340
-
341
- if (type === 'deletion' || type === 'skip') {
342
- inc(bin, fstrand, 'delskips', type)
343
- bin.total--
344
- } else if (!interbase) {
345
- inc(bin, fstrand, 'cov', base)
346
- }
347
- }
348
- }
279
+ // normal SNP based coloring
280
+ else {
281
+ const mismatches = feature.get('mismatches') as Mismatch[] | undefined
282
+
283
+ if (mismatches) {
284
+ for (let i = 0; i < mismatches.length; i++) {
285
+ const mismatch = mismatches[i]
286
+ const mstart = fstart + mismatch.start
287
+ for (let j = mstart; j < mstart + mismatchLen(mismatch); j++) {
288
+ const epos = j - region.start
289
+ if (epos >= 0 && epos < bins.length) {
290
+ const bin = bins[epos]
291
+ const { base, type } = mismatch
292
+ const interbase = isInterbase(type)
293
+ if (!interbase) {
294
+ dec(bin, fstrand, 'ref', 'ref')
295
+ } else {
296
+ inc(bin, fstrand, 'noncov', type)
349
297
  }
350
298
 
351
- mismatches
352
- .filter(mismatch => mismatch.type === 'skip')
353
- .forEach(mismatch => {
354
- const mstart = feature.get('start') + mismatch.start
355
- const start = mstart
356
- const end = mstart + mismatch.length
357
- const strand = feature.get('strand')
358
- const hash = `${start}_${end}_${strand}`
359
- if (!skipmap[hash]) {
360
- skipmap[hash] = {
361
- feature: feature,
362
- start,
363
- end,
364
- strand,
365
- xs: getTag(feature, 'XS') || getTag(feature, 'TS'),
366
- score: 1,
367
- }
368
- } else {
369
- skipmap[hash].score++
370
- }
371
- })
299
+ if (type === 'deletion' || type === 'skip') {
300
+ inc(bin, fstrand, 'delskips', type)
301
+ bin.total--
302
+ } else if (!interbase) {
303
+ inc(bin, fstrand, 'cov', base)
304
+ }
372
305
  }
373
306
  }
374
-
375
- return bins
376
- },
377
- [] as {
378
- total: number
379
- lowqual: BinType
380
- cov: BinType
381
- delskips: BinType
382
- noncov: BinType
383
- ref: BinType
384
- }[],
385
- ),
386
- )
387
- .toPromise()
307
+ }
308
+
309
+ mismatches
310
+ .filter(mismatch => mismatch.type === 'skip')
311
+ .forEach(mismatch => {
312
+ const mstart = feature.get('start') + mismatch.start
313
+ const start = mstart
314
+ const end = mstart + mismatch.length
315
+ const strand = feature.get('strand')
316
+ const hash = `${start}_${end}_${strand}`
317
+ if (!skipmap[hash]) {
318
+ skipmap[hash] = {
319
+ feature: feature,
320
+ start,
321
+ end,
322
+ strand,
323
+ xs: getTag(feature, 'XS') || getTag(feature, 'TS'),
324
+ score: 1,
325
+ }
326
+ } else {
327
+ skipmap[hash].score++
328
+ }
329
+ })
330
+ }
331
+ }
332
+ }
388
333
 
389
334
  return { bins, skipmap }
390
335
  }
@@ -71,11 +71,7 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
71
71
  }
72
72
  const opts = { ...scaleOpts, range: [0, height] }
73
73
  const viewScale = getScale(opts)
74
- const snpViewScale = getScale({
75
- ...opts,
76
- range: [0, height],
77
- scaleType: 'linear',
78
- })
74
+
79
75
  // clipping and insertion indicators, uses a smaller height/2 scale
80
76
  const indicatorViewScale = getScale({
81
77
  ...opts,
@@ -83,7 +79,6 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
83
79
  scaleType: 'linear',
84
80
  })
85
81
  const originY = getOrigin(scaleOpts.scaleType)
86
- const snpOriginY = getOrigin('linear')
87
82
 
88
83
  const indicatorThreshold = readConfObject(cfg, 'indicatorThreshold')
89
84
  const drawInterbaseCounts = readConfObject(cfg, 'drawInterbaseCounts')
@@ -94,13 +89,10 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
94
89
  const toY = (n: number) => height - (viewScale(n) || 0) + offset
95
90
  const toHeight = (n: number) => toY(originY) - toY(n)
96
91
 
97
- // this is always linear scale, even when plotted on top of log scale
98
- const snpToY = (n: number) => height - (snpViewScale(n) || 0) + offset
99
92
  const indicatorToY = (n: number) =>
100
93
  height - (indicatorViewScale(n) || 0) + offset
101
- const snpToHeight = (n: number) => snpToY(snpOriginY) - snpToY(n)
102
94
  const indicatorToHeight = (n: number) =>
103
- indicatorToY(snpOriginY) - indicatorToY(n)
95
+ indicatorToY(getOrigin('linear')) - indicatorToY(n)
104
96
 
105
97
  const colorForBase: { [key: string]: string } = {
106
98
  A: theme.palette.bases.A.main,
@@ -150,6 +142,7 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
150
142
  const feature = coverage[i]
151
143
  const [leftPx, rightPx] = featureSpanPx(feature, region, bpPerPx)
152
144
 
145
+ const score = feature.get('score') as number
153
146
  const snpinfo = feature.get('snpinfo') as SNPInfo
154
147
  const w = Math.max(rightPx - leftPx + 0.3, 1)
155
148
  const totalScore = snpinfo.total
@@ -163,7 +156,15 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
163
156
  colorForBase[base] ||
164
157
  modificationTagMap[base.replace('mod_', '')] ||
165
158
  '#888'
166
- ctx.fillRect(leftPx, snpToY(total + curr), w, snpToHeight(total))
159
+
160
+ const height = toHeight(score)
161
+ const bottom = toY(score) + height
162
+ ctx.fillRect(
163
+ leftPx,
164
+ bottom - ((total + curr) / score) * height,
165
+ w,
166
+ (total / score) * height,
167
+ )
167
168
  curr += total
168
169
  }
169
170
 
package/src/util.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
2
+ import { toArray } from 'rxjs/operators'
1
3
  import { Feature } from '@jbrowse/core/util/simpleFeature'
4
+ import { AugmentedRegion } from '@jbrowse/core/util'
2
5
  // get tag from BAM or CRAM feature, where CRAM uses feature.get('tags') and
3
6
  // BAM does not
4
7
  export function getTag(feature: Feature, tag: string) {
@@ -76,3 +79,25 @@ export function getColorWGBS(strand: number, base: string) {
76
79
  }
77
80
  return '#888'
78
81
  }
82
+
83
+ export async function fetchSequence(
84
+ region: AugmentedRegion,
85
+ adapter: BaseFeatureDataAdapter,
86
+ ) {
87
+ const { end, originalRefName, refName } = region
88
+
89
+ const feats = await adapter
90
+ .getFeatures({
91
+ ...region,
92
+ refName: originalRefName || refName,
93
+ end: end + 1,
94
+ })
95
+ .pipe(toArray())
96
+ .toPromise()
97
+ return feats[0]?.get('seq')
98
+ }
99
+
100
+ // has to check underlying C-G (aka CpG) on the reference sequence
101
+ export function shouldFetchReferenceSequence(type?: string) {
102
+ return type === 'methylation'
103
+ }