jbrowse-plugin-msaview 2.4.5 → 2.5.1
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/util.js +1 -1
- package/dist/LaunchMsaViewExtensionPoint/index.js +2 -1
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +15 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.js +53 -0
- package/dist/MsaViewPanel/loadProteinDomains.d.ts +16 -0
- package/dist/MsaViewPanel/loadProteinDomains.js +33 -0
- package/dist/MsaViewPanel/model.d.ts +69 -399
- package/dist/MsaViewPanel/model.js +17 -39
- package/dist/MsaViewPanel/pairwiseAlignment.js +2 -9
- package/dist/MsaViewPanel/structureConnection.d.ts +0 -6
- package/dist/MsaViewPanel/structureConnection.js +0 -16
- package/dist/MsaViewPanel/structureConnection.test.js +1 -51
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.d.ts +1 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +92 -0
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +30 -30
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/domainCache.d.ts +8 -0
- package/dist/utils/domainCache.js +28 -0
- package/dist/utils/eutils.d.ts +3 -0
- package/dist/utils/eutils.js +14 -0
- package/dist/utils/msa.js +23 -19
- package/dist/utils/ncbiBlast.js +23 -25
- package/dist/utils/ncbiDomains.d.ts +39 -0
- package/dist/utils/ncbiDomains.js +154 -0
- package/dist/utils/poll.d.ts +11 -0
- package/dist/utils/poll.js +19 -0
- package/dist/utils/taxonomyNames.js +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +17 -14
- package/src/LaunchMsaView/util.ts +1 -3
- package/src/LaunchMsaViewExtensionPoint/index.ts +3 -0
- package/src/MsaViewPanel/afterCreateAutoruns.ts +54 -0
- package/src/MsaViewPanel/loadProteinDomains.ts +57 -0
- package/src/MsaViewPanel/model.ts +23 -45
- package/src/MsaViewPanel/pairwiseAlignment.ts +2 -7
- package/src/MsaViewPanel/structureConnection.test.ts +1 -61
- package/src/MsaViewPanel/structureConnection.ts +0 -22
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +112 -0
- package/src/utils/domainCache.ts +44 -0
- package/src/utils/eutils.ts +16 -0
- package/src/utils/msa.ts +23 -19
- package/src/utils/ncbiBlast.ts +28 -33
- package/src/utils/ncbiDomains.ts +171 -0
- package/src/utils/poll.ts +28 -0
- package/src/utils/taxonomyNames.ts +3 -1
- package/src/version.ts +1 -1
|
@@ -5,11 +5,10 @@ import { addDisposer, types } from '@jbrowse/mobx-state-tree';
|
|
|
5
5
|
import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
|
|
6
6
|
import { autorun } from 'mobx';
|
|
7
7
|
import { MSAModelF } from 'react-msaview';
|
|
8
|
-
import { autoConnectStructures, highlightConnectedStructures, launchBlastIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, } from './afterCreateAutoruns';
|
|
9
|
-
import { genomeToMSA } from './genomeToMSA';
|
|
8
|
+
import { autoConnectStructures, autoLoadProteinDomains, highlightConnectedStructures, launchBlastIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
|
|
10
9
|
import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
|
|
11
10
|
import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment';
|
|
12
|
-
import { getProteinViews
|
|
11
|
+
import { getProteinViews } from './structureConnection';
|
|
13
12
|
import { getCanonicalRefName } from './util';
|
|
14
13
|
const ConnectStructureDialog = lazy(() => import('./components/ConnectStructureDialog'));
|
|
15
14
|
/**
|
|
@@ -28,9 +27,6 @@ export default function stateModelFactory() {
|
|
|
28
27
|
* #property
|
|
29
28
|
*/
|
|
30
29
|
connectedFeature: types.frozen(),
|
|
31
|
-
/**
|
|
32
|
-
* #property
|
|
33
|
-
*/
|
|
34
30
|
/**
|
|
35
31
|
* #property
|
|
36
32
|
*/
|
|
@@ -85,6 +81,12 @@ export default function stateModelFactory() {
|
|
|
85
81
|
* #volatile
|
|
86
82
|
*/
|
|
87
83
|
isStoringData: false,
|
|
84
|
+
/**
|
|
85
|
+
* #volatile
|
|
86
|
+
* guards the one-shot auto-fetch of protein domains so it doesn't refire
|
|
87
|
+
* when NCBI returns no domains (leaving interProAnnotations undefined)
|
|
88
|
+
*/
|
|
89
|
+
domainsRequested: false,
|
|
88
90
|
}))
|
|
89
91
|
.views(self => ({
|
|
90
92
|
/**
|
|
@@ -132,37 +134,6 @@ export default function stateModelFactory() {
|
|
|
132
134
|
},
|
|
133
135
|
}))
|
|
134
136
|
.views(self => ({
|
|
135
|
-
/**
|
|
136
|
-
* #getter
|
|
137
|
-
*/
|
|
138
|
-
get structureHoverCol() {
|
|
139
|
-
for (const conn of self.connectedProteinViews) {
|
|
140
|
-
const structure = conn.proteinView.structures[conn.structureIdx];
|
|
141
|
-
const structurePos = structure?.hoverPosition?.structureSeqPos;
|
|
142
|
-
if (structurePos !== undefined) {
|
|
143
|
-
const msaUngapped = conn.structureToMsa[structurePos];
|
|
144
|
-
if (msaUngapped !== undefined) {
|
|
145
|
-
const seq = self.getSequenceByRowName(conn.msaRowName);
|
|
146
|
-
if (seq) {
|
|
147
|
-
const globalCol = ungappedToGappedPosition(seq, msaUngapped);
|
|
148
|
-
if (globalCol !== undefined) {
|
|
149
|
-
return self.globalColToVisibleCol(globalCol);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return undefined;
|
|
156
|
-
},
|
|
157
|
-
}))
|
|
158
|
-
.views(self => ({
|
|
159
|
-
/**
|
|
160
|
-
* #getter
|
|
161
|
-
*/
|
|
162
|
-
get mouseCol2() {
|
|
163
|
-
return (self.structureHoverCol ??
|
|
164
|
-
genomeToMSA({ model: self }));
|
|
165
|
-
},
|
|
166
137
|
/**
|
|
167
138
|
* #getter
|
|
168
139
|
*/
|
|
@@ -251,6 +222,12 @@ export default function stateModelFactory() {
|
|
|
251
222
|
setIsStoringData(arg) {
|
|
252
223
|
self.isStoringData = arg;
|
|
253
224
|
},
|
|
225
|
+
/**
|
|
226
|
+
* #action
|
|
227
|
+
*/
|
|
228
|
+
setDomainsRequested(arg) {
|
|
229
|
+
self.domainsRequested = arg;
|
|
230
|
+
},
|
|
254
231
|
/**
|
|
255
232
|
* #action
|
|
256
233
|
*/
|
|
@@ -296,13 +273,12 @@ export default function stateModelFactory() {
|
|
|
296
273
|
throw new Error('Structure sequence not available');
|
|
297
274
|
}
|
|
298
275
|
const alignment = runPairwiseAlignment(ungappedMsaSequence, structureSequence);
|
|
299
|
-
const { seq1ToSeq2
|
|
276
|
+
const { seq1ToSeq2 } = buildAlignmentMaps(alignment);
|
|
300
277
|
const connection = {
|
|
301
278
|
proteinViewId,
|
|
302
279
|
structureIdx,
|
|
303
280
|
msaRowName: rowName,
|
|
304
281
|
msaToStructure: Object.fromEntries(seq1ToSeq2),
|
|
305
|
-
structureToMsa: Object.fromEntries(seq2ToSeq1),
|
|
306
282
|
};
|
|
307
283
|
self.connectedStructures.push(connection);
|
|
308
284
|
},
|
|
@@ -386,12 +362,14 @@ export default function stateModelFactory() {
|
|
|
386
362
|
processInit,
|
|
387
363
|
highlightConnectedStructures,
|
|
388
364
|
autoConnectStructures,
|
|
365
|
+
autoLoadProteinDomains,
|
|
389
366
|
observeProteinHighlights,
|
|
390
367
|
]) {
|
|
391
368
|
addDisposer(self, autorun(() => {
|
|
392
369
|
fn(self);
|
|
393
370
|
}));
|
|
394
371
|
}
|
|
372
|
+
addDisposer(self, autorun(syncGenomeHoverToMsaColumn(self)));
|
|
395
373
|
},
|
|
396
374
|
}));
|
|
397
375
|
}
|
|
@@ -93,15 +93,8 @@ function buildConsensus(alignedSeq1, alignedSeq2) {
|
|
|
93
93
|
for (let i = 0; i < alignedSeq1.length; i++) {
|
|
94
94
|
const a = alignedSeq1[i];
|
|
95
95
|
const b = alignedSeq2[i];
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
else if (a.toUpperCase() === b.toUpperCase()) {
|
|
100
|
-
consensus += '|';
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
consensus += ' ';
|
|
104
|
-
}
|
|
96
|
+
const match = a !== '-' && b !== '-' && a.toUpperCase() === b.toUpperCase();
|
|
97
|
+
consensus += match ? '|' : ' ';
|
|
105
98
|
}
|
|
106
99
|
return consensus;
|
|
107
100
|
}
|
|
@@ -38,14 +38,8 @@ export interface StructureConnection {
|
|
|
38
38
|
msaRowName: string;
|
|
39
39
|
/** Map from MSA ungapped position to structure sequence position */
|
|
40
40
|
msaToStructure: Record<number, number>;
|
|
41
|
-
/** Map from structure sequence position to MSA ungapped position */
|
|
42
|
-
structureToMsa: Record<number, number>;
|
|
43
41
|
}
|
|
44
42
|
/**
|
|
45
43
|
* Helper to convert gapped MSA column to ungapped position for a specific row
|
|
46
44
|
*/
|
|
47
45
|
export declare function gappedToUngappedPosition(sequence: string, gappedPosition: number): number | undefined;
|
|
48
|
-
/**
|
|
49
|
-
* Helper to convert ungapped position to gapped MSA column for a specific row
|
|
50
|
-
*/
|
|
51
|
-
export declare function ungappedToGappedPosition(sequence: string, ungappedPosition: number): number | undefined;
|
|
@@ -27,19 +27,3 @@ export function gappedToUngappedPosition(sequence, gappedPosition) {
|
|
|
27
27
|
}
|
|
28
28
|
return ungapped;
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Helper to convert ungapped position to gapped MSA column for a specific row
|
|
32
|
-
*/
|
|
33
|
-
export function ungappedToGappedPosition(sequence, ungappedPosition) {
|
|
34
|
-
let ungapped = 0;
|
|
35
|
-
for (let i = 0; i < sequence.length; i++) {
|
|
36
|
-
const element = sequence[i];
|
|
37
|
-
if (element !== '-') {
|
|
38
|
-
if (ungapped === ungappedPosition) {
|
|
39
|
-
return i;
|
|
40
|
-
}
|
|
41
|
-
ungapped++;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { gappedToUngappedPosition
|
|
2
|
+
import { gappedToUngappedPosition } from './structureConnection';
|
|
3
3
|
describe('gappedToUngappedPosition', () => {
|
|
4
4
|
test('returns correct ungapped position for non-gap character', () => {
|
|
5
5
|
const seq = 'M-KA-A';
|
|
@@ -51,53 +51,3 @@ describe('gappedToUngappedPosition', () => {
|
|
|
51
51
|
expect(gappedToUngappedPosition(seq, 2)).toBeUndefined();
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
|
-
describe('ungappedToGappedPosition', () => {
|
|
55
|
-
test('returns correct gapped position', () => {
|
|
56
|
-
const seq = 'M-KA-A';
|
|
57
|
-
// 0 12 34 (gapped)
|
|
58
|
-
// 0 1 23 (ungapped)
|
|
59
|
-
expect(ungappedToGappedPosition(seq, 0)).toBe(0); // M
|
|
60
|
-
expect(ungappedToGappedPosition(seq, 1)).toBe(2); // K
|
|
61
|
-
expect(ungappedToGappedPosition(seq, 2)).toBe(3); // A
|
|
62
|
-
expect(ungappedToGappedPosition(seq, 3)).toBe(5); // A
|
|
63
|
-
});
|
|
64
|
-
test('returns undefined for out-of-bounds ungapped position', () => {
|
|
65
|
-
const seq = 'M-KA';
|
|
66
|
-
expect(ungappedToGappedPosition(seq, 4)).toBeUndefined();
|
|
67
|
-
expect(ungappedToGappedPosition(seq, 100)).toBeUndefined();
|
|
68
|
-
});
|
|
69
|
-
test('handles sequence with no gaps', () => {
|
|
70
|
-
const seq = 'MKAA';
|
|
71
|
-
expect(ungappedToGappedPosition(seq, 0)).toBe(0);
|
|
72
|
-
expect(ungappedToGappedPosition(seq, 1)).toBe(1);
|
|
73
|
-
expect(ungappedToGappedPosition(seq, 2)).toBe(2);
|
|
74
|
-
expect(ungappedToGappedPosition(seq, 3)).toBe(3);
|
|
75
|
-
});
|
|
76
|
-
test('handles sequence with leading gaps', () => {
|
|
77
|
-
const seq = '--MKA';
|
|
78
|
-
expect(ungappedToGappedPosition(seq, 0)).toBe(2); // M
|
|
79
|
-
expect(ungappedToGappedPosition(seq, 1)).toBe(3); // K
|
|
80
|
-
expect(ungappedToGappedPosition(seq, 2)).toBe(4); // A
|
|
81
|
-
});
|
|
82
|
-
test('handles empty sequence', () => {
|
|
83
|
-
expect(ungappedToGappedPosition('', 0)).toBeUndefined();
|
|
84
|
-
});
|
|
85
|
-
test('handles all-gap sequence', () => {
|
|
86
|
-
const seq = '---';
|
|
87
|
-
expect(ungappedToGappedPosition(seq, 0)).toBeUndefined();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
describe('gappedToUngappedPosition and ungappedToGappedPosition are inverses', () => {
|
|
91
|
-
test('round-trip conversion works', () => {
|
|
92
|
-
const seq = 'M-KA--YL-S';
|
|
93
|
-
// For each non-gap position, converting to ungapped and back should return original
|
|
94
|
-
for (let i = 0; i < seq.length; i++) {
|
|
95
|
-
if (seq[i] !== '-') {
|
|
96
|
-
const ungapped = gappedToUngappedPosition(seq, i);
|
|
97
|
-
expect(ungapped).toBeDefined();
|
|
98
|
-
const backToGapped = ungappedToGappedPosition(seq, ungapped);
|
|
99
|
-
expect(backToGapped).toBe(i);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { getSession } from '@jbrowse/core/util';
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { syncGenomeHoverToMsaColumn } from './afterCreateAutoruns';
|
|
4
|
+
// Mock only getSession; keep the rest of the util module real so the
|
|
5
|
+
// afterCreateAutoruns import graph still loads.
|
|
6
|
+
vi.mock('@jbrowse/core/util', async (importOriginal) => ({
|
|
7
|
+
...(await importOriginal()),
|
|
8
|
+
getSession: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
const mockGetSession = vi.mocked(getSession);
|
|
11
|
+
const mafRegion = {
|
|
12
|
+
refName: 'chr1',
|
|
13
|
+
start: 1000,
|
|
14
|
+
end: 1010,
|
|
15
|
+
assemblyName: 'hg38',
|
|
16
|
+
};
|
|
17
|
+
// A model wired through the real genomeToMSA path: a connected genome view
|
|
18
|
+
// over a maf region, with seqPosToVisibleCol as identity so the asserted
|
|
19
|
+
// column equals the ungapped offset into the region.
|
|
20
|
+
function makeModel() {
|
|
21
|
+
const calls = [];
|
|
22
|
+
const model = {
|
|
23
|
+
querySeqName: 'hg38.chr1',
|
|
24
|
+
transcriptToMsaMap: undefined,
|
|
25
|
+
mafRegion,
|
|
26
|
+
connectedView: { initialized: true, assemblyNames: ['hg38'] },
|
|
27
|
+
seqPosToVisibleCol: (_name, pos) => pos,
|
|
28
|
+
setMousePos: (col) => {
|
|
29
|
+
calls.push(col);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
return { model, calls };
|
|
33
|
+
}
|
|
34
|
+
function hoverGenome(coord) {
|
|
35
|
+
mockGetSession.mockReturnValue({
|
|
36
|
+
hovered: { hoverFeature: {}, hoverPosition: { coord, refName: 'chr1' } },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function clearGenomeHover() {
|
|
40
|
+
mockGetSession.mockReturnValue({
|
|
41
|
+
hovered: null,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
});
|
|
48
|
+
test('genome hover at coord 1005 highlights MSA column 5', () => {
|
|
49
|
+
const { model, calls } = makeModel();
|
|
50
|
+
const run = syncGenomeHoverToMsaColumn(model);
|
|
51
|
+
hoverGenome(1005); // 1005 - mafRegion.start(1000) = ungapped 5
|
|
52
|
+
run();
|
|
53
|
+
expect(calls).toEqual([5]);
|
|
54
|
+
});
|
|
55
|
+
test('moving the genome hover moves the highlighted column', () => {
|
|
56
|
+
const { model, calls } = makeModel();
|
|
57
|
+
const run = syncGenomeHoverToMsaColumn(model);
|
|
58
|
+
hoverGenome(1002);
|
|
59
|
+
run();
|
|
60
|
+
hoverGenome(1007);
|
|
61
|
+
run();
|
|
62
|
+
expect(calls).toEqual([2, 7]);
|
|
63
|
+
});
|
|
64
|
+
test('leaving the genome clears the column it set', () => {
|
|
65
|
+
const { model, calls } = makeModel();
|
|
66
|
+
const run = syncGenomeHoverToMsaColumn(model);
|
|
67
|
+
hoverGenome(1004);
|
|
68
|
+
run();
|
|
69
|
+
clearGenomeHover();
|
|
70
|
+
run();
|
|
71
|
+
expect(calls).toEqual([4, undefined]);
|
|
72
|
+
});
|
|
73
|
+
test('a hover outside the maf region clears a previously-set column once', () => {
|
|
74
|
+
const { model, calls } = makeModel();
|
|
75
|
+
const run = syncGenomeHoverToMsaColumn(model);
|
|
76
|
+
hoverGenome(1004);
|
|
77
|
+
run();
|
|
78
|
+
hoverGenome(5000); // outside [1000,1010) -> genomeToMSA returns undefined
|
|
79
|
+
run();
|
|
80
|
+
run();
|
|
81
|
+
expect(calls).toEqual([4, undefined]);
|
|
82
|
+
});
|
|
83
|
+
test('never touches mouseCol when the genome never provides a column, so a direct MSA hover survives unrelated session hovers', () => {
|
|
84
|
+
const { model, calls } = makeModel();
|
|
85
|
+
const run = syncGenomeHoverToMsaColumn(model);
|
|
86
|
+
clearGenomeHover();
|
|
87
|
+
run();
|
|
88
|
+
hoverGenome(9999); // unrelated/out-of-range hover elsewhere
|
|
89
|
+
run();
|
|
90
|
+
expect(calls).toEqual([]);
|
|
91
|
+
});
|
|
92
|
+
});
|