@jbrowse/plugin-variants 1.4.3 → 1.5.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-variants",
3
- "version": "1.4.3",
3
+ "version": "1.5.2",
4
4
  "description": "JBrowse 2 variant adapters, tracks, etc.",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -35,12 +35,13 @@
35
35
  "useSrc": "node ../../scripts/useSrc.js"
36
36
  },
37
37
  "dependencies": {
38
- "@gmod/bgzf-filehandle": "^1.3.4",
39
- "@gmod/tabix": "^1.5.0",
40
- "@gmod/vcf": "^5.0.0",
41
- "@material-ui/data-grid": "^4.0.0-alpha.37",
38
+ "@flatten-js/interval-tree": "^1.0.14",
39
+ "@gmod/bgzf-filehandle": "^1.4.1",
40
+ "@gmod/tabix": "^1.5.2",
41
+ "@gmod/vcf": "^5.0.3",
42
42
  "@material-ui/icons": "^4.11.2",
43
- "generic-filehandle": "^2.0.0"
43
+ "@mui/x-data-grid": "^4.0.1",
44
+ "generic-filehandle": "^2.2.2"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "@jbrowse/core": "^1.0.0",
@@ -57,5 +58,5 @@
57
58
  "publishConfig": {
58
59
  "access": "public"
59
60
  },
60
- "gitHead": "99db07eaad1b00387768ba1e1f31f917c3aad9cf"
61
+ "gitHead": "94fdfbc34787ab8f12a87e00038da74b247b42fa"
61
62
  }
@@ -1,10 +1,7 @@
1
1
  import BoxRendererType from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
2
2
  import Plugin from '@jbrowse/core/Plugin'
3
3
  import PluginManager from '@jbrowse/core/PluginManager'
