jbrowse-plugin-msaview 2.2.2 → 2.2.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.
Files changed (162) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +229 -0
  3. package/dist/AddHighlightModel/GenomeMouseoverHighlight.js +23 -18
  4. package/dist/AddHighlightModel/GenomeMouseoverHighlight.js.map +1 -1
  5. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +23 -13
  6. package/dist/AddHighlightModel/MsaToGenomeHighlight.js.map +1 -1
  7. package/dist/AddHighlightModel/index.js +8 -1
  8. package/dist/AddHighlightModel/index.js.map +1 -1
  9. package/dist/AddHighlightModel/util.d.ts +2 -2
  10. package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +2 -2
  11. package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js +5 -11
  12. package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js.map +1 -1
  13. package/dist/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.js +5 -1
  14. package/dist/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.js.map +1 -1
  15. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +16 -16
  16. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js.map +1 -1
  17. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +38 -46
  18. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js.map +1 -1
  19. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +4 -3
  20. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +4 -3
  21. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js.map +1 -1
  22. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.d.ts +9 -0
  23. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +76 -0
  24. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js.map +1 -0
  25. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js +35 -13
  26. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js.map +1 -1
  27. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js +6 -12
  28. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js.map +1 -1
  29. package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.d.ts +6 -0
  30. package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js +15 -0
  31. package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js.map +1 -1
  32. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +12 -34
  33. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js.map +1 -1
  34. package/dist/LaunchMsaView/components/PreLoadedMSA/consts.d.ts +1 -0
  35. package/dist/LaunchMsaView/components/PreLoadedMSA/consts.js +1 -0
  36. package/dist/LaunchMsaView/components/PreLoadedMSA/consts.js.map +1 -1
  37. package/dist/LaunchMsaView/components/TabPanel.d.ts +2 -2
  38. package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +2 -2
  39. package/dist/LaunchMsaView/components/TranscriptSelector.js +3 -6
  40. package/dist/LaunchMsaView/components/TranscriptSelector.js.map +1 -1
  41. package/dist/LaunchMsaView/components/useSWRFeatureSequence.js +6 -4
  42. package/dist/LaunchMsaView/components/useSWRFeatureSequence.js.map +1 -1
  43. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +16 -0
  44. package/dist/LaunchMsaView/components/useTranscriptSelection.js +31 -0
  45. package/dist/LaunchMsaView/components/useTranscriptSelection.js.map +1 -0
  46. package/dist/LaunchMsaView/components/util.d.ts +3 -1
  47. package/dist/LaunchMsaView/components/util.js +12 -2
  48. package/dist/LaunchMsaView/components/util.js.map +1 -1
  49. package/dist/LaunchMsaView/util.d.ts +2 -0
  50. package/dist/LaunchMsaView/util.js +16 -4
  51. package/dist/LaunchMsaView/util.js.map +1 -1
  52. package/dist/LaunchMsaViewExtensionPoint/index.d.ts +2 -0
  53. package/dist/LaunchMsaViewExtensionPoint/index.js +31 -0
  54. package/dist/LaunchMsaViewExtensionPoint/index.js.map +1 -0
  55. package/dist/MsaViewPanel/components/ConnectStructureDialog.d.ts +7 -0
  56. package/dist/MsaViewPanel/components/ConnectStructureDialog.js +56 -0
  57. package/dist/MsaViewPanel/components/ConnectStructureDialog.js.map +1 -0
  58. package/dist/MsaViewPanel/components/MsaViewPanel.js +4 -2
  59. package/dist/MsaViewPanel/components/MsaViewPanel.js.map +1 -1
  60. package/dist/MsaViewPanel/doLaunchBlast.d.ts +1 -0
  61. package/dist/MsaViewPanel/doLaunchBlast.js +65 -19
  62. package/dist/MsaViewPanel/doLaunchBlast.js.map +1 -1
  63. package/dist/MsaViewPanel/genomeToMSA.d.ts +6 -0
  64. package/dist/MsaViewPanel/genomeToMSA.js +38 -8
  65. package/dist/MsaViewPanel/genomeToMSA.js.map +1 -1
  66. package/dist/MsaViewPanel/genomeToMSA.test.d.ts +1 -0
  67. package/dist/MsaViewPanel/genomeToMSA.test.js +244 -0
  68. package/dist/MsaViewPanel/genomeToMSA.test.js.map +1 -0
  69. package/dist/MsaViewPanel/model.d.ts +727 -226
  70. package/dist/MsaViewPanel/model.js +496 -52
  71. package/dist/MsaViewPanel/model.js.map +1 -1
  72. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +10 -2
  73. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +26 -27
  74. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js.map +1 -1
  75. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.d.ts +1 -0
  76. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +240 -0
  77. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js.map +1 -0
  78. package/dist/MsaViewPanel/msaDataStore.d.ts +14 -0
  79. package/dist/MsaViewPanel/msaDataStore.js +197 -0
  80. package/dist/MsaViewPanel/msaDataStore.js.map +1 -0
  81. package/dist/MsaViewPanel/pairwiseAlignment.d.ts +27 -0
  82. package/dist/MsaViewPanel/pairwiseAlignment.js +776 -0
  83. package/dist/MsaViewPanel/pairwiseAlignment.js.map +1 -0
  84. package/dist/MsaViewPanel/pairwiseAlignment.test.d.ts +1 -0
  85. package/dist/MsaViewPanel/pairwiseAlignment.test.js +112 -0
  86. package/dist/MsaViewPanel/pairwiseAlignment.test.js.map +1 -0
  87. package/dist/MsaViewPanel/structureConnection.d.ts +27 -0
  88. package/dist/MsaViewPanel/structureConnection.js +46 -0
  89. package/dist/MsaViewPanel/structureConnection.js.map +1 -0
  90. package/dist/MsaViewPanel/structureConnection.test.d.ts +1 -0
  91. package/dist/MsaViewPanel/structureConnection.test.js +122 -0
  92. package/dist/MsaViewPanel/structureConnection.test.js.map +1 -0
  93. package/dist/MsaViewPanel/types.d.ts +13 -0
  94. package/dist/MsaViewPanel/types.js +2 -0
  95. package/dist/MsaViewPanel/types.js.map +1 -0
  96. package/dist/MsaViewPanel/util.d.ts +7 -0
  97. package/dist/MsaViewPanel/util.js +10 -0
  98. package/dist/MsaViewPanel/util.js.map +1 -1
  99. package/dist/index.d.ts +5 -5
  100. package/dist/index.js +3 -1
  101. package/dist/index.js.map +1 -1
  102. package/dist/jbrowse-plugin-msaview.umd.production.min.js +39 -90
  103. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  104. package/dist/utils/blastCache.d.ts +34 -0
  105. package/dist/utils/blastCache.js +58 -0
  106. package/dist/utils/blastCache.js.map +1 -0
  107. package/dist/utils/fetch.d.ts +1 -1
  108. package/dist/utils/fetch.js +1 -1
  109. package/dist/utils/fetch.js.map +1 -1
  110. package/dist/utils/ncbiBlast.d.ts +1 -5
  111. package/dist/utils/taxonomyNames.d.ts +5 -0
  112. package/dist/utils/taxonomyNames.js +79 -0
  113. package/dist/utils/taxonomyNames.js.map +1 -0
  114. package/dist/utils/types.d.ts +8 -5
  115. package/package.json +50 -54
  116. package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +37 -21
  117. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +38 -17
  118. package/src/AddHighlightModel/index.tsx +9 -4
  119. package/src/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.tsx +13 -13
  120. package/src/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.ts +6 -0
  121. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +30 -23
  122. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +64 -51
  123. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +9 -6
  124. package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +146 -0
  125. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx +53 -22
  126. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.tsx +8 -13
  127. package/src/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.ts +25 -0
  128. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +27 -47
  129. package/src/LaunchMsaView/components/PreLoadedMSA/consts.ts +1 -0
  130. package/src/LaunchMsaView/components/TabPanel.tsx +2 -2
  131. package/src/LaunchMsaView/components/TranscriptSelector.tsx +13 -20
  132. package/src/LaunchMsaView/components/useSWRFeatureSequence.ts +5 -5
  133. package/src/LaunchMsaView/components/useTranscriptSelection.ts +48 -0
  134. package/src/LaunchMsaView/components/util.ts +17 -2
  135. package/src/LaunchMsaView/index.ts +1 -1
  136. package/src/LaunchMsaView/util.ts +25 -6
  137. package/src/LaunchMsaViewExtensionPoint/index.ts +74 -0
  138. package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +156 -0
  139. package/src/MsaViewPanel/components/MsaViewPanel.tsx +6 -1
  140. package/src/MsaViewPanel/doLaunchBlast.ts +83 -23
  141. package/src/MsaViewPanel/genomeToMSA.test.ts +281 -0
  142. package/src/MsaViewPanel/genomeToMSA.ts +43 -10
  143. package/src/MsaViewPanel/model.ts +617 -58
  144. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +256 -0
  145. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +42 -30
  146. package/src/MsaViewPanel/msaDataStore.ts +236 -0
  147. package/src/MsaViewPanel/pairwiseAlignment.test.ts +140 -0
  148. package/src/MsaViewPanel/pairwiseAlignment.ts +818 -0
  149. package/src/MsaViewPanel/structureConnection.test.ts +143 -0
  150. package/src/MsaViewPanel/structureConnection.ts +72 -0
  151. package/src/MsaViewPanel/types.ts +14 -0
  152. package/src/MsaViewPanel/util.ts +11 -0
  153. package/src/index.ts +3 -1
  154. package/src/utils/blastCache.ts +114 -0
  155. package/src/utils/fetch.ts +1 -1
  156. package/src/utils/taxonomyNames.ts +111 -0
  157. package/src/utils/types.ts +9 -1
  158. package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.d.ts +0 -5
  159. package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.js +0 -16
  160. package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.js.map +0 -1
  161. package/dist/out.js +0 -55367
  162. package/src/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.ts +0 -25
