jbrowse-plugin-msaview 2.5.0 → 2.5.2

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.
Files changed (35) hide show
  1. package/dist/LaunchMsaViewExtensionPoint/index.js +2 -1
  2. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +22 -1
  3. package/dist/MsaViewPanel/afterCreateAutoruns.js +83 -26
  4. package/dist/MsaViewPanel/loadProteinDomains.d.ts +16 -0
  5. package/dist/MsaViewPanel/loadProteinDomains.js +33 -0
  6. package/dist/MsaViewPanel/model.d.ts +189 -158
  7. package/dist/MsaViewPanel/model.js +17 -5
  8. package/dist/jbrowse-plugin-msaview.umd.production.min.js +28 -28
  9. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  10. package/dist/utils/domainCache.d.ts +8 -0
  11. package/dist/utils/domainCache.js +28 -0
  12. package/dist/utils/eutils.d.ts +3 -0
  13. package/dist/utils/eutils.js +14 -0
  14. package/dist/utils/msa.js +18 -15
  15. package/dist/utils/ncbiBlast.js +22 -24
  16. package/dist/utils/ncbiDomains.d.ts +39 -0
  17. package/dist/utils/ncbiDomains.js +156 -0
  18. package/dist/utils/poll.d.ts +11 -0
  19. package/dist/utils/poll.js +19 -0
  20. package/dist/utils/taxonomyNames.js +2 -1
  21. package/dist/version.d.ts +1 -1
  22. package/dist/version.js +1 -1
  23. package/package.json +22 -19
  24. package/src/LaunchMsaViewExtensionPoint/index.ts +3 -0
  25. package/src/MsaViewPanel/afterCreateAutoruns.ts +86 -29
  26. package/src/MsaViewPanel/loadProteinDomains.ts +57 -0
  27. package/src/MsaViewPanel/model.ts +22 -4
  28. package/src/utils/domainCache.ts +43 -0
  29. package/src/utils/eutils.ts +16 -0
  30. package/src/utils/msa.ts +18 -16
  31. package/src/utils/ncbiBlast.ts +27 -31
  32. package/src/utils/ncbiDomains.ts +173 -0
  33. package/src/utils/poll.ts +28 -0
  34. package/src/utils/taxonomyNames.ts +3 -1
  35. package/src/version.ts +1 -1
