jbrowse-plugin-mafviewer 1.4.3 → 1.4.6

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 (204) hide show
  1. package/README.md +1 -1
  2. package/dist/BigMafAdapter/BigMafAdapter.js +4 -5
  3. package/dist/BigMafAdapter/BigMafAdapter.js.map +1 -1
  4. package/dist/BigMafAdapter/configSchema.d.ts +2 -2
  5. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js +39 -109
  6. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js.map +1 -1
  7. package/dist/LinearMafDisplay/components/MAFTooltip.d.ts +0 -3
  8. package/dist/LinearMafDisplay/components/MAFTooltip.js.map +1 -1
  9. package/dist/LinearMafDisplay/components/MsaHighlightOverlay.d.ts +9 -0
  10. package/dist/LinearMafDisplay/components/MsaHighlightOverlay.js +34 -0
  11. package/dist/LinearMafDisplay/components/MsaHighlightOverlay.js.map +1 -0
  12. package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js +2 -2
  13. package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js.map +1 -1
  14. package/dist/LinearMafDisplay/components/Sidebar/RectBg.d.ts +1 -1
  15. package/dist/LinearMafDisplay/components/Sidebar/RectBg.js +2 -3
  16. package/dist/LinearMafDisplay/components/Sidebar/RectBg.js.map +1 -1
  17. package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js +81 -11
  18. package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js.map +1 -1
  19. package/dist/LinearMafDisplay/components/Sidebar/Tree.js +30 -9
  20. package/dist/LinearMafDisplay/components/Sidebar/Tree.js.map +1 -1
  21. package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.d.ts +0 -1
  22. package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.js.map +1 -1
  23. package/dist/LinearMafDisplay/components/useDragSelection.d.ts +25 -0
  24. package/dist/LinearMafDisplay/components/useDragSelection.js +103 -0
  25. package/dist/LinearMafDisplay/components/useDragSelection.js.map +1 -0
  26. package/dist/LinearMafDisplay/configSchema.d.ts +3 -30
  27. package/dist/LinearMafDisplay/renderSvg.js +1 -1
  28. package/dist/LinearMafDisplay/renderSvg.js.map +1 -1
  29. package/dist/LinearMafDisplay/stateModel.d.ts +1090 -102
  30. package/dist/LinearMafDisplay/stateModel.js +156 -17
  31. package/dist/LinearMafDisplay/stateModel.js.map +1 -1
  32. package/dist/LinearMafDisplay/types.d.ts +2 -2
  33. package/dist/LinearMafDisplay/util.d.ts +6 -0
  34. package/dist/LinearMafDisplay/util.js +28 -6
  35. package/dist/LinearMafDisplay/util.js.map +1 -1
  36. package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +43 -10
  37. package/dist/LinearMafRenderer/LinearMafRenderer.js +1 -1
  38. package/dist/LinearMafRenderer/LinearMafRenderer.js.map +1 -1
  39. package/dist/LinearMafRenderer/components/LinearMafRendering.d.ts +14 -5
  40. package/dist/LinearMafRenderer/components/LinearMafRendering.js +40 -20
  41. package/dist/LinearMafRenderer/components/LinearMafRendering.js.map +1 -1
  42. package/dist/LinearMafRenderer/configSchema.d.ts +1 -6
  43. package/dist/LinearMafRenderer/configSchema.js +1 -6
  44. package/dist/LinearMafRenderer/configSchema.js.map +1 -1
  45. package/dist/LinearMafRenderer/makeImageData.js +6 -7
  46. package/dist/LinearMafRenderer/makeImageData.js.map +1 -1
  47. package/dist/LinearMafRenderer/rendering/features.d.ts +0 -1
  48. package/dist/LinearMafRenderer/rendering/features.js +1 -14
  49. package/dist/LinearMafRenderer/rendering/features.js.map +1 -1
  50. package/dist/LinearMafRenderer/rendering/insertions.d.ts +1 -1
  51. package/dist/LinearMafRenderer/rendering/insertions.js +10 -8
  52. package/dist/LinearMafRenderer/rendering/insertions.js.map +1 -1
  53. package/dist/LinearMafRenderer/rendering/matches.d.ts +1 -1
  54. package/dist/LinearMafRenderer/rendering/matches.js +3 -15
  55. package/dist/LinearMafRenderer/rendering/matches.js.map +1 -1
  56. package/dist/LinearMafRenderer/rendering/mismatches.d.ts +1 -1
  57. package/dist/LinearMafRenderer/rendering/mismatches.js +3 -3
  58. package/dist/LinearMafRenderer/rendering/spatialIndex.js +8 -2
  59. package/dist/LinearMafRenderer/rendering/spatialIndex.js.map +1 -1
  60. package/dist/LinearMafRenderer/rendering/text.js +1 -3
  61. package/dist/LinearMafRenderer/rendering/text.js.map +1 -1
  62. package/dist/LinearMafRenderer/rendering/types.d.ts +6 -5
  63. package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js +1 -1
  64. package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js.map +1 -1
  65. package/dist/MafAddTrackWorkflow/index.js +1 -1
  66. package/dist/MafAddTrackWorkflow/index.js.map +1 -1
  67. package/dist/MafGetSequences/MafGetSequences.d.ts +1 -0
  68. package/dist/MafGetSequences/MafGetSequences.js +2 -1
  69. package/dist/MafGetSequences/MafGetSequences.js.map +1 -1
  70. package/dist/MafSequenceWidget/LabelsCanvas.d.ts +8 -0
  71. package/dist/MafSequenceWidget/LabelsCanvas.js +37 -0
  72. package/dist/MafSequenceWidget/LabelsCanvas.js.map +1 -0
  73. package/dist/MafSequenceWidget/MafSequenceHoverHighlight.d.ts +6 -0
  74. package/dist/MafSequenceWidget/MafSequenceHoverHighlight.js +52 -0
  75. package/dist/MafSequenceWidget/MafSequenceHoverHighlight.js.map +1 -0
  76. package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.d.ts +2 -0
  77. package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.js +12 -0
  78. package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.js.map +1 -0
  79. package/dist/MafSequenceWidget/MafSequenceWidget.d.ts +6 -0
  80. package/dist/MafSequenceWidget/MafSequenceWidget.js +189 -0
  81. package/dist/MafSequenceWidget/MafSequenceWidget.js.map +1 -0
  82. package/dist/MafSequenceWidget/SequenceCanvas.d.ts +12 -0
  83. package/dist/MafSequenceWidget/SequenceCanvas.js +86 -0
  84. package/dist/MafSequenceWidget/SequenceCanvas.js.map +1 -0
  85. package/dist/MafSequenceWidget/SequenceDisplay.d.ts +12 -0
  86. package/dist/MafSequenceWidget/SequenceDisplay.js +117 -0
  87. package/dist/MafSequenceWidget/SequenceDisplay.js.map +1 -0
  88. package/dist/MafSequenceWidget/SequenceTooltip.d.ts +11 -0
  89. package/dist/MafSequenceWidget/SequenceTooltip.js +39 -0
  90. package/dist/MafSequenceWidget/SequenceTooltip.js.map +1 -0
  91. package/dist/MafSequenceWidget/baseColors.d.ts +3 -0
  92. package/dist/MafSequenceWidget/baseColors.js +64 -0
  93. package/dist/MafSequenceWidget/baseColors.js.map +1 -0
  94. package/dist/MafSequenceWidget/colToGenomePos.d.ts +13 -0
  95. package/dist/MafSequenceWidget/colToGenomePos.js +32 -0
  96. package/dist/MafSequenceWidget/colToGenomePos.js.map +1 -0
  97. package/dist/MafSequenceWidget/colToGenomePos.test.d.ts +1 -0
  98. package/dist/MafSequenceWidget/colToGenomePos.test.js +136 -0
  99. package/dist/MafSequenceWidget/colToGenomePos.test.js.map +1 -0
  100. package/dist/MafSequenceWidget/configSchema.d.ts +1 -0
  101. package/dist/MafSequenceWidget/configSchema.js +3 -0
  102. package/dist/MafSequenceWidget/configSchema.js.map +1 -0
  103. package/dist/MafSequenceWidget/constants.d.ts +4 -0
  104. package/dist/MafSequenceWidget/constants.js +5 -0
  105. package/dist/MafSequenceWidget/constants.js.map +1 -0
  106. package/dist/MafSequenceWidget/index.d.ts +2 -0
  107. package/dist/MafSequenceWidget/index.js +16 -0
  108. package/dist/MafSequenceWidget/index.js.map +1 -0
  109. package/dist/MafSequenceWidget/stateModelFactory.d.ts +67 -0
  110. package/dist/MafSequenceWidget/stateModelFactory.js +21 -0
  111. package/dist/MafSequenceWidget/stateModelFactory.js.map +1 -0
  112. package/dist/MafTabixAdapter/MafTabixAdapter.js +4 -35
  113. package/dist/MafTabixAdapter/MafTabixAdapter.js.map +1 -1
  114. package/dist/MafTabixAdapter/configSchema.d.ts +4 -4
  115. package/dist/MafTrack/configSchema.d.ts +16 -11
  116. package/dist/index.js +2 -0
  117. package/dist/index.js.map +1 -1
  118. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +12 -24
  119. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
  120. package/dist/util/clipboard.d.ts +2 -0
  121. package/dist/util/clipboard.js +28 -0
  122. package/dist/util/clipboard.js.map +1 -0
  123. package/dist/util/fastaUtils.d.ts +2 -1
  124. package/dist/util/fastaUtils.js +93 -50
  125. package/dist/util/fastaUtils.js.map +1 -1
  126. package/dist/util/fastaUtils.test.js +190 -0
  127. package/dist/util/fastaUtils.test.js.map +1 -1
  128. package/dist/util/parseAssemblyName.d.ts +32 -0
  129. package/dist/util/parseAssemblyName.js +87 -0
  130. package/dist/util/parseAssemblyName.js.map +1 -0
  131. package/dist/util/parseAssemblyName.test.d.ts +1 -0
  132. package/dist/util/parseAssemblyName.test.js +269 -0
  133. package/dist/util/parseAssemblyName.test.js.map +1 -0
  134. package/package.json +12 -12
  135. package/src/BigMafAdapter/BigMafAdapter.ts +5 -5
  136. package/src/LinearMafDisplay/components/LinearMafDisplayComponent.tsx +63 -145
  137. package/src/LinearMafDisplay/components/MAFTooltip.tsx +0 -3
  138. package/src/LinearMafDisplay/components/MsaHighlightOverlay.tsx +62 -0
  139. package/src/LinearMafDisplay/components/Sidebar/ColorLegend.tsx +2 -6
  140. package/src/LinearMafDisplay/components/Sidebar/RectBg.tsx +8 -3
  141. package/src/LinearMafDisplay/components/Sidebar/SvgWrapper.tsx +117 -15
  142. package/src/LinearMafDisplay/components/Sidebar/Tree.tsx +53 -8
  143. package/src/LinearMafDisplay/components/Sidebar/YScaleBars.tsx +0 -1
  144. package/src/LinearMafDisplay/components/useDragSelection.ts +159 -0
  145. package/src/LinearMafDisplay/renderSvg.tsx +1 -1
  146. package/src/LinearMafDisplay/stateModel.ts +215 -20
  147. package/src/LinearMafDisplay/types.ts +2 -2
  148. package/src/LinearMafDisplay/util.ts +35 -7
  149. package/src/LinearMafRenderer/LinearMafRenderer.ts +3 -5
  150. package/src/LinearMafRenderer/components/LinearMafRendering.tsx +67 -33
  151. package/src/LinearMafRenderer/configSchema.ts +1 -6
  152. package/src/LinearMafRenderer/makeImageData.ts +5 -14
  153. package/src/LinearMafRenderer/rendering/features.ts +2 -36
  154. package/src/LinearMafRenderer/rendering/insertions.ts +13 -8
  155. package/src/LinearMafRenderer/rendering/matches.ts +2 -27
  156. package/src/LinearMafRenderer/rendering/mismatches.ts +3 -3
  157. package/src/LinearMafRenderer/rendering/spatialIndex.ts +9 -2
  158. package/src/LinearMafRenderer/rendering/text.ts +1 -2
  159. package/src/LinearMafRenderer/rendering/types.ts +8 -5
  160. package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +1 -1
  161. package/src/MafAddTrackWorkflow/index.ts +1 -1
  162. package/src/MafGetSequences/MafGetSequences.ts +10 -2
  163. package/src/MafSequenceWidget/LabelsCanvas.tsx +58 -0
  164. package/src/MafSequenceWidget/MafSequenceHoverHighlight.tsx +83 -0
  165. package/src/MafSequenceWidget/MafSequenceHoverHighlightExtension.tsx +24 -0
  166. package/src/MafSequenceWidget/MafSequenceWidget.tsx +294 -0
  167. package/src/MafSequenceWidget/SequenceCanvas.tsx +136 -0
  168. package/src/MafSequenceWidget/SequenceDisplay.tsx +188 -0
  169. package/src/MafSequenceWidget/SequenceTooltip.tsx +70 -0
  170. package/src/MafSequenceWidget/baseColors.ts +76 -0
  171. package/src/MafSequenceWidget/colToGenomePos.test.ts +166 -0
  172. package/src/MafSequenceWidget/colToGenomePos.ts +40 -0
  173. package/src/MafSequenceWidget/configSchema.ts +3 -0
  174. package/src/MafSequenceWidget/constants.ts +4 -0
  175. package/src/MafSequenceWidget/index.ts +24 -0
  176. package/src/MafSequenceWidget/stateModelFactory.ts +43 -0
  177. package/src/MafTabixAdapter/MafTabixAdapter.ts +12 -51
  178. package/src/index.ts +2 -0
  179. package/src/util/__snapshots__/fastaUtils.test.ts.snap +35 -0
  180. package/src/util/clipboard.ts +35 -0
  181. package/src/util/fastaUtils.test.ts +199 -0
  182. package/src/util/fastaUtils.ts +118 -51
  183. package/src/util/parseAssemblyName.test.ts +350 -0
  184. package/src/util/parseAssemblyName.ts +106 -0
  185. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.d.ts +0 -11
  186. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js +0 -97
  187. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js.map +0 -1
  188. package/dist/LinearMafDisplay/components/util.d.ts +0 -1
  189. package/dist/LinearMafDisplay/components/util.js +0 -8
  190. package/dist/LinearMafDisplay/components/util.js.map +0 -1
  191. package/dist/LinearMafRenderer/components/util.d.ts +0 -1
  192. package/dist/LinearMafRenderer/components/util.js +0 -13
  193. package/dist/LinearMafRenderer/components/util.js.map +0 -1
  194. package/dist/util/fetchSequences.d.ts +0 -18
  195. package/dist/util/fetchSequences.js +0 -39
  196. package/dist/util/fetchSequences.js.map +0 -1
  197. package/dist/util/useSequences.d.ts +0 -21
  198. package/dist/util/useSequences.js +0 -64
  199. package/dist/util/useSequences.js.map +0 -1
  200. package/src/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.tsx +0 -175
  201. package/src/LinearMafDisplay/components/util.ts +0 -7
  202. package/src/LinearMafRenderer/components/util.ts +0 -13
  203. package/src/util/fetchSequences.ts +0 -57
  204. package/src/util/useSequences.ts +0 -90
