@jbrowse/plugin-alignments 1.6.6 → 1.6.9

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 (32) hide show
  1. package/dist/BamAdapter/BamSlightlyLazyFeature.d.ts +1 -10
  2. package/dist/BamAdapter/MismatchParser.d.ts +3 -5
  3. package/dist/CramAdapter/CramSlightlyLazyFeature.d.ts +1 -2
  4. package/dist/LinearSNPCoverageDisplay/models/model.d.ts +2 -2
  5. package/dist/PileupRenderer/PileupRenderer.d.ts +20 -6
  6. package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +3 -11
  7. package/dist/plugin-alignments.cjs.development.js +591 -552
  8. package/dist/plugin-alignments.cjs.development.js.map +1 -1
  9. package/dist/plugin-alignments.cjs.production.min.js +1 -1
  10. package/dist/plugin-alignments.cjs.production.min.js.map +1 -1
  11. package/dist/plugin-alignments.esm.js +594 -555
  12. package/dist/plugin-alignments.esm.js.map +1 -1
  13. package/dist/util.d.ts +4 -0
  14. package/package.json +3 -3
  15. package/src/BamAdapter/BamAdapter.ts +10 -7
  16. package/src/BamAdapter/BamSlightlyLazyFeature.ts +11 -79
  17. package/src/BamAdapter/MismatchParser.test.ts +53 -297
  18. package/src/BamAdapter/MismatchParser.ts +54 -116
  19. package/src/BamAdapter/configSchema.ts +0 -4
  20. package/src/CramAdapter/CramSlightlyLazyFeature.ts +3 -10
  21. package/src/LinearAlignmentsDisplay/models/model.tsx +4 -6
  22. package/src/LinearPileupDisplay/components/ColorByModifications.tsx +76 -80
  23. package/src/LinearPileupDisplay/components/ColorByTag.tsx +24 -23
  24. package/src/LinearPileupDisplay/components/FilterByTag.tsx +73 -68
  25. package/src/LinearPileupDisplay/components/SetFeatureHeight.tsx +28 -26
  26. package/src/LinearPileupDisplay/components/SetMaxHeight.tsx +24 -13
  27. package/src/LinearPileupDisplay/components/SortByTag.tsx +29 -21
  28. package/src/LinearPileupDisplay/model.ts +6 -0
  29. package/src/PileupRenderer/PileupRenderer.tsx +178 -57
  30. package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +180 -229
  31. package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +12 -11
  32. package/src/util.ts +25 -0
@@ -4,12 +4,15 @@ 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
7
  import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain'
9
8
  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'
