@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
@@ -4,13 +4,13 @@ import { types } from 'mobx-state-tree'
4
4
  export default ConfigurationSchema(
5
5
  'PileupRenderer',
6
6
  {
7
+ // default magenta here is used to detect the user has not customized this
7
8
  color: {
8
9
  type: 'color',
9
10
  description: 'the color of each feature in a pileup alignment',
10
- defaultValue: `#c8c8c8`,
11
+ defaultValue: '#f0f',
11
12
  contextVariable: ['feature'],
12
13
  },
13
-
14
14
  orientationType: {
15
15
  type: 'stringEnum',
16
16
  model: types.enumeration('orientationType', ['fr', 'rf', 'ff']),
@@ -1,3 +1,16 @@
1
- export { default as ReactComponent } from './components/PileupRendering'
2
- export { default as configSchema } from './configSchema'
3
- export { default } from './PileupRenderer'
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import PileupRenderer from './PileupRenderer'
3
+ import ReactComponent from './components/PileupRendering'
4
+ import configSchema from './configSchema'
5
+
6
+ export default function register(pluginManager: PluginManager) {
7
+ pluginManager.addRendererType(
8
+ () =>
9
+ new PileupRenderer({
10
+ name: 'PileupRenderer',
11
+ ReactComponent,
12
+ configSchema,
13
+ pluginManager,
14
+ }),
15
+ )
16
+ }
@@ -2,7 +2,7 @@ import {
2
2
  BaseFeatureDataAdapter,
3
3
  BaseOptions,
4
4
  } from '@jbrowse/core/data_adapters/BaseAdapter'
5
- import { Region } from '@jbrowse/core/util/types'
5
+ import { AugmentedRegion as Region } from '@jbrowse/core/util/types'
6
6
  import SimpleFeature, { Feature } from '@jbrowse/core/util/simpleFeature'
7
7
  import { readConfObject } from '@jbrowse/core/configuration'
8
8
  import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain'
@@ -81,7 +81,11 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
81
81
  stream = stream.pipe(filter(f => filters.passes(f, opts)))
82
82
  }
83
83
 
84
- const bins = await this.generateCoverageBins(stream, region, opts)
84
+ const { bins, skipmap } = await this.generateCoverageBins(
85
+ stream,
86
+ region,
87
+ opts,
88
+ )
85
89
 
86
90
  bins.forEach((bin, index) => {
87
91
  if (bin.total) {
@@ -100,6 +104,23 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
100
104
  }
101
105
  })
102
106
 
107
+ // make fake features from the coverage
108
+ Object.entries(skipmap).forEach(([key, skip]) => {
109
+ observer.next(
110
+ new SimpleFeature({
111
+ id: key,
112
+ data: {
113
+ type: 'skip',
114
+ start: skip.start,
115
+ end: skip.end,
116
+ strand: skip.strand,
117
+ score: skip.score,
118
+ xs: skip.xs,
119
+ },
120
+ }),
121
+ )
122
+ })
123
+
103
124
  observer.complete()
104
125
  }, opts.signal)
105
126
  }
