UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

308 lines (268 loc) 13.2 kB
define( ['dojo/_base/declare', 'dojo/_base/array', 'JBrowse/View/Track/Wiggle/XYPlot', 'JBrowse/Util', 'JBrowse/View/Track/_AlignmentsMixin', 'JBrowse/Store/SeqFeature/SNPCoverage' ], function( declare, array, WiggleXY, Util, AlignmentsMixin, SNPCoverageStore ) { var dojof = Util.dojof; return declare( [WiggleXY, AlignmentsMixin], { constructor: function() { // force conf variables that are meaningless for this kind of track, and maybe harmful delete this.config.bicolor_pivot; delete this.config.scale; delete this.config.align; var thisB = this; this.store = new SNPCoverageStore( { store: this.store, config: { mismatchScale: this.config.mismatchScale, indicatorProp: this.config.indicatorProp, indicatorDepth: this.config.indicatorDepth }, browser: this.browser, filter: function( f ) { return thisB.filterFeature( f ); } }); }, _defaultConfig: function() { return Util.deepUpdate( dojo.clone( this.inherited(arguments) ), { autoscale: 'local', min_score: 0, mismatchScale: 1/10, indicatorProp: 0.5, indicatorDepth: 1, hideDuplicateReads: true, logScaleOption: false, hideQCFailingReads: true, hideSecondary: true, hideSupplementary: true, hideMissingMatepairs: false, hideImproperPairs: false, hideUnmapped: true } ); }, /* * Draw a set of features on the canvas. * @private */ _drawFeatures: function( scale, leftBase, rightBase, block, canvas, features, featureRects, dataScale ) { var thisB = this; var context = canvas.getContext('2d'); var canvasHeight = canvas.height; var ratio = Util.getResolution( context, this.browser.config.highResolutionMode ); var toY = dojo.hitch( this, function( val ) { return canvasHeight * ( 1-dataScale.normalize(val) ) / ratio; }); var originY = toY( dataScale.origin ); // a canvas element below the histogram that will contain indicators of likely SNPs var snpCanvasHeight = 20; var snpCanvas = dojo.create('canvas', {height: snpCanvasHeight, width: canvas.width, style: { cursor: 'default', width: "100%", height: snpCanvasHeight + "px" }, innerHTML: 'Your web browser cannot display this type of track.', className: 'SNP-indicator-track' }, block.domNode); var snpContext = snpCanvas.getContext('2d'); // finally query the various pixel ratios var ratio = Util.getResolution( snpContext, this.browser.config.highResolutionMode ); // upscale canvas if the two ratios don't match if ( this.browser.config.highResolutionMode !='disabled' && ratio!=1 ) { var oldWidth = snpCanvas.width; var oldHeight = snpCanvas.height; snpCanvas.width = oldWidth * ratio; snpCanvas.height = oldHeight * ratio; //c.style.width = oldWidth + 'px'; snpCanvas.style.height = oldHeight + 'px'; // now scale the context to counter // the fact that we've manually scaled // our canvas element snpContext.scale(ratio, ratio); } var negColor = this.config.style.neg_color; var clipColor = this.config.style.clip_marker_color; var bgColor = this.config.style.bg_color; var disableClipMarkers = this.config.disable_clip_markers; var drawRectangle = function(ID, yPos, height, fRect) { if( yPos <= canvasHeight ) { // if the rectangle is visible at all context.fillStyle = thisB.colorForBase(ID); if( yPos <= originY ) { // bar goes upward thisB._fillRectMod( context, fRect.l, yPos, fRect.w, height); if( !disableClipMarkers && yPos < 0 ) { // draw clip marker if necessary context.fillStyle = clipColor || negColor; thisB._fillRectMod( context, fRect.l, 0, fRect.w, 2 ); } } else { // bar goes downward thisB._fillRectMod( context, fRect.l, originY, fRect.w, height ); if( !disableClipMarkers && yPos >= canvasHeight ) { // draw clip marker if necessary context.fillStyle = clipColor || thisB.colorForBase(ID); thisB._fillRectMod( context, fRect.l, canvasHeight-3, fRect.w, 2 ); } } } }; // Note: 'reference' is done first to ensure the grey part of the graph is on top dojo.forEach( features, function(f,i) { var fRect = featureRects[i]; var score = f.get('score'); // draw the background color if we are configured to do so if( bgColor ) { context.fillStyle = bgColor; thisB._fillRectMod( context, fRect.l, 0, fRect.w, canvasHeight ); } drawRectangle( 'reference', toY( score.total() ), originY-toY( score.get('reference'))+1, fRect); }); var indicatorMinHeightProp = this.config.indicatorProp; var indicatorMinHeight = this.config.indicatorDepth; dojo.forEach( features, function(f,i) { var fRect = featureRects[i]; var score = f.get('score'); var totalHeight = score.total(); // draw indicators of SNPs if base coverage is greater than 50% of total coverage score.forEach( function( count, category ) { if ( !{reference:true,skip:true,deletion:true}[category] && count >= indicatorMinHeightProp*totalHeight && count >= indicatorMinHeight ) { snpContext.save(); if( thisB.browser.config.highResolutionMode != 'disabled' ) snpContext.scale(ratio, 1); snpContext.beginPath(); snpContext.arc( (fRect.l + 0.5*fRect.w), 0.40*snpCanvas.height/ratio, 0.20*snpCanvas.height/ratio, 1.75 * Math.PI, 1.25 * Math.PI, false); snpContext.lineTo(fRect.l + 0.5*fRect.w, 0); snpContext.closePath(); snpContext.fillStyle = thisB.colorForBase(category); snpContext.fill(); snpContext.lineWidth = 1; snpContext.strokeStyle = 'black'; snpContext.stroke(); if( thisB.browser.config.highResolutionMode != 'disabled' ) snpContext.restore(); } }); totalHeight -= score.get('reference'); score.forEach( function( count, category ) { if ( category != 'reference' ) { drawRectangle( category, toY(totalHeight), originY-toY( count )+1, fRect); totalHeight -= count; } }); }, this ); }, // Overwrites the method from WiggleBase _draw: function( scale, leftBase, rightBase, block, canvas, features, featureRects, dataScale, pixels, spans ) { // Note: pixels currently has no meaning, as the function that generates it is not yet defined for this track this._preDraw( scale, leftBase, rightBase, block, canvas, features, featureRects, dataScale ); this._drawFeatures( scale, leftBase, rightBase, block, canvas, features, featureRects, dataScale ); if ( spans ) { this._maskBySpans( scale, leftBase, canvas, spans ); } this._postDraw( scale, leftBase, rightBase, block, canvas, features, featureRects, dataScale ); }, /* If it's a boolean track, mask accordingly */ _maskBySpans: function( scale, leftBase, canvas, spans ) { var context = canvas.getContext('2d'); var canvasHeight = canvas.height; var booleanAlpha = this.config.style.masked_transparancy || 0.17; this.config.style.masked_transparancy = booleanAlpha; // make a temporary canvas to store image data var tempCan = dojo.create( 'canvas', {height: canvasHeight, width: canvas.width} ); var ctx2 = tempCan.getContext('2d'); for ( var index in spans ) { if (spans.hasOwnProperty(index)) { var w = Math.round(( spans[index].end - spans[index].start ) * scale ); var l = Math.round(( spans[index].start - leftBase ) * scale ); if (l+w >= canvas.width) w = canvas.width-l; // correct possible rounding errors if (w==0) continue; // skip if there's no width. ctx2.drawImage(canvas, l, 0, w, canvasHeight, l, 0, w, canvasHeight); context.globalAlpha = booleanAlpha; // clear masked region and redraw at lower opacity. context.clearRect(l, 0, w, canvasHeight); context.drawImage(tempCan, l, 0, w, canvasHeight, l, 0, w, canvasHeight); context.globalAlpha = 1; } } }, /* * The following method is required to override the equivalent method in "WiggleBase.js" * It displays more complete data. */ _showPixelValue: function( scoreDisplay, score ) { if( ! score || ! score.score ) return false; score = score.score; function fmtNum( num ) { return parseFloat( num ).toPrecision(6).replace(/0+$/,'').replace(/\.$/,''); } function pctString( count ) { count = Math.round(count/total*100); if( typeof count == 'number' && ! isNaN(count) ) return count+'%'; return ''; } if( score.snpsCounted ) { var total = score.total(); var scoreSummary = '<table>'; score.forEach( function( count, category ) { // if this count has more nested categories, do counts of those var subdistribution = ''; if( count.forEach ) { subdistribution = []; count.forEach( function( count, category ) { subdistribution.push( fmtNum(count) + ' '+category ); }); subdistribution = subdistribution.join(', '); if( subdistribution ) subdistribution = '('+subdistribution+')'; } category = { '*': 'del', reference: 'Ref', skip: 'Skip/intron' }[category] || category; scoreSummary += '<tr><td>'+category + '</td><td class="count">' + fmtNum(count) + '</td><td class="pct">' +pctString(count)+'</td><td class="subdist">'+subdistribution + '</td></tr>'; }); scoreSummary += '<tr class="total"><td>Total</td><td class="count">'+fmtNum(total)+'</td><td class="pct">&nbsp;</td><td class="subdist">&nbsp;</td></tr>'; scoreDisplay.innerHTML = scoreSummary+'</table>'; return true; } else { scoreDisplay.innerHTML = '<table><tr><td>Total</td><td class="count">'+fmtNum(score)+'</td></tr></table>'; return true; } }, _trackMenuOptions: function() { var thisB = this; var displayOptions = []; displayOptions.push({ label: 'View alignments', onClick: function(event) { thisB.config.type = 'JBrowse/View/Track/Alignments2' thisB.config._oldSnpCoverageHeight = thisB.config.style.height thisB.config.style.height = thisB.config._oldAlignmentsHeight thisB.browser.publish('/jbrowse/v1/v/tracks/replace', [thisB.config]); } }); return Promise.all([ this.inherited(arguments), this._alignmentsFilterTrackMenuOptions(), displayOptions ]) .then( function( options ) { var o = options.shift(); options.unshift({ type: 'dijit/MenuSeparator' } ); return o.concat.apply( o, options ); }); } }); });