4
- import PileupRenderer, {
5
- configSchema as pileupRendererConfigSchema,
6
- ReactComponent as PileupRendererReactComponent,
7
- } from '@jbrowse/plugin-alignments/src/PileupRenderer'
4
+ import PileupRenderer from '@jbrowse/plugin-alignments/src/PileupRenderer'
8
5
  import {
9
6
  configSchema as svgFeatureRendererConfigSchema,
10
7
  ReactComponent as SvgFeatureRendererReactComponent,
@@ -22,15 +19,7 @@ afterEach(() => {
22
19
 
23
20
  class PileupRendererPlugin extends Plugin {
24
21
  install(pluginManager) {
25
- pluginManager.addRendererType(
26
- () =>
27
- new PileupRenderer({
28
- name: 'PileupRenderer',
29
- ReactComponent: PileupRendererReactComponent,
30
- configSchema: pileupRendererConfigSchema,
31
- pluginManager,
32
- }),
33
- )
22
+ PileupRenderer(pluginManager)
34
23
  }
35
24
  }
36
25
 
@@ -18,4 +18,5 @@ export function LinearVariantDisplayConfigFactory(
18
18
  export type LinearVariantDisplayConfigModel = ReturnType<
19
19
  typeof LinearVariantDisplayConfigFactory
20
20
  >
21
- export type LinearVariantDisplayConfig = Instance<LinearVariantDisplayConfigModel>
21
+ export type LinearVariantDisplayConfig =
22
+ Instance<LinearVariantDisplayConfigModel>
@@ -37,7 +37,7 @@ const Chord = observer(function Chord({
37
37
  let endPosition
38
38
  let endBlock
39
39
  const alt = feature.get('ALT')?.[0]
40
- const bnd = parseBreakend(alt)
40
+ const bnd = alt && parseBreakend(alt)
41
41
  if (bnd) {
42
42
  // VCF BND
43
43
  const matePosition = bnd.MatePosition.split(':')
@@ -92,6 +92,7 @@ const Chord = observer(function Chord({
92
92
  onMouseOut={evt => {
93
93
  if (!selected) {
94
94
  evt.target.style.stroke = strokeColor
95
+ evt.target.style.strokeWidth = 1
95
96
  }
96
97
  }}
97
98
  />
@@ -12,7 +12,7 @@ import {
12
12
  import SimpleFeature, {
13
13
  SimpleFeatureSerialized,
14
14
  } from '@jbrowse/core/util/simpleFeature'
15
- import { DataGrid } from '@material-ui/data-grid'
15
+ import { DataGrid } from '@mui/x-data-grid'
16
16
  import { observer } from 'mobx-react'
17
17
  import { getSession } from '@jbrowse/core/util'
18
18
  import { getEnv } from 'mobx-state-tree'
@@ -204,14 +204,10 @@ function VariantFeatureDetails(props: any) {
204
204
  const { samples, ...rest } = feat
205
205
  const basicDescriptions = {
206
206
  CHROM: 'chromosome: An identifier from the reference genome',
207
- POS:
208
- 'position: The reference position, with the 1st base having position 1',
209
- ID:
210
- 'identifier: Semi-colon separated list of unique identifiers where available',
211
- REF:
212
- 'reference base(s): Each base must be one of A,C,G,T,N (case insensitive).',
213
- ALT:
214
- 'alternate base(s): Comma-separated list of alternate non-reference alleles',
207
+ POS: 'position: The reference position, with the 1st base having position 1',
208
+ ID: 'identifier: Semi-colon separated list of unique identifiers where available',
209
+ REF: 'reference base(s): Each base must be one of A,C,G,T,N (case insensitive).',
210
+ ALT: 'alternate base(s): Comma-separated list of alternate non-reference alleles',
215
211
  QUAL: 'quality: Phred-scaled quality score for the assertion made in ALT',
216
212
  FILTER:
217
213
  'filter status: PASS if this position has passed all filters, otherwise a semicolon-separated list of codes for filters that fail',
@@ -9,6 +9,7 @@ test('adapter can fetch variants from volvox.vcf', async () => {
9
9
  configSchema.create({
10
10
  vcfLocation: {
11
11
  localPath: require.resolve('./test_data/volvox.filtered.vcf'),
12
+ locationType: 'LocalPathLocation',
12
13
  },
13
14
  }),
14
15
  )
@@ -2,116 +2,123 @@ import {
2
2
  BaseFeatureDataAdapter,
3
3
  BaseOptions,
4
4
  } from '@jbrowse/core/data_adapters/BaseAdapter'
5
- import { FileLocation, Region } from '@jbrowse/core/util/types'
5
+ import { Region } from '@jbrowse/core/util/types'
6
6
  import { openLocation } from '@jbrowse/core/util/io'
7
7
  import { ObservableCreate } from '@jbrowse/core/util/rxjs'
8
8
  import { Feature } from '@jbrowse/core/util/simpleFeature'
9
9
  import { readConfObject } from '@jbrowse/core/configuration'
10
- import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema'
11
- import VcfFeature from '../VcfTabixAdapter/VcfFeature'
12
- import VCF from '@gmod/vcf'
10
+ import IntervalTree from '@flatten-js/interval-tree'
13
11
  import { unzip } from '@gmod/bgzf-filehandle'
12
+ import VCF from '@gmod/vcf'
13
+ import VcfFeature from '../VcfTabixAdapter/VcfFeature'
14
14
 
15
15
  const readVcf = (f: string) => {
16
16
  const lines = f.split('\n')
17
17
  const header: string[] = []
18
- const refNames: string[] = []
19
18
  const rest: string[] = []
20
19
  lines.forEach(line => {
21
- if (line.startsWith('##contig')) {
22
- refNames.push(line.split('##contig=<ID=')[1].split(',')[0])
23
- } else if (line.startsWith('#')) {
20
+ if (line.startsWith('#')) {
24
21
  header.push(line)
25
22
  } else if (line) {
26
23
  rest.push(line)
27
24
  }
28
25
  })
29
- return { header: header.join('\n'), lines: rest, refNames }
26
+ return { header: header.join('\n'), lines: rest }
27
+ }
28
+
29
+ function isGzip(buf: Buffer) {
30
+ return buf[0] === 31 && buf[1] === 139 && buf[2] === 8
30
31
  }
31
32
 
32
33
  export default class VcfAdapter extends BaseFeatureDataAdapter {
33
34
  public static capabilities = ['getFeatures', 'getRefNames']
34
35
 
35
- private setupP?: Promise<Feature[]>
36
-
37
- public constructor(config: AnyConfigurationModel) {
38
- super(config)
39
- }
40
-
41
- private async decodeFileContents() {
42
- const vcfLocation = readConfObject(
43
- this.config,
44
- 'vcfLocation',
45
- ) as FileLocation
46
-
47
- let fileContents = await openLocation(vcfLocation).readFile()
48
-
49
- if (
50
- typeof fileContents[0] === 'number' &&
51
- fileContents[0] === 31 &&
52
- typeof fileContents[1] === 'number' &&
53
- fileContents[1] === 139 &&
54
- typeof fileContents[2] === 'number' &&
55
- fileContents[2] === 8
56
- ) {
57
- fileContents = new TextDecoder().decode(await unzip(fileContents))
58
- } else {
59
- fileContents = fileContents.toString()
60
- }
61
-
62
- return readVcf(fileContents)
63
- }
36
+ protected vcfFeatures?: Promise<{
37
+ header: string
38
+ intervalTree: Record<string, IntervalTree>
39
+ }>
64
40
 
65
41
  public async getHeader() {
66
- const { header } = await this.decodeFileContents()
42
+ const { header } = await this.setup()
67
43
  return header
68
44
  }
69
45
 
70
46
  async getMetadata() {
71
- const { header } = await this.decodeFileContents()
47
+ const { header } = await this.setup()
72
48
  const parser = new VCF({ header: header })
73
49
  return parser.getMetadata()
74
50
  }
75
51
 
76
- public async getLines() {
77
- const { header, lines } = await this.decodeFileContents()
52
+ // converts lines into an interval tree
53
+ public async setupP() {
54
+ const buffer = await openLocation(
55
+ readConfObject(this.config, 'vcfLocation'),
56
+ this.pluginManager,
57
+ ).readFile()
78
58
 
79
- const parser = new VCF({ header: header })
59
+ const buf = isGzip(buffer as Buffer) ? await unzip(buffer) : buffer
60
+
61
+ // 512MB max chrome string length is 512MB
62
+ if (buf.length > 536_870_888) {
63
+ throw new Error('Data exceeds maximum string length (512MB)')
64
+ }
65
+
66
+ const str = new TextDecoder().decode(buf)
67
+ const { header, lines } = readVcf(str)
80
68
 
81
- return lines.map((line, index) => {
82
- return new VcfFeature({
83
- variant: parser.parseLine(line),
84
- parser,
85
- id: `${this.id}-vcf-${index}`,
69
+ const intervalTree = lines
70
+ .map((line, id) => {
71
+ const [refName, startP, , ref, , , , info] = line.split('\t')
72
+ const start = +startP - 1
73
+ const end = +(info.match(/END=(\d+)/)?.[1].trim() || start + ref.length)
74
+ return { line, refName, start, end, id }
86
75
  })
87
- })
76
+ .reduce((acc, obj) => {
77
+ const key = obj.refName
78
+ if (!acc[key]) {
79
+ acc[key] = new IntervalTree()
80
+ }
81
+ acc[key].insert([obj.start, obj.end], obj)
82
+ return acc
83
+ }, {} as Record<string, IntervalTree>)
84
+
85
+ return { header, intervalTree }
88
86
  }
89
87
 
90
88
  public async setup() {
91
- if (!this.setupP) {
92
- this.setupP = this.getLines()
89
+ if (!this.vcfFeatures) {
90
+ this.vcfFeatures = this.setupP().catch(e => {
91
+ this.vcfFeatures = undefined
92
+ throw e
93
+ })
93
94
  }
94
- return this.setupP
95
+ return this.vcfFeatures
95
96
  }
96
97
 
97
98
  public async getRefNames(_: BaseOptions = {}) {
98
- const { refNames } = await this.decodeFileContents()
99
- return refNames
99
+ const { intervalTree } = await this.setup()
100
+ return Object.keys(intervalTree)
100
101
  }
101
102
 
102
103
  public getFeatures(region: Region, opts: BaseOptions = {}) {
103
104
  return ObservableCreate<Feature>(async observer => {
104
- const feats = await this.setup()
105
- feats.forEach(f => {
106
- if (
107
- f.get('refName') === region.refName &&
108
- f.get('end') > region.start &&
109
- f.get('start') < region.end
110
- ) {
111
- observer.next(f)
112
- }
113
- })
114
- observer.complete()
105
+ try {
106
+ const { start, end, refName } = region
107
+ const { header, intervalTree } = await this.setup()
108
+ const parser = new VCF({ header: header })
109
+ intervalTree[refName]?.search([start, end]).forEach(f =>
110
+ observer.next(
111
+ new VcfFeature({
112
+ variant: parser.parseLine(f.line),
113
+ parser,
114
+ id: `${this.id}-${f.id}`,
115
+ }),
116
+ ),
117
+ )
118
+ observer.complete()
119
+ } catch (e) {
120
+ observer.error(e)
121
+ }
115
122
  }, opts.signal)
116
123
  }
117
124
 
@@ -66,7 +66,7 @@ Array [
66
66
  },
67
67
  "start": 276,
68
68
  "type": "SNV",
69
- "uniqueId": "test-vcf-0",
69
+ "uniqueId": "test-0",
70
70
  },
71
71
  Object {
72
72
  "ALT": Array [
@@ -132,7 +132,7 @@ Array [
132
132
  },
133
133
  "start": 1693,
134
134
  "type": "SNV",
135
- "uniqueId": "test-vcf-1",
135
+ "uniqueId": "test-1",
136
136
  },
137
137
  Object {
138
138
  "ALT": Array [
@@ -198,7 +198,7 @@ Array [
198
198
  },
199
199
  "start": 2643,
200
200
  "type": "SNV",
201
- "uniqueId": "test-vcf-2",
201
+ "uniqueId": "test-2",
202
202
  },
203
203
  Object {
204
204
  "ALT": Array [
@@ -258,7 +258,7 @@ Array [
258
258
  },
259
259
  "start": 3212,
260
260
  "type": "SNV",
261
- "uniqueId": "test-vcf-3",
261
+ "uniqueId": "test-3",
262
262
  },
263
263
  Object {
264
264
  "ALT": Array [
@@ -319,7 +319,7 @@ Array [
319
319
  },
320
320
  "start": 3857,
321
321
  "type": "deletion",
322
- "uniqueId": "test-vcf-4",
322
+ "uniqueId": "test-4",
323
323
  },
324
324
  ]
325
325
  `;
@@ -5,7 +5,7 @@ export default ConfigurationSchema(
5
5
  {
6
6
  vcfLocation: {
7
7
  type: 'fileLocation',
8
- defaultValue: { uri: '/path/to/my.vcf' },
8
+ defaultValue: { uri: '/path/to/my.vcf', locationType: 'UriLocation' },
9
9
  },
10
10
  },
11
11
  { explicitlyTyped: true },
@@ -7,11 +7,13 @@ test('adapter can fetch variants from volvox.vcf.gz', async () => {
7
7
  configSchema.create({
8
8
  vcfGzLocation: {
9
9
  localPath: require.resolve('./test_data/volvox.filtered.vcf.gz'),
10
+ locationType: 'LocalPathLocation',
10
11
  },
11
12
  index: {
12
13
  indexType: 'TBI',
13
14
  location: {
14
15
  localPath: require.resolve('./test_data/volvox.filtered.vcf.gz.tbi'),
16
+ locationType: 'LocalPathLocation',
15
17
  },
16
18
  },
17
19
  }),
@@ -21,11 +23,13 @@ test('adapter can fetch variants from volvox.vcf.gz', async () => {
21
23
  configSchema.create({
22
24
  vcfGzLocation: {
23
25
  localPath: require.resolve('./test_data/volvox.filtered.vcf.gz'),
26
+ locationType: 'LocalPathLocation',
24
27
  },
25
28
  index: {
26
29
  indexType: 'CSI',
27
30
  location: {
28
31
  localPath: require.resolve('./test_data/volvox.filtered.vcf.gz.csi'),
32
+ locationType: 'LocalPathLocation',
29
33
  },
30
34
  },
31
35
  }),
@@ -30,12 +30,19 @@ export default class extends BaseFeatureDataAdapter {
30
30
  const location = readConfObject(this.config, ['index', 'location'])
31
31
  const indexType = readConfObject(this.config, ['index', 'indexType'])
32
32
 
33
- const filehandle = openLocation(vcfGzLocation as FileLocation)
33
+ const filehandle = openLocation(
34
+ vcfGzLocation as FileLocation,
35
+ this.pluginManager,
36
+ )
34
37
  const isCSI = indexType === 'CSI'
35
38
  const vcf = new TabixIndexedFile({
36
39
  filehandle,
37
- csiFilehandle: isCSI ? openLocation(location) : undefined,
38
- tbiFilehandle: !isCSI ? openLocation(location) : undefined,
40
+ csiFilehandle: isCSI
41
+ ? openLocation(location, this.pluginManager)
42
+ : undefined,
43
+ tbiFilehandle: !isCSI
44
+ ? openLocation(location, this.pluginManager)
45
+ : undefined,
39
46
  chunkCacheSize: 50 * 2 ** 20,
40
47
  chunkSizeLimit: 1000000000,
41
48
  })
@@ -6,7 +6,7 @@ export default ConfigurationSchema(
6
6
  {
7
7
  vcfGzLocation: {
8
8
  type: 'fileLocation',
9
- defaultValue: { uri: '/path/to/my.vcf.gz' },
9
+ defaultValue: { uri: '/path/to/my.vcf.gz', locationType: 'UriLocation' },
10
10
  },
11
11
  index: ConfigurationSchema('VcfIndex', {
12
12
  indexType: {
@@ -16,7 +16,10 @@ export default ConfigurationSchema(
16
16
  },
17
17
  location: {
18
18
  type: 'fileLocation',
19
- defaultValue: { uri: '/path/to/my.vcf.gz.tbi' },
19
+ defaultValue: {
20
+ uri: '/path/to/my.vcf.gz.tbi',
21
+ locationType: 'UriLocation',
22
+ },
20
23
  },
21
24
  }),
22
25
  },
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  createBaseTrackConfig,
7
7
  createBaseTrackModel,
8
8
  } from '@jbrowse/core/pluggableElementTypes/models'
9
+ import { FileLocation } from '@jbrowse/core/util/types'
9
10
  import TrackType from '@jbrowse/core/pluggableElementTypes/TrackType'
10
11
  import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType'
11
12
  import Plugin from '@jbrowse/core/Plugin'
@@ -23,6 +24,13 @@ import {
23
24
  } from './VariantFeatureWidget'
24
25
  import { configSchema as vcfTabixAdapterConfigSchema } from './VcfTabixAdapter'
25
26
  import { configSchema as vcfAdapterConfigSchema } from './VcfAdapter'
27
+ import {
28
+ makeIndex,
29
+ makeIndexType,
30
+ AdapterGuesser,
31
+ getFileName,
32
+ TrackTypeGuesser,
33
+ } from '@jbrowse/core/util/tracks'
26
34
 
27
35
  export default class VariantsPlugin extends Plugin {
28
36
  name = 'VariantsPlugin'
@@ -37,6 +45,43 @@ export default class VariantsPlugin extends Plugin {
37
45
  import('./VcfTabixAdapter/VcfTabixAdapter').then(r => r.default),
38
46
  }),
39
47
  )
48
+ pluginManager.addToExtensionPoint(
49
+ 'Core-guessAdapterForLocation',
50
+ (adapterGuesser: AdapterGuesser) => {
51
+ return (
52
+ file: FileLocation,
53
+ index?: FileLocation,
54
+ adapterHint?: string,
55
+ ) => {
56
+ const regexGuess = /\.vcf\.b?gz$/i
57
+ const adapterName = 'VcfTabixAdapter'
58
+ const fileName = getFileName(file)
59
+ const indexName = index && getFileName(index)
60
+ if (regexGuess.test(fileName) || adapterHint === adapterName) {
61
+ return {
62
+ type: adapterName,
63
+ vcfGzLocation: file,
64
+ index: {
65
+ location: index || makeIndex(file, '.tbi'),
66
+ indexType: makeIndexType(indexName, 'CSI', 'TBI'),
67
+ },
68
+ }
69
+ }
70
+ return adapterGuesser(file, index, adapterHint)
71
+ }
72
+ },
73
+ )
74
+ pluginManager.addToExtensionPoint(
75
+ 'Core-guessTrackTypeForLocation',
76
+ (trackTypeGuesser: TrackTypeGuesser) => {
77
+ return (adapterName: string) => {
78
+ if (adapterName === 'VcfTabixAdapter') {
79
+ return 'VariantTrack'
80
+ }
81
+ return trackTypeGuesser(adapterName)
82
+ }
83
+ },
84
+ )
40
85
 
41
86
  pluginManager.addAdapterType(
42
87
  () =>
@@ -48,6 +93,28 @@ export default class VariantsPlugin extends Plugin {
48
93
  }),
49
94
  )
50
95
 
96
+ pluginManager.addToExtensionPoint(
97
+ 'Core-guessAdapterForLocation',
98
+ (adapterGuesser: AdapterGuesser) => {
99
+ return (
100
+ file: FileLocation,
101
+ index?: FileLocation,
102
+ adapterHint?: string,
103
+ ) => {
104
+ const regexGuess = /\.vcf$/i
105
+ const adapterName = 'VcfAdapter'
106
+ const fileName = getFileName(file)
107
+ if (regexGuess.test(fileName) || adapterHint === adapterName) {
108
+ return {
109
+ type: adapterName,
110
+ vcfLocation: file,
111
+ }
112
+ }
113
+ return adapterGuesser(file, index, adapterHint)
114
+ }
115
+ },
116
+ )
117
+
51
118
  pluginManager.addRendererType(() =>
52
119
  pluginManager.jbrequire(StructuralVariantChordRendererFactory),
53
120
  )
@@ -73,9 +140,8 @@ export default class VariantsPlugin extends Plugin {
73
140
  )
74
141
 
75
142
  pluginManager.addDisplayType(() => {
76
- const configSchema = linearVariantDisplayConfigSchemaFactory(
77
- pluginManager,
78
- )
143
+ const configSchema =
144
+ linearVariantDisplayConfigSchemaFactory(pluginManager)
79
145
  return new DisplayType({
80
146
  name: 'LinearVariantDisplay',
81
147
  configSchema,