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.
Files changed (32) hide show
  1. package/dist/chunks/AddTrackWorkflow-FANMD3WB.js +2 -0
  2. package/dist/chunks/{AddTrackWorkflow-IOPTETLH.js.map → AddTrackWorkflow-FANMD3WB.js.map} +2 -2
  3. package/dist/chunks/{GraphGenomeView-M4EGRLX4.js → GraphGenomeView-HCN57HV4.js} +2 -2
  4. package/dist/chunks/{LinearGraphDisplay-7ESV6SY2.js → LinearGraphDisplay-W5IT3HYI.js} +2 -2
  5. package/dist/chunks/chunk-7BKSSU2R.js +4 -0
  6. package/dist/chunks/chunk-7BKSSU2R.js.map +7 -0
  7. package/dist/chunks/{chunk-6V6OOEYK.js → chunk-OS52UJIX.js} +2 -2
  8. package/dist/chunks/chunk-T7LVVBTT.js +2 -0
  9. package/dist/chunks/chunk-T7LVVBTT.js.map +7 -0
  10. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
  11. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  12. package/package.json +1 -1
  13. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.test.tsx +7 -5
  14. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +2 -2
  15. package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +13 -6
  16. package/src/GraphTrack/GraphTrack.test.ts +68 -0
  17. package/src/GraphTrack/index.ts +8 -5
  18. package/src/RgfaTabixAdapter/index.ts +3 -2
  19. package/src/graphTrackDefaults/graphTrackDefaults.test.ts +80 -3
  20. package/src/graphTrackDefaults/guessAdapter.ts +62 -0
  21. package/src/graphTrackDefaults/index.ts +16 -51
  22. package/src/panSNAliases/panSNAliases.test.ts +40 -6
  23. package/src/panSNAliases/trackAdapterConfig.ts +49 -10
  24. package/src/version.ts +1 -1
  25. package/dist/chunks/AddTrackWorkflow-IOPTETLH.js +0 -2
  26. package/dist/chunks/chunk-CGPYVMGQ.js +0 -2
  27. package/dist/chunks/chunk-CGPYVMGQ.js.map +0 -7
  28. package/dist/chunks/chunk-MKW7A2MK.js +0 -4
  29. package/dist/chunks/chunk-MKW7A2MK.js.map +0 -7
  30. /package/dist/chunks/{GraphGenomeView-M4EGRLX4.js.map → GraphGenomeView-HCN57HV4.js.map} +0 -0
  31. /package/dist/chunks/{LinearGraphDisplay-7ESV6SY2.js.map → LinearGraphDisplay-W5IT3HYI.js.map} +0 -0
  32. /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.1",
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 walk-indexed rGFA track for the haplotypes typed', async () => {
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.RgfaTabixWalks,
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(screen.getByLabelText(GRAPH_FILE_LABELS.RgfaTabixWalks))
97
- fireEvent.click(screen.getByText(GRAPH_FILE_FIELDS.RgfaTabixWalks))
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: 'RgfaTabixAdapter',
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
- 'RgfaTabixWalks',
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 === 'RgfaTabixWalks'
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
- 'Path to a walk file (<prefix>.<sample>.walks.bed.gz from gfa-to-tabix --walks, for the sample this assembly is)',
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
+ })
@@ -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 needsLanes = (display: DisplayType | undefined) =>
28
+ const needs = (capability: string, display: DisplayType | undefined) =>
26
29
  display &&
27
30
  Object.assign(Object.create(display) as DisplayType, {
28
- adapterCapabilities: ['headerLanes'],
31
+ adapterCapabilities: [capability],
29
32
  })
30
33
  return [
31
34
  named('LinearGraphDisplay'),
32
- named('LinearBasicDisplay'),
33
- ...SYNTENY_DISPLAYS.map(name => needsLanes(named(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
- adapterCapabilities: ['getSubgraph'],
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 a walk-indexed rGFA adapter', () => {
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
- expect(guessers().adapter(walks)).toEqual({
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 { buildAdapterConfig } from '../GraphAddTrackWorkflow/buildTrackConfig'
2
- import { isGraphIndexLocation } from '../GraphAddTrackWorkflow/graphManifest'
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(['RgfaTabixAdapter', 'GbzBaseSyntenyAdapter'])
22
-
23
- export function isGbzLocation(loc: FileLocation) {
24
- return locationName(loc).toLowerCase().endsWith('.gbz.db')
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
- // Add track names that assembly on the track alone.
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 === 'RgfaTabixAdapter' && adapter.walksLocation
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
- : next(adapterName, file),
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.1', aliases: ['HG002#1#chr6'] },
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.1', aliases: [] }])).toBe(
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 with neither keeps its lane at the PanSN prefix', async () => {
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: 'HG00621.1' })).toEqual([])
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: [{ name: 'hg38', aliases: ['GRCh38'] }, HG00621_1],
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
  },