Skip to content

Commit 65c8ae4

Browse files
authored
Create features for single-row breakpoint split view rendering of deletions, inversions, etc. (#4561)
1 parent 7273eab commit 65c8ae4

File tree

61 files changed

+1505
-601
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1505
-601
lines changed

component_tests/cgv/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@jbrowse/plugin-variants": "file:./packed/jbrowse-plugin-variants.tgz",
4343
"@jbrowse/plugin-wiggle": "file:./packed/jbrowse-plugin-wiggle.tgz",
4444
"@jbrowse/product-core": "file:./packed/jbrowse-product-core.tgz",
45+
"@jbrowse/sv-core": "file:./packed/jbrowse-sv-core.tgz",
4546
"@jbrowse/embedded-core": "file:./packed/jbrowse-embedded-core.tgz",
4647
"@jbrowse/react-linear-genome-view": "file:./packed/jbrowse-react-linear-genome-view.tgz",
4748
"@jbrowse/react-circular-genome-view": "file:./packed/jbrowse-react-circular-genome-view.tgz"

component_tests/lgv/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@jbrowse/plugin-variants": "file:./packed/jbrowse-plugin-variants.tgz",
4343
"@jbrowse/plugin-wiggle": "file:./packed/jbrowse-plugin-wiggle.tgz",
4444
"@jbrowse/product-core": "file:./packed/jbrowse-product-core.tgz",
45+
"@jbrowse/sv-core": "file:./packed/jbrowse-sv-core.tgz",
4546
"@jbrowse/embedded-core": "file:./packed/jbrowse-embedded-core.tgz",
4647
"@jbrowse/react-linear-genome-view": "file:./packed/jbrowse-react-linear-genome-view.tgz"
4748
},

component_tests/react-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@jbrowse/plugin-spreadsheet-view": "file:./packed/jbrowse-plugin-spreadsheet-view.tgz",
5656
"@jbrowse/plugin-sv-inspector": "file:./packed/jbrowse-plugin-sv-inspector.tgz",
5757
"@jbrowse/app-core": "file:./packed/jbrowse-app-core.tgz",
58+
"@jbrowse/sv-core": "file:./packed/jbrowse-sv-core.tgz",
5859
"@jbrowse/product-core": "file:./packed/jbrowse-product-core.tgz",
5960
"@jbrowse/web-core": "file:./packed/jbrowse-web-core.tgz"
6061
},

packages/core/BaseFeatureWidget/BaseFeatureDetail/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export function isEmpty(obj: Record<string, unknown>) {
77
}
88

99
export function generateTitle(name: unknown, id: unknown, type: unknown) {
10-
return [ellipses(`${name}` || `${id}`), `${type}`]
10+
return [ellipses(`${name || id || ''}`), `${type}`]
1111
.filter(f => !!f)
1212
.join(' - ')
1313
}

packages/core/util/index.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,14 +1369,14 @@ export function mergeIntervals<T extends { start: number; end: number }>(
13691369
return intervals
13701370
}
13711371

1372-
const stack = []
1372+
const stack = [] as T[]
13731373
let top = null
13741374

13751375
// sort the intervals based on their start values
13761376
intervals = intervals.sort((a, b) => a.start - b.start)
13771377

13781378
// push the 1st interval into the stack
1379-
stack.push(intervals[0])
1379+
stack.push(intervals[0]!)
13801380

