jbrowse-plugin-msaview 3.2.0 → 3.4.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.
Files changed (106) hide show
  1. package/dist/AddHighlightModel/GenomeMouseoverHighlight.js +1 -1
  2. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +1 -1
  3. package/dist/AddHighlightModel/index.js +1 -1
  4. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +61 -17
  5. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +1 -1
  6. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +2 -2
  7. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.d.ts +12 -0
  8. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +21 -2
  9. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.test.d.ts +1 -0
  10. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.test.js +29 -0
  11. package/dist/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.js +1 -1
  12. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +28 -0
  13. package/dist/LaunchMsaView/components/BlastQuery/consts.js +21 -0
  14. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +1 -1
  15. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +9 -4
  16. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +9 -0
  17. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +20 -0
  18. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +1 -1
  19. package/dist/LaunchMsaView/components/useFeatureSequence.js +1 -1
  20. package/dist/LaunchMsaView/detectQueryRow.d.ts +15 -2
  21. package/dist/LaunchMsaView/detectQueryRow.js +20 -21
  22. package/dist/LaunchMsaView/detectQueryRow.test.js +15 -15
  23. package/dist/LaunchMsaView/useQueryRowName.js +5 -8
  24. package/dist/MsaViewPanel/afterCreateAutoruns.js +2 -2
  25. package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +2 -2
  26. package/dist/MsaViewPanel/components/JobLink.js +7 -1
  27. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +17 -0
  28. package/dist/MsaViewPanel/components/LaunchProgress.js +41 -0
  29. package/dist/MsaViewPanel/components/MsaViewPanel.js +6 -3
  30. package/dist/MsaViewPanel/components/MsaViewPanel.test.d.ts +1 -0
  31. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +68 -0
  32. package/dist/MsaViewPanel/doLaunchBlast.d.ts +1 -1
  33. package/dist/MsaViewPanel/doLaunchBlast.js +84 -52
  34. package/dist/MsaViewPanel/doLaunchOrthologs.d.ts +7 -5
  35. package/dist/MsaViewPanel/doLaunchOrthologs.js +64 -30
  36. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +106 -1
  37. package/dist/MsaViewPanel/genomeToMSA.js +4 -2
  38. package/dist/MsaViewPanel/genomeToMSA.test.js +34 -0
  39. package/dist/MsaViewPanel/model.d.ts +41 -11
  40. package/dist/MsaViewPanel/model.js +6 -0
  41. package/dist/MsaViewPanel/observeProteinHighlights.test.js +11 -0
  42. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +1 -0
  43. package/dist/MsaViewPanel/util.d.ts +18 -0
  44. package/dist/MsaViewPanel/util.js +17 -0
  45. package/dist/jbrowse-plugin-msaview.umd.production.min.js +47 -35
  46. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  47. package/dist/utils/blastCache.d.ts +10 -6
  48. package/dist/utils/blastCache.js +15 -3
  49. package/dist/utils/ebiBlast.d.ts +1 -1
  50. package/dist/utils/msa.d.ts +12 -0
  51. package/dist/utils/msa.js +35 -12
  52. package/dist/utils/msaRows.d.ts +31 -0
  53. package/dist/utils/msaRows.js +67 -0
  54. package/dist/utils/pantherOrthologs.d.ts +79 -0
  55. package/dist/utils/pantherOrthologs.js +262 -0
  56. package/dist/utils/phmmer.d.ts +53 -0
  57. package/dist/utils/phmmer.js +118 -0
  58. package/dist/utils/taxonomyNames.d.ts +1 -1
  59. package/dist/utils/taxonomyNames.js +6 -1
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/package.json +27 -21
  63. package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +1 -1
  64. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +1 -1
  65. package/src/AddHighlightModel/index.tsx +1 -1
  66. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +88 -30
  67. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +1 -1
  68. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +4 -4
  69. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.test.ts +50 -0
  70. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +23 -3
  71. package/src/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.tsx +1 -1
  72. package/src/LaunchMsaView/components/BlastQuery/consts.ts +40 -0
  73. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +1 -1
  74. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +21 -5
  75. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +52 -0
  76. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +1 -1
  77. package/src/LaunchMsaView/components/useFeatureSequence.ts +1 -1
  78. package/src/LaunchMsaView/detectQueryRow.test.ts +17 -15
  79. package/src/LaunchMsaView/detectQueryRow.ts +34 -23
  80. package/src/LaunchMsaView/useQueryRowName.ts +6 -9
  81. package/src/MsaViewPanel/afterCreateAutoruns.ts +2 -2
  82. package/src/MsaViewPanel/components/ErrorBoundary.tsx +2 -1
  83. package/src/MsaViewPanel/components/JobLink.tsx +7 -2
  84. package/src/MsaViewPanel/components/LaunchProgress.tsx +62 -0
  85. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +83 -0
  86. package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -4
  87. package/src/MsaViewPanel/doLaunchBlast.ts +127 -69
  88. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +119 -2
  89. package/src/MsaViewPanel/doLaunchOrthologs.ts +100 -38
  90. package/src/MsaViewPanel/genomeToMSA.test.ts +37 -0
  91. package/src/MsaViewPanel/genomeToMSA.ts +6 -2
  92. package/src/MsaViewPanel/model.ts +38 -5
  93. package/src/MsaViewPanel/observeProteinHighlights.test.ts +13 -0
  94. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +1 -0
  95. package/src/MsaViewPanel/util.ts +18 -0
  96. package/src/utils/blastCache.ts +33 -12
  97. package/src/utils/ebiBlast.ts +1 -1
  98. package/src/utils/msa.ts +43 -12
  99. package/src/utils/msaRows.ts +95 -0
  100. package/src/utils/pantherOrthologs.ts +399 -0
  101. package/src/utils/phmmer.ts +174 -0
  102. package/src/utils/taxonomyNames.ts +6 -1
  103. package/src/version.ts +1 -1
  104. package/dist/MsaViewPanel/components/LoadingBLAST.d.ts +0 -6
  105. package/dist/MsaViewPanel/components/LoadingBLAST.js +0 -26
  106. package/src/MsaViewPanel/components/LoadingBLAST.tsx +0 -48
