@jbrowse/plugin-variants 2.1.0 → 2.1.3

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 (119) hide show
  1. package/dist/ChordVariantDisplay/index.js +9 -9
  2. package/dist/ChordVariantDisplay/index.js.map +1 -1
  3. package/dist/ChordVariantDisplay/models/ChordVariantDisplay.js +23 -25
  4. package/dist/ChordVariantDisplay/models/ChordVariantDisplay.js.map +1 -1
  5. package/dist/LinearVariantDisplay/configSchema.js +3 -3
  6. package/dist/LinearVariantDisplay/configSchema.js.map +1 -1
  7. package/dist/LinearVariantDisplay/index.d.ts +3 -2
  8. package/dist/LinearVariantDisplay/index.js +17 -5
  9. package/dist/LinearVariantDisplay/index.js.map +1 -1
  10. package/dist/LinearVariantDisplay/model.js +24 -72
  11. package/dist/LinearVariantDisplay/model.js.map +1 -1
  12. package/dist/StructuralVariantChordRenderer/ReactComponent.d.ts +1 -1
  13. package/dist/StructuralVariantChordRenderer/ReactComponent.js +57 -103
  14. package/dist/StructuralVariantChordRenderer/ReactComponent.js.map +1 -1
  15. package/dist/StructuralVariantChordRenderer/index.js +7 -7
  16. package/dist/StructuralVariantChordRenderer/index.js.map +1 -1
  17. package/dist/VariantFeatureWidget/AnnotGrid.d.ts +5 -0
  18. package/dist/VariantFeatureWidget/AnnotGrid.js +22 -0
  19. package/dist/VariantFeatureWidget/AnnotGrid.js.map +1 -0
  20. package/dist/VariantFeatureWidget/BreakendOptionDialog.js +23 -40
  21. package/dist/VariantFeatureWidget/BreakendOptionDialog.js.map +1 -1
  22. package/dist/VariantFeatureWidget/BreakendPanel.d.ts +7 -0
  23. package/dist/VariantFeatureWidget/BreakendPanel.js +82 -0
  24. package/dist/VariantFeatureWidget/BreakendPanel.js.map +1 -0
  25. package/dist/VariantFeatureWidget/VariantAnnPanel.d.ts +5 -0
  26. package/dist/VariantFeatureWidget/VariantAnnPanel.js +25 -0
  27. package/dist/VariantFeatureWidget/VariantAnnPanel.js.map +1 -0
  28. package/dist/VariantFeatureWidget/VariantCsqPanel.d.ts +5 -0
  29. package/dist/VariantFeatureWidget/VariantCsqPanel.js +25 -0
  30. package/dist/VariantFeatureWidget/VariantCsqPanel.js.map +1 -0
  31. package/dist/VariantFeatureWidget/VariantFeatureWidget.js +23 -184
  32. package/dist/VariantFeatureWidget/VariantFeatureWidget.js.map +1 -1
  33. package/dist/VariantFeatureWidget/VariantSampleGrid.d.ts +6 -0
  34. package/dist/VariantFeatureWidget/VariantSampleGrid.js +83 -0
  35. package/dist/VariantFeatureWidget/VariantSampleGrid.js.map +1 -0
  36. package/dist/VariantFeatureWidget/index.d.ts +2 -0
  37. package/dist/VariantFeatureWidget/index.js +41 -4
  38. package/dist/VariantFeatureWidget/index.js.map +1 -1
  39. package/dist/VariantTrack/index.d.ts +3 -0
  40. package/dist/VariantTrack/index.js +19 -0
  41. package/dist/VariantTrack/index.js.map +1 -0
  42. package/dist/VcfAdapter/VcfAdapter.js +85 -221
  43. package/dist/VcfAdapter/VcfAdapter.js.map +1 -1
  44. package/dist/VcfAdapter/configSchema.js +1 -1
  45. package/dist/VcfAdapter/configSchema.js.map +1 -1
  46. package/dist/VcfAdapter/index.d.ts +3 -1
  47. package/dist/VcfAdapter/index.js +32 -3
  48. package/dist/VcfAdapter/index.js.map +1 -1
  49. package/dist/VcfTabixAdapter/VcfFeature.js +75 -106
  50. package/dist/VcfTabixAdapter/VcfFeature.js.map +1 -1
  51. package/dist/VcfTabixAdapter/VcfTabixAdapter.js +88 -226
  52. package/dist/VcfTabixAdapter/VcfTabixAdapter.js.map +1 -1
  53. package/dist/VcfTabixAdapter/configSchema.js +2 -2
  54. package/dist/VcfTabixAdapter/configSchema.js.map +1 -1
  55. package/dist/VcfTabixAdapter/index.d.ts +3 -0
  56. package/dist/VcfTabixAdapter/index.js +34 -2
  57. package/dist/VcfTabixAdapter/index.js.map +1 -1
  58. package/dist/extensionPoints.d.ts +3 -0
  59. package/dist/extensionPoints.js +51 -0
  60. package/dist/extensionPoints.js.map +1 -0
  61. package/dist/index.js +24 -162
  62. package/dist/index.js.map +1 -1
  63. package/esm/LinearVariantDisplay/index.d.ts +3 -2
  64. package/esm/LinearVariantDisplay/index.js +17 -2
  65. package/esm/LinearVariantDisplay/index.js.map +1 -1
  66. package/esm/StructuralVariantChordRenderer/ReactComponent.d.ts +1 -1
  67. package/esm/VariantFeatureWidget/AnnotGrid.d.ts +5 -0
  68. package/esm/VariantFeatureWidget/AnnotGrid.js +16 -0
  69. package/esm/VariantFeatureWidget/AnnotGrid.js.map +1 -0
  70. package/esm/VariantFeatureWidget/BreakendPanel.d.ts +7 -0
  71. package/esm/VariantFeatureWidget/BreakendPanel.js +53 -0
  72. package/esm/VariantFeatureWidget/BreakendPanel.js.map +1 -0
  73. package/esm/VariantFeatureWidget/VariantAnnPanel.d.ts +5 -0
  74. package/esm/VariantFeatureWidget/VariantAnnPanel.js +19 -0
  75. package/esm/VariantFeatureWidget/VariantAnnPanel.js.map +1 -0
  76. package/esm/VariantFeatureWidget/VariantCsqPanel.d.ts +5 -0
  77. package/esm/VariantFeatureWidget/VariantCsqPanel.js +19 -0
  78. package/esm/VariantFeatureWidget/VariantCsqPanel.js.map +1 -0
  79. package/esm/VariantFeatureWidget/VariantFeatureWidget.js +13 -104
  80. package/esm/VariantFeatureWidget/VariantFeatureWidget.js.map +1 -1
  81. package/esm/VariantFeatureWidget/VariantSampleGrid.d.ts +6 -0
  82. package/esm/VariantFeatureWidget/VariantSampleGrid.js +57 -0
  83. package/esm/VariantFeatureWidget/VariantSampleGrid.js.map +1 -0
  84. package/esm/VariantFeatureWidget/index.d.ts +2 -0
  85. package/esm/VariantFeatureWidget/index.js +11 -0
  86. package/esm/VariantFeatureWidget/index.js.map +1 -1
  87. package/esm/VariantTrack/index.d.ts +3 -0
  88. package/esm/VariantTrack/index.js +14 -0
  89. package/esm/VariantTrack/index.js.map +1 -0
  90. package/esm/VcfAdapter/VcfAdapter.js +9 -7
  91. package/esm/VcfAdapter/VcfAdapter.js.map +1 -1
  92. package/esm/VcfAdapter/index.d.ts +3 -1
  93. package/esm/VcfAdapter/index.js +9 -1
  94. package/esm/VcfAdapter/index.js.map +1 -1
  95. package/esm/VcfTabixAdapter/index.d.ts +3 -0
  96. package/esm/VcfTabixAdapter/index.js +9 -0
  97. package/esm/VcfTabixAdapter/index.js.map +1 -1
  98. package/esm/extensionPoints.d.ts +3 -0
  99. package/esm/extensionPoints.js +49 -0
  100. package/esm/extensionPoints.js.map +1 -0
  101. package/esm/index.js +12 -95
  102. package/esm/index.js.map +1 -1
  103. package/package.json +2 -3
  104. package/src/LinearVariantDisplay/index.ts +19 -2
  105. package/src/VariantFeatureWidget/AnnotGrid.tsx +28 -0
  106. package/src/VariantFeatureWidget/BreakendPanel.tsx +94 -0
  107. package/src/VariantFeatureWidget/VariantAnnPanel.tsx +31 -0
  108. package/src/VariantFeatureWidget/VariantCsqPanel.tsx +31 -0
  109. package/src/VariantFeatureWidget/VariantFeatureWidget.tsx +17 -198
  110. package/src/VariantFeatureWidget/VariantSampleGrid.tsx +121 -0
  111. package/src/VariantFeatureWidget/__snapshots__/VariantFeatureWidget.test.js.snap +6 -0
  112. package/src/VariantFeatureWidget/index.ts +15 -0
  113. package/src/VariantTrack/index.ts +26 -0
  114. package/src/VcfAdapter/VcfAdapter.ts +15 -16
  115. package/src/VcfAdapter/index.ts +14 -1
  116. package/src/VcfTabixAdapter/index.ts +15 -0
  117. package/src/__snapshots__/index.test.js.snap +4 -4
  118. package/src/extensionPoints.ts +74 -0
  119. package/src/index.ts +12 -155
