jbrowse-plugin-graphgenomeviewer 8.0.2 → 8.2.0
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-RJSOQZYQ.js → AddTrackWorkflow-7CXTSSMR.js} +2 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-EJIR3HZM.js +2 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-EJIR3HZM.js.map +7 -0
- package/dist/chunks/{GraphGenomeView-667ZUNDI.js → GraphGenomeView-2AKRM74B.js} +2 -2
- package/dist/chunks/{GraphTrackSettingsDialog-7M3Q635V.js → GraphTrackSettingsDialog-7XRZQW6T.js} +2 -2
- package/dist/chunks/{HighlightColorDialog-4F34Q4PY.js → HighlightColorDialog-LCBGD2ZA.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-WWQ73BO5.js +2 -0
- package/dist/chunks/{MinigraphBubbleAdapter-G25V6MZL.js → MinigraphBubbleAdapter-4OOBJ44C.js} +2 -2
- package/dist/chunks/RgfaTabixAdapter-CEGFN2QY.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-CEGFN2QY.js.map +7 -0
- package/dist/chunks/{WalkFormsAdapter-PBN7BJZW.js → WalkFormsAdapter-LTAKACZD.js} +2 -2
- package/dist/chunks/{WalkTabixSyntenyAdapter-27WDE5JF.js → WalkTabixSyntenyAdapter-B6V3YNW7.js} +2 -2
- package/dist/chunks/chunk-5FNGR33G.js +2 -0
- package/dist/chunks/chunk-5FNGR33G.js.map +7 -0
- package/dist/chunks/chunk-6VNDP3UB.js +2 -0
- package/dist/chunks/chunk-6VNDP3UB.js.map +7 -0
- package/dist/chunks/{chunk-CJAV55QB.js → chunk-BJHWGRMP.js} +1 -1
- package/dist/chunks/{chunk-CJAV55QB.js.map → chunk-BJHWGRMP.js.map} +1 -1
- package/dist/chunks/{chunk-IAXUZMOX.js → chunk-CNCYMJ5E.js} +2 -2
- package/dist/chunks/{chunk-IAXUZMOX.js.map → chunk-CNCYMJ5E.js.map} +2 -2
- package/dist/chunks/{chunk-2V75YPTT.js → chunk-CVHKXFVS.js} +2 -2
- package/dist/chunks/{chunk-2V75YPTT.js.map → chunk-CVHKXFVS.js.map} +2 -2
- package/dist/chunks/chunk-CZI7IP5S.js +2 -0
- package/dist/chunks/chunk-CZI7IP5S.js.map +7 -0
- package/dist/chunks/chunk-CZXJXNZN.js +3 -0
- package/dist/chunks/chunk-CZXJXNZN.js.map +7 -0
- package/dist/chunks/chunk-HAAABGE7.js +2 -0
- package/dist/chunks/{chunk-JMRW3IGK.js → chunk-IK3MQ4ZJ.js} +2 -2
- package/dist/chunks/chunk-IK3MQ4ZJ.js.map +7 -0
- package/dist/chunks/{chunk-JOYKGTDE.js → chunk-MCBYBZZJ.js} +2 -2
- package/dist/chunks/chunk-NOXHIOIM.js +2 -0
- package/dist/chunks/chunk-NOXHIOIM.js.map +7 -0
- package/dist/chunks/{chunk-FY7X6253.js → chunk-OGXVDZPS.js} +2 -2
- package/dist/chunks/{chunk-FY7X6253.js.map → chunk-OGXVDZPS.js.map} +2 -2
- package/dist/chunks/{chunk-3EQNVA3F.js → chunk-OLZZDBQJ.js} +2 -2
- package/dist/chunks/{chunk-3EQNVA3F.js.map → chunk-OLZZDBQJ.js.map} +2 -2
- package/dist/chunks/{chunk-I6F25EXY.js → chunk-PTOKTIQG.js} +2 -2
- package/dist/chunks/{chunk-O5FDMVL3.js → chunk-QWG7NASD.js} +2 -2
- package/dist/chunks/chunk-SL4JJJQJ.js +3 -0
- package/dist/chunks/chunk-SL4JJJQJ.js.map +7 -0
- package/dist/chunks/{chunk-XB5KC2EZ.js → chunk-T2VC3EEL.js} +1 -1
- package/dist/chunks/{chunk-XB5KC2EZ.js.map → chunk-T2VC3EEL.js.map} +1 -1
- package/dist/chunks/{chunk-2PNGGCQJ.js → chunk-UECOGSWM.js} +2 -2
- package/dist/chunks/{chunk-2PNGGCQJ.js.map → chunk-UECOGSWM.js.map} +1 -1
- package/dist/chunks/chunk-UHV6XIDG.js +2 -0
- package/dist/chunks/chunk-UHV6XIDG.js.map +7 -0
- package/dist/chunks/{chunk-5RHNR3TH.js → chunk-VKZALU57.js} +1 -1
- package/dist/chunks/{chunk-5RHNR3TH.js.map → chunk-VKZALU57.js.map} +1 -1
- package/dist/chunks/chunk-W7IHIVSE.js +2 -0
- package/dist/chunks/{chunk-XWCLYB5C.js.map → chunk-W7IHIVSE.js.map} +4 -4
- package/dist/chunks/{chunk-NLS74JSZ.js → chunk-WUOXZAGN.js} +2 -2
- package/dist/chunks/{chunk-NLS74JSZ.js.map → chunk-WUOXZAGN.js.map} +1 -1
- package/dist/chunks/{renderSvg-YNA5VK6A.js → renderSvg-E5Q7MLCH.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +4 -4
- package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +12 -2
- package/src/GraphAddTrackWorkflow/graphManifest.ts +16 -9
- package/src/GraphGenomeView/components/HoverTooltip.tsx +8 -0
- package/src/GraphGenomeView/pane/paneBase.ts +14 -9
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +94 -41
- package/src/RgfaTabixAdapter/configSchema.ts +35 -4
- package/src/RgfaTabixAdapter/format2.test.ts +128 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.segs.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f2.graph.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f2.graph.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/walks5_skip.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks5_skip.GRCh38.walks.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/walkFiles.test.ts +21 -0
- package/src/RgfaTabixAdapter/walkReader.ts +20 -15
- package/src/graphTrackDefaults/index.ts +2 -2
- package/src/locationName.ts +2 -0
- package/src/version.ts +1 -1
- package/dist/chunks/GbzBaseSyntenyAdapter-EGNUOKB2.js +0 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-EGNUOKB2.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-BAEMS5WG.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-JPTUWO2Z.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-JPTUWO2Z.js.map +0 -7
- package/dist/chunks/chunk-AH5UWTAV.js +0 -2
- package/dist/chunks/chunk-AH5UWTAV.js.map +0 -7
- package/dist/chunks/chunk-DG3E6BSX.js +0 -3
- package/dist/chunks/chunk-DG3E6BSX.js.map +0 -7
- package/dist/chunks/chunk-HLAQQI6G.js +0 -2
- package/dist/chunks/chunk-HLAQQI6G.js.map +0 -7
- package/dist/chunks/chunk-IKJZPPOA.js +0 -2
- package/dist/chunks/chunk-IKJZPPOA.js.map +0 -7
- package/dist/chunks/chunk-JMRW3IGK.js.map +0 -7
- package/dist/chunks/chunk-Q7QC2EQU.js +0 -2
- package/dist/chunks/chunk-RHYBJCA4.js +0 -2
- package/dist/chunks/chunk-RHYBJCA4.js.map +0 -7
- package/dist/chunks/chunk-X7YIGIYW.js +0 -2
- package/dist/chunks/chunk-X7YIGIYW.js.map +0 -7
- package/dist/chunks/chunk-XWCLYB5C.js +0 -2
- package/dist/chunks/chunk-ZGTG74GE.js +0 -3
- package/dist/chunks/chunk-ZGTG74GE.js.map +0 -7
- /package/dist/chunks/{AddTrackWorkflow-RJSOQZYQ.js.map → AddTrackWorkflow-7CXTSSMR.js.map} +0 -0
- /package/dist/chunks/{GraphGenomeView-667ZUNDI.js.map → GraphGenomeView-2AKRM74B.js.map} +0 -0
- /package/dist/chunks/{GraphTrackSettingsDialog-7M3Q635V.js.map → GraphTrackSettingsDialog-7XRZQW6T.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-4F34Q4PY.js.map → HighlightColorDialog-LCBGD2ZA.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-BAEMS5WG.js.map → LinearGraphDisplay-WWQ73BO5.js.map} +0 -0
- /package/dist/chunks/{MinigraphBubbleAdapter-G25V6MZL.js.map → MinigraphBubbleAdapter-4OOBJ44C.js.map} +0 -0
- /package/dist/chunks/{WalkFormsAdapter-PBN7BJZW.js.map → WalkFormsAdapter-LTAKACZD.js.map} +0 -0
- /package/dist/chunks/{WalkTabixSyntenyAdapter-27WDE5JF.js.map → WalkTabixSyntenyAdapter-B6V3YNW7.js.map} +0 -0
- /package/dist/chunks/{chunk-Q7QC2EQU.js.map → chunk-HAAABGE7.js.map} +0 -0
- /package/dist/chunks/{chunk-JOYKGTDE.js.map → chunk-MCBYBZZJ.js.map} +0 -0
- /package/dist/chunks/{chunk-I6F25EXY.js.map → chunk-PTOKTIQG.js.map} +0 -0
- /package/dist/chunks/{chunk-O5FDMVL3.js.map → chunk-QWG7NASD.js.map} +0 -0
- /package/dist/chunks/{renderSvg-YNA5VK6A.js.map → renderSvg-E5Q7MLCH.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbrowse-plugin-graphgenomeviewer",
|
|
3
|
-
"version": "8.0
|
|
3
|
+
"version": "8.2.0",
|
|
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": {
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@gmod/bgzf-filehandle": "^6.8.0",
|
|
29
29
|
"@gmod/gbz-base": "^7.4.1",
|
|
30
|
-
"@gmod/tabix": "^3.
|
|
30
|
+
"@gmod/tabix": "^3.13.0",
|
|
31
31
|
"@jbrowse/cigar-utils": "5.0.0-beta.18",
|
|
32
32
|
"@jbrowse/core": "5.0.0-beta.18",
|
|
33
33
|
"@jbrowse/display-kit": "5.0.0-beta.18",
|
|
34
34
|
"@jbrowse/display-ui": "5.0.0-beta.18",
|
|
35
|
-
"@jbrowse/graph-walks": "^4.
|
|
35
|
+
"@jbrowse/graph-walks": "^4.4.0",
|
|
36
36
|
"@jbrowse/render-core": "5.0.0-beta.18",
|
|
37
37
|
"@jbrowse/synteny-core": "5.0.0-beta.18"
|
|
38
38
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@emotion/react": "^11.14.0",
|
|
41
41
|
"@emotion/styled": "^11.14.1",
|
|
42
42
|
"@eslint/js": "^10.0.1",
|
|
43
|
-
"@jbrowse/bandage-core": "^12.
|
|
43
|
+
"@jbrowse/bandage-core": "^12.1.1",
|
|
44
44
|
"@jbrowse/cli": "^4.3.0",
|
|
45
45
|
"@jbrowse/mobx-state-tree": "^6.10.0",
|
|
46
46
|
"@jbrowse/plugin-linear-genome-view": "5.0.0-beta.18",
|
|
@@ -2,6 +2,7 @@ import { makeIndexType } from '@jbrowse/core/util/tracks'
|
|
|
2
2
|
|
|
3
3
|
import { besideSegments, segmentsSibling } from './graphManifest'
|
|
4
4
|
import {
|
|
5
|
+
GRAPH_SUFFIX,
|
|
5
6
|
SEGMENTS_SUFFIX,
|
|
6
7
|
WALKS_SUFFIX,
|
|
7
8
|
locationName,
|
|
@@ -21,7 +22,7 @@ export type GraphFileChoice =
|
|
|
21
22
|
| 'MinigraphBubbleAdapter'
|
|
22
23
|
|
|
23
24
|
export const GRAPH_FILE_LABELS: Record<GraphFileChoice, string> = {
|
|
24
|
-
RgfaTabixAdapter: '
|
|
25
|
+
RgfaTabixAdapter: 'Indexed GFA (gfa-to-tabix .segs.bed.gz or .graph.bed.gz)',
|
|
25
26
|
RgfaTabixWalks: 'Haplotype walks (.walks.bed.gz)',
|
|
26
27
|
GbzBaseSyntenyAdapter: 'GBZ database (.gbz.db)',
|
|
27
28
|
MinigraphBubbleAdapter: 'Minigraph bubbles (tabix BED)',
|
|
@@ -29,7 +30,7 @@ export const GRAPH_FILE_LABELS: Record<GraphFileChoice, string> = {
|
|
|
29
30
|
|
|
30
31
|
export const GRAPH_FILE_FIELDS: Record<GraphFileChoice, string> = {
|
|
31
32
|
RgfaTabixAdapter:
|
|
32
|
-
|
|
33
|
+
"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",
|
|
33
34
|
RgfaTabixWalks:
|
|
34
35
|
'Path to a walk file (<prefix>.<sample>.walks.bed.gz from gfa-to-tabix --walks, for the sample this assembly is)',
|
|
35
36
|
GbzBaseSyntenyAdapter: 'Path to the .gbz.db written by gbz-base construct',
|
|
@@ -187,6 +188,15 @@ export function buildAdapterConfig({
|
|
|
187
188
|
...panSN(assembly, sample),
|
|
188
189
|
}
|
|
189
190
|
}
|
|
191
|
+
if (locationName(loc).endsWith(GRAPH_SUFFIX)) {
|
|
192
|
+
return {
|
|
193
|
+
type: 'RgfaTabixAdapter',
|
|
194
|
+
graphLocation: loc,
|
|
195
|
+
graphIndex: tabixIndex(loc, indexLoc),
|
|
196
|
+
...referencePanSN(assembly, sample, manifest?.reference),
|
|
197
|
+
...coarseTier(loc, indexLoc, manifest?.tier),
|
|
198
|
+
}
|
|
199
|
+
}
|
|
190
200
|
const links = linksLocation(loc)
|
|
191
201
|
return {
|
|
192
202
|
type: 'RgfaTabixAdapter',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { openLocation } from '@jbrowse/core/util/io'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
GRAPH_SUFFIX,
|
|
4
5
|
SEGMENTS_SUFFIX,
|
|
5
6
|
locationName,
|
|
6
7
|
readsSiblings,
|
|
@@ -76,21 +77,27 @@ export function besideSegments(segs: FileLocation, fileName: string) {
|
|
|
76
77
|
return renamed(segs, name => name.replace(/[^/\\]*$/, fileName))
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// The suffix of a gfa-to-tabix index file: format 1's segments BED or format
|
|
81
|
+
// 2's graph BED
|
|
82
|
+
function indexSuffix(loc: FileLocation) {
|
|
83
|
+
const name = locationName(loc)
|
|
84
|
+
return [SEGMENTS_SUFFIX, GRAPH_SUFFIX].find(suffix => name.endsWith(suffix))
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
//
|
|
87
|
+
// A segments BED, or format 2's graph BED, the rest of the index and the
|
|
88
|
+
// manifest can be found beside
|
|
89
|
+
export function isGraphIndexLocation(loc: FileLocation) {
|
|
90
|
+
return readsSiblings(loc) && indexSuffix(loc) !== undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// `<prefix><suffix>` beside `<prefix>.segs.bed.gz` or `<prefix>.graph.bed.gz`
|
|
85
94
|
export function segmentsSibling(segs: FileLocation, suffix: string) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
name => `${name.slice(0, -SEGMENTS_SUFFIX.length)}${suffix}`,
|
|
89
|
-
)
|
|
95
|
+
const own = indexSuffix(segs) ?? SEGMENTS_SUFFIX
|
|
96
|
+
return renamed(segs, name => `${name.slice(0, -own.length)}${suffix}`)
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
export function manifestLocation(segs: FileLocation) {
|
|
93
|
-
return
|
|
100
|
+
return isGraphIndexLocation(segs)
|
|
94
101
|
? segmentsSibling(segs, '.graph.json')
|
|
95
102
|
: undefined
|
|
96
103
|
}
|
|
@@ -63,6 +63,14 @@ function hoverTip(
|
|
|
63
63
|
{own.sample} {regionLabel(own)} (rank {node.stable?.rank})
|
|
64
64
|
</>
|
|
65
65
|
) : null}
|
|
66
|
+
{/* gfa-to-tabix --max-component left its component off the
|
|
67
|
+
reference, so the cut holds only its ends */}
|
|
68
|
+
{node.moved ? (
|
|
69
|
+
<>
|
|
70
|
+
<br />
|
|
71
|
+
End of a component too large to draw here
|
|
72
|
+
</>
|
|
73
|
+
) : null}
|
|
66
74
|
</>
|
|
67
75
|
),
|
|
68
76
|
}
|
|
@@ -7,7 +7,7 @@ import { viewportOf } from '@jbrowse/bandage-core/viewport'
|
|
|
7
7
|
import { types } from '@jbrowse/mobx-state-tree'
|
|
8
8
|
import { RenderLifecycleMixin } from '@jbrowse/render-core/RenderLifecycleMixin'
|
|
9
9
|
|
|
10
|
-
import { SEGMENTS_SUFFIX } from '../../locationName.ts'
|
|
10
|
+
import { GRAPH_SUFFIX, SEGMENTS_SUFFIX } from '../../locationName.ts'
|
|
11
11
|
import { lenientOptionalEnum } from '../lenientEnum'
|
|
12
12
|
|
|
13
13
|
import type { MorphEntering } from './morph'
|
|
@@ -76,18 +76,23 @@ export const TUBE_MAP_FOLDS = [
|
|
|
76
76
|
{ bp: 1000, label: 'Under 1 kb' },
|
|
77
77
|
]
|
|
78
78
|
|
|
79
|
-
// The rGFA index prefix an adapter config names, from
|
|
80
|
-
//
|
|
79
|
+
// The rGFA index prefix an adapter config names, from any spelling the config
|
|
80
|
+
// schema accepts: the `uri` shorthand, or an explicit segments location or
|
|
81
|
+
// format 2 graph location.
|
|
81
82
|
export function bubblePrefix(adapterConfig: Record<string, unknown>) {
|
|
82
83
|
if (typeof adapterConfig.uri === 'string') {
|
|
83
84
|
return adapterConfig.uri
|
|
84
85
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
for (const [slot, suffix] of [
|
|
87
|
+
['segmentsLocation', SEGMENTS_SUFFIX],
|
|
88
|
+
['graphLocation', GRAPH_SUFFIX],
|
|
89
|
+
] as const) {
|
|
90
|
+
const uri = (adapterConfig[slot] as { uri?: string } | undefined)?.uri
|
|
91
|
+
if (typeof uri === 'string' && uri.endsWith(suffix)) {
|
|
92
|
+
return uri.slice(0, -suffix.length)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined
|
|
91
96
|
}
|
|
92
97
|
export const HOVER_BRIGHTEN = 1.15
|
|
93
98
|
export const SELECT_BRIGHTEN = 1.6
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
2
2
|
import { SimpleFeature, updateStatus } from '@jbrowse/core/util'
|
|
3
3
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
parseGraphHeader,
|
|
6
|
+
parseGraphRow,
|
|
7
|
+
parseSegmentRow,
|
|
8
|
+
rgfaCut,
|
|
9
|
+
segmentSamples,
|
|
10
|
+
} from '@jbrowse/graph-walks'
|
|
5
11
|
|
|
6
12
|
import { locationIsSet } from '../locationName.ts'
|
|
7
13
|
import { PanSNRefNames, openTabixSlot } from '../panSNTabix.ts'
|
|
@@ -15,10 +21,13 @@ import type { TabixIndexedFile } from '@gmod/tabix'
|
|
|
15
21
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
16
22
|
import type { Feature } from '@jbrowse/core/util'
|
|
17
23
|
import type { FileLocation, Region } from '@jbrowse/core/util/types'
|
|
24
|
+
import type { RgfaSegment } from '@jbrowse/graph-walks'
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
links: TabixIndexedFile
|
|
26
|
+
// gfa-to-tabix's format 1 pair, or format 2's one file
|
|
27
|
+
type GraphIndex = (
|
|
28
|
+
| { segments: TabixIndexedFile; links: TabixIndexedFile }
|
|
29
|
+
| { graph: TabixIndexedFile }
|
|
30
|
+
) & {
|
|
22
31
|
walks?: WalkIndex
|
|
23
32
|
refNames: PanSNRefNames
|
|
24
33
|
}
|
|
@@ -31,9 +40,33 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
|
|
|
31
40
|
|
|
32
41
|
// refNames off the SEGMENT index: it is the one that names every stable
|
|
33
42
|
// sequence the graph is anchored to, where the link index only names those a
|
|
34
|
-
// link touches.
|
|
35
|
-
// and it has no segment file.
|
|
43
|
+
// link touches. Format 2's one file names them all. A walk-indexed graph's
|
|
44
|
+
// walk file names the same sequences, and it has no segment file.
|
|
36
45
|
private openIndex(under: string[]): GraphIndex {
|
|
46
|
+
const walksLocation: FileLocation = this.getConf('walksLocation')
|
|
47
|
+
const walks =
|
|
48
|
+
under.length === 0 && locationIsSet(walksLocation)
|
|
49
|
+
? new WalkIndex(
|
|
50
|
+
openTabixSlot(this, ['walksLocation'], ['walksIndex']),
|
|
51
|
+
this,
|
|
52
|
+
)
|
|
53
|
+
: undefined
|
|
54
|
+
const graphLocation: FileLocation =
|
|
55
|
+
under.length === 0
|
|
56
|
+
? this.getConf('graphLocation')
|
|
57
|
+
: this.getConf(['coarse', 'graphLocation'])
|
|
58
|
+
if (locationIsSet(graphLocation)) {
|
|
59
|
+
const graph = openTabixSlot(
|
|
60
|
+
this,
|
|
61
|
+
[...under, 'graphLocation'],
|
|
62
|
+
[...under, 'graphIndex'],
|
|
63
|
+
)
|
|
64
|
+
return {
|
|
65
|
+
graph,
|
|
66
|
+
walks,
|
|
67
|
+
refNames: new PanSNRefNames(walks?.file ?? graph, this),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
37
70
|
const segments = openTabixSlot(
|
|
38
71
|
this,
|
|
39
72
|
[...under, 'segmentsLocation'],
|
|
@@ -44,14 +77,6 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
|
|
|
44
77
|
[...under, 'linksLocation'],
|
|
45
78
|
[...under, 'linksIndex'],
|
|
46
79
|
)
|
|
47
|
-
const walksLocation: FileLocation = this.getConf('walksLocation')
|
|
48
|
-
const walks =
|
|
49
|
-
under.length === 0 && locationIsSet(walksLocation)
|
|
50
|
-
? new WalkIndex(
|
|
51
|
-
openTabixSlot(this, ['walksLocation'], ['walksIndex']),
|
|
52
|
-
this,
|
|
53
|
-
)
|
|
54
|
-
: undefined
|
|
55
80
|
return {
|
|
56
81
|
segments,
|
|
57
82
|
links,
|
|
@@ -142,41 +167,69 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
|
|
|
142
167
|
}
|
|
143
168
|
}
|
|
144
169
|
|
|
145
|
-
|
|
170
|
+
// Each segment row under the window, from the segment file or from format
|
|
171
|
+
// 2's node rows, with whether the index is anchored
|
|
172
|
+
private async segmentRows(
|
|
146
173
|
tabixRefName: string,
|
|
147
174
|
query: Region,
|
|
148
175
|
opts: BaseOptions,
|
|
149
|
-
|
|
176
|
+
each: (segment: RgfaSegment, anchored: boolean) => void,
|
|
150
177
|
) {
|
|
151
|
-
|
|
152
|
-
|
|
178
|
+
const index = this.fine
|
|
179
|
+
if ('graph' in index) {
|
|
180
|
+
const header = parseGraphHeader(
|
|
181
|
+
await index.graph.getHeaderLines({ signal: opts.signal }),
|
|
182
|
+
)
|
|
183
|
+
return getLines(index.graph, tabixRefName, query.start, query.end, {
|
|
153
184
|
signal: opts.signal,
|
|
154
185
|
lineCallback: line => {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (anchored && segment.refName !== tabixRefName) {
|
|
159
|
-
return
|
|
186
|
+
const row = parseGraphRow(line, header)
|
|
187
|
+
if (row?.kind === 'N') {
|
|
188
|
+
each(row.segment, header.anchored)
|
|
160
189
|
}
|
|
161
|
-
// `samples` lists the haplotypes whose paths visit the segment and
|
|
162
|
-
// `sampleCount` counts them. A lane's color reads the count, and
|
|
163
|
-
// a count in jexl would otherwise rely on `.length` resolving
|
|
164
|
-
// through a member access on an array.
|
|
165
|
-
const samples = segmentSamples(segment)
|
|
166
|
-
emit(
|
|
167
|
-
new SimpleFeature({
|
|
168
|
-
uniqueId: segment.id,
|
|
169
|
-
refName: query.refName,
|
|
170
|
-
start: segment.start,
|
|
171
|
-
end: segment.end,
|
|
172
|
-
name: segment.id,
|
|
173
|
-
type: 'segment',
|
|
174
|
-
rank: segment.rank,
|
|
175
|
-
stableName: segment.refName,
|
|
176
|
-
...(samples && { samples, sampleCount: samples.length }),
|
|
177
|
-
}),
|
|
178
|
-
)
|
|
179
190
|
},
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
return getLines(index.segments, tabixRefName, query.start, query.end, {
|
|
194
|
+
signal: opts.signal,
|
|
195
|
+
lineCallback: line => {
|
|
196
|
+
const { segment, anchored } = parseSegmentRow(line)
|
|
197
|
+
each(segment, anchored)
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private async segmentFeatures(
|
|
203
|
+
tabixRefName: string,
|
|
204
|
+
query: Region,
|
|
205
|
+
opts: BaseOptions,
|
|
206
|
+
emit: (feature: Feature) => void,
|
|
207
|
+
) {
|
|
208
|
+
await updateStatus('Downloading segments', opts.statusCallback, () =>
|
|
209
|
+
this.segmentRows(tabixRefName, query, opts, (segment, anchored) => {
|
|
210
|
+
// An anchored index also returns the alleles under the region,
|
|
211
|
+
// which lie on other stable sequences and have no place on this one
|
|
212
|
+
if (anchored && segment.refName !== tabixRefName) {
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
// `samples` lists the haplotypes whose paths visit the segment and
|
|
216
|
+
// `sampleCount` counts them. A lane's color reads the count, and
|
|
217
|
+
// a count in jexl would otherwise rely on `.length` resolving
|
|
218
|
+
// through a member access on an array.
|
|
219
|
+
const samples = segmentSamples(segment)
|
|
220
|
+
emit(
|
|
221
|
+
new SimpleFeature({
|
|
222
|
+
uniqueId: segment.id,
|
|
223
|
+
refName: query.refName,
|
|
224
|
+
start: segment.start,
|
|
225
|
+
end: segment.end,
|
|
226
|
+
name: segment.id,
|
|
227
|
+
type: 'segment',
|
|
228
|
+
rank: segment.rank,
|
|
229
|
+
stableName: segment.refName,
|
|
230
|
+
...(samples && { samples, sampleCount: samples.length }),
|
|
231
|
+
}),
|
|
232
|
+
)
|
|
180
233
|
}),
|
|
181
234
|
)
|
|
182
235
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
2
2
|
import { WALK_BUDGETS } from '@jbrowse/graph-walks'
|
|
3
3
|
|
|
4
|
+
import { GRAPH_SUFFIX } from '../locationName.ts'
|
|
4
5
|
import {
|
|
5
6
|
fileLocationSlot,
|
|
6
7
|
tabixIndexSchema,
|
|
@@ -21,7 +22,10 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
|
21
22
|
* where the adapter follows links onto other contigs.
|
|
22
23
|
*
|
|
23
24
|
* The `uri` shorthand takes the prefix the build script was given and resolves
|
|
24
|
-
* `<uri>.segs.bed.gz`, `<uri>.links.bed.gz` and their `.tbi` indexes.
|
|
25
|
+
* `<uri>.segs.bed.gz`, `<uri>.links.bed.gz` and their `.tbi` indexes. With
|
|
26
|
+
* `format: 2` it resolves gfa-to-tabix's format 2 instead, `<uri>.graph.bed.gz`
|
|
27
|
+
* and its index: nodes and links in one file, a plain GFA's carriers as bits,
|
|
28
|
+
* from `gfa-to-tabix --format 2`. `coarse`
|
|
25
29
|
* takes the same shorthand for a second pair at one node per bubble, built by
|
|
26
30
|
* `gfa-to-tabix fold`, plus either the zoom past which a graph view following
|
|
27
31
|
* a linear view cuts it (`aboveBpPerPx`) or the size it was folded at
|
|
@@ -56,16 +60,23 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
|
56
60
|
*/
|
|
57
61
|
|
|
58
62
|
function prefixLocations(snap: Record<string, unknown>) {
|
|
59
|
-
const { uri, baseUri, csi } = snap
|
|
60
|
-
|
|
63
|
+
const { uri, baseUri, csi, format } = snap
|
|
64
|
+
if (typeof uri !== 'string') {
|
|
65
|
+
return snap
|
|
66
|
+
}
|
|
67
|
+
return format === 2
|
|
61
68
|
? {
|
|
69
|
+
...snap,
|
|
70
|
+
graphLocation: { uri: `${uri}${GRAPH_SUFFIX}`, baseUri },
|
|
71
|
+
graphIndex: tbiBeside(`${uri}${GRAPH_SUFFIX}`, baseUri, csi),
|
|
72
|
+
}
|
|
73
|
+
: {
|
|
62
74
|
...snap,
|
|
63
75
|
segmentsLocation: { uri: `${uri}.segs.bed.gz`, baseUri },
|
|
64
76
|
segmentsIndex: tbiBeside(`${uri}.segs.bed.gz`, baseUri, csi),
|
|
65
77
|
linksLocation: { uri: `${uri}.links.bed.gz`, baseUri },
|
|
66
78
|
linksIndex: tbiBeside(`${uri}.links.bed.gz`, baseUri, csi),
|
|
67
79
|
}
|
|
68
|
-
: snap
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
function normalizeSnapshot(snap: Record<string, unknown>) {
|
|
@@ -119,6 +130,15 @@ const RgfaTabixCoarseTier = ConfigurationSchema('RgfaTabixCoarseTier', {
|
|
|
119
130
|
'RgfaCoarseLinksIndex',
|
|
120
131
|
'/path/to/graph.tier.links.bed.gz.tbi',
|
|
121
132
|
),
|
|
133
|
+
/**
|
|
134
|
+
* #slot
|
|
135
|
+
* format 2's one file in place of the pair; see the adapter's `graphLocation`
|
|
136
|
+
*/
|
|
137
|
+
graphLocation: fileLocationSlot(''),
|
|
138
|
+
/**
|
|
139
|
+
* #slot
|
|
140
|
+
*/
|
|
141
|
+
graphIndex: tabixIndexSchema('RgfaCoarseGraphIndex'),
|
|
122
142
|
})
|
|
123
143
|
|
|
124
144
|
const RgfaTabixAdapter = ConfigurationSchema(
|
|
@@ -150,6 +170,17 @@ const RgfaTabixAdapter = ConfigurationSchema(
|
|
|
150
170
|
'RgfaLinksIndex',
|
|
151
171
|
'/path/to/graph.links.bed.gz.tbi',
|
|
152
172
|
),
|
|
173
|
+
/**
|
|
174
|
+
* #slot
|
|
175
|
+
* gfa-to-tabix's format 2, `<prefix>.graph.bed.gz`: segments and links in
|
|
176
|
+
* one file, read in place of `segmentsLocation` and `linksLocation`.
|
|
177
|
+
* Unset means the graph is the pair.
|
|
178
|
+
*/
|
|
179
|
+
graphLocation: fileLocationSlot(''),
|
|
180
|
+
/**
|
|
181
|
+
* #slot
|
|
182
|
+
*/
|
|
183
|
+
graphIndex: tabixIndexSchema('RgfaGraphIndex'),
|
|
153
184
|
/**
|
|
154
185
|
* #slot
|
|
155
186
|
* a `gfa-to-tabix --walks` file in walk format 5: per reference chunk,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { readConfObject } from '@jbrowse/core/configuration'
|
|
2
|
+
import { firstValueFrom } from 'rxjs'
|
|
3
|
+
import { toArray } from 'rxjs/operators'
|
|
4
|
+
|
|
5
|
+
import Adapter from './RgfaTabixAdapter.ts'
|
|
6
|
+
import configSchema from './configSchema.ts'
|
|
7
|
+
import { buildAdapterConfig } from '../GraphAddTrackWorkflow/buildTrackConfig.ts'
|
|
8
|
+
import { bubblePrefix } from '../GraphGenomeView/pane/paneBase.ts'
|
|
9
|
+
|
|
10
|
+
// gfa-to-tabix 1.5's anchored index of test_data/ecoli_pggb_subgraph.gfa on
|
|
11
|
+
// K12, in format 1 (`pggb_ecoli_f1`, a segs/links pair) and format 2
|
|
12
|
+
// (`pggb_ecoli_f2.graph.bed.gz`, one file, carriers as SB:H: bits)
|
|
13
|
+
const DATA = require
|
|
14
|
+
.resolve('./test_data/pggb_ecoli_f2.graph.bed.gz')
|
|
15
|
+
.replace(/pggb_ecoli_f2\.graph\.bed\.gz$/, '')
|
|
16
|
+
const local = (path: string) => ({
|
|
17
|
+
localPath: path,
|
|
18
|
+
locationType: 'LocalPathLocation',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
function formatOne() {
|
|
22
|
+
const f = `${DATA}pggb_ecoli_f1`
|
|
23
|
+
return new Adapter(
|
|
24
|
+
configSchema.create({
|
|
25
|
+
segmentsLocation: local(`${f}.segs.bed.gz`),
|
|
26
|
+
segmentsIndex: { location: local(`${f}.segs.bed.gz.tbi`) },
|
|
27
|
+
linksLocation: local(`${f}.links.bed.gz`),
|
|
28
|
+
linksIndex: { location: local(`${f}.links.bed.gz.tbi`) },
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatTwo() {
|
|
34
|
+
const f = `${DATA}pggb_ecoli_f2.graph.bed.gz`
|
|
35
|
+
return new Adapter(
|
|
36
|
+
configSchema.create({
|
|
37
|
+
graphLocation: local(f),
|
|
38
|
+
graphIndex: { location: local(`${f}.tbi`) },
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const window = (start: number) => ({
|
|
44
|
+
refName: 'chr',
|
|
45
|
+
assemblyName: 'K12',
|
|
46
|
+
start,
|
|
47
|
+
end: start + 60,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// A segment's carriers as a set, since format 2 names them in #samples order
|
|
51
|
+
const sorted = (samples: unknown) =>
|
|
52
|
+
Array.isArray(samples) ? [...samples].sort() : samples
|
|
53
|
+
|
|
54
|
+
test('format 2 names the same reference sequences', async () => {
|
|
55
|
+
expect(await formatTwo().getRefNames()).toEqual(
|
|
56
|
+
await formatOne().getRefNames(),
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('format 2 gives the carriage lane the same segments and carriers', async () => {
|
|
61
|
+
const features = (adapter: Adapter, start: number) =>
|
|
62
|
+
firstValueFrom(adapter.getFeatures(window(start)).pipe(toArray()))
|
|
63
|
+
for (let start = 1004500; start < 1004961; start += 41) {
|
|
64
|
+
const one = await features(formatOne(), start)
|
|
65
|
+
const two = await features(formatTwo(), start)
|
|
66
|
+
const shape = (f: (typeof one)[number]) => ({
|
|
67
|
+
name: f.get('name'),
|
|
68
|
+
start: f.get('start'),
|
|
69
|
+
end: f.get('end'),
|
|
70
|
+
rank: f.get('rank'),
|
|
71
|
+
samples: sorted(f.get('samples')),
|
|
72
|
+
sampleCount: f.get('sampleCount'),
|
|
73
|
+
})
|
|
74
|
+
expect(two.map(shape)).toEqual(one.map(shape))
|
|
75
|
+
expect(two.every(f => (f.get('sampleCount') as number) > 0)).toBe(true)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('format 2 cuts the same graph', async () => {
|
|
80
|
+
const lines = (gfa: unknown) =>
|
|
81
|
+
String(gfa)
|
|
82
|
+
.split('\n')
|
|
83
|
+
.map(l => l.replace(/\tSM:Z:\S*/, ''))
|
|
84
|
+
for (let start = 1004500; start < 1004961; start += 41) {
|
|
85
|
+
const one = await formatOne().getSubgraph(window(start))
|
|
86
|
+
const two = await formatTwo().getSubgraph(window(start))
|
|
87
|
+
expect(lines(two)).toEqual(lines(one))
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('the uri shorthand with format: 2 resolves the one file', () => {
|
|
92
|
+
const config = configSchema.create({
|
|
93
|
+
uri: 'hprc',
|
|
94
|
+
format: 2,
|
|
95
|
+
baseUri: 'https://example.com/',
|
|
96
|
+
})
|
|
97
|
+
expect(readConfObject(config, 'graphLocation')).toMatchObject({
|
|
98
|
+
uri: 'hprc.graph.bed.gz',
|
|
99
|
+
baseUri: 'https://example.com/',
|
|
100
|
+
})
|
|
101
|
+
expect(readConfObject(config, ['graphIndex', 'location'])).toMatchObject({
|
|
102
|
+
uri: 'hprc.graph.bed.gz.tbi',
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('Open track takes a format 2 graph file', () => {
|
|
107
|
+
const loc = {
|
|
108
|
+
uri: 'https://example.com/hprc.graph.bed.gz',
|
|
109
|
+
locationType: 'UriLocation' as const,
|
|
110
|
+
}
|
|
111
|
+
const adapter = buildAdapterConfig({
|
|
112
|
+
choice: 'RgfaTabixAdapter',
|
|
113
|
+
loc,
|
|
114
|
+
indexLoc: undefined,
|
|
115
|
+
assembly: 'hg38',
|
|
116
|
+
sample: 'GRCh38',
|
|
117
|
+
})
|
|
118
|
+
expect(adapter).toMatchObject({
|
|
119
|
+
type: 'RgfaTabixAdapter',
|
|
120
|
+
graphLocation: loc,
|
|
121
|
+
graphIndex: {
|
|
122
|
+
location: { uri: 'https://example.com/hprc.graph.bed.gz.tbi' },
|
|
123
|
+
},
|
|
124
|
+
assemblyNameToPanSN: { hg38: 'GRCh38' },
|
|
125
|
+
})
|
|
126
|
+
expect(adapter).not.toHaveProperty('segmentsLocation')
|
|
127
|
+
expect(bubblePrefix(adapter)).toBe('https://example.com/hprc')
|
|
128
|
+
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -365,6 +365,27 @@ test('a walk leaving the window is measured to where it rejoins the reference',
|
|
|
365
365
|
])
|
|
366
366
|
})
|
|
367
367
|
|
|
368
|
+
// gfa-to-tabix 1.5 --walks --chunk 10 of ten 10 bp reference nodes:
|
|
369
|
+
// W GRCh38 0 chr1 >1>2>3>4>5>6>7>8>9>10
|
|
370
|
+
// W HG002 1 ctg >1>2>9>10 a deletion of 20-80, whose chunks hold skip rows
|
|
371
|
+
const skipPrefix = fixture('walks5_skip')
|
|
372
|
+
|
|
373
|
+
test('a haplotype that jumps over the window draws as a deletion row', async () => {
|
|
374
|
+
const cut = (await makeAdapter(skipPrefix).getSubgraph(chr1(31, 60), {
|
|
375
|
+
walkRows: true,
|
|
376
|
+
})) as WalkCut
|
|
377
|
+
const rows = walkRowsOf(cut.walkRowRuns!).rows
|
|
378
|
+
expect(
|
|
379
|
+
rows.map(({ label, bp, gapBp, complete }) => ({
|
|
380
|
+
label,
|
|
381
|
+
bp,
|
|
382
|
+
gapBp,
|
|
383
|
+
complete,
|
|
384
|
+
})),
|
|
385
|
+
).toEqual([{ label: 'HG002#1', bp: 0, gapBp: 29, complete: true }])
|
|
386
|
+
expect(rows[0]!.runs.map(run => run.cause)).toEqual(['deleted'])
|
|
387
|
+
})
|
|
388
|
+
|
|
368
389
|
test('a walk the read cuts off is read on to the chunk its row names', async () => {
|
|
369
390
|
const getLines = vi.spyOn(TabixIndexedFile.prototype, 'getLines')
|
|
370
391
|
const cut = (await makeAdapter(followPrefix).getSubgraph(chr1(1000, 1500), {
|
|
@@ -55,9 +55,10 @@ export class WalkIndex {
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* The graph display's cut of a window: the reference walk, the walks of
|
|
58
|
-
* `haplotypes` (every walk when unset), and the nodes and links they visit
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* `haplotypes` (every walk when unset), and the nodes and links they visit,
|
|
59
|
+
* with the haplotypes whose deletion spans the window as `skips`, which walk
|
|
60
|
+
* rows draw. Walk rows draw runs, so the step budget spares them, and read a
|
|
61
|
+
* short list of named walks in full where format 5 abridged them.
|
|
61
62
|
*/
|
|
62
63
|
async subgraph(
|
|
63
64
|
refName: string,
|
|
@@ -76,18 +77,22 @@ export class WalkIndex {
|
|
|
76
77
|
const asked = expand?.length
|
|
77
78
|
? (name: string) => expand.includes(name)
|
|
78
79
|
: undefined
|
|
79
|
-
const { graph, kept, fragments } = await this.reader.cut(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
const { graph, kept, fragments, skips } = await this.reader.cut(
|
|
81
|
+
refName,
|
|
82
|
+
region,
|
|
83
|
+
{
|
|
84
|
+
...budgets,
|
|
85
|
+
stepBudget: walkRows ? Infinity : budgets.stepBudget,
|
|
86
|
+
signal,
|
|
87
|
+
keep,
|
|
88
|
+
...(named || asked
|
|
89
|
+
? {
|
|
90
|
+
expand: (name: string) => !!named?.(name) || !!asked?.(name),
|
|
91
|
+
}
|
|
92
|
+
: {}),
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
return walkCutFor(graph.tables(kept, fragments, skips), region, walkRows)
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
/**
|