-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsequence_class_methods_mixin.js
More file actions
134 lines (113 loc) · 4.8 KB
/
sequence_class_methods_mixin.js
File metadata and controls
134 lines (113 loc) · 4.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// TODO add dependency to _.deepClone
import _ from 'underscore';
var calculateOverhang = function(sequenceModel, pos) {
var overhangStart = sequenceModel.minOverhangBeyondStartStickyEndOnBothStrands(pos);
var overhangEnd = sequenceModel.minOverhangBeyondEndStickyEndOnBothStrands(pos);
return Math.max(overhangStart, 0) - Math.max(overhangEnd, 0);
};
var concatenateSequences = function(sequenceModels, circularise=false, truncateFeatures=true) {
var previousSequenceModel;
var previousStickyEnds;
var previousStickyEndFormats = {};
var newSequenceAttributes = _.reduce(sequenceModels, function(attributes, sequenceModel, i) {
var isFirst = i === 0;
var isLast = i === (sequenceModels.length - 1);
// Enforcing full sticky end format on all sequence models
previousStickyEndFormats[sequenceModel.get('id')] = sequenceModel.getStickyEndFormat();
sequenceModel.setStickyEndFormat('full');
var stickyEnds = sequenceModel.getStickyEnds(false) || {};
var appendSequenceBases = sequenceModel.getSequence();
// Add sticky ends
if(isFirst) {
if(stickyEnds.start) {
// Add sticky end at start
attributes.stickyEnds.start = stickyEnds.start;
}
}
if(isLast) {
if(stickyEnds.end) {
// Add sticky end at end
attributes.stickyEnds.end = stickyEnds.end;
}
}
var offset = 0;
if(isFirst && circularise) {
previousSequenceModel = sequenceModels[sequenceModels.length-1];
previousStickyEnds = previousSequenceModel.getStickyEnds(true);
}
if(previousSequenceModel) {
// Check sticky ends are compatible
if(previousSequenceModel.stickyEndConnects(sequenceModel)) {
attributes.sequence = attributes.sequence.substr(0, attributes.sequence.length - previousStickyEnds.end.offset);
var toRemove = stickyEnds.start.offset + stickyEnds.start.size;
appendSequenceBases = appendSequenceBases.substr(toRemove);
offset = attributes.sequence.length - toRemove;
} else {
throw `Can not concatenate sequences ${previousSequenceModel.id} and ${sequenceModel.id} as they have incompatible sticky ends: \`${previousSequenceModel.getEndStickyEndSequence().sequenceBases}\` and \`${sequenceModel.getStartStickyEndSequence().sequenceBases}\``;
}
}
if(isLast && circularise) {
appendSequenceBases = appendSequenceBases.substr(0, appendSequenceBases.length - stickyEnds.end.offset);
}
// Add the suitable sequence bases
attributes.sequence += appendSequenceBases;
// Add features
_.each(sequenceModel.getFeatures(), (feature) => {
var positions = _.flatten(_.map(feature.ranges, (range) => [range.to, range.from]));
var maxPos = Math.max(...positions);
var minPos = Math.min(...positions);
var accepted = true;
var overhangStart = sequenceModel.minOverhangBeyondStartStickyEndOnBothStrands(minPos);
var overhangEnd = sequenceModel.minOverhangBeyondEndStickyEndOnBothStrands(maxPos);
if((circularise || !isFirst) && (overhangStart > 0)) {
accepted = false;
}
if((circularise || !isLast) && (overhangEnd > 0)) {
accepted = false;
}
if(accepted || truncateFeatures) {
var copiedFeature = _.deepClone(feature);
_.each(copiedFeature.ranges, (range) => {
// TODO improve. Is partial truncate implementation. If there is a
// range completely outside the sequence, it will be truncate to be
// 1 long at the start / end of the sequence, which is a) stupid and
// b) might overlap with another truncated range. The range should
// just be dropped completely.
var overhangOnFrom = calculateOverhang(sequenceModel, range.from);
var overhangOnTo = calculateOverhang(sequenceModel, range.to);
range.from += offset;
range.to += offset;
if(truncateFeatures) {
range.from += overhangOnFrom;
range.to += overhangOnTo;
}
});
attributes.features.push(copiedFeature);
}
});
previousSequenceModel = sequenceModel;
previousStickyEnds = stickyEnds;
return attributes;
}, {
sequence: '',
stickyEnds: {},
features: [],
});
// Resetting sticky end formats to their initial values
_.each(sequenceModels, function(sequenceModel) {
var previousFormat = previousStickyEndFormats[sequenceModel.get('id')];
sequenceModel.setStickyEndFormat(previousFormat);
});
return new this(newSequenceAttributes);
};
var sequenceClassMethodsMixin = function(Klass) {
var classMethods = {
calculateOverhang,
concatenateSequences,
};
_.each(classMethods, function(methodFunction, methodName) {
Klass[methodName] = _.bind(methodFunction, Klass);
});
return Klass;
};
export default sequenceClassMethodsMixin;