jbrowse-plugin-msaview 2.7.1 → 2.7.3

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 (59) hide show
  1. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +6 -5
  2. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +1 -1
  3. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js +1 -1
  4. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.d.ts +1 -1
  5. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +5 -2
  6. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +3 -2
  7. package/dist/LaunchMsaView/components/calculateProteinSequence.js +31 -15
  8. package/dist/LaunchMsaView/components/geneticCodes.d.ts +15 -0
  9. package/dist/LaunchMsaView/components/geneticCodes.js +227 -0
  10. package/dist/LaunchMsaView/components/types.d.ts +1 -0
  11. package/dist/LaunchMsaView/index.js +32 -26
  12. package/dist/MsaViewPanel/doLaunchBlast.js +10 -22
  13. package/dist/MsaViewPanel/genomeToMSA.js +13 -6
  14. package/dist/MsaViewPanel/genomeToMSA.test.js +35 -6
  15. package/dist/MsaViewPanel/model.d.ts +68 -66
  16. package/dist/MsaViewPanel/model.js +14 -12
  17. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +32 -13
  18. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +43 -14
  19. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +108 -18
  20. package/dist/MsaViewPanel/msaDataStore.js +8 -17
  21. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +8 -6
  22. package/dist/jbrowse-plugin-msaview.umd.production.min.js +31 -31
  23. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  24. package/dist/utils/blastCache.d.ts +1 -2
  25. package/dist/utils/blastCache.js +9 -22
  26. package/dist/utils/domainCache.js +6 -15
  27. package/dist/utils/idb.d.ts +12 -0
  28. package/dist/utils/idb.js +21 -0
  29. package/dist/utils/taxonomyNames.js +13 -18
  30. package/dist/utils/useLocalStorage.d.ts +1 -0
  31. package/dist/utils/useLocalStorage.js +29 -0
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +5 -3
  35. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +6 -8
  36. package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +1 -1
  37. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.tsx +1 -1
  38. package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +4 -2
  39. package/src/LaunchMsaView/components/calculateProteinSequence.ts +35 -20
  40. package/src/LaunchMsaView/components/geneticCodes.ts +298 -0
  41. package/src/LaunchMsaView/components/types.ts +3 -0
  42. package/src/LaunchMsaView/index.ts +44 -35
  43. package/src/MsaViewPanel/doLaunchBlast.ts +20 -29
  44. package/src/MsaViewPanel/genomeToMSA.test.ts +38 -6
  45. package/src/MsaViewPanel/genomeToMSA.ts +14 -6
  46. package/src/MsaViewPanel/model.ts +17 -12
  47. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +117 -18
  48. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +70 -26
  49. package/src/MsaViewPanel/msaDataStore.ts +17 -17
  50. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +8 -6
  51. package/src/utils/blastCache.ts +20 -23
  52. package/src/utils/domainCache.ts +14 -18
  53. package/src/utils/idb.ts +28 -0
  54. package/src/utils/taxonomyNames.ts +22 -24
  55. package/src/utils/useLocalStorage.ts +31 -0
  56. package/src/version.ts +1 -1
  57. package/dist/MsaViewPanel/blosum62.d.ts +0 -2
  58. package/dist/MsaViewPanel/blosum62.js +0 -627
  59. package/src/MsaViewPanel/blosum62.ts +0 -628
@@ -29,7 +29,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
29
29
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
30
30
  displayName: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
31
31
  minimized: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
