@jbrowse/plugin-variants 2.1.0 → 2.1.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/dist/ChordVariantDisplay/index.js +9 -9
- package/dist/ChordVariantDisplay/index.js.map +1 -1
- package/dist/ChordVariantDisplay/models/ChordVariantDisplay.js +23 -25
- package/dist/ChordVariantDisplay/models/ChordVariantDisplay.js.map +1 -1
- package/dist/LinearVariantDisplay/configSchema.js +3 -3
- package/dist/LinearVariantDisplay/configSchema.js.map +1 -1
- package/dist/LinearVariantDisplay/index.d.ts +3 -2
- package/dist/LinearVariantDisplay/index.js +17 -5
- package/dist/LinearVariantDisplay/index.js.map +1 -1
- package/dist/LinearVariantDisplay/model.js +24 -72
- package/dist/LinearVariantDisplay/model.js.map +1 -1
- package/dist/StructuralVariantChordRenderer/ReactComponent.js +57 -103
- package/dist/StructuralVariantChordRenderer/ReactComponent.js.map +1 -1
- package/dist/StructuralVariantChordRenderer/index.js +7 -7
- package/dist/StructuralVariantChordRenderer/index.js.map +1 -1
- package/dist/VariantFeatureWidget/AnnotGrid.d.ts +5 -0
- package/dist/VariantFeatureWidget/AnnotGrid.js +22 -0
- package/dist/VariantFeatureWidget/AnnotGrid.js.map +1 -0
- package/dist/VariantFeatureWidget/BreakendOptionDialog.js +23 -40
- package/dist/VariantFeatureWidget/BreakendOptionDialog.js.map +1 -1
- package/dist/VariantFeatureWidget/BreakendPanel.d.ts +7 -0
- package/dist/VariantFeatureWidget/BreakendPanel.js +82 -0
- package/dist/VariantFeatureWidget/BreakendPanel.js.map +1 -0
- package/dist/VariantFeatureWidget/VariantAnnPanel.d.ts +5 -0
- package/dist/VariantFeatureWidget/VariantAnnPanel.js +25 -0
- package/dist/VariantFeatureWidget/VariantAnnPanel.js.map +1 -0
- package/dist/VariantFeatureWidget/VariantCsqPanel.d.ts +5 -0
- package/dist/VariantFeatureWidget/VariantCsqPanel.js +25 -0
- package/dist/VariantFeatureWidget/VariantCsqPanel.js.map +1 -0
- package/dist/VariantFeatureWidget/VariantFeatureWidget.js +23 -184
- package/dist/VariantFeatureWidget/VariantFeatureWidget.js.map +1 -1
- package/dist/VariantFeatureWidget/VariantSampleGrid.d.ts +2 -0
- package/dist/VariantFeatureWidget/VariantSampleGrid.js +83 -0
- package/dist/VariantFeatureWidget/VariantSampleGrid.js.map +1 -0
- package/dist/VariantFeatureWidget/index.d.ts +2 -0
- package/dist/VariantFeatureWidget/index.js +41 -4
- package/dist/VariantFeatureWidget/index.js.map +1 -1
- package/dist/VariantTrack/index.d.ts +3 -0
- package/dist/VariantTrack/index.js +19 -0
- package/dist/VariantTrack/index.js.map +1 -0
- package/dist/VcfAdapter/VcfAdapter.js +85 -221
- package/dist/VcfAdapter/VcfAdapter.js.map +1 -1
- package/dist/VcfAdapter/configSchema.js +1 -1
- package/dist/VcfAdapter/configSchema.js.map +1 -1
- package/dist/VcfAdapter/index.d.ts +3 -1
- package/dist/VcfAdapter/index.js +32 -3
- package/dist/VcfAdapter/index.js.map +1 -1
- package/dist/VcfTabixAdapter/VcfFeature.js +75 -106
- package/dist/VcfTabixAdapter/VcfFeature.js.map +1 -1
- package/dist/VcfTabixAdapter/VcfTabixAdapter.js +88 -226
- package/dist/VcfTabixAdapter/VcfTabixAdapter.js.map +1 -1
- package/dist/VcfTabixAdapter/configSchema.js +2 -2
- package/dist/VcfTabixAdapter/configSchema.js.map +1 -1
- package/dist/VcfTabixAdapter/index.d.ts +3 -0
- package/dist/VcfTabixAdapter/index.js +34 -2
- package/dist/VcfTabixAdapter/index.js.map +1 -1
- package/dist/extensionPoints.d.ts +3 -0
- package/dist/extensionPoints.js +51 -0
- package/dist/extensionPoints.js.map +1 -0
- package/dist/index.js +24 -162
- package/dist/index.js.map +1 -1
- package/esm/LinearVariantDisplay/index.d.ts +3 -2
- package/esm/LinearVariantDisplay/index.js +17 -2
- package/esm/LinearVariantDisplay/index.js.map +1 -1
- package/esm/VariantFeatureWidget/AnnotGrid.d.ts +5 -0
- package/esm/VariantFeatureWidget/AnnotGrid.js +16 -0
- package/esm/VariantFeatureWidget/AnnotGrid.js.map +1 -0
- package/esm/VariantFeatureWidget/BreakendPanel.d.ts +7 -0
- package/esm/VariantFeatureWidget/BreakendPanel.js +53 -0
- package/esm/VariantFeatureWidget/BreakendPanel.js.map +1 -0
- package/esm/VariantFeatureWidget/VariantAnnPanel.d.ts +5 -0
- package/esm/VariantFeatureWidget/VariantAnnPanel.js +19 -0
- package/esm/VariantFeatureWidget/VariantAnnPanel.js.map +1 -0
- package/esm/VariantFeatureWidget/VariantCsqPanel.d.ts +5 -0
- package/esm/VariantFeatureWidget/VariantCsqPanel.js +19 -0
- package/esm/VariantFeatureWidget/VariantCsqPanel.js.map +1 -0
- package/esm/VariantFeatureWidget/VariantFeatureWidget.js +13 -104
- package/esm/VariantFeatureWidget/VariantFeatureWidget.js.map +1 -1
- package/esm/VariantFeatureWidget/VariantSampleGrid.d.ts +2 -0
- package/esm/VariantFeatureWidget/VariantSampleGrid.js +57 -0
- package/esm/VariantFeatureWidget/VariantSampleGrid.js.map +1 -0
- package/esm/VariantFeatureWidget/index.d.ts +2 -0
- package/esm/VariantFeatureWidget/index.js +11 -0
- package/esm/VariantFeatureWidget/index.js.map +1 -1
- package/esm/VariantTrack/index.d.ts +3 -0
- package/esm/VariantTrack/index.js +14 -0
- package/esm/VariantTrack/index.js.map +1 -0
- package/esm/VcfAdapter/VcfAdapter.js +9 -7
- package/esm/VcfAdapter/VcfAdapter.js.map +1 -1
- package/esm/VcfAdapter/index.d.ts +3 -1
- package/esm/VcfAdapter/index.js +9 -1
- package/esm/VcfAdapter/index.js.map +1 -1
- package/esm/VcfTabixAdapter/index.d.ts +3 -0
- package/esm/VcfTabixAdapter/index.js +9 -0
- package/esm/VcfTabixAdapter/index.js.map +1 -1
- package/esm/extensionPoints.d.ts +3 -0
- package/esm/extensionPoints.js +49 -0
- package/esm/extensionPoints.js.map +1 -0
- package/esm/index.js +12 -95
- package/esm/index.js.map +1 -1
- package/package.json +2 -3
- package/src/LinearVariantDisplay/index.ts +19 -2
- package/src/VariantFeatureWidget/AnnotGrid.tsx +28 -0
- package/src/VariantFeatureWidget/BreakendPanel.tsx +94 -0
- package/src/VariantFeatureWidget/VariantAnnPanel.tsx +31 -0
- package/src/VariantFeatureWidget/VariantCsqPanel.tsx +31 -0
- package/src/VariantFeatureWidget/VariantFeatureWidget.tsx +13 -198
- package/src/VariantFeatureWidget/VariantSampleGrid.tsx +105 -0
- package/src/VariantFeatureWidget/__snapshots__/VariantFeatureWidget.test.js.snap +6 -0
- package/src/VariantFeatureWidget/index.ts +15 -0
- package/src/VariantTrack/index.ts +26 -0
- package/src/VcfAdapter/VcfAdapter.ts +15 -16
- package/src/VcfAdapter/index.ts +14 -1
- package/src/VcfTabixAdapter/index.ts +15 -0
- package/src/__snapshots__/index.test.js.snap +4 -4
- package/src/extensionPoints.ts +74 -0
- package/src/index.ts +12 -155
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import { LinearVariantDisplayConfigFactory } from './configSchema'
|
|
3
|
+
import { BaseLinearDisplayComponent } from '@jbrowse/plugin-linear-genome-view'
|
|
4
|
+
import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType'
|
|
5
|
+
import stateModelFactory from './model'
|
|
6
|
+
|
|
7
|
+
export default (pluginManager: PluginManager) => {
|
|
8
|
+
pluginManager.addDisplayType(() => {
|
|
9
|
+
const configSchema = LinearVariantDisplayConfigFactory(pluginManager)
|
|
10
|
+
return new DisplayType({
|
|
11
|
+
name: 'LinearVariantDisplay',
|
|
12
|
+
configSchema,
|
|
13
|
+
stateModel: stateModelFactory(configSchema),
|
|
14
|
+
trackType: 'VariantTrack',
|
|
15
|
+
viewType: 'LinearGenomeView',
|
|
16
|
+
ReactComponent: BaseLinearDisplayComponent,
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { DataGrid } from '@mui/x-data-grid'
|
|
4
|
+
|
|
5
|
+
export default function VariantAnnotPanel({
|
|
6
|
+
rows,
|
|
7
|
+
columns,
|
|
8
|
+
}: {
|
|
9
|
+
rows: any
|
|
10
|
+
columns: any[]
|
|
11
|
+
}) {
|
|
12
|
+
const rowHeight = 25
|
|
13
|
+
const hideFooter = rows.length < 100
|
|
14
|
+
const headerHeight = 80
|
|
15
|
+
return rows.length ? (
|
|
16
|
+
<div
|
|
17
|
+
style={{
|
|
18
|
+
height:
|
|
19
|
+
Math.min(rows.length, 100) * rowHeight +
|
|
20
|
+
headerHeight +
|
|
21
|
+
(hideFooter ? 0 : 50),
|
|
22
|
+
width: '100%',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<DataGrid rowHeight={rowHeight} rows={rows} columns={columns} />
|
|
26
|
+
</div>
|
|
27
|
+
) : null
|
|
28
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
import { Link, Typography } from '@mui/material'
|
|
4
|
+
import SimpleFeature, {
|
|
5
|
+
SimpleFeatureSerialized,
|
|
6
|
+
} from '@jbrowse/core/util/simpleFeature'
|
|
7
|
+
import { getSession } from '@jbrowse/core/util'
|
|
8
|
+
import { getEnv } from 'mobx-state-tree'
|
|
9
|
+
import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
|
|
10
|
+
import BreakendOptionDialog from './BreakendOptionDialog'
|
|
11
|
+
|
|
12
|
+
export default function BreakendPanel(props: {
|
|
13
|
+
locStrings: string[]
|
|
14
|
+
model: any
|
|
15
|
+
feature: SimpleFeatureSerialized
|
|
16
|
+
}) {
|
|
17
|
+
const { model, locStrings, feature } = props
|
|
18
|
+
const session = getSession(model)
|
|
19
|
+
const { pluginManager } = getEnv(session)
|
|
20
|
+
const [breakpointDialog, setBreakpointDialog] = useState(false)
|
|
21
|
+
let viewType
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
viewType = pluginManager.getViewType('BreakpointSplitView')
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// ignore
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const simpleFeature = new SimpleFeature(feature)
|
|
30
|
+
return (
|
|
31
|
+
<BaseCard {...props} title="Breakends">
|
|
32
|
+
<Typography>Link to linear view of breakend endpoints</Typography>
|
|
33
|
+
<ul>
|
|
34
|
+
{locStrings.map(locString => (
|
|
35
|
+
<li key={`${JSON.stringify(locString)}`}>
|
|
36
|
+
<Link
|
|
37
|
+
href="#"
|
|
38
|
+
onClick={event => {
|
|
39
|
+
event.preventDefault()
|
|
40
|
+
const { view } = model
|
|
41
|
+
try {
|
|
42
|
+
if (view) {
|
|
43
|
+
view.navToLocString?.(locString)
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'No view associated with this feature detail panel anymore',
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(e)
|
|
51
|
+
session.notify(`${e}`)
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{`LGV - ${locString}`}
|
|
56
|
+
</Link>
|
|
57
|
+
</li>
|
|
58
|
+
))}
|
|
59
|
+
</ul>
|
|
60
|
+
{viewType ? (
|
|
61
|
+
<div>
|
|
62
|
+
<Typography>
|
|
63
|
+
Launch split views with breakend source and target
|
|
64
|
+
</Typography>
|
|
65
|
+
<ul>
|
|
66
|
+
{locStrings.map(locString => (
|
|
67
|
+
<li key={`${JSON.stringify(locString)}`}>
|
|
68
|
+
<Link
|
|
69
|
+
href="#"
|
|
70
|
+
onClick={event => {
|
|
71
|
+
event.preventDefault()
|
|
72
|
+
setBreakpointDialog(true)
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{`${feature.refName}:${feature.start} // ${locString} (split view)`}
|
|
76
|
+
</Link>
|
|
77
|
+
</li>
|
|
78
|
+
))}
|
|
79
|
+
</ul>
|
|
80
|
+
{breakpointDialog ? (
|
|
81
|
+
<BreakendOptionDialog
|
|
82
|
+
model={model}
|
|
83
|
+
feature={simpleFeature}
|
|
84
|
+
viewType={viewType}
|
|
85
|
+
handleClose={() => {
|
|
86
|
+
setBreakpointDialog(false)
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
) : null}
|
|
90
|
+
</div>
|
|
91
|
+
) : null}
|
|
92
|
+
</BaseCard>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
|
|
4
|
+
import AnnotGrid from './AnnotGrid'
|
|
5
|
+
|
|
6
|
+
export default function VariantAnnPanel({
|
|
7
|
+
feature,
|
|
8
|
+
descriptions,
|
|
9
|
+
}: {
|
|
10
|
+
feature: any
|
|
11
|
+
descriptions: any
|
|
12
|
+
}) {
|
|
13
|
+
const annFields = (descriptions?.INFO?.ANN?.Description?.match(
|
|
14
|
+
/.*Functional annotations:'(.*)'$/,
|
|
15
|
+
)?.[1].split('|') || []) as string[]
|
|
16
|
+
const ann = (feature.INFO.ANN || []) as string[]
|
|
17
|
+
|
|
18
|
+
const rows =
|
|
19
|
+
ann.map((elt, id) => ({
|
|
20
|
+
id,
|
|
21
|
+
...Object.fromEntries(elt.split('|').map((e, i) => [annFields[i], e])),
|
|
22
|
+
})) || []
|
|
23
|
+
const columns = annFields.map(c => ({
|
|
24
|
+
field: c,
|
|
25
|
+
}))
|
|
26
|
+
return ann.length ? (
|
|
27
|
+
<BaseCard title="ANN table">
|
|
28
|
+
<AnnotGrid rows={rows} columns={columns} />
|
|
29
|
+
</BaseCard>
|
|
30
|
+
) : null
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
|
|
4
|
+
import AnnotGrid from './AnnotGrid'
|
|
5
|
+
|
|
6
|
+
export default function VariantCsqPanel({
|
|
7
|
+
feature,
|
|
8
|
+
descriptions,
|
|
9
|
+
}: {
|
|
10
|
+
feature: any
|
|
11
|
+
descriptions: any
|
|
12
|
+
}) {
|
|
13
|
+
const csqFields = (descriptions?.INFO?.CSQ?.Description?.match(
|
|
14
|
+
/.*Format: (.*)/,
|
|
15
|
+
)?.[1].split('|') || []) as string[]
|
|
16
|
+
|
|
17
|
+
const csq = (feature.INFO.CSQ || []) as string[]
|
|
18
|
+
const rows =
|
|
19
|
+
csq.map((elt, id) => ({
|
|
20
|
+
id,
|
|
21
|
+
...Object.fromEntries(elt.split('|').map((e, i) => [csqFields[i], e])),
|
|
22
|
+
})) || []
|
|
23
|
+
const columns = csqFields.map(c => ({
|
|
24
|
+
field: c,
|
|
25
|
+
}))
|
|
26
|
+
return csq.length ? (
|
|
27
|
+
<BaseCard title="CSQ table">
|
|
28
|
+
<AnnotGrid rows={rows} columns={columns} />
|
|
29
|
+
</BaseCard>
|
|
30
|
+
) : null
|
|
31
|
+
}
|
|
@@ -1,204 +1,15 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import React
|
|
3
|
-
import {
|
|
4
|
-
Divider,
|
|
5
|
-
Link,
|
|
6
|
-
Paper,
|
|
7
|
-
FormControlLabel,
|
|
8
|
-
Checkbox,
|
|
9
|
-
TextField,
|
|
10
|
-
Typography,
|
|
11
|
-
} from '@mui/material'
|
|
12
|
-
import SimpleFeature, {
|
|
13
|
-
SimpleFeatureSerialized,
|
|
14
|
-
} from '@jbrowse/core/util/simpleFeature'
|
|
15
|
-
import { DataGrid } from '@mui/x-data-grid'
|
|
2
|
+
import React from 'react'
|
|
16
3
|
import { observer } from 'mobx-react'
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
FeatureDetails,
|
|
21
|
-
BaseCard,
|
|
22
|
-
} from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
|
|
23
|
-
import BreakendOptionDialog from './BreakendOptionDialog'
|
|
4
|
+
import { Divider, Paper } from '@mui/material'
|
|
5
|
+
import { FeatureDetails } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
|
|
24
6
|
import { parseBreakend } from '@gmod/vcf'
|
|
25
7
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const { samples = {} } = feature
|
|
32
|
-
const preFilteredRows: any = Object.entries(samples)
|
|
33
|
-
if (!preFilteredRows.length) {
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
const infoFields = ['sample', ...Object.keys(preFilteredRows[0][1])].map(
|
|
37
|
-
field => ({
|
|
38
|
-
field,
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
let error
|
|
42
|
-
let rows = []
|
|
43
|
-
const filters = Object.keys(filter)
|
|
44
|
-
|
|
45
|
-
// catch some error thrown from regex
|
|
46
|
-
// note: maps all values into a string, if this is not done rows are not
|
|
47
|
-
// sortable by the data-grid
|
|
48
|
-
try {
|
|
49
|
-
rows = preFilteredRows
|
|
50
|
-
.map((row: any) => ({
|
|
51
|
-
...Object.fromEntries(
|
|
52
|
-
Object.entries(row[1]).map(entry => [entry[0], String(entry[1])]),
|
|
53
|
-
),
|
|
54
|
-
sample: row[0],
|
|
55
|
-
id: row[0],
|
|
56
|
-
}))
|
|
57
|
-
.filter((row: any) => {
|
|
58
|
-
return filters.length
|
|
59
|
-
? filters.every(key => {
|
|
60
|
-
const val = row[key]
|
|
61
|
-
const currFilter = filter[key]
|
|
62
|
-
return currFilter ? val.match(new RegExp(currFilter, 'i')) : true
|
|
63
|
-
})
|
|
64
|
-
: true
|
|
65
|
-
})
|
|
66
|
-
} catch (e) {
|
|
67
|
-
error = e
|
|
68
|
-
}
|
|
69
|
-
// disableSelectionOnClick helps avoid
|
|
70
|
-
// https://github.com/mui-org/material-ui-x/issues/1197
|
|
71
|
-
return (
|
|
72
|
-
<BaseCard {...props} title="Samples">
|
|
73
|
-
{error ? <Typography color="error">{`${error}`}</Typography> : null}
|
|
74
|
-
|
|
75
|
-
<FormControlLabel
|
|
76
|
-
control={
|
|
77
|
-
<Checkbox
|
|
78
|
-
checked={showFilters}
|
|
79
|
-
onChange={() => setShowFilters(f => !f)}
|
|
80
|
-
/>
|
|
81
|
-
}
|
|
82
|
-
label="Show sample filters"
|
|
83
|
-
/>
|
|
84
|
-
{showFilters ? (
|
|
85
|
-
<>
|
|
86
|
-
<Typography>
|
|
87
|
-
These filters can use a plain text search or regex style query, e.g.
|
|
88
|
-
in the genotype field, entering 1 will query for all genotypes that
|
|
89
|
-
include the first alternate allele e.g. 0|1 or 1|1, entering
|
|
90
|
-
[1-9]\d* will find any non-zero allele e.g. 0|2 or 2/33
|
|
91
|
-
</Typography>
|
|
92
|
-
{infoFields.map(({ field }) => {
|
|
93
|
-
return (
|
|
94
|
-
<TextField
|
|
95
|
-
key={`filter-${field}`}
|
|
96
|
-
placeholder={`Filter ${field}`}
|
|
97
|
-
value={filter[field] || ''}
|
|
98
|
-
onChange={event =>
|
|
99
|
-
setFilter({ ...filter, [field]: event.target.value })
|
|
100
|
-
}
|
|
101
|
-
/>
|
|
102
|
-
)
|
|
103
|
-
})}
|
|
104
|
-
</>
|
|
105
|
-
) : null}
|
|
106
|
-
<div style={{ height: 600, width: '100%', overflow: 'auto' }}>
|
|
107
|
-
<DataGrid
|
|
108
|
-
rows={rows}
|
|
109
|
-
columns={infoFields}
|
|
110
|
-
disableSelectionOnClick
|
|
111
|
-
rowHeight={25}
|
|
112
|
-
disableColumnMenu
|
|
113
|
-
/>
|
|
114
|
-
</div>
|
|
115
|
-
</BaseCard>
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function BreakendPanel(props: {
|
|
120
|
-
locStrings: string[]
|
|
121
|
-
model: any
|
|
122
|
-
feature: SimpleFeatureSerialized
|
|
123
|
-
}) {
|
|
124
|
-
const { model, locStrings, feature } = props
|
|
125
|
-
const session = getSession(model)
|
|
126
|
-
const { pluginManager } = getEnv(session)
|
|
127
|
-
const [breakpointDialog, setBreakpointDialog] = useState(false)
|
|
128
|
-
let viewType
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
viewType = pluginManager.getViewType('BreakpointSplitView')
|
|
132
|
-
} catch (e) {
|
|
133
|
-
// ignore
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const simpleFeature = new SimpleFeature(feature)
|
|
137
|
-
return (
|
|
138
|
-
<BaseCard {...props} title="Breakends">
|
|
139
|
-
<Typography>Link to linear view of breakend endpoints</Typography>
|
|
140
|
-
<ul>
|
|
141
|
-
{locStrings.map(locString => (
|
|
142
|
-
<li key={`${JSON.stringify(locString)}`}>
|
|
143
|
-
<Link
|
|
144
|
-
href="#"
|
|
145
|
-
onClick={event => {
|
|
146
|
-
event.preventDefault()
|
|
147
|
-
const { view } = model
|
|
148
|
-
try {
|
|
149
|
-
if (view) {
|
|
150
|
-
view.navToLocString?.(locString)
|
|
151
|
-
} else {
|
|
152
|
-
throw new Error(
|
|
153
|
-
'No view associated with this feature detail panel anymore',
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
} catch (e) {
|
|
157
|
-
console.error(e)
|
|
158
|
-
session.notify(`${e}`)
|
|
159
|
-
}
|
|
160
|
-
}}
|
|
161
|
-
>
|
|
162
|
-
{`LGV - ${locString}`}
|
|
163
|
-
</Link>
|
|
164
|
-
</li>
|
|
165
|
-
))}
|
|
166
|
-
</ul>
|
|
167
|
-
{viewType ? (
|
|
168
|
-
<div>
|
|
169
|
-
<Typography>
|
|
170
|
-
Launch split views with breakend source and target
|
|
171
|
-
</Typography>
|
|
172
|
-
<ul>
|
|
173
|
-
{locStrings.map(locString => (
|
|
174
|
-
<li key={`${JSON.stringify(locString)}`}>
|
|
175
|
-
<Link
|
|
176
|
-
href="#"
|
|
177
|
-
onClick={event => {
|
|
178
|
-
event.preventDefault()
|
|
179
|
-
setBreakpointDialog(true)
|
|
180
|
-
}}
|
|
181
|
-
>
|
|
182
|
-
{`${feature.refName}:${feature.start} // ${locString} (split view)`}
|
|
183
|
-
</Link>
|
|
184
|
-
</li>
|
|
185
|
-
))}
|
|
186
|
-
</ul>
|
|
187
|
-
{breakpointDialog ? (
|
|
188
|
-
<BreakendOptionDialog
|
|
189
|
-
model={model}
|
|
190
|
-
feature={simpleFeature}
|
|
191
|
-
viewType={viewType}
|
|
192
|
-
handleClose={() => {
|
|
193
|
-
setBreakpointDialog(false)
|
|
194
|
-
}}
|
|
195
|
-
/>
|
|
196
|
-
) : null}
|
|
197
|
-
</div>
|
|
198
|
-
) : null}
|
|
199
|
-
</BaseCard>
|
|
200
|
-
)
|
|
201
|
-
}
|
|
8
|
+
// locals
|
|
9
|
+
import VariantSampleGrid from './VariantSampleGrid'
|
|
10
|
+
import VariantCsqPanel from './VariantCsqPanel'
|
|
11
|
+
import VariantAnnPanel from './VariantAnnPanel'
|
|
12
|
+
import BreakendPanel from './BreakendPanel'
|
|
202
13
|
|
|
203
14
|
function VariantFeatureDetails(props: any) {
|
|
204
15
|
const { model } = props
|
|
@@ -224,6 +35,10 @@ function VariantFeatureDetails(props: any) {
|
|
|
224
35
|
{...props}
|
|
225
36
|
/>
|
|
226
37
|
<Divider />
|
|
38
|
+
<VariantCsqPanel feature={rest} descriptions={descriptions} />
|
|
39
|
+
<Divider />
|
|
40
|
+
<VariantAnnPanel feature={rest} descriptions={descriptions} />
|
|
41
|
+
<Divider />
|
|
227
42
|
{feat.type === 'breakend' ? (
|
|
228
43
|
<BreakendPanel
|
|
229
44
|
feature={feat}
|
|
@@ -240,7 +55,7 @@ function VariantFeatureDetails(props: any) {
|
|
|
240
55
|
locStrings={[`${feat.INFO.CHR2[0]}:${feat.INFO.END}`]}
|
|
241
56
|
/>
|
|
242
57
|
) : null}
|
|
243
|
-
<
|
|
58
|
+
<VariantSampleGrid feature={feat} {...props} />
|
|
244
59
|
</Paper>
|
|
245
60
|
)
|
|
246
61
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FormControlLabel,
|
|
6
|
+
Checkbox,
|
|
7
|
+
TextField,
|
|
8
|
+
Typography,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
|
|
11
|
+
import { DataGrid } from '@mui/x-data-grid'
|
|
12
|
+
import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
|
|
13
|
+
|
|
14
|
+
export default function VariantSamples(props: any) {
|
|
15
|
+
const [filter, setFilter] = useState<any>({})
|
|
16
|
+
const [showFilters, setShowFilters] = useState(false)
|
|
17
|
+
const { feature } = props
|
|
18
|
+
|
|
19
|
+
const { samples = {} } = feature
|
|
20
|
+
const preFilteredRows: any = Object.entries(samples)
|
|
21
|
+
if (!preFilteredRows.length) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
const infoFields = ['sample', ...Object.keys(preFilteredRows[0][1])].map(
|
|
25
|
+
field => ({
|
|
26
|
+
field,
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
let error
|
|
30
|
+
let rows = []
|
|
31
|
+
const filters = Object.keys(filter)
|
|
32
|
+
|
|
33
|
+
// catch some error thrown from regex
|
|
34
|
+
// note: maps all values into a string, if this is not done rows are not
|
|
35
|
+
// sortable by the data-grid
|
|
36
|
+
try {
|
|
37
|
+
rows = preFilteredRows
|
|
38
|
+
.map((row: any) => ({
|
|
39
|
+
...Object.fromEntries(
|
|
40
|
+
Object.entries(row[1]).map(entry => [entry[0], String(entry[1])]),
|
|
41
|
+
),
|
|
42
|
+
sample: row[0],
|
|
43
|
+
id: row[0],
|
|
44
|
+
}))
|
|
45
|
+
.filter((row: any) => {
|
|
46
|
+
return filters.length
|
|
47
|
+
? filters.every(key => {
|
|
48
|
+
const val = row[key]
|
|
49
|
+
const currFilter = filter[key]
|
|
50
|
+
return currFilter ? val.match(new RegExp(currFilter, 'i')) : true
|
|
51
|
+
})
|
|
52
|
+
: true
|
|
53
|
+
})
|
|
54
|
+
} catch (e) {
|
|
55
|
+
error = e
|
|
56
|
+
}
|
|
57
|
+
// disableSelectionOnClick helps avoid
|
|
58
|
+
// https://github.com/mui-org/material-ui-x/issues/1197
|
|
59
|
+
return (
|
|
60
|
+
<BaseCard {...props} title="Samples">
|
|
61
|
+
{error ? <Typography color="error">{`${error}`}</Typography> : null}
|
|
62
|
+
|
|
63
|
+
<FormControlLabel
|
|
64
|
+
control={
|
|
65
|
+
<Checkbox
|
|
66
|
+
checked={showFilters}
|
|
67
|
+
onChange={() => setShowFilters(f => !f)}
|
|
68
|
+
/>
|
|
69
|
+
}
|
|
70
|
+
label="Show sample filters"
|
|
71
|
+
/>
|
|
72
|
+
{showFilters ? (
|
|
73
|
+
<>
|
|
74
|
+
<Typography>
|
|
75
|
+
These filters can use a plain text search or regex style query, e.g.
|
|
76
|
+
in the genotype field, entering 1 will query for all genotypes that
|
|
77
|
+
include the first alternate allele e.g. 0|1 or 1|1, entering
|
|
78
|
+
[1-9]\d* will find any non-zero allele e.g. 0|2 or 2/33
|
|
79
|
+
</Typography>
|
|
80
|
+
{infoFields.map(({ field }) => {
|
|
81
|
+
return (
|
|
82
|
+
<TextField
|
|
83
|
+
key={`filter-${field}`}
|
|
84
|
+
placeholder={`Filter ${field}`}
|
|
85
|
+
value={filter[field] || ''}
|
|
86
|
+
onChange={event =>
|
|
87
|
+
setFilter({ ...filter, [field]: event.target.value })
|
|
88
|
+
}
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
})}
|
|
92
|
+
</>
|
|
93
|
+
) : null}
|
|
94
|
+
<div style={{ height: 600, width: '100%', overflow: 'auto' }}>
|
|
95
|
+
<DataGrid
|
|
96
|
+
rows={rows}
|
|
97
|
+
columns={infoFields}
|
|
98
|
+
disableSelectionOnClick
|
|
99
|
+
rowHeight={25}
|
|
100
|
+
disableColumnMenu
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</BaseCard>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
@@ -198,5 +198,11 @@ exports[`VariantTrack widget renders with just the required model elements 1`] =
|
|
|
198
198
|
<hr
|
|
199
199
|
class="MuiDivider-root MuiDivider-fullWidth css-9mgopn-MuiDivider-root"
|
|
200
200
|
/>
|
|
201
|
+
<hr
|
|
202
|
+
class="MuiDivider-root MuiDivider-fullWidth css-9mgopn-MuiDivider-root"
|
|
203
|
+
/>
|
|
204
|
+
<hr
|
|
205
|
+
class="MuiDivider-root MuiDivider-fullWidth css-9mgopn-MuiDivider-root"
|
|
206
|
+
/>
|
|
201
207
|
</div>
|
|
202
208
|
`;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { lazy } from 'react'
|
|
1
2
|
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
3
|
+
import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType'
|
|
2
4
|
import PluginManager from '@jbrowse/core/PluginManager'
|
|
3
5
|
import { types } from 'mobx-state-tree'
|
|
4
6
|
import { stateModelFactory as baseModelFactory } from '@jbrowse/core/BaseFeatureWidget'
|
|
@@ -15,3 +17,16 @@ export function stateModelFactory(pluginManager: PluginManager) {
|
|
|
15
17
|
}),
|
|
16
18
|
)
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
export default (pluginManager: PluginManager) => {
|
|
22
|
+
pluginManager.addWidgetType(
|
|
23
|
+
() =>
|
|
24
|
+
new WidgetType({
|
|
25
|
+
name: 'VariantFeatureWidget',
|
|
26
|
+
heading: 'Feature details',
|
|
27
|
+
configSchema,
|
|
28
|
+
stateModel: stateModelFactory(pluginManager),
|
|
29
|
+
ReactComponent: lazy(() => import('./VariantFeatureWidget')),
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
3
|
+
import TrackType from '@jbrowse/core/pluggableElementTypes/TrackType'
|
|
4
|
+
import {
|
|
5
|
+
createBaseTrackConfig,
|
|
6
|
+
createBaseTrackModel,
|
|
7
|
+
} from '@jbrowse/core/pluggableElementTypes/models'
|
|
8
|
+
|
|
9
|
+
export default (pluginManager: PluginManager) => {
|
|
10
|
+
pluginManager.addTrackType(() => {
|
|
11
|
+
const configSchema = ConfigurationSchema(
|
|
12
|
+
'VariantTrack',
|
|
13
|
+
{},
|
|
14
|
+
{ baseConfiguration: createBaseTrackConfig(pluginManager) },
|
|
15
|
+
)
|
|
16
|
+
return new TrackType({
|
|
17
|
+
name: 'VariantTrack',
|
|
18
|
+
configSchema,
|
|
19
|
+
stateModel: createBaseTrackModel(
|
|
20
|
+
pluginManager,
|
|
21
|
+
'VariantTrack',
|
|
22
|
+
configSchema,
|
|
23
|
+
),
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -6,23 +6,24 @@ 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
|
-
import { readConfObject } from '@jbrowse/core/configuration'
|
|
10
9
|
import IntervalTree from '@flatten-js/interval-tree'
|
|
11
10
|
import { unzip } from '@gmod/bgzf-filehandle'
|
|
12
11
|
import VCF from '@gmod/vcf'
|
|
13
12
|
import VcfFeature from '../VcfTabixAdapter/VcfFeature'
|
|
14
13
|
|
|
15
14
|
const readVcf = (f: string) => {
|
|
16
|
-
const lines = f.split('\n')
|
|
17
15
|
const header: string[] = []
|
|
18
16
|
const rest: string[] = []
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
f.split('\n')
|
|
18
|
+
.map(f => f.trim())
|
|
19
|
+
.filter(f => !!f)
|
|
20
|
+
.forEach(line => {
|
|
21
|
+
if (line.startsWith('#')) {
|
|
22
|
+
header.push(line)
|
|
23
|
+
} else if (line) {
|
|
24
|
+
rest.push(line)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
26
27
|
return { header: header.join('\n'), lines: rest }
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -51,19 +52,17 @@ export default class VcfAdapter extends BaseFeatureDataAdapter {
|
|
|
51
52
|
|
|
52
53
|
// converts lines into an interval tree
|
|
53
54
|
public async setupP() {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
this.pluginManager,
|
|
57
|
-
).readFile()
|
|
55
|
+
const pm = this.pluginManager
|
|
56
|
+
const buf = await openLocation(this.getConf('vcfLocation'), pm).readFile()
|
|
58
57
|
|
|
59
|
-
const
|
|
58
|
+
const buffer = isGzip(buf) ? await unzip(buf) : buf
|
|
60
59
|
|
|
61
60
|
// 512MB max chrome string length is 512MB
|
|
62
|
-
if (
|
|
61
|
+
if (buffer.length > 536_870_888) {
|
|
63
62
|
throw new Error('Data exceeds maximum string length (512MB)')
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
const str = new TextDecoder().decode(
|
|
65
|
+
const str = new TextDecoder().decode(buffer)
|
|
67
66
|
const { header, lines } = readVcf(str)
|
|
68
67
|
|
|
69
68
|
const intervalTree = lines
|
package/src/VcfAdapter/index.ts
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType'
|
|
3
|
+
import configSchema from './configSchema'
|
|
4
|
+
|
|
5
|
+
export default (pluginManager: PluginManager) => {
|
|
6
|
+
pluginManager.addAdapterType(
|
|
7
|
+
() =>
|
|
8
|
+
new AdapterType({
|
|
9
|
+
name: 'VcfAdapter',
|
|
10
|
+
configSchema,
|
|
11
|
+
getAdapterClass: () => import('./VcfAdapter').then(r => r.default),
|
|
12
|
+
}),
|
|
13
|
+
)
|
|
14
|
+
}
|