diff --git a/examples/data.js b/examples/data.js index 77d16e35..af1bb412 100644 --- a/examples/data.js +++ b/examples/data.js @@ -28,6 +28,16 @@ var sources = [ data: pileup.formats.vcf({ url: '/test-data/snv.chr17.vcf' }), + options: { + variantHeightByFrequency: true, + onVariantClicked: function(data) { + var content = "Variants:\n"; + for (var i =0;i< data.length;i++) { + content +=data[i].id+" - "+data[i].vcfLine+"\n"; + } + alert(content); + }, + }, name: 'Variants' }, { diff --git a/examples/index.html b/examples/index.html index d174f5d7..51f631b6 100644 --- a/examples/index.html +++ b/examples/index.html @@ -3,6 +3,7 @@ + diff --git a/src/main/Root.js b/src/main/Root.js index c5c63c06..2927dfb6 100644 --- a/src/main/Root.js +++ b/src/main/Root.js @@ -97,6 +97,7 @@ class Root extends React.Component { range={this.state.range} onRangeChange={this.handleRangeChange.bind(this)} source={track.source} + options={track.track.options} referenceSource={this.props.referenceSource} ref = {(c) => {this.trackReactElements[intKey]=c}} />); diff --git a/src/main/VisualizationWrapper.js b/src/main/VisualizationWrapper.js index d0d34915..bb4810ab 100644 --- a/src/main/VisualizationWrapper.js +++ b/src/main/VisualizationWrapper.js @@ -27,6 +27,7 @@ type Props = { onRangeChange: (newRange: GenomeRange) => void; referenceSource: TwoBitSource; source: any; + options: ?Object; }; class VisualizationWrapper extends React.Component { @@ -136,6 +137,7 @@ class VisualizationWrapper extends React.Component { if (!range) { return ; } + var options = _.extend({},this.props.visualization.options,this.props.options); var el = React.createElement(component, ({ range: range, @@ -143,7 +145,7 @@ class VisualizationWrapper extends React.Component { referenceSource: this.props.referenceSource, width: this.state.width, height: this.state.height, - options: this.props.visualization.options + options: options } : VizProps)); return
{el}
; diff --git a/src/main/data/vcf.js b/src/main/data/vcf.js index 558eb623..880a4f1b 100644 --- a/src/main/data/vcf.js +++ b/src/main/data/vcf.js @@ -16,6 +16,13 @@ export type Variant = { position: number; ref: string; alt: string; + id: string; + //this is the bigest allel frequency for single vcf entry + //single vcf entry might contain more than one variant like the example below + //20 1110696 rs6040355 A G,T 67 PASS NS=2;DP=10;AF=0.333,0.667;AA=T;DB + majorFrequency: ?number; + //this is the smallest allel frequency for single vcf entry + minorFrequency: ?number; vcfLine: string; } @@ -41,12 +48,33 @@ function extractLocusLine(vcfLine: string): LocusLine { function extractVariant(vcfLine: string): Variant { var parts = vcfLine.split('\t'); + var maxFrequency = null; + var minFrequency = null; + if (parts.length>=7){ + var params = parts[7].split(';'); + for (var i=0;i; + return ; } componentDidMount() { @@ -80,11 +82,31 @@ class VariantTrack extends React.Component { ctx.fillStyle = style.VARIANT_FILL; ctx.strokeStyle = style.VARIANT_STROKE; variants.forEach(variant => { + var variantHeightRatio = 1.0; + if (this.props.options.variantHeightByFrequency) { + var frequency = null; + if (this.props.options.allelFrequencyStrategy === undefined) { //default startegy + frequency = variant.majorFrequency; + } else if (this.props.options.allelFrequencyStrategy === AllelFrequencyStrategy.Major) { + frequency = variant.majorFrequency; + } else if (this.props.options.allelFrequencyStrategy === AllelFrequencyStrategy.Minor) { + frequency = variant.minorFrequency; + } else { + console.log("Unknown AllelFrequencyStrategy: ",this.props.options.allelFrequencyStrategy); + } + if (frequency !== null && frequency !== undefined) { + variantHeightRatio = frequency; + } + } + var height = style.VARIANT_HEIGHT*variantHeightRatio; + var variantY = y - 0.5 + style.VARIANT_HEIGHT - height; + var variantX = Math.round(scale(variant.position)) - 0.5; + var width = Math.round(scale(variant.position + 1)) - 0.5 - variantX; + ctx.pushObject(variant); - var x = Math.round(scale(variant.position)); - var width = Math.round(scale(variant.position + 1)) - 1 - x; - ctx.fillRect(x - 0.5, y - 0.5, width, style.VARIANT_HEIGHT); - ctx.strokeRect(x - 0.5, y - 0.5, width, style.VARIANT_HEIGHT); + + ctx.fillRect(variantX, variantY, width, height); + ctx.strokeRect(variantX, variantY, width, height); ctx.popObject(); }); @@ -99,10 +121,19 @@ class VariantTrack extends React.Component { ctx = canvasUtils.getContext(canvas), trackingCtx = new dataCanvas.ClickTrackingContext(ctx, x, y); this.renderScene(trackingCtx); - var variant = trackingCtx.hit && trackingCtx.hit[0]; - var alert = window.alert || console.log; - if (variant) { - alert(JSON.stringify(variant)); + + var variants = trackingCtx.hit; + if (variants && variants.length>0) { + var data = []; + for (var i=0;i { + expect(features).to.have.length(1); + expect(features[0].contig).to.equal('20'); + expect(features[0].majorFrequency).to.equal(0.7); + expect(features[0].minorFrequency).to.equal(0.7); + }); + }); + + it('should have highest frequency', function() { + var vcf = new VcfFile(new RemoteFile('/test-data/allelFrequency.vcf')); + var range = new ContigInterval('chr20', 61730, 61740); + return vcf.getFeaturesInRange(range).then(features => { + expect(features).to.have.length(1); + expect(features[0].contig).to.equal('20'); + expect(features[0].majorFrequency).to.equal(0.6); + expect(features[0].minorFrequency).to.equal(0.3); + }); + }); + it('should add chr', function() { var vcf = new VcfFile(new RemoteFile('/test-data/snv.vcf')); var range = new ContigInterval('chr20', 63799, 69094); diff --git a/src/test/viz/VariantTrack.js b/src/test/viz/VariantTrack.js new file mode 100644 index 00000000..06842461 --- /dev/null +++ b/src/test/viz/VariantTrack.js @@ -0,0 +1,75 @@ +/** + * @flow + */ +'use strict'; + +import {expect} from 'chai'; + +import pileup from '../../main/pileup'; +import dataCanvas from 'data-canvas'; +import {waitFor} from '../async'; + +import ReactTestUtils from 'react-addons-test-utils'; + +describe('VariantTrack', function() { + var testDiv = document.getElementById('testdiv'); + + beforeEach(() => { + testDiv.style.width = '700px'; + dataCanvas.RecordingContext.recordAll(); + }); + + afterEach(() => { + dataCanvas.RecordingContext.reset(); + // avoid pollution between tests. + testDiv.innerHTML = ''; + }); + var {drawnObjects} = dataCanvas.RecordingContext; + + function ready() { + return testDiv.getElementsByTagName('canvas').length > 0 && + drawnObjects(testDiv, '.variants').length > 0; + } + + it('should render variants', function() { + var variantClickedData = null; + var variantClicked = function (data) { + variantClickedData = data; + }; + var p = pileup.create(testDiv, { + range: {contig: '17', start: 9386380, stop: 9537390}, + tracks: [ + { + viz: pileup.viz.genome(), + data: pileup.formats.twoBit({ + url: '/test-data/test.2bit' + }), + isReference: true + }, + { + data: pileup.formats.vcf({ + url: '/test-data/test.vcf' + }), + viz: pileup.viz.variants(), + options: {onVariantClicked: variantClicked}, + } + ] + }); + + return waitFor(ready, 2000) + .then(() => { + var variants = drawnObjects(testDiv, '.variants'); + expect(variants.length).to.be.equal(1); + var canvasList = testDiv.getElementsByTagName('canvas'); + var canvas = canvasList[1]; + expect(variantClickedData).to.be.null; + + //check clicking on variant + ReactTestUtils.Simulate.click(canvas,{nativeEvent: {offsetX: -0.5, offsetY: -15.5}}); + + expect(variantClickedData).to.not.be.null; + p.destroy(); + }); + }); + +}); diff --git a/test-data/allelFrequency.vcf b/test-data/allelFrequency.vcf new file mode 100644 index 00000000..c63f5379 --- /dev/null +++ b/test-data/allelFrequency.vcf @@ -0,0 +1,21 @@ +##fileformat=VCFv4.1 +##source=VarScan2 +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##FILTER= +##FILTER= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT NORMAL TUMOR +20 61795 . G T . PASS DP=81;SS=1;SSC=2;GPV=4.6768E-16;SPV=5.4057E-1;AF=0.7 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:44:22:22:50%:16,6,9,13 0/1:.:37:18:19:51.35%:10,8,10,9 +20 62731 . C A,G . PASS DP=68;SS=1;SSC=1;GPV=1.4855E-11;SPV=7.5053E-1;AF=0.4,0.5 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:32:17:15:46.88%:9,8,9,6 0/1:.:36:21:15:41.67%:8,13,8,7 +20 61731 . C A,G,T . PASS DP=68;SS=1;SSC=1;GPV=1.4855E-11;SPV=7.5053E-1;AF=0.4,0.6,0.3 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:32:17:15:46.88%:9,8,9,6 0/1:.:36:21:15:41.67%:8,13,8,7 diff --git a/test-data/snv.chr17.vcf b/test-data/snv.chr17.vcf index bcb95918..f391885c 100644 --- a/test-data/snv.chr17.vcf +++ b/test-data/snv.chr17.vcf @@ -18,7 +18,7 @@ #CHROM POS ID REF ALT QUAL FILTER INFO FORMAT NORMAL TUMOR 17 125 . G T . PASS DP=81;SS=1;SSC=2;GPV=4.6768E-16;SPV=5.4057E-1 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:44:22:22:50%:16,6,9,13 0/1:.:37:18:19:51.35%:10,8,10,9 17 7512444 . G T . PASS DP=81;SS=1;SSC=2;GPV=4.6768E-16;SPV=5.4057E-1 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:44:22:22:50%:16,6,9,13 0/1:.:37:18:19:51.35%:10,8,10,9 -17 7512454 . C A . PASS DP=68;SS=1;SSC=1;GPV=1.4855E-11;SPV=7.5053E-1 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:32:17:15:46.88%:9,8,9,6 0/1:.:36:21:15:41.67%:8,13,8,7 +17 7512454 . C A . PASS DP=68;SS=1;SSC=1;GPV=1.4855E-11;SPV=7.5053E-1;AF=0.73 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:32:17:15:46.88%:9,8,9,6 0/1:.:36:21:15:41.67%:8,13,8,7 17 7512544 . C T . PASS DP=72;SS=1;SSC=7;GPV=3.6893E-16;SPV=1.8005E-1 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:39:19:19:50%:8,11,11,8 0/1:.:33:12:21:63.64%:5,7,8,13 17 7512644 . G T . PASS DP=35;SS=1;SSC=0;GPV=7.8434E-5;SPV=8.2705E-1 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:21:13:8:38.1%:4,9,0,8 0/1:.:14:10:4:28.57%:2,8,0,4 17 7512244 . G A . PASS DP=53;SS=1;SSC=0;GPV=1.5943E-31;SPV=1E0 GT:GQ:DP:RD:AD:FREQ:DP4 1/1:.:26:0:26:100%:0,0,12,14 1/1:.:27:0:27:100%:0,0,15,12 diff --git a/test-data/test.vcf b/test-data/test.vcf new file mode 100644 index 00000000..732d6007 --- /dev/null +++ b/test-data/test.vcf @@ -0,0 +1,19 @@ +##fileformat=VCFv4.1 +##source=VarScan2 +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##FILTER= +##FILTER= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT NORMAL TUMOR +17 9386385 . G T . PASS DP=81;SS=1;SSC=2;GPV=4.6768E-16;SPV=5.4057E-1;AF=0.7 GT:GQ:DP:RD:AD:FREQ:DP4 0/1:.:44:22:22:50%:16,6,9,13 0/1:.:37:18:19:51.35%:10,8,10,9