diff --git a/tests/data/transcripts/input_12.js b/tests/data/transcripts/input_12.js new file mode 100644 index 00000000..b3f4138b --- /dev/null +++ b/tests/data/transcripts/input_12.js @@ -0,0 +1,49 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq_2 + // which is located in file data/RefSeq_2.js + var feature = { + "data": { + "seq_id": "testRefSeq2", + "end": 28, + "start": 1, + "strand": 1, + "subfeatures": [ + { + "data": { + "seq_id": "testRefSeq2", + "end": 13, + "start": 1, + "strand": 1, + "type": "exon" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 28, + "start": 18, + "strand": 1, + "type": "exon" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 28, + "start": 18, + "strand": 1, + "type": "CDS" + } + } + ], + "type": "transcript" + }, + "normalized": true, +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); diff --git a/tests/data/transcripts/resize_5.js b/tests/data/transcripts/resize_5.js new file mode 100644 index 00000000..373ae834 --- /dev/null +++ b/tests/data/transcripts/resize_5.js @@ -0,0 +1,49 @@ +define([ + 'JBrowse/Model/SimpleFeature' + ], +function (SimpleFeature) { + // This transcript is corresponding to RefSeq_2 + // which is located in file data/RefSeq_2.js + var feature = { + "data": { + "seq_id": "testRefSeq2", + "end": 28, + "start": 1, + "strand": 1, + "subfeatures": [ + { + "data": { + "seq_id": "testRefSeq2", + "end": 13, + "start": 1, + "strand": 1, + "type": "exon" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 28, + "start": 21, + "strand": 1, + "type": "CDS" + } + }, + { + "data": { + "seq_id": "testRefSeq2", + "end": 28, + "start": 21, + "strand": 1, + "type": "exon" + } + } + ], + "type": "transcript" + }, + "normalized": true, +}; + +var transcript = SimpleFeature.fromJSON(feature); +return transcript; +}); \ No newline at end of file diff --git a/tests/data/transcripts/transcript_data.js b/tests/data/transcripts/transcript_data.js index 6e0c19fd..2422d29e 100644 --- a/tests/data/transcripts/transcript_data.js +++ b/tests/data/transcripts/transcript_data.js @@ -10,10 +10,12 @@ define([ './input_9', './input_10', './input_11', + './input_12', './resize_1', './resize_2', './resize_3', './resize_4', + './resize_5', './orf_1', './orf_2', './orf_3', @@ -36,10 +38,12 @@ define([ input_9, input_10, input_11, + input_12, resize_1, resize_2, resize_3, resize_4, + resize_5, orf_1, orf_2, orf_3, @@ -53,8 +57,8 @@ define([ ) { var transcript_data = { "input": [input_1, input_2, input_3, input_4, input_5, input_6, input_7, - input_8, input_9, input_10, input_11], - "resize": [resize_1, resize_2, resize_3, resize_4], + input_8, input_9, input_10, input_11, input_12], + "resize": [resize_1, resize_2, resize_3, resize_4, resize_5], "orf": [orf_1, orf_2, orf_3], "non_canonical": [non_canonical_1, non_canonical_2], "cds": [cds_1, cds_2], diff --git a/tests/js/EditTrack.spec.js b/tests/js/EditTrack.spec.js index 269a0b91..770ee5e9 100644 --- a/tests/js/EditTrack.spec.js +++ b/tests/js/EditTrack.spec.js @@ -103,7 +103,7 @@ describe( "Edit Track", function() { }); it( 'tests comparison function', function() { - expect(compareFeatures(transcript_data["input"][0], transcript_data["input"][0])).toBe(true); + expect(compareFeatures(transcript_data.input[0], transcript_data.input[0])).toBe(true); }); it('tests getWholeCDSCoordinates', function() { @@ -203,25 +203,37 @@ describe( "Edit Track", function() { expect(compareFeatures( editTrack.setCDS(transcript_data.input[4], 0, 24), transcript_data.cds[0])).toBe(true); + + var start = editTrack.getTranslationStart(transcript_data.input[1]); + var stop = editTrack.getTranslationStop(transcript_data.input[1]); + var outTranscript = editTrack.setCDS(transcript_data.input[1], start, stop); + expect(compareFeatures(outTranscript, transcript_data.input[1])).toBe(true); }); it( 'tests resizeExon', function() { - exon = editTrack.filterExons(transcript_data["input"][0])[0]; - var right = 17120; - var left = exon.get('start'); - outTranscript = editTrack.resizeExon(refSeq, transcript_data["input"][0], exon, left, right); - expect(compareFeatures(transcript_data["resize"][0], outTranscript)).toBe(true); - - exon = editTrack.filterExons(transcript_data["input"][1])[1]; - var right = exon.get('end') + 3; - var left = exon.get('start'); - outTranscript = editTrack.resizeExon(refSeq, transcript_data["input"][1], exon, left, right); - expect(compareFeatures(transcript_data["resize"][2], outTranscript)).toBe(true); + var right, left; + exon = editTrack.filterExons(transcript_data.input[0])[0]; + right = 17120; + left = exon.get('start'); + outTranscript = editTrack.resizeExon(refSeq, transcript_data.input[0], exon, left, right); + expect(compareFeatures(transcript_data.resize[0], outTranscript)).toBe(true); + + exon = editTrack.filterExons(transcript_data.input[1])[1]; + right = exon.get('end') + 3; + left = exon.get('start'); + outTranscript = editTrack.resizeExon(refSeq, transcript_data.input[1], exon, left, right); + expect(compareFeatures(transcript_data.resize[2], outTranscript)).toBe(true); + + exon = editTrack.filterExons(transcript_data.input[11])[1]; + right = exon.get('end'); + left = exon.get('start') + 3; + outTranscript = editTrack.resizeExon(refSeq, transcript_data.input[11], exon, left, right); + expect(compareFeatures(transcript_data.resize[4], outTranscript)).toBe(true); // check for the translation start changes exon = editTrack.filterExons(transcript_data.input[1])[0]; - var right = exon.get('end'); - var left = exon.get('start') - 3; + right = exon.get('end'); + left = exon.get('start') - 3; outTranscript = editTrack.resizeExon(refSeq, transcript_data.input[1], exon, left, right); // Verify if CDS is correct expect(editTrack.getCDS(refSeq, outTranscript)).toEqual(transcript_data.cds[1]); diff --git a/www/JBrowse/View/Track/EditTrack.js b/www/JBrowse/View/Track/EditTrack.js index 2b8d7e72..dcc74c92 100644 --- a/www/JBrowse/View/Track/EditTrack.js +++ b/www/JBrowse/View/Track/EditTrack.js @@ -645,34 +645,34 @@ var EditTrack = declare(DraggableFeatureTrack, return; } - var _exons, exons = []; + var origLeft = exonToResize.get('start'); + var origRight = exonToResize.get('end'); + var _exons; _exons = this.filterExons(transcript); _exons = _.reject(_exons, function (exon) { return exon === exonToResize; }); if (left !== right) { _exons.push(this.copyFeature(exonToResize, {start: left, end: right})); } - _exons = this.sortAnnotationsByLocation(_exons); - _.each(_exons, _.bind(function (f) { - var last = exons[exons.length - 1]; - if (last && (f.get('start') - last.get('end') <= 1)) { - last.set('end', Math.max(last.get('end'), f.get('end'))); + var translationStart = this.getTranslationStart(transcript); + var translationStop = this.getTranslationStop(transcript); + + var strand = transcript.get('strand'); + if (strand === 1) { + if (left >= translationStart && translationStart >= origLeft) { + translationStart = left; } - else { - exons.push(this.copyFeature(f)); + if (right <= translationStop && translationStop <= origRight) { + translationStop = right; } - }, this)); - - var newTranscript = this.createTranscript(exons, transcript.get('name')); - var translationStart = this.getTranslationStart(transcript); - if (translationStart) { - if (translationStart < newTranscript.get('start')) { - translationStart = newTranscript.get('start'); + } else { + if (right <= translationStart && translationStart <= origRight) { + translationStart = right; } - if (translationStart > newTranscript.get('end')) { - translationStart = newTranscript.get('end'); + if (left >= translationStop && translationStop >= origLeft) { + translationStop = left; } - newTranscript = this.setORF(refSeq, newTranscript, translationStart); } + var newTranscript = this.createTranscript(_exons, transcript.get('name'), refSeq, translationStart, translationStop); return newTranscript; }, @@ -767,17 +767,8 @@ var EditTrack = declare(DraggableFeatureTrack, var exons = _.reject(this.filterExons(transcript), function (exon) { return _.indexOf(str_exonsToDelete, JSON.stringify(exon)) !== -1; }); - var newTranscript = this.createTranscript(exons, transcript.get('name')); var translationStart = this.getTranslationStart(transcript); - if (translationStart) { - if (translationStart < newTranscript.get('start')) { - translationStart = newTranscript.get('start'); - } - if (translationStart > newTranscript.get('end')) { - translationStart = newTranscript.get('end'); - } - newTranscript = this.setORF(refSeq, newTranscript, translationStart); - } + var newTranscript = this.createTranscript(exons, transcript.get('name'), translationStart); return newTranscript; }, @@ -817,24 +808,8 @@ var EditTrack = declare(DraggableFeatureTrack, exons = exons.concat(this.filterExons(transcript)); }, this)); exons = this.sortAnnotationsByLocation(exons); - - // Combine partially or fully overlapping, and immediately adjacent - // exons into one. - var newexons = []; - _.each(exons, _.bind(function (f) { - var last = newexons[newexons.length - 1]; - if (last && (f.get('start') - last.get('end') <= 1)) { // we are looking for introns - newexons[newexons.length - 1] = this.copyFeature(last, {end: Math.max(last.get('end'), f.get('end'))}); - } - else { - newexons.push(f); - } - }, this)); - - // Create new transcript from the processed exons, and insert CDS. - var newTranscript = this.createTranscript(newexons); - newTranscript = this.setORF(refSeq, newTranscript, translationStart); - newTranscript.set('name', 'afra-' + newTranscript.get('seq_id') + '-mRNA-' + counter++); + var name = 'afra-' + exons[0].get('seq_id') + '-mRNA-' + counter++; + var newTranscript = this.createTranscript(exons, name, refSeq, translationStart); return newTranscript; }, @@ -1104,17 +1079,45 @@ var EditTrack = declare(DraggableFeatureTrack, return transcript; }, - createTranscript: function (subfeatures, name) { + /* + * Create Transcript from the given subfeatures + * + * createTranscripts also combines partially or fully overlapping exons. If + * translation start is not specified then all the non exonic subfeatures + * remain intact. 'setORF' of 'setCDS' is called on the created simplefeature + * depending on if translationStart and translationStop are specified. + * + * If only translationStart is given then 'setORF' is called. And if both + * translationStart and translationStop is given then 'setCDS' is called. + */ + createTranscript: function(subfeatures, name, refSeq, translationStart, translationStop) { // maintain a count of subfeatures seen, indexed by type var count = {}; + var exons = _.filter(subfeatures, function(f) { return f.get('type') === 'exon'; }); + exons = this.sortAnnotationsByLocation(exons); + + var newexons = []; + _.each(exons, _.bind(function (f) { + var last = newexons[newexons.length - 1]; + if (last && (f.get('start') - last.get('end') <= 0)) { + newexons[newexons.length - 1] = this.copyFeature(last, {end: Math.max(last.get('end'), f.get('end'))}); + } else { + newexons.push(f); + } + }, this)); + + var nonExons = _.filter(subfeatures, function (f) { return f.get('type') !== 'exon'; }); + var newSubFeatures = nonExons.concat(newexons); + newSubFeatures = this.sortAnnotationsByLocation(newSubFeatures); + var transcript = new SimpleFeature({ data: { type: 'transcript', name: name, - seq_id: subfeatures[0].get('seq_id'), - strand: subfeatures[0].get('strand'), - subfeatures: _.map(subfeatures, function (f) { + seq_id: newSubFeatures[0].get('seq_id'), + strand: newSubFeatures[0].get('strand'), + subfeatures: _.map(newSubFeatures, function (f) { var type = f.get('type'); count[type] = count[type] || 1; return {'data': { @@ -1134,6 +1137,24 @@ var EditTrack = declare(DraggableFeatureTrack, var fmax = _.max(_.map(subfeatures, function (f) { return f.get('end'); })); transcript.set('start', fmin); transcript.set('end', fmax); + + if (translationStart) { + if (translationStart < fmin) { + translationStart = fmin; + } + if (translationStart > fmax) { + translationStart = fmax; + } + } + + if (!_.isUndefined(translationStart) && !_.isUndefined(refSeq)) { + if (!_.isUndefined(translationStop)) { + transcript = this.setCDS(transcript, translationStart, translationStop); + } + else { + transcript = this.setORF(refSeq, transcript, translationStart); + } + } return transcript; },