@jbrowse/plugin-variants 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.
- package/dist/ChordVariantDisplay/index.d.ts +1 -1
- package/dist/VcfAdapter/VcfAdapter.d.ts +12 -9
- package/dist/VcfTabixAdapter/VcfTabixAdapter.d.ts +2 -2
- package/dist/plugin-variants.cjs.development.js +230 -186
- package/dist/plugin-variants.cjs.development.js.map +1 -1
- package/dist/plugin-variants.cjs.production.min.js +1 -1
- package/dist/plugin-variants.cjs.production.min.js.map +1 -1
- package/dist/plugin-variants.esm.js +231 -187
- package/dist/plugin-variants.esm.js.map +1 -1
- package/package.json +7 -7
- package/src/LinearVariantDisplay/configSchema.test.js +2 -13
- package/src/VariantFeatureWidget/VariantFeatureWidget.tsx +1 -1
- package/src/VariantFeatureWidget/__snapshots__/VariantFeatureWidget.test.js.snap +1 -0
- package/src/VcfAdapter/VcfAdapter.ts +62 -78
- package/src/VcfAdapter/__snapshots__/VcfAdapter.test.ts.snap +5 -5
- package/src/VcfTabixAdapter/VcfTabixAdapter.ts +1 -1
- package/src/index.ts +67 -0
- package/src/declare.d.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-variants",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "JBrowse 2 variant adapters, tracks, etc.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"useSrc": "node ../../scripts/useSrc.js"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@flatten-js/interval-tree": "^1.0.
|
|
39
|
-
"@gmod/bgzf-filehandle": "^1.
|
|
40
|
-
"@gmod/tabix": "^1.5.
|
|
41
|
-
"@gmod/vcf": "^5.0.
|
|
38
|
+
"@flatten-js/interval-tree": "^1.0.15",
|
|
39
|
+
"@gmod/bgzf-filehandle": "^1.4.2",
|
|
40
|
+
"@gmod/tabix": "^1.5.2",
|
|
41
|
+
"@gmod/vcf": "^5.0.4",
|
|
42
42
|
"@material-ui/icons": "^4.11.2",
|
|
43
43
|
"@mui/x-data-grid": "^4.0.1",
|
|
44
|
-
"generic-filehandle": "^2.2.
|
|
44
|
+
"generic-filehandle": "^2.2.2"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@jbrowse/core": "^1.0.0",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"access": "public"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "0c398214590969168694b4ed8e20b595178b9efd"
|
|
62
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
|
|
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
|
|
|
@@ -225,7 +225,7 @@ function VariantFeatureDetails(props: any) {
|
|
|
225
225
|
<BreakendPanel
|
|
226
226
|
feature={feat}
|
|
227
227
|
locStrings={feat.ALT.map(
|
|
228
|
-
(alt: string) => parseBreakend(alt)
|
|
228
|
+
(alt: string) => parseBreakend(alt)?.MatePosition || '',
|
|
229
229
|
)}
|
|
230
230
|
model={model}
|
|
231
231
|
/>
|
|
@@ -2,135 +2,119 @@ import {
|
|
|
2
2
|
BaseFeatureDataAdapter,
|
|
3
3
|
BaseOptions,
|
|
4
4
|
} from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
5
|
-
import {
|
|
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
10
|
import IntervalTree from '@flatten-js/interval-tree'
|
|
12
|
-
import VcfFeature from '../VcfTabixAdapter/VcfFeature'
|
|
13
|
-
import VCF from '@gmod/vcf'
|
|
14
11
|
import { unzip } from '@gmod/bgzf-filehandle'
|
|
15
|
-
import
|
|
16
|
-
import
|
|
12
|
+
import VCF from '@gmod/vcf'
|
|
13
|
+
import VcfFeature from '../VcfTabixAdapter/VcfFeature'
|
|
17
14
|
|
|
18
15
|
const readVcf = (f: string) => {
|
|
19
16
|
const lines = f.split('\n')
|
|
20
17
|
const header: string[] = []
|
|
21
|
-
const refNames: string[] = []
|
|
22
18
|
const rest: string[] = []
|
|
23
19
|
lines.forEach(line => {
|
|
24
|
-
if (line.startsWith('
|
|
25
|
-
refNames.push(line.split('##contig=<ID=')[1].split(',')[0])
|
|
26
|
-
} else if (line.startsWith('#')) {
|
|
20
|
+
if (line.startsWith('#')) {
|
|
27
21
|
header.push(line)
|
|
28
22
|
} else if (line) {
|
|
29
23
|
rest.push(line)
|
|
30
24
|
}
|
|
31
25
|
})
|
|
32
|
-
return { header: header.join('\n'), lines: rest
|
|
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
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
export default class VcfAdapter extends BaseFeatureDataAdapter {
|
|
36
34
|
public static capabilities = ['getFeatures', 'getRefNames']
|
|
37
35
|
|
|
38
|
-
protected vcfFeatures?: Promise<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
getSubAdapter?: getSubAdapterType,
|
|
43
|
-
pluginManager?: PluginManager,
|
|
44
|
-
) {
|
|
45
|
-
super(config, getSubAdapter, pluginManager)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private async decodeFileContents() {
|
|
49
|
-
const vcfLocation = readConfObject(
|
|
50
|
-
this.config,
|
|
51
|
-
'vcfLocation',
|
|
52
|
-
) as FileLocation
|
|
53
|
-
|
|
54
|
-
let fileContents = await openLocation(
|
|
55
|
-
vcfLocation,
|
|
56
|
-
this.pluginManager,
|
|
57
|
-
).readFile()
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
typeof fileContents[0] === 'number' &&
|
|
61
|
-
fileContents[0] === 31 &&
|
|
62
|
-
typeof fileContents[1] === 'number' &&
|
|
63
|
-
fileContents[1] === 139 &&
|
|
64
|
-
typeof fileContents[2] === 'number' &&
|
|
65
|
-
fileContents[2] === 8
|
|
66
|
-
) {
|
|
67
|
-
fileContents = new TextDecoder().decode(await unzip(fileContents))
|
|
68
|
-
} else {
|
|
69
|
-
fileContents = fileContents.toString()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return readVcf(fileContents)
|
|
73
|
-
}
|
|
36
|
+
protected vcfFeatures?: Promise<{
|
|
37
|
+
header: string
|
|
38
|
+
intervalTree: Record<string, IntervalTree>
|
|
39
|
+
}>
|
|
74
40
|
|
|
75
41
|
public async getHeader() {
|
|
76
|
-
const { header } = await this.
|
|
42
|
+
const { header } = await this.setup()
|
|
77
43
|
return header
|
|
78
44
|
}
|
|
79
45
|
|
|
80
46
|
async getMetadata() {
|
|
81
|
-
const { header } = await this.
|
|
47
|
+
const { header } = await this.setup()
|
|
82
48
|
const parser = new VCF({ header: header })
|
|
83
49
|
return parser.getMetadata()
|
|
84
50
|
}
|
|
85
51
|
|
|
86
|
-
|
|
87
|
-
|
|
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()
|
|
58
|
+
|
|
59
|
+
const buf = isGzip(buffer) ? await unzip(buffer) : buffer
|
|
88
60
|
|
|
89
|
-
|
|
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
|
+
}
|
|
90
65
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
66
|
+
const str = new TextDecoder().decode(buf)
|
|
67
|
+
const { header, lines } = readVcf(str)
|
|
68
|
+
|
|
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 }
|
|
96
75
|
})
|
|
97
|
-
|
|
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 }
|
|
98
86
|
}
|
|
99
87
|
|
|
100
88
|
public async setup() {
|
|
101
89
|
if (!this.vcfFeatures) {
|
|
102
|
-
this.vcfFeatures = this.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const key = obj.get('refName')
|
|
106
|
-
if (!acc[key]) {
|
|
107
|
-
acc[key] = new IntervalTree()
|
|
108
|
-
}
|
|
109
|
-
acc[key].insert([obj.get('start'), obj.get('end')], obj)
|
|
110
|
-
return acc
|
|
111
|
-
},
|
|
112
|
-
{},
|
|
113
|
-
)
|
|
90
|
+
this.vcfFeatures = this.setupP().catch(e => {
|
|
91
|
+
this.vcfFeatures = undefined
|
|
92
|
+
throw e
|
|
114
93
|
})
|
|
115
94
|
}
|
|
116
95
|
return this.vcfFeatures
|
|
117
96
|
}
|
|
118
97
|
|
|
119
98
|
public async getRefNames(_: BaseOptions = {}) {
|
|
120
|
-
const {
|
|
121
|
-
return
|
|
99
|
+
const { intervalTree } = await this.setup()
|
|
100
|
+
return Object.keys(intervalTree)
|
|
122
101
|
}
|
|
123
102
|
|
|
124
103
|
public getFeatures(region: Region, opts: BaseOptions = {}) {
|
|
125
104
|
return ObservableCreate<Feature>(async observer => {
|
|
126
105
|
try {
|
|
127
106
|
const { start, end, refName } = region
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
)
|
|
134
118
|
observer.complete()
|
|
135
119
|
} catch (e) {
|
|
136
120
|
observer.error(e)
|
|
@@ -66,7 +66,7 @@ Array [
|
|
|
66
66
|
},
|
|
67
67
|
"start": 276,
|
|
68
68
|
"type": "SNV",
|
|
69
|
-
"uniqueId": "test-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
322
|
+
"uniqueId": "test-4",
|
|
323
323
|
},
|
|
324
324
|
]
|
|
325
325
|
`;
|
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
|
)
|
package/src/declare.d.ts
DELETED