@@ -1,2 +1,19 @@
1
- export { LinearVariantDisplayConfigFactory as configSchemaFactory } from './configSchema'
2
- export { default as modelFactory } from './model'
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, { useState } from '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 { getSession } from '@jbrowse/core/util'
18
- import { getEnv } from 'mobx-state-tree'
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
- function VariantSamples(props: any) {
27
- const [filter, setFilter] = useState<any>({})
28
- const [showFilters, setShowFilters] = useState(false)
29
- const { feature } = props
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,11 @@ function VariantFeatureDetails(props: any) {
240
55
  locStrings={[`${feat.INFO.CHR2[0]}:${feat.INFO.END}`]}
241
56
  />
242
57
  ) : null}
243
- <VariantSamples feature={feat} {...props} />
58
+ <VariantSampleGrid
59
+ feature={feat}
60
+ {...props}
61
+ descriptions={descriptions}
62
+ />
244
63
  </Paper>
245
64
  )
246
65
  }
@@ -0,0 +1,121 @@
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
+ import { SimpleFeatureSerialized } from '@jbrowse/core/util/simpleFeature'
14
+
15
+ interface Entry {
16
+ sample: string
17
+ id: string
18
+ [key: string]: string
19
+ }
20
+
21
+ export default function VariantSamples(props: {
22
+ feature: SimpleFeatureSerialized
23
+ descriptions: any
24
+ }) {
25
+ const { feature, descriptions } = props
26
+ const [filter, setFilter] = useState<Record<string, string>>({})
27
+ const [showFilters, setShowFilters] = useState(false)
28
+ const { samples = {} } = feature as Record<
29
+ string,
30
+ Record<string, Record<string, unknown>>
31
+ >
32
+ const preFilteredRows = Object.entries(samples)
33
+ if (!preFilteredRows.length) {
34
+ return null
35
+ }
36
+
37
+ const infoFields = ['sample', ...Object.keys(preFilteredRows[0][1])].map(
38
+ field => ({
39
+ field,
40
+ description: descriptions.FORMAT?.[field]?.Description,
41
+ }),
42
+ )
43
+
44
+ let error
45
+ let rows = [] as Entry[]
46
+ const filters = Object.keys(filter)
47
+
48
+ // catch some error thrown from regex
49
+ // note: maps all values into a string, if this is not done rows are not
50
+ // sortable by the data-grid
51
+ try {
52
+ rows = preFilteredRows
53
+ .map(
54
+ row =>
55
+ ({
56
+ ...Object.fromEntries(
57
+ Object.entries(row[1]).map(entry => [entry[0], String(entry[1])]),
58
+ ),
59
+ sample: row[0],
60
+ id: row[0],
61
+ } as Entry),
62
+ )
63
+ .filter(row =>
64
+ filters.length
65
+ ? filters.every(key => {
66
+ const val = row[key]
67
+ const currFilter = filter[key]
68
+ return currFilter ? val.match(new RegExp(currFilter, 'i')) : true
69
+ })
70
+ : true,
71
+ )
72
+ } catch (e) {
73
+ error = e
74
+ }
75
+ // disableSelectionOnClick helps avoid
76
+ // https://github.com/mui-org/material-ui-x/issues/1197
77
+ return (
78
+ <BaseCard {...props} title="Samples">
79
+ {error ? <Typography color="error">{`${error}`}</Typography> : null}
80
+
81
+ <FormControlLabel
82
+ control={
83
+ <Checkbox
84
+ checked={showFilters}
85
+ onChange={() => setShowFilters(f => !f)}
86
+ />
87
+ }
88
+ label="Show sample filters"
89
+ />
90
+ {showFilters ? (
91
+ <>
92
+ <Typography>
93
+ These filters can use a plain text search or regex style query, e.g.
94
+ in the genotype field, entering 1 will query for all genotypes that
95
+ include the first alternate allele e.g. 0|1 or 1|1, entering
96
+ [1-9]\d* will find any non-zero allele e.g. 0|2 or 2/33
97
+ </Typography>
98
+ {infoFields.map(({ field }) => (
99
+ <TextField
100
+ key={`filter-${field}`}
101
+ placeholder={`Filter ${field}`}
102
+ value={filter[field] || ''}
103
+ onChange={event =>
104
+ setFilter({ ...filter, [field]: event.target.value })
105
+ }
106
+ />
107
+ ))}
108
+ </>
109
+ ) : null}
110
+ <div style={{ height: 600, width: '100%', overflow: 'auto' }}>
111
+ <DataGrid
112
+ rows={rows}
113
+ columns={infoFields}
114
+ disableSelectionOnClick
115
+ rowHeight={25}
116
+ disableColumnMenu
117
+ />
118
+ </div>
119
+ </BaseCard>
120
+ )
121
+ }
@@ -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
- lines.forEach(line => {
20
- if (line.startsWith('#')) {
21
- header.push(line)
22
- } else if (line) {
23
- rest.push(line)
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 buffer = await openLocation(
55
- readConfObject(this.config, 'vcfLocation'),
56
- this.pluginManager,
57
- ).readFile()
55
+ const pm = this.pluginManager
56
+ const buf = await openLocation(this.getConf('vcfLocation'), pm).readFile()
58
57
 
59
- const buf = isGzip(buffer) ? await unzip(buffer) : buffer
58
+ const buffer = isGzip(buf) ? await unzip(buf) : buf
60
59
 
61
60
  // 512MB max chrome string length is 512MB
62
- if (buf.length > 536_870_888) {
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(buf)
65
+ const str = new TextDecoder().decode(buffer)
67
66
  const { header, lines } = readVcf(str)
68
67
 
69
68
  const intervalTree = lines