@@ -1,8 +1,9 @@
1
1
  import { beforeEach, describe, expect, test, vi } from 'vitest';
2
- import { doLaunchOrthologs } from './doLaunchOrthologs';
3
2
  import { launchMSA } from '../utils/msa';
4
3
  import { defaultMaxSpecies, fetchOrthologRows, fetchProteinForGene, resolveGeneId, } from '../utils/ncbiOrthologs';
4
+ import { fetchPantherOrthologs } from '../utils/pantherOrthologs';
5
5
  import { fetchTaxonomyInfo } from '../utils/taxonomyNames';
6
+ import { doLaunchOrthologs } from './doLaunchOrthologs';
6
7
  // Every network call is mocked and nothing else is. What is under test is the
7
8
  // argument shaping either side of those calls -- which species get asked for,
8
9
  // what becomes the QUERY row, and whether the row earns the Accession that
@@ -14,11 +15,15 @@ vi.mock('../utils/ncbiOrthologs', async (importOriginal) => ({
14
15
  fetchProteinForGene: vi.fn(),
15
16
  fetchOrthologRows: vi.fn(),
16
17
  }));
18
+ vi.mock('../utils/pantherOrthologs', () => ({
19
+ fetchPantherOrthologs: vi.fn(),
20
+ }));
17
21
  vi.mock('../utils/msa', () => ({ launchMSA: vi.fn() }));
18
22
  vi.mock('../utils/taxonomyNames', () => ({ fetchTaxonomyInfo: vi.fn() }));
19
23
  const mockResolveGeneId = vi.mocked(resolveGeneId);
20
24
  const mockFetchProtein = vi.mocked(fetchProteinForGene);
21
25
  const mockFetchRows = vi.mocked(fetchOrthologRows);
26
+ const mockFetchPanther = vi.mocked(fetchPantherOrthologs);
22
27
  const mockLaunchMSA = vi.mocked(launchMSA);
23
28
  const mockFetchTaxonomy = vi.mocked(fetchTaxonomyInfo);
