@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.
- package/DNAComplementMap.d.ts +1 -1
- package/addGapsToSeqReads.d.ts +16 -3
- package/adjustAnnotationsToInsert.d.ts +2 -1
- package/adjustBpsToReplaceOrInsert.d.ts +2 -1
- package/aliasedEnzymesByName.d.ts +37 -1
- package/aminoAcidToDegenerateDnaMap.d.ts +1 -31
- package/aminoAcidToDegenerateRnaMap.d.ts +1 -1
- package/annotateSingleSeq.d.ts +5 -4
- package/annotationTypes.d.ts +2 -2
- package/autoAnnotate.d.ts +17 -8
- package/bioData.d.ts +10 -58
- package/calculateEndStability.d.ts +1 -1
- package/calculateNebTa.d.ts +6 -1
- package/calculateNebTm.d.ts +6 -4
- package/calculatePercentGC.d.ts +1 -1
- package/calculateSantaLuciaTm.d.ts +28 -114
- package/calculateTm.d.ts +13 -1
- package/computeDigestFragments.d.ts +30 -24
- package/condensePairwiseAlignmentDifferences.d.ts +1 -1
- package/convertAACaretPositionOrRangeToDna.d.ts +2 -1
- package/convertDnaCaretPositionOrRangeToAA.d.ts +2 -1
- package/cutSequenceByRestrictionEnzyme.d.ts +2 -1
- package/defaultEnzymesByName.d.ts +2 -1
- package/degenerateDnaToAminoAcidMap.d.ts +1 -1
- package/degenerateRnaToAminoAcidMap.d.ts +1 -1
- package/deleteSequenceDataAtRange.d.ts +2 -1
- package/diffUtils.d.ts +9 -7
- package/doesEnzymeChopOutsideOfRecognitionSite.d.ts +2 -1
- package/featureTypesAndColors.d.ts +19 -6
- package/filterSequenceString.d.ts +14 -10
- package/findApproxMatches.d.ts +7 -1
- package/findNearestRangeOfSequenceOverlapToPosition.d.ts +2 -1
- package/findOrfsInPlasmid.d.ts +2 -11
- package/findSequenceMatches.d.ts +11 -1
- package/generateAnnotations.d.ts +2 -1
- package/generateSequenceData.d.ts +8 -13
- package/getAllInsertionsInSeqReads.d.ts +11 -1
- package/getAminoAcidDataForEachBaseOfDna.d.ts +6 -5
- package/getAminoAcidFromSequenceTriplet.d.ts +1 -1
- package/getAminoAcidStringFromSequenceString.d.ts +3 -1
- package/getCodonRangeForAASliver.d.ts +3 -4
- package/getComplementAminoAcidStringFromSequenceString.d.ts +1 -1
- package/getComplementSequenceAndAnnotations.d.ts +5 -1
- package/getComplementSequenceString.d.ts +1 -1
- package/getCutsiteType.d.ts +2 -1
- package/getCutsitesFromSequence.d.ts +2 -1
- package/getDegenerateDnaStringFromAAString.d.ts +1 -1
- package/getDegenerateRnaStringFromAAString.d.ts +1 -1
- package/getDigestFragmentsForCutsites.d.ts +4 -1
- package/getDigestFragmentsForRestrictionEnzymes.d.ts +8 -1
- package/getInsertBetweenVals.d.ts +2 -1
- package/getLeftAndRightOfSequenceInRangeGivenPosition.d.ts +2 -1
- package/getOrfsFromSequence.d.ts +17 -11
- package/getOverlapBetweenTwoSequences.d.ts +2 -1
- package/getPossiblePartsFromSequenceAndEnzymes.d.ts +18 -1
- package/getReverseAminoAcidStringFromSequenceString.d.ts +1 -1
- package/getReverseComplementAminoAcidStringFromSequenceString.d.ts +1 -1
- package/getReverseComplementAnnotation.d.ts +11 -1
- package/getReverseComplementSequenceAndAnnotations.d.ts +5 -1
- package/getReverseComplementSequenceString.d.ts +1 -1
- package/getReverseSequenceString.d.ts +1 -1
- package/getSequenceDataBetweenRange.d.ts +9 -1
- package/getVirtualDigest.d.ts +11 -10
- package/guessIfSequenceIsDnaAndNotProtein.d.ts +5 -1
- package/index.cjs +733 -484
- package/index.d.ts +8 -5
- package/index.js +733 -484
- package/index.umd.cjs +733 -484
- package/insertGapsIntoRefSeq.d.ts +2 -1
- package/insertSequenceDataAtPositionOrRange.d.ts +10 -1
- package/isEnzymeType2S.d.ts +2 -1
- package/mapAnnotationsToRows.d.ts +9 -1
- package/package.json +9 -6
- package/prepareCircularViewData.d.ts +2 -1
- package/prepareRowData.d.ts +7 -3
- package/proteinAlphabet.d.ts +1 -1
- package/rotateBpsToPosition.d.ts +1 -1
- package/rotateSequenceDataToPosition.d.ts +3 -1
- package/shiftAnnotationsByLen.d.ts +4 -3
- package/src/DNAComplementMap.ts +32 -0
- package/src/addGapsToSeqReads.ts +436 -0
- package/src/adjustAnnotationsToInsert.ts +20 -0
- package/src/adjustBpsToReplaceOrInsert.ts +73 -0
- package/src/aliasedEnzymesByName.ts +7366 -0
- package/src/aminoAcidToDegenerateDnaMap.ts +32 -0
- package/src/aminoAcidToDegenerateRnaMap.ts +32 -0
- package/src/annotateSingleSeq.ts +37 -0
- package/src/annotationTypes.ts +23 -0
- package/src/autoAnnotate.test.js +0 -1
- package/src/autoAnnotate.ts +290 -0
- package/src/bioData.ts +65 -0
- package/src/calculateEndStability.ts +91 -0
- package/src/calculateNebTa.ts +46 -0
- package/src/calculateNebTm.ts +132 -0
- package/src/calculatePercentGC.ts +3 -0
- package/src/calculateSantaLuciaTm.ts +184 -0
- package/src/calculateTm.ts +242 -0
- package/src/computeDigestFragments.ts +238 -0
- package/src/condensePairwiseAlignmentDifferences.ts +85 -0
- package/src/convertAACaretPositionOrRangeToDna.ts +28 -0
- package/src/convertDnaCaretPositionOrRangeToAA.ts +28 -0
- package/src/cutSequenceByRestrictionEnzyme.ts +345 -0
- package/src/defaultEnzymesByName.ts +280 -0
- package/src/degenerateDnaToAminoAcidMap.ts +5 -0
- package/src/degenerateRnaToAminoAcidMap.ts +5 -0
- package/src/deleteSequenceDataAtRange.ts +13 -0
- package/src/diffUtils.ts +80 -0
- package/src/doesEnzymeChopOutsideOfRecognitionSite.ts +16 -0
- package/src/featureTypesAndColors.js +1 -1
- package/src/featureTypesAndColors.ts +167 -0
- package/src/filterSequenceString.ts +153 -0
- package/src/findApproxMatches.ts +58 -0
- package/src/findNearestRangeOfSequenceOverlapToPosition.ts +43 -0
- package/src/findOrfsInPlasmid.js +6 -1
- package/src/findOrfsInPlasmid.ts +31 -0
- package/src/findSequenceMatches.ts +154 -0
- package/src/generateAnnotations.ts +39 -0
- package/src/generateSequenceData.ts +212 -0
- package/src/getAllInsertionsInSeqReads.ts +100 -0
- package/src/getAminoAcidDataForEachBaseOfDna.ts +305 -0
- package/src/getAminoAcidFromSequenceTriplet.ts +27 -0
- package/src/getAminoAcidStringFromSequenceString.ts +36 -0
- package/src/getCodonRangeForAASliver.ts +73 -0
- package/src/getComplementAminoAcidStringFromSequenceString.ts +10 -0
- package/src/getComplementSequenceAndAnnotations.ts +25 -0
- package/src/getComplementSequenceString.ts +23 -0
- package/src/getCutsiteType.ts +18 -0
- package/src/getCutsitesFromSequence.ts +22 -0
- package/src/getDegenerateDnaStringFromAAString.ts +15 -0
- package/src/getDegenerateRnaStringFromAAString.ts +15 -0
- package/src/getDigestFragmentsForCutsites.ts +126 -0
- package/src/getDigestFragmentsForRestrictionEnzymes.ts +50 -0
- package/src/getInsertBetweenVals.ts +31 -0
- package/src/getLeftAndRightOfSequenceInRangeGivenPosition.ts +40 -0
- package/src/getMassOfAaString.ts +29 -0
- package/src/getOrfsFromSequence.ts +132 -0
- package/src/getOverlapBetweenTwoSequences.ts +30 -0
- package/src/getPossiblePartsFromSequenceAndEnzymes.ts +149 -0
- package/src/getReverseAminoAcidStringFromSequenceString.ts +22 -0
- package/src/getReverseComplementAminoAcidStringFromSequenceString.ts +10 -0
- package/src/getReverseComplementAnnotation.ts +33 -0
- package/src/getReverseComplementSequenceAndAnnotations.ts +46 -0
- package/src/getReverseComplementSequenceString.ts +18 -0
- package/src/getReverseSequenceString.ts +12 -0
- package/src/getSequenceDataBetweenRange.ts +154 -0
- package/src/getVirtualDigest.ts +139 -0
- package/src/guessIfSequenceIsDnaAndNotProtein.ts +39 -0
- package/src/index.test.ts +43 -0
- package/src/index.ts +111 -0
- package/src/insertGapsIntoRefSeq.ts +43 -0
- package/src/insertSequenceDataAtPosition.ts +2 -0
- package/src/insertSequenceDataAtPositionOrRange.ts +328 -0
- package/src/isEnzymeType2S.ts +5 -0
- package/src/mapAnnotationsToRows.ts +256 -0
- package/src/prepareCircularViewData.ts +24 -0
- package/src/prepareRowData.ts +61 -0
- package/src/prepareRowData_output1.json +1 -0
- package/src/proteinAlphabet.ts +271 -0
- package/src/rotateBpsToPosition.ts +12 -0
- package/src/rotateSequenceDataToPosition.ts +54 -0
- package/src/shiftAnnotationsByLen.ts +24 -0
- package/src/threeLetterSequenceStringToAminoAcidMap.ts +198 -0
- package/src/tidyUpAnnotation.ts +205 -0
- package/src/tidyUpSequenceData.ts +213 -0
- package/src/types.ts +109 -0
- package/threeLetterSequenceStringToAminoAcidMap.d.ts +11 -921
- package/tidyUpAnnotation.d.ts +13 -11
- package/tidyUpSequenceData.d.ts +15 -1
- 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,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
|
+
}
|