9
+ import { toArray } from 'rxjs/operators'
10
+ import {
11
+ getTag,
12
+ getTagAlt,
13
+ fetchSequence,
14
+ shouldFetchReferenceSequence,
15
+ } from '../util'
13
16
  import {
14
17
  parseCigar,
15
18
  getNextRefPos,
@@ -48,11 +51,8 @@ function dec(bin: any, strand: number, type: string, field: string) {
48
51
 
49
52
  export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
50
53
  protected async configure() {
51
- const subadapterConfig = readConfObject(this.config, 'subadapter')
52
- const sequenceConf = readConfObject(this.config, [
53
- 'subadapter',
54
- 'sequenceAdapter',
55
- ])
54
+ const subadapterConfig = this.getConf('subadapter')
55
+ const sequenceConf = this.getConf(['subadapter', 'sequenceAdapter'])
56
56
  const dataAdapter = await this.getSubAdapter?.(subadapterConfig)
57
57
 
58
58
  const sequenceAdapter = sequenceConf
@@ -71,18 +71,30 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
71
71
  }
72
72
  }
73
73
 
74
+ async fetchSequence(region: Region) {
75
+ const { sequenceAdapter } = await this.configure()
76
+ if (!sequenceAdapter) {
77
+ return undefined
78
+ }
79
+
80
+ return fetchSequence(region, sequenceAdapter)
81
+ }
82
+
74
83
  getFeatures(region: Region, opts: SNPCoverageOptions = {}) {
75
84
  return ObservableCreate<Feature>(async observer => {
76
85
  const { subadapter } = await this.configure()
77
- let stream = subadapter.getFeatures(region, opts)
86
+ let feats = await subadapter
87
+ .getFeatures(region, opts)
88
+ .pipe(toArray())
89
+ .toPromise()
78
90
 
79
91
  if (opts.filters) {
80
92
  const { filters } = opts
81
- stream = stream.pipe(filter(f => filters.passes(f, opts)))
93
+ feats = feats.filter(f => filters.passes(f, opts))
82
94
  }
83
95
 
84
96
  const { bins, skipmap } = await this.generateCoverageBins(
85
- stream,
97
+ feats,
86
98
  region,
87
99
  opts,
88
100
  )
@@ -135,22 +147,12 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
135
147
 
136
148
  freeResources(/* { region } */): void {}
137
149
 
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
150
  async generateCoverageBins(
147
- features: Observable<Feature>,
151
+ features: Feature[],
148
152
  region: Region,
149
153
  opts: { bpPerPx?: number; colorBy?: { type: string; tag?: string } },
150
154
  ) {
151
155
  const { colorBy } = opts
152
- const { sequenceAdapter } = await this.configure()
153
- const { originalRefName, refName, start, end } = region
154
156
  const binMax = Math.ceil(region.end - region.start)
155
157
 
156
158
  const skipmap = {} as {
@@ -164,227 +166,176 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
164
166
  }
165
167
  }
166
168
 
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)
169
+ // bins contain:
170
+ // - cov feature if they contribute to coverage
171
+ // - noncov are insertions/clip features that don't contribute to coverage
172
+ // - delskips deletions or introns that don't contribute to coverage
171
173
  type BinType = { total: number; strands: { [key: string]: number } }
172
174
 
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
- }
175
+ const regionSeq =
176
+ features.length && shouldFetchReferenceSequence(opts.colorBy?.type)
177
+ ? await this.fetchSequence(region)
178
+ : undefined
179
+
180
+ const bins = [] as {
181
+ total: number
182
+ lowqual: BinType
183
+ cov: BinType
184
+ delskips: BinType
185
+ noncov: BinType
186
+ ref: BinType
187
+ }[]
188
+
189
+ for (let i = 0; i < features.length; i++) {
190
+ const feature = features[i]
191
+ const ops = parseCigar(feature.get('CIGAR'))
192
+ const fstart = feature.get('start')
193
+ const fend = feature.get('end')
194
+ const fstrand = feature.get('strand')
195
+
196
+ for (let j = fstart; j < fend; j++) {
197
+ const i = j - region.start
198
+ if (i >= 0 && i < binMax) {
199
+ const bin = bins[i] || {
200
+ total: 0,
201
+ lowqual: {} as BinType,
202
+ cov: {} as BinType,
203
+ delskips: {} as BinType,
204
+ noncov: {} as BinType,
205
+ ref: {} as BinType,
206
+ }
207
+ if (j !== fend) {
208
+ bin.total++
209
+ inc(bin, fstrand, 'ref', 'ref')
210
+ }
211
+ bins[i] = bin
212
+ }
213
+ }
188
214
 
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
215
+ if (colorBy?.type === 'modifications') {
216
+ const seq = feature.get('seq') as string
217
+ const mm = (getTagAlt(feature, 'MM', 'Mm') as string) || ''
218
+
219
+ getModificationPositions(mm, seq, fstrand).forEach(
220
+ ({ type, positions }) => {
221
+ const mod = `mod_${type}`
222
+ for (const pos of getNextRefPos(ops, positions)) {
223
+ const epos = pos + fstart - region.start
224
+ if (epos >= 0 && epos < bins.length && pos + fstart < fend) {
225
+ const bin = bins[epos]
226
+ inc(bin, fstrand, 'cov', mod)
215
227
  }
216
228
  }
229
+ },
230
+ )
231
+ }
217
232
 
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
- )
233
+ // methylation based coloring takes into account both reference
234
+ // sequence CpG detection and reads
235
+ else if (colorBy?.type === 'methylation') {
236
+ if (!regionSeq) {
237
+ throw new Error(
238
+ 'no region sequence detected, need sequenceAdapter configuration',
239
+ )
240
+ }
241
+ const seq = feature.get('seq')
242
+ const mm = getTagAlt(feature, 'MM', 'Mm') || ''
243
+ const methBins = new Array(region.end - region.start).fill(0)
244
+
245
+ getModificationPositions(mm, seq, fstrand).forEach(
246
+ ({ type, positions }) => {
247
+ // we are processing methylation
248
+ if (type === 'm') {
249
+ for (const pos of getNextRefPos(ops, positions)) {
250
+ const epos = pos + fstart - region.start
251
+ if (epos >= 0 && epos < methBins.length) {
252
+ methBins[epos] = 1
253
+ }
254
+ }
265
255
  }
256
+ },
257
+ )
266
258
 
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
- }
259
+ for (let j = fstart; j < fend; j++) {
260
+ const i = j - region.start
261
+ if (i >= 0 && i < bins.length - 1) {
262
+ const l1 = regionSeq[i].toLowerCase()
263
+ const l2 = regionSeq[i + 1].toLowerCase()
264
+ const bin = bins[i]
265
+ const bin1 = bins[i + 1]
266
+
267
+ // color
268
+ if (l1 === 'c' && l2 === 'g') {
269
+ if (methBins[i] || methBins[i + 1]) {
270
+ inc(bin, fstrand, 'cov', 'meth')
271
+ inc(bin1, fstrand, 'cov', 'meth')
272
+ dec(bin, fstrand, 'ref', 'ref')
273
+ dec(bin1, fstrand, 'ref', 'ref')
274
+ } else {
275
+ inc(bin, fstrand, 'cov', 'unmeth')
276
+ inc(bin1, fstrand, 'cov', 'unmeth')
277
+ dec(bin, fstrand, 'ref', 'ref')
278
+ dec(bin1, fstrand, 'ref', 'ref')
316
279
  }
317
280
  }
281
+ }
282
+ }
283
+ }
318
284
 
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
- }
285
+ // normal SNP based coloring
286
+ else {
287
+ const mismatches = feature.get('mismatches') as Mismatch[] | undefined
288
+
289
+ if (mismatches) {
290
+ for (let i = 0; i < mismatches.length; i++) {
291
+ const mismatch = mismatches[i]
292
+ const mstart = fstart + mismatch.start
293
+ for (let j = mstart; j < mstart + mismatchLen(mismatch); j++) {
294
+ const epos = j - region.start
295
+ if (epos >= 0 && epos < bins.length) {
296
+ const bin = bins[epos]
297
+ const { base, type } = mismatch
298
+ const interbase = isInterbase(type)
299
+ if (!interbase) {
300
+ dec(bin, fstrand, 'ref', 'ref')
301
+ } else {
302
+ inc(bin, fstrand, 'noncov', type)
349
303
  }
350
304
 
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
- })
305
+ if (type === 'deletion' || type === 'skip') {
306
+ inc(bin, fstrand, 'delskips', type)
307
+ bin.total--
308
+ } else if (!interbase) {
309
+ inc(bin, fstrand, 'cov', base)
310
+ }
372
311
  }
373
312
  }
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()
313
+ }
314
+
315
+ mismatches
316
+ .filter(mismatch => mismatch.type === 'skip')
317
+ .forEach(mismatch => {
318
+ const mstart = feature.get('start') + mismatch.start
319
+ const start = mstart
320
+ const end = mstart + mismatch.length
321
+ const strand = feature.get('strand')
322
+ const hash = `${start}_${end}_${strand}`
323
+ if (!skipmap[hash]) {
324
+ skipmap[hash] = {
325
+ feature: feature,
326
+ start,
327
+ end,
328
+ strand,
329
+ xs: getTag(feature, 'XS') || getTag(feature, 'TS'),
330
+ score: 1,
331
+ }
332
+ } else {
333
+ skipmap[hash].score++
334
+ }
335
+ })
336
+ }
337
+ }
338
+ }
388
339
 
389
340
  return { bins, skipmap }
390
341
  }
@@ -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
+ }