jbrowse-plugin-msaview 2.7.2 → 2.7.4
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/LaunchMsaViewDialog.js +7 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.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/OrthologQuery/OrthologPanel.d.ts +8 -0
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +86 -0
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +9 -0
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +13 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +8 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.js +28 -0
- package/dist/MsaViewPanel/doLaunchBlast.js +10 -22
- package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +23 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.js +97 -0
- package/dist/MsaViewPanel/genomeToMSA.js +13 -6
- package/dist/MsaViewPanel/genomeToMSA.test.js +35 -6
- package/dist/MsaViewPanel/model.d.ts +88 -70
- package/dist/MsaViewPanel/model.js +26 -13
- 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 +35 -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/ncbiOrthologs.d.ts +105 -0
- package/dist/utils/ncbiOrthologs.js +211 -0
- package/dist/utils/ncbiOrthologs.test.d.ts +1 -0
- package/dist/utils/ncbiOrthologs.test.js +41 -0
- package/dist/utils/taxonomyNames.js +13 -18
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +6 -8
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +13 -2
- package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +1 -1
- package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +4 -2
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +172 -0
- package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +28 -0
- package/src/MsaViewPanel/afterCreateAutoruns.ts +27 -0
- package/src/MsaViewPanel/doLaunchBlast.ts +20 -29
- package/src/MsaViewPanel/doLaunchOrthologs.ts +123 -0
- package/src/MsaViewPanel/genomeToMSA.test.ts +38 -6
- package/src/MsaViewPanel/genomeToMSA.ts +14 -6
- package/src/MsaViewPanel/model.ts +41 -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/ncbiOrthologs.test.ts +56 -0
- package/src/utils/ncbiOrthologs.ts +320 -0
- package/src/utils/taxonomyNames.ts +22 -24
- 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
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
|
|
1
2
|
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
|
|
3
|
+
import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
|
|
4
|
+
// codon at protein position i covers three consecutive genome bases starting at
|
|
5
|
+
// 100 + i * 3, i.e. a single-exon forward-strand transcript
|
|
6
|
+
function forwardCodons(n) {
|
|
7
|
+
return Object.fromEntries(Array.from({ length: n }, (_, i) => [
|
|
8
|
+
i,
|
|
9
|
+
[100 + i * 3, 101 + i * 3, 102 + i * 3],
|
|
10
|
+
]));
|
|
11
|
+
}
|
|
3
12
|
describe('msaCoordToGenomeCoord', () => {
|
|
4
13
|
test('returns undefined when neither transcriptToMsaMap nor mafRegion is defined', () => {
|
|
5
14
|
const model = {
|
|
@@ -16,7 +25,7 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
16
25
|
querySeqName: 'QUERY',
|
|
17
26
|
transcriptToMsaMap: {
|
|
18
27
|
refName: 'chr1',
|
|
19
|
-
|
|
28
|
+
p2gCodon: forwardCodons(2),
|
|
20
29
|
},
|
|
21
30
|
rows: [['OTHER', 'MKAA']],
|
|
22
31
|
};
|
|
@@ -28,7 +37,7 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
28
37
|
querySeqName: 'QUERY',
|
|
29
38
|
transcriptToMsaMap: {
|
|
30
39
|
refName: 'chr1',
|
|
31
|
-
|
|
40
|
+
p2gCodon: forwardCodons(2),
|
|
32
41
|
},
|
|
33
42
|
rows: [['QUERY', 'M-KA']],
|
|
34
43
|
};
|
|
@@ -41,7 +50,7 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
41
50
|
querySeqName: 'QUERY',
|
|
42
51
|
transcriptToMsaMap: {
|
|
43
52
|
refName: 'chr1',
|
|
44
|
-
|
|
53
|
+
p2gCodon: forwardCodons(4),
|
|
45
54
|
},
|
|
46
55
|
rows: [['QUERY', 'MKAA']],
|
|
47
56
|
};
|
|
@@ -58,7 +67,7 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
58
67
|
querySeqName: 'QUERY',
|
|
59
68
|
transcriptToMsaMap: {
|
|
60
69
|
refName: 'chr1',
|
|
61
|
-
|
|
70
|
+
p2gCodon: forwardCodons(4),
|
|
62
71
|
},
|
|
63
72
|
rows: [['QUERY', 'M-K-AA']],
|
|
64
73
|
// 012345 gapped positions
|
|
@@ -79,34 +88,33 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
79
88
|
end: 109,
|
|
80
89
|
});
|
|
81
90
|
});
|
|
82
|
-
test('returns undefined when
|
|
91
|
+
test('returns undefined when the position has no codon mapping', () => {
|
|
83
92
|
const model = {
|
|
84
93
|
querySeqName: 'QUERY',
|
|
85
94
|
transcriptToMsaMap: {
|
|
86
95
|
refName: 'chr1',
|
|
87
|
-
|
|
96
|
+
p2gCodon: forwardCodons(1),
|
|
88
97
|
},
|
|
89
98
|
rows: [['QUERY', 'MKAA']],
|
|
90
99
|
};
|
|
91
|
-
//
|
|
92
|
-
const result = msaCoordToGenomeCoord({ model, coord:
|
|
100
|
+
// ungapped position 1 has no entry in p2gCodon
|
|
101
|
+
const result = msaCoordToGenomeCoord({ model, coord: 1 });
|
|
93
102
|
expect(result).toBeUndefined();
|
|
94
103
|
});
|
|
95
|
-
test('
|
|
104
|
+
test('maps the final residue, whose codon has no successor', () => {
|
|
96
105
|
const model = {
|
|
97
106
|
querySeqName: 'QUERY',
|
|
98
107
|
transcriptToMsaMap: {
|
|
99
108
|
refName: 'chr1',
|
|
100
|
-
|
|
109
|
+
p2gCodon: forwardCodons(4),
|
|
101
110
|
},
|
|
102
111
|
rows: [['QUERY', 'MKAA']],
|
|
103
112
|
};
|
|
104
|
-
|
|
105
|
-
const result = msaCoordToGenomeCoord({ model, coord: 0 });
|
|
113
|
+
const result = msaCoordToGenomeCoord({ model, coord: 3 });
|
|
106
114
|
expect(result).toEqual({
|
|
107
115
|
refName: 'chr1',
|
|
108
|
-
start:
|
|
109
|
-
end:
|
|
116
|
+
start: 109,
|
|
117
|
+
end: 112,
|
|
110
118
|
});
|
|
111
119
|
});
|
|
112
120
|
test('returns undefined for out of bounds coord', () => {
|
|
@@ -114,7 +122,7 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
114
122
|
querySeqName: 'QUERY',
|
|
115
123
|
transcriptToMsaMap: {
|
|
116
124
|
refName: 'chr1',
|
|
117
|
-
|
|
125
|
+
p2gCodon: forwardCodons(2),
|
|
118
126
|
},
|
|
119
127
|
rows: [['QUERY', 'MK']],
|
|
120
128
|
};
|
|
@@ -127,7 +135,7 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
127
135
|
querySeqName: 'SEQ2',
|
|
128
136
|
transcriptToMsaMap: {
|
|
129
137
|
refName: 'chr1',
|
|
130
|
-
|
|
138
|
+
p2gCodon: { 0: [200, 201, 202], 1: [203, 204, 205] },
|
|
131
139
|
},
|
|
132
140
|
rows: [
|
|
133
141
|
['SEQ1', 'AAAA'],
|
|
@@ -142,6 +150,88 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
142
150
|
end: 203,
|
|
143
151
|
});
|
|
144
152
|
});
|
|
153
|
+
// The mapping comes from the real g2p_mapper rather than hand-written
|
|
154
|
+
// fixtures: on the reverse strand p2g stores the codon's *highest*
|
|
155
|
+
// coordinate, which is what the old p2g[pos]..p2g[pos+1] arithmetic got wrong
|
|
156
|
+
describe('real g2p_mapper mappings', () => {
|
|
157
|
+
test('forward strand, single exon', () => {
|
|
158
|
+
const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
|
|
159
|
+
refName: 'chr1',
|
|
160
|
+
start: 100,
|
|
161
|
+
end: 112,
|
|
162
|
+
strand: 1,
|
|
163
|
+
subfeatures: [{ refName: 'chr1', type: 'CDS', start: 100, end: 112 }],
|
|
164
|
+
});
|
|
165
|
+
const model = {
|
|
166
|
+
querySeqName: 'QUERY',
|
|
167
|
+
transcriptToMsaMap: { refName, p2gCodon },
|
|
168
|
+
rows: [['QUERY', 'MKAA']],
|
|
169
|
+
};
|
|
170
|
+
expect(msaCoordToGenomeCoord({ model, coord: 0 })).toEqual({
|
|
171
|
+
refName: 'chr1',
|
|
172
|
+
start: 100,
|
|
173
|
+
end: 103,
|
|
174
|
+
});
|
|
175
|
+
expect(msaCoordToGenomeCoord({ model, coord: 3 })).toEqual({
|
|
176
|
+
refName: 'chr1',
|
|
177
|
+
start: 109,
|
|
178
|
+
end: 112,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
test('reverse strand codon covers the last three bases of the CDS', () => {
|
|
182
|
+
const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
|
|
183
|
+
refName: 'chr1',
|
|
184
|
+
start: 100,
|
|
185
|
+
end: 112,
|
|
186
|
+
strand: -1,
|
|
187
|
+
subfeatures: [{ refName: 'chr1', type: 'CDS', start: 100, end: 112 }],
|
|
188
|
+
});
|
|
189
|
+
const model = {
|
|
190
|
+
querySeqName: 'QUERY',
|
|
191
|
+
transcriptToMsaMap: { refName, p2gCodon },
|
|
192
|
+
rows: [['QUERY', 'MKAA']],
|
|
193
|
+
};
|
|
194
|
+
// the first residue is translated from the 3' end of the genome region
|
|
195
|
+
expect(msaCoordToGenomeCoord({ model, coord: 0 })).toEqual({
|
|
196
|
+
refName: 'chr1',
|
|
197
|
+
start: 109,
|
|
198
|
+
end: 112,
|
|
199
|
+
});
|
|
200
|
+
expect(msaCoordToGenomeCoord({ model, coord: 3 })).toEqual({
|
|
201
|
+
refName: 'chr1',
|
|
202
|
+
start: 100,
|
|
203
|
+
end: 103,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
test('codon split across an exon boundary yields one region per piece', () => {
|
|
207
|
+
// exon 1 contributes 4 bases, so residue 1 straddles the intron
|
|
208
|
+
const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
|
|
209
|
+
refName: 'chr1',
|
|
210
|
+
start: 100,
|
|
211
|
+
end: 210,
|
|
212
|
+
strand: 1,
|
|
213
|
+
subfeatures: [
|
|
214
|
+
{ refName: 'chr1', type: 'CDS', start: 100, end: 104 },
|
|
215
|
+
{ refName: 'chr1', type: 'CDS', start: 200, end: 202 },
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
const model = {
|
|
219
|
+
querySeqName: 'QUERY',
|
|
220
|
+
transcriptToMsaMap: { refName, p2gCodon },
|
|
221
|
+
rows: [['QUERY', 'MK']],
|
|
222
|
+
};
|
|
223
|
+
expect(msaCoordToGenomeRegions({ model, coord: 1 })).toEqual([
|
|
224
|
+
{ refName: 'chr1', start: 103, end: 104 },
|
|
225
|
+
{ refName: 'chr1', start: 200, end: 202 },
|
|
226
|
+
]);
|
|
227
|
+
// the single-region form bounds the pieces, for navigation
|
|
228
|
+
expect(msaCoordToGenomeCoord({ model, coord: 1 })).toEqual({
|
|
229
|
+
refName: 'chr1',
|
|
230
|
+
start: 103,
|
|
231
|
+
end: 202,
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
145
235
|
// MAF region tests
|
|
146
236
|
describe('mafRegion', () => {
|
|
147
237
|
test('returns genome position for mafRegion mapping', () => {
|
|
@@ -217,7 +307,7 @@ describe('msaCoordToGenomeCoord', () => {
|
|
|
217
307
|
querySeqName: 'hg38.chr1',
|
|
218
308
|
transcriptToMsaMap: {
|
|
219
309
|
refName: 'chr2',
|
|
220
|
-
|
|
310
|
+
p2gCodon: { 0: [5000, 5001, 5002], 1: [5003, 5004, 5005] },
|
|
221
311
|
},
|
|
222
312
|
mafRegion: {
|
|
223
313
|
refName: 'chr1',
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createDbOpener } from '../utils/idb';
|
|
2
2
|
const DB_NAME = 'jbrowse-msaview-data';
|
|
3
3
|
const DB_VERSION = 1;
|
|
4
4
|
const STORE_NAME = 'msa-data';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
}).catch((e) => {
|
|
15
|
-
dbPromise = undefined;
|
|
16
|
-
throw e;
|
|
17
|
-
});
|
|
18
|
-
return dbPromise;
|
|
19
|
-
}
|
|
5
|
+
const getDB = createDbOpener(DB_NAME, DB_VERSION, db => {
|
|
6
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
7
|
+
const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
|
|
8
|
+
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
9
|
+
}
|
|
10
|
+
});
|
|
20
11
|
export function generateDataStoreId() {
|
|
21
12
|
return `msa-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
22
13
|
}
|
|
@@ -41,7 +32,7 @@ export async function storeMsaData(id, data) {
|
|
|
41
32
|
export async function retrieveMsaData(id) {
|
|
42
33
|
try {
|
|
43
34
|
const db = await getDB();
|
|
44
|
-
const result =
|
|
35
|
+
const result = await db.get(STORE_NAME, id);
|
|
45
36
|
if (result) {
|
|
46
37
|
return {
|
|
47
38
|
msa: result.msa,
|
|
@@ -45,12 +45,14 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
|
|
|
45
45
|
beforeEach(() => {
|
|
46
46
|
vi.clearAllMocks();
|
|
47
47
|
});
|
|
48
|
-
test('genome hover at coord 1005 highlights MSA column
|
|
48
|
+
test('genome hover at coord 1005 highlights MSA column 4', () => {
|
|
49
49
|
const { model, calls } = makeModel();
|
|
50
50
|
const run = syncGenomeHoverToMsaColumn(model);
|
|
51
|
-
|
|
51
|
+
// the hover coord is 1-based, so 1005 is the 0-based base 1004, i.e.
|
|
52
|
+
// ungapped offset 4 into a region starting at 1000
|
|
53
|
+
hoverGenome(1005);
|
|
52
54
|
run();
|
|
53
|
-
expect(calls).toEqual([
|
|
55
|
+
expect(calls).toEqual([4]);
|
|
54
56
|
});
|
|
55
57
|
test('moving the genome hover moves the highlighted column', () => {
|
|
56
58
|
const { model, calls } = makeModel();
|
|
@@ -59,7 +61,7 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
|
|
|
59
61
|
run();
|
|
60
62
|
hoverGenome(1007);
|
|
61
63
|
run();
|
|
62
|
-
expect(calls).toEqual([
|
|
64
|
+
expect(calls).toEqual([1, 6]);
|
|
63
65
|
});
|
|
64
66
|
test('leaving the genome clears the column it set', () => {
|
|
65
67
|
const { model, calls } = makeModel();
|
|
@@ -68,7 +70,7 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
|
|
|
68
70
|
run();
|
|
69
71
|
clearGenomeHover();
|
|
70
72
|
run();
|
|
71
|
-
expect(calls).toEqual([
|
|
73
|
+
expect(calls).toEqual([3, undefined]);
|
|
72
74
|
});
|
|
73
75
|
test('a hover outside the maf region clears a previously-set column once', () => {
|
|
74
76
|
const { model, calls } = makeModel();
|
|
@@ -78,7 +80,7 @@ describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
|
|
|
78
80
|
hoverGenome(5000); // outside [1000,1010) -> genomeToMSA returns undefined
|
|
79
81
|
run();
|
|
80
82
|
run();
|
|
81
|
-
expect(calls).toEqual([
|
|
83
|
+
expect(calls).toEqual([3, undefined]);
|
|
82
84
|
});
|
|
83
85
|
test('never touches mouseCol when the genome never provides a column, so a direct MSA hover survives unrelated session hovers', () => {
|
|
84
86
|
const { model, calls } = makeModel();
|