24
29
  const HUMAN = 9606;
@@ -199,3 +204,103 @@ describe('the Accession that drives the domain overlay', () => {
199
204
  expect(queryMetadata(result).Accession).toBeUndefined();
200
205
  });
201
206
  });
207
+ // The second source. What is under test is the dispatch and what the PANTHER
208
+ // result becomes on the query row -- the rows themselves are shaped upstream,
209
+ // and the tail of the launch (labels, aligner, metadata) is the same code the
210
+ // NCBI tests above already cover.
211
+ describe('the PANTHER source', () => {
212
+ const YEAST = 559292;
213
+ const found = {
214
+ matched: 'CDC28',
215
+ query: {
216
+ code: 'YEAST',
217
+ accession: 'P00546',
218
+ geneRef: 'SGD=S000000364',
219
+ sequence: 'MSGELANYKRLEKVGEGTYGVVYKA',
220
+ },
221
+ rows: [
222
+ {
223
+ taxId: HUMAN,
224
+ label: 'human',
225
+ scientificName: 'Homo sapiens',
226
+ commonName: 'human',
227
+ geneId: 'HGNC=1771',
228
+ protein: 'P24941',
229
+ sequence: 'MENFQKVEKIGEGTYGVVYKARNK',
230
+ },
231
+ ],
232
+ };
233
+ beforeEach(() => {
234
+ mockFetchPanther.mockResolvedValue(found);
235
+ mockFetchTaxonomy.mockResolvedValue(new Map([[YEAST, { sciname: 'Saccharomyces cerevisiae' }]]));
236
+ });
237
+ test('source omitted is NCBI, so an old launch never reaches PANTHER', async () => {
238
+ await doLaunchOrthologs({ self: makeModel(params()) });
239
+ expect(mockFetchPanther).not.toHaveBeenCalled();
240
+ expect(mockResolveGeneId).toHaveBeenCalled();
241
+ });
242
+ test('source panther asks PANTHER with the same species semantics, and skips NCBI', async () => {
243
+ await doLaunchOrthologs({
244
+ self: makeModel({
245
+ taxId: YEAST,
246
+ source: 'panther',
247
+ geneCandidates: ['CDC28'],
248
+ msaAlgorithm: 'clustalo',
249
+ taxa: [HUMAN, YEAST],
250
+ maxSpecies: 7,
251
+ }),
252
+ });
253
+ expect(mockResolveGeneId).not.toHaveBeenCalled();
254
+ expect(mockFetchRows).not.toHaveBeenCalled();
255
+ const { candidates, taxId, taxa, exclude, limit } = mockFetchPanther.mock.calls[0][0];
256
+ expect(candidates).toEqual(['CDC28']);
257
+ expect(taxId).toBe(YEAST);
258
+ expect([...taxa]).toEqual([HUMAN, YEAST]);
259
+ expect(exclude).toBe(YEAST);
260
+ expect(limit).toBe(7);
261
+ });
262
+ test("the query row is PANTHER's own entry for the gene when no sequence was supplied, and carries its UniProt accession for the domain overlay", async () => {
263
+ const result = await doLaunchOrthologs({
264
+ self: makeModel({
265
+ taxId: YEAST,
266
+ source: 'panther',
267
+ geneCandidates: ['CDC28'],
268
+ msaAlgorithm: 'clustalo',
269
+ }),
270
+ });
271
+ expect(queryRowName()).toBe('Saccharomyces_cerevisiae_query');
272
+ expect(queryRowSent()).toBe(found.query.sequence);
273
+ expect(queryMetadata(result)).toEqual({
274
+ 'Gene ID': 'SGD=S000000364',
275
+ Accession: 'P00546',
276
+ });
277
+ expect(JSON.parse(result.treeMetadata).human).toMatchObject({
278
+ Accession: 'P24941',
279
+ 'Gene ID': 'HGNC=1771',
280
+ });
281
+ });
282
+ test('a supplied sequence still wins, and a different isoform earns no Accession', async () => {
283
+ const result = await doLaunchOrthologs({
284
+ self: makeModel({
285
+ taxId: YEAST,
286
+ source: 'panther',
287
+ geneCandidates: ['CDC28'],
288
+ msaAlgorithm: 'clustalo',
289
+ proteinSequence: 'MDIFFERENTISOFORM',
290
+ }),
291
+ });
292
+ expect(queryRowSent()).toBe('MDIFFERENTISOFORM');
293
+ expect(queryMetadata(result).Accession).toBeUndefined();
294
+ });
295
+ test('names PANTHER when it has no protein for the query row', async () => {
296
+ mockFetchPanther.mockResolvedValue({ ...found, query: undefined });
297
+ await expect(doLaunchOrthologs({
298
+ self: makeModel({
299
+ taxId: YEAST,
300
+ source: 'panther',
301
+ geneCandidates: ['CDC28'],
302
+ msaAlgorithm: 'clustalo',
303
+ }),
304
+ })).rejects.toThrow(/PANTHER returned no representative protein/);
305
+ });
306
+ });
@@ -1,9 +1,11 @@
1
1
  import { getSession } from '@jbrowse/core/util';
