jbrowse-plugin-msaview 2.7.1 → 2.7.3
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/AddHighlightModel/MsaToGenomeHighlight.js +6 -5
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.d.ts +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +5 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +3 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.js +31 -15
- package/dist/LaunchMsaView/components/geneticCodes.d.ts +15 -0
- package/dist/LaunchMsaView/components/geneticCodes.js +227 -0
- package/dist/LaunchMsaView/components/types.d.ts +1 -0
- package/dist/LaunchMsaView/index.js +32 -26
- package/dist/MsaViewPanel/doLaunchBlast.js +10 -22
- package/dist/MsaViewPanel/genomeToMSA.js +13 -6
- package/dist/MsaViewPanel/genomeToMSA.test.js +35 -6
- package/dist/MsaViewPanel/model.d.ts +68 -66
- package/dist/MsaViewPanel/model.js +14 -12
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +32 -13
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +43 -14
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +108 -18
- package/dist/MsaViewPanel/msaDataStore.js +8 -17
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +8 -6
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +31 -31
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +1 -2
- package/dist/utils/blastCache.js +9 -22
- package/dist/utils/domainCache.js +6 -15
- package/dist/utils/idb.d.ts +12 -0
- package/dist/utils/idb.js +21 -0
- package/dist/utils/taxonomyNames.js +13 -18
- package/dist/utils/useLocalStorage.d.ts +1 -0
- package/dist/utils/useLocalStorage.js +29 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -3
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +6 -8
- package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +1 -1
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.tsx +1 -1
- package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +4 -2
- package/src/LaunchMsaView/components/calculateProteinSequence.ts +35 -20
- package/src/LaunchMsaView/components/geneticCodes.ts +298 -0
- package/src/LaunchMsaView/components/types.ts +3 -0
- package/src/LaunchMsaView/index.ts +44 -35
- package/src/MsaViewPanel/doLaunchBlast.ts +20 -29
- package/src/MsaViewPanel/genomeToMSA.test.ts +38 -6
- package/src/MsaViewPanel/genomeToMSA.ts +14 -6
- package/src/MsaViewPanel/model.ts +17 -12
- package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +117 -18
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +70 -26
- package/src/MsaViewPanel/msaDataStore.ts +17 -17
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +8 -6
- package/src/utils/blastCache.ts +20 -23
- package/src/utils/domainCache.ts +14 -18
- package/src/utils/idb.ts +28 -0
- package/src/utils/taxonomyNames.ts +22 -24
- package/src/utils/useLocalStorage.ts +31 -0
- package/src/version.ts +1 -1
- package/dist/MsaViewPanel/blosum62.d.ts +0 -2
- package/dist/MsaViewPanel/blosum62.js +0 -627
- package/src/MsaViewPanel/blosum62.ts +0 -628
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// NCBI translation tables (genetic codes), copied verbatim from
|
|
2
|
+
// jbrowse-components packages/core/src/util/geneticCodes.ts, which parses them
|
|
3
|
+
// from NCBI's authoritative gc.prt. `ncbieaa` gives the amino acid for each of
|
|
4
|
+
// the 64 codons in a fixed base order; `sncbieaa` marks valid start codons.
|
|
5
|
+
//
|
|
6
|
+
// Vendored rather than imported: core exports this only on main, and the msaview
|
|
7
|
+
// bundle has to translate the same way on every host a config names, back to
|
|
8
|
+
// v4.0.0. Replace the whole file with a re-export of
|
|
9
|
+
// `@jbrowse/core/util/geneticCodes` once a release ships it -- that path is not
|
|
10
|
+
// in ReExports, so it bundles rather than binding to the host.
|
|
11
|
+
export interface NcbiGeneticCode {
|
|
12
|
+
id: number
|
|
13
|
+
name: string
|
|
14
|
+
ncbieaa: string
|
|
15
|
+
sncbieaa: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ncbiGeneticCodes: NcbiGeneticCode[] = [
|
|
19
|
+
{
|
|
20
|
+
id: 1,
|
|
21
|
+
name: 'Standard',
|
|
22
|
+
ncbieaa: 'FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
23
|
+
sncbieaa:
|
|
24
|
+
'---M------**--*----M---------------M----------------------------',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 2,
|
|
28
|
+
name: 'Vertebrate Mitochondrial',
|
|
29
|
+
ncbieaa: 'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSS**VVVVAAAADDEEGGGG',
|
|
30
|
+
sncbieaa:
|
|
31
|
+
'----------**--------------------MMMM----------**---M------------',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 3,
|
|
35
|
+
name: 'Yeast Mitochondrial',
|
|
36
|
+
ncbieaa: 'FFLLSSSSYY**CCWWTTTTPPPPHHQQRRRRIIMMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
37
|
+
sncbieaa:
|
|
38
|
+
'----------**----------------------MM---------------M------------',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 4,
|
|
42
|
+
name: 'Mold Mitochondrial; Protozoan Mitochondrial; Coelenterate Mitochondrial; Mycoplasma; Spiroplasma',
|
|
43
|
+
ncbieaa: 'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
44
|
+
sncbieaa:
|
|
45
|
+
'--MM------**-------M------------MMMM---------------M------------',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 5,
|
|
49
|
+
name: 'Invertebrate Mitochondrial',
|
|
50
|
+
ncbieaa: 'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSSSSVVVVAAAADDEEGGGG',
|
|
51
|
+
sncbieaa:
|
|
52
|
+
'---M------**--------------------MMMM---------------M------------',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 6,
|
|
56
|
+
name: 'Ciliate Nuclear; Dasycladacean Nuclear; Hexamita Nuclear',
|
|
57
|
+
ncbieaa: 'FFLLSSSSYYQQCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
58
|
+
sncbieaa:
|
|
59
|
+
'--------------*--------------------M----------------------------',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 9,
|
|
63
|
+
name: 'Echinoderm Mitochondrial; Flatworm Mitochondrial',
|
|
64
|
+
ncbieaa: 'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNNKSSSSVVVVAAAADDEEGGGG',
|
|
65
|
+
sncbieaa:
|
|
66
|
+
'----------**-----------------------M---------------M------------',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 10,
|
|
70
|
+
name: 'Euplotid Nuclear',
|
|
71
|
+
ncbieaa: 'FFLLSSSSYY**CCCWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
72
|
+
sncbieaa:
|
|
73
|
+
'----------**-----------------------M----------------------------',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 11,
|
|
77
|
+
name: 'Bacterial, Archaeal and Plant Plastid',
|
|
78
|
+
ncbieaa: 'FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
79
|
+
sncbieaa:
|
|
80
|
+
'---M------**--*----M------------MMMM---------------M------------',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 12,
|
|
84
|
+
name: 'Alternative Yeast Nuclear',
|
|
85
|
+
ncbieaa: 'FFLLSSSSYY**CC*WLLLSPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
86
|
+
sncbieaa:
|
|
87
|
+
'----------**--*----M---------------M----------------------------',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 13,
|
|
91
|
+
name: 'Ascidian Mitochondrial',
|
|
92
|
+
ncbieaa: 'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSSGGVVVVAAAADDEEGGGG',
|
|
93
|
+
sncbieaa:
|
|
94
|
+
'---M------**----------------------MM---------------M------------',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 14,
|
|
98
|
+
name: 'Alternative Flatworm Mitochondrial',
|
|
99
|
+
ncbieaa: 'FFLLSSSSYYY*CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNNKSSSSVVVVAAAADDEEGGGG',
|
|
100
|
+
sncbieaa:
|
|
101
|
+
'-----------*-----------------------M----------------------------',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 15,
|
|
105
|
+
name: 'Blepharisma Macronuclear',
|
|
106
|
+
ncbieaa: 'FFLLSSSSYY*QCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
107
|
+
sncbieaa:
|
|
108
|
+
'----------*---*--------------------M----------------------------',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 16,
|
|
112
|
+
name: 'Chlorophycean Mitochondrial',
|
|
113
|
+
ncbieaa: 'FFLLSSSSYY*LCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
114
|
+
sncbieaa:
|
|
115
|
+
'----------*---*--------------------M----------------------------',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 21,
|
|
119
|
+
name: 'Trematode Mitochondrial',
|
|
120
|
+
ncbieaa: 'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNNKSSSSVVVVAAAADDEEGGGG',
|
|
121
|
+
sncbieaa:
|
|
122
|
+
'----------**-----------------------M---------------M------------',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 22,
|
|
126
|
+
name: 'Scenedesmus obliquus Mitochondrial',
|
|
127
|
+
ncbieaa: 'FFLLSS*SYY*LCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
128
|
+
sncbieaa:
|
|
129
|
+
'------*---*---*--------------------M----------------------------',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 23,
|
|
133
|
+
name: 'Thraustochytrium Mitochondrial',
|
|
134
|
+
ncbieaa: 'FF*LSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
135
|
+
sncbieaa:
|
|
136
|
+
'--*-------**--*-----------------M--M---------------M------------',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 24,
|
|
140
|
+
name: 'Rhabdopleuridae Mitochondrial',
|
|
141
|
+
ncbieaa: 'FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSSKVVVVAAAADDEEGGGG',
|
|
142
|
+
sncbieaa:
|
|
143
|
+
'---M------**-------M---------------M---------------M------------',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 25,
|
|
147
|
+
name: 'Candidate Division SR1 and Gracilibacteria',
|
|
148
|
+
ncbieaa: 'FFLLSSSSYY**CCGWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
149
|
+
sncbieaa:
|
|
150
|
+
'---M------**-----------------------M---------------M------------',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 26,
|
|
154
|
+
name: 'Pachysolen tannophilus Nuclear',
|
|
155
|
+
ncbieaa: 'FFLLSSSSYY**CC*WLLLAPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
156
|
+
sncbieaa:
|
|
157
|
+
'----------**--*----M---------------M----------------------------',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 27,
|
|
161
|
+
name: 'Karyorelict Nuclear',
|
|
162
|
+
ncbieaa: 'FFLLSSSSYYQQCCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
163
|
+
sncbieaa:
|
|
164
|
+
'--------------*--------------------M----------------------------',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 28,
|
|
168
|
+
name: 'Condylostoma Nuclear',
|
|
169
|
+
ncbieaa: 'FFLLSSSSYYQQCCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
170
|
+
sncbieaa:
|
|
171
|
+
'----------**--*--------------------M----------------------------',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 29,
|
|
175
|
+
name: 'Mesodinium Nuclear',
|
|
176
|
+
ncbieaa: 'FFLLSSSSYYYYCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
177
|
+
sncbieaa:
|
|
178
|
+
'--------------*--------------------M----------------------------',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 30,
|
|
182
|
+
name: 'Peritrich Nuclear',
|
|
183
|
+
ncbieaa: 'FFLLSSSSYYEECC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
184
|
+
sncbieaa:
|
|
185
|
+
'--------------*--------------------M----------------------------',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 31,
|
|
189
|
+
name: 'Blastocrithidia Nuclear',
|
|
190
|
+
ncbieaa: 'FFLLSSSSYYEECCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
191
|
+
sncbieaa:
|
|
192
|
+
'----------**-----------------------M----------------------------',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 32,
|
|
196
|
+
name: 'Balanophoraceae Plastid',
|
|
197
|
+
ncbieaa: 'FFLLSSSSYY*WCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG',
|
|
198
|
+
sncbieaa:
|
|
199
|
+
'---M------*---*----M------------MMMM---------------M------------',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: 33,
|
|
203
|
+
name: 'Cephalodiscidae Mitochondrial',
|
|
204
|
+
ncbieaa: 'FFLLSSSSYYY*CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSSKVVVVAAAADDEEGGGG',
|
|
205
|
+
sncbieaa:
|
|
206
|
+
'---M-------*-------M---------------M---------------M------------',
|
|
207
|
+
},
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
// The codon order shared by every NCBI table -- the Base1/Base2/Base3 comment
|
|
211
|
+
// rows in gc.prt. codon i = BASE1[i] + BASE2[i] + BASE3[i].
|
|
212
|
+
const BASE1 = 'TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG'
|
|
213
|
+
const BASE2 = 'TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG'
|
|
214
|
+
const BASE3 = 'TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG'
|
|
215
|
+
|
|
216
|
+
const CODONS = Array.from(
|
|
217
|
+
{ length: 64 },
|
|
218
|
+
(_, i) => BASE1[i]! + BASE2[i]! + BASE3[i]!,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
const ncbiCodeById = new Map(ncbiGeneticCodes.map(t => [t.id, t]))
|
|
222
|
+
|
|
223
|
+
export interface GeneticCode {
|
|
224
|
+
id: number
|
|
225
|
+
name: string
|
|
226
|
+
// case-insensitive codon -> amino acid letter, '*' for a stop codon
|
|
227
|
+
codonTable: Record<string, string>
|
|
228
|
+
// valid start codons (uppercase)
|
|
229
|
+
starts: string[]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Expand an uppercase codon map so every case combination of a triplet resolves,
|
|
233
|
+
// which is what callers reading raw sequence need.
|
|
234
|
+
function caseExpand(table: Record<string, string>) {
|
|
235
|
+
const out: Record<string, string> = {}
|
|
236
|
+
for (const [codon, aa] of Object.entries(table)) {
|
|
237
|
+
const cases = (i: number) => {
|
|
238
|
+
const n = codon.charAt(i)
|
|
239
|
+
return [n.toUpperCase(), n.toLowerCase()]
|
|
240
|
+
}
|
|
241
|
+
for (const n0 of cases(0)) {
|
|
242
|
+
for (const n1 of cases(1)) {
|
|
243
|
+
for (const n2 of cases(2)) {
|
|
244
|
+
out[n0 + n1 + n2] = aa
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function buildGeneticCode(id: number): GeneticCode {
|
|
253
|
+
const def = ncbiCodeById.get(id)
|
|
254
|
+
if (!def && id !== 1) {
|
|
255
|
+
console.warn(
|
|
256
|
+
`Unknown genetic code (transl_table=${id}); using standard code`,
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
const {
|
|
260
|
+
id: resolvedId,
|
|
261
|
+
name,
|
|
262
|
+
ncbieaa,
|
|
263
|
+
sncbieaa,
|
|
264
|
+
} = def ?? ncbiCodeById.get(1)!
|
|
265
|
+
const table: Record<string, string> = {}
|
|
266
|
+
const starts: string[] = []
|
|
267
|
+
for (const [i, codon] of CODONS.entries()) {
|
|
268
|
+
table[codon] = ncbieaa[i]!
|
|
269
|
+
if (sncbieaa[i] === 'M') {
|
|
270
|
+
starts.push(codon)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { id: resolvedId, name, codonTable: caseExpand(table), starts }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const geneticCodeCache = new Map<number, GeneticCode>()
|
|
277
|
+
|
|
278
|
+
// Resolves the codon map + start set for an NCBI translation-table id, falling
|
|
279
|
+
// back to the standard code (1) for an unrecognized id. Memoized: there are only
|
|
280
|
+
// ~27 tables and each result is immutable.
|
|
281
|
+
export function getGeneticCode(id = 1): GeneticCode {
|
|
282
|
+
let code = geneticCodeCache.get(id)
|
|
283
|
+
if (!code) {
|
|
284
|
+
code = buildGeneticCode(id)
|
|
285
|
+
geneticCodeCache.set(id, code)
|
|
286
|
+
}
|
|
287
|
+
return code
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Parses a GFF/GenBank `transl_table` attribute value into an NCBI table id. The
|
|
291
|
+
// GFF adapter yields a string (or an array if the attribute repeated), so this
|
|
292
|
+
// normalizes both; returns undefined for a missing or non-positive-integer value
|
|
293
|
+
// so callers fall back to their default code.
|
|
294
|
+
export function parseTranslTable(value: unknown): number | undefined {
|
|
295
|
+
const raw = Array.isArray(value) ? value[0] : value
|
|
296
|
+
const n = Number(raw)
|
|
297
|
+
return Number.isInteger(n) && n > 0 ? n : undefined
|
|
298
|
+
}
|
|
@@ -15,8 +15,9 @@ function isDisplay(elt: { name: string }): elt is DisplayType {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// The canvas LinearBasicDisplay (JBrowse >=4.3) exposes the right-clicked
|
|
18
|
-
// feature via contextMenuInfo + async fetchFullFeature
|
|
19
|
-
//
|
|
18
|
+
// feature via contextMenuInfo + async fetchFullFeature. Hosts before that -- and
|
|
19
|
+
// the v3.7.0 in the wild that shipped configs still name -- expose it
|
|
20
|
+
// synchronously as contextMenuFeature, and only have that one.
|
|
20
21
|
interface ContextMenuInfo {
|
|
21
22
|
item: { featureId: string; type?: string }
|
|
22
23
|
displayedRegionIndex: number
|
|
@@ -25,13 +26,16 @@ interface ContextMenuInfo {
|
|
|
25
26
|
interface DisplayModel {
|
|
26
27
|
contextMenuItems: () => MenuItem[]
|
|
27
28
|
contextMenuInfo?: ContextMenuInfo
|
|
28
|
-
isGeneLike
|
|
29
|
-
fetchFullFeature
|
|
29
|
+
isGeneLike?: boolean
|
|
30
|
+
fetchFullFeature?: (
|
|
30
31
|
featureId: string,
|
|
31
32
|
displayedRegionIndex: number,
|
|
32
33
|
) => Promise<Feature | undefined>
|
|
34
|
+
contextMenuFeature?: Feature
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
const GENE_LIKE_TYPES = new Set(['gene', 'mRNA', 'transcript'])
|
|
38
|
+
|
|
35
39
|
function extendStateModel(stateModel: IAnyModelType) {
|
|
36
40
|
return stateModel.views((self: DisplayModel) => {
|
|
37
41
|
const superContextMenuItems = self.contextMenuItems
|
|
@@ -39,40 +43,45 @@ function extendStateModel(stateModel: IAnyModelType) {
|
|
|
39
43
|
contextMenuItems() {
|
|
40
44
|
const track = getContainingTrack(self)
|
|
41
45
|
const session = getSession(track)
|
|
46
|
+
const launch = (feature: Feature) => {
|
|
47
|
+
session.queueDialog(handleClose => [
|
|
48
|
+
LaunchMsaViewDialog,
|
|
49
|
+
{ model: track, handleClose, feature },
|
|
50
|
+
])
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
const info = self.contextMenuInfo
|
|
43
|
-
const
|
|
54
|
+
const fetchFullFeature = self.fetchFullFeature
|
|
55
|
+
const legacyFeature = self.contextMenuFeature
|
|
56
|
+
const onClick =
|
|
57
|
+
info && fetchFullFeature && self.isGeneLike
|
|
58
|
+
? () => {
|
|
59
|
+
fetchFullFeature(info.item.featureId, info.displayedRegionIndex)
|
|
60
|
+
.then(feature => {
|
|
61
|
+
if (feature) {
|
|
62
|
+
launch(feature)
|
|
63
|
+
} else {
|
|
64
|
+
session.notify(
|
|
65
|
+
'Could not load feature for MSA view',
|
|
66
|
+
'warning',
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
.catch((e: unknown) => {
|
|
71
|
+
session.notifyError(`${e}`, e)
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
: legacyFeature &&
|
|
75
|
+
GENE_LIKE_TYPES.has(String(legacyFeature.get('type')))
|
|
76
|
+
? () => {
|
|
77
|
+
launch(legacyFeature)
|
|
78
|
+
}
|
|
79
|
+
: undefined
|
|
80
|
+
|
|
44
81
|
return [
|
|
45
82
|
...superContextMenuItems(),
|
|
46
|
-
...(
|
|
47
|
-
? [
|
|
48
|
-
{
|
|
49
|
-
label: 'Launch MSA view',
|
|
50
|
-
icon: AddIcon,
|
|
51
|
-
onClick: () => {
|
|
52
|
-
self
|
|
53
|
-
.fetchFullFeature(
|
|
54
|
-
info.item.featureId,
|
|
55
|
-
info.displayedRegionIndex,
|
|
56
|
-
)
|
|
57
|
-
.then(feature => {
|
|
58
|
-
if (feature) {
|
|
59
|
-
session.queueDialog(handleClose => [
|
|
60
|
-
LaunchMsaViewDialog,
|
|
61
|
-
{ model: track, handleClose, feature },
|
|
62
|
-
])
|
|
63
|
-
} else {
|
|
64
|
-
session.notify(
|
|
65
|
-
'Could not load feature for MSA view',
|
|
66
|
-
'warning',
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
.catch((e: unknown) => {
|
|
71
|
-
session.notifyError(`${e}`, e)
|
|
72
|
-
})
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
]
|
|
83
|
+
...(onClick
|
|
84
|
+
? [{ label: 'Launch MSA view', icon: AddIcon, onClick }]
|
|
76
85
|
: []),
|
|
77
86
|
]
|
|
78
87
|
},
|
|
@@ -25,36 +25,29 @@ export async function doLaunchBlast({
|
|
|
25
25
|
} = self.blastParams!
|
|
26
26
|
const cleanedSeq = cleanProteinSequence(proteinSequence)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const onProgress = (arg: string) => {
|
|
29
|
+
self.setProgress(arg)
|
|
30
|
+
}
|
|
31
|
+
|
|
30
32
|
if (existingRid) {
|
|
33
|
+
// publish it before the first poll so the view can link out to NCBI while
|
|
34
|
+
// the job is still running
|
|
31
35
|
self.setRid(existingRid)
|
|
32
|
-
const result = await queryBlastFromRid({
|
|
33
|
-
rid: existingRid,
|
|
34
|
-
baseUrl,
|
|
35
|
-
onProgress: arg => {
|
|
36
|
-
self.setProgress(arg)
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
hits = result.hits
|
|
40
|
-
rid = result.rid
|
|
41
|
-
} else {
|
|
42
|
-
const result = await queryBlast({
|
|
43
|
-
query: cleanedSeq,
|
|
44
|
-
blastDatabase,
|
|
45
|
-
blastProgram,
|
|
46
|
-
baseUrl,
|
|
47
|
-
onProgress: arg => {
|
|
48
|
-
self.setProgress(arg)
|
|
49
|
-
},
|
|
50
|
-
onRid: r => {
|
|
51
|
-
self.setRid(r)
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
hits = result.hits
|
|
55
|
-
rid = result.rid
|
|
56
36
|
}
|
|
57
37
|
|
|
38
|
+
const { hits, rid } = existingRid
|
|
39
|
+
? await queryBlastFromRid({ rid: existingRid, baseUrl, onProgress })
|
|
40
|
+
: await queryBlast({
|
|
41
|
+
query: cleanedSeq,
|
|
42
|
+
blastDatabase,
|
|
43
|
+
blastProgram,
|
|
44
|
+
baseUrl,
|
|
45
|
+
onProgress,
|
|
46
|
+
onRid: r => {
|
|
47
|
+
self.setRid(r)
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
58
51
|
self.setProgress('Fetching species taxonomy info...')
|
|
59
52
|
const taxids = hits
|
|
60
53
|
.map(h => h.description[0]?.taxid)
|
|
@@ -80,9 +73,7 @@ export async function doLaunchBlast({
|
|
|
80
73
|
const result = await launchMSA({
|
|
81
74
|
algorithm: msaAlgorithm,
|
|
82
75
|
sequence: [`>QUERY\n${cleanedSeq}`, ...sequences].join('\n'),
|
|
83
|
-
onProgress
|
|
84
|
-
self.setProgress(arg)
|
|
85
|
-
},
|
|
76
|
+
onProgress,
|
|
86
77
|
})
|
|
87
78
|
|
|
88
79
|
const treeMetadataJson = JSON.stringify(treeMetadata)
|
|
@@ -91,8 +91,9 @@ describe('genomeToMSA', () => {
|
|
|
91
91
|
|
|
92
92
|
const result = genomeToMSA({ model })
|
|
93
93
|
|
|
94
|
-
// coord 1005 -
|
|
95
|
-
|
|
94
|
+
// hover coord 1005 is 1-based, so the 0-based genome position is 1004,
|
|
95
|
+
// which is ungapped position 4 of a region starting at 1000
|
|
96
|
+
expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 4)
|
|
96
97
|
expect(result).toBe(5)
|
|
97
98
|
})
|
|
98
99
|
|
|
@@ -125,10 +126,11 @@ describe('genomeToMSA', () => {
|
|
|
125
126
|
})
|
|
126
127
|
|
|
127
128
|
test('returns undefined when hover coord is before mafRegion start', () => {
|
|
129
|
+
// 1-based coord 1000 is the 0-based base 999, one before the region
|
|
128
130
|
mockGetSession.mockReturnValue({
|
|
129
131
|
hovered: {
|
|
130
132
|
hoverFeature: {},
|
|
131
|
-
hoverPosition: { coord:
|
|
133
|
+
hoverPosition: { coord: 1000, refName: 'chr1' },
|
|
132
134
|
},
|
|
133
135
|
} as any)
|
|
134
136
|
|
|
@@ -153,10 +155,11 @@ describe('genomeToMSA', () => {
|
|
|
153
155
|
})
|
|
154
156
|
|
|
155
157
|
test('returns undefined when hover coord is at or after mafRegion end', () => {
|
|
158
|
+
// 1-based coord 1011 is the 0-based base 1010, one past the region
|
|
156
159
|
mockGetSession.mockReturnValue({
|
|
157
160
|
hovered: {
|
|
158
161
|
hoverFeature: {},
|
|
159
|
-
hoverPosition: { coord:
|
|
162
|
+
hoverPosition: { coord: 1011, refName: 'chr1' },
|
|
160
163
|
},
|
|
161
164
|
} as any)
|
|
162
165
|
|
|
@@ -223,7 +226,9 @@ describe('genomeToMSA', () => {
|
|
|
223
226
|
const model = {
|
|
224
227
|
querySeqName: 'QUERY',
|
|
225
228
|
transcriptToMsaMap: {
|
|
226
|
-
|
|
229
|
+
refName: 'chr1',
|
|
230
|
+
// g2p is keyed by 0-based genome position, the hover coord is 1-based
|
|
231
|
+
g2p: { 1004: 10 },
|
|
227
232
|
},
|
|
228
233
|
mafRegion: undefined,
|
|
229
234
|
connectedView: { initialized: true },
|
|
@@ -236,6 +241,32 @@ describe('genomeToMSA', () => {
|
|
|
236
241
|
expect(result).toBe(10)
|
|
237
242
|
})
|
|
238
243
|
|
|
244
|
+
test('returns undefined when the hover is on another refName', () => {
|
|
245
|
+
// session.hovered is global, so a hover on an unrelated chromosome can
|
|
246
|
+
// carry a coordinate that happens to be a g2p key
|
|
247
|
+
mockGetSession.mockReturnValue({
|
|
248
|
+
hovered: {
|
|
249
|
+
hoverFeature: {},
|
|
250
|
+
hoverPosition: { coord: 1005, refName: 'chr2' },
|
|
251
|
+
},
|
|
252
|
+
} as any)
|
|
253
|
+
|
|
254
|
+
const mockSeqPosToVisibleCol = vi.fn()
|
|
255
|
+
const model = {
|
|
256
|
+
querySeqName: 'QUERY',
|
|
257
|
+
transcriptToMsaMap: {
|
|
258
|
+
refName: 'chr1',
|
|
259
|
+
g2p: { 1004: 10 },
|
|
260
|
+
},
|
|
261
|
+
mafRegion: undefined,
|
|
262
|
+
connectedView: { initialized: true },
|
|
263
|
+
seqPosToVisibleCol: mockSeqPosToVisibleCol,
|
|
264
|
+
} as any
|
|
265
|
+
|
|
266
|
+
expect(genomeToMSA({ model })).toBeUndefined()
|
|
267
|
+
expect(mockSeqPosToVisibleCol).not.toHaveBeenCalled()
|
|
268
|
+
})
|
|
269
|
+
|
|
239
270
|
test('returns undefined when g2p has no mapping for coord', () => {
|
|
240
271
|
mockGetSession.mockReturnValue({
|
|
241
272
|
hovered: {
|
|
@@ -247,7 +278,8 @@ describe('genomeToMSA', () => {
|
|
|
247
278
|
const model = {
|
|
248
279
|
querySeqName: 'QUERY',
|
|
249
280
|
transcriptToMsaMap: {
|
|
250
|
-
|
|
281
|
+
refName: 'chr1',
|
|
282
|
+
g2p: { 1000: 0 }, // No entry for 1004
|
|
251
283
|
},
|
|
252
284
|
mafRegion: undefined,
|
|
253
285
|
connectedView: { initialized: true },
|
|
@@ -12,22 +12,30 @@ export function genomeToMSA({ model }: { model: JBrowsePluginMsaViewModel }) {
|
|
|
12
12
|
return undefined
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const { coord
|
|
15
|
+
const { coord, refName } = hovered.hoverPosition
|
|
16
|
+
|
|
17
|
+
// hoverPosition.coord is a 1-based display coordinate (core's pxToBp adds the
|
|
18
|
+
// +1), while g2p and mafRegion are keyed by 0-based genome position
|
|
19
|
+
const genomePos = coord - 1
|
|
16
20
|
|
|
17
21
|
if (mafRegion) {
|
|
18
22
|
if (
|
|
19
23
|
refName !== mafRegion.refName ||
|
|
20
24
|
!connectedView.assemblyNames.includes(mafRegion.assemblyName) ||
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
genomePos < mafRegion.start ||
|
|
26
|
+
genomePos >= mafRegion.end
|
|
23
27
|
) {
|
|
24
28
|
return undefined
|
|
25
29
|
}
|
|
26
|
-
return model.seqPosToVisibleCol(querySeqName,
|
|
30
|
+
return model.seqPosToVisibleCol(querySeqName, genomePos - mafRegion.start)
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// session.hovered is global -- set by whichever LinearGenomeView the cursor
|
|
34
|
+
// was last over, on any assembly -- so the refName gate is load bearing:
|
|
35
|
+
// without it the same numeric coordinate on an unrelated chromosome matches a
|
|
36
|
+
// g2p key and lights up a column for a different locus
|
|
37
|
+
if (refName === transcriptToMsaMap?.refName) {
|
|
38
|
+
const seqPos = transcriptToMsaMap.g2p[genomePos]
|
|
31
39
|
if (seqPos !== undefined) {
|
|
32
40
|
return model.seqPosToVisibleCol(querySeqName, seqPos)
|
|
33
41
|
}
|
|
@@ -19,7 +19,10 @@ import {
|
|
|
19
19
|
storeDataToIndexedDB,
|
|
20
20
|
syncGenomeHoverToMsaColumn,
|
|
21
21
|
} from './afterCreateAutoruns'
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
msaCoordToGenomeCoord,
|
|
24
|
+
msaCoordToGenomeRegions,
|
|
25
|
+
} from './msaCoordToGenomeCoord'
|
|
23
26
|
|
|
24
27
|
import type { MafRegion, MsaViewInitState } from './types'
|
|
25
28
|
import type {
|
|
@@ -188,35 +191,37 @@ export default function stateModelFactory() {
|
|
|
188
191
|
.views(self => ({
|
|
189
192
|
/**
|
|
190
193
|
* #getter
|
|
191
|
-
* Genome
|
|
194
|
+
* Genome regions under the current MSA hover column. Suppressed on the LGV
|
|
192
195
|
* while it's being hovered (GenomeMouseoverHighlight shows the crisp 1bp
|
|
193
196
|
* marker there instead of this wider codon band).
|
|
194
197
|
*/
|
|
195
|
-
get
|
|
198
|
+
get connectedHoverHighlights(): IRegion[] {
|
|
196
199
|
const { mouseCol } = self
|
|
197
200
|
return mouseCol === undefined
|
|
198
|
-
?
|
|
199
|
-
:
|
|
201
|
+
? []
|
|
202
|
+
: msaCoordToGenomeRegions({ model: self, coord: mouseCol })
|
|
200
203
|
},
|
|
201
204
|
/**
|
|
202
205
|
* #getter
|
|
203
|
-
* Genome
|
|
206
|
+
* Genome regions under the persistent MSA click selection. Shown
|
|
204
207
|
* regardless of LGV hover, so hovering the genome doesn't hide it.
|
|
205
208
|
*/
|
|
206
|
-
get
|
|
209
|
+
get connectedClickHighlights(): IRegion[] {
|
|
207
210
|
const { mouseClickCol } = self
|
|
208
211
|
return mouseClickCol === undefined
|
|
209
|
-
?
|
|
210
|
-
:
|
|
212
|
+
? []
|
|
213
|
+
: msaCoordToGenomeRegions({ model: self, coord: mouseClickCol })
|
|
211
214
|
},
|
|
212
215
|
/**
|
|
213
216
|
* #getter
|
|
217
|
+
* cross-plugin contract: jbrowse-plugin-mafviewer reads this off the view
|
|
218
|
+
* to draw the same highlights in its own display
|
|
214
219
|
*/
|
|
215
220
|
get connectedHighlights(): IRegion[] {
|
|
216
221
|
return [
|
|
217
|
-
this.
|
|
218
|
-
this.
|
|
219
|
-
]
|
|
222
|
+
...this.connectedHoverHighlights,
|
|
223
|
+
...this.connectedClickHighlights,
|
|
224
|
+
]
|
|
220
225
|
},
|
|
221
226
|
}))
|
|
222
227
|
|