jbrowse-plugin-msaview 2.0.6 → 2.1.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/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.d.ts +8 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js +81 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js.map +1 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeLaunchView.d.ts +13 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeLaunchView.js +18 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeLaunchView.js.map +1 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeUtils.d.ts +5 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeUtils.js +35 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeUtils.js.map +1 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/fetchGeneList.d.ts +1 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/fetchGeneList.js +12 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/fetchGeneList.js.map +1 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/util.d.ts +4 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/util.js +38 -0
- package/dist/LaunchMsaView/components/EnsemblGeneTree/util.js.map +1 -0
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +20 -14
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js.map +1 -1
- package/dist/LaunchMsaView/components/MSALoader/MSALoader.d.ts +8 -0
- package/dist/LaunchMsaView/components/MSALoader/MSALoader.js +94 -0
- package/dist/LaunchMsaView/components/MSALoader/MSALoader.js.map +1 -0
- package/dist/LaunchMsaView/components/MSALoader/fetchGeneList.d.ts +1 -0
- package/dist/LaunchMsaView/components/MSALoader/fetchGeneList.js +12 -0
- package/dist/LaunchMsaView/components/MSALoader/fetchGeneList.js.map +1 -0
- package/dist/LaunchMsaView/components/MSALoader/preCalculatedLaunchView.d.ts +9 -0
- package/dist/LaunchMsaView/components/MSALoader/preCalculatedLaunchView.js +36 -0
- package/dist/LaunchMsaView/components/MSALoader/preCalculatedLaunchView.js.map +1 -0
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/calculateProteinSequence.d.ts +4 -12
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/calculateProteinSequence.js +2 -3
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/calculateProteinSequence.js.map +1 -1
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/fetchSeq.d.ts +8 -0
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/fetchSeq.js +23 -0
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/fetchSeq.js.map +1 -0
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/types.d.ts +10 -0
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/types.js +2 -0
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/types.js.map +1 -0
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/useFeatureSequence.d.ts +2 -6
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/useFeatureSequence.js +1 -22
- package/dist/LaunchMsaView/components/NewNCBIBlastQuery/useFeatureSequence.js.map +1 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +8 -10
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/TabPanel.d.ts +6 -0
- package/dist/LaunchMsaView/components/TabPanel.js +6 -0
- package/dist/LaunchMsaView/components/TabPanel.js.map +1 -0
- package/dist/MsaViewPanel/model.d.ts +12 -8
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +52 -40
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/package.json +1 -1
- package/src/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.tsx +143 -0
- package/src/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeLaunchView.ts +36 -0
- package/src/LaunchMsaView/components/EnsemblGeneTree/ensemblGeneTreeUtils.ts +81 -0
- package/src/LaunchMsaView/components/EnsemblGeneTree/fetchGeneList.ts +13 -0
- package/src/LaunchMsaView/components/EnsemblGeneTree/util.ts +45 -0
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +30 -19
- package/src/LaunchMsaView/components/MSALoader/MSALoader.tsx +130 -0
- package/src/LaunchMsaView/components/MSALoader/fetchGeneList.ts +13 -0
- package/src/LaunchMsaView/components/MSALoader/preCalculatedLaunchView.ts +55 -0
- package/src/LaunchMsaView/components/NewNCBIBlastQuery/calculateProteinSequence.ts +6 -20
- package/src/LaunchMsaView/components/NewNCBIBlastQuery/fetchSeq.ts +37 -0
- package/src/LaunchMsaView/components/NewNCBIBlastQuery/types.ts +11 -0
- package/src/LaunchMsaView/components/NewNCBIBlastQuery/useFeatureSequence.ts +5 -41
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +10 -16
- package/src/LaunchMsaView/components/TabPanel.tsx +19 -0
- package/dist/LaunchMsaView/components/TabUtils.d.ts +0 -8
- package/dist/LaunchMsaView/components/TabUtils.js +0 -7
- package/dist/LaunchMsaView/components/TabUtils.js.map +0 -1
- package/dist/LaunchMsaView/components/tabUtil.d.ts +0 -4
- package/dist/LaunchMsaView/components/tabUtil.js +0 -7
- package/dist/LaunchMsaView/components/tabUtil.js.map +0 -1
- package/src/LaunchMsaView/components/TabUtils.tsx +0 -25
- package/src/LaunchMsaView/components/tabUtil.ts +0 -6
package/package.json
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui'
|
|
4
|
+
import {
|
|
5
|
+
AbstractTrackModel,
|
|
6
|
+
Feature,
|
|
7
|
+
getContainingView,
|
|
8
|
+
getSession,
|
|
9
|
+
} from '@jbrowse/core/util'
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
DialogActions,
|
|
13
|
+
DialogContent,
|
|
14
|
+
MenuItem,
|
|
15
|
+
TextField,
|
|
16
|
+
Typography,
|
|
17
|
+
} from '@mui/material'
|
|
18
|
+
import { observer } from 'mobx-react'
|
|
19
|
+
import { makeStyles } from 'tss-react/mui'
|
|
20
|
+
|
|
21
|
+
import { ensemblGeneTreeLaunchView } from './ensemblGeneTreeLaunchView'
|
|
22
|
+
import { geneTreeFetcher } from './ensemblGeneTreeUtils'
|
|
23
|
+
import {
|
|
24
|
+
getGeneDisplayName,
|
|
25
|
+
getId,
|
|
26
|
+
getTranscriptDisplayName,
|
|
27
|
+
getTranscriptFeatures,
|
|
28
|
+
} from '../../util'
|
|
29
|
+
|
|
30
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
31
|
+
|
|
32
|
+
const useStyles = makeStyles()({
|
|
33
|
+
dialogContent: {
|
|
34
|
+
width: '80em',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
type Ret = Awaited<ReturnType<typeof geneTreeFetcher>>
|
|
39
|
+
|
|
40
|
+
const EnsemblGeneTree = observer(function ({
|
|
41
|
+
model,
|
|
42
|
+
feature,
|
|
43
|
+
handleClose,
|
|
44
|
+
}: {
|
|
45
|
+
model: AbstractTrackModel
|
|
46
|
+
feature: Feature
|
|
47
|
+
handleClose: () => void
|
|
48
|
+
}) {
|
|
49
|
+
const session = getSession(model)
|
|
50
|
+
const view = getContainingView(model) as LinearGenomeViewModel
|
|
51
|
+
const { classes } = useStyles()
|
|
52
|
+
const [error, setError] = useState<unknown>()
|
|
53
|
+
const [treeData, setTreeData] = useState<Ret>()
|
|
54
|
+
const [isTreeLoading, setIsTreeLoading] = useState(false)
|
|
55
|
+
const [treeError, setTreeError] = useState<unknown>()
|
|
56
|
+
const options = getTranscriptFeatures(feature)
|
|
57
|
+
const [userSelection, setUserSelection] = useState(getId(options[0]))
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
61
|
+
;(async () => {
|
|
62
|
+
try {
|
|
63
|
+
setIsTreeLoading(true)
|
|
64
|
+
const result = await geneTreeFetcher(userSelection)
|
|
65
|
+
setTreeData(result)
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.error(e)
|
|
68
|
+
setTreeError(e)
|
|
69
|
+
} finally {
|
|
70
|
+
setIsTreeLoading(false)
|
|
71
|
+
}
|
|
72
|
+
})()
|
|
73
|
+
}, [userSelection])
|
|
74
|
+
|
|
75
|
+
const loadingMessage = isTreeLoading
|
|
76
|
+
? 'Loading tree data from Ensembl GeneTree'
|
|
77
|
+
: undefined
|
|
78
|
+
const e = treeError ?? error
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<DialogContent className={classes.dialogContent}>
|
|
83
|
+
{e ? <ErrorMessage error={e} /> : null}
|
|
84
|
+
{loadingMessage ? <LoadingEllipses message={loadingMessage} /> : null}
|
|
85
|
+
<Typography>Load data from Ensembl GeneTree</Typography>
|
|
86
|
+
<TextField
|
|
87
|
+
select
|
|
88
|
+
label="Choose isoform to view MSA for"
|
|
89
|
+
value={userSelection}
|
|
90
|
+
onChange={event => {
|
|
91
|
+
setUserSelection(event.target.value)
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
{options.map(val => (
|
|
95
|
+
<MenuItem value={getId(val)} key={val.id()}>
|
|
96
|
+
{getTranscriptDisplayName(val)}
|
|
97
|
+
</MenuItem>
|
|
98
|
+
))}
|
|
99
|
+
</TextField>
|
|
100
|
+
</DialogContent>
|
|
101
|
+
|
|
102
|
+
<DialogActions>
|
|
103
|
+
<Button
|
|
104
|
+
color="primary"
|
|
105
|
+
variant="contained"
|
|
106
|
+
onClick={() => {
|
|
107
|
+
try {
|
|
108
|
+
if (!treeData) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
setError(undefined)
|
|
112
|
+
|
|
113
|
+
ensemblGeneTreeLaunchView({
|
|
114
|
+
feature,
|
|
115
|
+
view,
|
|
116
|
+
session,
|
|
117
|
+
newViewTitle: getGeneDisplayName(feature),
|
|
118
|
+
data: treeData,
|
|
119
|
+
})
|
|
120
|
+
handleClose()
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error(e)
|
|
123
|
+
setError(e)
|
|
124
|
+
}
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
Submit
|
|
128
|
+
</Button>
|
|
129
|
+
<Button
|
|
130
|
+
color="secondary"
|
|
131
|
+
variant="contained"
|
|
132
|
+
onClick={() => {
|
|
133
|
+
handleClose()
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
Cancel
|
|
137
|
+
</Button>
|
|
138
|
+
</DialogActions>
|
|
139
|
+
</>
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
export default EnsemblGeneTree
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AbstractSessionModel, Feature } from '@jbrowse/core/util'
|
|
2
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
3
|
+
|
|
4
|
+
export function ensemblGeneTreeLaunchView({
|
|
5
|
+
session,
|
|
6
|
+
newViewTitle,
|
|
7
|
+
view,
|
|
8
|
+
feature,
|
|
9
|
+
data,
|
|
10
|
+
}: {
|
|
11
|
+
session: AbstractSessionModel
|
|
12
|
+
newViewTitle: string
|
|
13
|
+
view: LinearGenomeViewModel
|
|
14
|
+
feature: Feature
|
|
15
|
+
data: {
|
|
16
|
+
tree: string
|
|
17
|
+
msa: string
|
|
18
|
+
treeMetadata: string
|
|
19
|
+
}
|
|
20
|
+
}) {
|
|
21
|
+
session.addView('MsaView', {
|
|
22
|
+
type: 'MsaView',
|
|
23
|
+
displayName: newViewTitle,
|
|
24
|
+
treeAreaWidth: 200,
|
|
25
|
+
treeWidth: 100,
|
|
26
|
+
drawNodeBubbles: false,
|
|
27
|
+
labelsAlignRight: true,
|
|
28
|
+
showBranchLen: false,
|
|
29
|
+
colWidth: 10,
|
|
30
|
+
rowHeight: 12,
|
|
31
|
+
colorSchemeName: 'percent_identity_dynamic',
|
|
32
|
+
data,
|
|
33
|
+
connectedViewId: view.id,
|
|
34
|
+
connectedFeature: feature.toJSON(),
|
|
35
|
+
})
|
|
36
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { fetchWithLocalStorageCache, jsonfetch, textfetch } from './util'
|
|
2
|
+
|
|
3
|
+
interface TreeNodeSequence {
|
|
4
|
+
mol_seq: {
|
|
5
|
+
seq: string
|
|
6
|
+
location?: string
|
|
7
|
+
}
|
|
8
|
+
id: {
|
|
9
|
+
accession: string
|
|
10
|
+
}[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface TreeNodeTaxonomy {
|
|
14
|
+
common_name: string
|
|
15
|
+
scientific_name: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// This is a self-referential tree data structure
|
|
19
|
+
interface TreeNode {
|
|
20
|
+
children?: TreeNode[]
|
|
21
|
+
sequence?: TreeNodeSequence
|
|
22
|
+
taxonomy: TreeNodeTaxonomy
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TreeRow {
|
|
26
|
+
id: string
|
|
27
|
+
seq: string
|
|
28
|
+
species: string
|
|
29
|
+
genomicLocString?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function gatherSequencesFromTree(tree: TreeNode, arr: TreeRow[] = []) {
|
|
33
|
+
if (tree.children) {
|
|
34
|
+
for (const child of tree.children) {
|
|
35
|
+
if (child.sequence) {
|
|
36
|
+
const id = child.sequence.id[0]?.accession
|
|
37
|
+
if (id) {
|
|
38
|
+
arr.push({
|
|
39
|
+
id,
|
|
40
|
+
seq: child.sequence.mol_seq.seq,
|
|
41
|
+
species:
|
|
42
|
+
child.taxonomy.common_name || child.taxonomy.scientific_name,
|
|
43
|
+
genomicLocString: child.sequence.mol_seq.location,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
gatherSequencesFromTree(child, arr)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return arr
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const base = 'https://rest.ensembl.org'
|
|
54
|
+
|
|
55
|
+
export async function geneTreeFetcher(id2: string) {
|
|
56
|
+
const id = id2.replace(/\..*/, '')
|
|
57
|
+
const { species } = await fetchWithLocalStorageCache(`${id}-ensembl`, () =>
|
|
58
|
+
jsonfetch<{ species: string }>(
|
|
59
|
+
`${base}/lookup/id/${id}?content-type=application/json`,
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
const treeBase = `${base}/genetree/member/id/${species}/${id}`
|
|
63
|
+
const msa = await fetchWithLocalStorageCache(`${id}-msa`, () =>
|
|
64
|
+
jsonfetch<{ tree: TreeNode }>(
|
|
65
|
+
`${treeBase}?content-type=application/json;aligned=1;sequence=pep`,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const tree = await fetchWithLocalStorageCache<string>(`${id}-tree`, () =>
|
|
70
|
+
textfetch(`${treeBase}?nh_format=simple;content-type=text/x-nh`),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const res = gatherSequencesFromTree(msa.tree)
|
|
74
|
+
return {
|
|
75
|
+
tree,
|
|
76
|
+
msa: res.map(r => `>${r.id}\n${r.seq}`).join('\n'),
|
|
77
|
+
treeMetadata: JSON.stringify(
|
|
78
|
+
Object.fromEntries(res.map(r => [r.id, { genome: r.species }] as const)),
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export async function fetchGeneList() {
|
|
2
|
+
const res = await fetch(
|
|
3
|
+
'https://jbrowse.org/demos/msaview/knownCanonical/list.txt',
|
|
4
|
+
)
|
|
5
|
+
if (!res.ok) {
|
|
6
|
+
throw new Error(`HTTP ${res.status} fetching list ${await res.text()}`)
|
|
7
|
+
}
|
|
8
|
+
const result = await res.text()
|
|
9
|
+
return result
|
|
10
|
+
.split('\n')
|
|
11
|
+
.map(f => f.trim())
|
|
12
|
+
.filter(f => !!f)
|
|
13
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ungzip } from 'pako'
|
|
2
|
+
|
|
3
|
+
export async function jsonfetch<T>(url: string, arg?: RequestInit) {
|
|
4
|
+
const res = await fetch(url, arg)
|
|
5
|
+
if (!res.ok) {
|
|
6
|
+
throw new Error(`HTTP ${res.status} from ${url}: ${await res.text()}`)
|
|
7
|
+
}
|
|
8
|
+
return res.json() as Promise<T>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function textfetch(url: string, arg?: RequestInit) {
|
|
12
|
+
const res = await fetch(url, arg)
|
|
13
|
+
if (!res.ok) {
|
|
14
|
+
throw new Error(`HTTP ${res.status} from ${url}`)
|
|
15
|
+
}
|
|
16
|
+
return res.text()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function fetchWithLocalStorageCache<T>(
|
|
20
|
+
key: string,
|
|
21
|
+
fetchFn: () => Promise<T>,
|
|
22
|
+
): Promise<T> {
|
|
23
|
+
const cachedData = localStorage.getItem(key)
|
|
24
|
+
|
|
25
|
+
if (cachedData) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(cachedData) as T
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`Error parsing cached data for ${key}:`, error)
|
|
30
|
+
// Continue to fetch fresh data if parsing fails
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = await fetchFn()
|
|
35
|
+
localStorage.setItem(key, JSON.stringify(data))
|
|
36
|
+
return data
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function unzipfetch(url: string, arg?: RequestInit) {
|
|
40
|
+
const res = await fetch(url, arg)
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(`HTTP ${res.status} from ${url}: ${await res.text()}`)
|
|
43
|
+
}
|
|
44
|
+
return ungzip(await res.arrayBuffer(), { to: 'string' })
|
|
45
|
+
}
|
|
@@ -2,12 +2,13 @@ import React, { useState } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { Dialog } from '@jbrowse/core/ui'
|
|
4
4
|
import { AbstractTrackModel, Feature } from '@jbrowse/core/util'
|
|
5
|
-
import {
|
|
5
|
+
import { Tab, Tabs } from '@mui/material'
|
|
6
6
|
|
|
7
|
+
import EnsemblGeneTree from './EnsemblGeneTree/EnsemblGeneTree'
|
|
8
|
+
import MSALoader from './MSALoader/MSALoader'
|
|
7
9
|
import NewNcbiBlastQueryPanel from './NewNCBIBlastQuery'
|
|
8
10
|
import PreLoadedMSA from './PreLoadedMSA/PreLoadedMSADataPanel'
|
|
9
|
-
import
|
|
10
|
-
import { a11yProps } from './tabUtil'
|
|
11
|
+
import TabPanel from './TabPanel'
|
|
11
12
|
|
|
12
13
|
export default function LaunchProteinViewDialog({
|
|
13
14
|
handleClose,
|
|
@@ -24,36 +25,46 @@ export default function LaunchProteinViewDialog({
|
|
|
24
25
|
<Dialog
|
|
25
26
|
maxWidth="xl"
|
|
26
27
|
title="Launch MSA view"
|
|
28
|
+
open
|
|
27
29
|
onClose={() => {
|
|
28
30
|
handleClose()
|
|
29
31
|
}}
|
|
30
|
-
open
|
|
31
32
|
>
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</
|
|
43
|
-
<
|
|
33
|
+
<Tabs
|
|
34
|
+
value={value}
|
|
35
|
+
onChange={(_, val) => {
|
|
36
|
+
setValue(val)
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<Tab label="NCBI BLAST query" value={0} />
|
|
40
|
+
<Tab label="UCSC 100-way dataset" value={1} />
|
|
41
|
+
<Tab label="Ensembl GeneTree" value={2} />
|
|
42
|
+
<Tab label="Manually open MSA" value={3} />
|
|
43
|
+
</Tabs>
|
|
44
|
+
<TabPanel value={value} index={0}>
|
|
44
45
|
<NewNcbiBlastQueryPanel
|
|
45
46
|
handleClose={handleClose}
|
|
46
47
|
feature={feature}
|
|
47
48
|
model={model}
|
|
48
49
|
/>
|
|
49
|
-
</
|
|
50
|
-
<
|
|
50
|
+
</TabPanel>
|
|
51
|
+
<TabPanel value={value} index={1}>
|
|
51
52
|
<PreLoadedMSA
|
|
52
53
|
model={model}
|
|
53
54
|
feature={feature}
|
|
54
55
|
handleClose={handleClose}
|
|
55
56
|
/>
|
|
56
|
-
</
|
|
57
|
+
</TabPanel>
|
|
58
|
+
<TabPanel value={value} index={2}>
|
|
59
|
+
<EnsemblGeneTree
|
|
60
|
+
model={model}
|
|
61
|
+
feature={feature}
|
|
62
|
+
handleClose={handleClose}
|
|
63
|
+
/>
|
|
64
|
+
</TabPanel>
|
|
65
|
+
<TabPanel value={value} index={3}>
|
|
66
|
+
<MSALoader model={model} feature={feature} handleClose={handleClose} />
|
|
67
|
+
</TabPanel>
|
|
57
68
|
</Dialog>
|
|
58
69
|
)
|
|
59
70
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { FileSelector } from '@jbrowse/core/ui'
|
|
4
|
+
import {
|
|
5
|
+
AbstractTrackModel,
|
|
6
|
+
Feature,
|
|
7
|
+
FileLocation,
|
|
8
|
+
getContainingView,
|
|
9
|
+
getSession,
|
|
10
|
+
} from '@jbrowse/core/util'
|
|
11
|
+
import { openLocation } from '@jbrowse/core/util/io'
|
|
12
|
+
import { Button, DialogActions, DialogContent } from '@mui/material'
|
|
13
|
+
import { observer } from 'mobx-react'
|
|
14
|
+
import { makeStyles } from 'tss-react/mui'
|
|
15
|
+
|
|
16
|
+
import { fetchGeneList } from './fetchGeneList'
|
|
17
|
+
import { preCalculatedLaunchView } from './preCalculatedLaunchView'
|
|
18
|
+
import { getGeneDisplayName, getId, getTranscriptFeatures } from '../../util'
|
|
19
|
+
|
|
20
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
21
|
+
|
|
22
|
+
const useStyles = makeStyles()({
|
|
23
|
+
dialogContent: {
|
|
24
|
+
width: '80em',
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const PreLoadedMSA = observer(function PreLoadedMSA2({
|
|
29
|
+
model,
|
|
30
|
+
feature,
|
|
31
|
+
handleClose,
|
|
32
|
+
}: {
|
|
33
|
+
model: AbstractTrackModel
|
|
34
|
+
feature: Feature
|
|
35
|
+
handleClose: () => void
|
|
36
|
+
}) {
|
|
37
|
+
const session = getSession(model)
|
|
38
|
+
const view = getContainingView(model) as LinearGenomeViewModel
|
|
39
|
+
const { classes } = useStyles()
|
|
40
|
+
const [, setError] = useState<unknown>()
|
|
41
|
+
const [geneNameList, setGeneNameList] = useState<string[]>()
|
|
42
|
+
const [fileLocation, setFileLocation] = useState<FileLocation>()
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
46
|
+
;(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const data = await fetchGeneList()
|
|
49
|
+
setGeneNameList(data)
|
|
50
|
+
const set = new Set(data)
|
|
51
|
+
const options = getTranscriptFeatures(feature)
|
|
52
|
+
const ret = options.find(val => set.has(getId(val)))
|
|
53
|
+
if (ret) {
|
|
54
|
+
setUserSelection(getId(ret))
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error(e)
|
|
58
|
+
setError(e)
|
|
59
|
+
}
|
|
60
|
+
})()
|
|
61
|
+
}, [feature])
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
65
|
+
;(async () => {
|
|
66
|
+
try {
|
|
67
|
+
if (fileLocation) {
|
|
68
|
+
await openLocation(fileLocation).readFile()
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.error(e)
|
|
72
|
+
setError(e)
|
|
73
|
+
}
|
|
74
|
+
})()
|
|
75
|
+
}, [fileLocation, feature])
|
|
76
|
+
|
|
77
|
+
const set = new Set(geneNameList)
|
|
78
|
+
const options = getTranscriptFeatures(feature)
|
|
79
|
+
const ret = options.find(val => set.has(getId(val)))
|
|
80
|
+
const [userSelection, setUserSelection] = useState(getId(options[0]))
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
<DialogContent className={classes.dialogContent}>
|
|
85
|
+
<FileSelector location={fileLocation} setLocation={setFileLocation} />
|
|
86
|
+
</DialogContent>
|
|
87
|
+
|
|
88
|
+
<DialogActions>
|
|
89
|
+
<Button
|
|
90
|
+
color="primary"
|
|
91
|
+
variant="contained"
|
|
92
|
+
onClick={() => {
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
94
|
+
;(async () => {
|
|
95
|
+
try {
|
|
96
|
+
if (!ret) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
await preCalculatedLaunchView({
|
|
100
|
+
userSelection,
|
|
101
|
+
session,
|
|
102
|
+
newViewTitle: getGeneDisplayName(ret),
|
|
103
|
+
view,
|
|
104
|
+
feature: ret,
|
|
105
|
+
})
|
|
106
|
+
handleClose()
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error(e)
|
|
109
|
+
setError(e)
|
|
110
|
+
}
|
|
111
|
+
})()
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
Submit
|
|
115
|
+
</Button>
|
|
116
|
+
<Button
|
|
117
|
+
color="secondary"
|
|
118
|
+
variant="contained"
|
|
119
|
+
onClick={() => {
|
|
120
|
+
handleClose()
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
Cancel
|
|
124
|
+
</Button>
|
|
125
|
+
</DialogActions>
|
|
126
|
+
</>
|
|
127
|
+
)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
export default PreLoadedMSA
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export async function fetchGeneList() {
|
|
2
|
+
const res = await fetch(
|
|
3
|
+
'https://jbrowse.org/demos/msaview/knownCanonical/list.txt',
|
|
4
|
+
)
|
|
5
|
+
if (!res.ok) {
|
|
6
|
+
throw new Error(`HTTP ${res.status} fetching list ${await res.text()}`)
|
|
7
|
+
}
|
|
8
|
+
const result = await res.text()
|
|
9
|
+
return result
|
|
10
|
+
.split('\n')
|
|
11
|
+
.map(f => f.trim())
|
|
12
|
+
.filter(f => !!f)
|
|
13
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AbstractSessionModel, Feature } from '@jbrowse/core/util'
|
|
2
|
+
import { ungzip } from 'pako'
|
|
3
|
+
|
|
4
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
5
|
+
|
|
6
|
+
async function myfetch(url: string) {
|
|
7
|
+
const res = await fetch(url)
|
|
8
|
+
if (!res.ok) {
|
|
9
|
+
throw new Error(`HTTP ${res.status} fetching ${await res.text()}`)
|
|
10
|
+
}
|
|
11
|
+
const data = await res.arrayBuffer()
|
|
12
|
+
return new TextDecoder().decode(ungzip(data))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function preCalculatedLaunchView({
|
|
16
|
+
userSelection,
|
|
17
|
+
session,
|
|
18
|
+
newViewTitle,
|
|
19
|
+
view,
|
|
20
|
+
feature,
|
|
21
|
+
}: {
|
|
22
|
+
session: AbstractSessionModel
|
|
23
|
+
userSelection: string
|
|
24
|
+
newViewTitle: string
|
|
25
|
+
view: LinearGenomeViewModel
|
|
26
|
+
feature: Feature
|
|
27
|
+
}) {
|
|
28
|
+
const d = await myfetch(
|
|
29
|
+
`https://jbrowse.org/demos/msaview/knownCanonical/${userSelection}.mfa.gz`,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
session.addView('MsaView', {
|
|
33
|
+
type: 'MsaView',
|
|
34
|
+
displayName: newViewTitle,
|
|
35
|
+
treeAreaWidth: 200,
|
|
36
|
+
treeWidth: 100,
|
|
37
|
+
drawNodeBubbles: false,
|
|
38
|
+
labelsAlignRight: true,
|
|
39
|
+
showBranchLen: false,
|
|
40
|
+
colWidth: 10,
|
|
41
|
+
rowHeight: 12,
|
|
42
|
+
colorSchemeName: 'percent_identity_dynamic',
|
|
43
|
+
treeFilehandle: {
|
|
44
|
+
uri: 'https://hgdownload.soe.ucsc.edu/goldenPath/hg38/multiz100way/hg38.100way.nh',
|
|
45
|
+
},
|
|
46
|
+
treeMetadataFilehandle: {
|
|
47
|
+
uri: 'https://s3.amazonaws.com/jbrowse.org/demos/app/species.json',
|
|
48
|
+
},
|
|
49
|
+
data: {
|
|
50
|
+
msa: d,
|
|
51
|
+
},
|
|
52
|
+
connectedViewId: view.id,
|
|
53
|
+
connectedFeature: feature.toJSON(),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Feature,
|
|
3
2
|
defaultCodonTable,
|
|
4
3
|
generateCodonTable,
|
|
5
4
|
revcom,
|
|
6
5
|
} from '@jbrowse/core/util'
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
end: number
|
|
11
|
-
type: string
|
|
12
|
-
}
|
|
7
|
+
import type { Feat } from './types'
|
|
8
|
+
import type { Feature } from '@jbrowse/core/util'
|
|
13
9
|
|
|
14
10
|
export function stitch(subfeats: Feat[], sequence: string) {
|
|
15
11
|
return subfeats.map(sub => sequence.slice(sub.start, sub.end)).join('')
|
|
@@ -33,10 +29,7 @@ export function calculateProteinSequence({
|
|
|
33
29
|
return protein
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
export function revlist(
|
|
37
|
-
list: { start: number; end: number; type: string }[],
|
|
38
|
-
seqlen: number,
|
|
39
|
-
) {
|
|
32
|
+
export function revlist(list: Feat[], seqlen: number) {
|
|
40
33
|
return list
|
|
41
34
|
.map(sub => ({
|
|
42
35
|
...sub,
|
|
@@ -65,23 +58,16 @@ export function getProteinSequence({
|
|
|
65
58
|
seq: string
|
|
66
59
|
selectedTranscript: Feature
|
|
67
60
|
}) {
|
|
68
|
-
|
|
69
|
-
const f = selectedTranscript.toJSON() as {
|
|
70
|
-
start: number
|
|
71
|
-
end: number
|
|
72
|
-
strand: number
|
|
73
|
-
type: string
|
|
74
|
-
subfeatures: { start: number; end: number; type: string }[]
|
|
75
|
-
}
|
|
61
|
+
const f = selectedTranscript.toJSON()
|
|
76
62
|
const cds = dedupe(
|
|
77
63
|
f.subfeatures
|
|
78
|
-
|
|
64
|
+
?.sort((a, b) => a.start - b.start)
|
|
79
65
|
.map(sub => ({
|
|
80
66
|
...sub,
|
|
81
67
|
start: sub.start - f.start,
|
|
82
68
|
end: sub.end - f.start,
|
|
83
69
|
}))
|
|
84
|
-
.filter(f => f.type === 'CDS'),
|
|
70
|
+
.filter(f => f.type === 'CDS') || [],
|
|
85
71
|
)
|
|
86
72
|
|
|
87
73
|
return calculateProteinSequence({
|