@@ -97,3 +97,202 @@ test('gap in assembly1', () => {
97
97
  })
98
98
  expect(result).toMatchSnapshot()
99
99
  })
100
+
101
+ test('includeInsertions - single insertion in one sample', () => {
102
+ // Reference seq has a gap (insertion in assembly2)
103
+ // seq: AC--GTAC (reference with gap = insertion in aligned seq)
104
+ // assembly1: AC--GTAC (no insertion, matches reference gap)
105
+ // assembly2: ACTTGTAC (has TT insertion)
106
+ const mockFeature = new SimpleFeature({
107
+ uniqueId: '123',
108
+ refName: 'abc',
109
+ start: 100,
110
+ end: 106, // 6 bp reference (AC GTAC without the gap)
111
+ seq: 'AC--GTAC',
112
+ alignments: {
113
+ assembly1: {
114
+ chr: 'chr1',
115
+ start: 100,
116
+ seq: 'AC--GTAC',
117
+ strand: 1,
118
+ },
119
+ assembly2: {
120
+ chr: 'chr2',
121
+ start: 200,
122
+ seq: 'ACTTGTAC',
123
+ strand: 1,
124
+ },
125
+ },
126
+ })
127
+
128
+ const result = processFeaturesToFasta({
129
+ features: makeMap([mockFeature]),
130
+ samples: [{ id: 'assembly1' }, { id: 'assembly2' }],
131
+ includeInsertions: true,
132
+ showAllLetters: true,
133
+ regions: [
134
+ {
135
+ refName: 'chr1',
136
+ start: 100,
137
+ end: 106,
138
+ assemblyName: 'assembly1',
139
+ },
140
+ ],
141
+ })
142
+ // assembly1 should have gaps where the insertion is
143
+ // assembly2 should have the TT insertion
144
+ expect(result).toMatchSnapshot()
145
+ })
146
+
147
+ test('includeInsertions - insertions in multiple samples with different lengths', () => {
148
+ // Reference has gap, different samples have different insertion lengths
149
+ // seq: AC---GTAC (reference with 3-bp gap)
150
+ // assembly1: AC-T-GTAC (has T insertion, 1 bp)
151
+ // assembly2: ACTTTGTAC (has TTT insertion, 3 bp)
152
+ const mockFeature = new SimpleFeature({
153
+ uniqueId: '123',
154
+ refName: 'abc',
155
+ start: 100,
156
+ end: 106,
157
+ seq: 'AC---GTAC',
158
+ alignments: {
159
+ assembly1: {
160
+ chr: 'chr1',
161
+ start: 100,
162
+ seq: 'AC-T-GTAC',
163
+ strand: 1,
164
+ },
165
+ assembly2: {
166
+ chr: 'chr2',
167
+ start: 200,
168
+ seq: 'ACTTTGTAC',
169
+ strand: 1,
170
+ },
171
+ },
172
+ })
173
+
174
+ const result = processFeaturesToFasta({
175
+ features: makeMap([mockFeature]),
176
+ samples: [{ id: 'assembly1' }, { id: 'assembly2' }],
177
+ includeInsertions: true,
178
+ showAllLetters: true,
179
+ regions: [
180
+ {
181
+ refName: 'chr1',
182
+ start: 100,
183
+ end: 106,
184
+ assemblyName: 'assembly1',
185
+ },
186
+ ],
187
+ })
188
+ // assembly1 should have T-- (padded to max insertion length 3)
189
+ // assembly2 should have TTT
190
+ expect(result).toMatchSnapshot()
191
+ })
192
+
193
+ test('includeInsertions - insertions at multiple positions', () => {
194
+ // Reference has gaps at two positions
195
+ // seq: A-CG-TAC
196
+ // assembly1: ATCGGTAC (T insertion at pos 1, G insertion at pos 4)
197
+ // assembly2: A-CG-TAC (no insertions)
198
+ const mockFeature = new SimpleFeature({
199
+ uniqueId: '123',
200
+ refName: 'abc',
201
+ start: 100,
202
+ end: 106,
203
+ seq: 'A-CG-TAC',
204
+ alignments: {
205
+ assembly1: {
206
+ chr: 'chr1',
207
+ start: 100,
208
+ seq: 'ATCGGTAC',
209
+ strand: 1,
210
+ },
211
+ assembly2: {
212
+ chr: 'chr2',
213
+ start: 200,
214
+ seq: 'A-CG-TAC',
215
+ strand: 1,
216
+ },
217
+ },
218
+ })
219
+
220
+ const result = processFeaturesToFasta({
221
+ features: makeMap([mockFeature]),
222
+ samples: [{ id: 'assembly1' }, { id: 'assembly2' }],
223
+ includeInsertions: true,
224
+ showAllLetters: true,
225
+ regions: [
226
+ {
227
+ refName: 'chr1',
228
+ start: 100,
229
+ end: 106,
230
+ assemblyName: 'assembly1',
231
+ },
232
+ ],
233
+ })
234
+ expect(result).toMatchSnapshot()
235
+ })
236
+
237
+ test('includeInsertions=false ignores insertions', () => {
238
+ const mockFeature = new SimpleFeature({
239
+ uniqueId: '123',
240
+ refName: 'abc',
241
+ start: 100,
242
+ end: 106,
243
+ seq: 'AC--GTAC',
244
+ alignments: {
245
+ assembly1: {
246
+ chr: 'chr1',
247
+ start: 100,
248
+ seq: 'AC--GTAC',
249
+ strand: 1,
250
+ },
251
+ assembly2: {
252
+ chr: 'chr2',
253
+ start: 200,
254
+ seq: 'ACTTGTAC',
255
+ strand: 1,
256
+ },
257
+ },
258
+ })
259
+
260
+ const result = processFeaturesToFasta({
261
+ features: makeMap([mockFeature]),
262
+ samples: [{ id: 'assembly1' }, { id: 'assembly2' }],
263
+ includeInsertions: false,
264
+ showAllLetters: true,
265
+ regions: [
266
+ {
267
+ refName: 'chr1',
268
+ start: 100,
269
+ end: 106,
270
+ assemblyName: 'assembly1',
271
+ },
272
+ ],
273
+ })
274
+ // Without insertions, both should be 6 characters (no expansion)
275
+ expect(result[0]).toHaveLength(6)
276
+ expect(result[1]).toHaveLength(6)
277
+ expect(result).toMatchSnapshot()
278
+ })
279
+
280
+ test('includeInsertions with no insertions present', () => {
281
+ // No gaps in reference = no insertions
282
+ const result = processFeaturesToFasta({
283
+ features: makeMap([mockFeature]),
284
+ samples: [{ id: 'assembly1' }, { id: 'assembly2' }],
285
+ includeInsertions: true,
286
+ showAllLetters: true,
287
+ regions: [
288
+ {
289
+ refName: 'chr1',
290
+ start: 100,
291
+ end: 105,
292
+ assemblyName: 'assembly1',
293
+ },
294
+ ],
295
+ })
296
+ // Should behave same as without includeInsertions since there are none
297
+ expect(result).toMatchSnapshot()
298
+ })
@@ -3,6 +3,11 @@ import { Sample } from '../LinearMafDisplay/types'
3
3
  import type { AlignmentRecord } from '../LinearMafRenderer/rendering'
