diff --git a/docs/tutorial/data_files/gvcf.vcf.gz b/docs/tutorial/data_files/gvcf.vcf.gz new file mode 100644 index 0000000000..06b8bb6fbe Binary files /dev/null and b/docs/tutorial/data_files/gvcf.vcf.gz differ diff --git a/docs/tutorial/data_files/gvcf.vcf.gz.conf b/docs/tutorial/data_files/gvcf.vcf.gz.conf new file mode 100644 index 0000000000..32d2515887 --- /dev/null +++ b/docs/tutorial/data_files/gvcf.vcf.gz.conf @@ -0,0 +1,6 @@ +[ tracks . volvox_gvcf_test] +storeClass = JBrowse/Store/SeqFeature/VCFTabix +urlTemplate = ../../raw/volvox/gvcf.vcf.gz +category = VCF +type = JBrowse/View/Track/CanvasVariants +key = VCF - GVCF test data diff --git a/docs/tutorial/data_files/gvcf.vcf.gz.tbi b/docs/tutorial/data_files/gvcf.vcf.gz.tbi new file mode 100644 index 0000000000..edbea175eb Binary files /dev/null and b/docs/tutorial/data_files/gvcf.vcf.gz.tbi differ diff --git a/package.json b/package.json index 6f790cb9d4..b8393e32fa 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "repository": "https://github.com/GMOD/jbrowse.git", "scripts": { "test": "phantomjs tests/js_tests/run-jasmine.js http://localhost:3000/tests/js_tests/index.html", - "start": "utils/jb_run.js -p 8082", - "watch": "watch 'npm run build' src/JBrowse plugins", + "start": "./jb_run.js -p 8082", + "watch": "watch 'npm run build' src/JBrowse plugins tests/js_tests", "build": "node node_modules/webpack/bin/webpack.js --config webpack.config.js", "hint": "jshint src/JBrowse/" }, diff --git a/release-notes.txt b/release-notes.txt index cef5c18d36..b3a732d34d 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -12,6 +12,9 @@ * Add ability for the CanvasFeatures feature labels to stay visible on the screen. (issue #390, pull #971, @cmdcolin, @rbuels) + * Improve VCF tracks support for GVCF generated by GATK, and fix a number of related + VCF details display bugs. (pull #991, @cmdcolin) + * `generate-names.pl` now supports indexing GFF3 files, enabling better use of GFF3Tabix tracks. Thanks to @billzt for the initial implementation! (issue #780, pull #900, @rbuels) diff --git a/setup.sh b/setup.sh index fd779cc0a3..d4a34f1391 100755 --- a/setup.sh +++ b/setup.sh @@ -144,6 +144,7 @@ echo -n "Formatting Volvox example data ..."; cat docs/tutorial/data_files/volvox.sort.gff3.gz.conf >> sample_data/json/volvox/tracks.conf cat docs/tutorial/data_files/volvox.bw.gff3.gz.conf >> sample_data/json/volvox/tracks.conf cat docs/tutorial/data_files/volvox.sort.bed.gz.conf >> sample_data/json/volvox/tracks.conf + cat docs/tutorial/data_files/gvcf.vcf.gz.conf >> sample_data/json/volvox/tracks.conf cat docs/tutorial/data_files/bookmarks.conf >> sample_data/json/volvox/tracks.conf bin/add-json.pl '{ "dataset_id": "volvox", "include": [ "../../raw/volvox/functions.conf" ] }' sample_data/json/volvox/trackList.json bin/add-json.pl '{ "dataset_id": "volvox", "plugins": [ "NeatHTMLFeatures","NeatCanvasFeatures","HideTrackLabels" ] }' sample_data/json/volvox/trackList.json diff --git a/src/JBrowse/Store/SeqFeature/VCFTabix/Parser.js b/src/JBrowse/Store/SeqFeature/VCFTabix/Parser.js index 7993e76606..b5dbec7fd7 100644 --- a/src/JBrowse/Store/SeqFeature/VCFTabix/Parser.js +++ b/src/JBrowse/Store/SeqFeature/VCFTabix/Parser.js @@ -3,6 +3,7 @@ define([ 'dojo/_base/array', 'dojo/json', 'JBrowse/Util/TextIterator', + 'JBrowse/Util', 'JBrowse/Digest/Crc32', './LazyFeature' ], @@ -11,6 +12,7 @@ define([ array, JSON, TextIterator, + Util, Digest, LazyFeature ) { @@ -112,13 +114,14 @@ return declare( null, { }; } - if( alt && alt[0] != '<' ) + if( alt ) { featureData.alternative_alleles = { meta: { description: 'VCF ALT field, list of alternate non-reference alleles called on at least one of the samples' }, values: alt }; + } // parse the info field and store its contents as attributes in featureData this._parseInfoField( featureData, fields ); @@ -230,7 +233,9 @@ return declare( null, { "CNV": { description: "Copy number variable region (may be both deletion and duplication)", so_term: 'copy_number_variation' }, "DUP:TANDEM": { description: "Tandem duplication", so_term: 'copy_number_gain' }, "DEL:ME": { description: "Deletion of mobile element relative to the reference" }, - "INS:ME": { description: "Insertion of a mobile element relative to the reference" } + "INS:ME": { description: "Insertion of a mobile element relative to the reference" }, + "NON_REF": { description: "Represents any possible alternative allele at this location", so_term: 'sequence_variant' }, + "*": { description: "Represents any possible alternative allele at this location", so_term: 'sequence_variant' } }, /** @@ -360,7 +365,6 @@ return declare( null, { return this._find_SO_term_from_alt_definitions( alt ); }, this ), function( t ) { return t; } ); - if( types[0] ) return types.join(','); @@ -376,7 +380,7 @@ return declare( null, { if( ! alt ) return 'no alternative alleles'; - alt = alt.replace(/^<|>$/g,''); + alt = alt.replace(/<|>/g,''); var def = this.getVCFMetaData( 'alt', alt ); return def && def.description ? alt+' - '+def.description : SO_term+" "+ref+" -> "+ alt; @@ -391,8 +395,11 @@ return declare( null, { // look for a definition with an SO type for this var def = (this.header.alt||{})[alt] || this._vcfReservedAltTypes[alt]; - if( def && def.so_term ) + if( def && def.so_term ) { return def.so_term; + } else if( def && (this._vcfReservedAltTypes[alt]||{}).so_term ) { + return this._vcfReservedAltTypes[alt].so_term; + } // try to look for a definition for a parent term if we can alt = alt.split(':'); diff --git a/src/JBrowse/View/Track/_VariantDetailMixin.js b/src/JBrowse/View/Track/_VariantDetailMixin.js index d081acb114..740617a279 100644 --- a/src/JBrowse/View/Track/_VariantDetailMixin.js +++ b/src/JBrowse/View/Track/_VariantDetailMixin.js @@ -45,7 +45,12 @@ return declare( [FeatureDetailMixin, NamedFeatureFiltersMixin], { return container; }, - + renderDetailValue: function( parent, title, val, f, class_ ) { + if(title == "alternative_alleles") { + val = Util.escapeHTML(val); + } + return this.inherited(arguments, [parent,title,val,f,class_]); + }, _isReservedTag: function( t ) { return this.inherited(arguments) || {genotypes:1}[t.toLowerCase()]; }, @@ -113,7 +118,12 @@ return declare( [FeatureDetailMixin, NamedFeatureFiltersMixin], { descriptions[k] = subValue[k].meta && subValue[k].meta.description || null; } return descriptions; - })() + })(), + renderCell: { + "GT": function( field, value, node, options ) { + thisB.renderDetailValue( node, '', Util.escapeHTML(value), f, '' ); + } + } } ); }; @@ -141,11 +151,9 @@ return declare( [FeatureDetailMixin, NamedFeatureFiltersMixin], { var value_parse = value.values[0]; var splitter = (value_parse.match(/[\|\/]/g)||[])[0]; // only accept | and / splitters since . can mean no call - if(alt) { - alt = alt[0].split(','); // force split on alt alleles - } + alt = alt[0].split(','); // force split on alt alleles var refseq = underlyingRefSeq ? 'ref ('+underlyingRefSeq+')' : 'ref'; - value = array.map( splitter?value_parse.split(splitter):value_parse, function( gtIndex ) { + value = array.map( splitter ? value_parse.split(splitter) : value_parse, function( gtIndex ) { gtIndex = parseInt( gtIndex ) || gtIndex; if(gtIndex == '.') { return 'no-call' } else if(gtIndex == 0) { return refseq; } diff --git a/tests/data/raw.g.vcf.gz b/tests/data/raw.g.vcf.gz new file mode 100644 index 0000000000..04f24b892f Binary files /dev/null and b/tests/data/raw.g.vcf.gz differ diff --git a/tests/data/raw.g.vcf.gz.tbi b/tests/data/raw.g.vcf.gz.tbi new file mode 100644 index 0000000000..d254e3122c Binary files /dev/null and b/tests/data/raw.g.vcf.gz.tbi differ diff --git a/tests/data/raw.g.vcf.idx b/tests/data/raw.g.vcf.idx new file mode 100644 index 0000000000..6c2026000c Binary files /dev/null and b/tests/data/raw.g.vcf.idx differ diff --git a/tests/js_tests/spec/VCF.spec.js b/tests/js_tests/spec/VCF.spec.js index 8dcd25bf8e..398f3b2f1f 100644 --- a/tests/js_tests/spec/VCF.spec.js +++ b/tests/js_tests/spec/VCF.spec.js @@ -35,6 +35,37 @@ describe('VCF store', function() { runs(function() { expect(features.length).toEqual( 560 ); }); + }); + + + + + it('reads gvcf * alleles', function() { + var store = new VCFStore({ + browser: new Browser({unitTestMode: true}), + config: { + urlTemplate: '../../docs/tutorial/data_files/gvcf.vcf.gz', + baseUrl: '.' + }, + refSeq: { name: '1', start: 0, end: 5000 } + }); + + var features = []; + waitsFor( function() { return features.done; } ); + store.getFeatures({ ref: 'ctgA', + start: 0, + end: 5000 + }, + function(f) { features.push( f ); }, + function( ) { features.done = true; }, + function(e) { console.error(e.stack||''+e); } + ); + runs(function() { + expect(features.length).toEqual( 7 ); + expect(features[2].get('alternative_alleles').values).toEqual("TC,<*>"); + }); + + }); @@ -46,6 +77,7 @@ describe('VCF store', function() { baseUrl: '.' }, refSeq: { name: 'ctgA', start: 0, end: 50000 } + }); var features = []; @@ -67,6 +99,36 @@ describe('VCF store', function() { }); + it('reads gatk non_ref alleles', function() { + var store = new VCFStore({ + browser: new Browser({unitTestMode: true}), + config: { + urlTemplate: '../data/raw.g.vcf.gz', + baseUrl: '.' + }, + refSeq: { name: 'ctgA', start: 0, end: 5000 } + }); + + + var features = []; + waitsFor( function() { return features.done; } ); + store.getFeatures({ ref: 'ctgA', + start: 0, + end: 100 + }, + function(f) { features.push( f ); }, + function( ) { features.done = true; }, + function(e) { console.error(e.stack||''+e); } + ); + runs(function() { + expect(features.length).toEqual( 37 ); + expect(features[0].get('alternative_alleles').values).toEqual(''); + }); + }); + + + + it('parses END field', function() { var store = new VCFStore({ browser: new Browser({unitTestMode: true}),