@teselagen/sequence-utils 0.3.36 → 0.3.38-beta.1

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 (169) hide show
  1. package/DNAComplementMap.d.ts +1 -1
  2. package/addGapsToSeqReads.d.ts +16 -3
  3. package/adjustAnnotationsToInsert.d.ts +2 -1
  4. package/adjustBpsToReplaceOrInsert.d.ts +2 -1
  5. package/aliasedEnzymesByName.d.ts +37 -1
  6. package/aminoAcidToDegenerateDnaMap.d.ts +1 -31
  7. package/aminoAcidToDegenerateRnaMap.d.ts +1 -1
  8. package/annotateSingleSeq.d.ts +5 -4
  9. package/annotationTypes.d.ts +2 -2
  10. package/autoAnnotate.d.ts +17 -8
  11. package/bioData.d.ts +10 -58
  12. package/calculateEndStability.d.ts +1 -1
  13. package/calculateNebTa.d.ts +6 -1
  14. package/calculateNebTm.d.ts +6 -4
  15. package/calculatePercentGC.d.ts +1 -1
  16. package/calculateSantaLuciaTm.d.ts +28 -114
  17. package/calculateTm.d.ts +13 -1
  18. package/computeDigestFragments.d.ts +30 -24
  19. package/condensePairwiseAlignmentDifferences.d.ts +1 -1
  20. package/convertAACaretPositionOrRangeToDna.d.ts +2 -1
  21. package/convertDnaCaretPositionOrRangeToAA.d.ts +2 -1
  22. package/cutSequenceByRestrictionEnzyme.d.ts +2 -1
  23. package/defaultEnzymesByName.d.ts +2 -1
  24. package/degenerateDnaToAminoAcidMap.d.ts +1 -1
  25. package/degenerateRnaToAminoAcidMap.d.ts +1 -1
  26. package/deleteSequenceDataAtRange.d.ts +2 -1
  27. package/diffUtils.d.ts +9 -7
  28. package/doesEnzymeChopOutsideOfRecognitionSite.d.ts +2 -1
  29. package/featureTypesAndColors.d.ts +19 -6
  30. package/filterSequenceString.d.ts +14 -10
  31. package/findApproxMatches.d.ts +7 -1
  32. package/findNearestRangeOfSequenceOverlapToPosition.d.ts +2 -1
  33. package/findOrfsInPlasmid.d.ts +2 -11
  34. package/findSequenceMatches.d.ts +11 -1
  35. package/generateAnnotations.d.ts +2 -1
  36. package/generateSequenceData.d.ts +8 -13
  37. package/getAllInsertionsInSeqReads.d.ts +11 -1
  38. package/getAminoAcidDataForEachBaseOfDna.d.ts +6 -5
  39. package/getAminoAcidFromSequenceTriplet.d.ts +1 -1
  40. package/getAminoAcidStringFromSequenceString.d.ts +3 -1
  41. package/getCodonRangeForAASliver.d.ts +3 -4
  42. package/getComplementAminoAcidStringFromSequenceString.d.ts +1 -1
  43. package/getComplementSequenceAndAnnotations.d.ts +5 -1
  44. package/getComplementSequenceString.d.ts +1 -1
  45. package/getCutsiteType.d.ts +2 -1
  46. package/getCutsitesFromSequence.d.ts +2 -1
  47. package/getDegenerateDnaStringFromAAString.d.ts +1 -1
  48. package/getDegenerateRnaStringFromAAString.d.ts +1 -1
  49. package/getDigestFragmentsForCutsites.d.ts +4 -1
  50. package/getDigestFragmentsForRestrictionEnzymes.d.ts +8 -1
  51. package/getInsertBetweenVals.d.ts +2 -1
  52. package/getLeftAndRightOfSequenceInRangeGivenPosition.d.ts +2 -1
  53. package/getOrfsFromSequence.d.ts +17 -11
  54. package/getOverlapBetweenTwoSequences.d.ts +2 -1
  55. package/getPossiblePartsFromSequenceAndEnzymes.d.ts +18 -1
  56. package/getReverseAminoAcidStringFromSequenceString.d.ts +1 -1
  57. package/getReverseComplementAminoAcidStringFromSequenceString.d.ts +1 -1
  58. package/getReverseComplementAnnotation.d.ts +11 -1
  59. package/getReverseComplementSequenceAndAnnotations.d.ts +5 -1
  60. package/getReverseComplementSequenceString.d.ts +1 -1
  61. package/getReverseSequenceString.d.ts +1 -1
  62. package/getSequenceDataBetweenRange.d.ts +9 -1
  63. package/getVirtualDigest.d.ts +11 -10
  64. package/guessIfSequenceIsDnaAndNotProtein.d.ts +5 -1
  65. package/index.cjs +733 -484
  66. package/index.d.ts +8 -5
  67. package/index.js +733 -484
  68. package/index.umd.cjs +733 -484
  69. package/insertGapsIntoRefSeq.d.ts +2 -1
  70. package/insertSequenceDataAtPositionOrRange.d.ts +10 -1
  71. package/isEnzymeType2S.d.ts +2 -1
  72. package/mapAnnotationsToRows.d.ts +9 -1
  73. package/package.json +9 -6
  74. package/prepareCircularViewData.d.ts +2 -1
  75. package/prepareRowData.d.ts +7 -3
  76. package/proteinAlphabet.d.ts +1 -1
  77. package/rotateBpsToPosition.d.ts +1 -1
  78. package/rotateSequenceDataToPosition.d.ts +3 -1
  79. package/shiftAnnotationsByLen.d.ts +4 -3
  80. package/src/DNAComplementMap.ts +32 -0
  81. package/src/addGapsToSeqReads.ts +436 -0
  82. package/src/adjustAnnotationsToInsert.ts +20 -0
  83. package/src/adjustBpsToReplaceOrInsert.ts +73 -0
  84. package/src/aliasedEnzymesByName.ts +7366 -0
  85. package/src/aminoAcidToDegenerateDnaMap.ts +32 -0
  86. package/src/aminoAcidToDegenerateRnaMap.ts +32 -0
  87. package/src/annotateSingleSeq.ts +37 -0
  88. package/src/annotationTypes.ts +23 -0
  89. package/src/autoAnnotate.test.js +0 -1
  90. package/src/autoAnnotate.ts +290 -0
  91. package/src/bioData.ts +65 -0
  92. package/src/calculateEndStability.ts +91 -0
  93. package/src/calculateNebTa.ts +46 -0
  94. package/src/calculateNebTm.ts +132 -0
  95. package/src/calculatePercentGC.ts +3 -0
  96. package/src/calculateSantaLuciaTm.ts +184 -0
  97. package/src/calculateTm.ts +242 -0
  98. package/src/computeDigestFragments.ts +238 -0
  99. package/src/condensePairwiseAlignmentDifferences.ts +85 -0
  100. package/src/convertAACaretPositionOrRangeToDna.ts +28 -0
  101. package/src/convertDnaCaretPositionOrRangeToAA.ts +28 -0
  102. package/src/cutSequenceByRestrictionEnzyme.ts +345 -0
  103. package/src/defaultEnzymesByName.ts +280 -0
  104. package/src/degenerateDnaToAminoAcidMap.ts +5 -0
  105. package/src/degenerateRnaToAminoAcidMap.ts +5 -0
  106. package/src/deleteSequenceDataAtRange.ts +13 -0
  107. package/src/diffUtils.ts +80 -0
  108. package/src/doesEnzymeChopOutsideOfRecognitionSite.ts +16 -0
  109. package/src/featureTypesAndColors.js +1 -1
  110. package/src/featureTypesAndColors.ts +167 -0
  111. package/src/filterSequenceString.ts +153 -0
  112. package/src/findApproxMatches.ts +58 -0
  113. package/src/findNearestRangeOfSequenceOverlapToPosition.ts +43 -0
  114. package/src/findOrfsInPlasmid.js +6 -1
  115. package/src/findOrfsInPlasmid.ts +31 -0
  116. package/src/findSequenceMatches.ts +154 -0
  117. package/src/generateAnnotations.ts +39 -0
  118. package/src/generateSequenceData.ts +212 -0
  119. package/src/getAllInsertionsInSeqReads.ts +100 -0
  120. package/src/getAminoAcidDataForEachBaseOfDna.ts +305 -0
  121. package/src/getAminoAcidFromSequenceTriplet.ts +27 -0
  122. package/src/getAminoAcidStringFromSequenceString.ts +36 -0
  123. package/src/getCodonRangeForAASliver.ts +73 -0
  124. package/src/getComplementAminoAcidStringFromSequenceString.ts +10 -0
  125. package/src/getComplementSequenceAndAnnotations.ts +25 -0
  126. package/src/getComplementSequenceString.ts +23 -0
  127. package/src/getCutsiteType.ts +18 -0
  128. package/src/getCutsitesFromSequence.ts +22 -0
  129. package/src/getDegenerateDnaStringFromAAString.ts +15 -0
  130. package/src/getDegenerateRnaStringFromAAString.ts +15 -0
  131. package/src/getDigestFragmentsForCutsites.ts +126 -0
  132. package/src/getDigestFragmentsForRestrictionEnzymes.ts +50 -0
  133. package/src/getInsertBetweenVals.ts +31 -0
  134. package/src/getLeftAndRightOfSequenceInRangeGivenPosition.ts +40 -0
  135. package/src/getMassOfAaString.ts +29 -0
  136. package/src/getOrfsFromSequence.ts +132 -0
  137. package/src/getOverlapBetweenTwoSequences.ts +30 -0
  138. package/src/getPossiblePartsFromSequenceAndEnzymes.ts +149 -0
  139. package/src/getReverseAminoAcidStringFromSequenceString.ts +22 -0
  140. package/src/getReverseComplementAminoAcidStringFromSequenceString.ts +10 -0
  141. package/src/getReverseComplementAnnotation.ts +33 -0
  142. package/src/getReverseComplementSequenceAndAnnotations.ts +46 -0
  143. package/src/getReverseComplementSequenceString.ts +18 -0
  144. package/src/getReverseSequenceString.ts +12 -0
  145. package/src/getSequenceDataBetweenRange.ts +154 -0
  146. package/src/getVirtualDigest.ts +139 -0
  147. package/src/guessIfSequenceIsDnaAndNotProtein.ts +39 -0
  148. package/src/index.test.ts +43 -0
  149. package/src/index.ts +111 -0
  150. package/src/insertGapsIntoRefSeq.ts +43 -0
  151. package/src/insertSequenceDataAtPosition.ts +2 -0
  152. package/src/insertSequenceDataAtPositionOrRange.ts +328 -0
  153. package/src/isEnzymeType2S.ts +5 -0
  154. package/src/mapAnnotationsToRows.ts +256 -0
  155. package/src/prepareCircularViewData.ts +24 -0
  156. package/src/prepareRowData.ts +61 -0
  157. package/src/prepareRowData_output1.json +1 -0
  158. package/src/proteinAlphabet.ts +271 -0
  159. package/src/rotateBpsToPosition.ts +12 -0
  160. package/src/rotateSequenceDataToPosition.ts +54 -0
  161. package/src/shiftAnnotationsByLen.ts +24 -0
  162. package/src/threeLetterSequenceStringToAminoAcidMap.ts +198 -0
  163. package/src/tidyUpAnnotation.ts +205 -0
  164. package/src/tidyUpSequenceData.ts +213 -0
  165. package/src/types.ts +109 -0
  166. package/threeLetterSequenceStringToAminoAcidMap.d.ts +11 -921
  167. package/tidyUpAnnotation.d.ts +13 -11
  168. package/tidyUpSequenceData.d.ts +15 -1
  169. package/types.d.ts +105 -0