32
- }, "height" | "id" | "type" | "drawLabels" | "labelsAlignRight" | "treeAreaWidth" | "treeWidth" | "showBranchLen" | "drawTree" | "drawNodeBubbles" | "autoTreeAreaWidth" | "bgColor" | "colorSchemeName" | "showColumnStats" | "msaFormat" | "data" | "showDomains" | "hideGaps" | "allowedGappyness" | "subFeatureRows" | "drawMsaLetters" | "scrollZoom" | "rowHeight" | "scrollY" | "scrollX" | "colWidth" | "treeFilehandle" | "msaFilehandle" | "treeMetadataFilehandle" | "gffFilehandle" | "currentAlignment" | "collapsed" | "showOnly" | "turnedOffTracks" | "featureFilters" | "relativeTo" | "highlightColumns"> & Omit<Omit<Omit<{}, "drawLabels" | "labelsAlignRight" | "treeAreaWidth" | "treeWidth" | "showBranchLen" | "drawTree" | "drawNodeBubbles" | "autoTreeAreaWidth"> & {
32
+ }, "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" | "showDomains" | "showOnly" | "subFeatureRows" | "treeFilehandle" | "treeMetadataFilehandle" | "turnedOffTracks"> & Omit<Omit<Omit<{}, "autoTreeAreaWidth" | "drawLabels" | "drawNodeBubbles" | "drawTree" | "labelsAlignRight" | "showBranchLen" | "treeAreaWidth" | "treeWidth"> & {
33
33
  drawLabels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
34
34
  labelsAlignRight: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
35
35
  treeAreaWidth: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
@@ -38,12 +38,12 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
38
38
  drawTree: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
39
39
  drawNodeBubbles: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
40
40
  autoTreeAreaWidth: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
41
- }, "bgColor" | "colorSchemeName" | "showColumnStats" | "msaFormat"> & {
41
+ }, "bgColor" | "colorSchemeName" | "msaFormat" | "showColumnStats"> & {
42
42
  bgColor: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
43
43
  colorSchemeName: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
44
44
  showColumnStats: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
45
45
  msaFormat: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<import("react-msaview").MSAFormat>>;
46
- }, "height" | "id" | "type" | "data" | "showDomains" | "hideGaps" | "allowedGappyness" | "subFeatureRows" | "drawMsaLetters" | "scrollZoom" | "rowHeight" | "scrollY" | "scrollX" | "colWidth" | "treeFilehandle" | "msaFilehandle" | "treeMetadataFilehandle" | "gffFilehandle" | "currentAlignment" | "collapsed" | "showOnly" | "turnedOffTracks" | "featureFilters" | "relativeTo" | "highlightColumns"> & {
46
+ }, "height" | "id" | "type" | "allowedGappyness" | "colWidth" | "collapsed" | "currentAlignment" | "data" | "drawMsaLetters" | "featureFilters" | "gffFilehandle" | "hideGaps" | "highlightColumns" | "msaFilehandle" | "relativeTo" | "rowHeight" | "scrollX" | "scrollY" | "scrollZoom" | "showDomains" | "showOnly" | "subFeatureRows" | "treeFilehandle" | "treeMetadataFilehandle" | "turnedOffTracks"> & {
47
47
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
48
48
  showDomains: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
49
49
  hideGaps: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
@@ -97,18 +97,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
97
97
  authInfo: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
98
98
  }> | undefined;
99
99
  }, ({
100
- locationType: "UriLocation";
101
- uri: string;
102
- } & Partial<{
103
- locationType: "UriLocation";
104
- uri: string;
105
- baseUri: string | undefined;
106
- internetAccountId: string | undefined;
107
- internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
108
- internetAccountType: string;
109
- authInfo: any;
110
- }> | undefined;
111
- }>) | ({
112
100
  blobId: string;
113
101
  locationType: "BlobLocation";
114
102
  name: string;
@@ -130,6 +118,18 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
130
118
  } & Partial<{
131
119
  locationType: "LocalPathLocation";
132
120
  localPath: string;
121
+ }>) | ({
122
+ locationType: "UriLocation";
123
+ uri: string;
124
+ } & Partial<{
125
+ locationType: "UriLocation";
126
+ uri: string;
127
+ baseUri: string | undefined;
128
+ internetAccountId: string | undefined;
129
+ internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
130
+ internetAccountType: string;
131
+ authInfo: any;
132
+ }> | undefined;
133
133
  }>)>, import("@jbrowse/core/util/types/mst").LegacyFileLocation | import("@jbrowse/mobx-state-tree").ModelCreationType<{
134
134
  locationType: "LocalPathLocation";
135
135
  localPath: string;
@@ -191,18 +191,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
191
191
  authInfo: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
192
192
  }> | undefined;