2
- import { hasHoverPosition } from './util';
2
+ import { hasHoverPosition, hasQueryRow } from './util';
3
3
  export function genomeToMSA({ model }) {
4
4
  const { hovered } = getSession(model);
5
5
  const { querySeqName, transcriptToMsaMap, connectedView, mafRegion } = model;
6
- if (!connectedView?.initialized || !hasHoverPosition(hovered)) {
6
+ if (!connectedView?.initialized ||
7
+ !hasHoverPosition(hovered) ||
8
+ !hasQueryRow(model)) {
7
9
  return undefined;
8
10
  }
9
11
  const { coord, refName } = hovered.hoverPosition;
@@ -19,6 +19,7 @@ describe('genomeToMSA', () => {
19
19
  });
20
20
  const model = {
21
21
  querySeqName: 'hg38.chr1',
22
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
22
23
  transcriptToMsaMap: undefined,
23
24
  mafRegion: {
24
25
  refName: 'chr1',
@@ -38,6 +39,7 @@ describe('genomeToMSA', () => {
38
39
  });
39
40
  const model = {
40
41
  querySeqName: 'hg38.chr1',
42
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
41
43
  transcriptToMsaMap: undefined,
42
44
  mafRegion: {
43
45
  refName: 'chr1',
@@ -62,6 +64,7 @@ describe('genomeToMSA', () => {
62
64
  const mockSeqPosToVisibleCol = vi.fn().mockReturnValue(5);
63
65
  const model = {
64
66
  querySeqName: 'hg38.chr1',
67
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
65
68
  transcriptToMsaMap: undefined,
66
69
  mafRegion: {
67
70
  refName: 'chr1',
@@ -90,6 +93,7 @@ describe('genomeToMSA', () => {
90
93
  });
91
94
  const model = {
92
95
  querySeqName: 'hg38.chr1',
96
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
93
97
  transcriptToMsaMap: undefined,
94
98
  mafRegion: {
95
99
  refName: 'chr1',
@@ -116,6 +120,7 @@ describe('genomeToMSA', () => {
116
120
  });
117
121
  const model = {
118
122
  querySeqName: 'hg38.chr1',
123
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
119
124
  transcriptToMsaMap: undefined,
120
125
  mafRegion: {
121
126
  refName: 'chr1',
@@ -142,6 +147,7 @@ describe('genomeToMSA', () => {
142
147
  });
143
148
  const model = {
144
149
  querySeqName: 'hg38.chr1',
150
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
145
151
  transcriptToMsaMap: undefined,
146
152
  mafRegion: {
147
153
  refName: 'chr1',
@@ -167,6 +173,7 @@ describe('genomeToMSA', () => {
167
173
  });
168
174
  const model = {
169
175
  querySeqName: 'hg38.chr1',
176
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
170
177
  transcriptToMsaMap: undefined,
171
178
  mafRegion: {
172
179
  refName: 'chr1',
@@ -195,6 +202,7 @@ describe('genomeToMSA', () => {
195
202
  const mockSeqPosToVisibleCol = vi.fn().mockReturnValue(10);
196
203
  const model = {
197
204
  querySeqName: 'QUERY',
205
+ rows: [['QUERY', 'MKVLTAEEK']],
198
206
  transcriptToMsaMap: {
199
207
  refName: 'chr1',
200
208
  // g2p is keyed by 0-based genome position, the hover coord is 1-based
@@ -220,6 +228,7 @@ describe('genomeToMSA', () => {
220
228
  const mockSeqPosToVisibleCol = vi.fn();
221
229
  const model = {
222
230
  querySeqName: 'QUERY',
231
+ rows: [['QUERY', 'MKVLTAEEK']],
223
232
  transcriptToMsaMap: {
224
233
  refName: 'chr1',
225
234
  g2p: { 1004: 10 },
@@ -240,6 +249,7 @@ describe('genomeToMSA', () => {
240
249
  });
241
250
  const model = {
242
251
  querySeqName: 'QUERY',
252
+ rows: [['QUERY', 'MKVLTAEEK']],
243
253
  transcriptToMsaMap: {
244
254
  refName: 'chr1',
245
255
  g2p: { 1000: 0 }, // No entry for 1004
@@ -261,6 +271,7 @@ describe('genomeToMSA', () => {
261
271
  });
262
272
  const model = {
263
273
  querySeqName: 'QUERY',
274
+ rows: [['QUERY', 'MKVLTAEEK']],
264
275
  transcriptToMsaMap: undefined,
265
276
  mafRegion: undefined,
266
277
  connectedView: { initialized: true },
@@ -269,4 +280,27 @@ describe('genomeToMSA', () => {
269
280
  const result = genomeToMSA({ model });
270
281
  expect(result).toBeUndefined();
271
282
  });
283
+ // seqPosToVisibleCol answers 0 for a row name it does not know, so without a
284
+ // guard an alignment whose query row is missing -- the default 'QUERY' on an
285
+ // uploaded file, or the empty name the manual panel leaves when it matches
286
+ // nothing -- lights column 0 on every genome hover
287
+ test('returns undefined when querySeqName names no row here', () => {
288
+ mockGetSession.mockReturnValue({
289
+ hovered: {
290
+ hoverFeature: {},
291
+ hoverPosition: { coord: 1005, refName: 'chr1' },
292
+ },
293
+ });
294
+ const seqPosToVisibleCol = vi.fn(() => 0);
295
+ const model = {
296
+ querySeqName: 'QUERY',
297
+ rows: [['some_other_row', 'MKVLTAEEK']],
298
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 3 } },
299
+ mafRegion: undefined,
300
+ connectedView: { initialized: true },
301
+ seqPosToVisibleCol,
302
+ };
303
+ expect(genomeToMSA({ model })).toBeUndefined();
304
+ expect(seqPosToVisibleCol).not.toHaveBeenCalled();
305
+ });
272
306
  });
@@ -1,6 +1,6 @@
1
1
  export type { MSAFormat } from 'msa-parsers';
2
+ import type { BlastDatabase, MsaAlgorithm, PhmmerDatabase } from '../LaunchMsaView/components/BlastQuery/consts';
2
3
  import type { MafRegion, MsaViewInitState } from './types';
3
- import type { BlastDatabase, MsaAlgorithm } from '../LaunchMsaView/components/BlastQuery/consts';
4
4
  import type { Feature } from '@jbrowse/core/util';
5
5
  import type { Instance } from '@jbrowse/mobx-state-tree';
6
6
  import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
@@ -11,15 +11,43 @@ export interface IRegion {
11
11
  start: number;
12
12
  end: number;
13
13
  }
14
- export interface BlastParams {
15
- blastDatabase: BlastDatabase;
16
- msaAlgorithm: MsaAlgorithm;
14
+ /**
15
+ * A search to run, discriminated by the program that runs it: the two arms
16
+ * differ in which databases they name and in whether an aligner runs at all, so
17
+ * splitting them is what lets doLaunchBlast read the database without asserting
18
+ * whose it is.
19
+ *
20
+ * The field is still `blastDatabase` rather than `database`: it is persisted in
21
+ * session snapshots and in the IndexedDB result cache, so renaming it would
22
+ * orphan every row already written.
23
+ */
24
+ export type BlastParams = {
17
25
  selectedTranscript?: Feature;
18
26
  proteinSequence: string;
19
- }
27
+ } & ({
28
+ /** absent on params written before phmmer existed, which were all blastp */
29
+ searchProgram?: 'blastp';
30
+ blastDatabase: BlastDatabase;
31
+ msaAlgorithm: MsaAlgorithm;
32
+ } | {
33
+ searchProgram: 'phmmer';
34
+ /** phmmer names its databases its own way: `swissprot`, not `uniprotkb_swissprot` */
35
+ blastDatabase: PhmmerDatabase;
36
+ /** phmmer aligns as it searches, so there is no aligner to choose */
37
+ msaAlgorithm?: undefined;
38
+ });
39
+ /**
40
+ * Where the ortholog set comes from. NCBI's sets cover vertebrates and
41
+ * insects; PANTHER's span its 144 reference proteomes, human to yeast to
42
+ * Arabidopsis, so a gene from outside NCBI's scope aligns only through it.
43
+ */
44
+ export declare const orthologSources: readonly ["ncbi", "panther"];
45
+ export type OrthologSource = (typeof orthologSources)[number];
20
46
  export interface OrthologParams {
21
47
  /** NCBI taxon id of the assembly the query gene came from */
22
48
  taxId: number;
49
+ /** `ncbi` when omitted, so every launch written before this key keeps its meaning */
50
+ source?: OrthologSource;
23
51
  /**
24
52
  * taxon ids to include as rows. The query taxon has its own row already, so
25
53
  * it is excluded from this set whether or not it is named.
@@ -57,7 +85,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
57
85
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
58
86
  displayName: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
59
87
  minimized: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
60
- }, "height" | "id" | "type" | "autoTreeAreaWidth" | "drawLabels" | "drawNodeBubbles" | "drawTree" | "labelsAlignRight" | "showBranchLen" | "treeAreaWidth" | "treeWidth" | "bgColor" | "colorSchemeName" | "msaFormat" | "showColumnStats" | "allowedGappyness" | "colWidth" | "collapsed" | "currentAlignment" | "data" | "drawMsaLetters" | "featureFilters" | "gffFilehandle" | "hideGaps" | "highlightColumns" | "msaFilehandle" | "relativeTo" | "rowHeight" | "scrollX" | "scrollY" | "scrollZoom" | "showDomainLegend" | "showDomains" | "showOnly" | "subFeatureRows" | "treeFilehandle" | "treeMetadataFilehandle" | "turnedOffTracks"> & Omit<Omit<Omit<{}, "autoTreeAreaWidth" | "drawLabels" | "drawNodeBubbles" | "drawTree" | "labelsAlignRight" | "showBranchLen" | "treeAreaWidth" | "treeWidth"> & {
88
+ }, "id" | "type" | "autoTreeAreaWidth" | "drawLabels" | "drawNodeBubbles" | "drawTree" | "labelsAlignRight" | "showBranchLen" | "treeAreaWidth" | "treeWidth" | "bgColor" | "colorSchemeName" | "msaFormat" | "showColumnStats" | "allowedGappyness" | "colWidth" | "collapsed" | "currentAlignment" | "data" | "drawMsaLetters" | "featureFilters" | "gffFilehandle" | "height" | "hideGaps" | "highlightColumns" | "msaFilehandle" | "relativeTo" | "rowHeight" | "scrollX" | "scrollY" | "scrollZoom" | "showDomainLegend" | "showDomains" | "showOnly" | "subFeatureRows" | "treeFilehandle" | "treeMetadataFilehandle" | "turnedOffTracks"> & Omit<Omit<Omit<{}, "autoTreeAreaWidth" | "drawLabels" | "drawNodeBubbles" | "drawTree" | "labelsAlignRight" | "showBranchLen" | "treeAreaWidth" | "treeWidth"> & {
61
89
  drawLabels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
62
90
  labelsAlignRight: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
63
91
  treeAreaWidth: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
@@ -71,7 +99,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
71
99
  colorSchemeName: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
72
100
  showColumnStats: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
73
101
  msaFormat: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<import("msa-parsers").MSAFormat>>;
74
- }, "height" | "id" | "type" | "allowedGappyness" | "colWidth" | "collapsed" | "currentAlignment" | "data" | "drawMsaLetters" | "featureFilters" | "gffFilehandle" | "hideGaps" | "highlightColumns" | "msaFilehandle" | "relativeTo" | "rowHeight" | "scrollX" | "scrollY" | "scrollZoom" | "showDomainLegend" | "showDomains" | "showOnly" | "subFeatureRows" | "treeFilehandle" | "treeMetadataFilehandle" | "turnedOffTracks"> & {
102
+ }, "id" | "type" | "allowedGappyness" | "colWidth" | "collapsed" | "currentAlignment" | "data" | "drawMsaLetters" | "featureFilters" | "gffFilehandle" | "height" | "hideGaps" | "highlightColumns" | "msaFilehandle" | "relativeTo" | "rowHeight" | "scrollX" | "scrollY" | "scrollZoom" | "showDomainLegend" | "showDomains" | "showOnly" | "subFeatureRows" | "treeFilehandle" | "treeMetadataFilehandle" | "turnedOffTracks"> & {
75
103
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
76
104
  showDomains: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
77
105
  showDomainLegend: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
@@ -477,10 +505,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
477
505
  setTreeMetadata(treeMetadata?: string): void;
478
506
  setGFF(gff?: string): void;
479
507
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, {
480
- tree: string | undefined;
481
- msa: string | undefined;
482
- treeMetadata: string | undefined;
483
- gff: string | undefined;
508
+ [k: string]: string | undefined;
484
509
  }>, [undefined]>;
485
510
  featureFilters: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IMapType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>>, [undefined]>;
486
511
  relativeTo: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
@@ -550,6 +575,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
550
575
  highlightedColumns: number[] | undefined;
551
576
  minimapHeight: number;
552
577
  conservationTrackHeight: number;
578
+ sequenceLogoTrackHeight: number;
553
579
  marginLeft: number;
554
580
  error: unknown;
555
581
  annotations: import("msa-parsers").Annotation[];
@@ -646,6 +672,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
646
672
  }[];
647
673
  readonly colClustalX: Record<string, string>[];
648
674
  readonly conservation: number[];
675
+ readonly alphabetMaxBits: number;
649
676
  readonly propertyConservation: number[];
650
677
  readonly hierarchy: import("react-msaview").HierarchyNode<import("react-msaview").NodeWithIdsAndLength>;
651
678
  readonly totalHeight: number;
@@ -660,6 +687,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
660
687
  readonly dataInitialized: boolean;
661
688
  readonly blocksX: number[];
662
689
  readonly blocksY: number[];
690
+ readonly visibleMsaHeight: number;
663
691
  } & {
664
692
  readonly blocks2d: (readonly [number, number])[];
665
693
  readonly isLoading: boolean;
@@ -695,6 +723,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
695
723
  }): void;
696
724
  } & {
697
725
  readonly labelWidthMap: Map<string, number>;
726
+ readonly labelWidthScale: number;
698
727
  readonly labelsWidth: number;
699
728
  readonly secondaryStructureConsensus: string | undefined;
700
729
  readonly seqConsensus: string | undefined;
@@ -761,6 +790,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
761
790
  } & {
762
791
  setHeaderHeight(arg: number): void;
763
792
  setConservationTrackHeight(arg: number): void;
793
+ setSequenceLogoTrackHeight(arg: number): void;
764
794
  reset(): void;
765
795
  exportSVG(opts: {
766
796
  theme: import("@mui/material").Theme;
@@ -6,6 +6,12 @@ import { autorun } from 'mobx';
6
6
  import { MSAModelF } from 'react-msaview';
7
7
  import { autoLoadProteinDomains, launchBlastIfNeeded, launchOrthologsIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
8
8
  import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
9
+ /**
10
+ * Where the ortholog set comes from. NCBI's sets cover vertebrates and
11
+ * insects; PANTHER's span its 144 reference proteomes, human to yeast to
12
+ * Arabidopsis, so a gene from outside NCBI's scope aligns only through it.
13
+ */
14
+ export const orthologSources = ['ncbi', 'panther'];
9
15
  /**
10
16
  * #stateModel MsaViewPlugin
11
17
  * extends
@@ -18,6 +18,7 @@ function makeModel({ highlightColumns } = {}) {
18
18
  const calls = [];
19
19
  const model = {
20
20
  querySeqName: 'query',
21
+ rows: [['query', 'MKVLTAEEK']],
21
22
  connectedViewId: CONNECTED,
22
23
  // g2p is indexed by genome coord; identity keeps the arithmetic out of the way
23
24
  transcriptToMsaMap: {
@@ -195,6 +196,16 @@ describe('scope and redundant writes', () => {
195
196
  run();
196
197
  expect(calls).toEqual([]);
197
198
  });
199
+ // seqPosToGlobalCol answers 0 for a row name it does not know, so without a
200
+ // guard a structure hover would light column 0 of whatever row is first
201
+ test('a query row this alignment does not have contributes no column', () => {
202
+ const { model, calls } = makeModel();
203
+ Object.assign(model, { rows: [['some_other_row', 'MKVLTAEEK']] });
204
+ const run = observeProteinHighlights(model);
205
+ session({ hover: [{ start: 10, end: 12 }] });
206
+ run();
207
+ expect(calls).toEqual([]);
208
+ });
198
209
  test('nothing happens until the view is connected and mapped', () => {
199
210
  const { calls } = makeModel();
200
211
  const bare = {
@@ -21,6 +21,7 @@ function makeModel() {
21
21
  const calls = [];
22
22
  const model = {
23
23
  querySeqName: 'hg38.chr1',
24
+ rows: [['hg38.chr1', 'ACGTACGTAC']],
24
25
  transcriptToMsaMap: undefined,
25
26
  mafRegion,
26
27
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Whether `querySeqName` names a row this alignment actually has.
3
+ *
4
+ * react-msaview's `seqPosToGlobalCol` answers 0 for a name it does not know, so
5
+ * without this every genome position maps to the first column and hovering the
6
+ * genome — or a connected structure — lights column 0 of an unrelated row. The
7
+ * name is wrong more often than it looks: it defaults to `QUERY`, which an
8
+ * uploaded alignment has no reason to carry, and the manual panel leaves it
9
+ * empty when it cannot match the protein to a row.
10
+ *
11
+ * The other direction has no such hole: msaCoordToGenomeRegions needs the query
12
+ * row's sequence to map a column at all, so a missing row is already nothing
13
+ * there.
14
+ */
15
+ export declare function hasQueryRow(model: {
16
+ rows: string[][];
17
+ querySeqName: string;
18
+ }): boolean;
1
19
  export declare function hasHoverPosition(hovered: unknown): hovered is {
2
20
  hoverPosition: {
3
21
  coord: number;
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Whether `querySeqName` names a row this alignment actually has.
3
+ *
4
+ * react-msaview's `seqPosToGlobalCol` answers 0 for a name it does not know, so
5
+ * without this every genome position maps to the first column and hovering the
6
+ * genome — or a connected structure — lights column 0 of an unrelated row. The
7
+ * name is wrong more often than it looks: it defaults to `QUERY`, which an
8
+ * uploaded alignment has no reason to carry, and the manual panel leaves it
9
+ * empty when it cannot match the protein to a row.
10
+ *
11
+ * The other direction has no such hole: msaCoordToGenomeRegions needs the query
12
+ * row's sequence to map a column at all, so a missing row is already nothing
13
+ * there.
14
+ */
15
+ export function hasQueryRow(model) {
16
+ return model.rows.some(r => r[0] === model.querySeqName);
17
+ }
1
18
  export function hasHoverPosition(hovered) {
2
19
  return (!!hovered &&
3
20
  typeof hovered === 'object' &&