jbrowse-plugin-msaview 2.6.3 → 2.6.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/dist/AddHighlightModel/MsaToGenomeHighlight.js +8 -9
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +0 -2
- package/dist/MsaViewPanel/afterCreateAutoruns.js +1 -63
- package/dist/MsaViewPanel/model.d.ts +53 -29
- package/dist/MsaViewPanel/model.js +5 -103
- package/dist/MsaViewPanel/structureConnection.d.ts +0 -38
- package/dist/MsaViewPanel/structureConnection.js +0 -20
- package/dist/MsaViewPanel/structureConnection.test.js +1 -35
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +26 -26
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/ncbiDomains.d.ts +4 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +8 -9
- package/src/MsaViewPanel/afterCreateAutoruns.ts +1 -84
- package/src/MsaViewPanel/model.ts +4 -135
- package/src/MsaViewPanel/structureConnection.test.ts +1 -53
- package/src/MsaViewPanel/structureConnection.ts +0 -47
- package/src/version.ts +1 -1
- package/dist/MsaViewPanel/autoConnectStructures.test.d.ts +0 -1
- package/dist/MsaViewPanel/autoConnectStructures.test.js +0 -60
- package/dist/MsaViewPanel/components/ConnectStructureDialog.d.ts +0 -7
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js +0 -60
- package/dist/MsaViewPanel/pairwiseAlignment.d.ts +0 -20
- package/dist/MsaViewPanel/pairwiseAlignment.js +0 -138
- package/dist/MsaViewPanel/pairwiseAlignment.test.d.ts +0 -1
- package/dist/MsaViewPanel/pairwiseAlignment.test.js +0 -111
- package/src/MsaViewPanel/autoConnectStructures.test.ts +0 -76
- package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +0 -154
- package/src/MsaViewPanel/pairwiseAlignment.test.ts +0 -140
- package/src/MsaViewPanel/pairwiseAlignment.ts +0 -182
|
@@ -3,7 +3,6 @@ import { getSession } from '@jbrowse/core/util';
|
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
4
|
import { hasHoverPosition, useStyles } from './util';
|
|
5
5
|
import { isMsaView } from '../MsaViewPanel/model';
|
|
6
|
-
import { getCanonicalRefName } from '../MsaViewPanel/util';
|
|
7
6
|
const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({ model, }) {
|
|
8
7
|
const { views, hovered } = getSession(model);
|
|
9
8
|
const msaView = views
|
|
@@ -17,16 +16,16 @@ const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({ model, })
|
|
|
17
16
|
// Inner component: handles the scroll-dependent rendering
|
|
18
17
|
const MsaToGenomeHighlightRenderer = observer(function ({ model, highlights, }) {
|
|
19
18
|
const { classes } = useStyles();
|
|
20
|
-
const { assemblyManager } = getSession(model);
|
|
21
19
|
const { offsetPx } = model;
|
|
22
20
|
return (React.createElement(React.Fragment, null, highlights.map((r, idx) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
21
|
+
// Use the highlight's own refName, which is already in the connected
|
|
22
|
+
// view's coordinate space (it comes from the connectedFeature the
|
|
23
|
+
// launcher set on this LGV). Do NOT canonicalize: bpToPx matches
|
|
24
|
+
// displayed regions by exact refName with no alias resolution, so
|
|
25
|
+
// rewriting e.g. "chr17" to the assembly-canonical "17" misses a view
|
|
26
|
+
// whose regions are "chr17". (GenomeMouseoverHighlight does the same.)
|
|
27
|
+
const s = model.bpToPx({ refName: r.refName, coord: r.start });
|
|
28
|
+
const e = model.bpToPx({ refName: r.refName, coord: r.end });
|
|
30
29
|
if (s && e) {
|
|
31
30
|
const width = Math.max(Math.abs(e.offsetPx - s.offsetPx), 4);
|
|
32
31
|
const left = Math.min(s.offsetPx, e.offsetPx) - offsetPx;
|
|
@@ -18,8 +18,6 @@ export declare function processInit(self: JBrowsePluginMsaViewModel): void;
|
|
|
18
18
|
* column the user is hovering directly in the MSA.
|
|
19
19
|
*/
|
|
20
20
|
export declare function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewModel): () => void;
|
|
21
|
-
export declare function highlightConnectedStructures(self: JBrowsePluginMsaViewModel): void;
|
|
22
|
-
export declare function autoConnectStructures(self: JBrowsePluginMsaViewModel): void;
|
|
23
21
|
/**
|
|
24
22
|
* Mirror a connected 3D protein view's hovered residue onto the MSA's
|
|
25
23
|
* highlighted columns. Returns the autorun body and keeps a flag tracking
|
|
@@ -4,7 +4,7 @@ import { fetchIndexedMsa } from './fetchIndexedMsa';
|
|
|
4
4
|
import { genomeToMSA } from './genomeToMSA';
|
|
5
5
|
import { loadProteinDomains } from './loadProteinDomains';
|
|
6
6
|
import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } from './msaDataStore';
|
|
7
|
-
import {
|
|
7
|
+
import { getProteinViews } from './structureConnection';
|
|
8
8
|
import { getUniprotIdFromAlphaFoldUrl } from './util';
|
|
9
9
|
export function loadStoredData(self) {
|
|
10
10
|
const { dataStoreId, rows } = self;
|
|
@@ -183,68 +183,6 @@ export function syncGenomeHoverToMsaColumn(self) {
|
|
|
183
183
|
}
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
|
-
export function highlightConnectedStructures(self) {
|
|
187
|
-
const { mouseCol, connectedProteinViews } = self;
|
|
188
|
-
if (connectedProteinViews.length === 0) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
for (const conn of connectedProteinViews) {
|
|
192
|
-
const structure = conn.proteinView.structures[conn.structureIdx];
|
|
193
|
-
if (!structure) {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (mouseCol === undefined) {
|
|
197
|
-
structure.clearHighlightFromExternal?.();
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
const seq = self.getSequenceByRowName(conn.msaRowName);
|
|
201
|
-
if (!seq) {
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
const msaUngapped = gappedToUngappedPosition(seq, mouseCol);
|
|
205
|
-
if (msaUngapped === undefined) {
|
|
206
|
-
structure.clearHighlightFromExternal?.();
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const structurePos = conn.msaToStructure[msaUngapped];
|
|
210
|
-
if (structurePos === undefined) {
|
|
211
|
-
structure.clearHighlightFromExternal?.();
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
structure.highlightFromExternal?.(structurePos);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
export function autoConnectStructures(self) {
|
|
219
|
-
const { connectedViewId, uniprotId, rows, connectedStructures } = self;
|
|
220
|
-
if (rows.length === 0) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
for (const view of getProteinViews(getSession(self).views)) {
|
|
224
|
-
for (let structureIdx = 0; structureIdx < view.structures.length; structureIdx++) {
|
|
225
|
-
const structure = view.structures[structureIdx];
|
|
226
|
-
if (!structure) {
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
if (!structureMatchesMsa({ structure, connectedViewId, uniprotId })) {
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
const alreadyConnected = connectedStructures.some(c => c.proteinViewId === view.id && c.structureIdx === structureIdx);
|
|
233
|
-
if (alreadyConnected) {
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
if (!structure.structureSequences?.[0]) {
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
try {
|
|
240
|
-
self.connectToStructure(view.id, structureIdx);
|
|
241
|
-
}
|
|
242
|
-
catch (e) {
|
|
243
|
-
console.error('Failed to auto-connect to ProteinView:', e);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
186
|
/**
|
|
249
187
|
* Mirror a connected 3D protein view's hovered residue onto the MSA's
|
|
250
188
|
* highlighted columns. Returns the autorun body and keeps a flag tracking
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export type { MSAFormat } from 'msa-parsers';
|
|
2
|
-
import type { ProteinView, StructureConnection } from './structureConnection';
|
|
3
2
|
import type { MafRegion, MsaViewInitState } from './types';
|
|
4
3
|
import type { BlastDatabase, BlastProgram, MsaAlgorithm } from '../LaunchMsaView/components/NCBIBlastQuery/consts';
|
|
5
4
|
import type { Feature } from '@jbrowse/core/util';
|
|
@@ -159,7 +158,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
159
158
|
featureFilters: import("@jbrowse/mobx-state-tree").IMapType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>>;
|
|
160
159
|
relativeTo: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
161
160
|
highlightColumns: import("@jbrowse/mobx-state-tree").IType<number[] | undefined, number[] | undefined, number[] | undefined>;
|
|
162
|
-
}, "init" | "querySeqName" | "zoomToBaseLevel" | "connectedViewId" | "connectedFeature" | "blastParams" | "uniprotId" | "
|
|
161
|
+
}, "init" | "querySeqName" | "zoomToBaseLevel" | "connectedViewId" | "connectedFeature" | "blastParams" | "uniprotId" | "dataStoreId" | "mafRegion"> & {
|
|
163
162
|
connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
164
163
|
connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
|
|
165
164
|
blastParams: import("@jbrowse/mobx-state-tree").IType<BlastParams | undefined, BlastParams | undefined, BlastParams | undefined>;
|
|
@@ -167,7 +166,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
167
166
|
uniprotId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
168
167
|
zoomToBaseLevel: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
|
|
169
168
|
init: import("@jbrowse/mobx-state-tree").IType<MsaViewInitState | undefined, MsaViewInitState | undefined, MsaViewInitState | undefined>;
|
|
170
|
-
connectedStructures: import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<StructureConnection, StructureConnection, StructureConnection>>;
|
|
171
169
|
dataStoreId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
172
170
|
mafRegion: import("@jbrowse/mobx-state-tree").IType<MafRegion | undefined, MafRegion | undefined, MafRegion | undefined>;
|
|
173
171
|
}, {
|
|
@@ -386,43 +384,92 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
386
384
|
name: string;
|
|
387
385
|
accession: string;
|
|
388
386
|
description: string;
|
|
387
|
+
featureType: string | undefined;
|
|
389
388
|
start: number;
|
|
390
389
|
end: number;
|
|
390
|
+
strand: number | undefined;
|
|
391
391
|
}>;
|
|
392
392
|
readonly tidyInterProAnnotations: {
|
|
393
393
|
id: string;
|
|
394
394
|
name: string;
|
|
395
395
|
accession: string;
|
|
396
396
|
description: string;
|
|
397
|
+
featureType: string | undefined;
|
|
397
398
|
start: number;
|
|
398
399
|
end: number;
|
|
400
|
+
strand: number | undefined;
|
|
399
401
|
}[];
|
|
400
402
|
readonly tidyFilteredInterProAnnotations: {
|
|
401
403
|
id: string;
|
|
402
404
|
name: string;
|
|
403
405
|
accession: string;
|
|
404
406
|
description: string;
|
|
407
|
+
featureType: string | undefined;
|
|
405
408
|
start: number;
|
|
406
409
|
end: number;
|
|
410
|
+
strand: number | undefined;
|
|
407
411
|
}[];
|
|
408
412
|
readonly tidyFilteredGatheredInterProAnnotations: Record<string, {
|
|
409
413
|
id: string;
|
|
410
414
|
name: string;
|
|
411
415
|
accession: string;
|
|
412
416
|
description: string;
|
|
417
|
+
featureType: string | undefined;
|
|
413
418
|
start: number;
|
|
414
419
|
end: number;
|
|
420
|
+
strand: number | undefined;
|
|
415
421
|
}[]>;
|
|
416
422
|
} & {
|
|
417
423
|
readonly showVerticalScrollbar: boolean;
|
|
418
424
|
} & {
|
|
419
425
|
readonly verticalScrollbarWidth: 0 | 20;
|
|
426
|
+
readonly segmentDomainTypes: {
|
|
427
|
+
id: string;
|
|
428
|
+
name: string;
|
|
429
|
+
accession: string;
|
|
430
|
+
description: string;
|
|
431
|
+
featureType: string | undefined;
|
|
432
|
+
start: number;
|
|
433
|
+
end: number;
|
|
434
|
+
strand: number | undefined;
|
|
435
|
+
}[];
|
|
436
|
+
readonly categoricalDomainTypes: {
|
|
437
|
+
id: string;
|
|
438
|
+
name: string;
|
|
439
|
+
accession: string;
|
|
440
|
+
description: string;
|
|
441
|
+
featureType: string | undefined;
|
|
442
|
+
start: number;
|
|
443
|
+
end: number;
|
|
444
|
+
strand: number | undefined;
|
|
445
|
+
}[];
|
|
420
446
|
readonly fillPalette: {
|
|
421
|
-
[
|
|
447
|
+
[x: string]: string;
|
|
422
448
|
};
|
|
423
449
|
readonly strokePalette: {
|
|
424
450
|
[k: string]: string;
|
|
425
451
|
};
|
|
452
|
+
readonly segmentLabels: Map<string, string>;
|
|
453
|
+
readonly visibleDomainTypes: {
|
|
454
|
+
id: string;
|
|
455
|
+
name: string;
|
|
456
|
+
accession: string;
|
|
457
|
+
description: string;
|
|
458
|
+
featureType: string | undefined;
|
|
459
|
+
start: number;
|
|
460
|
+
end: number;
|
|
461
|
+
strand: number | undefined;
|
|
462
|
+
}[];
|
|
463
|
+
readonly mouseOverDomains: {
|
|
464
|
+
id: string;
|
|
465
|
+
name: string;
|
|
466
|
+
accession: string;
|
|
467
|
+
description: string;
|
|
468
|
+
featureType: string | undefined;
|
|
469
|
+
start: number;
|
|
470
|
+
end: number;
|
|
471
|
+
strand: number | undefined;
|
|
472
|
+
}[];
|
|
426
473
|
getRowData(name: string): {
|
|
427
474
|
data: {
|
|
428
475
|
name?: string;
|
|
@@ -478,12 +525,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
478
525
|
* #getter
|
|
479
526
|
*/
|
|
480
527
|
readonly connectedView: MaybeLGV;
|
|
481
|
-
/**
|
|
482
|
-
* #getter
|
|
483
|
-
*/
|
|
484
|
-
readonly connectedProteinViews: (StructureConnection & {
|
|
485
|
-
proteinView: ProteinView;
|
|
486
|
-
})[];
|
|
487
528
|
} & {
|
|
488
529
|
/**
|
|
489
530
|
* #getter
|
|
@@ -546,18 +587,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
546
587
|
* #action
|
|
547
588
|
*/
|
|
548
589
|
handleMsaClick(coord: number): void;
|
|
549
|
-
/**
|
|
550
|
-
* #action
|
|
551
|
-
*/
|
|
552
|
-
connectToStructure(proteinViewId: string, structureIdx: number, msaRowName?: string): void;
|
|
553
|
-
/**
|
|
554
|
-
* #action
|
|
555
|
-
*/
|
|
556
|
-
disconnectFromStructure(proteinViewId: string, structureIdx: number): void;
|
|
557
|
-
/**
|
|
558
|
-
* #action
|
|
559
|
-
*/
|
|
560
|
-
disconnectAllStructures(): void;
|
|
561
590
|
} & {
|
|
562
591
|
/**
|
|
563
592
|
* #action
|
|
@@ -567,17 +596,12 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
567
596
|
/**
|
|
568
597
|
* #method
|
|
569
598
|
*/
|
|
570
|
-
extraViewMenuItems():
|
|
599
|
+
extraViewMenuItems(): {
|
|
571
600
|
label: string;
|
|
572
601
|
checked: boolean;
|
|
573
602
|
type: string;
|
|
574
603
|
onClick: () => void;
|
|
575
|
-
}
|
|
576
|
-
label: string;
|
|
577
|
-
onClick: () => void;
|
|
578
|
-
checked?: undefined;
|
|
579
|
-
type?: undefined;
|
|
580
|
-
})[];
|
|
604
|
+
}[];
|
|
581
605
|
} & {
|
|
582
606
|
afterCreate(): void;
|
|
583
607
|
}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree").ModelSnapshotType<{
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import { lazy } from 'react';
|
|
2
1
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes';
|
|
3
2
|
import { getSession } from '@jbrowse/core/util';
|
|
4
3
|
import { addDisposer, types } from '@jbrowse/mobx-state-tree';
|
|
5
4
|
import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
|
|
6
5
|
import { autorun } from 'mobx';
|
|
7
6
|
import { MSAModelF } from 'react-msaview';
|
|
8
|
-
import {
|
|
7
|
+
import { autoLoadProteinDomains, launchBlastIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
|
|
9
8
|
import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
|
|
10
|
-
import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment';
|
|
11
|
-
import { getProteinViews } from './structureConnection';
|
|
12
|
-
import { getCanonicalRefName } from './util';
|
|
13
|
-
const ConnectStructureDialog = lazy(() => import('./components/ConnectStructureDialog'));
|
|
14
9
|
/**
|
|
15
10
|
* #stateModel MsaViewPlugin
|
|
16
11
|
* extends
|
|
@@ -47,10 +42,6 @@ export default function stateModelFactory() {
|
|
|
47
42
|
* #property
|
|
48
43
|
*/
|
|
49
44
|
init: types.frozen(),
|
|
50
|
-
/**
|
|
51
|
-
* #property
|
|
52
|
-
*/
|
|
53
|
-
connectedStructures: types.array(types.frozen()),
|
|
54
45
|
/**
|
|
55
46
|
* #property
|
|
56
47
|
*/
|
|
@@ -118,20 +109,6 @@ export default function stateModelFactory() {
|
|
|
118
109
|
const { views } = getSession(self);
|
|
119
110
|
return views.find(f => f.id === self.connectedViewId);
|
|
120
111
|
},
|
|
121
|
-
/**
|
|
122
|
-
* #getter
|
|
123
|
-
*/
|
|
124
|
-
get connectedProteinViews() {
|
|
125
|
-
const proteinViews = getProteinViews(getSession(self).views);
|
|
126
|
-
const result = [];
|
|
127
|
-
for (const conn of self.connectedStructures) {
|
|
128
|
-
const proteinView = proteinViews.find(v => v.id === conn.proteinViewId);
|
|
129
|
-
if (proteinView) {
|
|
130
|
-
result.push({ ...conn, proteinView });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
},
|
|
135
112
|
}))
|
|
136
113
|
.views(self => ({
|
|
137
114
|
/**
|
|
@@ -233,71 +210,20 @@ export default function stateModelFactory() {
|
|
|
233
210
|
*/
|
|
234
211
|
handleMsaClick(coord) {
|
|
235
212
|
const { connectedView, zoomToBaseLevel } = self;
|
|
236
|
-
const { assemblyManager } = getSession(self);
|
|
237
213
|
const r2 = msaCoordToGenomeCoord({ model: self, coord });
|
|
238
214
|
if (!r2 || !connectedView) {
|
|
239
215
|
return;
|
|
240
216
|
}
|
|
217
|
+
// Use the genome coord's own refName for both nav paths — it matches the
|
|
218
|
+
// connected view's displayed regions. Canonicalizing (e.g. "chr17"->"17")
|
|
219
|
+
// can miss a view whose regions are an alias (same as the bpToPx path).
|
|
241
220
|
if (zoomToBaseLevel) {
|
|
242
221
|
connectedView.navTo(r2);
|
|
243
222
|
}
|
|
244
223
|
else {
|
|
245
|
-
|
|
246
|
-
assemblyManager,
|
|
247
|
-
assemblyNames: connectedView.assemblyNames,
|
|
248
|
-
refName: r2.refName,
|
|
249
|
-
});
|
|
250
|
-
connectedView.centerAt(r2.start, r);
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
/**
|
|
254
|
-
* #action
|
|
255
|
-
*/
|
|
256
|
-
connectToStructure(proteinViewId, structureIdx, msaRowName) {
|
|
257
|
-
const rowName = msaRowName ?? self.querySeqName;
|
|
258
|
-
const msaSequence = self.getSequenceByRowName(rowName);
|
|
259
|
-
if (!msaSequence) {
|
|
260
|
-
throw new Error(`MSA row "${rowName}" not found`);
|
|
261
|
-
}
|
|
262
|
-
const ungappedMsaSequence = msaSequence.replaceAll('-', '');
|
|
263
|
-
const proteinView = getProteinViews(getSession(self).views).find(v => v.id === proteinViewId);
|
|
264
|
-
if (!proteinView) {
|
|
265
|
-
throw new Error(`ProteinView "${proteinViewId}" not found`);
|
|
266
|
-
}
|
|
267
|
-
const structure = proteinView.structures[structureIdx];
|
|
268
|
-
if (!structure) {
|
|
269
|
-
throw new Error(`Structure at index ${structureIdx} not found`);
|
|
270
|
-
}
|
|
271
|
-
const structureSequence = structure.structureSequences?.[0];
|
|
272
|
-
if (!structureSequence) {
|
|
273
|
-
throw new Error('Structure sequence not available');
|
|
274
|
-
}
|
|
275
|
-
const alignment = runPairwiseAlignment(ungappedMsaSequence, structureSequence);
|
|
276
|
-
const { seq1ToSeq2 } = buildAlignmentMaps(alignment);
|
|
277
|
-
const connection = {
|
|
278
|
-
proteinViewId,
|
|
279
|
-
structureIdx,
|
|
280
|
-
msaRowName: rowName,
|
|
281
|
-
msaToStructure: Object.fromEntries(seq1ToSeq2),
|
|
282
|
-
};
|
|
283
|
-
self.connectedStructures.push(connection);
|
|
284
|
-
},
|
|
285
|
-
/**
|
|
286
|
-
* #action
|
|
287
|
-
*/
|
|
288
|
-
disconnectFromStructure(proteinViewId, structureIdx) {
|
|
289
|
-
const idx = self.connectedStructures.findIndex(c => c.proteinViewId === proteinViewId &&
|
|
290
|
-
c.structureIdx === structureIdx);
|
|
291
|
-
if (idx !== -1) {
|
|
292
|
-
self.connectedStructures.splice(idx, 1);
|
|
224
|
+
connectedView.centerAt(r2.start, r2.refName);
|
|
293
225
|
}
|
|
294
226
|
},
|
|
295
|
-
/**
|
|
296
|
-
* #action
|
|
297
|
-
*/
|
|
298
|
-
disconnectAllStructures() {
|
|
299
|
-
self.connectedStructures.clear();
|
|
300
|
-
},
|
|
301
227
|
}))
|
|
302
228
|
.actions(self => {
|
|
303
229
|
const superSetMouseClickPos = self.setMouseClickPos.bind(self);
|
|
@@ -327,28 +253,6 @@ export default function stateModelFactory() {
|
|
|
327
253
|
self.setZoomToBaseLevel(!self.zoomToBaseLevel);
|
|
328
254
|
},
|
|
329
255
|
},
|
|
330
|
-
{
|
|
331
|
-
label: 'Connect to protein structure...',
|
|
332
|
-
onClick: () => {
|
|
333
|
-
getSession(self).queueDialog(handleClose => [
|
|
334
|
-
ConnectStructureDialog,
|
|
335
|
-
{
|
|
336
|
-
model: self,
|
|
337
|
-
handleClose,
|
|
338
|
-
},
|
|
339
|
-
]);
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
...(self.connectedStructures.length > 0
|
|
343
|
-
? [
|
|
344
|
-
{
|
|
345
|
-
label: 'Disconnect from protein structures',
|
|
346
|
-
onClick: () => {
|
|
347
|
-
self.disconnectAllStructures();
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
]
|
|
351
|
-
: []),
|
|
352
256
|
];
|
|
353
257
|
},
|
|
354
258
|
}))
|
|
@@ -360,8 +264,6 @@ export default function stateModelFactory() {
|
|
|
360
264
|
storeDataToIndexedDB,
|
|
361
265
|
launchBlastIfNeeded,
|
|
362
266
|
processInit,
|
|
363
|
-
highlightConnectedStructures,
|
|
364
|
-
autoConnectStructures,
|
|
365
267
|
autoLoadProteinDomains,
|
|
366
268
|
]) {
|
|
367
269
|
addDisposer(self, autorun(() => {
|
|
@@ -7,11 +7,6 @@ export interface ProteinViewStructure {
|
|
|
7
7
|
start: number;
|
|
8
8
|
end: number;
|
|
9
9
|
}[];
|
|
10
|
-
hoverPosition?: {
|
|
11
|
-
structureSeqPos?: number;
|
|
12
|
-
};
|
|
13
|
-
clearHighlightFromExternal?: () => void;
|
|
14
|
-
highlightFromExternal?: (pos: number) => void;
|
|
15
10
|
}
|
|
16
11
|
export interface ProteinView {
|
|
17
12
|
type: 'ProteinView';
|
|
@@ -26,39 +21,6 @@ export declare function isProteinView(view: unknown): view is ProteinView;
|
|
|
26
21
|
export declare function getProteinViews(views: {
|
|
27
22
|
type: string;
|
|
28
23
|
}[]): ProteinView[];
|
|
29
|
-
/**
|
|
30
|
-
* Whether a 3D structure belongs to a given alignment — the single source of
|
|
31
|
-
* truth for pairing an MsaView with a ProteinView's structure. A structure
|
|
32
|
-
* matches when it either:
|
|
33
|
-
* - shares the alignment's genome view (both pinned to the same
|
|
34
|
-
* LinearGenomeView via `connectedViewId` — the genome-centric gene-explorer
|
|
35
|
-
* flow, the same key genome↔MSA and genome↔structure already bridge through),
|
|
36
|
-
* or
|
|
37
|
-
* - shares the alignment's UniProt accession (the BLAST/AlphaFold flow, which
|
|
38
|
-
* has no genome view to bridge through).
|
|
39
|
-
*
|
|
40
|
-
* The residue map itself is built by sequence (connectToStructure pairwise-
|
|
41
|
-
* aligns the query row against the structure), so neither key is mechanically
|
|
42
|
-
* required — they only scope WHICH structure pairs with the alignment.
|
|
43
|
-
*/
|
|
44
|
-
export declare function structureMatchesMsa({ structure, connectedViewId, uniprotId, }: {
|
|
45
|
-
structure: Pick<ProteinViewStructure, 'connectedViewId' | 'uniprotId'>;
|
|
46
|
-
connectedViewId?: string;
|
|
47
|
-
uniprotId?: string;
|
|
48
|
-
}): boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Represents a connection between the MSA view and a protein structure
|
|
51
|
-
*/
|
|
52
|
-
export interface StructureConnection {
|
|
53
|
-
/** ID of the ProteinView containing the structure */
|
|
54
|
-
proteinViewId: string;
|
|
55
|
-
/** Index of the structure within the ProteinView's structures array */
|
|
56
|
-
structureIdx: number;
|
|
57
|
-
/** Name of the MSA row that corresponds to this structure */
|
|
58
|
-
msaRowName: string;
|
|
59
|
-
/** Map from MSA ungapped position to structure sequence position */
|
|
60
|
-
msaToStructure: Record<number, number>;
|
|
61
|
-
}
|
|
62
24
|
/**
|
|
63
25
|
* Helper to convert gapped MSA column to ungapped position for a specific row
|
|
64
26
|
*/
|
|
@@ -8,26 +8,6 @@ export function isProteinView(view) {
|
|
|
8
8
|
export function getProteinViews(views) {
|
|
9
9
|
return views.filter(isProteinView);
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Whether a 3D structure belongs to a given alignment — the single source of
|
|
13
|
-
* truth for pairing an MsaView with a ProteinView's structure. A structure
|
|
14
|
-
* matches when it either:
|
|
15
|
-
* - shares the alignment's genome view (both pinned to the same
|
|
16
|
-
* LinearGenomeView via `connectedViewId` — the genome-centric gene-explorer
|
|
17
|
-
* flow, the same key genome↔MSA and genome↔structure already bridge through),
|
|
18
|
-
* or
|
|
19
|
-
* - shares the alignment's UniProt accession (the BLAST/AlphaFold flow, which
|
|
20
|
-
* has no genome view to bridge through).
|
|
21
|
-
*
|
|
22
|
-
* The residue map itself is built by sequence (connectToStructure pairwise-
|
|
23
|
-
* aligns the query row against the structure), so neither key is mechanically
|
|
24
|
-
* required — they only scope WHICH structure pairs with the alignment.
|
|
25
|
-
*/
|
|
26
|
-
export function structureMatchesMsa({ structure, connectedViewId, uniprotId, }) {
|
|
27
|
-
const sharesGenomeView = !!connectedViewId && structure.connectedViewId === connectedViewId;
|
|
28
|
-
const sharesUniprot = !!uniprotId && structure.uniprotId === uniprotId;
|
|
29
|
-
return sharesGenomeView || sharesUniprot;
|
|
30
|
-
}
|
|
31
11
|
/**
|
|
32
12
|
* Helper to convert gapped MSA column to ungapped position for a specific row
|
|
33
13
|
*/
|
|
@@ -1,39 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { gappedToUngappedPosition
|
|
3
|
-
describe('structureMatchesMsa', () => {
|
|
4
|
-
test('matches on a shared genome view alone (no UniProt id)', () => {
|
|
5
|
-
expect(structureMatchesMsa({
|
|
6
|
-
structure: { connectedViewId: 'lgv-TP53' },
|
|
7
|
-
connectedViewId: 'lgv-TP53',
|
|
8
|
-
})).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
test('matches on a shared UniProt id alone (no genome view)', () => {
|
|
11
|
-
expect(structureMatchesMsa({
|
|
12
|
-
structure: { uniprotId: 'P04637' },
|
|
13
|
-
uniprotId: 'P04637',
|
|
14
|
-
})).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
test('shared genome view wins even when UniProt ids differ', () => {
|
|
17
|
-
expect(structureMatchesMsa({
|
|
18
|
-
structure: { connectedViewId: 'lgv-TP53', uniprotId: 'OTHER' },
|
|
19
|
-
connectedViewId: 'lgv-TP53',
|
|
20
|
-
uniprotId: 'P04637',
|
|
21
|
-
})).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
test('no match when neither key matches', () => {
|
|
24
|
-
expect(structureMatchesMsa({
|
|
25
|
-
structure: { connectedViewId: 'lgv-OTHER', uniprotId: 'OTHER' },
|
|
26
|
-
connectedViewId: 'lgv-TP53',
|
|
27
|
-
uniprotId: 'P04637',
|
|
28
|
-
})).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
test('two undefined connectedViewIds do not count as a shared genome view', () => {
|
|
31
|
-
// both sides lacking a genome view must NOT auto-pair on `undefined ===
|
|
32
|
-
// undefined`; only an explicit shared id (or UniProt id) connects
|
|
33
|
-
expect(structureMatchesMsa({ structure: {} })).toBe(false);
|
|
34
|
-
expect(structureMatchesMsa({ structure: { uniprotId: 'P04637' } })).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
2
|
+
import { gappedToUngappedPosition } from './structureConnection';
|
|
37
3
|
describe('gappedToUngappedPosition', () => {
|
|
38
4
|
test('returns correct ungapped position for non-gap character', () => {
|
|
39
5
|
const seq = 'M-KA-A';
|