@teselagen/ove 0.8.40 → 0.8.42
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/AlignmentView/findAlignmentDifferences.d.ts +53 -0
- package/index.cjs.js +260 -153
- package/index.es.js +260 -153
- package/index.umd.js +260 -153
- package/ove.css +80 -0
- package/package.json +2 -2
- package/src/AlignmentView/AlignmentVisibilityTool.js +9 -11
- package/src/AlignmentView/Minimap.js +21 -3
- package/src/AlignmentView/Mismatches.js +164 -139
- package/src/AlignmentView/findAlignmentDifferences.js +116 -0
- package/src/AlignmentView/findAlignmentDifferences.test.js +208 -0
- package/src/AlignmentView/index.js +18 -2
- package/src/AlignmentView/style.css +80 -0
- package/src/redux/alignments.js +58 -20
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/* Copyright (C) 2018 TeselaGen Biotechnology, Inc. */
|
|
2
|
+
import * as chai from "chai";
|
|
3
|
+
import {
|
|
4
|
+
findAlignmentDifferences,
|
|
5
|
+
groupConsecutiveDifferences
|
|
6
|
+
} from "./findAlignmentDifferences";
|
|
7
|
+
|
|
8
|
+
chai.should();
|
|
9
|
+
const { expect } = chai;
|
|
10
|
+
|
|
11
|
+
describe("findAlignmentDifferences", () => {
|
|
12
|
+
it("returns empty array when fewer than 2 tracks", () => {
|
|
13
|
+
expect(findAlignmentDifferences([])).to.deep.equal([]);
|
|
14
|
+
expect(findAlignmentDifferences(["ACGT"])).to.deep.equal([]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns empty array when template is empty", () => {
|
|
18
|
+
expect(findAlignmentDifferences(["", ""])).to.deep.equal([]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns empty array when sequences are identical", () => {
|
|
22
|
+
expect(findAlignmentDifferences(["ACGT", "ACGT"])).to.deep.equal([]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("detects a single mismatch", () => {
|
|
26
|
+
// pos 1: A vs T
|
|
27
|
+
const diffs = findAlignmentDifferences(["ACGT", "ATGT"]);
|
|
28
|
+
expect(diffs).to.have.lengthOf(1);
|
|
29
|
+
expect(diffs[0]).to.deep.include({ position: 1, type: "mismatch" });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("detects multiple mismatches", () => {
|
|
33
|
+
const diffs = findAlignmentDifferences(["AAAA", "TATA"]);
|
|
34
|
+
expect(diffs).to.have.lengthOf(2);
|
|
35
|
+
expect(diffs.map(d => d.position)).to.deep.equal([0, 2]);
|
|
36
|
+
diffs.forEach(d => expect(d.type).to.equal("mismatch"));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("detects mismatches in protein sequences (amino acids)", () => {
|
|
40
|
+
const diffs = findAlignmentDifferences(["MAST", "MAST"]);
|
|
41
|
+
expect(diffs).to.have.lengthOf(0);
|
|
42
|
+
|
|
43
|
+
const diffs2 = findAlignmentDifferences(["MAST", "MVST"]);
|
|
44
|
+
expect(diffs2).to.have.lengthOf(1);
|
|
45
|
+
expect(diffs2[0]).to.deep.include({ position: 1, type: "mismatch" });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("is case-insensitive", () => {
|
|
49
|
+
const diffs = findAlignmentDifferences(["ACGT", "acgt"]);
|
|
50
|
+
expect(diffs).to.have.lengthOf(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("detects a single-base insertion (gap in template)", () => {
|
|
54
|
+
// template: A-GT, query: ACGT → position 1 is an insertion
|
|
55
|
+
const diffs = findAlignmentDifferences(["A-GT", "ACGT"]);
|
|
56
|
+
const insertions = diffs.filter(d => d.type === "insertion");
|
|
57
|
+
expect(insertions).to.have.lengthOf(1);
|
|
58
|
+
expect(insertions[0].position).to.equal(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("detects a multi-base insertion", () => {
|
|
62
|
+
// template: A---GT, query: ACCCGT
|
|
63
|
+
const diffs = findAlignmentDifferences(["A---GT", "ACCCGT"]);
|
|
64
|
+
const insertions = diffs.filter(d => d.type === "insertion");
|
|
65
|
+
expect(insertions).to.have.lengthOf(3);
|
|
66
|
+
expect(insertions.map(d => d.position)).to.deep.equal([1, 2, 3]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("detects a single-base deletion (gap in non-template)", () => {
|
|
70
|
+
// template: ACGT, query: A-GT → position 1 is a deletion
|
|
71
|
+
const diffs = findAlignmentDifferences(["ACGT", "A-GT"]);
|
|
72
|
+
const deletions = diffs.filter(d => d.type === "deletion");
|
|
73
|
+
expect(deletions).to.have.lengthOf(1);
|
|
74
|
+
expect(deletions[0].position).to.equal(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("detects a multi-base deletion", () => {
|
|
78
|
+
// template: ACCCGT, query: A---GT
|
|
79
|
+
const diffs = findAlignmentDifferences(["ACCCGT", "A---GT"]);
|
|
80
|
+
const deletions = diffs.filter(d => d.type === "deletion");
|
|
81
|
+
expect(deletions).to.have.lengthOf(3);
|
|
82
|
+
expect(deletions.map(d => d.position)).to.deep.equal([1, 2, 3]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("detects leading non-aligned region", () => {
|
|
86
|
+
// query hasn't started yet at position 0-1
|
|
87
|
+
const diffs = findAlignmentDifferences(["ACGT", "--GT"]);
|
|
88
|
+
const gaps = diffs.filter(d => d.type === "gap");
|
|
89
|
+
expect(gaps).to.have.lengthOf(2);
|
|
90
|
+
expect(gaps.map(d => d.position)).to.deep.equal([0, 1]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("detects trailing non-aligned region", () => {
|
|
94
|
+
// query has ended after position 1
|
|
95
|
+
const diffs = findAlignmentDifferences(["ACGT", "AC--"]);
|
|
96
|
+
const gaps = diffs.filter(d => d.type === "gap");
|
|
97
|
+
expect(gaps).to.have.lengthOf(2);
|
|
98
|
+
expect(gaps.map(d => d.position)).to.deep.equal([2, 3]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("detects both leading and trailing non-aligned regions", () => {
|
|
102
|
+
const diffs = findAlignmentDifferences(["ACGTAC", "-CGTA-"]);
|
|
103
|
+
const gaps = diffs.filter(d => d.type === "gap");
|
|
104
|
+
expect(gaps.map(d => d.position)).to.deep.equal([0, 5]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("fully-gapped query returns all positions as gap", () => {
|
|
108
|
+
const diffs = findAlignmentDifferences(["ACGT", "----"]);
|
|
109
|
+
expect(diffs).to.have.lengthOf(4);
|
|
110
|
+
diffs.forEach(d => expect(d.type).to.equal("gap"));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("handles mixed difference types in one alignment", () => {
|
|
114
|
+
// template: AACGT
|
|
115
|
+
// query: -ACGG
|
|
116
|
+
// pos 0: gap (leading '-' in query), pos 4: mismatch (T vs G)
|
|
117
|
+
const diffs = findAlignmentDifferences(["AACGT", "-ACGG"]);
|
|
118
|
+
expect(diffs.find(d => d.position === 0)?.type).to.equal("gap");
|
|
119
|
+
expect(diffs.find(d => d.position === 4)?.type).to.equal("mismatch");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("handles multiple non-template tracks", () => {
|
|
123
|
+
// template: ACGT
|
|
124
|
+
// track1: ATGT → mismatch at pos 1
|
|
125
|
+
// track2: AC-T → deletion at pos 2
|
|
126
|
+
const diffs = findAlignmentDifferences(["ACGT", "ATGT", "AC-T"]);
|
|
127
|
+
expect(diffs.find(d => d.position === 1)?.type).to.equal("mismatch");
|
|
128
|
+
expect(diffs.find(d => d.position === 2)?.type).to.equal("deletion");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("includes all track bases in returned diff object", () => {
|
|
132
|
+
// mismatch at position 1: template 'C' vs non-template 'T'
|
|
133
|
+
const diffs = findAlignmentDifferences(["ACGT", "ATGT"]);
|
|
134
|
+
expect(diffs[0].bases).to.have.lengthOf(2);
|
|
135
|
+
expect(diffs[0].bases[0]).to.equal("c"); // template at pos 1 (lowercased)
|
|
136
|
+
expect(diffs[0].bases[1]).to.equal("t"); // non-template at pos 1 (lowercased)
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("groupConsecutiveDifferences", () => {
|
|
141
|
+
it("returns empty array for empty input", () => {
|
|
142
|
+
expect(groupConsecutiveDifferences([])).to.deep.equal([]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("does not group mismatches — each stays individual", () => {
|
|
146
|
+
// 4 consecutive mismatches → 4 separate entries
|
|
147
|
+
const diffs = findAlignmentDifferences(["AAAA", "TTTT"]);
|
|
148
|
+
const grouped = groupConsecutiveDifferences(diffs);
|
|
149
|
+
expect(grouped).to.have.lengthOf(4);
|
|
150
|
+
grouped.forEach((g, i) => {
|
|
151
|
+
expect(g.type).to.equal("mismatch");
|
|
152
|
+
expect(g.start).to.equal(i);
|
|
153
|
+
expect(g.end).to.equal(i);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("groups consecutive deletions into one region", () => {
|
|
158
|
+
// template: ACCCGT, query: A---GT → 3 consecutive deletions
|
|
159
|
+
const diffs = findAlignmentDifferences(["ACCCGT", "A---GT"]);
|
|
160
|
+
const grouped = groupConsecutiveDifferences(diffs);
|
|
161
|
+
expect(grouped).to.have.lengthOf(1);
|
|
162
|
+
expect(grouped[0]).to.deep.include({ type: "deletion", start: 1, end: 3 });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("groups consecutive insertions into one region", () => {
|
|
166
|
+
// template: A---GT, query: ACCCGT → 3 consecutive insertions
|
|
167
|
+
const diffs = findAlignmentDifferences(["A---GT", "ACCCGT"]);
|
|
168
|
+
const grouped = groupConsecutiveDifferences(diffs);
|
|
169
|
+
expect(grouped).to.have.lengthOf(1);
|
|
170
|
+
expect(grouped[0]).to.deep.include({ type: "insertion", start: 1, end: 3 });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("groups consecutive gaps into one region", () => {
|
|
174
|
+
// leading 2-base gap + trailing 2-base gap
|
|
175
|
+
const diffs = findAlignmentDifferences(["ACGTAC", "--GT--"]);
|
|
176
|
+
const grouped = groupConsecutiveDifferences(diffs);
|
|
177
|
+
expect(grouped).to.have.lengthOf(2);
|
|
178
|
+
expect(grouped[0]).to.deep.include({ type: "gap", start: 0, end: 1 });
|
|
179
|
+
expect(grouped[1]).to.deep.include({ type: "gap", start: 4, end: 5 });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("keeps non-consecutive same-type differences separate", () => {
|
|
183
|
+
// two deletions separated by a match; ends with base so trailing '-' is still aligned
|
|
184
|
+
// template: ACACA, query: A-A-A → deletions at pos 1 and 3, matches elsewhere
|
|
185
|
+
const diffs = findAlignmentDifferences(["ACACA", "A-A-A"]);
|
|
186
|
+
const grouped = groupConsecutiveDifferences(diffs);
|
|
187
|
+
expect(grouped).to.have.lengthOf(2);
|
|
188
|
+
expect(grouped[0]).to.deep.include({ type: "deletion", start: 1, end: 1 });
|
|
189
|
+
expect(grouped[1]).to.deep.include({ type: "deletion", start: 3, end: 3 });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("handles mixed types — groups each type's runs independently", () => {
|
|
193
|
+
// template: A--CGT, query: ACCC-T
|
|
194
|
+
// pos 0: A vs A → match
|
|
195
|
+
// pos 1,2: template '-', query 'C','C' → insertions
|
|
196
|
+
// pos 3: C vs C → match
|
|
197
|
+
// pos 4: G vs '-' → deletion
|
|
198
|
+
// pos 5: T vs T → match
|
|
199
|
+
const diffs = findAlignmentDifferences(["A--CGT", "ACCC-T"]);
|
|
200
|
+
const grouped = groupConsecutiveDifferences(diffs);
|
|
201
|
+
const insertionGroups = grouped.filter(g => g.type === "insertion");
|
|
202
|
+
const deletionGroups = grouped.filter(g => g.type === "deletion");
|
|
203
|
+
expect(insertionGroups).to.have.lengthOf(1);
|
|
204
|
+
expect(insertionGroups[0]).to.deep.include({ start: 1, end: 2 });
|
|
205
|
+
expect(deletionGroups).to.have.lengthOf(1);
|
|
206
|
+
expect(deletionGroups[0]).to.deep.include({ start: 4, end: 4 });
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -163,6 +163,11 @@ export const AlignmentView = props => {
|
|
|
163
163
|
const [tempTrimAfter, setTempTrimAfter] = useState({});
|
|
164
164
|
const [tempTrimmingCaret, setTempTrimmingCaret] = useState({});
|
|
165
165
|
const [searchMatchLayers, setSearchMatchLayers] = React.useState([]);
|
|
166
|
+
const [activeFilterType, setActiveFilterType] = useState("all");
|
|
167
|
+
|
|
168
|
+
const handleFilterChange = useCallback(({ activeFilter }) => {
|
|
169
|
+
setActiveFilterType(activeFilter);
|
|
170
|
+
}, []);
|
|
166
171
|
const bindOutsideChangeHelper = useRef({});
|
|
167
172
|
const alignmentHolder = useRef(null);
|
|
168
173
|
const alignmentHolderTop = useRef(null);
|
|
@@ -1018,7 +1023,13 @@ export const AlignmentView = props => {
|
|
|
1018
1023
|
})
|
|
1019
1024
|
: linearViewOptions))}
|
|
1020
1025
|
additionalSelectionLayers={[
|
|
1021
|
-
...(
|
|
1026
|
+
...(i !== 0
|
|
1027
|
+
? (additionalSelectionLayers || []).filter(layer =>
|
|
1028
|
+
activeFilterType === "all"
|
|
1029
|
+
? layer.differenceType !== "gap"
|
|
1030
|
+
: layer.differenceType === activeFilterType
|
|
1031
|
+
)
|
|
1032
|
+
: additionalSelectionLayers || []),
|
|
1022
1033
|
...(searchMatchLayers || [])
|
|
1023
1034
|
]}
|
|
1024
1035
|
dimensions={{
|
|
@@ -1758,7 +1769,11 @@ export const AlignmentView = props => {
|
|
|
1758
1769
|
id={id}
|
|
1759
1770
|
setSearchMatchLayers={setSearchMatchLayers}
|
|
1760
1771
|
/>
|
|
1761
|
-
<FindMismatches
|
|
1772
|
+
<FindMismatches
|
|
1773
|
+
alignmentJson={alignmentTracks}
|
|
1774
|
+
id={id}
|
|
1775
|
+
onFilterChange={handleFilterChange}
|
|
1776
|
+
/>
|
|
1762
1777
|
{additionalTopEl}
|
|
1763
1778
|
{saveMessage && (
|
|
1764
1779
|
<div
|
|
@@ -1869,6 +1884,7 @@ export const AlignmentView = props => {
|
|
|
1869
1884
|
</>
|
|
1870
1885
|
}
|
|
1871
1886
|
alignmentTracks={alignmentTracks}
|
|
1887
|
+
activeFilterType={activeFilterType}
|
|
1872
1888
|
dimensions={{
|
|
1873
1889
|
width: Math.max(width, 10) || 10
|
|
1874
1890
|
}}
|
|
@@ -64,6 +64,10 @@
|
|
|
64
64
|
/* .alignmentViewTrackContainer:hover .alignmentTrackNameDiv {
|
|
65
65
|
opacity: 1 !important;
|
|
66
66
|
} */
|
|
67
|
+
.ve-alignment-top-bar {
|
|
68
|
+
align-items: center;
|
|
69
|
+
}
|
|
70
|
+
|
|
67
71
|
.ve-alignment-top-bar > * {
|
|
68
72
|
overflow-wrap: normal;
|
|
69
73
|
flex: 0 0 auto;
|
|
@@ -80,6 +84,82 @@
|
|
|
80
84
|
.veAlignmentMismatch {
|
|
81
85
|
opacity: 0.9;
|
|
82
86
|
}
|
|
87
|
+
|
|
88
|
+
.veDiffNavigator {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 4px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Menu item content layout */
|
|
95
|
+
.veDiffMenuItem-inner {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 6px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Navigation pill — groups prev/counter/next into one unit */
|
|
102
|
+
.veDiffNav {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
border-radius: 3px;
|
|
106
|
+
background: rgba(92, 112, 128, 0.06);
|
|
107
|
+
border: 1px solid rgba(92, 112, 128, 0.18);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.veDiffNav-center {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: baseline;
|
|
113
|
+
gap: 3px;
|
|
114
|
+
padding: 0 6px;
|
|
115
|
+
min-width: 64px;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.veDiffNav-fraction {
|
|
120
|
+
font-size: 11px;
|
|
121
|
+
font-variant-numeric: tabular-nums;
|
|
122
|
+
color: #5c7080;
|
|
123
|
+
line-height: 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.veDiffNav-sep {
|
|
127
|
+
margin: 0 1px;
|
|
128
|
+
opacity: 0.45;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Position number — monospace fits sequence coordinates */
|
|
132
|
+
.veDiffNav-pos {
|
|
133
|
+
font-size: 10px;
|
|
134
|
+
font-family: monospace;
|
|
135
|
+
font-variant-numeric: tabular-nums;
|
|
136
|
+
color: #a7b6c2;
|
|
137
|
+
line-height: 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.veDiffNav-empty {
|
|
141
|
+
font-size: 11px;
|
|
142
|
+
font-style: italic;
|
|
143
|
+
color: #a7b6c2;
|
|
144
|
+
padding: 0 4px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.bp3-dark .veDiffNav {
|
|
148
|
+
background: rgba(167, 182, 194, 0.05);
|
|
149
|
+
border-color: rgba(167, 182, 194, 0.14);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.bp3-dark .veDiffNav-fraction {
|
|
153
|
+
color: #5c7080;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.bp3-dark .veDiffNav-pos {
|
|
157
|
+
color: #4f6272;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.bp3-dark .veDiffNav-empty {
|
|
161
|
+
color: #5c7080;
|
|
162
|
+
}
|
|
83
163
|
.veRowItem:has(.rowViewTextContainer) .veAlignmentMismatch {
|
|
84
164
|
opacity: 0.5;
|
|
85
165
|
}
|
package/src/redux/alignments.js
CHANGED
|
@@ -88,19 +88,44 @@ function addHighlightedDifferences(alignmentTracks) {
|
|
|
88
88
|
);
|
|
89
89
|
// .filter by the user-specified mismatch overrides (initially [])
|
|
90
90
|
const mismatches = matchHighlightRanges.filter(({ isMatch }) => !isMatch);
|
|
91
|
+
|
|
92
|
+
// Compute non-aligned (gap) regions from leading/trailing dashes
|
|
93
|
+
const alignedSeq = track.alignmentData.sequence;
|
|
94
|
+
const seqLen = alignedSeq.length;
|
|
95
|
+
const startIndex = seqLen - alignedSeq.replace(/^-+/, "").length;
|
|
96
|
+
const endIndex = alignedSeq.replace(/-+$/, "").length;
|
|
97
|
+
const gapRanges = [
|
|
98
|
+
startIndex > 0 && {
|
|
99
|
+
start: 0,
|
|
100
|
+
end: startIndex - 1,
|
|
101
|
+
differenceType: "gap"
|
|
102
|
+
},
|
|
103
|
+
endIndex < seqLen && {
|
|
104
|
+
start: endIndex,
|
|
105
|
+
end: seqLen - 1,
|
|
106
|
+
differenceType: "gap"
|
|
107
|
+
}
|
|
108
|
+
].filter(Boolean);
|
|
109
|
+
|
|
91
110
|
return {
|
|
92
111
|
...track,
|
|
93
112
|
sequenceData,
|
|
94
113
|
matchHighlightRanges,
|
|
95
|
-
additionalSelectionLayers:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
additionalSelectionLayers: [
|
|
115
|
+
...matchHighlightRanges
|
|
116
|
+
.filter(({ isMatch }) => !isMatch)
|
|
117
|
+
.map(range => ({
|
|
99
118
|
...range,
|
|
100
119
|
...highlightRangeProps,
|
|
101
120
|
className: "veAlignmentMismatch"
|
|
102
|
-
}
|
|
103
|
-
|
|
121
|
+
})),
|
|
122
|
+
...gapRanges.map(range => ({
|
|
123
|
+
...range,
|
|
124
|
+
...highlightRangeProps,
|
|
125
|
+
className: "veAlignmentMismatch"
|
|
126
|
+
}))
|
|
127
|
+
],
|
|
128
|
+
gapRanges,
|
|
104
129
|
mismatches
|
|
105
130
|
};
|
|
106
131
|
});
|
|
@@ -271,39 +296,52 @@ export default (state = {}, { payload = {}, type }) => {
|
|
|
271
296
|
return state;
|
|
272
297
|
};
|
|
273
298
|
|
|
274
|
-
//returns an array like so: [{start: 0, end: 4, isMatch: false
|
|
299
|
+
//returns an array like so: [{start: 0, end: 4, isMatch: false, differenceType: "mismatch"}, ...]
|
|
300
|
+
// differenceType is one of: "mismatch" | "insertion" | "deletion" | null (for match ranges)
|
|
275
301
|
function getRangeMatchesBetweenTemplateAndNonTemplate(tempSeq, nonTempSeq) {
|
|
276
302
|
//assume all sequences are the same length (with gap characters "-" in some places)
|
|
277
303
|
//loop through all non template sequences and compare them with the template
|
|
278
304
|
|
|
279
305
|
const seqLength = nonTempSeq.length;
|
|
280
306
|
const ranges = [];
|
|
281
|
-
// const startIndex = "".match/[-]/ Math.max(0, .indexOf("-"));
|
|
282
307
|
const nonTempSeqWithoutLeadingDashes = nonTempSeq.replace(/^-+/g, "");
|
|
283
308
|
const nonTempSeqWithoutTrailingDashes = nonTempSeq.replace(/-+$/g, "");
|
|
284
309
|
|
|
285
310
|
const startIndex = seqLength - nonTempSeqWithoutLeadingDashes.length;
|
|
286
311
|
const endIndex =
|
|
287
312
|
seqLength - (seqLength - nonTempSeqWithoutTrailingDashes.length);
|
|
313
|
+
|
|
288
314
|
for (let index = startIndex; index < endIndex; index++) {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
315
|
+
const tempBase = tempSeq[index].toLowerCase();
|
|
316
|
+
const nonTempBase = nonTempSeq[index].toLowerCase();
|
|
317
|
+
const isMatch = tempBase === nonTempBase;
|
|
318
|
+
|
|
319
|
+
let differenceType = null;
|
|
320
|
+
if (!isMatch) {
|
|
321
|
+
if (tempBase === "-") {
|
|
322
|
+
differenceType = "insertion";
|
|
323
|
+
} else if (nonTempBase === "-") {
|
|
324
|
+
differenceType = "deletion";
|
|
295
325
|
} else {
|
|
296
|
-
|
|
297
|
-
start: index,
|
|
298
|
-
end: index,
|
|
299
|
-
isMatch
|
|
300
|
-
});
|
|
326
|
+
differenceType = "mismatch";
|
|
301
327
|
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const previousRange = ranges[ranges.length - 1];
|
|
331
|
+
if (
|
|
332
|
+
previousRange &&
|
|
333
|
+
previousRange.isMatch === isMatch &&
|
|
334
|
+
previousRange.differenceType === differenceType
|
|
335
|
+
) {
|
|
336
|
+
previousRange.end++;
|
|
337
|
+
} else if (previousRange) {
|
|
338
|
+
ranges.push({ start: index, end: index, isMatch, differenceType });
|
|
302
339
|
} else {
|
|
303
340
|
ranges.push({
|
|
304
341
|
start: startIndex,
|
|
305
342
|
end: startIndex,
|
|
306
|
-
isMatch
|
|
343
|
+
isMatch,
|
|
344
|
+
differenceType
|
|
307
345
|
});
|
|
308
346
|
}
|
|
309
347
|
}
|