@@ -126,9 +147,20 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
126
147
  ) {
127
148
  const { colorBy } = opts
128
149
  const { sequenceAdapter } = await this.configure()
129
- const { refName, start, end } = region
150
+ const { originalRefName, refName, start, end } = region
130
151
  const binMax = Math.ceil(region.end - region.start)
131
152
 
153
+ const skipmap = {} as {
154
+ [key: string]: {
155
+ score: number
156
+ feature: unknown
157
+ start: number
158
+ end: number
159
+ strand: number
160
+ xs: string
161
+ }
162
+ }
163
+
132
164
  // bins contain cov feature if they contribute to coverage, or noncov which
133
165
  // are interbase or other features that don't contribute to coverage.
134
166
  // delskips are elements that don't contribute to coverage, but should be
@@ -148,13 +180,18 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
148
180
 
149
181
  if (sequenceAdapter) {
150
182
  const [feat] = await sequenceAdapter
151
- .getFeatures({ refName, start, end: end + 1, assemblyName: 'na' })
183
+ .getFeatures({
184
+ refName: originalRefName || refName,
185
+ start,
186
+ end: end + 1,
187
+ assemblyName: 'na',
188
+ })
152
189
  .pipe(toArray())
153
190
  .toPromise()
154
191
  regionSeq = feat?.get('seq')
155
192
  }
156
193
 
157
- return features
194
+ const bins = await features
158
195
  .pipe(
159
196
  reduce((bins, feature) => {
160
197
  const cigar = feature.get('CIGAR')
@@ -262,30 +299,61 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
262
299
 
263
300
  // normal SNP based coloring
264
301
  else {
265
- const mismatches = feature.get('mismatches')
266
- for (let i = 0; i < mismatches?.length; i++) {
267
- const mismatch = mismatches[i] as Mismatch
268
- const mstart = fstart + mismatch.start
269
- for (let j = mstart; j < mstart + mismatchLen(mismatch); j++) {
270
- const epos = j - region.start
271
- if (epos >= 0 && epos < bins.length) {
272
- const bin = bins[epos]
273
- const { base, type } = mismatch
274
- const interbase = isInterbase(type)
275
- if (!interbase) {
276
- dec(bin, fstrand, 'ref', 'ref')
277
- } else {
278
- inc(bin, fstrand, 'noncov', type)
279
- }
302
+ const mismatches = feature.get('mismatches') as
303
+ | Mismatch[]
304
+ | undefined
305
+
306
+ if (mismatches) {
307
+ for (let i = 0; i < mismatches.length; i++) {
308
+ const mismatch = mismatches[i]
309
+ const mstart = fstart + mismatch.start
310
+ for (let j = mstart; j < mstart + mismatchLen(mismatch); j++) {
311
+ const epos = j - region.start
312
+ if (epos >= 0 && epos < bins.length) {
313
+ const bin = bins[epos]
314
+ const { base, type } = mismatch
315
+ const interbase = isInterbase(type)
316
+ if (!interbase) {
317
+ dec(bin, fstrand, 'ref', 'ref')
318
+ } else {
319
+ inc(bin, fstrand, 'noncov', type)
320
+ }
280
321
 
281
- if (type === 'deletion' || type === 'skip') {
282
- inc(bin, fstrand, 'delskips', type)
283
- bin.total--
284
- } else if (!interbase) {
285
- inc(bin, fstrand, 'cov', base)
322
+ if (type === 'deletion' || type === 'skip') {
323
+ inc(bin, fstrand, 'delskips', type)
324
+ bin.total--
325
+ } else if (!interbase) {
326
+ inc(bin, fstrand, 'cov', base)
327
+ }
286
328
  }
287
329
  }
288
330
  }
331
+
332
+ mismatches
333
+ .filter(mismatch => mismatch.type === 'skip')
334
+ .forEach(mismatch => {
335
+ const mstart = feature.get('start') + mismatch.start
336
+ const start = mstart
337
+ const end = mstart + mismatch.length
338
+ const strand = feature.get('strand')
339
+ const hash = `${start}_${end}_${strand}`
340
+ if (!skipmap[hash]) {
341
+ skipmap[hash] = {
342
+ feature: feature,
343
+ start,
344
+ end,
345
+ strand,
346
+ xs:
347
+ feature.get('xs') ||
348
+ feature.get('ts') ||
349
+ feature.get('tags').XS ||
350
+ feature.get('tags').TS,
351
+ score: 1,
352
+ }
353
+ } else {
354
+ skipmap[hash].score++
355
+ }
356
+ })
289
357
  }
290
358
  }
291
359
 
@@ -293,6 +361,8 @@ export default class SNPCoverageAdapter extends BaseFeatureDataAdapter {
293
361
  }, initBins),
294
362
  )
295
363
  .toPromise()
364
+
365
+ return { bins, skipmap }
296
366
  }
297
367
  }
298
368
 
@@ -1,11 +1,23 @@
1
1
  import PluginManager from '@jbrowse/core/PluginManager'
2
+ import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType'
2
3
  import configSchemaFactory from './configSchema'
3
4
  import { capabilities } from './SNPCoverageAdapter'
4
5
 
5
6
  export default (pluginManager: PluginManager) => {
6
- return {
7
- getAdapterClass: () => import('./SNPCoverageAdapter').then(r => r.default),
8
- configSchema: configSchemaFactory(pluginManager),
9
- adapterCapabilities: capabilities,
10
- }
7
+ pluginManager.addAdapterType(
8
+ () =>
9
+ new AdapterType({
10
+ name: 'SNPCoverageAdapter',
11
+ adapterMetadata: {
12
+ category: null,
13
+ displayName: null,
14
+ hiddenFromGUI: true,
15
+ description: null,
16
+ },
17
+ getAdapterClass: () =>
18
+ import('./SNPCoverageAdapter').then(r => r.default),
19
+ configSchema: configSchemaFactory(pluginManager),
20
+ adapterCapabilities: capabilities,
21
+ }),
22
+ )
11
23
  }
@@ -2,6 +2,7 @@ import { createJBrowseTheme } from '@jbrowse/core/ui'
2
2
  import { featureSpanPx } from '@jbrowse/core/util'
3
3
  import { Feature } from '@jbrowse/core/util/simpleFeature'
4
4
  import { readConfObject } from '@jbrowse/core/configuration'
5
+ import { bpSpanPx } from '@jbrowse/core/util'
5
6
  import { RenderArgsDeserialized as FeatureRenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/FeatureRendererType'
6
7
  import {
7
8
  getOrigin,
@@ -26,6 +27,16 @@ export interface RenderArgsDeserializedWithFeatures
26
27
  modificationTagMap: Record<string, string>
27
28
  }
28
29
 
30
+ type Counts = {
31
+ [key: string]: { total: number; strands: { [key: string]: number } }
32
+ }
33
+
34
+ interface SNPInfo {
35
+ cov: Counts
36
+ noncov: Counts
37
+ total: number
38
+ }
39
+
29
40
  export default class SNPCoverageRenderer extends WiggleBaseRenderer {
30
41
  // note: the snps are drawn on linear scale even if the data is drawn in log
31
42
  // scape hence the two different scales being used
@@ -37,13 +48,13 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
37
48
  features,
38
49
  regions,
39
50
  bpPerPx,
51
+ displayCrossHatches,
52
+ modificationTagMap,
40
53
  scaleOpts,
41
54
  height: unadjustedHeight,
42
55
  theme: configTheme,
43
56
  config: cfg,
44
- displayCrossHatches,
45
57
  ticks: { values },
46
- modificationTagMap,
47
58
  } = props
48
59
  const theme = createJBrowseTheme(configTheme)
49
60
  const [region] = regions
@@ -63,6 +74,7 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
63
74
 
64
75
  const indicatorThreshold = readConfObject(cfg, 'indicatorThreshold')
65
76
  const drawInterbaseCounts = readConfObject(cfg, 'drawInterbaseCounts')
77
+ const drawArcs = readConfObject(cfg, 'drawArcs')
66
78
  const drawIndicators = readConfObject(cfg, 'drawIndicators')
67
79
 
68
80
  // get the y coordinate that we are plotting at, this can be log scale
@@ -87,15 +99,19 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
87
99
  ref: 'lightgrey',
88
100
  }
89
101
 
102
+ const feats = [...features.values()]
103
+ const coverage = feats.filter(f => f.get('type') !== 'skip')
104
+ const skips = feats.filter(f => f.get('type') === 'skip')
105
+
90
106
  // Use two pass rendering, which helps in visualizing the SNPs at higher
91
107
  // bpPerPx First pass: draw the gray background
92
108
  ctx.fillStyle = colorForBase.total
93
- for (const feature of features.values()) {
109
+ coverage.forEach(feature => {
94
110
  const [leftPx, rightPx] = featureSpanPx(feature, region, bpPerPx)
95
111
  const w = rightPx - leftPx + 0.3
96
112
  const score = feature.get('score') as number
97
113
  ctx.fillRect(leftPx, toY(score), w, toHeight(score))
98
- }
114
+ })
99
115
  ctx.fillStyle = 'grey'
100
116
  ctx.beginPath()
101
117
  ctx.lineTo(0, 0)
@@ -105,16 +121,10 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
105
121
  // Second pass: draw the SNP data, and add a minimum feature width of 1px
106
122
  // which can be wider than the actual bpPerPx This reduces overdrawing of
107
123
  // the grey background over the SNPs
108
- for (const feature of features.values()) {
124
+ coverage.forEach(feature => {
109
125
  const [leftPx, rightPx] = featureSpanPx(feature, region, bpPerPx)
110
- type Counts = {
111
- [key: string]: { total: number; strands: { [key: string]: number } }
112
- }
113
- const snpinfo = feature.get('snpinfo') as {
114
- cov: Counts
115
- noncov: Counts
116
- total: number
117
- }
126
+
127
+ const snpinfo = feature.get('snpinfo') as SNPInfo
118
128
  const w = Math.max(rightPx - leftPx + 0.3, 1)
119
129
  const totalScore = snpinfo.total
120
130
 
@@ -175,6 +185,43 @@ export default class SNPCoverageRenderer extends WiggleBaseRenderer {
175
185
  ctx.fill()
176
186
  }
177
187
  }
188
+ })
189
+
190
+ ctx.globalAlpha = 0.7
191
+
192
+ if (drawArcs) {
193
+ skips.forEach(f => {
194
+ const [left, right] = bpSpanPx(
195
+ f.get('start'),
196
+ f.get('end'),
197
+ region,
198
+ bpPerPx,
199
+ )
200
+
201
+ ctx.beginPath()
202
+ const str = f.get('strand') as number
203
+ const xs = f.get('xs') as string
204
+ const pos = 'rgb(255,200,200)'
205
+ const neg = 'rgb(200,200,255)'
206
+ const neutral = 'rgb(200,200,200)'
207
+
208
+ if (xs === '+') {
209
+ ctx.strokeStyle = pos
210
+ } else if (xs === '-') {
211
+ ctx.strokeStyle = neg
212
+ } else if (str === 1) {
213
+ ctx.strokeStyle = pos
214
+ } else if (str === -1) {
215
+ ctx.strokeStyle = neg
216
+ } else {
217
+ ctx.strokeStyle = neutral
218
+ }
219
+
220
+ ctx.lineWidth = Math.log(f.get('score') + 1)
221
+ ctx.moveTo(left, height - offset * 2)
222
+ ctx.bezierCurveTo(left, 0, right, 0, right, height - offset * 2)
223
+ ctx.stroke()
224
+ })
178
225
  }
179
226
 
180
227
  if (displayCrossHatches) {
@@ -14,6 +14,11 @@ export default ConfigurationSchema(
14
14
  'the proportion of reads containing a insertion/clip indicator',
15
15
  defaultValue: 0.3,
16
16
  },
17
+ drawArcs: {
18
+ type: 'boolean',
19
+ description: 'Draw sashimi-style arcs for intron features',
20
+ defaultValue: true,
21
+ },
17
22
  drawInterbaseCounts: {
18
23
  type: 'boolean',
19
24
  description:
@@ -0,0 +1,24 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+ import ConfigSchema from './configSchema'
3
+ import PluginManager from '@jbrowse/core/PluginManager'
4
+ import SNPCoverageRenderer from './SNPCoverageRenderer'
5
+
6
+ import { WiggleRendering } from '@jbrowse/plugin-wiggle'
7
+
8
+ export const configSchema = ConfigurationSchema(
9
+ 'SNPCoverageRenderer',
10
+ {},
11
+ { baseConfiguration: ConfigSchema, explicitlyTyped: true },
12
+ )
13
+
14
+ export default function register(pluginManager: PluginManager) {
15
+ pluginManager.addRendererType(
16
+ () =>
17
+ new SNPCoverageRenderer({
18
+ name: 'SNPCoverageRenderer',
19
+ ReactComponent: WiggleRendering,
20
+ configSchema,
21
+ pluginManager,
22
+ }),
23
+ )
24
+ }