jbrowse-plugin-graphgenomeviewer 8.7.1 → 8.7.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/chunks/AddTrackWorkflow-FANMD3WB.js +2 -0
- package/dist/chunks/{AddTrackWorkflow-IOPTETLH.js.map → AddTrackWorkflow-FANMD3WB.js.map} +2 -2
- package/dist/chunks/{GraphGenomeView-M4EGRLX4.js → GraphGenomeView-HCN57HV4.js} +2 -2
- package/dist/chunks/{LinearGraphDisplay-7ESV6SY2.js → LinearGraphDisplay-W5IT3HYI.js} +2 -2
- package/dist/chunks/chunk-7BKSSU2R.js +4 -0
- package/dist/chunks/chunk-7BKSSU2R.js.map +7 -0
- package/dist/chunks/{chunk-6V6OOEYK.js → chunk-OS52UJIX.js} +2 -2
- package/dist/chunks/chunk-T7LVVBTT.js +2 -0
- package/dist/chunks/chunk-T7LVVBTT.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +1 -1
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.test.tsx +7 -5
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +2 -2
- package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +13 -6
- package/src/GraphTrack/GraphTrack.test.ts +68 -0
- package/src/GraphTrack/index.ts +8 -5
- package/src/RgfaTabixAdapter/index.ts +3 -2
- package/src/graphTrackDefaults/graphTrackDefaults.test.ts +80 -3
- package/src/graphTrackDefaults/guessAdapter.ts +62 -0
- package/src/graphTrackDefaults/index.ts +16 -51
- package/src/panSNAliases/panSNAliases.test.ts +40 -6
- package/src/panSNAliases/trackAdapterConfig.ts +49 -10
- package/src/version.ts +1 -1
- package/dist/chunks/AddTrackWorkflow-IOPTETLH.js +0 -2
- package/dist/chunks/chunk-CGPYVMGQ.js +0 -2
- package/dist/chunks/chunk-CGPYVMGQ.js.map +0 -7
- package/dist/chunks/chunk-MKW7A2MK.js +0 -4
- package/dist/chunks/chunk-MKW7A2MK.js.map +0 -7
- /package/dist/chunks/{GraphGenomeView-M4EGRLX4.js.map → GraphGenomeView-HCN57HV4.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-7ESV6SY2.js.map → LinearGraphDisplay-W5IT3HYI.js.map} +0 -0
- /package/dist/chunks/{chunk-6V6OOEYK.js.map → chunk-OS52UJIX.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbrowse-plugin-graphgenomeviewer",
|
|
3
|
-
"version": "8.7.
|
|
3
|
+
"version": "8.7.2",
|
|
4
4
|
"description": "JBrowse 2 graph genome (pangenome) view: rGFA reference-anchored layouts, the Bandage force-directed engine, and haplotype lanes read from a gbz-base database",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -77,10 +77,10 @@ test('switching the file type forgets the index chosen for the last one', async
|
|
|
77
77
|
expect(added[0]!.adapter).not.toHaveProperty('haplotypeIndexLocation')
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
-
test('a walk file adds a
|
|
80
|
+
test('a walk file adds a track with lanes, cut for the haplotypes typed', async () => {
|
|
81
81
|
added.length = 0
|
|
82
82
|
picks.set(
|
|
83
|
-
GRAPH_FILE_FIELDS.
|
|
83
|
+
GRAPH_FILE_FIELDS.WalkTabixSyntenyAdapter,
|
|
84
84
|
'https://example.com/chr22.GRCh38.walks.bed.gz',
|
|
85
85
|
)
|
|
86
86
|
render(
|
|
@@ -93,8 +93,10 @@ test('a walk file adds a walk-indexed rGFA track for the haplotypes typed', asyn
|
|
|
93
93
|
}
|
|
94
94
|
/>,
|
|
95
95
|
)
|
|
96
|
-
fireEvent.click(
|
|
97
|
-
|
|
96
|
+
fireEvent.click(
|
|
97
|
+
screen.getByLabelText(GRAPH_FILE_LABELS.WalkTabixSyntenyAdapter),
|
|
98
|
+
)
|
|
99
|
+
fireEvent.click(screen.getByText(GRAPH_FILE_FIELDS.WalkTabixSyntenyAdapter))
|
|
98
100
|
fireEvent.change(screen.getByTestId('graph-haplotypes-input'), {
|
|
99
101
|
target: { value: 'HG002, HG00733#1' },
|
|
100
102
|
})
|
|
@@ -103,7 +105,7 @@ test('a walk file adds a walk-indexed rGFA track for the haplotypes typed', asyn
|
|
|
103
105
|
expect(added).toHaveLength(1)
|
|
104
106
|
})
|
|
105
107
|
expect(added[0]!.adapter).toEqual({
|
|
106
|
-
type: '
|
|
108
|
+
type: 'WalkTabixSyntenyAdapter',
|
|
107
109
|
walksLocation: {
|
|
108
110
|
uri: 'https://example.com/chr22.GRCh38.walks.bed.gz',
|
|
109
111
|
locationType: 'UriLocation',
|
|
@@ -54,7 +54,7 @@ const useStyles = makeStyles()(theme => ({
|
|
|
54
54
|
|
|
55
55
|
const CHOICES: GraphFileChoice[] = [
|
|
56
56
|
'RgfaTabixAdapter',
|
|
57
|
-
'
|
|
57
|
+
'WalkTabixSyntenyAdapter',
|
|
58
58
|
'GbzBaseSyntenyAdapter',
|
|
59
59
|
'MinigraphBubbleAdapter',
|
|
60
60
|
]
|
|
@@ -72,7 +72,7 @@ const GraphAddTrackWidget = observer(function GraphAddTrackWidget({
|
|
|
72
72
|
const [indexLoc, setIndexLoc] = useState<FileLocation>()
|
|
73
73
|
const [readsLoc, setReadsLoc] = useState<FileLocation>()
|
|
74
74
|
const gbz = choice === 'GbzBaseSyntenyAdapter'
|
|
75
|
-
const walks = choice === '
|
|
75
|
+
const walks = choice === 'WalkTabixSyntenyAdapter'
|
|
76
76
|
const [sample, setSample] = useState('')
|
|
77
77
|
const [haplotypes, setHaplotypes] = useState('')
|
|
78
78
|
const [trackName, setTrackName] = useState('Pangenome graph')
|
|
@@ -14,25 +14,31 @@ import {
|
|
|
14
14
|
import type { GraphManifest } from './graphManifest'
|
|
15
15
|
import type { FileLocation } from '@jbrowse/core/util'
|
|
16
16
|
|
|
17
|
-
// 'RgfaTabixWalks' is an RgfaTabixAdapter read from a walk file
|
|
17
|
+
// 'RgfaTabixWalks' is an RgfaTabixAdapter read from a walk file, which draws
|
|
18
|
+
// the graph without haplotype lanes
|
|
18
19
|
export type GraphFileChoice =
|
|
19
20
|
| 'RgfaTabixAdapter'
|
|
20
21
|
| 'RgfaTabixWalks'
|
|
22
|
+
| 'WalkTabixSyntenyAdapter'
|
|
21
23
|
| 'GbzBaseSyntenyAdapter'
|
|
22
24
|
| 'MinigraphBubbleAdapter'
|
|
23
25
|
|
|
24
26
|
export const GRAPH_FILE_LABELS: Record<GraphFileChoice, string> = {
|
|
25
27
|
RgfaTabixAdapter: 'Indexed GFA (gfa-to-tabix .segs.bed.gz or .graph.bed.gz)',
|
|
26
|
-
RgfaTabixWalks: 'Haplotype walks (.walks.bed.gz)',
|
|
28
|
+
RgfaTabixWalks: 'Haplotype walks without lanes (.walks.bed.gz)',
|
|
29
|
+
WalkTabixSyntenyAdapter: 'Haplotype walks (.walks.bed.gz)',
|
|
27
30
|
GbzBaseSyntenyAdapter: 'GBZ database (.gbz.db)',
|
|
28
31
|
MinigraphBubbleAdapter: 'Minigraph bubbles (tabix BED)',
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
const WALKS_FIELD =
|
|
35
|
+
'Path to a walk file (<prefix>.<sample>.walks.bed.gz from gfa-to-tabix --walks, for the sample this assembly is)'
|
|
36
|
+
|
|
31
37
|
export const GRAPH_FILE_FIELDS: Record<GraphFileChoice, string> = {
|
|
32
38
|
RgfaTabixAdapter:
|
|
33
39
|
"Path to segments BED (.segs.bed.gz from gfa-to-tabix; the .links.bed.gz, its index and any .graph.json are read beside it), or format 2's .graph.bed.gz",
|
|
34
|
-
RgfaTabixWalks:
|
|
35
|
-
|
|
40
|
+
RgfaTabixWalks: WALKS_FIELD,
|
|
41
|
+
WalkTabixSyntenyAdapter: WALKS_FIELD,
|
|
36
42
|
GbzBaseSyntenyAdapter: 'Path to the .gbz.db written by gbz-base construct',
|
|
37
43
|
MinigraphBubbleAdapter: 'Path to bubbles BED (.bed.gz from gfatools bubble)',
|
|
38
44
|
}
|
|
@@ -43,6 +49,7 @@ const TABIX_INDEX_FIELD =
|
|
|
43
49
|
export const GRAPH_INDEX_FIELDS: Record<GraphFileChoice, string> = {
|
|
44
50
|
RgfaTabixAdapter: TABIX_INDEX_FIELD,
|
|
45
51
|
RgfaTabixWalks: TABIX_INDEX_FIELD,
|
|
52
|
+
WalkTabixSyntenyAdapter: TABIX_INDEX_FIELD,
|
|
46
53
|
GbzBaseSyntenyAdapter:
|
|
47
54
|
'Path to the haplotype index (optional; graph.haplotype-index.db beside the database is assumed)',
|
|
48
55
|
MinigraphBubbleAdapter: TABIX_INDEX_FIELD,
|
|
@@ -167,14 +174,14 @@ export function buildAdapterConfig({
|
|
|
167
174
|
// a walk file's haplotypes to cut for until the user picks others
|
|
168
175
|
haplotypes?: string[]
|
|
169
176
|
}) {
|
|
170
|
-
if (choice === 'RgfaTabixWalks') {
|
|
177
|
+
if (choice === 'RgfaTabixWalks' || choice === 'WalkTabixSyntenyAdapter') {
|
|
171
178
|
if (!locationName(loc).endsWith(WALKS_SUFFIX)) {
|
|
172
179
|
throw new Error(
|
|
173
180
|
`Expected a walk file ending in ${WALKS_SUFFIX}, got ${locationName(loc)}`,
|
|
174
181
|
)
|
|
175
182
|
}
|
|
176
183
|
return {
|
|
177
|
-
type: 'RgfaTabixAdapter',
|
|
184
|
+
type: choice === 'RgfaTabixWalks' ? 'RgfaTabixAdapter' : choice,
|
|
178
185
|
walksLocation: loc,
|
|
179
186
|
walksIndex: tabixIndex(loc, indexLoc),
|
|
180
187
|
...referencePanSN(assembly, sample, walkFileSample(loc)),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
3
|
+
import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes'
|
|
4
|
+
import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType'
|
|
5
|
+
import { types } from '@jbrowse/mobx-state-tree'
|
|
6
|
+
|
|
7
|
+
import GraphTrackF, { SYNTENY_DISPLAYS } from './index'
|
|
8
|
+
import GbzBaseSyntenyAdapterF from '../GbzBaseSyntenyAdapter/index'
|
|
9
|
+
import RgfaTabixAdapterF from '../RgfaTabixAdapter/index'
|
|
10
|
+
import WalkTabixSyntenyAdapterF from '../WalkTabixSyntenyAdapter/index'
|
|
11
|
+
|
|
12
|
+
function displayStub(name: string) {
|
|
13
|
+
const configSchema = ConfigurationSchema(
|
|
14
|
+
name,
|
|
15
|
+
{},
|
|
16
|
+
{ explicitlyTyped: true, explicitIdentifier: 'displayId' },
|
|
17
|
+
)
|
|
18
|
+
return new DisplayType({
|
|
19
|
+
name,
|
|
20
|
+
configSchema,
|
|
21
|
+
stateModel: types.compose(
|
|
22
|
+
name,
|
|
23
|
+
BaseDisplay,
|
|
24
|
+
types.model({ type: types.literal(name) }),
|
|
25
|
+
),
|
|
26
|
+
// core's own displays belong to other track types, and GraphTrack picks
|
|
27
|
+
// them up by name
|
|
28
|
+
trackType: 'FeatureTrack',
|
|
29
|
+
viewType: 'LinearGenomeView',
|
|
30
|
+
ReactComponent: () => null,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function displaysFor() {
|
|
35
|
+
const pluginManager = new PluginManager()
|
|
36
|
+
for (const name of [
|
|
37
|
+
'LinearGraphDisplay',
|
|
38
|
+
'LinearBasicDisplay',
|
|
39
|
+
...SYNTENY_DISPLAYS,
|
|
40
|
+
]) {
|
|
41
|
+
pluginManager.addDisplayType(() => displayStub(name))
|
|
42
|
+
}
|
|
43
|
+
RgfaTabixAdapterF(pluginManager)
|
|
44
|
+
GbzBaseSyntenyAdapterF(pluginManager)
|
|
45
|
+
WalkTabixSyntenyAdapterF(pluginManager)
|
|
46
|
+
GraphTrackF(pluginManager)
|
|
47
|
+
pluginManager.createPluggableElements()
|
|
48
|
+
const track = pluginManager.getTrackType('GraphTrack')
|
|
49
|
+
return (adapter: string) => {
|
|
50
|
+
const has = new Set(
|
|
51
|
+
pluginManager.getAdapterType(adapter).adapterCapabilities,
|
|
52
|
+
)
|
|
53
|
+
return track.displayTypes
|
|
54
|
+
.filter(d => d.adapterCapabilities.every(c => has.has(c)))
|
|
55
|
+
.map(d => d.name)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test('the segments lane is offered only where getFeatures gives reference segments', () => {
|
|
60
|
+
const offered = displaysFor()
|
|
61
|
+
expect(offered('RgfaTabixAdapter')).toEqual([
|
|
62
|
+
'LinearGraphDisplay',
|
|
63
|
+
'LinearBasicDisplay',
|
|
64
|
+
])
|
|
65
|
+
for (const lanes of ['WalkTabixSyntenyAdapter', 'GbzBaseSyntenyAdapter']) {
|
|
66
|
+
expect(offered(lanes)).toEqual(['LinearGraphDisplay', ...SYNTENY_DISPLAYS])
|
|
67
|
+
}
|
|
68
|
+
})
|
package/src/GraphTrack/index.ts
CHANGED
|
@@ -9,7 +9,10 @@ import type PluginManager from '@jbrowse/core/PluginManager'
|
|
|
9
9
|
import type DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType'
|
|
10
10
|
|
|
11
11
|
// core's synteny displays, copied to ask for headerLanes so only the gbz-base
|
|
12
|
-
// and walk-tabix adapters open in a synteny, dotplot or circular view
|
|
12
|
+
// and walk-tabix adapters open in a synteny, dotplot or circular view.
|
|
13
|
+
// LinearBasicDisplay is copied to ask for referenceSegments: a lanes adapter
|
|
14
|
+
// answers getFeatures with every haplotype's lane features, which the segments
|
|
15
|
+
// lane can only offer to force-load
|
|
13
16
|
export const SYNTENY_DISPLAYS = [
|
|
14
17
|
'MultiWaySyntenyDisplay',
|
|
15
18
|
'LinearSyntenyDisplay',
|
|
@@ -22,15 +25,15 @@ function displaysInOrder(pluginManager: PluginManager): DisplayType[] {
|
|
|
22
25
|
'display',
|
|
23
26
|
) as DisplayType[]
|
|
24
27
|
const named = (name: string) => displays.find(d => d.name === name)
|
|
25
|
-
const
|
|
28
|
+
const needs = (capability: string, display: DisplayType | undefined) =>
|
|
26
29
|
display &&
|
|
27
30
|
Object.assign(Object.create(display) as DisplayType, {
|
|
28
|
-
adapterCapabilities: [
|
|
31
|
+
adapterCapabilities: [capability],
|
|
29
32
|
})
|
|
30
33
|
return [
|
|
31
34
|
named('LinearGraphDisplay'),
|
|
32
|
-
named('LinearBasicDisplay'),
|
|
33
|
-
...SYNTENY_DISPLAYS.map(name =>
|
|
35
|
+
needs('referenceSegments', named('LinearBasicDisplay')),
|
|
36
|
+
...SYNTENY_DISPLAYS.map(name => needs('headerLanes', named(name))),
|
|
34
37
|
].filter((d): d is DisplayType => d !== undefined)
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -11,8 +11,9 @@ export default function RgfaTabixAdapterF(pluginManager: PluginManager) {
|
|
|
11
11
|
name: 'RgfaTabixAdapter',
|
|
12
12
|
displayName: 'Indexed rGFA adapter',
|
|
13
13
|
configSchema,
|
|
14
|
-
// what a graph display needs of its track's adapter
|
|
15
|
-
|
|
14
|
+
// what a graph display needs of its track's adapter, and the
|
|
15
|
+
// reference's segments that its getFeatures gives the segments lane
|
|
16
|
+
adapterCapabilities: ['getSubgraph', 'referenceSegments'],
|
|
16
17
|
adapterMetadata: {
|
|
17
18
|
category: 'Graph adapters',
|
|
18
19
|
},
|
|
@@ -119,15 +119,85 @@ test("a walk lanes adapter naming no assemblies takes the track's", () => {
|
|
|
119
119
|
})
|
|
120
120
|
})
|
|
121
121
|
|
|
122
|
-
test('a walk file guesses
|
|
122
|
+
test('a walk file guesses the lanes adapter, so the track has graph and lanes', () => {
|
|
123
123
|
const walks = uri('https://example.com/chr22.GRCh38.walks.bed.gz?sig=1')
|
|
124
|
-
|
|
125
|
-
type: 'RgfaTabixAdapter',
|
|
124
|
+
const expected = {
|
|
126
125
|
walksLocation: walks,
|
|
127
126
|
walksIndex: {
|
|
128
127
|
indexType: 'TBI',
|
|
129
128
|
location: uri('https://example.com/chr22.GRCh38.walks.bed.gz.tbi?sig=1'),
|
|
130
129
|
},
|
|
130
|
+
}
|
|
131
|
+
const { adapter, trackType } = guessers()
|
|
132
|
+
expect(adapter(walks)).toEqual({
|
|
133
|
+
type: 'WalkTabixSyntenyAdapter',
|
|
134
|
+
...expected,
|
|
135
|
+
})
|
|
136
|
+
expect(adapter(walks, undefined, 'WalkTabixSyntenyAdapter')).toEqual({
|
|
137
|
+
type: 'WalkTabixSyntenyAdapter',
|
|
138
|
+
...expected,
|
|
139
|
+
})
|
|
140
|
+
expect(adapter(walks, undefined, 'RgfaTabixAdapter')).toEqual({
|
|
141
|
+
type: 'RgfaTabixAdapter',
|
|
142
|
+
...expected,
|
|
143
|
+
})
|
|
144
|
+
expect(trackType('WalkTabixSyntenyAdapter')).toBe('GraphTrack')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('a walk file picked in the browser needs its index picked too', () => {
|
|
148
|
+
const picked = (name: string) => ({
|
|
149
|
+
name,
|
|
150
|
+
blobId: name,
|
|
151
|
+
locationType: 'BlobLocation' as const,
|
|
152
|
+
})
|
|
153
|
+
const { adapter } = guessers()
|
|
154
|
+
expect(adapter(picked('g.GRCh38.walks.bed.gz'))).toEqual({
|
|
155
|
+
type: 'Fallback',
|
|
156
|
+
})
|
|
157
|
+
expect(
|
|
158
|
+
adapter(picked('g.GRCh38.walks.bed.gz'), picked('g.walks.bed.gz.csi')),
|
|
159
|
+
).toMatchObject({
|
|
160
|
+
type: 'WalkTabixSyntenyAdapter',
|
|
161
|
+
walksIndex: { indexType: 'CSI' },
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test("gfa-to-tabix's bubbles file guesses a bubble feature track", () => {
|
|
166
|
+
const bubbles = uri('https://example.com/hprc.bubbles.bed.gz')
|
|
167
|
+
const { adapter, trackType } = guessers()
|
|
168
|
+
expect(adapter(bubbles)).toEqual({
|
|
169
|
+
type: 'MinigraphBubbleAdapter',
|
|
170
|
+
bubblesLocation: bubbles,
|
|
171
|
+
index: {
|
|
172
|
+
indexType: 'TBI',
|
|
173
|
+
location: uri('https://example.com/hprc.bubbles.bed.gz.tbi'),
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
expect(
|
|
177
|
+
adapter(
|
|
178
|
+
uri('https://example.com/b.bed.gz'),
|
|
179
|
+
undefined,
|
|
180
|
+
'MinigraphBubbleAdapter',
|
|
181
|
+
),
|
|
182
|
+
).toMatchObject({ type: 'MinigraphBubbleAdapter' })
|
|
183
|
+
expect(adapter(bubbles, undefined, 'BedTabixAdapter')).toEqual({
|
|
184
|
+
type: 'Fallback',
|
|
185
|
+
})
|
|
186
|
+
expect(trackType('MinigraphBubbleAdapter')).toBe('FeatureTrack')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('a CSI beside the segments implies one beside the links', () => {
|
|
190
|
+
expect(
|
|
191
|
+
guessers().adapter(
|
|
192
|
+
uri('https://example.com/hprc.segs.bed.gz'),
|
|
193
|
+
uri('https://example.com/hprc.segs.bed.gz.CSI'),
|
|
194
|
+
),
|
|
195
|
+
).toMatchObject({
|
|
196
|
+
segmentsIndex: { indexType: 'CSI' },
|
|
197
|
+
linksIndex: {
|
|
198
|
+
indexType: 'CSI',
|
|
199
|
+
location: { uri: 'https://example.com/hprc.links.bed.gz.csi' },
|
|
200
|
+
},
|
|
131
201
|
})
|
|
132
202
|
})
|
|
133
203
|
|
|
@@ -150,4 +220,11 @@ test("a walk file with no PanSN map reads as its name's sample on the track's as
|
|
|
150
220
|
expect(withWalkReference(mapped)).toBe(mapped)
|
|
151
221
|
const same = { ...track, assemblyNames: ['GRCh38'] }
|
|
152
222
|
expect(withWalkReference(same)).toBe(same)
|
|
223
|
+
const lanes = {
|
|
224
|
+
...track,
|
|
225
|
+
adapter: { ...track.adapter, type: 'WalkTabixSyntenyAdapter' },
|
|
226
|
+
}
|
|
227
|
+
expect(withWalkReference(lanes).adapter).toMatchObject({
|
|
228
|
+
assemblyNameToPanSN: { hg38: 'GRCh38' },
|
|
229
|
+
})
|
|
153
230
|
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { buildAdapterConfig } from '../GraphAddTrackWorkflow/buildTrackConfig'
|
|
2
|
+
import { isGraphIndexLocation } from '../GraphAddTrackWorkflow/graphManifest'
|
|
3
|
+
import { WALKS_SUFFIX, locationName, readsSiblings } from '../locationName'
|
|
4
|
+
|
|
5
|
+
import type { GraphFileChoice } from '../GraphAddTrackWorkflow/buildTrackConfig'
|
|
6
|
+
import type { FileLocation } from '@jbrowse/core/util'
|
|
7
|
+
|
|
8
|
+
const BUBBLES_SUFFIX = '.bubbles.bed.gz'
|
|
9
|
+
|
|
10
|
+
function isGbzLocation(loc: FileLocation) {
|
|
11
|
+
return locationName(loc).toLowerCase().endsWith('.gbz.db')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function guessChoice(
|
|
15
|
+
file: FileLocation,
|
|
16
|
+
index: FileLocation | undefined,
|
|
17
|
+
adapterHint: string | undefined,
|
|
18
|
+
): GraphFileChoice | undefined {
|
|
19
|
+
const name = locationName(file)
|
|
20
|
+
const allows = (type: GraphFileChoice) => !adapterHint || adapterHint === type
|
|
21
|
+
// a file picked in the browser has no index beside it to assume
|
|
22
|
+
const indexReachable = index !== undefined || readsSiblings(file)
|
|
23
|
+
return isGbzLocation(file)
|
|
24
|
+
? allows('GbzBaseSyntenyAdapter')
|
|
25
|
+
? 'GbzBaseSyntenyAdapter'
|
|
26
|
+
: undefined
|
|
27
|
+
: isGraphIndexLocation(file)
|
|
28
|
+
? allows('RgfaTabixAdapter')
|
|
29
|
+
? 'RgfaTabixAdapter'
|
|
30
|
+
: undefined
|
|
31
|
+
: !indexReachable
|
|
32
|
+
? undefined
|
|
33
|
+
: name.endsWith(WALKS_SUFFIX)
|
|
34
|
+
? // lanes beside the graph, unless the hint asks for the rGFA
|
|
35
|
+
// adapter, which reads the file without them
|
|
36
|
+
adapterHint === 'RgfaTabixAdapter'
|
|
37
|
+
? 'RgfaTabixWalks'
|
|
38
|
+
: allows('WalkTabixSyntenyAdapter')
|
|
39
|
+
? 'WalkTabixSyntenyAdapter'
|
|
40
|
+
: undefined
|
|
41
|
+
: allows('MinigraphBubbleAdapter') &&
|
|
42
|
+
(name.endsWith(BUBBLES_SUFFIX) || adapterHint !== undefined)
|
|
43
|
+
? 'MinigraphBubbleAdapter'
|
|
44
|
+
: undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function guessGraphAdapter(
|
|
48
|
+
file: FileLocation,
|
|
49
|
+
index?: FileLocation,
|
|
50
|
+
adapterHint?: string,
|
|
51
|
+
) {
|
|
52
|
+
const choice = guessChoice(file, index, adapterHint)
|
|
53
|
+
return choice
|
|
54
|
+
? buildAdapterConfig({
|
|
55
|
+
choice,
|
|
56
|
+
loc: file,
|
|
57
|
+
indexLoc: index,
|
|
58
|
+
assembly: '',
|
|
59
|
+
sample: '',
|
|
60
|
+
})
|
|
61
|
+
: undefined
|
|
62
|
+
}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
WALKS_SUFFIX,
|
|
5
|
-
locationName,
|
|
6
|
-
readsSiblings,
|
|
7
|
-
walkFileSample,
|
|
8
|
-
} from '../locationName'
|
|
1
|
+
import { guessGraphAdapter } from './guessAdapter'
|
|
2
|
+
import { walkFileSample } from '../locationName'
|
|
9
3
|
import { LANE_ADAPTERS } from '../panSNAliases/trackAdapterConfig'
|
|
10
4
|
|
|
11
|
-
import type { GraphFileChoice } from '../GraphAddTrackWorkflow/buildTrackConfig'
|
|
12
5
|
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
13
6
|
import type { FileLocation } from '@jbrowse/core/util'
|
|
14
7
|
import type {
|
|
@@ -18,14 +11,14 @@ import type {
|
|
|
18
11
|
|
|
19
12
|
type TrackConfigSnapshot = Record<string, unknown>
|
|
20
13
|
|
|
21
|
-
const GRAPH_ADAPTERS = new Set([
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const GRAPH_ADAPTERS = new Set([
|
|
15
|
+
'RgfaTabixAdapter',
|
|
16
|
+
'GbzBaseSyntenyAdapter',
|
|
17
|
+
'WalkTabixSyntenyAdapter',
|
|
18
|
+
])
|
|
26
19
|
|
|
27
20
|
// A lanes adapter cuts on the first assembly it names. A track added through
|
|
28
|
-
//
|
|
21
|
+
// Open track names that assembly on the track alone.
|
|
29
22
|
export function withLaneAnchor(snap: TrackConfigSnapshot) {
|
|
30
23
|
const adapter = snap.adapter as
|
|
31
24
|
{ type?: string; assemblyNames?: unknown[] } | undefined
|
|
@@ -38,6 +31,8 @@ export function withLaneAnchor(snap: TrackConfigSnapshot) {
|
|
|
38
31
|
: snap
|
|
39
32
|
}
|
|
40
33
|
|
|
34
|
+
const WALK_ADAPTERS = new Set(['RgfaTabixAdapter', 'WalkTabixSyntenyAdapter'])
|
|
35
|
+
|
|
41
36
|
// A walk file opened with no PanSN map reads as the sample its name gives, on
|
|
42
37
|
// the track's assembly; a guessed adapter had no assembly to key that by
|
|
43
38
|
export function withWalkReference(snap: TrackConfigSnapshot) {
|
|
@@ -52,7 +47,9 @@ export function withWalkReference(snap: TrackConfigSnapshot) {
|
|
|
52
47
|
? (snap.assemblyNames as string[])
|
|
53
48
|
: []
|
|
54
49
|
const sample =
|
|
55
|
-
adapter?.type
|
|
50
|
+
adapter?.type !== undefined &&
|
|
51
|
+
WALK_ADAPTERS.has(adapter.type) &&
|
|
52
|
+
adapter.walksLocation
|
|
56
53
|
? walkFileSample(adapter.walksLocation)
|
|
57
54
|
: undefined
|
|
58
55
|
return assembly &&
|
|
@@ -66,40 +63,6 @@ export function withWalkReference(snap: TrackConfigSnapshot) {
|
|
|
66
63
|
: snap
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
export function guessGraphAdapter(
|
|
70
|
-
file: FileLocation,
|
|
71
|
-
index?: FileLocation,
|
|
72
|
-
adapterHint?: string,
|
|
73
|
-
) {
|
|
74
|
-
const guess = (choice: GraphFileChoice) =>
|
|
75
|
-
buildAdapterConfig({
|
|
76
|
-
choice,
|
|
77
|
-
loc: file,
|
|
78
|
-
indexLoc: index,
|
|
79
|
-
assembly: '',
|
|
80
|
-
sample: '',
|
|
81
|
-
})
|
|
82
|
-
if (
|
|
83
|
-
(!adapterHint || adapterHint === 'GbzBaseSyntenyAdapter') &&
|
|
84
|
-
isGbzLocation(file)
|
|
85
|
-
) {
|
|
86
|
-
return guess('GbzBaseSyntenyAdapter')
|
|
87
|
-
}
|
|
88
|
-
if (!adapterHint || adapterHint === 'RgfaTabixAdapter') {
|
|
89
|
-
if (isGraphIndexLocation(file)) {
|
|
90
|
-
return guess('RgfaTabixAdapter')
|
|
91
|
-
}
|
|
92
|
-
// a file picked in the browser has no index beside it to assume
|
|
93
|
-
if (
|
|
94
|
-
locationName(file).endsWith(WALKS_SUFFIX) &&
|
|
95
|
-
(index !== undefined || readsSiblings(file))
|
|
96
|
-
) {
|
|
97
|
-
return guess('RgfaTabixWalks')
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return undefined
|
|
101
|
-
}
|
|
102
|
-
|
|
103
66
|
export default function GraphTrackDefaultsF(pluginManager: PluginManager) {
|
|
104
67
|
pluginManager.addToExtensionPoint(
|
|
105
68
|
'Core-preProcessTrackConfig',
|
|
@@ -122,6 +85,8 @@ export default function GraphTrackDefaultsF(pluginManager: PluginManager) {
|
|
|
122
85
|
(adapterName, file) =>
|
|
123
86
|
GRAPH_ADAPTERS.has(adapterName)
|
|
124
87
|
? 'GraphTrack'
|
|
125
|
-
:
|
|
88
|
+
: adapterName === 'MinigraphBubbleAdapter'
|
|
89
|
+
? 'FeatureTrack'
|
|
90
|
+
: next(adapterName, file),
|
|
126
91
|
)
|
|
127
92
|
}
|
|
@@ -50,12 +50,33 @@ describe('the map a track sends', () => {
|
|
|
50
50
|
test('a bare sample, a contig name or a non-numeric haplotype is not read', () => {
|
|
51
51
|
const config = withPanSNAliases(GBZ, [
|
|
52
52
|
{ name: 'hg38', aliases: ['GRCh38', 'hg38.p14'] },
|
|
53
|
-
{ name: 'HG002
|
|
53
|
+
{ name: 'HG002', aliases: ['HG002#1#chr6'] },
|
|
54
54
|
{ name: 'odd', aliases: ['odd#a', '#1', 'odd#'] },
|
|
55
|
+
{ name: 'hg38.p14', aliases: [] },
|
|
55
56
|
])
|
|
56
57
|
expect(config).toBe(GBZ)
|
|
57
58
|
})
|
|
58
59
|
|
|
60
|
+
test('an assembly named sample.haplotype maps to that haplotype, its alias first', () => {
|
|
61
|
+
expect(
|
|
62
|
+
withPanSNAliases(GBZ, [
|
|
63
|
+
{ name: 'HG00621.1', aliases: [] },
|
|
64
|
+
{ name: 'HG00621.2', aliases: ['HG00438#1'] },
|
|
65
|
+
{ name: 'HG00438.1', aliases: [] },
|
|
66
|
+
]).assemblyNameToPanSN,
|
|
67
|
+
).toEqual({ 'HG00621.1': 'HG00621#1', 'HG00621.2': 'HG00438#1' })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("the anchor's own name is not read as a haplotype", () => {
|
|
71
|
+
expect(
|
|
72
|
+
withPanSNAliases(
|
|
73
|
+
{ ...GBZ, assemblyNames: ['GCA_000001405.15'] },
|
|
74
|
+
[{ name: 'GCA_000001405.15', aliases: [] }],
|
|
75
|
+
'GCA_000001405.15',
|
|
76
|
+
),
|
|
77
|
+
).toEqual({ ...GBZ, assemblyNames: ['GCA_000001405.15'] })
|
|
78
|
+
})
|
|
79
|
+
|
|
59
80
|
test("the config's own entry wins for its assembly and for its prefix", () => {
|
|
60
81
|
const config = withPanSNAliases(
|
|
61
82
|
{ ...GBZ, assemblyNameToPanSN: { hg38: 'GRCh38#0', mine: 'HG00621#1' } },
|
|
@@ -72,9 +93,9 @@ describe('the map a track sends', () => {
|
|
|
72
93
|
})
|
|
73
94
|
})
|
|
74
95
|
|
|
75
|
-
test('an assembly with no PanSN alias leaves the config as it was', () => {
|
|
96
|
+
test('an assembly with no PanSN alias or haplotype name leaves the config as it was', () => {
|
|
76
97
|
const config = { ...GBZ, assemblyNameToPanSN: { hg38: 'GRCh38#0' } }
|
|
77
|
-
expect(withPanSNAliases(config, [{ name: 'HG00621
|
|
98
|
+
expect(withPanSNAliases(config, [{ name: 'HG00621', aliases: [] }])).toBe(
|
|
78
99
|
config,
|
|
79
100
|
)
|
|
80
101
|
})
|
|
@@ -260,10 +281,18 @@ describe('the lanes the adapter reads with that map', () => {
|
|
|
260
281
|
expect(await laneNamed(adapter, 'HG00621#1')).toBe('HG00621#1')
|
|
261
282
|
})
|
|
262
283
|
|
|
263
|
-
test('an assembly
|
|
284
|
+
test('an assembly named sample.haplotype names the lane and reads its contigs', async () => {
|
|
264
285
|
const adapter = lanesAdapter([{ name: 'HG00621.1', aliases: [] }])
|
|
286
|
+
expect(await laneNamed(adapter, 'HG00621#1')).toBe('HG00621.1')
|
|
287
|
+
expect(
|
|
288
|
+
await adapter.getRefNames({ assemblyName: 'HG00621.1' }),
|
|
289
|
+
).not.toEqual([])
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('an assembly with neither keeps its lane at the PanSN prefix', async () => {
|
|
293
|
+
const adapter = lanesAdapter([{ name: 'mine', aliases: [] }])
|
|
265
294
|
expect(await laneNamed(adapter, 'HG00621#1')).toBe('HG00621#1')
|
|
266
|
-
expect(await adapter.getRefNames({ assemblyName: '
|
|
295
|
+
expect(await adapter.getRefNames({ assemblyName: 'mine' })).toEqual([])
|
|
267
296
|
})
|
|
268
297
|
})
|
|
269
298
|
|
|
@@ -337,7 +366,11 @@ test.each(['GbzBaseSyntenyAdapter', 'WalkTabixSyntenyAdapter'])(
|
|
|
337
366
|
.volatile(() => ({
|
|
338
367
|
rpcManager: {},
|
|
339
368
|
assemblyManager: {
|
|
340
|
-
assemblyList: [
|
|
369
|
+
assemblyList: [
|
|
370
|
+
{ name: 'hg38', aliases: ['GRCh38'] },
|
|
371
|
+
HG00621_1,
|
|
372
|
+
{ name: 'HG00438.1', aliases: [] },
|
|
373
|
+
],
|
|
341
374
|
get: () => undefined,
|
|
342
375
|
has: () => true,
|
|
343
376
|
isValidRefName: () => true,
|
|
@@ -367,6 +400,7 @@ test.each(['GbzBaseSyntenyAdapter', 'WalkTabixSyntenyAdapter'])(
|
|
|
367
400
|
expect(display.adapterConfig.assemblyNames).toEqual(['hg38'])
|
|
368
401
|
expect(display.adapterConfig.assemblyNameToPanSN).toEqual({
|
|
369
402
|
'HG00621.1': 'HG00621#1',
|
|
403
|
+
'HG00438.1': 'HG00438#1',
|
|
370
404
|
hg38: 'GRCh38#0',
|
|
371
405
|
})
|
|
372
406
|
},
|