@@ -1,7 +1,13 @@
1
1
  import { JBrowsePluginMsaViewModel } from './model'
2
2
  import { makeId, strip } from '../LaunchMsaView/components/util'
3
+ import { cleanProteinSequence } from '../LaunchMsaView/util'
4
+ import { saveBlastResult } from '../utils/blastCache'
3
5
  import { launchMSA } from '../utils/msa'
4
6
  import { queryBlast } from '../utils/ncbiBlast'
7
+ import { fetchTaxonomyInfo } from '../utils/taxonomyNames'
8
+
9
+ import type { TaxonomyInfo } from '../utils/taxonomyNames'
10
+ import type { BlastHitDescription } from '../utils/types'
5
11
 
6
12
  export async function doLaunchBlast({
7
13
  self,
@@ -14,42 +20,96 @@ export async function doLaunchBlast({
14
20
  blastProgram,
15
21
  msaAlgorithm,
16
22
  proteinSequence,
23
+ selectedTranscript,
17
24
  } = self.blastParams!
18
- const { hits } = await queryBlast({
19
- query: proteinSequence.replaceAll('*', '').replaceAll('&', ''),
25
+ const cleanedSeq = cleanProteinSequence(proteinSequence)
26
+
27
+ const { hits, rid } = await queryBlast({
28
+ query: cleanedSeq,
20
29
  blastDatabase,
21
30
  blastProgram,
22
31
  baseUrl,
23
32
  onProgress: arg => {
24
33
  self.setProgress(arg)
25
34
  },
26
- onRid: rid => {
27
- self.setRid(rid)
35
+ onRid: r => {
36
+ self.setRid(r)
28
37
  },
29
38
  })
30
39
 
31
- return launchMSA({
40
+ self.setProgress('Fetching species taxonomy info...')
41
+ const taxids = hits
42
+ .map(h => h.description[0]?.taxid)
43
+ .filter((t): t is number => t !== undefined)
44
+ const taxonomyInfo = await fetchTaxonomyInfo(taxids)
45
+
46
+ const treeMetadata: Record<string, Record<string, string>> = {}
47
+
48
+ const sequences = hits.map(h => {
49
+ const desc = h.description[0] ?? {
50
+ accession: 'unknown',
51
+ id: 'unknown',
52
+ sciname: 'unknown',
53
+ }
54
+ const rowName = makeId(desc, taxonomyInfo)
55
+ const seq = strip(h.hsps[0]?.hseq ?? '')
56
+
57
+ treeMetadata[rowName] = buildRowMetadata(desc, taxonomyInfo)
58
+
59
+ return `>${rowName}\n${seq}`
60
+ })
61
+
62
+ const result = await launchMSA({
32
63
  algorithm: msaAlgorithm,
33
- sequence: [
34
- `>QUERY\n${proteinSequence.replaceAll('*', '').replaceAll('&', '')}`,
35
- ...hits
36
- .map(
37
- h =>
38
- [
39
- makeId(
40
- h.description[0] ?? {
41
- accession: 'unknown',
42
- id: 'unknown',
43
- sciname: 'unknown',
44
- },
45
- ),
46
- strip(h.hsps[0]?.hseq ?? ''),
47
- ] as const,
48
- )
49
- .map(([id, seq]) => `>${id}\n${seq}`),
50
- ].join('\n'),
64
+ sequence: [`>QUERY\n${cleanedSeq}`, ...sequences].join('\n'),
51
65
  onProgress: arg => {
52
66
  self.setProgress(arg)
53
67
  },
54
68
  })
69
+
70
+ const treeMetadataJson = JSON.stringify(treeMetadata)
71
+
72
+ await saveBlastResult({
73
+ proteinSequence: cleanedSeq,
74
+ blastDatabase,
75
+ blastProgram,
76
+ msaAlgorithm,
77
+ msa: result.msa,
78
+ tree: result.tree,
79
+ treeMetadata: treeMetadataJson,
80
+ rid,
81
+ geneId: selectedTranscript?.get('parentId'),
82
+ transcriptId: selectedTranscript?.get('id'),
83
+ })
84
+
85
+ return {
86
+ ...result,
87
+ treeMetadata: treeMetadataJson,
88
+ }
89
+ }
90
+
91
+ function buildRowMetadata(
92
+ desc: BlastHitDescription,
93
+ taxonomyInfo: Map<number, TaxonomyInfo>,
94
+ ) {
95
+ const metadata: Record<string, string> = {}
96
+ const taxInfo = desc.taxid ? taxonomyInfo.get(desc.taxid) : undefined
97
+
98
+ if (taxInfo?.sciname) {
99
+ metadata['Scientific name'] = taxInfo.sciname
100
+ }
101
+ if (taxInfo?.commonName) {
102
+ metadata['Common name'] = taxInfo.commonName
103
+ }
104
+ if (desc.accession) {
105
+ metadata.Accession = desc.accession
106
+ }
107
+ if (desc.id) {
108
+ metadata.ID = desc.id
109
+ }
110
+ if (desc.title) {
111
+ metadata.Description = desc.title
112
+ }
113
+
114
+ return metadata
55
115
  }
@@ -0,0 +1,281 @@
1
+ import { getSession } from '@jbrowse/core/util'
2
+ import { beforeEach, describe, expect, test, vi } from 'vitest'
3
+
4
+ import { genomeToMSA } from './genomeToMSA'
5
+
6
+ // Mock getSession
7
+ vi.mock('@jbrowse/core/util', () => ({
8
+ getSession: vi.fn(),
9
+ }))
10
+
11
+ const mockGetSession = vi.mocked(getSession)
12
+
13
+ describe('genomeToMSA', () => {
14
+ beforeEach(() => {
15
+ vi.clearAllMocks()
16
+ })
17
+
18
+ test('returns undefined when connectedView is not initialized', () => {
19
+ mockGetSession.mockReturnValue({
20
+ hovered: {
21
+ hoverFeature: {},
22
+ hoverPosition: { coord: 1005, refName: 'chr1' },
23
+ },
24
+ } as any)
25
+
26
+ const model = {
27
+ querySeqName: 'hg38.chr1',
28
+ transcriptToMsaMap: undefined,
29
+ mafRegion: {
30
+ refName: 'chr1',
31
+ start: 1000,
32
+ end: 1010,
33
+ assemblyName: 'hg38',
34
+ },
35
+ connectedView: { initialized: false },
36
+ seqPosToVisibleCol: vi.fn(),
37
+ } as any
38
+
39
+ const result = genomeToMSA({ model })
40
+ expect(result).toBeUndefined()
41
+ })
42
+
43
+ test('returns undefined when hovered is not valid', () => {
44
+ mockGetSession.mockReturnValue({
45
+ hovered: null,
46
+ } as any)
47
+
48
+ const model = {
49
+ querySeqName: 'hg38.chr1',
50
+ transcriptToMsaMap: undefined,
51
+ mafRegion: {
52
+ refName: 'chr1',
53
+ start: 1000,
54
+ end: 1010,
55
+ assemblyName: 'hg38',
56
+ },
57
+ connectedView: { initialized: true },
58
+ seqPosToVisibleCol: vi.fn(),
59
+ } as any
60
+
61
+ const result = genomeToMSA({ model })
62
+ expect(result).toBeUndefined()
63
+ })
64
+
65
+ describe('mafRegion mapping', () => {
66
+ test('returns visible column for valid hover within mafRegion', () => {
67
+ mockGetSession.mockReturnValue({
68
+ hovered: {
69
+ hoverFeature: {},
70
+ hoverPosition: { coord: 1005, refName: 'chr1' },
71
+ },
72
+ } as any)
73
+
74
+ const mockSeqPosToVisibleCol = vi.fn().mockReturnValue(5)
75
+
76
+ const model = {
77
+ querySeqName: 'hg38.chr1',
78
+ transcriptToMsaMap: undefined,
79
+ mafRegion: {
80
+ refName: 'chr1',
81
+ start: 1000,
82
+ end: 1010,
83
+ assemblyName: 'hg38',
84
+ },
85
+ connectedView: {
86
+ initialized: true,
87
+ assemblyNames: ['hg38'],
88
+ },
89
+ seqPosToVisibleCol: mockSeqPosToVisibleCol,
90
+ } as any
91
+
92
+ const result = genomeToMSA({ model })
93
+
94
+ // coord 1005 - start 1000 = ungapped position 5
95
+ expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 5)
96
+ expect(result).toBe(5)
97
+ })
98
+
99
+ test('returns undefined when hover refName does not match mafRegion', () => {
100
+ mockGetSession.mockReturnValue({
101
+ hovered: {
102
+ hoverFeature: {},
103
+ hoverPosition: { coord: 1005, refName: 'chr2' },
104
+ },
105
+ } as any)
106
+
107
+ const model = {
108
+ querySeqName: 'hg38.chr1',
109
+ transcriptToMsaMap: undefined,
110
+ mafRegion: {
111
+ refName: 'chr1',
112
+ start: 1000,
113
+ end: 1010,
114
+ assemblyName: 'hg38',
115
+ },
116
+ connectedView: {
117
+ initialized: true,
118
+ assemblyNames: ['hg38'],
119
+ },
120
+ seqPosToVisibleCol: vi.fn(),
121
+ } as any
122
+
123
+ const result = genomeToMSA({ model })
124
+ expect(result).toBeUndefined()
125
+ })
126
+
127
+ test('returns undefined when hover coord is before mafRegion start', () => {
128
+ mockGetSession.mockReturnValue({
129
+ hovered: {
130
+ hoverFeature: {},
131
+ hoverPosition: { coord: 999, refName: 'chr1' },
132
+ },
133
+ } as any)
134
+
135
+ const model = {
136
+ querySeqName: 'hg38.chr1',
137
+ transcriptToMsaMap: undefined,
138
+ mafRegion: {
139
+ refName: 'chr1',
140
+ start: 1000,
141
+ end: 1010,
142
+ assemblyName: 'hg38',
143
+ },
144
+ connectedView: {
145
+ initialized: true,
146
+ assemblyNames: ['hg38'],
147
+ },
148
+ seqPosToVisibleCol: vi.fn(),
149
+ } as any
150
+
151
+ const result = genomeToMSA({ model })
152
+ expect(result).toBeUndefined()
153
+ })
154
+
155
+ test('returns undefined when hover coord is at or after mafRegion end', () => {
156
+ mockGetSession.mockReturnValue({
157
+ hovered: {
158
+ hoverFeature: {},
159
+ hoverPosition: { coord: 1010, refName: 'chr1' },
160
+ },
161
+ } as any)
162
+
163
+ const model = {
164
+ querySeqName: 'hg38.chr1',
165
+ transcriptToMsaMap: undefined,
166
+ mafRegion: {
167
+ refName: 'chr1',
168
+ start: 1000,
169
+ end: 1010,
170
+ assemblyName: 'hg38',
171
+ },
172
+ connectedView: {
173
+ initialized: true,
174
+ assemblyNames: ['hg38'],
175
+ },
176
+ seqPosToVisibleCol: vi.fn(),
177
+ } as any
178
+
179
+ const result = genomeToMSA({ model })
180
+ expect(result).toBeUndefined()
181
+ })
182
+
183
+ test('returns undefined when assembly does not match', () => {
184
+ mockGetSession.mockReturnValue({
185
+ hovered: {
186
+ hoverFeature: {},
187
+ hoverPosition: { coord: 1005, refName: 'chr1' },
188
+ },
189
+ } as any)
190
+
191
+ const model = {
192
+ querySeqName: 'hg38.chr1',
193
+ transcriptToMsaMap: undefined,
194
+ mafRegion: {
195
+ refName: 'chr1',
196
+ start: 1000,
197
+ end: 1010,
198
+ assemblyName: 'hg38',
199
+ },
200
+ connectedView: {
201
+ initialized: true,
202
+ assemblyNames: ['mm39'], // Different assembly
203
+ },
204
+ seqPosToVisibleCol: vi.fn(),
205
+ } as any
206
+
207
+ const result = genomeToMSA({ model })
208
+ expect(result).toBeUndefined()
209
+ })
210
+ })
211
+
212
+ describe('transcriptToMsaMap mapping (original behavior)', () => {
213
+ test('returns visible column using g2p mapping', () => {
214
+ mockGetSession.mockReturnValue({
215
+ hovered: {
216
+ hoverFeature: {},
217
+ hoverPosition: { coord: 1005, refName: 'chr1' },
218
+ },
219
+ } as any)
220
+
221
+ const mockSeqPosToVisibleCol = vi.fn().mockReturnValue(10)
222
+
223
+ const model = {
224
+ querySeqName: 'QUERY',
225
+ transcriptToMsaMap: {
226
+ g2p: { 1005: 10 },
227
+ },
228
+ mafRegion: undefined,
229
+ connectedView: { initialized: true },
230
+ seqPosToVisibleCol: mockSeqPosToVisibleCol,
231
+ } as any
232
+
233
+ const result = genomeToMSA({ model })
234
+
235
+ expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('QUERY', 10)
236
+ expect(result).toBe(10)
237
+ })
238
+
239
+ test('returns undefined when g2p has no mapping for coord', () => {
240
+ mockGetSession.mockReturnValue({
241
+ hovered: {
242
+ hoverFeature: {},
243
+ hoverPosition: { coord: 1005, refName: 'chr1' },
244
+ },
245
+ } as any)
246
+
247
+ const model = {
248
+ querySeqName: 'QUERY',
249
+ transcriptToMsaMap: {
250
+ g2p: { 1000: 0 }, // No entry for 1005
251
+ },
252
+ mafRegion: undefined,
253
+ connectedView: { initialized: true },
254
+ seqPosToVisibleCol: vi.fn(),
255
+ } as any
256
+
257
+ const result = genomeToMSA({ model })
258
+ expect(result).toBeUndefined()
259
+ })
260
+ })
261
+
262
+ test('returns undefined when neither mafRegion nor transcriptToMsaMap is set', () => {
263
+ mockGetSession.mockReturnValue({
264
+ hovered: {
265
+ hoverFeature: {},
266
+ hoverPosition: { coord: 1005, refName: 'chr1' },
267
+ },
268
+ } as any)
269
+
270
+ const model = {
271
+ querySeqName: 'QUERY',
272
+ transcriptToMsaMap: undefined,
273
+ mafRegion: undefined,
274
+ connectedView: { initialized: true },
275
+ seqPosToVisibleCol: vi.fn(),
276
+ } as any
277
+
278
+ const result = genomeToMSA({ model })
279
+ expect(result).toBeUndefined()
280
+ })
281
+ })
@@ -3,20 +3,53 @@ import { getSession } from '@jbrowse/core/util'
3
3
  import { JBrowsePluginMsaViewModel } from './model'
4
4
  import { checkHovered } from './util'
5
5
 
6
+ /**
7
+ * Convert a genome coordinate from session.hovered to a visible MSA column.
8
+ *
9
+ * @param model - The MSA view model
10
+ * @returns The visible column index, or undefined if no mapping exists
11
+ */
6
12
  export function genomeToMSA({ model }: { model: JBrowsePluginMsaViewModel }) {
7
13
  const { hovered } = getSession(model)
8
- const { querySeqName, transcriptToMsaMap, connectedView } = model
9
- if (
10
- connectedView?.initialized &&
11
- transcriptToMsaMap &&
12
- checkHovered(hovered)
13
- ) {
14
- const { coord: hoverCoord } = hovered.hoverPosition
14
+ const { querySeqName, transcriptToMsaMap, connectedView, mafRegion } = model
15
+
16
+ if (!connectedView?.initialized || !checkHovered(hovered)) {
17
+ return undefined
18
+ }
19
+
20
+ const { coord: hoverCoord, refName } = hovered.hoverPosition
21
+
22
+ // Handle MAF region mapping
23
+ if (mafRegion) {
24
+ // Check if the hover is on the same refName as the MAF region
25
+ if (refName !== mafRegion.refName) {
26
+ return undefined
27
+ }
28
+ // Check if we're on the same assembly (if assembly info is available)
29
+ const viewAssemblies = connectedView.assemblyNames
30
+ if (!viewAssemblies.includes(mafRegion.assemblyName)) {
31
+ return undefined
32
+ }
33
+ // Check if the hover coordinate is within the MAF region
34
+ if (hoverCoord < mafRegion.start || hoverCoord >= mafRegion.end) {
35
+ return undefined
36
+ }
37
+ // Calculate the ungapped position relative to the region start
38
+ const ungappedPos = hoverCoord - mafRegion.start
39
+ // Convert to visible column using the query sequence
40
+ return model.seqPosToVisibleCol(querySeqName, ungappedPos)
41
+ }
42
+
43
+ // Handle transcript mapping (original behavior)
44
+ if (transcriptToMsaMap) {
15
45
  const { g2p } = transcriptToMsaMap
16
- const ret = g2p[hoverCoord]
17
- if (ret !== undefined) {
18
- return model.seqCoordToRowSpecificGlobalCoord(querySeqName, ret + 1)
46
+ // g2p maps genome coordinate to sequence position (0-based)
47
+ const seqPos = g2p[hoverCoord]
48
+ if (seqPos !== undefined) {
49
+ // Convert sequence position to visible column
50
+ return model.seqPosToVisibleCol(querySeqName, seqPos)
19
51
  }
20
52
  }
53
+
21
54
  return undefined
22
55
  }