4
4
  import type { Feature, Region } from '@jbrowse/core/util'
5
5
 
6
+ interface InsertionInfo {
7
+ sequence: string
8
+ sampleIndex: number
9
+ }
10
+
6
11
  /**
7
12
  * Process features into FASTA format
8
13
  * @param features - The features to process
@@ -14,90 +19,152 @@ export function processFeaturesToFasta({
14
19
  showAllLetters,
15
20
  samples,
16
21
  features,
22
+ includeInsertions,
17
23
  }: {
18
24
  regions: Region[]
19
25
  samples: Sample[]
20
26
  showAsUpperCase?: boolean
21
27
  mismatchRendering?: boolean
22
28
  showAllLetters?: boolean
29
+ includeInsertions?: boolean
23
30
  features: Map<string, Feature>
24
31
  }) {
25
32
  const region = regions[0]!
26
33
  const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]))
27
34
  const rlen = region.end - region.start
28
- const outputRows = samples.map(() => '-'.repeat(rlen))
35
+
36
+ // Use character arrays instead of strings for O(1) mutations
37
+ const outputRowsArrays = samples.map(() => new Array(rlen).fill('-'))
38
+
39
+ // Track insertions at each position if includeInsertions is enabled
40
+ // Key is the reference position (0-based relative to region), value is array of insertions
41
+ const insertionsAtPosition = new Map<number, InsertionInfo[]>()
42
+
29
43
  for (const feature of features.values()) {
30
44
  const leftCoord = feature.get('start')
31
45
  const vals = feature.get('alignments') as Record<string, AlignmentRecord>
32
46
  const seq = feature.get('seq')
33
- for (const [sample, val] of Object.entries(vals)) {
34
- const origAlignment = val.seq
35
- const alignment = origAlignment
36
47
 
48
+ for (const [sample, val] of Object.entries(vals)) {
49
+ const alignment = val.seq
37
50
  const row = sampleToRowMap.get(sample)
38
51
  if (row === undefined) {
39
52
  continue
40
53
  }
41
54
 
42
- // gaps
55
+ const rowArray = outputRowsArrays[row]!
56
+
57
+ // Single-pass processing: handle gaps, matches, mismatches, and collect insertions
43
58
  for (let i = 0, o = 0, l = alignment.length; i < l; i++) {
44
59
  if (seq[i] !== '-') {
45
- if (alignment[i] === '-') {
46
- const l = leftCoord + o - region.start
47
- if (l >= 0 && l < rlen) {
48
- outputRows[row] =
49
- outputRows[row]!.slice(0, l) +
50
- '-' +
51
- outputRows[row]!.slice(l + 1)
60
+ const c = alignment[i]
61
+ const pos = leftCoord + o - region.start
62
+
63
+ if (pos >= 0 && pos < rlen) {
64
+ if (c === '-') {
65
+ // Gap
66
+ rowArray[pos] = '-'
67
+ } else if (c !== ' ') {
68
+ if (showAllLetters) {
69
+ // Show all letters mode: write character directly
70
+ rowArray[pos] = c
71
+ } else if (seq[i] === c) {
72
+ // Match: use dot notation
73
+ rowArray[pos] = '.'
74
+ } else {
75
+ // Mismatch: write character
76
+ rowArray[pos] = c
77
+ }
52
78
  }
53
79
  }
54
80
  o++
55
- }
56
- }
57
-
58
- if (!showAllLetters) {
59
- // matches
60
- for (let i = 0, o = 0, l = alignment.length; i < l; i++) {
61
- if (seq[i] !== '-') {
81
+ } else if (includeInsertions) {
82
+ // This is an insertion (reference has gap)
83
+ // Collect all consecutive insertion characters
84
+ let insertionSequence = ''
85
+ while (i < alignment.length && seq[i] === '-') {
62
86
  const c = alignment[i]
63
- const l = leftCoord + o - region.start
64
- if (l >= 0 && l < rlen) {
65
- if (seq[i] === c && c !== '-' && c !== ' ') {
66
- outputRows[row] =
67
- outputRows[row]!.slice(0, l) +
68
- '.' +
69
- outputRows[row]!.slice(l + 1)
70
- }
71
- }
72
- o++
87
+ insertionSequence += c !== '-' && c !== ' ' ? c : '-'
88
+ i++
73
89
  }
74
- }
75
- }
90
+ i-- // Back up one since the outer loop will increment
76
91
 
77
- // mismatches
78
- for (let i = 0, o = 0, l = alignment.length; i < l; i++) {
79
- const c = alignment[i]
80
- if (seq[i] !== '-') {
81
- if (c !== '-') {
82
- const l = leftCoord + o - region.start
83
- if (l >= 0 && l < rlen) {
84
- if (seq[i] !== c && c !== ' ') {
85
- outputRows[row] =
86
- outputRows[row]!.slice(0, l) +
87
- c +
88
- outputRows[row]!.slice(l + 1)
89
- } else if (showAllLetters) {
90
- outputRows[row] =
91
- outputRows[row]!.slice(0, l) +
92
- c +
93
- outputRows[row]!.slice(l + 1)
94
- }
92
+ if (insertionSequence.length > 0) {
93
+ // Position is relative to region start, insertions come after position o-1
94
+ // (or before position 0 if o is 0)
95
+ const insertPos = leftCoord + o - region.start
96
+ if (insertPos >= 0 && insertPos <= rlen) {
97
+ const existing = insertionsAtPosition.get(insertPos) || []
98
+ existing.push({ sequence: insertionSequence, sampleIndex: row })
99
+ insertionsAtPosition.set(insertPos, existing)
95
100
  }
96
101
  }
97
- o++
98
102
  }
99
103
  }
100
104
  }
101
105
  }
102
- return outputRows
106
+
107
+ if (includeInsertions && insertionsAtPosition.size > 0) {
108
+ return expandWithInsertions(
109
+ outputRowsArrays,
110
+ insertionsAtPosition,
111
+ samples.length,
112
+ )
113
+ }
114
+
115
+ // Convert character arrays back to strings
116
+ return outputRowsArrays.map(arr => arr.join(''))
117
+ }
118
+
119
+ /**
120
+ * Expand sequences to include insertions
121
+ * At each position with insertions, find the max insertion length,
122
+ * then expand all sequences by that amount
123
+ */
124
+ function expandWithInsertions(
125
+ outputRowsArrays: string[][],
126
+ insertionsAtPosition: Map<number, InsertionInfo[]>,
127
+ numSamples: number,
128
+ ) {
129
+ // Sort insertion positions in descending order so we can insert from right to left
130
+ // without affecting earlier positions
131
+ const sortedPositions = [...insertionsAtPosition.keys()].sort((a, b) => b - a)
132
+
133
+ for (const pos of sortedPositions) {
134
+ const insertions = insertionsAtPosition.get(pos)!
135
+
136
+ // Find max insertion length at this position
137
+ let maxLen = 0
138
+ for (const ins of insertions) {
139
+ if (ins.sequence.length > maxLen) {
140
+ maxLen = ins.sequence.length
141
+ }
142
+ }
143
+
144
+ // Create a map from sample index to insertion sequence
145
+ const sampleInsertions = new Map<number, string>()
146
+ for (const ins of insertions) {
147
+ sampleInsertions.set(ins.sampleIndex, ins.sequence)
148
+ }
149
+
150
+ // Insert characters at this position for each sample
151
+ for (let sampleIdx = 0; sampleIdx < numSamples; sampleIdx++) {
152
+ const rowArray = outputRowsArrays[sampleIdx]!
153
+ const insertionSeq = sampleInsertions.get(sampleIdx)
154
+
155
+ if (insertionSeq) {
156
+ // This sample has an insertion - add it, padded with gaps if needed
157
+ const paddedInsertion = insertionSeq.padEnd(maxLen, '-')
158
+ // Insert after position `pos`
159
+ rowArray.splice(pos, 0, ...paddedInsertion.split(''))
160
+ } else {
161
+ // No insertion for this sample - fill with gaps
162
+ const gaps = new Array(maxLen).fill('-')
163
+ rowArray.splice(pos, 0, ...gaps)
164
+ }
165
+ }
166
+ }
167
+
168
+ // Convert character arrays back to strings
169
+ return outputRowsArrays.map(arr => arr.join(''))
103
170
  }