jbrowse-plugin-msaview 2.2.3 → 2.2.5
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/CHANGELOG.md +1 -1
- package/README.md +229 -0
- package/dist/AddHighlightModel/GenomeMouseoverHighlight.js +23 -18
- package/dist/AddHighlightModel/GenomeMouseoverHighlight.js.map +1 -1
- package/dist/AddHighlightModel/MsaToGenomeHighlight.js +23 -13
- package/dist/AddHighlightModel/MsaToGenomeHighlight.js.map +1 -1
- package/dist/AddHighlightModel/index.js +8 -1
- package/dist/AddHighlightModel/index.js.map +1 -1
- package/dist/AddHighlightModel/util.d.ts +2 -2
- package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +2 -2
- package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js +5 -11
- package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js.map +1 -1
- package/dist/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.js +5 -1
- package/dist/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.js.map +1 -1
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +16 -16
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js.map +1 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +38 -46
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js.map +1 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +4 -3
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +4 -3
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.d.ts +9 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +76 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js.map +1 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js +35 -13
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js +6 -12
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.d.ts +6 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js +15 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js.map +1 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +12 -34
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/consts.d.ts +1 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/consts.js +1 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/consts.js.map +1 -1
- package/dist/LaunchMsaView/components/TabPanel.d.ts +2 -2
- package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +2 -2
- package/dist/LaunchMsaView/components/TranscriptSelector.js +3 -6
- package/dist/LaunchMsaView/components/TranscriptSelector.js.map +1 -1
- package/dist/LaunchMsaView/components/useSWRFeatureSequence.js +6 -4
- package/dist/LaunchMsaView/components/useSWRFeatureSequence.js.map +1 -1
- package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +16 -0
- package/dist/LaunchMsaView/components/useTranscriptSelection.js +31 -0
- package/dist/LaunchMsaView/components/useTranscriptSelection.js.map +1 -0
- package/dist/LaunchMsaView/components/util.d.ts +3 -1
- package/dist/LaunchMsaView/components/util.js +12 -2
- package/dist/LaunchMsaView/components/util.js.map +1 -1
- package/dist/LaunchMsaView/util.d.ts +2 -0
- package/dist/LaunchMsaView/util.js +16 -4
- package/dist/LaunchMsaView/util.js.map +1 -1
- package/dist/LaunchMsaViewExtensionPoint/index.d.ts +2 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js +31 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js.map +1 -0
- package/dist/MsaViewPanel/components/ConnectStructureDialog.d.ts +7 -0
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js +56 -0
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js.map +1 -0
- package/dist/MsaViewPanel/components/MsaViewPanel.js +4 -2
- package/dist/MsaViewPanel/components/MsaViewPanel.js.map +1 -1
- package/dist/MsaViewPanel/doLaunchBlast.d.ts +1 -0
- package/dist/MsaViewPanel/doLaunchBlast.js +65 -19
- package/dist/MsaViewPanel/doLaunchBlast.js.map +1 -1
- package/dist/MsaViewPanel/genomeToMSA.d.ts +6 -0
- package/dist/MsaViewPanel/genomeToMSA.js +38 -8
- package/dist/MsaViewPanel/genomeToMSA.js.map +1 -1
- package/dist/MsaViewPanel/genomeToMSA.test.d.ts +1 -0
- package/dist/MsaViewPanel/genomeToMSA.test.js +244 -0
- package/dist/MsaViewPanel/genomeToMSA.test.js.map +1 -0
- package/dist/MsaViewPanel/model.d.ts +719 -226
- package/dist/MsaViewPanel/model.js +467 -39
- package/dist/MsaViewPanel/model.js.map +1 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +7 -2
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +26 -27
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js.map +1 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.d.ts +1 -0
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +240 -0
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js.map +1 -0
- package/dist/MsaViewPanel/msaDataStore.d.ts +14 -0
- package/dist/MsaViewPanel/msaDataStore.js +197 -0
- package/dist/MsaViewPanel/msaDataStore.js.map +1 -0
- package/dist/MsaViewPanel/pairwiseAlignment.d.ts +27 -0
- package/dist/MsaViewPanel/pairwiseAlignment.js +776 -0
- package/dist/MsaViewPanel/pairwiseAlignment.js.map +1 -0
- package/dist/MsaViewPanel/pairwiseAlignment.test.d.ts +1 -0
- package/dist/MsaViewPanel/pairwiseAlignment.test.js +112 -0
- package/dist/MsaViewPanel/pairwiseAlignment.test.js.map +1 -0
- package/dist/MsaViewPanel/structureConnection.d.ts +27 -0
- package/dist/MsaViewPanel/structureConnection.js +46 -0
- package/dist/MsaViewPanel/structureConnection.js.map +1 -0
- package/dist/MsaViewPanel/structureConnection.test.d.ts +1 -0
- package/dist/MsaViewPanel/structureConnection.test.js +122 -0
- package/dist/MsaViewPanel/structureConnection.test.js.map +1 -0
- package/dist/MsaViewPanel/types.d.ts +13 -0
- package/dist/MsaViewPanel/types.js +2 -0
- package/dist/MsaViewPanel/types.js.map +1 -0
- package/dist/MsaViewPanel/util.d.ts +7 -0
- package/dist/MsaViewPanel/util.js +10 -0
- package/dist/MsaViewPanel/util.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +39 -90
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +34 -0
- package/dist/utils/blastCache.js +58 -0
- package/dist/utils/blastCache.js.map +1 -0
- package/dist/utils/fetch.d.ts +1 -1
- package/dist/utils/fetch.js +1 -1
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/ncbiBlast.d.ts +1 -5
- package/dist/utils/taxonomyNames.d.ts +5 -0
- package/dist/utils/taxonomyNames.js +79 -0
- package/dist/utils/taxonomyNames.js.map +1 -0
- package/dist/utils/types.d.ts +8 -5
- package/package.json +50 -54
- package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +37 -21
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +38 -17
- package/src/AddHighlightModel/index.tsx +9 -4
- package/src/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.tsx +13 -13
- package/src/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.ts +6 -0
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +30 -23
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +64 -51
- package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +9 -6
- package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +146 -0
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx +53 -22
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.tsx +8 -13
- package/src/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.ts +25 -0
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +27 -47
- package/src/LaunchMsaView/components/PreLoadedMSA/consts.ts +1 -0
- package/src/LaunchMsaView/components/TabPanel.tsx +2 -2
- package/src/LaunchMsaView/components/TranscriptSelector.tsx +13 -20
- package/src/LaunchMsaView/components/useSWRFeatureSequence.ts +5 -5
- package/src/LaunchMsaView/components/useTranscriptSelection.ts +48 -0
- package/src/LaunchMsaView/components/util.ts +17 -2
- package/src/LaunchMsaView/index.ts +1 -1
- package/src/LaunchMsaView/util.ts +25 -6
- package/src/LaunchMsaViewExtensionPoint/index.ts +74 -0
- package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +156 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +6 -1
- package/src/MsaViewPanel/doLaunchBlast.ts +83 -23
- package/src/MsaViewPanel/genomeToMSA.test.ts +281 -0
- package/src/MsaViewPanel/genomeToMSA.ts +43 -10
- package/src/MsaViewPanel/model.ts +590 -43
- package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +256 -0
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +43 -29
- package/src/MsaViewPanel/msaDataStore.ts +236 -0
- package/src/MsaViewPanel/pairwiseAlignment.test.ts +140 -0
- package/src/MsaViewPanel/pairwiseAlignment.ts +818 -0
- package/src/MsaViewPanel/structureConnection.test.ts +143 -0
- package/src/MsaViewPanel/structureConnection.ts +72 -0
- package/src/MsaViewPanel/types.ts +14 -0
- package/src/MsaViewPanel/util.ts +11 -0
- package/src/index.ts +3 -1
- package/src/utils/blastCache.ts +114 -0
- package/src/utils/fetch.ts +1 -1
- package/src/utils/taxonomyNames.ts +111 -0
- package/src/utils/types.ts +9 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.d.ts +0 -5
- package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.js +0 -16
- package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.js.map +0 -1
- package/dist/out.js +0 -55381
- package/src/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.ts +0 -25
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
gappedToUngappedPosition,
|
|
5
|
+
mapToRecord,
|
|
6
|
+
ungappedToGappedPosition,
|
|
7
|
+
} from './structureConnection'
|
|
8
|
+
|
|
9
|
+
describe('gappedToUngappedPosition', () => {
|
|
10
|
+
test('returns correct ungapped position for non-gap character', () => {
|
|
11
|
+
const seq = 'M-KA-A'
|
|
12
|
+
// 0 12 34 (gapped positions)
|
|
13
|
+
// 0 1 2 (ungapped positions for M, K, A, A)
|
|
14
|
+
expect(gappedToUngappedPosition(seq, 0)).toBe(0) // M -> 0
|
|
15
|
+
expect(gappedToUngappedPosition(seq, 2)).toBe(1) // K -> 1
|
|
16
|
+
expect(gappedToUngappedPosition(seq, 3)).toBe(2) // A -> 2
|
|
17
|
+
expect(gappedToUngappedPosition(seq, 5)).toBe(3) // A -> 3
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('returns undefined for gap position', () => {
|
|
21
|
+
const seq = 'M-KA-A'
|
|
22
|
+
expect(gappedToUngappedPosition(seq, 1)).toBeUndefined() // gap
|
|
23
|
+
expect(gappedToUngappedPosition(seq, 4)).toBeUndefined() // gap
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('returns undefined for out-of-bounds position', () => {
|
|
27
|
+
const seq = 'MKA'
|
|
28
|
+
expect(gappedToUngappedPosition(seq, -1)).toBeUndefined()
|
|
29
|
+
expect(gappedToUngappedPosition(seq, 3)).toBeUndefined()
|
|
30
|
+
expect(gappedToUngappedPosition(seq, 100)).toBeUndefined()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('handles sequence with no gaps', () => {
|
|
34
|
+
const seq = 'MKAA'
|
|
35
|
+
expect(gappedToUngappedPosition(seq, 0)).toBe(0)
|
|
36
|
+
expect(gappedToUngappedPosition(seq, 1)).toBe(1)
|
|
37
|
+
expect(gappedToUngappedPosition(seq, 2)).toBe(2)
|
|
38
|
+
expect(gappedToUngappedPosition(seq, 3)).toBe(3)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('handles sequence with leading gaps', () => {
|
|
42
|
+
const seq = '--MKA'
|
|
43
|
+
expect(gappedToUngappedPosition(seq, 0)).toBeUndefined()
|
|
44
|
+
expect(gappedToUngappedPosition(seq, 1)).toBeUndefined()
|
|
45
|
+
expect(gappedToUngappedPosition(seq, 2)).toBe(0) // M
|
|
46
|
+
expect(gappedToUngappedPosition(seq, 3)).toBe(1) // K
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('handles sequence with trailing gaps', () => {
|
|
50
|
+
const seq = 'MKA--'
|
|
51
|
+
expect(gappedToUngappedPosition(seq, 2)).toBe(2) // A
|
|
52
|
+
expect(gappedToUngappedPosition(seq, 3)).toBeUndefined()
|
|
53
|
+
expect(gappedToUngappedPosition(seq, 4)).toBeUndefined()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('handles empty sequence', () => {
|
|
57
|
+
expect(gappedToUngappedPosition('', 0)).toBeUndefined()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('handles all-gap sequence', () => {
|
|
61
|
+
const seq = '---'
|
|
62
|
+
expect(gappedToUngappedPosition(seq, 0)).toBeUndefined()
|
|
63
|
+
expect(gappedToUngappedPosition(seq, 1)).toBeUndefined()
|
|
64
|
+
expect(gappedToUngappedPosition(seq, 2)).toBeUndefined()
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('ungappedToGappedPosition', () => {
|
|
69
|
+
test('returns correct gapped position', () => {
|
|
70
|
+
const seq = 'M-KA-A'
|
|
71
|
+
// 0 12 34 (gapped)
|
|
72
|
+
// 0 1 23 (ungapped)
|
|
73
|
+
expect(ungappedToGappedPosition(seq, 0)).toBe(0) // M
|
|
74
|
+
expect(ungappedToGappedPosition(seq, 1)).toBe(2) // K
|
|
75
|
+
expect(ungappedToGappedPosition(seq, 2)).toBe(3) // A
|
|
76
|
+
expect(ungappedToGappedPosition(seq, 3)).toBe(5) // A
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('returns undefined for out-of-bounds ungapped position', () => {
|
|
80
|
+
const seq = 'M-KA'
|
|
81
|
+
expect(ungappedToGappedPosition(seq, 4)).toBeUndefined()
|
|
82
|
+
expect(ungappedToGappedPosition(seq, 100)).toBeUndefined()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('handles sequence with no gaps', () => {
|
|
86
|
+
const seq = 'MKAA'
|
|
87
|
+
expect(ungappedToGappedPosition(seq, 0)).toBe(0)
|
|
88
|
+
expect(ungappedToGappedPosition(seq, 1)).toBe(1)
|
|
89
|
+
expect(ungappedToGappedPosition(seq, 2)).toBe(2)
|
|
90
|
+
expect(ungappedToGappedPosition(seq, 3)).toBe(3)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('handles sequence with leading gaps', () => {
|
|
94
|
+
const seq = '--MKA'
|
|
95
|
+
expect(ungappedToGappedPosition(seq, 0)).toBe(2) // M
|
|
96
|
+
expect(ungappedToGappedPosition(seq, 1)).toBe(3) // K
|
|
97
|
+
expect(ungappedToGappedPosition(seq, 2)).toBe(4) // A
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('handles empty sequence', () => {
|
|
101
|
+
expect(ungappedToGappedPosition('', 0)).toBeUndefined()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('handles all-gap sequence', () => {
|
|
105
|
+
const seq = '---'
|
|
106
|
+
expect(ungappedToGappedPosition(seq, 0)).toBeUndefined()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('gappedToUngappedPosition and ungappedToGappedPosition are inverses', () => {
|
|
111
|
+
test('round-trip conversion works', () => {
|
|
112
|
+
const seq = 'M-KA--YL-S'
|
|
113
|
+
// For each non-gap position, converting to ungapped and back should return original
|
|
114
|
+
for (let i = 0; i < seq.length; i++) {
|
|
115
|
+
if (seq[i] !== '-') {
|
|
116
|
+
const ungapped = gappedToUngappedPosition(seq, i)
|
|
117
|
+
expect(ungapped).toBeDefined()
|
|
118
|
+
const backToGapped = ungappedToGappedPosition(seq, ungapped!)
|
|
119
|
+
expect(backToGapped).toBe(i)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('mapToRecord', () => {
|
|
126
|
+
test('converts Map to Record', () => {
|
|
127
|
+
const map = new Map<number, number>([
|
|
128
|
+
[0, 5],
|
|
129
|
+
[1, 10],
|
|
130
|
+
[2, 15],
|
|
131
|
+
])
|
|
132
|
+
const record = mapToRecord(map)
|
|
133
|
+
expect(record[0]).toBe(5)
|
|
134
|
+
expect(record[1]).toBe(10)
|
|
135
|
+
expect(record[2]).toBe(15)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('handles empty Map', () => {
|
|
139
|
+
const map = new Map<number, number>()
|
|
140
|
+
const record = mapToRecord(map)
|
|
141
|
+
expect(Object.keys(record)).toHaveLength(0)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a connection between the MSA view and a protein structure
|
|
3
|
+
*/
|
|
4
|
+
export interface StructureConnection {
|
|
5
|
+
/** ID of the ProteinView containing the structure */
|
|
6
|
+
proteinViewId: string
|
|
7
|
+
/** Index of the structure within the ProteinView's structures array */
|
|
8
|
+
structureIdx: number
|
|
9
|
+
/** Name of the MSA row that corresponds to this structure */
|
|
10
|
+
msaRowName: string
|
|
11
|
+
/** Map from MSA ungapped position to structure sequence position */
|
|
12
|
+
msaToStructure: Record<number, number>
|
|
13
|
+
/** Map from structure sequence position to MSA ungapped position */
|
|
14
|
+
structureToMsa: Record<number, number>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Helper to convert gapped MSA column to ungapped position for a specific row
|
|
19
|
+
*/
|
|
20
|
+
export function gappedToUngappedPosition(
|
|
21
|
+
sequence: string,
|
|
22
|
+
gappedPosition: number,
|
|
23
|
+
): number | undefined {
|
|
24
|
+
if (gappedPosition < 0 || gappedPosition >= sequence.length) {
|
|
25
|
+
return undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let ungapped = 0
|
|
29
|
+
for (let i = 0; i < gappedPosition; i++) {
|
|
30
|
+
if (sequence[i] !== '-') {
|
|
31
|
+
ungapped++
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If the position itself is a gap, return undefined
|
|
36
|
+
if (sequence[gappedPosition] === '-') {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ungapped
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Helper to convert ungapped position to gapped MSA column for a specific row
|
|
45
|
+
*/
|
|
46
|
+
export function ungappedToGappedPosition(
|
|
47
|
+
sequence: string,
|
|
48
|
+
ungappedPosition: number,
|
|
49
|
+
): number | undefined {
|
|
50
|
+
let ungapped = 0
|
|
51
|
+
for (let i = 0; i < sequence.length; i++) {
|
|
52
|
+
const element = sequence[i]
|
|
53
|
+
if (element !== '-') {
|
|
54
|
+
if (ungapped === ungappedPosition) {
|
|
55
|
+
return i
|
|
56
|
+
}
|
|
57
|
+
ungapped++
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Convert Map to plain object for MST frozen storage
|
|
65
|
+
*/
|
|
66
|
+
export function mapToRecord(map: Map<number, number>): Record<number, number> {
|
|
67
|
+
const record: Record<number, number> = {}
|
|
68
|
+
for (const [key, value] of map) {
|
|
69
|
+
record[key] = value
|
|
70
|
+
}
|
|
71
|
+
return record
|
|
72
|
+
}
|
package/src/MsaViewPanel/util.ts
CHANGED
|
@@ -11,3 +11,14 @@ export function checkHovered(hovered: unknown): hovered is {
|
|
|
11
11
|
'hoverPosition' in hovered
|
|
12
12
|
)
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extracts UniProt ID from an AlphaFold URL
|
|
17
|
+
* Examples:
|
|
18
|
+
* - https://alphafold.ebi.ac.uk/files/AF-P12345-F1-model_v6.cif -> P12345
|
|
19
|
+
* - https://alphafold.ebi.ac.uk/files/msa/AF-P12345-F1-msa_v6.a3m -> P12345
|
|
20
|
+
*/
|
|
21
|
+
export function getUniprotIdFromAlphaFoldUrl(url: string) {
|
|
22
|
+
const match = /AF-([A-Z0-9]+)-F\d+/.exec(url)
|
|
23
|
+
return match?.[1]
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,13 +2,14 @@ import Plugin from '@jbrowse/core/Plugin'
|
|
|
2
2
|
import PluginManager from '@jbrowse/core/PluginManager'
|
|
3
3
|
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
4
4
|
import { AbstractSessionModel, isAbstractMenuManager } from '@jbrowse/core/util'
|
|
5
|
+
import { types } from '@jbrowse/mobx-state-tree'
|
|
5
6
|
import GridOn from '@mui/icons-material/GridOn'
|
|
6
|
-
import { types } from 'mobx-state-tree'
|
|
7
7
|
|
|
8
8
|
import { version } from '../package.json'
|
|
9
9
|
import AddHighlightModelF from './AddHighlightModel'
|
|
10
10
|
import BgzipFastaMsaAdapterF from './BgzipFastaMsaAdapter'
|
|
11
11
|
import LaunchMsaViewF from './LaunchMsaView'
|
|
12
|
+
import LaunchMsaViewExtensionPointF from './LaunchMsaViewExtensionPoint'
|
|
12
13
|
import MsaViewF from './MsaViewPanel'
|
|
13
14
|
|
|
14
15
|
export default class MsaViewPlugin extends Plugin {
|
|
@@ -18,6 +19,7 @@ export default class MsaViewPlugin extends Plugin {
|
|
|
18
19
|
install(pluginManager: PluginManager) {
|
|
19
20
|
MsaViewF(pluginManager)
|
|
20
21
|
LaunchMsaViewF(pluginManager)
|
|
22
|
+
LaunchMsaViewExtensionPointF(pluginManager)
|
|
21
23
|
AddHighlightModelF(pluginManager)
|
|
22
24
|
BgzipFastaMsaAdapterF(pluginManager)
|
|
23
25
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { openDB } from 'idb'
|
|
2
|
+
|
|
3
|
+
const DB_NAME = 'jbrowse-msaview-blast-cache'
|
|
4
|
+
const STORE_NAME = 'blast-results'
|
|
5
|
+
const DB_VERSION = 2
|
|
6
|
+
|
|
7
|
+
export interface CachedBlastResult {
|
|
8
|
+
id: string
|
|
9
|
+
proteinSequence: string
|
|
10
|
+
blastDatabase: string
|
|
11
|
+
blastProgram: string
|
|
12
|
+
msaAlgorithm: string
|
|
13
|
+
msa: string
|
|
14
|
+
tree: string
|
|
15
|
+
treeMetadata: string
|
|
16
|
+
rid: string
|
|
17
|
+
timestamp: number
|
|
18
|
+
geneId?: string
|
|
19
|
+
transcriptId?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function getDB() {
|
|
23
|
+
return openDB(DB_NAME, DB_VERSION, {
|
|
24
|
+
upgrade(db, oldVersion) {
|
|
25
|
+
if (oldVersion < 2 && db.objectStoreNames.contains(STORE_NAME)) {
|
|
26
|
+
db.deleteObjectStore(STORE_NAME)
|
|
27
|
+
}
|
|
28
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
29
|
+
db.createObjectStore(STORE_NAME, { keyPath: 'id' })
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createCacheKey(
|
|
36
|
+
proteinSequence: string,
|
|
37
|
+
blastDatabase: string,
|
|
38
|
+
blastProgram: string,
|
|
39
|
+
) {
|
|
40
|
+
return `${blastDatabase}:${blastProgram}:${proteinSequence.slice(0, 100)}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getCachedBlastResult({
|
|
44
|
+
proteinSequence,
|
|
45
|
+
blastDatabase,
|
|
46
|
+
blastProgram,
|
|
47
|
+
}: {
|
|
48
|
+
proteinSequence: string
|
|
49
|
+
blastDatabase: string
|
|
50
|
+
blastProgram: string
|
|
51
|
+
}) {
|
|
52
|
+
const db = await getDB()
|
|
53
|
+
const id = createCacheKey(proteinSequence, blastDatabase, blastProgram)
|
|
54
|
+
return db.get(STORE_NAME, id)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function saveBlastResult({
|
|
58
|
+
proteinSequence,
|
|
59
|
+
blastDatabase,
|
|
60
|
+
blastProgram,
|
|
61
|
+
msaAlgorithm,
|
|
62
|
+
msa,
|
|
63
|
+
tree,
|
|
64
|
+
treeMetadata,
|
|
65
|
+
rid,
|
|
66
|
+
geneId,
|
|
67
|
+
transcriptId,
|
|
68
|
+
}: {
|
|
69
|
+
proteinSequence: string
|
|
70
|
+
blastDatabase: string
|
|
71
|
+
blastProgram: string
|
|
72
|
+
msaAlgorithm: string
|
|
73
|
+
msa: string
|
|
74
|
+
tree: string
|
|
75
|
+
treeMetadata: string
|
|
76
|
+
rid: string
|
|
77
|
+
geneId?: string
|
|
78
|
+
transcriptId?: string
|
|
79
|
+
}) {
|
|
80
|
+
const db = await getDB()
|
|
81
|
+
const id = createCacheKey(proteinSequence, blastDatabase, blastProgram)
|
|
82
|
+
const entry: CachedBlastResult = {
|
|
83
|
+
id,
|
|
84
|
+
proteinSequence,
|
|
85
|
+
blastDatabase,
|
|
86
|
+
blastProgram,
|
|
87
|
+
msaAlgorithm,
|
|
88
|
+
msa,
|
|
89
|
+
tree,
|
|
90
|
+
treeMetadata,
|
|
91
|
+
rid,
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
geneId,
|
|
94
|
+
transcriptId,
|
|
95
|
+
}
|
|
96
|
+
await db.put(STORE_NAME, entry)
|
|
97
|
+
return entry
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function getAllCachedResults() {
|
|
101
|
+
const db = await getDB()
|
|
102
|
+
const results = await db.getAll(STORE_NAME)
|
|
103
|
+
return results.toSorted((a, b) => b.timestamp - a.timestamp)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function deleteCachedResult(id: string) {
|
|
107
|
+
const db = await getDB()
|
|
108
|
+
await db.delete(STORE_NAME, id)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function clearAllCachedResults() {
|
|
112
|
+
const db = await getDB()
|
|
113
|
+
await db.clear(STORE_NAME)
|
|
114
|
+
}
|
package/src/utils/fetch.ts
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { openDB } from 'idb'
|
|
2
|
+
|
|
3
|
+
const DB_NAME = 'jbrowse-msaview-taxonomy-cache'
|
|
4
|
+
const STORE_NAME = 'common-names'
|
|
5
|
+
const DB_VERSION = 1
|
|
6
|
+
|
|
7
|
+
interface CachedTaxonomy {
|
|
8
|
+
taxid: number
|
|
9
|
+
sciname: string
|
|
10
|
+
commonName?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function getDB() {
|
|
14
|
+
return openDB(DB_NAME, DB_VERSION, {
|
|
15
|
+
upgrade(db) {
|
|
16
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
17
|
+
db.createObjectStore(STORE_NAME, { keyPath: 'taxid' })
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function getCachedCommonName(taxid: number) {
|
|
24
|
+
const db = await getDB()
|
|
25
|
+
return db.get(STORE_NAME, taxid) as Promise<CachedTaxonomy | undefined>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function saveTaxonomyCache(entries: CachedTaxonomy[]) {
|
|
29
|
+
const db = await getDB()
|
|
30
|
+
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
await tx.store.put(entry)
|
|
33
|
+
}
|
|
34
|
+
await tx.done
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TaxonomyInfo {
|
|
38
|
+
sciname: string
|
|
39
|
+
commonName?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function fetchTaxonomyInfo(
|
|
43
|
+
taxids: number[],
|
|
44
|
+
): Promise<Map<number, TaxonomyInfo>> {
|
|
45
|
+
const result = new Map<number, TaxonomyInfo>()
|
|
46
|
+
const uncachedTaxids: number[] = []
|
|
47
|
+
|
|
48
|
+
for (const taxid of taxids) {
|
|
49
|
+
const cached = await getCachedCommonName(taxid)
|
|
50
|
+
if (cached) {
|
|
51
|
+
result.set(taxid, {
|
|
52
|
+
sciname: cached.sciname,
|
|
53
|
+
commonName: cached.commonName,
|
|
54
|
+
})
|
|
55
|
+
} else {
|
|
56
|
+
uncachedTaxids.push(taxid)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (uncachedTaxids.length === 0) {
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const batchSize = 100
|
|
65
|
+
const toCache: CachedTaxonomy[] = []
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < uncachedTaxids.length; i += batchSize) {
|
|
68
|
+
const batch = uncachedTaxids.slice(i, i + batchSize)
|
|
69
|
+
const idsParam = batch.join(',')
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(
|
|
73
|
+
`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=taxonomy&id=${idsParam}&retmode=xml`,
|
|
74
|
+
)
|
|
75
|
+
const text = await response.text()
|
|
76
|
+
|
|
77
|
+
for (const taxid of batch) {
|
|
78
|
+
const taxonRegex = new RegExp(
|
|
79
|
+
`<Taxon>.*?<TaxId>${taxid}</TaxId>.*?</Taxon>`,
|
|
80
|
+
's',
|
|
81
|
+
)
|
|
82
|
+
const taxonMatch = taxonRegex.exec(text)
|
|
83
|
+
|
|
84
|
+
if (taxonMatch) {
|
|
85
|
+
const taxonXml = taxonMatch[0]
|
|
86
|
+
const genbankCommon =
|
|
87
|
+
/<GenbankCommonName>(.*?)<\/GenbankCommonName>/.exec(taxonXml)
|
|
88
|
+
const commonName = /<CommonName>(.*?)<\/CommonName>/.exec(taxonXml)
|
|
89
|
+
const sciName = /<ScientificName>(.*?)<\/ScientificName>/.exec(
|
|
90
|
+
taxonXml,
|
|
91
|
+
)
|
|
92
|
+
const name = genbankCommon?.[1] ?? commonName?.[1]
|
|
93
|
+
|
|
94
|
+
const sci = sciName?.[1] ?? ''
|
|
95
|
+
result.set(taxid, { sciname: sci, commonName: name })
|
|
96
|
+
toCache.push({ taxid, sciname: sci, commonName: name })
|
|
97
|
+
} else {
|
|
98
|
+
toCache.push({ taxid, sciname: '', commonName: undefined })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Failed to fetch taxonomy data:', error)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (toCache.length > 0) {
|
|
107
|
+
await saveTaxonomyCache(toCache)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return result
|
|
111
|
+
}
|
package/src/utils/types.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
export interface BlastHitDescription {
|
|
2
|
+
accession: string
|
|
3
|
+
id: string
|
|
4
|
+
sciname: string
|
|
5
|
+
taxid?: number
|
|
6
|
+
title?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
export interface BlastResults {
|
|
2
10
|
BlastOutput2: {
|
|
3
11
|
report: {
|
|
4
12
|
results: {
|
|
5
13
|
search: {
|
|
6
14
|
hits: {
|
|
7
|
-
description:
|
|
15
|
+
description: BlastHitDescription[]
|
|
8
16
|
hsps: { hseq: string }[]
|
|
9
17
|
}[]
|
|
10
18
|
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { getId } from '../../util';
|
|
2
|
-
// Function to find a valid transcript ID that exists in the MSA list
|
|
3
|
-
export function findValidTranscriptId({ transcriptsList, validMsaList, }) {
|
|
4
|
-
if (!validMsaList || validMsaList.length === 0) {
|
|
5
|
-
return null;
|
|
6
|
-
}
|
|
7
|
-
// Try to find a transcript ID that exists in the MSA list
|
|
8
|
-
for (const transcript of transcriptsList) {
|
|
9
|
-
const id = getId(transcript);
|
|
10
|
-
if (id && validMsaList.includes(id)) {
|
|
11
|
-
return id;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=findValidTranscriptId.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"findValidTranscriptId.js","sourceRoot":"","sources":["../../../../src/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAIlC,qEAAqE;AACrE,MAAM,UAAU,qBAAqB,CAAC,EACpC,eAAe,EACf,YAAY,GAIb;IACC,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAA;IACb,CAAC;IAED,0DAA0D;IAC1D,KAAK,MAAM,UAAU,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,KAAK,CAAC,UAAU,CAAC,CAAA;QAC5B,IAAI,EAAE,IAAI,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|