@@ -0,0 +1,328 @@
1
+ import { getRangeLength, Range } from "@teselagen/range-utils";
2
+ import { map, cloneDeep } from "lodash-es";
3
+ import convertDnaCaretPositionOrRangeToAa from "./convertDnaCaretPositionOrRangeToAA";
4
+ import rotateSequenceDataToPosition from "./rotateSequenceDataToPosition";
5
+ import { adjustRangeToDeletionOfAnotherRange } from "@teselagen/range-utils";
6
+ import tidyUpSequenceData from "./tidyUpSequenceData";
7
+ import { modifiableTypes } from "./annotationTypes";
8
+ import adjustBpsToReplaceOrInsert from "./adjustBpsToReplaceOrInsert";
9
+ import adjustAnnotationsToInsert from "./adjustAnnotationsToInsert";
10
+ import { Annotation, ChromatogramData, SequenceData } from "./types";
11
+
12
+ interface InsertSequenceDataOptions {
13
+ maintainOriginSplit?: boolean;
14
+ doNotRemoveInvalidChars?: boolean;
15
+ topLevelSeqData?: SequenceData;
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ export default function insertSequenceDataAtPositionOrRange(
20
+ _sequenceDataToInsert: SequenceData,
21
+ _existingSequenceData: SequenceData,
22
+ caretPositionOrRange: number | Range,
23
+ options: InsertSequenceDataOptions = {}
24
+ ): SequenceData {
25
+ //maintainOriginSplit means that if you're inserting around the origin with n bps selected before the origin
26
+ //when inserting new seq, n bps of the new seq should go in before the origin and the rest should be
27
+ //inserted at the sequence start
28
+ const { maintainOriginSplit } = options;
29
+ let existingSequenceData = tidyUpSequenceData(_existingSequenceData, {
30
+ doNotRemoveInvalidChars: true,
31
+ ...options
32
+ });
33
+ const sequenceDataToInsert = tidyUpSequenceData(_sequenceDataToInsert, {
34
+ topLevelSeqData: existingSequenceData,
35
+ ...options
36
+ });
37
+ const newSequenceData = cloneDeep(existingSequenceData);
38
+ const insertLength =
39
+ sequenceDataToInsert.isProtein && sequenceDataToInsert.proteinSequence
40
+ ? sequenceDataToInsert.proteinSequence.length * 3
41
+ : sequenceDataToInsert.sequence.length;
42
+ let caretPosition =
43
+ typeof caretPositionOrRange === "number"
44
+ ? caretPositionOrRange
45
+ : caretPositionOrRange.start;
46
+
47
+ const isInsertSameLengthAsSelection =
48
+ typeof caretPositionOrRange !== "number" &&
49
+ sequenceDataToInsert.sequence.length ===
50
+ getRangeLength(
51
+ caretPositionOrRange,
52
+ existingSequenceData.sequence.length
53
+ );
54
+
55
+ if (
56
+ typeof caretPositionOrRange !== "number" &&
57
+ caretPositionOrRange.start > -1 &&
58
+ getRangeLength(
59
+ caretPositionOrRange,
60
+ existingSequenceData.sequence.length
61
+ ) === existingSequenceData.sequence.length
62
+ ) {
63
+ //handle the case where we're deleting everything!
64
+ const emptyAnnotations = modifiableTypes.reduce(
65
+ (acc, type) => {
66
+ acc[type] = [];
67
+ return acc;
68
+ },
69
+ {} as Record<string, Annotation[]>
70
+ );
71
+
72
+ existingSequenceData = tidyUpSequenceData(
73
+ {
74
+ ...existingSequenceData,
75
+ ...emptyAnnotations,
76
+ sequence: "",
77
+ doNotRemoveInvalidChars: true,
78
+ proteinSequence: "",
79
+ chromatogramData: undefined
80
+ },
81
+ options
82
+ );
83
+ newSequenceData.chromatogramData = undefined;
84
+ } else if (
85
+ newSequenceData.chromatogramData &&
86
+ newSequenceData.chromatogramData.baseTraces
87
+ ) {
88
+ //handle chromatogramData updates
89
+ if (
90
+ typeof caretPositionOrRange !== "number" &&
91
+ caretPositionOrRange.start > -1
92
+ ) {
93
+ if (caretPositionOrRange.start > caretPositionOrRange.end) {
94
+ newSequenceData.chromatogramData = trimChromatogram({
95
+ chromatogramData: newSequenceData.chromatogramData,
96
+ range: {
97
+ start: caretPositionOrRange.start,
98
+ end: newSequenceData.sequence.length
99
+ },
100
+ justBaseCalls: isInsertSameLengthAsSelection
101
+ });
102
+ if (newSequenceData.chromatogramData) {
103
+ newSequenceData.chromatogramData = trimChromatogram({
104
+ chromatogramData: newSequenceData.chromatogramData,
105
+ range: {
106
+ start: 0,
107
+ end: caretPositionOrRange.end
108
+ },
109
+ justBaseCalls: isInsertSameLengthAsSelection
110
+ });
111
+ }
112
+ } else {
113
+ newSequenceData.chromatogramData = trimChromatogram({
114
+ chromatogramData: newSequenceData.chromatogramData,
115
+ range: {
116
+ start: caretPositionOrRange.start,
117
+ end: caretPositionOrRange.end
118
+ },
119
+ justBaseCalls: isInsertSameLengthAsSelection
120
+ });
121
+ }
122
+ }
123
+ if (sequenceDataToInsert.sequence && newSequenceData.chromatogramData) {
124
+ insertIntoChromatogram({
125
+ chromatogramData: newSequenceData.chromatogramData,
126
+ caretPosition:
127
+ typeof caretPositionOrRange !== "number" &&
128
+ caretPositionOrRange.start > -1
129
+ ? caretPositionOrRange.start
130
+ : (caretPositionOrRange as number),
131
+ seqToInsert: sequenceDataToInsert.sequence,
132
+ justBaseCalls: isInsertSameLengthAsSelection
133
+ });
134
+ }
135
+ }
136
+
137
+ //update the sequence
138
+ newSequenceData.sequence = adjustBpsToReplaceOrInsert(
139
+ existingSequenceData.sequence,
140
+ sequenceDataToInsert.sequence,
141
+ caretPositionOrRange
142
+ );
143
+ newSequenceData.size = newSequenceData.sequence.length;
144
+ newSequenceData.proteinSequence = adjustBpsToReplaceOrInsert(
145
+ existingSequenceData.proteinSequence || "",
146
+ sequenceDataToInsert.proteinSequence || "",
147
+ convertDnaCaretPositionOrRangeToAa(caretPositionOrRange)
148
+ );
149
+ newSequenceData.proteinSize = (newSequenceData.proteinSequence || "").length;
150
+
151
+ //handle the insert
152
+ modifiableTypes.forEach(annotationType => {
153
+ let existingAnnotations = existingSequenceData[
154
+ annotationType
155
+ ] as Annotation[];
156
+ if (!existingAnnotations) return;
157
+
158
+ //update the annotations:
159
+ //handle the delete if necessary
160
+ if (
161
+ typeof caretPositionOrRange !== "number" &&
162
+ caretPositionOrRange.start > -1
163
+ ) {
164
+ //we have a range! so let's delete it!
165
+ const range = caretPositionOrRange;
166
+ caretPosition = range.start > range.end ? 0 : range.start;
167
+ //update all annotations for the deletion
168
+ existingAnnotations = adjustAnnotationsToDelete(
169
+ existingAnnotations,
170
+ range,
171
+ existingSequenceData.sequence.length
172
+ );
173
+ }
174
+ //first clear the newSequenceData's annotations
175
+ newSequenceData[annotationType] = [];
176
+ const annotationsToInsert = sequenceDataToInsert[
177
+ annotationType
178
+ ] as Annotation[];
179
+ //in two steps adjust the annotations to the insert
180
+ if (newSequenceData[annotationType]) {
181
+ // Explicitly cast to unknown array inside concat to avoid TS errors with specific Annotation types if they diverge slightly,
182
+ // though strictly they should be Annotation[]
183
+ (newSequenceData[annotationType] as Annotation[]) = (
184
+ newSequenceData[annotationType] as Annotation[]
185
+ ).concat(
186
+ adjustAnnotationsToInsert(
187
+ existingAnnotations,
188
+ caretPosition,
189
+ insertLength
190
+ )
191
+ );
192
+ if (annotationsToInsert) {
193
+ (newSequenceData[annotationType] as Annotation[]) = (
194
+ newSequenceData[annotationType] as Annotation[]
195
+ ).concat(
196
+ adjustAnnotationsToInsert(annotationsToInsert, 0, caretPosition)
197
+ );
198
+ }
199
+ }
200
+ });
201
+
202
+ if (
203
+ maintainOriginSplit &&
204
+ typeof caretPositionOrRange !== "number" &&
205
+ caretPositionOrRange.start > caretPositionOrRange.end
206
+ ) {
207
+ //we're replacing around the origin and maintainOriginSplit=true
208
+ //so rotate the resulting seqData n bps
209
+ const caretPosToRotateTo =
210
+ existingSequenceData.sequence.length - caretPositionOrRange.start;
211
+ return rotateSequenceDataToPosition(
212
+ newSequenceData,
213
+ Math.min(caretPosToRotateTo, insertLength)
214
+ );
215
+ }
216
+ return newSequenceData;
217
+ }
218
+
219
+ function adjustAnnotationsToDelete(
220
+ annotationsToBeAdjusted: Annotation[],
221
+ range: Range,
222
+ maxLength: number
223
+ ): Annotation[] {
224
+ return map(annotationsToBeAdjusted, annotation => {
225
+ const newRange = adjustRangeToDeletionOfAnotherRange(
226
+ annotation,
227
+ range,
228
+ maxLength
229
+ );
230
+ const newLocations =
231
+ annotation.locations &&
232
+ annotation.locations
233
+ .map(loc => adjustRangeToDeletionOfAnotherRange(loc, range, maxLength))
234
+ .filter(range => !!range);
235
+
236
+ // Check if newRange is valid (start/end exist) before returning
237
+ if (!newRange) return null;
238
+
239
+ if (newLocations && newLocations.length) {
240
+ return {
241
+ ...newRange,
242
+ start: newLocations[0].start,
243
+ end: newLocations[newLocations.length - 1].end,
244
+ ...(newLocations.length > 0 && { locations: newLocations })
245
+ };
246
+ } else {
247
+ return newRange;
248
+ }
249
+ }).filter((range): range is Annotation => !!range); //filter any fully deleted ranges
250
+ }
251
+
252
+ function insertIntoChromatogram({
253
+ chromatogramData,
254
+ caretPosition,
255
+ seqToInsert,
256
+ justBaseCalls
257
+ }: {
258
+ chromatogramData: ChromatogramData;
259
+ caretPosition: number;
260
+ seqToInsert: string;
261
+ justBaseCalls?: boolean;
262
+ }): ChromatogramData | void {
263
+ if (!seqToInsert.length) return;
264
+
265
+ if (chromatogramData.baseCalls) {
266
+ (chromatogramData.baseCalls as unknown[]).splice(
267
+ caretPosition,
268
+ 0,
269
+ ...seqToInsert.split("")
270
+ );
271
+ }
272
+ if (justBaseCalls) {
273
+ //return early if just base calls
274
+ return chromatogramData;
275
+ }
276
+
277
+ const baseTracesToInsert: unknown[] = [];
278
+ const qualNumsToInsert: number[] = [];
279
+
280
+ for (let index = 0; index < seqToInsert.length; index++) {
281
+ qualNumsToInsert.push(0);
282
+ const toPush = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
283
+ baseTracesToInsert.push({
284
+ aTrace: toPush,
285
+ cTrace: toPush,
286
+ gTrace: toPush,
287
+ tTrace: toPush
288
+ });
289
+ }
290
+
291
+ if (chromatogramData.baseTraces) {
292
+ (chromatogramData.baseTraces as unknown[]).splice(
293
+ caretPosition,
294
+ 0,
295
+ ...baseTracesToInsert
296
+ );
297
+ }
298
+ if (chromatogramData.qualNums) {
299
+ (chromatogramData.qualNums as unknown[]).splice(
300
+ caretPosition,
301
+ 0,
302
+ ...qualNumsToInsert
303
+ );
304
+ }
305
+
306
+ return chromatogramData;
307
+ }
308
+
309
+ function trimChromatogram({
310
+ chromatogramData,
311
+ range: { start, end },
312
+ justBaseCalls
313
+ }: {
314
+ chromatogramData: ChromatogramData;
315
+ range: { start: number; end: number };
316
+ justBaseCalls?: boolean;
317
+ }): ChromatogramData {
318
+ [
319
+ "baseCalls",
320
+ ...(justBaseCalls ? [] : ["qualNums", "baseTraces", "basePos"])
321
+ ].forEach(type => {
322
+ if (chromatogramData[type]) {
323
+ (chromatogramData[type] as unknown[]).splice(start, end - start + 1);
324
+ }
325
+ });
326
+
327
+ return chromatogramData;
328
+ }
@@ -0,0 +1,5 @@
1
+ import { RestrictionEnzyme } from "./types";
2
+
3
+ export default function isEnzymeType2S(e: RestrictionEnzyme) {
4
+ return e.site.length < e.topSnipOffset || e.site.length < e.bottomSnipOffset;
5
+ }
@@ -0,0 +1,256 @@
1
+ import { each, forEach, startsWith, filter } from "lodash-es";
2
+ import {
3
+ getYOffsetForPotentiallyCircularRange,
4
+ splitRangeIntoTwoPartsIfItIsCircular,
5
+ checkIfPotentiallyCircularRangesOverlap
6
+ } from "@teselagen/range-utils";
7
+ import { Annotation } from "./types";
8
+
9
+ export interface MappedAnnotation extends Annotation {
10
+ yOffset?: number;
11
+ enclosingRangeType?: "beginning" | "end" | "beginningAndEnd";
12
+ annotation?: Annotation;
13
+ }
14
+
15
+ export default function mapAnnotationsToRows(
16
+ annotations: Annotation[],
17
+ sequenceLength: number,
18
+ bpsPerRow: number,
19
+ { splitForwardReverse }: { splitForwardReverse?: boolean } = {}
20
+ ) {
21
+ const annotationsToRowsMap: Record<number | string, MappedAnnotation[]> = {};
22
+ const yOffsetLevelMap: Record<
23
+ string | number,
24
+ { start: number; end: number }[][]
25
+ > = {};
26
+ const wrappedAnnotations: Record<string, boolean> = {};
27
+
28
+ each(annotations, annotation => {
29
+ const containsLocations = !!(
30
+ annotation.locations && annotation.locations.length
31
+ );
32
+ if (annotation.overlapsSelf) {
33
+ if (!wrappedAnnotations[annotation.id as string]) {
34
+ mapAnnotationToRows({
35
+ wrappedAnnotations,
36
+ annotation: {
37
+ ...annotation,
38
+ start: 0,
39
+ end: sequenceLength - 1,
40
+ id: `__tempAnnRemoveMe__${annotation.id}`,
41
+ overlapsSelf: false
42
+ },
43
+ sequenceLength,
44
+ bpsPerRow,
45
+ annotationsToRowsMap,
46
+ yOffsetLevelMap,
47
+ containsLocations,
48
+ splitForwardReverse
49
+ });
50
+ wrappedAnnotations[annotation.id as string] = true;
51
+ }
52
+ }
53
+
54
+ mapAnnotationToRows({
55
+ wrappedAnnotations,
56
+ annotation,
57
+ sequenceLength,
58
+ bpsPerRow,
59
+ annotationsToRowsMap,
60
+ yOffsetLevelMap,
61
+ containsLocations,
62
+ splitForwardReverse
63
+ });
64
+
65
+ if (containsLocations) {
66
+ annotation.locations?.forEach(location => {
67
+ mapAnnotationToRows({
68
+ wrappedAnnotations,
69
+ annotation,
70
+ sequenceLength,
71
+ bpsPerRow,
72
+ annotationsToRowsMap,
73
+ yOffsetLevelMap,
74
+ location: location as Annotation,
75
+ splitForwardReverse
76
+ });
77
+ });
78
+ }
79
+ });
80
+
81
+ forEach(annotationsToRowsMap, (annotationsForRow, i) => {
82
+ annotationsToRowsMap[i] = filter(
83
+ annotationsForRow,
84
+ ann => !startsWith(String(ann.id), "__tempAnnRemoveMe__")
85
+ );
86
+ });
87
+ return annotationsToRowsMap;
88
+ }
89
+
90
+ interface MapAnnotationToRowsParams {
91
+ wrappedAnnotations: Record<string, boolean>;
92
+ annotation: Annotation;
93
+ sequenceLength: number;
94
+ bpsPerRow: number;
95
+ annotationsToRowsMap: Record<number | string, MappedAnnotation[]>;
96
+ yOffsetLevelMap: Record<string | number, { start: number; end: number }[][]>;
97
+ location?: Annotation;
98
+ containsLocations?: boolean;
99
+ splitForwardReverse?: boolean;
100
+ }
101
+
102
+ function mapAnnotationToRows({
103
+ annotation,
104
+ sequenceLength,
105
+ bpsPerRow,
106
+ annotationsToRowsMap,
107
+ yOffsetLevelMap,
108
+ location,
109
+ containsLocations,
110
+ splitForwardReverse
111
+ }: MapAnnotationToRowsParams) {
112
+ const ranges = splitRangeIntoTwoPartsIfItIsCircular(
113
+ location || annotation,
114
+ sequenceLength
115
+ );
116
+
117
+ ranges.forEach((range, index) => {
118
+ const startingRow = Math.floor(range.start / bpsPerRow);
119
+ const endingRow = Math.floor(range.end / bpsPerRow);
120
+
121
+ for (let rowNumber = startingRow; rowNumber <= endingRow; rowNumber++) {
122
+ if (!annotationsToRowsMap[rowNumber]) {
123
+ annotationsToRowsMap[rowNumber] = [];
124
+ }
125
+ const key = splitForwardReverse
126
+ ? annotation.forward
127
+ ? rowNumber + "_forward"
128
+ : rowNumber + "_reverse"
129
+ : rowNumber;
130
+
131
+ const annotationsForRow = annotationsToRowsMap[rowNumber];
132
+ if (!yOffsetLevelMap[key]) {
133
+ yOffsetLevelMap[key] = [];
134
+ }
135
+
136
+ let yOffset: number | undefined;
137
+ const yOffsetsForRow = yOffsetLevelMap[key];
138
+ const start =
139
+ rowNumber === startingRow ? range.start : rowNumber * bpsPerRow;
140
+ const end =
141
+ rowNumber === endingRow
142
+ ? range.end
143
+ : rowNumber * bpsPerRow + bpsPerRow - 1;
144
+
145
+ if (annotation.overlapsSelf) {
146
+ annotationsForRow.forEach(ann => {
147
+ if (ann.id === `__tempAnnRemoveMe__${annotation.id}`) {
148
+ yOffset = ann.yOffset;
149
+ }
150
+ });
151
+ if (yOffset === undefined) {
152
+ annotationsForRow.forEach(ann => {
153
+ if (ann.id === annotation.id) {
154
+ yOffset = ann.yOffset;
155
+ }
156
+ });
157
+ }
158
+ } else {
159
+ if (location) {
160
+ annotationsForRow.forEach(ann => {
161
+ if (ann.id === annotation.id) {
162
+ yOffset = ann.yOffset;
163
+ }
164
+ });
165
+ } else {
166
+ if (
167
+ index > 0 &&
168
+ annotationsForRow.length &&
169
+ annotationsForRow[annotationsForRow.length - 1].annotation ===
170
+ annotation
171
+ ) {
172
+ yOffset = annotationsForRow[annotationsForRow.length - 1].yOffset;
173
+ } else {
174
+ const siblingRangesOnThisRow = ranges.slice(index + 1).filter(r => {
175
+ const rStartRow = Math.floor(r.start / bpsPerRow);
176
+ const rEndRow = Math.floor(r.end / bpsPerRow);
177
+ return rowNumber >= rStartRow && rowNumber <= rEndRow;
178
+ });
179
+
180
+ if (siblingRangesOnThisRow.length > 0) {
181
+ // We have future ranges for this annotation on this row.
182
+ // We must choose a yOffset that works for the current range AND all future ranges.
183
+ let foundYOffset = -1;
184
+ yOffsetsForRow.some((rangesAlreadyAddedToYOffset, levelIndex) => {
185
+ const rangeBlocked = rangesAlreadyAddedToYOffset.some(
186
+ comparisonRange => {
187
+ return checkIfPotentiallyCircularRangesOverlap(
188
+ range,
189
+ comparisonRange
190
+ );
191
+ }
192
+ );
193
+ if (rangeBlocked) return false;
194
+
195
+ // Check siblings
196
+ const siblingBlocked = siblingRangesOnThisRow.some(
197
+ siblingRange => {
198
+ return rangesAlreadyAddedToYOffset.some(comparisonRange => {
199
+ return checkIfPotentiallyCircularRangesOverlap(
200
+ siblingRange,
201
+ comparisonRange
202
+ );
203
+ });
204
+ }
205
+ );
206
+
207
+ if (!siblingBlocked) {
208
+ foundYOffset = levelIndex;
209
+ return true;
210
+ }
211
+ return false;
212
+ });
213
+
214
+ if (foundYOffset > -1) {
215
+ yOffset = foundYOffset;
216
+ yOffsetsForRow[foundYOffset].push(range);
217
+ } else {
218
+ // Create new level
219
+ yOffset = yOffsetsForRow.length;
220
+ yOffsetsForRow.push([range]);
221
+ }
222
+ } else {
223
+ yOffset = getYOffsetForPotentiallyCircularRange(
224
+ range,
225
+ yOffsetsForRow,
226
+ false
227
+ );
228
+ }
229
+ }
230
+ if (yOffset !== undefined) {
231
+ if (!yOffsetsForRow[yOffset]) yOffsetsForRow[yOffset] = [];
232
+ yOffsetsForRow[yOffset].push({
233
+ start: start,
234
+ end: end
235
+ });
236
+ }
237
+ }
238
+ }
239
+
240
+ annotationsForRow.push({
241
+ ...annotation,
242
+ id: annotation.id,
243
+ annotation: annotation,
244
+ start: start,
245
+ end: end,
246
+ ...(containsLocations && { containsLocations }),
247
+ ...(location && { isJoinedLocation: !!location }),
248
+ yOffset: yOffset,
249
+ enclosingRangeType: range.type as
250
+ | "beginning"
251
+ | "end"
252
+ | "beginningAndEnd"
253
+ });
254
+ }
255
+ });
256
+ }
@@ -0,0 +1,24 @@
1
+ import { cloneDeep } from "lodash-es";
2
+ import { getYOffsetsForPotentiallyCircularRanges } from "@teselagen/range-utils";
3
+ import { annotationTypes } from "./annotationTypes";
4
+ import { SequenceData, Annotation } from "./types";
5
+
6
+ //basically just adds yOffsets to the annotations
7
+ export default function prepareCircularViewData(sequenceData: SequenceData) {
8
+ const clonedSeqData = cloneDeep(sequenceData);
9
+ annotationTypes.forEach(annotationType => {
10
+ if (annotationType !== "cutsites") {
11
+ const annotations = clonedSeqData[annotationType] as Annotation[];
12
+ if (annotations) {
13
+ const maxYOffset = getYOffsetsForPotentiallyCircularRanges(
14
+ annotations,
15
+ true
16
+ ).maxYOffset;
17
+ (
18
+ clonedSeqData[annotationType] as Annotation[] & { maxYOffset: number }
19
+ ).maxYOffset = maxYOffset;
20
+ }
21
+ }
22
+ });
23
+ return clonedSeqData;
24
+ }
@@ -0,0 +1,61 @@
1
+ // ac.throw([ac.posInt, ac.posInt, ac.bool], arguments);
2
+ import mapAnnotationsToRows, { MappedAnnotation } from "./mapAnnotationsToRows";
3
+
4
+ import { annotationTypes } from "./annotationTypes";
5
+
6
+ import { SequenceData, Annotation } from "./types";
7
+
8
+ export interface RowData {
9
+ rowNumber: number;
10
+ start: number;
11
+ end: number;
12
+ sequence: string;
13
+ [key: string]: MappedAnnotation[] | number | string;
14
+ }
15
+
16
+ export default function prepareRowData(
17
+ sequenceData: SequenceData,
18
+ bpsPerRow: number
19
+ ) {
20
+ // ac.throw([ac.sequenceData, ac.posInt], arguments);
21
+ const sequenceLength = sequenceData.sequence.length;
22
+ const totalRows = Math.ceil(sequenceLength / bpsPerRow) || 1; //this check makes sure there is always at least 1 row!
23
+ const rows: RowData[] = [];
24
+ const rowMap: Record<
25
+ string,
26
+ Record<string | number, MappedAnnotation[]>
27
+ > = {};
28
+ annotationTypes.forEach(type => {
29
+ rowMap[type] = mapAnnotationsToRows(
30
+ (sequenceData[type] as Annotation[]) || [],
31
+ sequenceLength,
32
+ bpsPerRow,
33
+ { splitForwardReverse: type === "primers" }
34
+ );
35
+ });
36
+
37
+ for (let rowNumber = 0; rowNumber < totalRows; rowNumber++) {
38
+ const row: RowData = {
39
+ rowNumber,
40
+ start: 0,
41
+ end: 0,
42
+ sequence: ""
43
+ };
44
+ row.rowNumber = rowNumber;
45
+ row.start = rowNumber * bpsPerRow;
46
+ row.end =
47
+ (rowNumber + 1) * bpsPerRow - 1 < sequenceLength
48
+ ? (rowNumber + 1) * bpsPerRow - 1
49
+ : sequenceLength - 1;
50
+ if (row.end < 0) {
51
+ row.end = 0;
52
+ }
53
+ annotationTypes.forEach(type => {
54
+ row[type] = rowMap[type][rowNumber] || [];
55
+ });
56
+ row.sequence = sequenceData.sequence.slice(row.start, row.end + 1);
57
+
58
+ rows[rowNumber] = row;
59
+ }
60
+ return rows;
61
+ }
@@ -138,6 +138,7 @@
138
138
  },
139
139
  "start": 1,
140
140
  "end": 3,
141
+ "forward": true,
141
142
  "yOffset": 0,
142
143
  "enclosingRangeType": "beginningAndEnd"
143
144
  }