193
193
  }, ({
194
- locationType: "UriLocation";
195
- uri: string;
196
- } & Partial<{
197
- locationType: "UriLocation";
198
- uri: string;
199
- baseUri: string | undefined;
200
- internetAccountId: string | undefined;
201
- internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
202
- internetAccountType: string;
203
- authInfo: any;
204
- }> | undefined;
205
- }>) | ({
206
194
  blobId: string;
207
195
  locationType: "BlobLocation";
208
196
  name: string;
@@ -224,6 +212,18 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
224
212
  } & Partial<{
225
213
  locationType: "LocalPathLocation";
226
214
  localPath: string;
215
+ }>) | ({
216
+ locationType: "UriLocation";
217
+ uri: string;
218
+ } & Partial<{
219
+ locationType: "UriLocation";
220
+ uri: string;
221
+ baseUri: string | undefined;
222
+ internetAccountId: string | undefined;
223
+ internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
224
+ internetAccountType: string;
225
+ authInfo: any;
226
+ }> | undefined;
227
227
  }>)>, import("@jbrowse/core/util/types/mst").LegacyFileLocation | import("@jbrowse/mobx-state-tree").ModelCreationType<{
228
228
  locationType: "LocalPathLocation";
229
229
  localPath: string;
@@ -285,18 +285,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
285
285
  authInfo: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
286
286
  }> | undefined;
287
287
  }, ({
288
- locationType: "UriLocation";
289
- uri: string;
290
- } & Partial<{
291
- locationType: "UriLocation";
292
- uri: string;
293
- baseUri: string | undefined;
294
- internetAccountId: string | undefined;
295
- internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
296
- internetAccountType: string;
297
- authInfo: any;
298
- }> | undefined;
299
- }>) | ({
300
288
  blobId: string;
301
289
  locationType: "BlobLocation";
302
290
  name: string;
@@ -318,6 +306,18 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
318
306
  } & Partial<{
319
307
  locationType: "LocalPathLocation";
320
308
  localPath: string;
309
+ }>) | ({
310
+ locationType: "UriLocation";
311
+ uri: string;
312
+ } & Partial<{
313
+ locationType: "UriLocation";
314
+ uri: string;
315
+ baseUri: string | undefined;
316
+ internetAccountId: string | undefined;
317
+ internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
318
+ internetAccountType: string;
319
+ authInfo: any;
320
+ }> | undefined;
321
321
  }>)>, import("@jbrowse/core/util/types/mst").LegacyFileLocation | import("@jbrowse/mobx-state-tree").ModelCreationType<{
322
322
  locationType: "LocalPathLocation";
323
323
  localPath: string;
@@ -379,18 +379,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
379
379
  authInfo: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
380
380
  }> | undefined;
381
381
  }, ({
382
- locationType: "UriLocation";
383
- uri: string;
384
- } & Partial<{
385
- locationType: "UriLocation";
386
- uri: string;
387
- baseUri: string | undefined;
388
- internetAccountId: string | undefined;
389
- internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
390
- internetAccountType: string;
391
- authInfo: any;
392
- }> | undefined;
393
- }>) | ({
394
382
  blobId: string;
395
383
  locationType: "BlobLocation";
396
384
  name: string;
@@ -412,6 +400,18 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
412
400
  } & Partial<{
413
401
  locationType: "LocalPathLocation";
414
402
  localPath: string;
403
+ }>) | ({
404
+ locationType: "UriLocation";
405
+ uri: string;
406
+ } & Partial<{
407
+ locationType: "UriLocation";
408
+ uri: string;
409
+ baseUri: string | undefined;
410
+ internetAccountId: string | undefined;
411
+ internetAccountPreAuthorization: import("@jbrowse/mobx-state-tree").ModelCreationType<{
412
+ internetAccountType: string;
413
+ authInfo: any;
414
+ }> | undefined;
415
415
  }>)>, import("@jbrowse/core/util/types/mst").LegacyFileLocation | import("@jbrowse/mobx-state-tree").ModelCreationType<{
416
416
  locationType: "LocalPathLocation";
417
417
  localPath: string;
@@ -581,7 +581,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
581
581
  readonly noTree: boolean;
582
582
  readonly noDomains: boolean;
583
583
  menuItems(): never[];
584
- readonly treeMetadata: any;
584
+ readonly treeMetadata: Record<string, Record<string, string> | undefined>;
585
585
  readonly MSA: import("react-msaview").MSAParserType | null;
586
586
  readonly numColumns: number;
587
587
  readonly tree: import("react-msaview").NodeWithIds;
@@ -789,7 +789,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
789
789
  accession?: string;
790
790
  dbxref?: string;
791
791
  } | undefined;