@@ -1,7 +1,7 @@
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, }) => {
4
+ ({ session, data, msaFileLocation, treeFileLocation, connectedViewId, connectedFeature, displayName, colorSchemeName, colWidth, rowHeight, treeAreaWidth, treeWidth, drawNodeBubbles, labelsAlignRight, showBranchLen, querySeqName, highlightColumns, }) => {
5
5
  if (!data && !msaFileLocation) {
6
6
  throw new Error('No MSA data or file location provided when launching MSA view');
7
7
  }
@@ -18,6 +18,7 @@ export default function LaunchMsaViewExtensionPointF(pluginManager) {
18
18
  drawNodeBubbles,
19
19
  labelsAlignRight,
20
20
  showBranchLen,
21
+ highlightColumns,
21
22
  init: {
22
23
  msaData: data?.msa,
23
24
  treeData: data?.tree,
@@ -2,6 +2,13 @@ import type { JBrowsePluginMsaViewModel } from './model';
2
2
  export declare function loadStoredData(self: JBrowsePluginMsaViewModel): void;
3
3
  export declare function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel): void;
4
4
  export declare function launchBlastIfNeeded(self: JBrowsePluginMsaViewModel): void;
5
+ /**
6
+ * Once an accession-bearing alignment is present (fresh from BLAST or restored
7
+ * from cache), fetch NCBI CDD domains for those accessions and overlay them.
8
+ * Runs once per view; the domainsRequested guard prevents refiring when NCBI
9
+ * returns no domains (which leaves interProAnnotations undefined).
10
+ */
11
+ export declare function autoLoadProteinDomains(self: JBrowsePluginMsaViewModel): void;
5
12
  export declare function processInit(self: JBrowsePluginMsaViewModel): void;
6
13
  /**
7
14
  * Mirror the connected genome view's hover position onto the MSA's hovered
@@ -13,5 +20,19 @@ export declare function processInit(self: JBrowsePluginMsaViewModel): void;
13
20
  export declare function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewModel): () => void;
14
21
  export declare function highlightConnectedStructures(self: JBrowsePluginMsaViewModel): void;
15
22
  export declare function autoConnectStructures(self: JBrowsePluginMsaViewModel): void;
16
- export declare function observeProteinHighlights(self: JBrowsePluginMsaViewModel): void;
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;
17
38
  export declare function runCleanup(): void;
@@ -1,6 +1,7 @@
1
1
  import { getSession } from '@jbrowse/core/util';
2
2
  import { doLaunchBlast } from './doLaunchBlast';
3
3
  import { genomeToMSA } from './genomeToMSA';
4
+ import { loadProteinDomains } from './loadProteinDomains';
4
5
  import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } from './msaDataStore';
5
6
  import { gappedToUngappedPosition, getProteinViews, } from './structureConnection';
6
7
  import { getUniprotIdFromAlphaFoldUrl } from './util';
@@ -18,6 +19,9 @@ export function loadStoredData(self) {
18
19
  if (storedData.tree) {
19
20
  self.setTree(storedData.tree);
20
21
  }
22
+ if (storedData.treeMetadata) {
23
+ self.setTreeMetadata(storedData.treeMetadata);
24
+ }
21
25
  }
22
26
  }
23
27
  catch (e) {
@@ -84,6 +88,33 @@ export function launchBlastIfNeeded(self) {
84
88
  })();
85
89
  }
86
90
  }
91
+ /**
92
+ * Once an accession-bearing alignment is present (fresh from BLAST or restored
93
+ * from cache), fetch NCBI CDD domains for those accessions and overlay them.
94
+ * Runs once per view; the domainsRequested guard prevents refiring when NCBI
95
+ * returns no domains (which leaves interProAnnotations undefined).
96
+ */
97
+ export function autoLoadProteinDomains(self) {
98
+ const { rows, domainsRequested, interProAnnotations } = self;
99
+ const hasAccessions = self.data.treeMetadata?.includes('"Accession"') ?? false;
100
+ if (rows.length > 0 &&
101
+ hasAccessions &&
102
+ !interProAnnotations &&
103
+ !domainsRequested) {
104
+ self.setDomainsRequested(true);
105
+ void (async () => {
106
+ try {
107
+ await loadProteinDomains(self);
108
+ }
109
+ catch (e) {
110
+ console.error('[msaview-domains] auto-load failed:', e);
111
+ }
112
+ finally {
113
+ self.setProgress('');
114
+ }
115
+ })();
116
+ }
117
+ }
87
118
  export function processInit(self) {
88
119
  const { init } = self;
89
120
  if (init) {
@@ -218,37 +249,63 @@ export function autoConnectStructures(self) {
218
249
  }
219
250
  }
220
251
  }
252
+ /**
253
+ * Mirror a connected 3D protein view's hovered residue onto the MSA's
254
+ * highlighted columns. Returns the autorun body and keeps a flag tracking
255
+ * whether the current highlight was set by THIS sync: when a protein hover ends
256
+ * we restore the declarative highlightColumns seed (or clear) rather than
257
+ * blindly wiping it.
258
+ *
259
+ * Without the flag this autorun fires once on creation — with the view connected
260
+ * to a *genome* LGV but no 3D protein structure attached — computes zero columns,
261
+ * and calls setHighlightedColumns(undefined), clobbering the seed that
262
+ * MSAModelF.afterCreate just set from the declarative `highlightColumns`. That is
263
+ * the bug that made the BRAF/TP53 genome-browser links open with no V600/R248
264
+ * column lit (SRC has no highlightColumns, so nothing was there to wipe).
265
+ */
221
266
  export function observeProteinHighlights(self) {
222
- const { connectedViewId, transcriptToMsaMap, querySeqName } = self;
223
- if (!connectedViewId || !transcriptToMsaMap) {
224
- return;
225
- }
226
- const columns = new Set();
227
- for (const view of getProteinViews(getSession(self).views)) {
228
- for (const structure of view.structures) {
229
- if (structure.connectedViewId !== connectedViewId) {
230
- continue;
231
- }
232
- const highlights = structure.hoverGenomeHighlights;
233
- if (!highlights || highlights.length === 0) {
234
- continue;
235
- }
236
- const { g2p } = transcriptToMsaMap;
237
- for (const highlight of highlights) {
238
- for (let coord = highlight.start; coord < highlight.end; coord++) {
239
- const proteinPos = g2p[coord];
240
- if (proteinPos !== undefined) {
241
- const col = self.seqPosToGlobalCol(querySeqName, proteinPos);
242
- columns.add(col);
267
+ let proteinDriven = false;
268
+ return () => {
269
+ const { connectedViewId, transcriptToMsaMap, querySeqName } = self;
270
+ if (!connectedViewId || !transcriptToMsaMap) {
271
+ return;
272
+ }
273
+ const columns = new Set();
274
+ for (const view of getProteinViews(getSession(self).views)) {
275
+ for (const structure of view.structures) {
276
+ if (structure.connectedViewId !== connectedViewId) {
277
+ continue;
278
+ }
279
+ const highlights = structure.hoverGenomeHighlights;
280
+ if (!highlights || highlights.length === 0) {
281
+ continue;
282
+ }
283
+ const { g2p } = transcriptToMsaMap;
284
+ for (const highlight of highlights) {
285
+ for (let coord = highlight.start; coord < highlight.end; coord++) {
286
+ const proteinPos = g2p[coord];
287
+ if (proteinPos !== undefined) {
288
+ const col = self.seqPosToGlobalCol(querySeqName, proteinPos);
289
+ columns.add(col);
290
+ }
243
291
  }
244
292
  }
245
293
  }
246
294
  }
247
- }
248
- const visibleColumns = Array.from(columns)
249
- .map(col => self.globalColToVisibleCol(col))
250
- .filter((col) => col !== undefined);
251
- self.setHighlightedColumns(visibleColumns.length > 0 ? visibleColumns : undefined);
295
+ const visibleColumns = Array.from(columns)
296
+ .map(col => self.globalColToVisibleCol(col))
297
+ .filter((col) => col !== undefined);
298
+ if (visibleColumns.length > 0) {
299
+ self.setHighlightedColumns(visibleColumns);
300
+ proteinDriven = true;
301
+ }
302
+ else if (proteinDriven) {
303
+ // our protein-hover highlight ended — fall back to the declarative seed
304
+ // instead of wiping a column the URL/user asked to keep lit
305
+ self.setHighlightedColumns(self.highlightColumns?.length ? self.highlightColumns : undefined);
306
+ proteinDriven = false;
307
+ }
308
+ };
252
309
  }
253
310
  export function runCleanup() {
254
311
  cleanupOldData().catch((e) => {
@@ -0,0 +1,16 @@
1
+ import type { InterProScanResults } from 'react-msaview';
2
+ interface DomainModel {
3
+ data: {
4
+ treeMetadata?: string;
5
+ };
6
+ setProgress: (arg: string) => void;
7
+ setDomains: (data: Record<string, InterProScanResults>) => void;
8
+ }
9
+ /**
10
+ * Overlay protein domains on the alignment using NCBI's pre-computed CDD
11
+ * annotations. The BLAST workflow stores each hit's accession in treeMetadata,
12
+ * so we look those up via efetch and key the results by MSA row name (which is
13
+ * what react-msaview matches domains against).
14
+ */
15
+ export declare function loadProteinDomains(self: DomainModel): Promise<void>;
16
+ export {};
@@ -0,0 +1,33 @@
1
+ import { fetchProteinDomains } from '../utils/ncbiDomains';
2
+ /**
3
+ * Overlay protein domains on the alignment using NCBI's pre-computed CDD
4
+ * annotations. The BLAST workflow stores each hit's accession in treeMetadata,
5
+ * so we look those up via efetch and key the results by MSA row name (which is
6
+ * what react-msaview matches domains against).
7
+ */
8
+ export async function loadProteinDomains(self) {
9
+ const metadataJson = self.data.treeMetadata;
10
+ if (!metadataJson) {
11
+ throw new Error('No sequence metadata available to look up domains');
12
+ }
13
+ const metadata = JSON.parse(metadataJson);
14
+ const rowAccessions = Object.entries(metadata)
15
+ .map(([rowName, meta]) => ({ rowName, accession: meta.Accession }))
16
+ .filter((r) => !!r.accession);
17
+ if (rowAccessions.length === 0) {
18
+ throw new Error('No NCBI accessions found in alignment rows');
19
+ }
20
+ self.setProgress(`Fetching protein domains from NCBI for ${rowAccessions.length} sequences...`);
21
+ const byAccession = await fetchProteinDomains(rowAccessions.map(r => r.accession));
22
+ const annotations = {};
23
+ for (const { rowName, accession } of rowAccessions) {
24
+ const matches = byAccession.get(accession);
25
+ if (matches && matches.length > 0) {
26
+ annotations[rowName] = { matches, xref: [{ id: rowName }] };
27
+ }
28
+ }
29
+ if (Object.keys(annotations).length === 0) {
30
+ throw new Error('No CDD domain annotations found for these proteins');
31
+ }
32
+ self.setDomains(annotations);
33
+ }