@gmod/jbrowse
Version:
JBrowse - client-side genome browser
272 lines (251 loc) • 10.8 kB
JavaScript
define([
'dojo/_base/declare',
'JBrowse/View/FeatureGlyph/Segments',
],
function(
declare,
SegmentsGlyph,
) {
return declare( SegmentsGlyph, {
_defaultConfig: function() {
return this._mergeConfigs(
this.inherited(arguments),
{
style: {
connectorColor: '#333',
connectorThickness: 1,
borderColor: 'rgba( 0, 0, 0, 0.3 )'
},
itemRgb: true,
height: 11,
thinHeight: 5,
subParts: () => false, // UCSC BED-like features don't have formal subparts
subSubParts: () => false, // UCSC BED-like features don't have formal subparts
});
},
parseItemRgb(itemRgb) {
const stringEncoding = /(\d+),(\d+),(\d+)/.exec(itemRgb)
const hex2 = num => num.toString(16).padStart(2,'0')
if (stringEncoding) {
const r = Number(stringEncoding[1])
const g = Number(stringEncoding[2])
const b = Number(stringEncoding[3])
if (!isNaN(r) && !isNaN(g) && !isNaN(b) && (r || g || b))
return `
} else {
const rgb = Number(itemRgb)
if (rgb)
return `
}
},
renderSegments( context, fRect ) {
const styleFunc = (feature, stylename) => {
if (stylename === 'height')
return this._getFeatureHeight( fRect.viewInfo, feature )
else if (stylename === 'color' && this.getConf('itemRgb',[feature,this])) {
const itemRgb = this.parseItemRgb(feature.get('itemRgb') || feature.get('reserved'))
if (itemRgb)
return itemRgb
}
return this.getStyle(feature, stylename)
}
const thickStart = Number(fRect.f.get('thick_start'))
const thickEnd = Number(fRect.f.get('thick_end'))
const blockCount = Number(fRect.f.get('block_count'))
if (blockCount && (fRect.f.get('end')-fRect.f.get('start')) > 5) {
let sizes = fRect.f.get('block_sizes')
if (!Array.isArray(sizes)) sizes = sizes.split(',').map(str => Number(str))
let starts = fRect.f.get('chrom_starts')
if (!Array.isArray(starts)) starts = starts.split(',').map(str => Number(str))
const blocksOffset = fRect.f.get('start')
for (let b = 0; b < blockCount; b += 1) {
const blockStart = (starts[b]|0) + blocksOffset
const blockEnd = blockStart + (sizes[b]|0)
// render the sub-block, either as a rect, or as a stroked path
this.renderSegment(
context,
fRect.viewInfo,
blockStart,
blockEnd,
thickStart,
thickEnd,
fRect.t,
fRect.rect.h,
fRect.f,
styleFunc,
)
}
} else {
// render the whole thing as a single block
this.renderSegment(
context,
fRect.viewInfo,
fRect.f.get('start'),
fRect.f.get('end'),
thickStart,
thickEnd,
fRect.t,
fRect.rect.h,
fRect.f,
styleFunc,
)
}
},
renderSegment(context, viewInfo, start, end, thickStart, thickEnd, top, overallHeight, parentFeature, style) {
const left = viewInfo.block.bpToX(start)
const width = viewInfo.block.bpToX(end) - left
const right = left + width
const height = this._getFeatureHeight( viewInfo, parentFeature )
if (!height) return
if (height !== overallHeight) top += Math.round((overallHeight - height)/2)
const bottom = top + height
const thickStartPx = viewInfo.block.bpToX(thickStart)
const thickEndPx = viewInfo.block.bpToX(thickEnd)
const widthClamped = Math.max(1,width)
const thinHeight = this.getConf('thinHeight',[parentFeature,this])
const thinHeightDiff = (height-thinHeight)/2
const bgcolor = style( parentFeature, 'color' )
const borderColor = style( parentFeature, 'borderColor' )
const lineWidth = style( parentFeature, 'borderWidth')
const halfLineWidth = lineWidth/2
if (width > 3) {
let pathPoints
let strokePoints
if (thickStart <= start && thickEnd >= end) {
// ===========
pathPoints = [ left, top, width, height ]
strokePoints = [ left + halfLineWidth, top + halfLineWidth, width - lineWidth, height - lineWidth ]
} else if (thickStart >= end || thickEnd <= start) {
// -----------
pathPoints = [ left, top + thinHeightDiff, width, thinHeight ]
strokePoints = [ left + halfLineWidth, top + halfLineWidth + thinHeightDiff, width - lineWidth, thinHeight - lineWidth ]
} else if (thickStart <= start && thickEnd < end) {
// ====-------
pathPoints = [
[],
[],
[],
[],
[],
[],
[],
[],
]
strokePoints = [
[],
[],
[],
[],
[],
[],
[],
[],
]
} else if (thickStart > start && thickEnd >= end) {
// -----======
pathPoints = [
[],
[],
[],
[],
[],
[],
[],
[],
]
strokePoints = [
[],
[],
[],
[],
[],
[],
[],
[],
]
} else if (thickStart > start && thickEnd < end) {
// ----====---
pathPoints = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
]
strokePoints = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
]
}
// background
if( bgcolor ) {
context.fillStyle = bgcolor
if (pathPoints[0].length) {
context.beginPath()
context.moveTo(...pathPoints[0])
for (let i = 1; i < pathPoints.length; i += 1) {
context.lineTo(...pathPoints[i])
}
context.fill()
} else {
context.fillRect(...pathPoints)
}
}
// foreground border
if (borderColor && lineWidth) {
context.lineWidth = lineWidth;
context.strokeStyle = borderColor;
// need to stroke a smaller path to remain within
// the bounds of the feature's overall height and
// width, because of the way stroking is done in
// canvas. thus the +0.5 and -1 business.
//context.strokeRect( left + lineWidth / 2, top + lineWidth / 2, width - lineWidth, height - lineWidth );
if (strokePoints[0].length) {
context.beginPath()
context.moveTo(...strokePoints[0])
for (let i = 1; i < strokePoints.length; i += 1) {
context.lineTo(...strokePoints[i])
}
context.stroke()
} else {
context.strokeRect(...strokePoints)
}
}
} else {
// for very tiny features, just draw them as rectangles of blurry height, and shade them
context.globalAlpha = 1
context.fillStyle = bgcolor
if (thickStart <= start && thickEnd >= end) {
context.fillRect(left,top,widthClamped,height)
context.globalAlpha = lineWidth * 2 / width;
context.fillStyle = borderColor;
context.fillRect(left, top, widthClamped, height);
context.globalAlpha = 1;
} else {
context.fillRect(left,top+thinHeightDiff,widthClamped,thinHeight)
context.globalAlpha = lineWidth * 2 / width;
context.fillStyle = borderColor;
context.fillRect(left, top+thinHeightDiff, widthClamped, thinHeight);
context.globalAlpha = 1;
}
}
},
})
});