792
- treeMetadata: any;
792
+ treeMetadata: Record<string, string> | undefined;
793
793
  };
794
794
  } & {
795
795
  setHeaderHeight(arg: number): void;
@@ -841,19 +841,21 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
841
841
  } & {
842
842
  /**
843
843
  * #getter
844
- * Genome region under the current MSA hover column. Suppressed on the LGV
844
+ * Genome regions under the current MSA hover column. Suppressed on the LGV
845
845
  * while it's being hovered (GenomeMouseoverHighlight shows the crisp 1bp
846
846
  * marker there instead of this wider codon band).
847
847
  */
848
- readonly connectedHoverHighlight: IRegion | undefined;
848
+ readonly connectedHoverHighlights: IRegion[];
849
849
  /**
850
850
  * #getter
851
- * Genome region under the persistent MSA click selection. Shown
851
+ * Genome regions under the persistent MSA click selection. Shown
852
852
  * regardless of LGV hover, so hovering the genome doesn't hide it.
853
853
  */
854
- readonly connectedClickHighlight: IRegion | undefined;
854
+ readonly connectedClickHighlights: IRegion[];
855
855
  /**
856
856
  * #getter
857
+ * cross-plugin contract: jbrowse-plugin-mafviewer reads this off the view
858
+ * to draw the same highlights in its own display
857
859
  */
858
860
  readonly connectedHighlights: IRegion[];
859
861
  } & {
@@ -935,11 +937,10 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
935
937
  displayName: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
936
938
  minimized: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
937
939
  }> & {
938
- data: {
939
- treeMetadata?: string | undefined;
940
- msa?: string | undefined;
941
- tree?: string | undefined;
942
- };
940
+ bgColor: boolean;
941
+ colorSchemeName: string;
942
+ showColumnStats: boolean;
943
+ msaFormat: import("react-msaview").MSAFormat | undefined;
943
944
  drawLabels: boolean;
944
945
  labelsAlignRight: boolean;
945
946
  treeAreaWidth: number;
@@ -948,10 +949,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
948
949
  drawTree: boolean;
949
950
  drawNodeBubbles: boolean;
950
951
  autoTreeAreaWidth: boolean;
951
- bgColor: boolean;
952
- colorSchemeName: string;
953
- showColumnStats: boolean;
954
- msaFormat: import("react-msaview").MSAFormat | undefined;
955
952
  id: string;
956
953
  showDomains: boolean;
957
954
  hideGaps: boolean;
@@ -1052,6 +1049,11 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
1052
1049
  featureFilters: import("mobx").IKeyValueMap<boolean>;
1053
1050
  relativeTo: string | undefined;
1054
1051
  highlightColumns: number[] | undefined;
1052
+ data: {
1053
+ tree?: string | undefined;
1054
+ msa?: string | undefined;
1055
+ treeMetadata?: string | undefined;
1056
+ };
1055
1057
  } & import("@jbrowse/mobx-state-tree")._NotCustomized>;
1056
1058
  export type JBrowsePluginMsaViewStateModel = ReturnType<typeof stateModelFactory>;
1057
1059
  export type JBrowsePluginMsaViewModel = Instance<JBrowsePluginMsaViewStateModel>;
@@ -5,7 +5,7 @@ import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
5
5
  import { autorun } from 'mobx';
6
6
  import { MSAModelF } from 'react-msaview';