13811381
// start from the next interval and merge if needed
13821382
for (let i = 1; i < intervals.length; i++) {
@@ -1404,13 +1404,12 @@ interface BasicFeature {
14041404
end: number
14051405
start: number
14061406
refName: string
1407+
assemblyName?: string
14071408
}
14081409

1409-
// hashmap of refName->array of features
1410-
type FeaturesPerRef = Record<string, BasicFeature[]>
1411-
1412-
export function gatherOverlaps(regions: BasicFeature[]) {
1413-
const memo = {} as FeaturesPerRef
1410+
// returns new array non-overlapping features
1411+
export function gatherOverlaps(regions: BasicFeature[], w = 5000) {
1412+
const memo = {} as Record<string, BasicFeature[]>
14141413
for (const x of regions) {
14151414
if (!memo[x.refName]) {
14161415
memo[x.refName] = []
@@ -1419,7 +1418,10 @@ export function gatherOverlaps(regions: BasicFeature[]) {
14191418
}
14201419

14211420
return Object.values(memo).flatMap(group =>
1422-
mergeIntervals(group.sort((a, b) => a.start - b.start)),
1421+
mergeIntervals(
1422+
group.sort((a, b) => a.start - b.start),
1423+
w,
1424+
),
14231425
)
14241426
}
14251427

packages/sv-core/package.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "@jbrowse/sv-core",
3+
"version": "2.15.0",
4+
"description": "JBrowse 2 code shared between sv type code",
5+
"keywords": [
6+
"jbrowse",
7+
"jbrowse2",
8+
"bionode",
9+
"biojs",
10+
"genomics"
11+
],
12+
"license": "Apache-2.0",
13+
"homepage": "https://jbrowse.org",
14+
"bugs": "https://github.com/GMOD/jbrowse-components/issues",
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/GMOD/jbrowse-components.git",
18+
"directory": "packages/sv-core"
19+
},
20+
"author": "JBrowse Team",
21+
"distMain": "dist/index.js",
22+
"distModule": "esm/index.js",
23+
"srcMain": "src/index.ts",
24+
"srcModule": "src/index.ts",
25+
"main": "src/index.ts",
26+
"module": "",
27+
"files": [
28+
"dist",
29+
"esm"
30+
],
31+
"scripts": {
32+
"build:esm": "tsc --build tsconfig.build.esm.json",
33+
"build:commonjs": "tsc --build tsconfig.build.commonjs.json",
34+
"build": "npm run build:esm && npm run build:commonjs",
35+
"test": "cd ../..; jest --passWithNoTests packages/sv-core",
36+
"clean": "rimraf dist esm *.tsbuildinfo",
37+
"prebuild": "yarn clean",
38+
"prepack": "yarn build && yarn useDist",
39+
"postpack": "yarn useSrc",
40+
"useDist": "node ../../scripts/useDist.js",
41+
"useSrc": "node ../../scripts/useSrc.js"
42+
},
43+
"dependencies": {
44+
"@babel/runtime": "^7.16.3",
45+
"@jbrowse/app-core": "^2.15.0",
46+
"@jbrowse/product-core": "^2.15.0",
47+
"@mui/icons-material": "^6.0.0",
48+
"@mui/material": "^6.0.0",
49+
"clone": "^2.0.0",
50+
"copy-to-clipboard": "^3.3.1",
51+
"react-error-boundary": "^4.0.3"
52+
},
53+
"peerDependencies": {
54+
"mobx": "^6.0.0",
55+
"mobx-react": "^9.0.0",
56+
"mobx-state-tree": "^5.0.0",
57+
"react": ">=17.0.0",
58+
"react-dom": ">=17.0.0",
59+
"rxjs": "^7.0.0",
60+
"tss-react": "^4.0.0"
61+
},
62+
"publishConfig": {
63+
"access": "public"
64+
}
65+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import React, { useState } from 'react'
2+
import { observer } from 'mobx-react'
3+
import { Button, DialogActions, DialogContent } from '@mui/material'
4+
import { getSnapshot } from 'mobx-state-tree'
5+
import { Dialog } from '@jbrowse/core/ui'
6+
import { when } from 'mobx'
7+
import { getSession, Feature } from '@jbrowse/core/util'
8+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
9+
import type { Assembly } from '@jbrowse/core/assemblyManager/assembly'
10+
11+
// locals
12+
import Checkbox2 from './Checkbox2'
13+
14+
interface Display {
15+
id: string
16+
[key: string]: unknown
17+
}
18+
interface Track {
19+
id: string
20+
displays: Display[]
21+
[key: string]: unknown
22+
}
23+
24+
function stripIds(arr: Track[]) {
25+
return arr.map(({ id, displays, ...rest }) => ({
26+
...rest,
27+
displays: displays.map(({ id, ...rest }) => rest),
28+
}))
29+
}
30+
31+
const BreakendMultiLevelOptionDialog = observer(function ({
32+
model,
33+
handleClose,
34+
feature,
35+
assemblyName,
36+
viewType,
37+
view,
38+
}: {
39+
model: unknown
40+
handleClose: () => void
41+
feature: Feature
42+
view: LinearGenomeViewModel
43+
assemblyName: string
44+
viewType: {
45+
getBreakendCoveringRegions: (arg: {
46+
feature: Feature
47+
assembly: Assembly
48+
}) => {
49+
pos: number
50+
refName: string
51+
mateRefName: string
52+
matePos: number
53+
}
54+
}
55+
}) {
56+
const [copyTracks, setCopyTracks] = useState(true)
57+
const [mirror, setMirror] = useState(true)
58+
59+
return (
60+
<Dialog
61+
open
62+
onClose={handleClose}
63+
title="Multi-level breakpoint split view options"
64+
>
65+
<DialogContent>
66+
<Checkbox2
67+
checked={copyTracks}
68+
label="Copy tracks into the new view"
69+
onChange={event => {
70+
setCopyTracks(event.target.checked)
71+
}}
72+
/>
73+
74+
{copyTracks ? (
75+
<Checkbox2
76+
checked={mirror}
77+
disabled={!copyTracks}
78+
label="Mirror the copied tracks (only available if copying tracks and using two level)"
79+
onChange={event => {
80+
setMirror(event.target.checked)
81+
}}
82+
/>
83+
) : null}
84+
</DialogContent>
85+
<DialogActions>
86+
<Button
87+
onClick={() => {
88+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
89+
;(async () => {
90+
const session = getSession(model)
91+
try {
92+
const asm =
93+
await session.assemblyManager.waitForAssembly(assemblyName)
94+
if (!asm) {
95+
throw new Error(`assembly ${assemblyName} not found`)
96+
}
97+
98+
const { refName, pos, mateRefName, matePos } =
99+
viewType.getBreakendCoveringRegions({
100+
feature,
101+
assembly: asm,
102+
})
103+
104+
const viewTracks = getSnapshot(view.tracks) as Track[]
105+
const breakpointSplitView = session.addView(
106+
'BreakpointSplitView',
107+
{
108+
type: 'BreakpointSplitView',
109+
displayName: `${
110+
feature.get('name') || feature.get('id') || 'breakend'
111+
} split detail`,
112+
113+
views: [
114+
{
115+
type: 'LinearGenomeView',
116+
hideHeader: true,
117+
tracks: stripIds(getSnapshot(view.tracks)),
118+
},
119+
{
120+
type: 'LinearGenomeView',
121+
hideHeader: true,
122+
tracks: stripIds(
123+
mirror ? [...viewTracks].reverse() : viewTracks,
124+
),
125+
},
126+
],
127+
},
128+
) as unknown as { views: LinearGenomeViewModel[] }
129+
const r1 = asm.regions!.find(r => r.refName === refName)
130+
const r2 = asm.regions!.find(r => r.refName === mateRefName)
131+
if (!r1 || !r2) {
132+
throw new Error("can't find regions")
133+
}
134+
await Promise.all([
135+
breakpointSplitView.views[0]!.navToLocations([
136+
{
137+
refName,
138+
start: r1.start,
139+
end: pos,
140+
assemblyName,
141+
},
142+
{
143+
refName,
144+
start: pos + 1,
145+
end: r1.end,
146+
assemblyName,
147+
},
148+
]),
149+
breakpointSplitView.views[1]!.navToLocations([
150+
{
151+
refName: mateRefName,
152+
start: r2.start,
153+
end: matePos,
154+
assemblyName,
155+
},
156+
{
157+
refName: mateRefName,
158+
start: matePos + 1,
159+
end: r2.end,
160+
assemblyName,
161+
},
162+
]),
163+
])
164+
await when(
165+
() =>
166+
breakpointSplitView.views[1]!.initialized &&
167+
breakpointSplitView.views[0]!.initialized,
168+
)
169+
breakpointSplitView.views[1]!.zoomTo(10)
170+
breakpointSplitView.views[0]!.zoomTo(10)
171+
breakpointSplitView.views[1]!.centerAt(matePos, mateRefName)
172+
breakpointSplitView.views[0]!.centerAt(pos, refName)
173+
} catch (e) {
174+
console.error(e)
175+
session.notify(`${e}`)
176+
}
177+
})()
178+
handleClose()
179+
}}
180+
variant="contained"
181+
color="primary"
182+
autoFocus
183+
>
184+
OK
185+
</Button>
186+
<Button
187+
color="secondary"
188+
variant="contained"
189+
onClick={() => {
190+
handleClose()
191+
}}
192+
>
193+
Cancel
194+
</Button>
195+
</DialogActions>
196+
</Dialog>
197+
)
198+
})
199+
200+
export default BreakendMultiLevelOptionDialog

0 commit comments

Comments
 (0)