@gmod/jbrowse
Version:
JBrowse - client-side genome browser
197 lines (177 loc) • 7.71 kB
JavaScript
define([
'dojo/_base/declare',
'dojo/_base/array',
'dojo/_base/lang',
'JBrowse/View/FeatureGlyph/Alignment',
'JBrowse/View/FeatureGlyph/AlignmentColoring',
'JBrowse/Util'
],
function(
declare,
array,
lang,
Alignment,
AlignmentColoring,
Util
) {
return declare(Alignment, {
clearFeat(context, fRect) {
if( this.track.displayMode != 'collapsed' )
context.clearRect( Math.floor(fRect.l), fRect.t, Math.ceil(fRect.w), fRect.h );
},
renderFeature(context, fRect) {
const f = fRect.f
this.clearFeat(context, fRect)
if (f.pairedFeature()) {
this.renderConnector( context, fRect )
this.renderSegments( context, fRect )
if (fRect.w > 2) {
if (fRect.viewInfo.scale > 0.2) {
this._drawMismatches( context, fRect, this._getMismatches( f.read1 ), f.read1 )
this._drawMismatches( context, fRect, this._getMismatches( f.read2 ), f.read2 )
}
else {
this._drawMismatches( context, fRect, this._getSkipsAndDeletions( f.read1 ), f.read1 )
this._drawMismatches( context, fRect, this._getSkipsAndDeletions( f.read2 ), f.read2 )
}
}
const x1 = f.read1.get('start')
const x2 = f.read1.get('end')
const y1 = f.read2.get('start')
const y2 = f.read2.get('end')
if(Util.intersect(x1, x2, y1, y2)) {
const s1 = x2 > y2 ? x1 : y1
const s2 = x1 > y1 ? y2 : x2
const block = fRect.viewInfo.block
const l = block.bpToX(s1)
const r = block.bpToX(s2)
// avoid drawing small overlaps
if(r - l > 2) {
context.fillStyle = this.getStyle(f, 'overlapColor')
context.fillRect(
l, // left
fRect.rect.t,
r-l, // width
fRect.rect.h)
var s = this.getStyle(f, 'overlapStroke')
if(s) {
context.strokeStyle = s
context.strokeRect(
l, // left
fRect.rect.t,
r-l, // width
fRect.rect.h)
}
if(fRect.viewInfo.scale > 0.2) {
var m1 = this._getMismatches( f.read1 );
var m2 = this._getMismatches( f.read2 );
if(!m1 && !m2) {
return;
} else {
var mismatches = [];
for(var i = 0; i < m1.length; i++) {
let foundMatching = false;
for(var j = 0; j < m2.length; j++) {
if(x1+m1[i].start == y1+m2[j].start && s1 <= x1+m1[i].start && s2 >= x1+m1[i].start) {
foundMatching = true
mismatches.push({ start: m1[i].start, base1: m1[i].base, base2: m2[j].base, type: 'mismatch', length: 1 });
}
}
if(m1[i].type == 'mismatch' && s1 <= x1+m1[i].start && s2 >= x1+m1[i].start && !foundMatching ) {
mismatches.push({ start: m1[i].start, base1: m1[i].base, base2: '-', type: 'mismatch', length: 1 });
}
}
if(mismatches.length !== 0) {
this._drawOverlappingMismatches(context, fRect, mismatches, x1)
}
mismatches = [];
for(var i = 0; i < m2.length; i++) {
let foundMatching = false;
for(var j = 0; j < m1.length; j++) {
if(x1+m1[j].start == y1+m2[i].start && s1 <= y1+m2[i].start && s2 >= y1+m2[i].start) {
// mismatches.push({ start: m2[i].start, base1: m2[i].base, base2: m1[j].base, type: 'mismatch', length: 1 });
// would have been found in above loop also previous iteration
foundMatching = true
}
}
if(m2[i].type == 'mismatch' && s1 <= y1+m2[i].start && s2 >= y1+m2[i].start && !foundMatching) {
mismatches.push({ start: m2[i].start, base1: m2[i].base, base2: '-', type: 'mismatch', length: 1 });
}
}
if(mismatches.length !== 0) {
this._drawOverlappingMismatches(context, fRect, mismatches, y1)
}
}
}
}
}
} else {
this.inherited(arguments)
}
},
renderSegments(context, fRect) {
this.renderBox(context, fRect.viewInfo, fRect.f.read1, fRect.t, fRect.rect.h, fRect.f);
this.renderBox(context, fRect.viewInfo, fRect.f.read2, fRect.t, fRect.rect.h, fRect.f);
},
renderConnector(context, fRect) {
// connector
var connectorColor = this.getStyle( fRect.f, 'connectorColor' );
if (connectorColor) {
context.fillStyle = connectorColor;
var connectorThickness = this.getStyle( fRect.f, 'connectorThickness' );
context.fillRect(
fRect.rect.l, // left
Math.round(fRect.rect.t+(fRect.rect.h-connectorThickness)/2), // top
fRect.rect.w, // width
connectorThickness
);
}
},
// draw both gaps and mismatches
_drawOverlappingMismatches(context, fRect, mismatches, fstart) {
var block = fRect.viewInfo.block;
var scale = block.scale;
var charSize = this.getCharacterMeasurements( context );
context.textBaseline = 'middle'; // reset to alphabetic (the default) after loop
array.forEach( mismatches, function( mismatch ) {
var start = fstart + mismatch.start;
var end = fstart + mismatch.start + mismatch.length;
var mRect = {
h: (fRect.rect||{}).h || fRect.h,
l: block.bpToX( start ),
t: fRect.rect.t
};
mRect.w = Math.max( block.bpToX( end ) - mRect.l, 1 );
if( mismatch.type == 'mismatch' ) {
if(mismatch.base1 == mismatch.base2) {
context.fillStyle = this.track.colorForBase( mismatch.type == 'deletion' ? 'deletion' : mismatch.base1 );
} else {
context.fillStyle = 'black';
}
context.fillRect( mRect.l, mRect.t, mRect.w, mRect.h );
if( mRect.w >= charSize.w && mRect.h >= charSize.h-3 ) {
context.font = this.config.style.mismatchFont;
if(mismatch.base1 == mismatch.base2) {
context.fillStyle = 'black'
context.fillText( mismatch.base1, mRect.l+(mRect.w-charSize.w)/2+1, mRect.t+mRect.h/2 );
} else if(scale >= 10) {
context.fillStyle = 'white'
context.fillText( mismatch.base1 + '/' +mismatch.base2, mRect.l+(mRect.w-charSize.w*2)/2+1, mRect.t+mRect.h/2 );
}
}
}
},this);
context.textBaseline = 'alphabetic';
},
_defaultConfig() {
return this._mergeConfigs(dojo.clone( this.inherited(arguments) ), {
style: {
connectorColor: AlignmentColoring.connectorColor,
connectorThickness: 1,
overlapColor: 'lightgrey',
overlapStroke: 'grey'
}
});
}
});
});