7
7
  import { autoLoadProteinDomains, launchBlastIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
8
- import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
8
+ import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
9
9
  /**
10
10
  * #stateModel MsaViewPlugin
11
11
  * extends
@@ -119,35 +119,37 @@ export default function stateModelFactory() {
119
119
  .views(self => ({
120
120
  /**
121
121
  * #getter
122
- * Genome region under the current MSA hover column. Suppressed on the LGV
122
+ * Genome regions under the current MSA hover column. Suppressed on the LGV
123
123
  * while it's being hovered (GenomeMouseoverHighlight shows the crisp 1bp
124
124
  * marker there instead of this wider codon band).
125
125
  */
126
- get connectedHoverHighlight() {
126
+ get connectedHoverHighlights() {
127
127
  const { mouseCol } = self;
128
128
  return mouseCol === undefined
129
- ? undefined
130
- : msaCoordToGenomeCoord({ model: self, coord: mouseCol });
129
+ ? []
130
+ : msaCoordToGenomeRegions({ model: self, coord: mouseCol });
131
131
  },
132
132
  /**
133
133
  * #getter
134
- * Genome region under the persistent MSA click selection. Shown
134
+ * Genome regions under the persistent MSA click selection. Shown
135
135
  * regardless of LGV hover, so hovering the genome doesn't hide it.
136
136
  */
137
- get connectedClickHighlight() {
137
+ get connectedClickHighlights() {
138
138
  const { mouseClickCol } = self;
139
139
  return mouseClickCol === undefined
140
- ? undefined
141
- : msaCoordToGenomeCoord({ model: self, coord: mouseClickCol });
140
+ ? []
141
+ : msaCoordToGenomeRegions({ model: self, coord: mouseClickCol });
142
142
  },
143
143
  /**
144
144
  * #getter
145
+ * cross-plugin contract: jbrowse-plugin-mafviewer reads this off the view
146
+ * to draw the same highlights in its own display
145
147
  */
146
148
  get connectedHighlights() {
147
149
  return [
148
- this.connectedHoverHighlight,
149
- this.connectedClickHighlight,
150
- ].filter((r) => r !== undefined);
150
+ ...this.connectedHoverHighlights,
151
+ ...this.connectedClickHighlights,
152
+ ];
151
153
  },
152
154
  }))
