jbrowse-plugin-msaview 2.6.3 → 2.6.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 +8 -9
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +0 -2
- package/dist/MsaViewPanel/afterCreateAutoruns.js +1 -63
- package/dist/MsaViewPanel/model.d.ts +3 -28
- 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/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- 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
|
}, {
|
|
@@ -478,12 +476,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
478
476
|
* #getter
|
|
479
477
|
*/
|
|
480
478
|
readonly connectedView: MaybeLGV;
|
|
481
|
-
/**
|
|
482
|
-
* #getter
|
|
483
|
-
*/
|
|
484
|
-
readonly connectedProteinViews: (StructureConnection & {
|
|
485
|
-
proteinView: ProteinView;
|
|
486
|
-
})[];
|
|
487
479
|
} & {
|
|
488
480
|
/**
|
|
489
481
|
* #getter
|
|
@@ -546,18 +538,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
546
538
|
* #action
|
|
547
539
|
*/
|
|
548
540
|
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
541
|
} & {
|
|
562
542
|
/**
|
|
563
543
|
* #action
|
|
@@ -567,17 +547,12 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
|
|
|
567
547
|
/**
|
|
568
548
|
* #method
|
|
569
549
|
*/
|
|
570
|
-
extraViewMenuItems():
|
|
550
|
+
extraViewMenuItems(): {
|
|
571
551
|
label: string;
|
|
572
552
|
checked: boolean;
|
|
573
553
|
type: string;
|
|
574
554
|
onClick: () => void;
|
|
575
|
-
}
|
|
576
|
-
label: string;
|
|
577
|
-
onClick: () => void;
|
|
578
|
-
checked?: undefined;
|
|
579
|
-
type?: undefined;
|
|
580
|
-
})[];
|
|
555
|
+
}[];
|
|
581
556
|
} & {
|
|
582
557
|
afterCreate(): void;
|
|
583
558
|
}, 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';
|