jbrowse-plugin-msaview 2.5.1 → 2.6.0
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/LaunchMsaViewExtensionPoint/index.js +5 -2
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +15 -1
- package/dist/MsaViewPanel/afterCreateAutoruns.js +73 -27
- package/dist/MsaViewPanel/fetchTabixMsa.d.ts +12 -0
- package/dist/MsaViewPanel/fetchTabixMsa.js +33 -0
- package/dist/MsaViewPanel/model.js +3 -1
- package/dist/MsaViewPanel/types.d.ts +7 -0
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +29 -27
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/ncbiDomains.js +3 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +17 -16
- package/src/LaunchMsaViewExtensionPoint/index.ts +10 -1
- package/src/MsaViewPanel/afterCreateAutoruns.ts +84 -30
- package/src/MsaViewPanel/fetchTabixMsa.ts +50 -0
- package/src/MsaViewPanel/model.ts +3 -1
- package/src/MsaViewPanel/types.ts +8 -0
- package/src/utils/domainCache.ts +1 -2
- package/src/utils/ncbiDomains.ts +3 -1
- package/src/version.ts +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
2
2
|
pluginManager.addToExtensionPoint('LaunchView-MsaView',
|
|
3
3
|
// @ts-expect-error
|
|
4
|
-
({ session, data, msaFileLocation, treeFileLocation, connectedViewId, connectedFeature, displayName, colorSchemeName, colWidth, rowHeight, treeAreaWidth, treeWidth, drawNodeBubbles, labelsAlignRight, showBranchLen, querySeqName, highlightColumns, }) => {
|
|
5
|
-
if (!data && !msaFileLocation) {
|
|
4
|
+
({ session, data, msaFileLocation, msaTabixLocation, msaIndexLocation, msaId, treeFileLocation, connectedViewId, connectedFeature, displayName, colorSchemeName, colWidth, rowHeight, treeAreaWidth, treeWidth, drawNodeBubbles, labelsAlignRight, showBranchLen, querySeqName, highlightColumns, }) => {
|
|
5
|
+
if (!data && !msaFileLocation && !msaTabixLocation) {
|
|
6
6
|
throw new Error('No MSA data or file location provided when launching MSA view');
|
|
7
7
|
}
|
|
8
8
|
session.addView('MsaView', {
|
|
@@ -23,6 +23,9 @@ export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
|
23
23
|
msaData: data?.msa,
|
|
24
24
|
treeData: data?.tree,
|
|
25
25
|
msaUrl: msaFileLocation?.uri,
|
|
26
|
+
msaTabixLocation,
|
|
27
|
+
msaIndexLocation,
|
|
28
|
+
msaId,
|
|
26
29
|
treeUrl: treeFileLocation?.uri,
|
|
27
30
|
querySeqName,
|
|
28
31
|
},
|
|
@@ -20,5 +20,19 @@ export declare function processInit(self: JBrowsePluginMsaViewModel): void;
|
|
|
20
20
|
export declare function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewModel): () => void;
|
|
21
21
|
export declare function highlightConnectedStructures(self: JBrowsePluginMsaViewModel): void;
|
|
22
22
|
export declare function autoConnectStructures(self: JBrowsePluginMsaViewModel): void;
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Mirror a connected 3D protein view's hovered residue onto the MSA's
|
|
25
|
+
* highlighted columns. Returns the autorun body and keeps a flag tracking
|
|
26
|
+
* whether the current highlight was set by THIS sync: when a protein hover ends
|
|
27
|
+
* we restore the declarative highlightColumns seed (or clear) rather than
|
|
28
|
+
* blindly wiping it.
|
|
29
|
+
*
|
|
30
|
+
* Without the flag this autorun fires once on creation — with the view connected
|
|
31
|
+
* to a *genome* LGV but no 3D protein structure attached — computes zero columns,
|
|
32
|
+
* and calls setHighlightedColumns(undefined), clobbering the seed that
|
|
33
|
+
* MSAModelF.afterCreate just set from the declarative `highlightColumns`. That is
|
|
34
|
+
* the bug that made the BRAF/TP53 genome-browser links open with no V600/R248
|
|
35
|
+
* column lit (SRC has no highlightColumns, so nothing was there to wipe).
|
|
36
|
+
*/
|
|
37
|
+
export declare function observeProteinHighlights(self: JBrowsePluginMsaViewModel): () => void;
|
|
24
38
|
export declare function runCleanup(): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
2
|
import { doLaunchBlast } from './doLaunchBlast';
|
|
3
|
+
import { fetchTabixMsa } from './fetchTabixMsa';
|
|
3
4
|
import { genomeToMSA } from './genomeToMSA';
|
|
4
5
|
import { loadProteinDomains } from './loadProteinDomains';
|
|
5
6
|
import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } from './msaDataStore';
|
|
@@ -121,7 +122,7 @@ export function processInit(self) {
|
|
|
121
122
|
void (async () => {
|
|
122
123
|
try {
|
|
123
124
|
self.setError(undefined);
|
|
124
|
-
const { msaData, msaUrl, treeData, treeUrl, querySeqName } = init;
|
|
125
|
+
const { msaData, msaUrl, msaTabixLocation, msaIndexLocation, msaId, treeData, treeUrl, querySeqName, } = init;
|
|
125
126
|
if (msaUrl) {
|
|
126
127
|
const id = getUniprotIdFromAlphaFoldUrl(msaUrl);
|
|
127
128
|
if (id) {
|
|
@@ -143,6 +144,25 @@ export function processInit(self) {
|
|
|
143
144
|
const data = await response.text();
|
|
144
145
|
self.setMSA(data);
|
|
145
146
|
}
|
|
147
|
+
else if (msaTabixLocation) {
|
|
148
|
+
const feature = self.connectedFeature;
|
|
149
|
+
if (feature) {
|
|
150
|
+
const fasta = await fetchTabixMsa({
|
|
151
|
+
location: msaTabixLocation,
|
|
152
|
+
indexLocation: msaIndexLocation,
|
|
153
|
+
msaId: msaId ?? String(feature.name),
|
|
154
|
+
refName: String(feature.refName),
|
|
155
|
+
start: Number(feature.start),
|
|
156
|
+
end: Number(feature.end),
|
|
157
|
+
});
|
|
158
|
+
if (fasta) {
|
|
159
|
+
self.setMSA(fasta);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
throw new Error(`No alignment for ${msaId ?? String(feature.name)} in ${msaTabixLocation.uri}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
146
166
|
if (treeData) {
|
|
147
167
|
self.setTree(treeData);
|
|
148
168
|
}
|
|
@@ -249,37 +269,63 @@ export function autoConnectStructures(self) {
|
|
|
249
269
|
}
|
|
250
270
|
}
|
|
251
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Mirror a connected 3D protein view's hovered residue onto the MSA's
|
|
274
|
+
* highlighted columns. Returns the autorun body and keeps a flag tracking
|
|
275
|
+
* whether the current highlight was set by THIS sync: when a protein hover ends
|
|
276
|
+
* we restore the declarative highlightColumns seed (or clear) rather than
|
|
277
|
+
* blindly wiping it.
|
|
278
|
+
*
|
|
279
|
+
* Without the flag this autorun fires once on creation — with the view connected
|
|
280
|
+
* to a *genome* LGV but no 3D protein structure attached — computes zero columns,
|
|
281
|
+
* and calls setHighlightedColumns(undefined), clobbering the seed that
|
|
282
|
+
* MSAModelF.afterCreate just set from the declarative `highlightColumns`. That is
|
|
283
|
+
* the bug that made the BRAF/TP53 genome-browser links open with no V600/R248
|
|
284
|
+
* column lit (SRC has no highlightColumns, so nothing was there to wipe).
|
|
285
|
+
*/
|
|
252
286
|
export function observeProteinHighlights(self) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
287
|
+
let proteinDriven = false;
|
|
288
|
+
return () => {
|
|
289
|
+
const { connectedViewId, transcriptToMsaMap, querySeqName } = self;
|
|
290
|
+
if (!connectedViewId || !transcriptToMsaMap) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const columns = new Set();
|
|
294
|
+
for (const view of getProteinViews(getSession(self).views)) {
|
|
295
|
+
for (const structure of view.structures) {
|
|
296
|
+
if (structure.connectedViewId !== connectedViewId) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const highlights = structure.hoverGenomeHighlights;
|
|
300
|
+
if (!highlights || highlights.length === 0) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const { g2p } = transcriptToMsaMap;
|
|
304
|
+
for (const highlight of highlights) {
|
|
305
|
+
for (let coord = highlight.start; coord < highlight.end; coord++) {
|
|
306
|
+
const proteinPos = g2p[coord];
|
|
307
|
+
if (proteinPos !== undefined) {
|
|
308
|
+
const col = self.seqPosToGlobalCol(querySeqName, proteinPos);
|
|
309
|
+
columns.add(col);
|
|
310
|
+
}
|
|
274
311
|
}
|
|
275
312
|
}
|
|
276
313
|
}
|
|
277
314
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
.
|
|
282
|
-
|
|
315
|
+
const visibleColumns = Array.from(columns)
|
|
316
|
+
.map(col => self.globalColToVisibleCol(col))
|
|
317
|
+
.filter((col) => col !== undefined);
|
|
318
|
+
if (visibleColumns.length > 0) {
|
|
319
|
+
self.setHighlightedColumns(visibleColumns);
|
|
320
|
+
proteinDriven = true;
|
|
321
|
+
}
|
|
322
|
+
else if (proteinDriven) {
|
|
323
|
+
// our protein-hover highlight ended — fall back to the declarative seed
|
|
324
|
+
// instead of wiping a column the URL/user asked to keep lit
|
|
325
|
+
self.setHighlightedColumns(self.highlightColumns?.length ? self.highlightColumns : undefined);
|
|
326
|
+
proteinDriven = false;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
283
329
|
}
|
|
284
330
|
export function runCleanup() {
|
|
285
331
|
cleanupOldData().catch((e) => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function fetchTabixMsa({ location, indexLocation, msaId, refName, start, end, }: {
|
|
2
|
+
location: {
|
|
3
|
+
uri: string;
|
|
4
|
+
};
|
|
5
|
+
indexLocation?: {
|
|
6
|
+
uri: string;
|
|
7
|
+
};
|
|
8
|
+
msaId: string;
|
|
9
|
+
refName: string;
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
}): Promise<string | undefined>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TabixIndexedFile } from '@gmod/tabix';
|
|
2
|
+
import { openLocation } from '@jbrowse/core/util/io';
|
|
3
|
+
// Pull one transcript's whole multiple-alignment out of a locus-keyed tabix
|
|
4
|
+
// file. Each line is `refName<TAB>start<TAB>end<TAB>msaId<TAB>packed`, where
|
|
5
|
+
// `packed` is `name:SEQ;name:SEQ;...` — no newlines, so the alignment survives
|
|
6
|
+
// as a single tabix column. We query the transcript's genomic locus, then pick
|
|
7
|
+
// the line whose msaId matches, and rebuild a FASTA string.
|
|
8
|
+
export async function fetchTabixMsa({ location, indexLocation, msaId, refName, start, end, }) {
|
|
9
|
+
const uri = (loc) => openLocation({ uri: loc.uri, locationType: 'UriLocation' });
|
|
10
|
+
const file = new TabixIndexedFile({
|
|
11
|
+
filehandle: uri(location),
|
|
12
|
+
csiFilehandle: uri(indexLocation ?? { uri: `${location.uri}.csi` }),
|
|
13
|
+
});
|
|
14
|
+
const lines = [];
|
|
15
|
+
await file.getLines(refName, start, end, {
|
|
16
|
+
lineCallback: line => {
|
|
17
|
+
lines.push(line);
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
const line = lines.find(l => l.split('\t')[3] === msaId);
|
|
21
|
+
return line ? unpack(line) : undefined;
|
|
22
|
+
}
|
|
23
|
+
function unpack(line) {
|
|
24
|
+
const packed = line.split('\t')[4] ?? '';
|
|
25
|
+
return packed
|
|
26
|
+
.split(';')
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.map(pair => {
|
|
29
|
+
const colon = pair.indexOf(':');
|
|
30
|
+
return `>${pair.slice(0, colon)}\n${pair.slice(colon + 1)}`;
|
|
31
|
+
})
|
|
32
|
+
.join('\n');
|
|
33
|
+
}
|
|
@@ -363,13 +363,15 @@ export default function stateModelFactory() {
|
|
|
363
363
|
highlightConnectedStructures,
|
|
364
364
|
autoConnectStructures,
|
|
365
365
|
autoLoadProteinDomains,
|
|
366
|
-
observeProteinHighlights,
|
|
367
366
|
]) {
|
|
368
367
|
addDisposer(self, autorun(() => {
|
|
369
368
|
fn(self);
|
|
370
369
|
}));
|
|
371
370
|
}
|
|
371
|
+
// these two keep per-reaction state across runs (a "did I set it?" flag),
|
|
372
|
+
// so they're factories returning the autorun body rather than plain fns
|
|
372
373
|
addDisposer(self, autorun(syncGenomeHoverToMsaColumn(self)));
|
|
374
|
+
addDisposer(self, autorun(observeProteinHighlights(self)));
|
|
373
375
|
},
|
|
374
376
|
}));
|
|
375
377
|
}
|