153
155
  .actions(self => ({
@@ -1,17 +1,36 @@
1
1
  import type { MafRegion } from './types';
2
- export declare function msaCoordToGenomeCoord({ model, coord: mouseCol, }: {
3
- model: {
4
- querySeqName: string;
5
- transcriptToMsaMap: {
6
- refName: string;
7
- p2g: Record<number, number>;
8
- } | undefined;
9
- mafRegion?: MafRegion;
10
- rows: string[][];
11
- };
12
- coord: number;
13
- }): {
2
+ interface GenomeRegion {
14
3
  refName: string;
15
4
  start: number;
16
5
  end: number;
17
- } | undefined;
6
+ }
7
+ interface CoordModel {
8
+ querySeqName: string;
9
+ transcriptToMsaMap: {
10
+ refName: string;
11
+ p2gCodon: Record<number, number[]>;
12
+ } | undefined;
13
+ mafRegion?: MafRegion;
14
+ rows: string[][];
15
+ }
16
+ /**
17
+ * The genome regions covered by MSA column `coord` of the query row, in 0-based
18
+ * half-open coordinates (what bpToPx and navTo take).
19
+ *
20
+ * Usually one region -- one codon, or one base in a MAF alignment -- but a
21
+ * codon split across an exon boundary yields one region per contiguous piece,
22
+ * which is why this returns a list.
23
+ */
24
+ export declare function msaCoordToGenomeRegions({ model, coord: mouseCol, }: {
25
+ model: CoordModel;
26
+ coord: number;
27
+ }): GenomeRegion[];
28
+ /**
29
+ * A single region spanning the codon at MSA column `coord`, for navigation. For
30
+ * a codon split across an exon boundary this spans the intervening intron.
31
+ */
32
+ export declare function msaCoordToGenomeCoord(args: {
33
+ model: CoordModel;
34
+ coord: number;
35
+ }): GenomeRegion | undefined;
36
+ export {};
@@ -1,27 +1,56 @@
1
+ import { getCodonRanges } from 'g2p_mapper';
1
2
  import { gappedToUngappedPosition } from './structureConnection';
2
- export function msaCoordToGenomeCoord({ model, coord: mouseCol, }) {
3
- const { querySeqName, transcriptToMsaMap, mafRegion } = model;
4
- const querySeq = model.rows.find(f => f[0] === querySeqName)?.[1];
3
+ /**
4
+ * The genome regions covered by MSA column `coord` of the query row, in 0-based
5
+ * half-open coordinates (what bpToPx and navTo take).
6
+ *
7
+ * Usually one region -- one codon, or one base in a MAF alignment -- but a
8
+ * codon split across an exon boundary yields one region per contiguous piece,
9
+ * which is why this returns a list.
10
+ */
11
+ export function msaCoordToGenomeRegions({ model, coord: mouseCol, }) {
12
+ const { querySeqName, transcriptToMsaMap, mafRegion, rows } = model;
13
+ const querySeq = rows.find(f => f[0] === querySeqName)?.[1];
5
14
  if (!querySeq) {
6
- return undefined;
15
+ return [];
7
16
  }
8
17
  const ungappedPos = gappedToUngappedPosition(querySeq, mouseCol);
9
18
  if (ungappedPos === undefined) {
10
- return undefined;
19
+ return [];
11
20
  }
12
21
  if (mafRegion) {
13
22
  const genomePos = mafRegion.start + ungappedPos;
14
23
  return genomePos < mafRegion.end
15
- ? { refName: mafRegion.refName, start: genomePos, end: genomePos + 1 }
16
- : undefined;
24
+ ? [{ refName: mafRegion.refName, start: genomePos, end: genomePos + 1 }]
25
+ : [];
17
26
  }
18
27
  if (transcriptToMsaMap) {
19
- const { refName, p2g } = transcriptToMsaMap;
20
- const s = p2g[ungappedPos];
21
- const e = p2g[ungappedPos + 1];
22
- return s !== undefined && e !== undefined
23
- ? { refName, start: Math.min(s, e), end: Math.max(s, e) }
24
- : undefined;
28
+ const { refName, p2gCodon } = transcriptToMsaMap;
29
+ // p2gCodon holds every genomic base of the codon, so the range is exact on
30
+ // either strand. Deriving it from consecutive p2g entries instead
31
+ // (p2g[pos]..p2g[pos+1]) was off by one base on the reverse strand -- where
32
+ // p2g stores the codon's *highest* coordinate -- dropped the final residue,
33
+ // whose successor has no p2g entry, and spanned the whole intron for a
34
+ // codon split across an exon boundary.
35
+ return (getCodonRanges(p2gCodon, ungappedPos)?.map(([start, end]) => ({
36
+ refName,
37
+ start,
38
+ end,
39
+ })) ?? []);
25
40
  }
26
- return undefined;
41
+ return [];
42
+ }
43
+ /**
44
+ * A single region spanning the codon at MSA column `coord`, for navigation. For
45
+ * a codon split across an exon boundary this spans the intervening intron.
46
+ */
47
+ export function msaCoordToGenomeCoord(args) {
48
+ const regions = msaCoordToGenomeRegions(args);
49
+ const first = regions[0];
50
+ const last = regions.at(-1);
51
+ // getCodonRanges returns ranges sorted ascending, so first.start..last.end
52
+ // bounds the codon
53
+ return first && last
54
+ ? { refName: first.refName, start: first.start, end: last.end }
55
+ : undefined;
27
56
  }
@@ -1,5 +1,14 @@
1
+ import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
1
2
  import { describe, expect, test } from 'vitest';
2
- import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
3
+ import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
4
+ // codon at protein position i covers three consecutive genome bases starting at
5
+ // 100 + i * 3, i.e. a single-exon forward-strand transcript
6
+ function forwardCodons(n) {
7
+ return Object.fromEntries(Array.from({ length: n }, (_, i) => [
8
+ i,
9
+ [100 + i * 3, 101 + i * 3, 102 + i * 3],
10
+ ]));
11
+ }
3
12
  describe('msaCoordToGenomeCoord', () => {
4
13
  test('returns undefined when neither transcriptToMsaMap nor mafRegion is defined', () => {
5
14
  const model = {
@@ -16,7 +25,7 @@ describe('msaCoordToGenomeCoord', () => {
16
25
  querySeqName: 'QUERY',
17
26
  transcriptToMsaMap: {
18
27
  refName: 'chr1',
19
- p2g: { 0: 100, 1: 103 },
28
+ p2gCodon: forwardCodons(2),
20
29
  },
21
30
  rows: [['OTHER', 'MKAA']],
22
31
  };
@@ -28,7 +37,7 @@ describe('msaCoordToGenomeCoord', () => {
28
37
  querySeqName: 'QUERY',
29
38
  transcriptToMsaMap: {
30
39
  refName: 'chr1',
31
- p2g: { 0: 100, 1: 103 },
40
+ p2gCodon: forwardCodons(2),
32
41
  },
33
42
  rows: [['QUERY', 'M-KA']],
34
43
  };
@@ -41,7 +50,7 @@ describe('msaCoordToGenomeCoord', () => {
41
50
  querySeqName: 'QUERY',
42
51
  transcriptToMsaMap: {
43
52
  refName: 'chr1',
44
- p2g: { 0: 100, 1: 103, 2: 106, 3: 109 },
53
+ p2gCodon: forwardCodons(4),
45
54
  },
46
55
  rows: [['QUERY', 'MKAA']],
47
56
  };
@@ -58,7 +67,7 @@ describe('msaCoordToGenomeCoord', () => {
58
67
  querySeqName: 'QUERY',
59
68
  transcriptToMsaMap: {
60
69
  refName: 'chr1',
61
- p2g: { 0: 100, 1: 103, 2: 106, 3: 109 },
70
+ p2gCodon: forwardCodons(4),
62
71
  },
63
72
  rows: [['QUERY', 'M-K-AA']],
64
73
  // 012345 gapped positions
@@ -79,34 +88,33 @@ describe('msaCoordToGenomeCoord', () => {
79
88
  end: 109,
80
89
  });
81
90
  });
82
- test('returns undefined when p2g mapping is incomplete', () => {
91
+ test('returns undefined when the position has no codon mapping', () => {
83
92
  const model = {
84
93
  querySeqName: 'QUERY',
85
94
  transcriptToMsaMap: {
86
95
  refName: 'chr1',
87
- p2g: { 0: 100 }, // Missing entry for position 1
96
+ p2gCodon: forwardCodons(1),
88
97
  },
89
98
  rows: [['QUERY', 'MKAA']],
90
99
  };
91
- // Position 0 needs p2g[0] and p2g[1], but p2g[1] is missing
92
- const result = msaCoordToGenomeCoord({ model, coord: 0 });
100
+ // ungapped position 1 has no entry in p2gCodon
101
+ const result = msaCoordToGenomeCoord({ model, coord: 1 });
93
102
  expect(result).toBeUndefined();
94
103
  });
95
- test('handles reverse strand (start > end in p2g)', () => {
104
+ test('maps the final residue, whose codon has no successor', () => {
96
105
  const model = {
97
106
  querySeqName: 'QUERY',
98
107
  transcriptToMsaMap: {
99
108
  refName: 'chr1',
100
- p2g: { 0: 109, 1: 106, 2: 103, 3: 100 }, // Reverse strand
109
+ p2gCodon: forwardCodons(4),
101
110
  },
102
111
  rows: [['QUERY', 'MKAA']],
103
112
  };
104
- // Should return min/max correctly
105
- const result = msaCoordToGenomeCoord({ model, coord: 0 });
113
+ const result = msaCoordToGenomeCoord({ model, coord: 3 });
106
114
  expect(result).toEqual({
107
115
  refName: 'chr1',
108
- start: 106, // min(109, 106)
109
- end: 109, // max(109, 106)
116
+ start: 109,
117
+ end: 112,
110
118
  });
111
119
  });
112
120
  test('returns undefined for out of bounds coord', () => {
@@ -114,7 +122,7 @@ describe('msaCoordToGenomeCoord', () => {
114
122
  querySeqName: 'QUERY',
115
123
  transcriptToMsaMap: {
116
124
  refName: 'chr1',
117
- p2g: { 0: 100, 1: 103 },
125
+ p2gCodon: forwardCodons(2),
118
126
  },
119
127
  rows: [['QUERY', 'MK']],
120
128
  };
@@ -127,7 +135,7 @@ describe('msaCoordToGenomeCoord', () => {
127
135
  querySeqName: 'SEQ2',
128
136
  transcriptToMsaMap: {
129
137
  refName: 'chr1',
130
- p2g: { 0: 200, 1: 203 },
138
+ p2gCodon: { 0: [200, 201, 202], 1: [203, 204, 205] },
131
139
  },
132
140
  rows: [
133
141
  ['SEQ1', 'AAAA'],
@@ -142,6 +150,88 @@ describe('msaCoordToGenomeCoord', () => {
142
150
  end: 203,
143
151
  });
144
152
  });
153
+ // The mapping comes from the real g2p_mapper rather than hand-written
154
+ // fixtures: on the reverse strand p2g stores the codon's *highest*
155
+ // coordinate, which is what the old p2g[pos]..p2g[pos+1] arithmetic got wrong
156
+ describe('real g2p_mapper mappings', () => {
157
+ test('forward strand, single exon', () => {
158
+ const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
159
+ refName: 'chr1',
160
+ start: 100,
161
+ end: 112,
162
+ strand: 1,
163
+ subfeatures: [{ refName: 'chr1', type: 'CDS', start: 100, end: 112 }],
164
+ });
165
+ const model = {
166
+ querySeqName: 'QUERY',
167
+ transcriptToMsaMap: { refName, p2gCodon },
168
+ rows: [['QUERY', 'MKAA']],
169
+ };
170
+ expect(msaCoordToGenomeCoord({ model, coord: 0 })).toEqual({
171
+ refName: 'chr1',
172
+ start: 100,
173
+ end: 103,
174
+ });
175
+ expect(msaCoordToGenomeCoord({ model, coord: 3 })).toEqual({
176
+ refName: 'chr1',
177
+ start: 109,
178
+ end: 112,
179
+ });
180
+ });
181
+ test('reverse strand codon covers the last three bases of the CDS', () => {
182
+ const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
183
+ refName: 'chr1',
184
+ start: 100,
185
+ end: 112,
186
+ strand: -1,
187
+ subfeatures: [{ refName: 'chr1', type: 'CDS', start: 100, end: 112 }],
188
+ });
189
+ const model = {
190
+ querySeqName: 'QUERY',
191
+ transcriptToMsaMap: { refName, p2gCodon },
192
+ rows: [['QUERY', 'MKAA']],
193
+ };
194
+ // the first residue is translated from the 3' end of the genome region
195
+ expect(msaCoordToGenomeCoord({ model, coord: 0 })).toEqual({
196
+ refName: 'chr1',
197
+ start: 109,
198
+ end: 112,
199
+ });
200
+ expect(msaCoordToGenomeCoord({ model, coord: 3 })).toEqual({
201
+ refName: 'chr1',
202
+ start: 100,
203
+ end: 103,
204
+ });
205
+ });
206
+ test('codon split across an exon boundary yields one region per piece', () => {
207
+ // exon 1 contributes 4 bases, so residue 1 straddles the intron
208
+ const { p2gCodon, refName } = genomeToTranscriptSeqMapping({
209
+ refName: 'chr1',
210
+ start: 100,
211
+ end: 210,
212
+ strand: 1,
213
+ subfeatures: [
214
+ { refName: 'chr1', type: 'CDS', start: 100, end: 104 },
215
+ { refName: 'chr1', type: 'CDS', start: 200, end: 202 },
216
+ ],
217
+ });
218
+ const model = {
219
+ querySeqName: 'QUERY',
220
+ transcriptToMsaMap: { refName, p2gCodon },
221
+ rows: [['QUERY', 'MK']],
222
+ };
223
+ expect(msaCoordToGenomeRegions({ model, coord: 1 })).toEqual([
224
+ { refName: 'chr1', start: 103, end: 104 },
225
+ { refName: 'chr1', start: 200, end: 202 },
226
+ ]);
227
+ // the single-region form bounds the pieces, for navigation
228
+ expect(msaCoordToGenomeCoord({ model, coord: 1 })).toEqual({
229
+ refName: 'chr1',
230
+ start: 103,
231
+ end: 202,
232
+ });
233
+ });
234
+ });
145
235
  // MAF region tests
146
236
  describe('mafRegion', () => {
147
237
  test('returns genome position for mafRegion mapping', () => {
@@ -217,7 +307,7 @@ describe('msaCoordToGenomeCoord', () => {
217
307
  querySeqName: 'hg38.chr1',
218
308
  transcriptToMsaMap: {
219
309
  refName: 'chr2',
220
- p2g: { 0: 5000, 1: 5003 },
310
+ p2gCodon: { 0: [5000, 5001, 5002], 1: [5003, 5004, 5005] },
221
311
  },
222
312
  mafRegion: {
223
313
  refName: 'chr1',